Skip to content

Commit 406c414

Browse files
committed
NR: Improve load, save and OneDrive sync
1 parent f27d70c commit 406c414

File tree

6 files changed

+53
-45
lines changed

6 files changed

+53
-45
lines changed

src/NewsReader/NewsReader.Applications.Test/Services/MockDataService.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ namespace Test.NewsReader.Applications.Services;
44

55
public class MockDataService : IDataService
66
{
7-
public string GetHash() => throw new NotImplementedException();
7+
public string GetHash(Stream dataStream) => throw new NotImplementedException();
88

99
public Stream GetReadStream() => throw new NotImplementedException();
1010

11+
public Stream GetWriteStream() => throw new NotImplementedException();
12+
1113
public Task<T?> Load<T>(Stream? dataStream = null) where T : class => Task.FromResult<T?>(null);
1214

13-
public void Save(object data) => throw new NotImplementedException();
15+
public void Save(object data, Stream target) => throw new NotImplementedException();
1416
}

src/NewsReader/NewsReader.Applications/Controllers/AppController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public async void Start()
6060

6161
public void Save()
6262
{
63-
Task.Run(dataController.Save).GetAwaiter().GetResult(); // Task.Run needed to avoid dead-lock when Save uses await.
63+
dataController.Save().GetAwaiter().GetResult();
6464
}
6565

6666
public async void Update()

src/NewsReader/NewsReader.Applications/Controllers/DataController.cs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public async Task<FeedManager> Load()
4848
FeedManager? feedManager;
4949
try
5050
{
51-
feedManager = (await dataService.Load<FeedManager>()) ?? new FeedManager();
51+
using var stream = dataService.GetReadStream();
52+
feedManager = await dataService.Load<FeedManager>(stream).ConfigureAwait(false);
53+
feedManager ??= new();
5254
}
5355
catch (Exception ex)
5456
{
@@ -62,19 +64,25 @@ public async Task<FeedManager> Load()
6264

6365
public Task Update() => DownloadAndMerge();
6466

65-
public Task Save()
67+
public async Task Save()
6668
{
67-
if (!loadCompletion.Task.IsCompleted) return Task.CompletedTask;
68-
var feedManager = loadCompletion.Task.GetAwaiter().GetResult();
69+
if (!loadCompletion.Task.IsCompleted) return;
70+
var feedManager = await loadCompletion.Task.ConfigureAwait(false);
6971
try
7072
{
71-
dataService.Save(feedManager);
72-
return Upload();
73+
var memory = new MemoryStream();
74+
dataService.Save(feedManager, memory);
75+
using (var stream = dataService.GetWriteStream())
76+
{
77+
memory.Position = 0;
78+
await memory.CopyToAsync(stream).ConfigureAwait(false);
79+
}
80+
memory.Position = 0;
81+
await Upload(memory).ConfigureAwait(false);
7382
}
7483
catch (Exception ex)
7584
{
7685
Log.Default.Error(ex, "DataController.Save: Error");
77-
return Task.CompletedTask;
7886
}
7987
}
8088

@@ -100,10 +108,11 @@ private async Task DownloadAndMerge()
100108
FeedManager? feedManagerFromWeb = null;
101109
try
102110
{
103-
var (stream, cTag) = await webStorageService.DownloadFile(appSettings.WebStorageCTag);
104-
if (!string.IsNullOrEmpty(cTag))
111+
var download = await webStorageService.DownloadFile(appSettings.WebStorageCTag);
112+
using var stream = download.stream;
113+
if (stream is not null && !string.IsNullOrEmpty(download.cTag))
105114
{
106-
appSettings.WebStorageCTag = cTag;
115+
appSettings.WebStorageCTag = download.cTag;
107116
feedManagerFromWeb = await dataService.Load<FeedManager>(stream);
108117
}
109118
else
@@ -125,18 +134,23 @@ private async Task DownloadAndMerge()
125134
}
126135
}
127136

