From e7dac4699673a5ce0c76627e32ba9f41eccdc89b Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Tue, 6 Aug 2019 19:08:29 +1000 Subject: [PATCH] housekeeping: update to fix analyzer warnings/errors --- src/Akavache.Core/Akavache.Core.csproj | 4 +- src/Akavache.Core/BitmapImageMixin.cs | 30 +++++++++- .../BlobCache/InMemoryBlobCache.cs | 47 ++++++++------- src/Akavache.Core/BulkOperationsMixin.cs | 30 ++++++++++ .../Json/JsonSerializationMixin.cs | 59 ++++++++++++++++++- .../KeyedOperations/KeyedOperationQueue.cs | 2 + .../apple-common/MacFilesystemProvider.cs | 18 +++--- .../Platforms/shared/AkavacheHttpMixin.cs | 57 +++++++++++++----- .../Platforms/shared/Registrations.cs | 5 ++ .../Platforms/shared/StreamMixins.cs | 5 ++ .../Platforms/uap/WinRTFilesystemProvider.cs | 5 ++ src/Akavache.Core/RelativeTimeMixin.cs | 25 ++++++++ src/Akavache.Mobile/Registrations.cs | 5 ++ src/Akavache.Sqlite3/Queues/OperationQueue.cs | 8 +++ .../Queues/OperationQueueCoalescing.cs | 2 + src/Akavache.Sqlite3/Registrations.cs | 5 ++ .../SqlLiteCache/SQLiteEncryptedBlobCache.cs | 12 +++- .../SqlLiteCache/SqlRawPersistentBlobCache.cs | 53 +++++++++-------- src/Akavache.Tests/Akavache.Tests.csproj | 2 +- .../SqliteBlobCacheBulkOperationsTests.cs | 2 + .../SqliteBlobCacheDateTimeTests.cs | 2 + .../SqliteBlobCacheExtensionsTests.cs | 1 + .../SqliteBlobCacheInterfaceTests.cs | 2 + ...qliteBlobCacheObjectBulkOperationsTests.cs | 2 + .../TestBases/BlobCacheExtensionsTestBase.cs | 9 ++- src/analyzers.ruleset | 2 + 26 files changed, 317 insertions(+), 77 deletions(-) diff --git a/src/Akavache.Core/Akavache.Core.csproj b/src/Akavache.Core/Akavache.Core.csproj index 7e3256bd..65db0770 100644 --- a/src/Akavache.Core/Akavache.Core.csproj +++ b/src/Akavache.Core/Akavache.Core.csproj @@ -30,11 +30,11 @@ - + - + diff --git a/src/Akavache.Core/BitmapImageMixin.cs b/src/Akavache.Core/BitmapImageMixin.cs index 1bb751e7..48b39813 100644 --- a/src/Akavache.Core/BitmapImageMixin.cs +++ b/src/Akavache.Core/BitmapImageMixin.cs @@ -27,6 +27,11 @@ public static class BitmapImageMixin /// Observable is guaranteed to be returned on the UI thread. public static IObservable LoadImage(this IBlobCache blobCache, string key, float? desiredWidth = null, float? desiredHeight = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.Get(key) .SelectMany(ThrowOnBadImageBuffer) .SelectMany(x => BytesToImage(x, desiredWidth, desiredHeight)); @@ -47,6 +52,11 @@ public static IObservable LoadImage(this IBlobCache blobCache, string k /// Observable is guaranteed to be returned on the UI thread. public static IObservable LoadImageFromUrl(this IBlobCache blobCache, string url, bool fetchAlways = false, float? desiredWidth = null, float? desiredHeight = null, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(url, null, fetchAlways, absoluteExpiration) .SelectMany(ThrowOnBadImageBuffer) .SelectMany(x => BytesToImage(x, desiredWidth, desiredHeight)); @@ -67,6 +77,11 @@ public static IObservable LoadImageFromUrl(this IBlobCache blobCache, s /// Observable is guaranteed to be returned on the UI thread. public static IObservable LoadImageFromUrl(this IBlobCache blobCache, Uri url, bool fetchAlways = false, float? desiredWidth = null, float? desiredHeight = null, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(url, null, fetchAlways, absoluteExpiration) .SelectMany(ThrowOnBadImageBuffer) .SelectMany(x => BytesToImage(x, desiredWidth, desiredHeight)); @@ -88,6 +103,11 @@ public static IObservable LoadImageFromUrl(this IBlobCache blobCache, U /// Observable is guaranteed to be returned on the UI thread. public static IObservable LoadImageFromUrl(this IBlobCache blobCache, string key, string url, bool fetchAlways = false, float? desiredWidth = null, float? desiredHeight = null, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(key, url, null, fetchAlways, absoluteExpiration) .SelectMany(ThrowOnBadImageBuffer) .SelectMany(x => BytesToImage(x, desiredWidth, desiredHeight)); @@ -109,6 +129,11 @@ public static IObservable LoadImageFromUrl(this IBlobCache blobCache, s /// Observable is guaranteed to be returned on the UI thread. public static IObservable LoadImageFromUrl(this IBlobCache blobCache, string key, Uri url, bool fetchAlways = false, float? desiredWidth = null, float? desiredHeight = null, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(key, url, null, fetchAlways, absoluteExpiration) .SelectMany(ThrowOnBadImageBuffer) .SelectMany(x => BytesToImage(x, desiredWidth, desiredHeight)); @@ -129,7 +154,10 @@ public static IObservable ThrowOnBadImageBuffer(byte[] compressedImage) private static IObservable BytesToImage(byte[] compressedImage, float? desiredWidth, float? desiredHeight) { - return BitmapLoader.Current.Load(new MemoryStream(compressedImage), desiredWidth, desiredHeight).ToObservable(); + using (var ms = new MemoryStream(compressedImage)) + { + return BitmapLoader.Current.Load(ms, desiredWidth, desiredHeight).ToObservable(); + } } } } diff --git a/src/Akavache.Core/BlobCache/InMemoryBlobCache.cs b/src/Akavache.Core/BlobCache/InMemoryBlobCache.cs index 8a8f962b..82972df8 100644 --- a/src/Akavache.Core/BlobCache/InMemoryBlobCache.cs +++ b/src/Akavache.Core/BlobCache/InMemoryBlobCache.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reactive; @@ -24,6 +25,7 @@ namespace Akavache /// public class InMemoryBlobCache : ISecureBlobCache, IObjectBlobCache, IEnableLogger { + [SuppressMessage("Design", "CA2213: non-disposed field.", Justification = "Used for notification of dispose.")] private readonly AsyncSubject _shutdown = new AsyncSubject(); private readonly IDisposable _inner; private Dictionary _cache = new Dictionary(); @@ -441,12 +443,15 @@ private byte[] SerializeObject(T value) { var settings = Locator.Current.GetService() ?? new JsonSerializerSettings(); settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime. - var ms = new MemoryStream(); var serializer = JsonSerializer.Create(settings); - var writer = new BsonDataWriter(ms); - - serializer.Serialize(writer, new ObjectWrapper { Value = value }); - return ms.ToArray(); + using (var ms = new MemoryStream()) + { + using (var writer = new BsonDataWriter(ms)) + { + serializer.Serialize(writer, new ObjectWrapper { Value = value }); + return ms.ToArray(); + } + } } private T DeserializeObject(byte[] data) @@ -454,24 +459,26 @@ private T DeserializeObject(byte[] data) var settings = Locator.Current.GetService() ?? new JsonSerializerSettings(); settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime. var serializer = JsonSerializer.Create(settings); - var reader = new BsonDataReader(new MemoryStream(data)); - var forcedDateTimeKind = BlobCache.ForcedDateTimeKind; - - if (forcedDateTimeKind.HasValue) + using (var reader = new BsonDataReader(new MemoryStream(data))) { - reader.DateTimeKindHandling = forcedDateTimeKind.Value; - } + var forcedDateTimeKind = BlobCache.ForcedDateTimeKind; - try - { - return serializer.Deserialize>(reader).Value; - } - catch (Exception ex) - { - this.Log().Warn(ex, "Failed to deserialize data as boxed, we may be migrating from an old Akavache"); - } + if (forcedDateTimeKind.HasValue) + { + reader.DateTimeKindHandling = forcedDateTimeKind.Value; + } - return serializer.Deserialize(reader); + try + { + return serializer.Deserialize>(reader).Value; + } + catch (Exception ex) + { + this.Log().Warn(ex, "Failed to deserialize data as boxed, we may be migrating from an old Akavache"); + } + + return serializer.Deserialize(reader); + } } } } diff --git a/src/Akavache.Core/BulkOperationsMixin.cs b/src/Akavache.Core/BulkOperationsMixin.cs index 89aacb4b..e16b5ec8 100644 --- a/src/Akavache.Core/BulkOperationsMixin.cs +++ b/src/Akavache.Core/BulkOperationsMixin.cs @@ -23,6 +23,11 @@ public static class BulkOperationsMixin /// A observable with the specified values. public static IObservable> Get(this IBlobCache blobCache, IEnumerable keys) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IBulkBlobCache bulkCache) { return bulkCache.Get(keys); @@ -47,6 +52,11 @@ public static class BulkOperationsMixin /// A observable which signals when complete. public static IObservable Insert(this IBlobCache blobCache, IDictionary keyValuePairs, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IBulkBlobCache bulkCache) { return bulkCache.Insert(keyValuePairs, absoluteExpiration); @@ -66,6 +76,11 @@ public static IObservable Insert(this IBlobCache blobCache, IDictionaryA observable with the specified values. public static IObservable> GetCreatedAt(this IBlobCache blobCache, IEnumerable keys) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IBulkBlobCache bulkCache) { return bulkCache.GetCreatedAt(keys); @@ -84,6 +99,11 @@ public static IObservable Insert(this IBlobCache blobCache, IDictionaryA observable which signals when complete. public static IObservable Invalidate(this IBlobCache blobCache, IEnumerable keys) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IBulkBlobCache bulkCache) { return bulkCache.Invalidate(keys); @@ -128,6 +148,11 @@ public static IObservable Invalidate(this IBlobCache blobCache, IEnumerabl /// A observable which signals when complete. public static IObservable InsertObjects(this IBlobCache blobCache, IDictionary keyValuePairs, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBulkBlobCache bulkCache) { return bulkCache.InsertObjects(keyValuePairs, absoluteExpiration); @@ -147,6 +172,11 @@ public static IObservable InsertObjects(this IBlobCache blobCache, IDic /// A observable which signals when complete. public static IObservable InvalidateObjects(this IBlobCache blobCache, IEnumerable keys) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBulkBlobCache bulkCache) { return bulkCache.InvalidateObjects(keys); diff --git a/src/Akavache.Core/Json/JsonSerializationMixin.cs b/src/Akavache.Core/Json/JsonSerializationMixin.cs index 04605aae..db824a82 100644 --- a/src/Akavache.Core/Json/JsonSerializationMixin.cs +++ b/src/Akavache.Core/Json/JsonSerializationMixin.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reactive; @@ -37,6 +38,11 @@ public static class JsonSerializationMixin /// An observable which signals when the insertion has completed. public static IObservable InsertObject(this IBlobCache blobCache, string key, T value, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBlobCache objCache) { return objCache.InsertObject(key, value, absoluteExpiration); @@ -76,6 +82,11 @@ public static IObservable InsertAllObjects(this IBlobCache blobCache, I /// A Future result representing the object in the cache. public static IObservable GetObject(this IBlobCache blobCache, string key) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBlobCache objCache) { return objCache.GetObject(key); @@ -93,6 +104,11 @@ public static IObservable GetObject(this IBlobCache blobCache, string key) /// with the specified Type. public static IObservable> GetAllObjects(this IBlobCache blobCache) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBlobCache objCache) { return objCache.GetAllObjects(); @@ -133,7 +149,12 @@ public static IObservable> GetAllObjects(this IBlobCache blobC /// the cache. public static IObservable GetOrFetchObject(this IBlobCache blobCache, string key, Func> fetchFunc, DateTimeOffset? absoluteExpiration = null) { - return blobCache.GetObject(key).Catch(_ => + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + + return blobCache.GetObject(key).Catch(ex => { var prefixedKey = blobCache.GetHashCode().ToString(CultureInfo.InvariantCulture) + key; @@ -165,6 +186,11 @@ public static IObservable GetOrFetchObject(this IBlobCache blobCache, stri /// the cache. public static IObservable GetOrFetchObject(this IBlobCache blobCache, string key, Func> fetchFunc, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.GetOrFetchObject(key, () => fetchFunc().ToObservable(), absoluteExpiration); } @@ -186,6 +212,11 @@ public static IObservable GetOrFetchObject(this IBlobCache blobCache, stri /// the cache. public static IObservable GetOrCreateObject(this IBlobCache blobCache, string key, Func fetchFunc, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.GetOrFetchObject(key, () => Observable.Return(fetchFunc()), absoluteExpiration); } @@ -199,6 +230,11 @@ public static IObservable GetOrCreateObject(this IBlobCache blobCache, str /// The date the key was created on. public static IObservable GetObjectCreatedAt(this IBlobCache blobCache, string key) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBlobCache objCache) { return objCache.GetObjectCreatedAt(key); @@ -238,6 +274,7 @@ public static IObservable GetOrCreateObject(this IBlobCache blobCache, str /// if the fetched value should be cached. /// An Observable stream containing either one or two /// results (possibly a cached version, then the latest version). + [SuppressMessage("Design", "CA2000: call dispose", Justification = "Disposed by member")] public static IObservable GetAndFetchLatest( this IBlobCache blobCache, string key, @@ -247,6 +284,11 @@ public static IObservable GetOrCreateObject(this IBlobCache blobCache, str bool shouldInvalidateOnError = false, Func cacheValidationPredicate = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + var fetch = Observable.Defer(() => blobCache.GetObjectCreatedAt(key)) .Select(x => fetchPredicate == null || x == null || fetchPredicate(x.Value)) .Where(x => x) @@ -320,6 +362,11 @@ public static IObservable GetOrCreateObject(this IBlobCache blobCache, str bool shouldInvalidateOnError = false, Func cacheValidationPredicate = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.GetAndFetchLatest(key, () => fetchFunc().ToObservable(), fetchPredicate, absoluteExpiration, shouldInvalidateOnError, cacheValidationPredicate); } @@ -334,6 +381,11 @@ public static IObservable GetOrCreateObject(this IBlobCache blobCache, str /// An observable that signals when the operation has completed. public static IObservable InvalidateObject(this IBlobCache blobCache, string key) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBlobCache objCache) { return objCache.InvalidateObject(key); @@ -353,6 +405,11 @@ public static IObservable InvalidateObject(this IBlobCache blobCache, s /// this. public static IObservable InvalidateAllObjects(this IBlobCache blobCache) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + if (blobCache is IObjectBlobCache objCache) { return objCache.InvalidateAllObjects(); diff --git a/src/Akavache.Core/KeyedOperations/KeyedOperationQueue.cs b/src/Akavache.Core/KeyedOperations/KeyedOperationQueue.cs index be96d1c6..594af568 100644 --- a/src/Akavache.Core/KeyedOperations/KeyedOperationQueue.cs +++ b/src/Akavache.Core/KeyedOperations/KeyedOperationQueue.cs @@ -4,6 +4,7 @@ // See the LICENSE file in the project root for full license information. using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reactive; using System.Reactive.Concurrency; @@ -29,6 +30,7 @@ public class KeyedOperationQueue : IKeyedOperationQueue, IEnableLogger, IDisposa /// Initializes a new instance of the class. /// /// The scheduler for Observable operations. + [SuppressMessage("Design", "CA2000: call dispose", Justification = "Disposed by member")] public KeyedOperationQueue(IScheduler scheduler = null) { scheduler = scheduler ?? BlobCache.TaskpoolScheduler; diff --git a/src/Akavache.Core/Platforms/apple-common/MacFilesystemProvider.cs b/src/Akavache.Core/Platforms/apple-common/MacFilesystemProvider.cs index c05f08ad..5dd48b16 100644 --- a/src/Akavache.Core/Platforms/apple-common/MacFilesystemProvider.cs +++ b/src/Akavache.Core/Platforms/apple-common/MacFilesystemProvider.cs @@ -63,17 +63,17 @@ public string GetDefaultSecretCacheDirectory() private string CreateAppDirectory(NSSearchPathDirectory targetDir, string subDir = "BlobCache") { - NSError err; - - var fm = new NSFileManager(); - var url = fm.GetUrl(targetDir, NSSearchPathDomain.All, null, true, out err); - var ret = Path.Combine(url.RelativePath, BlobCache.ApplicationName, subDir); - if (!Directory.Exists(ret)) + using (var fm = new NSFileManager()) { - _inner.CreateRecursive(ret).Wait(); - } + var url = fm.GetUrl(targetDir, NSSearchPathDomain.All, null, true, out _); + var ret = Path.Combine(url.RelativePath, BlobCache.ApplicationName, subDir); + if (!Directory.Exists(ret)) + { + _inner.CreateRecursive(ret).Wait(); + } - return ret; + return ret; + } } } } diff --git a/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs b/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs index e1e9dda2..b9bc906c 100644 --- a/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs +++ b/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs @@ -36,6 +36,11 @@ public class AkavacheHttpMixin : IAkavacheHttpMixin /// The data downloaded from the URL. public IObservable DownloadUrl(IBlobCache blobCache, string url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(url, url, headers, fetchAlways, absoluteExpiration); } @@ -53,6 +58,16 @@ public IObservable DownloadUrl(IBlobCache blobCache, string url, IDictio /// The data downloaded from the URL. public IObservable DownloadUrl(IBlobCache blobCache, Uri url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + + if (url is null) + { + throw new ArgumentNullException(nameof(url)); + } + return blobCache.DownloadUrl(url.ToString(), url, headers, fetchAlways, absoluteExpiration); } @@ -71,6 +86,11 @@ public IObservable DownloadUrl(IBlobCache blobCache, Uri url, IDictionar /// The data downloaded from the URL. public IObservable DownloadUrl(IBlobCache blobCache, string key, string url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + var doFetch = MakeWebRequest(new Uri(url), headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); var fetchAndCache = doFetch.SelectMany(x => blobCache.Insert(key, x, absoluteExpiration).Select(_ => x)); @@ -104,6 +124,11 @@ public IObservable DownloadUrl(IBlobCache blobCache, string key, string /// The data downloaded from the URL. public IObservable DownloadUrl(IBlobCache blobCache, string key, Uri url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + var doFetch = MakeWebRequest(url, headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); var fetchAndCache = doFetch.SelectMany(x => blobCache.Insert(key, x, absoluteExpiration).Select(_ => x)); @@ -208,15 +233,17 @@ private IObservable ProcessWebResponse(WebResponse wr, string url, DateT return Observable.Throw(new WebException(hwr.StatusDescription)); } - var ms = new MemoryStream(); - using (var responseStream = hwr.GetResponseStream()) + using (var ms = new MemoryStream()) { - Debug.Assert(responseStream != null, "The response stream is somehow null: " + url + " with expiry: " + absoluteExpiration); - responseStream.CopyTo(ms); - } + using (var responseStream = hwr.GetResponseStream()) + { + Debug.Assert(responseStream != null, "The response stream is somehow null: " + url + " with expiry: " + absoluteExpiration); + responseStream.CopyTo(ms); + } - var ret = ms.ToArray(); - return Observable.Return(ret); + var ret = ms.ToArray(); + return Observable.Return(ret); + } } private IObservable ProcessWebResponse(WebResponse wr, Uri url, DateTimeOffset? absoluteExpiration) @@ -228,15 +255,17 @@ private IObservable ProcessWebResponse(WebResponse wr, Uri url, DateTime return Observable.Throw(new WebException(hwr.StatusDescription)); } - var ms = new MemoryStream(); - using (var responseStream = hwr.GetResponseStream()) + using (var ms = new MemoryStream()) { - Debug.Assert(responseStream != null, "The response stream is somehow null: " + url + " with expiry: " + absoluteExpiration); - responseStream.CopyTo(ms); - } + using (var responseStream = hwr.GetResponseStream()) + { + Debug.Assert(responseStream != null, "The response stream is somehow null: " + url + " with expiry: " + absoluteExpiration); + responseStream.CopyTo(ms); + } - var ret = ms.ToArray(); - return Observable.Return(ret); + var ret = ms.ToArray(); + return Observable.Return(ret); + } } } } diff --git a/src/Akavache.Core/Platforms/shared/Registrations.cs b/src/Akavache.Core/Platforms/shared/Registrations.cs index e2acdd16..3651d679 100644 --- a/src/Akavache.Core/Platforms/shared/Registrations.cs +++ b/src/Akavache.Core/Platforms/shared/Registrations.cs @@ -25,6 +25,11 @@ public class Registrations : IWantsToRegisterStuff /// public void Register(IMutableDependencyResolver resolver, IReadonlyDependencyResolver readonlyDependencyResolver) { + if (resolver is null) + { + throw new ArgumentNullException(nameof(resolver)); + } + #if XAMARIN_MOBILE var fs = new IsolatedStorageProvider(); #elif WINDOWS_UWP diff --git a/src/Akavache.Core/Platforms/shared/StreamMixins.cs b/src/Akavache.Core/Platforms/shared/StreamMixins.cs index 6e442f35..7620cf70 100644 --- a/src/Akavache.Core/Platforms/shared/StreamMixins.cs +++ b/src/Akavache.Core/Platforms/shared/StreamMixins.cs @@ -28,6 +28,11 @@ public static class StreamMixins /// An observable that signals when the write operation has completed. public static IObservable WriteAsyncRx(this Stream blobCache, byte[] data, int start, int length) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + #if WINDOWS_UWP return blobCache.WriteAsync(data, start, length).ToObservable(); #else diff --git a/src/Akavache.Core/Platforms/uap/WinRTFilesystemProvider.cs b/src/Akavache.Core/Platforms/uap/WinRTFilesystemProvider.cs index 1f557349..1b14ac01 100644 --- a/src/Akavache.Core/Platforms/uap/WinRTFilesystemProvider.cs +++ b/src/Akavache.Core/Platforms/uap/WinRTFilesystemProvider.cs @@ -48,6 +48,11 @@ public IObservable OpenFileForWriteAsync(string path, IScheduler schedul /// public IObservable CreateRecursive(string path) { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException(nameof(path)); + } + var paths = path.Split('\\'); var firstFolderThatExists = Observable.Range(0, paths.Length - 1) diff --git a/src/Akavache.Core/RelativeTimeMixin.cs b/src/Akavache.Core/RelativeTimeMixin.cs index 5315f33b..17b41885 100644 --- a/src/Akavache.Core/RelativeTimeMixin.cs +++ b/src/Akavache.Core/RelativeTimeMixin.cs @@ -25,6 +25,11 @@ public static class RelativeTimeMixin /// A observable which will signal when the item is added. public static IObservable Insert(this IBlobCache blobCache, string key, byte[] data, TimeSpan expiration) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.Insert(key, data, blobCache.Scheduler.Now + expiration); } @@ -39,6 +44,11 @@ public static IObservable Insert(this IBlobCache blobCache, string key, by /// A observable which will signal when the item is added. public static IObservable InsertObject(this IBlobCache blobCache, string key, T value, TimeSpan expiration) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.InsertObject(key, value, blobCache.Scheduler.Now + expiration); } @@ -53,6 +63,11 @@ public static IObservable InsertObject(this IBlobCache blobCache, strin /// A observable which will signal when the data is available. public static IObservable DownloadUrl(this IBlobCache blobCache, string url, TimeSpan expiration, Dictionary headers = null, bool fetchAlways = false) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(url, headers, fetchAlways, blobCache.Scheduler.Now + expiration); } @@ -67,6 +82,11 @@ public static IObservable DownloadUrl(this IBlobCache blobCache, string /// A observable which will signal when the data is available. public static IObservable DownloadUrl(this IBlobCache blobCache, Uri url, TimeSpan expiration, Dictionary headers = null, bool fetchAlways = false) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.DownloadUrl(url, headers, fetchAlways, blobCache.Scheduler.Now + expiration); } @@ -81,6 +101,11 @@ public static IObservable DownloadUrl(this IBlobCache blobCache, Uri url /// A observable which will signal when the item is added. public static IObservable SaveLogin(this ISecureBlobCache blobCache, string user, string password, string host, TimeSpan expiration) { + if (blobCache is null) + { + throw new ArgumentNullException(nameof(blobCache)); + } + return blobCache.SaveLogin(user, password, host, blobCache.Scheduler.Now + expiration); } } diff --git a/src/Akavache.Mobile/Registrations.cs b/src/Akavache.Mobile/Registrations.cs index b88f370e..aee582ea 100644 --- a/src/Akavache.Mobile/Registrations.cs +++ b/src/Akavache.Mobile/Registrations.cs @@ -22,6 +22,11 @@ public class Registrations : IWantsToRegisterStuff /// public void Register(IMutableDependencyResolver resolver, IReadonlyDependencyResolver readonlyDependencyResolver) { + if (resolver is null) + { + throw new ArgumentNullException(nameof(resolver)); + } + resolver.Register( () => new JsonSerializerSettings { diff --git a/src/Akavache.Sqlite3/Queues/OperationQueue.cs b/src/Akavache.Sqlite3/Queues/OperationQueue.cs index 07827805..c2b1aff3 100644 --- a/src/Akavache.Sqlite3/Queues/OperationQueue.cs +++ b/src/Akavache.Sqlite3/Queues/OperationQueue.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reactive; using System.Reactive.Concurrency; @@ -44,6 +45,7 @@ internal partial class SqliteOperationQueue : IEnableLogger, IDisposable private BlockingCollection _operationQueue = new BlockingCollection(); + [SuppressMessage("Design", "CA2213: dispose field", Justification = "Will be invalid")] private IDisposable _start; private CancellationTokenSource _shouldQuit; @@ -372,6 +374,10 @@ public void Dispose() { _commit.Value.Dispose(); } + + _operationQueue?.Dispose(); + _flushLock?.Dispose(); + _shouldQuit?.Dispose(); } internal List DumpQueue() @@ -400,6 +406,7 @@ private static void MarshalCompletion(object completion, Action block, IObservab } // NB: Callers must hold flushLock to call this + [SuppressMessage("Design", "CA2000: dispose variable", Justification = "Swapped ownership.")] private void FlushInternal() { var newQueue = new BlockingCollection(); @@ -408,6 +415,7 @@ private void FlushInternal() ProcessItems(CoalesceOperations(existingItems)); } + [SuppressMessage("Design", "CA2000: Dispose variable", Justification = "Ownership transferred.")] private void ProcessItems(List toProcess) { var commitResult = new AsyncSubject(); diff --git a/src/Akavache.Sqlite3/Queues/OperationQueueCoalescing.cs b/src/Akavache.Sqlite3/Queues/OperationQueueCoalescing.cs index 9fb55782..3b8997de 100644 --- a/src/Akavache.Sqlite3/Queues/OperationQueueCoalescing.cs +++ b/src/Akavache.Sqlite3/Queues/OperationQueueCoalescing.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reactive; using System.Reactive.Subjects; @@ -255,6 +256,7 @@ private static OperationQueueItem GroupUnrelatedInserts(IEnumerable unrelatedDeletes) { var subj = new AsyncSubject(); diff --git a/src/Akavache.Sqlite3/Registrations.cs b/src/Akavache.Sqlite3/Registrations.cs index ad2f7038..c072c456 100644 --- a/src/Akavache.Sqlite3/Registrations.cs +++ b/src/Akavache.Sqlite3/Registrations.cs @@ -31,6 +31,11 @@ public static void Start(string applicationName, Action initSql) /// public void Register(IMutableDependencyResolver resolver, IReadonlyDependencyResolver readonlyDependencyResolver) { + if (resolver is null) + { + throw new ArgumentNullException(nameof(resolver)); + } + // NB: We want the most recently registered fs, since there really // only should be one var fs = Locator.Current.GetService(); diff --git a/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs b/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs index 68d69905..810e654d 100644 --- a/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs +++ b/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs @@ -38,6 +38,11 @@ public SQLiteEncryptedBlobCache(string databaseFile, IEncryptionProvider encrypt /// protected override IObservable BeforeWriteToDiskFilter(byte[] data, IScheduler scheduler) { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + if (data.Length == 0) { return Observable.Return(data); @@ -49,6 +54,11 @@ protected override IObservable BeforeWriteToDiskFilter(byte[] data, ISch /// protected override IObservable AfterReadFromDiskFilter(byte[] data, IScheduler scheduler) { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + if (data.Length == 0) { return Observable.Return(data); @@ -57,4 +67,4 @@ protected override IObservable AfterReadFromDiskFilter(byte[] data, ISch return _encryption.DecryptBlock(data); } } -} \ No newline at end of file +} diff --git a/src/Akavache.Sqlite3/SqlLiteCache/SqlRawPersistentBlobCache.cs b/src/Akavache.Sqlite3/SqlLiteCache/SqlRawPersistentBlobCache.cs index bae276b5..584be13c 100644 --- a/src/Akavache.Sqlite3/SqlLiteCache/SqlRawPersistentBlobCache.cs +++ b/src/Akavache.Sqlite3/SqlLiteCache/SqlRawPersistentBlobCache.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -29,6 +30,7 @@ public class SqlRawPersistentBlobCache : IObjectBlobCache, IEnableLogger { private static readonly object DisposeGate = 42; private readonly IObservable _initializer; + [SuppressMessage("Design", "CA2213: Dispose field", Justification = "Used to indicate disposal.")] private readonly AsyncSubject _shutdown = new AsyncSubject(); private SqliteOperationQueue _opQueue; private IDisposable _queueThread; @@ -511,12 +513,15 @@ private byte[] SerializeObject(T value) { var settings = Locator.Current.GetService() ?? new JsonSerializerSettings(); settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime. - var ms = new MemoryStream(); var serializer = JsonSerializer.Create(settings); - var writer = new BsonDataWriter(ms); - - serializer.Serialize(writer, new ObjectWrapper { Value = value }); - return ms.ToArray(); + using (var ms = new MemoryStream()) + { + using (var writer = new BsonDataWriter(ms)) + { + serializer.Serialize(writer, new ObjectWrapper { Value = value }); + return ms.ToArray(); + } + } } private IObservable DeserializeObject(byte[] data) @@ -524,32 +529,34 @@ private IObservable DeserializeObject(byte[] data) var settings = Locator.Current.GetService() ?? new JsonSerializerSettings(); settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime. var serializer = JsonSerializer.Create(settings); - var reader = new BsonDataReader(new MemoryStream(data)); - var forcedDateTimeKind = BlobCache.ForcedDateTimeKind; - - if (forcedDateTimeKind.HasValue) + using (var reader = new BsonDataReader(new MemoryStream(data))) { - reader.DateTimeKindHandling = forcedDateTimeKind.Value; - } + var forcedDateTimeKind = BlobCache.ForcedDateTimeKind; + + if (forcedDateTimeKind.HasValue) + { + reader.DateTimeKindHandling = forcedDateTimeKind.Value; + } - try - { try { - var boxedVal = serializer.Deserialize>(reader).Value; - return Observable.Return(boxedVal); + try + { + var boxedVal = serializer.Deserialize>(reader).Value; + return Observable.Return(boxedVal); + } + catch (Exception ex) + { + this.Log().Warn(ex, "Failed to deserialize data as boxed, we may be migrating from an old Akavache"); + } + + var rawVal = serializer.Deserialize(reader); + return Observable.Return(rawVal); } catch (Exception ex) { - this.Log().Warn(ex, "Failed to deserialize data as boxed, we may be migrating from an old Akavache"); + return Observable.Throw(ex); } - - var rawVal = serializer.Deserialize(reader); - return Observable.Return(rawVal); - } - catch (Exception ex) - { - return Observable.Throw(ex); } } } diff --git a/src/Akavache.Tests/Akavache.Tests.csproj b/src/Akavache.Tests/Akavache.Tests.csproj index 58dad52d..617cc7e7 100644 --- a/src/Akavache.Tests/Akavache.Tests.csproj +++ b/src/Akavache.Tests/Akavache.Tests.csproj @@ -3,7 +3,7 @@ netcoreapp2.2 $(TargetFrameworks);net461 - $(NoWarn);CA1307 + $(NoWarn);CA1307;CA2000;CA1062 diff --git a/src/Akavache.Tests/SqliteBlobCacheBulkOperationsTests.cs b/src/Akavache.Tests/SqliteBlobCacheBulkOperationsTests.cs index 932b0a6b..2cb461e9 100644 --- a/src/Akavache.Tests/SqliteBlobCacheBulkOperationsTests.cs +++ b/src/Akavache.Tests/SqliteBlobCacheBulkOperationsTests.cs @@ -4,6 +4,8 @@ // See the LICENSE file in the project root for full license information. using System.IO; +using System.Reactive.Concurrency; + using Akavache.Sqlite3; namespace Akavache.Tests diff --git a/src/Akavache.Tests/SqliteBlobCacheDateTimeTests.cs b/src/Akavache.Tests/SqliteBlobCacheDateTimeTests.cs index 0b2a0b37..246f809b 100644 --- a/src/Akavache.Tests/SqliteBlobCacheDateTimeTests.cs +++ b/src/Akavache.Tests/SqliteBlobCacheDateTimeTests.cs @@ -4,6 +4,8 @@ // See the LICENSE file in the project root for full license information. using System.IO; +using System.Reactive.Concurrency; + using Akavache.Sqlite3; namespace Akavache.Tests diff --git a/src/Akavache.Tests/SqliteBlobCacheExtensionsTests.cs b/src/Akavache.Tests/SqliteBlobCacheExtensionsTests.cs index c89680e9..58d0a87a 100644 --- a/src/Akavache.Tests/SqliteBlobCacheExtensionsTests.cs +++ b/src/Akavache.Tests/SqliteBlobCacheExtensionsTests.cs @@ -5,6 +5,7 @@ using System; using System.IO; +using System.Reactive.Concurrency; using System.Reactive.Linq; using Akavache.Sqlite3; using Xunit; diff --git a/src/Akavache.Tests/SqliteBlobCacheInterfaceTests.cs b/src/Akavache.Tests/SqliteBlobCacheInterfaceTests.cs index f394b638..e685a807 100644 --- a/src/Akavache.Tests/SqliteBlobCacheInterfaceTests.cs +++ b/src/Akavache.Tests/SqliteBlobCacheInterfaceTests.cs @@ -4,6 +4,8 @@ // See the LICENSE file in the project root for full license information. using System.IO; +using System.Reactive.Concurrency; + using Akavache.Sqlite3; namespace Akavache.Tests diff --git a/src/Akavache.Tests/SqliteBlobCacheObjectBulkOperationsTests.cs b/src/Akavache.Tests/SqliteBlobCacheObjectBulkOperationsTests.cs index 6d4d3ac2..2bcea5e9 100644 --- a/src/Akavache.Tests/SqliteBlobCacheObjectBulkOperationsTests.cs +++ b/src/Akavache.Tests/SqliteBlobCacheObjectBulkOperationsTests.cs @@ -4,6 +4,8 @@ // See the LICENSE file in the project root for full license information. using System.IO; +using System.Reactive.Concurrency; + using Akavache.Sqlite3; namespace Akavache.Tests diff --git a/src/Akavache.Tests/TestBases/BlobCacheExtensionsTestBase.cs b/src/Akavache.Tests/TestBases/BlobCacheExtensionsTestBase.cs index acc0bb2b..4cd3ed70 100644 --- a/src/Akavache.Tests/TestBases/BlobCacheExtensionsTestBase.cs +++ b/src/Akavache.Tests/TestBases/BlobCacheExtensionsTestBase.cs @@ -218,18 +218,17 @@ public async Task FetchFunctionShouldBeCalledOnceForGetOrFetchObject() return Observable.Return(new Tuple("Foo", "Bar")); }); - string path; - using (Utility.WithEmptyDirectory(out path)) + using (Utility.WithEmptyDirectory(out string path)) { using (var fixture = CreateBlobCache(path)) { - var result = await fixture.GetOrFetchObject("Test", fetcher).FirstAsync(); + var result = await fixture.GetOrFetchObject("Test", fetcher).ObserveOn(ImmediateScheduler.Instance).FirstAsync(); Assert.Equal("Foo", result.Item1); Assert.Equal("Bar", result.Item2); Assert.Equal(1, fetchCount); // 2nd time around, we should be grabbing from cache - result = await fixture.GetOrFetchObject("Test", fetcher).FirstAsync(); + result = await fixture.GetOrFetchObject("Test", fetcher).ObserveOn(ImmediateScheduler.Instance).FirstAsync(); Assert.Equal("Foo", result.Item1); Assert.Equal("Bar", result.Item2); Assert.Equal(1, fetchCount); @@ -243,7 +242,7 @@ public async Task FetchFunctionShouldBeCalledOnceForGetOrFetchObject() using (var fixture = CreateBlobCache(path)) { - var result = await fixture.GetOrFetchObject("Test", fetcher).FirstAsync(); + var result = await fixture.GetOrFetchObject("Test", fetcher).ObserveOn(ImmediateScheduler.Instance).FirstAsync(); Assert.Equal("Foo", result.Item1); Assert.Equal("Bar", result.Item2); Assert.Equal(1, fetchCount); diff --git a/src/analyzers.ruleset b/src/analyzers.ruleset index 4fe3001f..38ae5ab5 100644 --- a/src/analyzers.ruleset +++ b/src/analyzers.ruleset @@ -14,6 +14,7 @@ + @@ -21,6 +22,7 @@ +