128-
private async Task Upload()
137+
private async Task Upload(Stream stream)
129138
{
130139
if (!isInSync || webStorageService.CurrentAccount == null || !networkInfoService.InternetAccess) return;
131140
try
132141
{
133-
var dataFileHash = dataService.GetHash();
142+
var dataFileHash = dataService.GetHash(stream);
134143
if (dataFileHash != appSettings.LastUploadedFileHash)
135144
{
136-
var cTag = await webStorageService.UploadFile(dataService.GetReadStream()).ConfigureAwait(false);
145+
stream.Position = 0;
146+
var cTag = await webStorageService.UploadFile(stream).ConfigureAwait(false);
137147
appSettings.WebStorageCTag = cTag;
138148
appSettings.LastUploadedFileHash = dataFileHash;
139149
}
150+
else
151+
{
152+
Log.Default.Info("DataController.Upload: Skip because it has the same hash.");
153+
}
140154
}
141155
catch (Exception ex)
142156
{

src/NewsReader/NewsReader.Applications/Services/IDataService.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ public interface IDataService
44
{
55
Stream GetReadStream();
66

7-
string GetHash();
7+
Stream GetWriteStream();
88

9-
Task<T?> Load<T>(Stream? dataStream = null) where T : class;
9+
string GetHash(Stream dataStream);
1010

11-
void Save(object data);
11+
Task<T?> Load<T>(Stream dataStream) where T : class;
12+
13+
void Save(object data, Stream target);
1214
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11

22
[assembly: SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "<Pending>", Scope = "type", Target = "~T:Waf.NewsReader.Presentation.Views.FeedItemView")]
3-
[assembly: SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.DataService.GetHash~System.String")]
43
[assembly: SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes", Scope = "type", Target = "~T:Waf.NewsReader.Presentation.Services.AppTraceTarget")]
54
[assembly: SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes", Scope = "type", Target = "~T:Waf.NewsReader.Presentation.Services.WebStorageService")]
65
[assembly: SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "<Pending>", Scope = "type", Target = "~T:Waf.NewsReader.Presentation.App")]
76
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.App.OnCreated")]
87
[assembly: SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "<Pending>", Scope = "type", Target = "~T:Waf.NewsReader.Presentation.Services.WebStorageService")]
98
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.DataService.Load``1(System.IO.Stream)~System.Threading.Tasks.Task{``0}")]
10-
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.DataService.Save(System.Object)")]
119
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.WebStorageService.DownloadFile(System.String)~System.Threading.Tasks.Task{System.ValueTuple{System.IO.Stream,System.String}}")]
1210
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.WebStorageService.UploadFile(System.IO.Stream)~System.Threading.Tasks.Task{System.String}")]
1311
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.App.OnDeactivated")]
1412
[assembly: SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.WebStorageService.UploadFile(System.IO.Stream)~System.Threading.Tasks.Task{System.String}")]
1513
[assembly: SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.WebStorageService.InitGraphClient~System.Threading.Tasks.Task")]
14+
[assembly: SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "<Pending>", Scope = "member", Target = "~M:Waf.NewsReader.Presentation.Services.DataService.GetHash(System.IO.Stream)~System.String")]

src/NewsReader/NewsReader.Presentation/Services/DataService.cs

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,22 @@ public class DataService : IDataService
1212

1313
public Stream GetReadStream() => File.OpenRead(containerFileName);
1414

15-
public string GetHash()
15+
public Stream GetWriteStream() => File.Create(containerFileName);
16+
17+
public string GetHash(Stream dataStream)
1618
{
17-
using var stream = GetReadStream();
1819
using var sha1 = SHA1.Create();
19-
return Convert.ToHexString(sha1.ComputeHash(stream));
20+
return Convert.ToHexString(sha1.ComputeHash(dataStream));
2021
}
2122

22-
public async Task<T?> Load<T>(Stream? dataStream = null) where T : class
23+
public async Task<T?> Load<T>(Stream dataStream) where T : class
2324
{
24-
Log.Default.Info("DataService.Load started. From {0}.", dataStream is null ? containerFileName : "stream");
25-
try
26-
{
27-
return await Task.Run(() => // Use background thread -> otherwise, a Android.OS.NetworkOnMainThreadException occurs for network streams.
28-
{
29-
using var archiveStream = dataStream ?? GetReadStream();
30-
var result = LoadItem<T>(archiveStream, itemFileName);
31-
Log.Default.Info("DataService.Load completed. From {0}.", dataStream is null ? containerFileName : "stream");
32-
return result;
33-
}).ConfigureAwait(false);
34-
}
35-
catch (FileNotFoundException)
36-
{
37-
return null;
38-
}
25+
Log.Default.Info("DataService.Load started.");
26+
using var memory = new MemoryStream();
27+
await dataStream.CopyToAsync(memory).ConfigureAwait(false);
28+
var result = LoadItem<T>(dataStream, itemFileName);
29+
Log.Default.Info("DataService.Load completed.");
30+
return result;
3931
}
4032

4133
private static T LoadItem<T>(Stream archiveStream, string fileName) where T : class
@@ -47,18 +39,17 @@ private static T LoadItem<T>(Stream archiveStream, string fileName) where T : cl
4739
return (T)(serializer.ReadObject(stream) ?? throw new InvalidOperationException($"Deserialize returned null."));
4840
}
4941

50-
public void Save(object data)
42+
public void Save(object data, Stream target)
5143
{
5244
ArgumentNullException.ThrowIfNull(data);
53-
Log.Default.Info("DataService.Save started. To {0}.", containerFileName);
54-
using var archiveStream = File.Create(containerFileName);
55-
using var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, leaveOpen: true);
45+
Log.Default.Info("DataService.Save started.");
46+
using var archive = new ZipArchive(target, ZipArchiveMode.Create, leaveOpen: true);
5647
var entry = archive.CreateEntry(itemFileName, CompressionLevel.Optimal);
5748
// Set always the same write time -> only content changes should result in a different hash.
5849
entry.LastWriteTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero);
5950
using var stream = entry.Open();
6051
var serializer = new DataContractSerializer(data.GetType());
6152
serializer.WriteObject(stream, data);
62-
Log.Default.Info("DataService.Save completed. To {0}.", containerFileName);
53+
Log.Default.Info("DataService.Save completed.");
6354
}
6455
}

0 commit comments

Comments
 (0)