From da438630f9273aba45881a2522fe07bd11def938 Mon Sep 17 00:00:00 2001 From: Glenn <5834289+glennawatson@users.noreply.github.com> Date: Sun, 26 Jun 2022 01:00:02 +1000 Subject: [PATCH] feature: Add maui and use HttpClient (#788) --- README.md | 25 +- packages/repositories.config | 19 -- src/Akavache.Core/Akavache.Core.csproj | 35 ++- src/Akavache.Core/BlobCache/BlobCache.cs.orig | 230 +++++++++++++++ src/Akavache.Core/DependencyResolverMixin.cs | 2 +- src/Akavache.Core/HttpMixinExtensions.cs | 66 +++++ .../IAkavacheHttpClientFactory.cs | 19 ++ src/Akavache.Core/IAkavacheHttpMixin.cs | 52 +++- .../Json/JsonSerializationMixin.cs | 2 +- .../Platforms/shared/AkavacheHttpMixin.cs | 263 ++++++++---------- .../DefaultAkavacheHttpClientFactory.cs | 19 ++ .../Platforms/shared/Registrations.cs | 1 + src/Akavache.Drawing/Akavache.Drawing.csproj | 10 +- src/Akavache.Drawing/BitmapImageMixin.cs | 2 +- src/Akavache.Mobile/Akavache.Mobile.csproj | 6 +- src/Akavache.Sqlite3/Akavache.Sqlite3.csproj | 6 +- src/Akavache.Sqlite3/Registrations.cs | 4 +- src/Akavache.Sqlite3/SQLite.cs | 2 +- .../SqlLiteCache/SQLiteEncryptedBlobCache.cs | 4 +- ...ovalTests.AkavacheCore.net6.0.approved.txt | 21 ++ src/Akavache.Tests/API/ApiApprovalTests.cs | 2 +- src/Akavache.Tests/Akavache.Tests.csproj | 3 +- .../Helpers/IntegrationTestHelper.cs | 2 +- src/Akavache.Tests/Performance/ReadTests.cs | 5 +- src/Akavache.Tests/Performance/WriteTests.cs | 5 +- src/Akavache.Tests/UtilityTests.cs | 4 +- src/Akavache/Akavache.csproj | 6 +- src/Akavache/LinkerPreserve.cs | 3 +- src/Directory.build.props | 1 + src/Directory.build.targets | 14 +- 30 files changed, 615 insertions(+), 218 deletions(-) delete mode 100644 packages/repositories.config create mode 100644 src/Akavache.Core/BlobCache/BlobCache.cs.orig create mode 100644 src/Akavache.Core/IAkavacheHttpClientFactory.cs create mode 100644 src/Akavache.Core/Platforms/shared/DefaultAkavacheHttpClientFactory.cs diff --git a/README.md b/README.md index 7ae71a41b..6eb8c2158 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ settings) as well as cached local data that expires. Akavache is currently compatible with: -* Xamarin.iOS / Xamarin.Mac -* Xamarin.Android -* .NET 4.5 Desktop (WPF) -* Windows Phone 8.1 Universal Apps +* Xamarin.iOS / Xamarin.Mac / Xamarin.Android / Xamarin.TVOS / Xamarin.WatchOS +* Maui iOS / Mac / Mac Catalyst / Android / TVOS +* .NET 4.6.2 (and above) and .NET 6 Desktop (WPF and WinForms) +* .NET 6.0 * Windows 10 (Universal Windows Platform) * Tizen 4.0 @@ -94,27 +94,14 @@ There are four built-in locations that have some magic applied on some systems: ### Platform-specific notes -* **Xamarin.iOS / Xamarin.Mac** - No issues. - -* **Xamarin.Android** - No issues. - -* **.NET 4.5 Desktop (WPF)** - No issues. - -* **Windows Phone 8.1 Universal Apps** - You must mark your application as `x86` - or `ARM`, or else you will get a strange runtime error about SQLitePCL_Raw not - loading correctly. You must *also* ensure that the Microsoft Visual C++ runtime - is added to your project. - * **Windows 10 (Universal Windows Platform)** - You must mark your application as `x86` or `ARM`, or else you will get a strange runtime error about SQLitePCL_Raw not loading correctly. You must *also* ensure that the Microsoft Visual C++ runtime is added to your project. -* **Tizen 4.0** - No issues. - -#### Handling Xamarin Linker +#### Handling Xamarin/Maui Linker -There are two options to ensure the Akavache.Sqlite3 dll will not be removed by Xamarin build tools +There are two options to ensure the Akavache.Sqlite3 dll will not be removed by Xamarin and Maui build tools #### 1) Add a file to reference the types diff --git a/packages/repositories.config b/packages/repositories.config deleted file mode 100644 index 780b7c045..000000000 --- a/packages/repositories.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/Akavache.Core/Akavache.Core.csproj b/src/Akavache.Core/Akavache.Core.csproj index 4d81ae46e..1032d694a 100644 --- a/src/Akavache.Core/Akavache.Core.csproj +++ b/src/Akavache.Core/Akavache.Core.csproj @@ -1,13 +1,12 @@  - netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid11.0;tizen40;net6.0 + netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid11.0;tizen40;net6.0;net6.0-android;net6.0-ios;net6.0-tvos;net6.0-macos;net6.0-maccatalyst $(TargetFrameworks);net462;uap10.0.16299;net6.0-windows Akavache.Core Akavache An asynchronous, persistent key-value store for desktop and mobile applications on .NET akavache.core latest - enable @@ -17,6 +16,10 @@ + + + + @@ -41,27 +44,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Akavache.Core/BlobCache/BlobCache.cs.orig b/src/Akavache.Core/BlobCache/BlobCache.cs.orig new file mode 100644 index 000000000..6e888ba60 --- /dev/null +++ b/src/Akavache.Core/BlobCache/BlobCache.cs.orig @@ -0,0 +1,230 @@ +// Copyright (c) 2022 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Threading.Tasks; + +using Newtonsoft.Json.Bson; + +using Splat; + +namespace Akavache; + +/// +/// A class which represents a blobbed cache. +/// +public static class BlobCache +{ + private static string? _applicationName; + private static IBlobCache? _localMachine; + private static IBlobCache? _userAccount; + private static ISecureBlobCache? _secure; + private static bool _shutdownRequested; + + private static IScheduler? _taskPoolOverride; + + [ThreadStatic] + private static IBlobCache? _unitTestLocalMachine; + + [ThreadStatic] + private static IBlobCache? _unitTestUserAccount; + + [ThreadStatic] + private static ISecureBlobCache? _unitTestSecure; + + static BlobCache() + { + Locator.RegisterResolverCallbackChanged(() => + { + if (Locator.CurrentMutable is null) + { + return; + } + + Locator.CurrentMutable.InitializeAkavache(Locator.Current); + }); + + InMemory = new InMemoryBlobCache(Scheduler.Default); + } + + /// + /// Gets or sets your application's name. Set this at startup, this defines where + /// your data will be stored (usually at %AppData%\[ApplicationName]). + /// + [SuppressMessage("Design", "CA1065: Properties should not fire exceptions.", Justification = "Extreme non standard case.")] + public static string ApplicationName + { +<<<<<<< HEAD + get + { + if (_applicationName is null) + { + throw new InvalidOperationException("Make sure to set BlobCache.ApplicationName on startup"); + } + + return _applicationName; + } +======= + get => _applicationName ?? throw new("Make sure to set BlobCache.ApplicationName on startup"); +>>>>>>> main + + set => _applicationName = value; + } + + /// + /// Gets or sets the local machine cache. Store data here that is unrelated to the + /// user account or shouldn't be uploaded to other machines (i.e. + /// image cache data). + /// + public static IBlobCache LocalMachine + { + get => _unitTestLocalMachine ?? _localMachine ?? (_shutdownRequested ? new ShutdownBlobCache() : null) ?? Locator.Current.GetService("LocalMachine") ?? throw new InvalidOperationException("Unable to resolve LocalMachine cache. Make sure Akavache is initialized properly."); + set + { + if (ModeDetector.InUnitTestRunner()) + { + _unitTestLocalMachine = value; + _localMachine ??= value; + } + else + { + _localMachine = value; + } + } + } + + /// + /// Gets or sets the user account cache. Store data here that is associated with + /// the user; in large organizations, this data will be synced to all + /// machines via NT Roaming Profiles. + /// + public static IBlobCache UserAccount + { + get => _unitTestUserAccount ?? _userAccount ?? (_shutdownRequested ? new ShutdownBlobCache() : null) ?? Locator.Current.GetService("UserAccount") ?? throw new InvalidOperationException("Unable to resolve UserAccount cache. Make sure Akavache is initialized properly."); + set + { + if (ModeDetector.InUnitTestRunner()) + { + _unitTestUserAccount = value; + _userAccount ??= value; + } + else + { + _userAccount = value; + } + } + } + + /// + /// Gets or sets an IBlobCache that is encrypted - store sensitive data in this + /// cache such as login information. + /// + public static ISecureBlobCache Secure + { + get => _unitTestSecure ?? _secure ?? (_shutdownRequested ? new ShutdownBlobCache() : null) ?? Locator.Current.GetService() ?? throw new InvalidOperationException("Unable to resolve Secure cache. Make sure Akavache is initialized properly."); + set + { + if (ModeDetector.InUnitTestRunner()) + { + _unitTestSecure = value; + _secure ??= value; + } + else + { + _secure = value; + } + } + } + + /// + /// Gets or sets an IBlobCache that simply stores data in memory. Data stored in + /// this cache will be lost when the application restarts. + /// + public static ISecureBlobCache InMemory { get; set; } + + /// + /// Gets or sets the DateTimeKind handling for BSON readers to be forced. + /// + /// + /// + /// By default, uses a of and + /// uses . Thus, DateTimes are serialized as UTC but deserialized as local time. To force BSON readers to + /// use some other DateTimeKind, you can set this value. + /// + /// + public static DateTimeKind? ForcedDateTimeKind { get; set; } + + /// + /// Gets or sets the Scheduler used for task pools. + /// + public static IScheduler TaskpoolScheduler + { + get => _taskPoolOverride ?? Locator.Current.GetService("Taskpool") ?? TaskPoolScheduler.Default; + set => _taskPoolOverride = value; + } + + /// + /// Makes sure that the system has been initialized. + /// + public static void EnsureInitialized() => + + // NB: This method doesn't actually do anything, it just ensures + // that the static constructor runs + LogHost.Default.Debug("Initializing Akavache"); + + /// + /// This method shuts down all of the blob caches. Make sure call it + /// on app exit and await / Wait() on it. + /// + /// A Task representing when all caches have finished shutting + /// down. + public static Task Shutdown() + { + _shutdownRequested = true; + var toDispose = new[] { LocalMachine, UserAccount, Secure, InMemory, }; + + var ret = toDispose.Select(x => + { + x.Dispose(); + return x.Shutdown; + }).Merge().ToList().Select(_ => Unit.Default); + + return ret.ToTask(); + } + + private class ShutdownBlobCache : ISecureBlobCache + { + IObservable IBlobCache.Shutdown => Observable.Return(Unit.Default); + + public IScheduler Scheduler => System.Reactive.Concurrency.Scheduler.Immediate; + + /// + public DateTimeKind? ForcedDateTimeKind + { + get => null; + set { } + } + + public void Dispose() + { + } + + public IObservable Insert(string key, byte[] data, DateTimeOffset? absoluteExpiration = null) => Observable.Empty(); + + public IObservable Get(string key) => Observable.Empty(); + + public IObservable> GetAllKeys() => Observable.Empty>(); + + public IObservable GetCreatedAt(string key) => Observable.Empty(); + + public IObservable Flush() => Observable.Empty(); + + public IObservable Invalidate(string key) => Observable.Empty(); + + public IObservable InvalidateAll() => Observable.Empty(); + + public IObservable Vacuum() => Observable.Empty(); + } +} diff --git a/src/Akavache.Core/DependencyResolverMixin.cs b/src/Akavache.Core/DependencyResolverMixin.cs index e6e1fbe8f..c4ee225cf 100644 --- a/src/Akavache.Core/DependencyResolverMixin.cs +++ b/src/Akavache.Core/DependencyResolverMixin.cs @@ -37,7 +37,7 @@ public static void InitializeAkavache(this IMutableDependencyResolver resolver, if (fdr?.AssemblyQualifiedName is null) { - throw new($"Cannot find valid assembly name for the {nameof(DependencyResolverMixin)} class."); + throw new InvalidOperationException($"Cannot find valid assembly name for the {nameof(DependencyResolverMixin)} class."); } var assemblyName = new AssemblyName( diff --git a/src/Akavache.Core/HttpMixinExtensions.cs b/src/Akavache.Core/HttpMixinExtensions.cs index c4854e5bf..17a768a50 100644 --- a/src/Akavache.Core/HttpMixinExtensions.cs +++ b/src/Akavache.Core/HttpMixinExtensions.cs @@ -75,4 +75,70 @@ public static IObservable DownloadUrl(this IBlobCache blobCache, string /// The data downloaded from the URL. public static IObservable DownloadUrl(this IBlobCache blobCache, string key, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) => HttpMixin.DownloadUrl(blobCache, key, url, headers, fetchAlways, absoluteExpiration); + + /// + /// Download data from an HTTP URL and insert the result into the + /// cache. If the data is already in the cache, this returns + /// a cached value. The URL itself is used as the key. + /// + /// The blob cache to perform the operation on. + /// The type of HTTP Method. + /// The URL to download. + /// An optional Dictionary containing the HTTP + /// request headers. + /// Force a web request to always be issued, skipping the cache. + /// An optional expiration date. + /// The data downloaded from the URL. + public static IObservable DownloadUrl(this IBlobCache blobCache, HttpMethod method, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) => + HttpMixin.DownloadUrl(blobCache, method, new Uri(url), headers, fetchAlways, absoluteExpiration); + + /// + /// Download data from an HTTP URL and insert the result into the + /// cache. If the data is already in the cache, this returns + /// a cached value. The URL itself is used as the key. + /// + /// The blob cache to perform the operation on. + /// The type of HTTP Method. + /// The URL to download. + /// An optional Dictionary containing the HTTP + /// request headers. + /// Force a web request to always be issued, skipping the cache. + /// An optional expiration date. + /// The data downloaded from the URL. + public static IObservable DownloadUrl(this IBlobCache blobCache, HttpMethod method, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) => + HttpMixin.DownloadUrl(blobCache, method, url, headers, fetchAlways, absoluteExpiration); + + /// + /// Download data from an HTTP URL and insert the result into the + /// cache. If the data is already in the cache, this returns + /// a cached value. An explicit key is provided rather than the URL itself. + /// + /// The blob cache to perform the operation on. + /// The type of HTTP Method. + /// The key to store with. + /// The URL to download. + /// An optional Dictionary containing the HTTP + /// request headers. + /// Force a web request to always be issued, skipping the cache. + /// An optional expiration date. + /// The data downloaded from the URL. + public static IObservable DownloadUrl(this IBlobCache blobCache, HttpMethod method, string key, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) => + HttpMixin.DownloadUrl(blobCache, method, key, new Uri(url), headers, fetchAlways, absoluteExpiration); + + /// + /// Download data from an HTTP URL and insert the result into the + /// cache. If the data is already in the cache, this returns + /// a cached value. An explicit key is provided rather than the URL itself. + /// + /// The blob cache to perform the operation on. + /// The type of HTTP Method. + /// The key to store with. + /// The URL to download. + /// An optional Dictionary containing the HTTP + /// request headers. + /// Force a web request to always be issued, skipping the cache. + /// An optional expiration date. + /// The data downloaded from the URL. + public static IObservable DownloadUrl(this IBlobCache blobCache, HttpMethod method, string key, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) => + HttpMixin.DownloadUrl(blobCache, method, key, url, headers, fetchAlways, absoluteExpiration); } diff --git a/src/Akavache.Core/IAkavacheHttpClientFactory.cs b/src/Akavache.Core/IAkavacheHttpClientFactory.cs new file mode 100644 index 000000000..a0492963c --- /dev/null +++ b/src/Akavache.Core/IAkavacheHttpClientFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2022 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Akavache; + +/// +/// A factory abstraction for a component that can create instances with custom configuration for a given logical name. +/// +public interface IAkavacheHttpClientFactory +{ + /// + /// Creates and configures an instance using the configuration that corresponds to the logical name specified by name. + /// + /// The logical name of the client to create. + /// A new instance. + HttpClient CreateClient(string name); +} diff --git a/src/Akavache.Core/IAkavacheHttpMixin.cs b/src/Akavache.Core/IAkavacheHttpMixin.cs index eb3fdb8c2..887c98ee2 100644 --- a/src/Akavache.Core/IAkavacheHttpMixin.cs +++ b/src/Akavache.Core/IAkavacheHttpMixin.cs @@ -21,6 +21,18 @@ public interface IAkavacheHttpMixin /// A observable that signals when there is byte data. IObservable DownloadUrl(IBlobCache blobCache, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); + /// + /// Gets a observable for a download. + /// + /// The blob cache where to get the value from if available. + /// The type of method. + /// The url where to get the resource if not available in the cache. + /// The headers to use in the HTTP action. + /// If we should just fetch and not bother checking the cache first. + /// A optional expiration date time. + /// A observable that signals when there is byte data. + IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); + /// /// Gets a observable for a download. /// @@ -32,6 +44,18 @@ public interface IAkavacheHttpMixin /// A observable that signals when there is byte data. IObservable DownloadUrl(IBlobCache blobCache, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); + /// + /// Gets a observable for a download. + /// + /// The blob cache where to get the value from if available. + /// The type of method. + /// The url where to get the resource if not available in the cache. + /// The headers to use in the HTTP action. + /// If we should just fetch and not bother checking the cache first. + /// A optional expiration date time. + /// A observable that signals when there is byte data. + IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); + /// /// Gets a observable for a download. /// @@ -44,6 +68,19 @@ public interface IAkavacheHttpMixin /// A observable that signals when there is byte data. IObservable DownloadUrl(IBlobCache blobCache, string key, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); + /// + /// Gets a observable for a download. + /// + /// The blob cache where to get the value from if available. + /// The type of method. + /// The key to use for the download cache entry. + /// The url where to get the resource if not available in the cache. + /// The headers to use in the HTTP action. + /// If we should just fetch and not bother checking the cache first. + /// A optional expiration date time. + /// A observable that signals when there is byte data. + IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, string key, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); + /// /// Gets a observable for a download. /// @@ -55,4 +92,17 @@ public interface IAkavacheHttpMixin /// A optional expiration date time. /// A observable that signals when there is byte data. IObservable DownloadUrl(IBlobCache blobCache, string key, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); -} \ No newline at end of file + + /// + /// Gets a observable for a download. + /// + /// The blob cache where to get the value from if available. + /// The type of method. + /// The key to use for the download cache entry. + /// The url where to get the resource if not available in the cache. + /// The headers to use in the HTTP action. + /// If we should just fetch and not bother checking the cache first. + /// A optional expiration date time. + /// A observable that signals when there is byte data. + IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, string key, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null); +} diff --git a/src/Akavache.Core/Json/JsonSerializationMixin.cs b/src/Akavache.Core/Json/JsonSerializationMixin.cs index 1c839aa72..5f3b6df86 100644 --- a/src/Akavache.Core/Json/JsonSerializationMixin.cs +++ b/src/Akavache.Core/Json/JsonSerializationMixin.cs @@ -277,7 +277,7 @@ public static IObservable> GetAllObjects(this IBlobCache blobC if (fetch is null) { - return Observable.Throw(new("Could not find a valid way to fetch the value")); + return Observable.Throw(new InvalidOperationException("Could not find a valid way to fetch the value")); } var result = blobCache.GetObject(key).Select(x => (x, true)) diff --git a/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs b/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs index 1384fdab5..6bca52d68 100644 --- a/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs +++ b/src/Akavache.Core/Platforms/shared/AkavacheHttpMixin.cs @@ -3,8 +3,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -using System.Net; - using Splat; namespace Akavache; @@ -14,18 +12,9 @@ namespace Akavache; /// public class AkavacheHttpMixin : IAkavacheHttpMixin { - /// - /// Download data from an HTTP URL and insert the result into the - /// cache. If the data is already in the cache, this returns - /// a cached value. The URL itself is used as the key. - /// - /// The blob cache associated with the action. - /// The URL to download. - /// An optional Dictionary containing the HTTP - /// request headers. - /// Force a web request to always be issued, skipping the cache. - /// An optional expiration date. - /// The data downloaded from the URL. + private static IAkavacheHttpClientFactory HttpFactoryClient => Locator.Current.GetService() ?? throw new InvalidOperationException("Unable to resolve IAkavacheHttpClientFactory, probably Akavache is not initialized."); + + /// public IObservable DownloadUrl(IBlobCache blobCache, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { if (blobCache is null) @@ -36,18 +25,7 @@ public IObservable DownloadUrl(IBlobCache blobCache, string url, IDictio return blobCache.DownloadUrl(url, url, headers, fetchAlways, absoluteExpiration); } - /// - /// Download data from an HTTP URL and insert the result into the - /// cache. If the data is already in the cache, this returns - /// a cached value. The URL itself is used as the key. - /// - /// The blob cache associated with the action. - /// The URL to download. - /// An optional Dictionary containing the HTTP - /// request headers. - /// Force a web request to always be issued, skipping the cache. - /// An optional expiration date. - /// 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) @@ -63,19 +41,7 @@ public IObservable DownloadUrl(IBlobCache blobCache, Uri url, IDictionar return blobCache.DownloadUrl(url.ToString(), url, headers, fetchAlways, absoluteExpiration); } - /// - /// Download data from an HTTP URL and insert the result into the - /// cache. If the data is already in the cache, this returns - /// a cached value. An explicit key is provided rather than the URL itself. - /// - /// The blob cache associated with the action. - /// The key to store with. - /// The URL to download. - /// An optional Dictionary containing the HTTP - /// request headers. - /// Force a web request to always be issued, skipping the cache. - /// An optional expiration date. - /// 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) @@ -83,29 +49,25 @@ public IObservable DownloadUrl(IBlobCache blobCache, string key, string throw new ArgumentNullException(nameof(blobCache)); } - var doFetch = MakeWebRequest(new(url), headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); + var doFetch = MakeWebRequest(HttpMethod.Get, new Uri(url), headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); var fetchAndCache = doFetch.SelectMany(x => blobCache.Insert(key, x, absoluteExpiration).Select(_ => x)); - var ret = !fetchAlways ? blobCache.Get(key).Catch(fetchAndCache) : fetchAndCache; + IObservable ret; + if (!fetchAlways) + { + ret = blobCache.Get(key).Catch(fetchAndCache); + } + else + { + ret = fetchAndCache; + } var conn = ret.PublishLast(); conn.Connect(); return conn; } - /// - /// Download data from an HTTP URL and insert the result into the - /// cache. If the data is already in the cache, this returns - /// a cached value. An explicit key is provided rather than the URL itself. - /// - /// The blob cache associated with the action. - /// The key to store with. - /// The URL to download. - /// An optional Dictionary containing the HTTP - /// request headers. - /// Force a web request to always be issued, skipping the cache. - /// An optional expiration date. - /// 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) @@ -113,146 +75,145 @@ public IObservable DownloadUrl(IBlobCache blobCache, string key, Uri url throw new ArgumentNullException(nameof(blobCache)); } - var doFetch = MakeWebRequest(url, headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); + var doFetch = MakeWebRequest(HttpMethod.Get, url, headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); var fetchAndCache = doFetch.SelectMany(x => blobCache.Insert(key, x, absoluteExpiration).Select(_ => x)); - var ret = !fetchAlways ? blobCache.Get(key).Catch(fetchAndCache) : fetchAndCache; + IObservable ret; + if (!fetchAlways) + { + ret = blobCache.Get(key).Catch(fetchAndCache); + } + else + { + ret = fetchAndCache; + } var conn = ret.PublishLast(); conn.Connect(); return conn; } - private static IObservable MakeWebRequest( - Uri uri, - IDictionary? headers = null, - string? content = null, - int retries = 3, - TimeSpan? timeout = null) + /// + public IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { - IObservable request; - -#if !WINDOWS_UWP - if (ModeDetector.InUnitTestRunner()) - { - request = Observable.Defer(() => - { - var hwr = CreateWebRequest(uri, headers); - - if (content is null) - { - return Observable.Start(() => hwr.GetResponse(), BlobCache.TaskpoolScheduler); - } - - var buf = Encoding.UTF8.GetBytes(content); - return Observable.Start( - () => - { - hwr.GetRequestStream().Write(buf, 0, buf.Length); - return hwr.GetResponse(); - }, - BlobCache.TaskpoolScheduler); - }); - } - else -#endif + if (blobCache is null) { - request = Observable.Defer(() => - { - var hwr = CreateWebRequest(uri, headers); - - if (content is null) - { - return Observable.FromAsync(() => Task.Factory.FromAsync(hwr.BeginGetResponse, hwr.EndGetResponse, hwr)); - } - - var buf = Encoding.UTF8.GetBytes(content); - - // NB: You'd think that BeginGetResponse would never block, - // seeing as how it's asynchronous. You'd be wrong :-/ - var ret = new AsyncSubject(); - Observable.Start( - () => - { - Observable.FromAsync(() => Task.Factory.FromAsync(hwr.BeginGetRequestStream, hwr.EndGetRequestStream, hwr)) - .SelectMany(x => x.WriteAsyncRx(buf, 0, buf.Length)) - .SelectMany(_ => Observable.FromAsync(() => Task.Factory.FromAsync(hwr.BeginGetResponse, hwr.EndGetResponse, hwr))) - .Multicast(ret).Connect(); - }, - BlobCache.TaskpoolScheduler); - - return ret; - }); + throw new ArgumentNullException(nameof(blobCache)); } - return request.Timeout(timeout ?? TimeSpan.FromSeconds(15), BlobCache.TaskpoolScheduler).Retry(retries); + return blobCache.DownloadUrl(method, url, url, headers, fetchAlways, absoluteExpiration); } - private static WebRequest CreateWebRequest(Uri uri, IDictionary? headers) + /// + public IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { - var hwr = WebRequest.Create(uri); - if (headers is not null) + if (blobCache is null) { - foreach (var x in headers) - { - hwr.Headers[x.Key] = x.Value; - } + throw new ArgumentNullException(nameof(blobCache)); } - return hwr; + if (url is null) + { + throw new ArgumentNullException(nameof(url)); + } + + return blobCache.DownloadUrl(method, url.ToString(), url, headers, fetchAlways, absoluteExpiration); } - private static IObservable ProcessWebResponse(WebResponse wr, string url, DateTimeOffset? absoluteExpiration) + /// + public IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, string key, string url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { - if (wr is not HttpWebResponse hwr) + if (blobCache is null) { - throw new ArgumentException("The Web Response is somehow null but shouldn't be: " + url + " with expiry: " + absoluteExpiration, nameof(wr)); + throw new ArgumentNullException(nameof(blobCache)); } - if ((int)hwr.StatusCode >= 400) + var doFetch = MakeWebRequest(method, new Uri(url), headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); + var fetchAndCache = doFetch.SelectMany(x => blobCache.Insert(key, x, absoluteExpiration).Select(_ => x)); + + IObservable ret; + if (!fetchAlways) { - return Observable.Throw(new WebException(hwr.StatusDescription)); + ret = blobCache.Get(key).Catch(fetchAndCache); } - - using var ms = new MemoryStream(); - using (var responseStream = hwr.GetResponseStream()) + else { - if (responseStream is null) - { - throw new InvalidOperationException("The response stream is somehow null: " + url + " with expiry: " + absoluteExpiration); - } - - responseStream.CopyTo(ms); + ret = fetchAndCache; } - var ret = ms.ToArray(); - return Observable.Return(ret); + var conn = ret.PublishLast(); + conn.Connect(); + return conn; } - private static IObservable ProcessWebResponse(WebResponse wr, Uri url, DateTimeOffset? absoluteExpiration) + /// + public IObservable DownloadUrl(IBlobCache blobCache, HttpMethod method, string key, Uri url, IDictionary? headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) { - if (wr is not HttpWebResponse hwr) + if (blobCache is null) { - throw new ArgumentException("The Web Response is somehow null but shouldn't be: " + url + " with expiry: " + absoluteExpiration, nameof(wr)); + throw new ArgumentNullException(nameof(blobCache)); } - if ((int)hwr.StatusCode >= 400) + var doFetch = MakeWebRequest(method, url, headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); + var fetchAndCache = doFetch.SelectMany(x => blobCache.Insert(key, x, absoluteExpiration).Select(_ => x)); + + IObservable ret; + if (!fetchAlways) { - return Observable.Throw(new WebException(hwr.StatusDescription)); + ret = blobCache.Get(key).Catch(fetchAndCache); } + else + { + ret = fetchAndCache; + } + + var conn = ret.PublishLast(); + conn.Connect(); + return conn; + } + + private static IObservable MakeWebRequest( + HttpMethod method, + Uri uri, + IDictionary? headers = null, + int retries = 3, + TimeSpan? timeout = null) + { + var request = Observable.Defer(() => + { + var client = HttpFactoryClient.CreateClient(nameof(AkavacheHttpMixin)); + + var request = CreateWebRequest(method, uri, headers); + + return Observable.FromAsync(() => client.SendAsync(request), BlobCache.TaskpoolScheduler); + }); + + return request.Timeout(timeout ?? TimeSpan.FromSeconds(15), BlobCache.TaskpoolScheduler).Retry(retries); + } - using var ms = new MemoryStream(); - using (var responseStream = hwr.GetResponseStream()) + private static HttpRequestMessage CreateWebRequest(HttpMethod method, Uri uri, IDictionary? headers) + { + var requestMessage = new HttpRequestMessage(method, uri); + if (headers is not null) { - if (responseStream is null) + foreach (var x in headers) { - throw new InvalidOperationException("The response stream is somehow null: " + url + " with expiry: " + absoluteExpiration); + requestMessage.Headers.TryAddWithoutValidation(x.Key, x.Value); } + } - responseStream.CopyTo(ms); + return requestMessage; + } + + private static IObservable ProcessWebResponse(HttpResponseMessage response, string url, DateTimeOffset? absoluteExpiration) + { + if (!response.IsSuccessStatusCode) + { + return Observable.Throw(new HttpRequestException("Invalid response: " + url + " reason " + response.ReasonPhrase + " with expiry: " + absoluteExpiration)); } - var ret = ms.ToArray(); - return Observable.Return(ret); + return Observable.FromAsync(() => response.Content.ReadAsByteArrayAsync()); } + + private static IObservable ProcessWebResponse(HttpResponseMessage response, Uri url, DateTimeOffset? absoluteExpiration) => ProcessWebResponse(response, url.ToString(), absoluteExpiration); } diff --git a/src/Akavache.Core/Platforms/shared/DefaultAkavacheHttpClientFactory.cs b/src/Akavache.Core/Platforms/shared/DefaultAkavacheHttpClientFactory.cs new file mode 100644 index 000000000..e9e9b3ee1 --- /dev/null +++ b/src/Akavache.Core/Platforms/shared/DefaultAkavacheHttpClientFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2022 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections.Concurrent; + +namespace Akavache; + +/// +/// The default http client factory. Can be replaced with for example the Microsoft Http Client factory. +/// +public class DefaultAkavacheHttpClientFactory : IAkavacheHttpClientFactory +{ + private static ConcurrentDictionary _instances = new(); + + /// + public HttpClient CreateClient(string name) => _instances.GetOrAdd(name, _ => new HttpClient()); +} diff --git a/src/Akavache.Core/Platforms/shared/Registrations.cs b/src/Akavache.Core/Platforms/shared/Registrations.cs index 1234fb921..d45c81a92 100644 --- a/src/Akavache.Core/Platforms/shared/Registrations.cs +++ b/src/Akavache.Core/Platforms/shared/Registrations.cs @@ -54,6 +54,7 @@ public void Register(IMutableDependencyResolver resolver, IReadonlyDependencyRes resolver.Register(() => secure.Value, typeof(ISecureBlobCache), null); resolver.Register(() => new AkavacheHttpMixin(), typeof(IAkavacheHttpMixin), null); + resolver.Register(() => new DefaultAkavacheHttpClientFactory(), typeof(IAkavacheHttpClientFactory), null); #if COCOA BlobCache.ApplicationName = NSBundle.MainBundle.BundleIdentifier; diff --git a/src/Akavache.Drawing/Akavache.Drawing.csproj b/src/Akavache.Drawing/Akavache.Drawing.csproj index 9e396361d..6e73e3a30 100644 --- a/src/Akavache.Drawing/Akavache.Drawing.csproj +++ b/src/Akavache.Drawing/Akavache.Drawing.csproj @@ -1,6 +1,6 @@  - netstandard2.0;netstandard2.1;MonoAndroid11.0;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;tizen40; + netstandard2.0;netstandard2.1;MonoAndroid11.0;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;tizen40;net6.0-android;net6.0-ios;net6.0-tvos;net6.0-macos;net6.0-maccatalyst $(TargetFrameworks);net462;uap10.0.16299;net6.0-windows Akavache.Drawing Akavache @@ -9,8 +9,16 @@ latest enable + + + + + + + + diff --git a/src/Akavache.Drawing/BitmapImageMixin.cs b/src/Akavache.Drawing/BitmapImageMixin.cs index 1aa5cab01..fbf8462ea 100644 --- a/src/Akavache.Drawing/BitmapImageMixin.cs +++ b/src/Akavache.Drawing/BitmapImageMixin.cs @@ -115,7 +115,7 @@ public static IObservable LoadImageFromUrl(this IBlobCache blobCache, s /// too small). public static IObservable ThrowOnBadImageBuffer(byte[] compressedImage) => (compressedImage is null || compressedImage.Length < 64) ? - Observable.Throw(new("Invalid Image")) : + Observable.Throw(new InvalidOperationException("Invalid Image")) : Observable.Return(compressedImage); private static IObservable BytesToImage(byte[] compressedImage, float? desiredWidth, float? desiredHeight) diff --git a/src/Akavache.Mobile/Akavache.Mobile.csproj b/src/Akavache.Mobile/Akavache.Mobile.csproj index 0d7359192..ce37061ba 100644 --- a/src/Akavache.Mobile/Akavache.Mobile.csproj +++ b/src/Akavache.Mobile/Akavache.Mobile.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid11.0;tizen40;net6.0 + netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid11.0;tizen40;net6.0;net6.0-android;net6.0-ios;net6.0-tvos;net6.0-macos;net6.0-maccatalyst $(TargetFrameworks);net462;uap10.0.16299 Akavache.Mobile Akavache.Mobile @@ -11,6 +11,10 @@ enable + + + + diff --git a/src/Akavache.Sqlite3/Akavache.Sqlite3.csproj b/src/Akavache.Sqlite3/Akavache.Sqlite3.csproj index 00c98bbeb..14e3c1bcc 100644 --- a/src/Akavache.Sqlite3/Akavache.Sqlite3.csproj +++ b/src/Akavache.Sqlite3/Akavache.Sqlite3.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid11.0;tizen40;net6.0 + netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid11.0;tizen40;net6.0;net6.0-android;net6.0-ios;net6.0-tvos;net6.0-macos;net6.0-maccatalyst $(TargetFrameworks);net462;uap10.0.16299 Akavache.Sqlite3 Akavache.Sqlite3 @@ -13,6 +13,10 @@ enable + + + + diff --git a/src/Akavache.Sqlite3/Registrations.cs b/src/Akavache.Sqlite3/Registrations.cs index 698677459..3cd75360f 100644 --- a/src/Akavache.Sqlite3/Registrations.cs +++ b/src/Akavache.Sqlite3/Registrations.cs @@ -39,7 +39,7 @@ public void Register(IMutableDependencyResolver resolver, IReadonlyDependencyRes var fs = Locator.Current.GetService(); if (fs is null) { - throw new("Failed to initialize Akavache properly. Do you have a reference to Akavache.dll?"); + throw new InvalidOperationException("Failed to initialize Akavache properly. Do you have a reference to Akavache.dll?"); } var localCache = new Lazy(() => @@ -84,4 +84,4 @@ public void Register(IMutableDependencyResolver resolver, IReadonlyDependencyRes }); resolver.Register(() => secure.Value, typeof(ISecureBlobCache)); } -} \ No newline at end of file +} diff --git a/src/Akavache.Sqlite3/SQLite.cs b/src/Akavache.Sqlite3/SQLite.cs index fd0bde013..2518affa1 100644 --- a/src/Akavache.Sqlite3/SQLite.cs +++ b/src/Akavache.Sqlite3/SQLite.cs @@ -3232,7 +3232,7 @@ public static Result Open(string filename, out Sqlite3DatabaseHandle db, int fla public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) { - Sqlite3Statement stmt = default(Sqlite3Statement); + var stmt = default(Sqlite3Statement); #if USE_WP8_NATIVE_SQLITE || USE_SQLITEPCL_RAW var r = Sqlite3Raw.sqlite3_prepare_v2(db, query, out stmt); #else diff --git a/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs b/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs index 547957f96..6798f4720 100644 --- a/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs +++ b/src/Akavache.Sqlite3/SqlLiteCache/SQLiteEncryptedBlobCache.cs @@ -23,7 +23,7 @@ public class SQLiteEncryptedBlobCache : SqlRawPersistentBlobCache, ISecureBlobCa /// If there is no encryption provider available. public SQLiteEncryptedBlobCache(string databaseFile, IEncryptionProvider? encryptionProvider = null, IScheduler? scheduler = null) : base(databaseFile, scheduler) => - _encryption = encryptionProvider ?? Locator.Current.GetService() ?? throw new("No IEncryptionProvider available. This should never happen, your DependencyResolver is broken"); + _encryption = encryptionProvider ?? Locator.Current.GetService() ?? throw new InvalidOperationException("No IEncryptionProvider available. This should never happen, your DependencyResolver is broken"); /// protected override IObservable BeforeWriteToDiskFilter(byte[] data, IScheduler scheduler) @@ -56,4 +56,4 @@ protected override IObservable AfterReadFromDiskFilter(byte[] data, ISch return _encryption.DecryptBlock(data); } -} \ No newline at end of file +} diff --git a/src/Akavache.Tests/API/ApiApprovalTests.AkavacheCore.net6.0.approved.txt b/src/Akavache.Tests/API/ApiApprovalTests.AkavacheCore.net6.0.approved.txt index f31ab5aa7..6fbcff54c 100644 --- a/src/Akavache.Tests/API/ApiApprovalTests.AkavacheCore.net6.0.approved.txt +++ b/src/Akavache.Tests/API/ApiApprovalTests.AkavacheCore.net6.0.approved.txt @@ -12,8 +12,12 @@ namespace Akavache public AkavacheHttpMixin() { } public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, string key, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, string key, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string key, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string key, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } } public static class BlobCache { @@ -49,6 +53,11 @@ namespace Akavache { CurrentUser = 0, } + public class DefaultAkavacheHttpClientFactory : Akavache.IAkavacheHttpClientFactory + { + public DefaultAkavacheHttpClientFactory() { } + public System.Net.Http.HttpClient CreateClient(string name) { } + } public static class DependencyResolverMixin { public static void InitializeAkavache(this Splat.IMutableDependencyResolver resolver, Splat.IReadonlyDependencyResolver readonlyDependencyResolver) { } @@ -63,15 +72,27 @@ namespace Akavache { public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, string key, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, string key, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string key, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + public static System.IObservable DownloadUrl(this Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string key, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default) { } + } + public interface IAkavacheHttpClientFactory + { + System.Net.Http.HttpClient CreateClient(string name); } public interface IAkavacheHttpMixin { System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); + System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); + System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, string key, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, string key, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); + System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string key, string url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); + System.IObservable DownloadUrl(Akavache.IBlobCache blobCache, System.Net.Http.HttpMethod method, string key, System.Uri url, System.Collections.Generic.IDictionary? headers = null, bool fetchAlways = false, System.DateTimeOffset? absoluteExpiration = default); } public interface IBlobCache : System.IDisposable { diff --git a/src/Akavache.Tests/API/ApiApprovalTests.cs b/src/Akavache.Tests/API/ApiApprovalTests.cs index 89e6b1e8d..972f0c4a7 100644 --- a/src/Akavache.Tests/API/ApiApprovalTests.cs +++ b/src/Akavache.Tests/API/ApiApprovalTests.cs @@ -96,4 +96,4 @@ private static string Filter(string text) !l.StartsWith("[assembly: AssemblyInformationalVersion(", StringComparison.InvariantCulture) && !string.IsNullOrWhiteSpace(l))); } -} \ No newline at end of file +} diff --git a/src/Akavache.Tests/Akavache.Tests.csproj b/src/Akavache.Tests/Akavache.Tests.csproj index 876b00856..c4c703f86 100644 --- a/src/Akavache.Tests/Akavache.Tests.csproj +++ b/src/Akavache.Tests/Akavache.Tests.csproj @@ -3,7 +3,8 @@ net6.0 $(NoWarn);CA1307;CA2000;CA1062 - latest + latest + disable diff --git a/src/Akavache.Tests/Helpers/IntegrationTestHelper.cs b/src/Akavache.Tests/Helpers/IntegrationTestHelper.cs index dfea1f450..7f84364eb 100644 --- a/src/Akavache.Tests/Helpers/IntegrationTestHelper.cs +++ b/src/Akavache.Tests/Helpers/IntegrationTestHelper.cs @@ -62,7 +62,7 @@ public static HttpResponseMessage GetResponse(params string[] paths) goto foundIt; } - throw new("Couldn't find response body"); + throw new InvalidOperationException("Couldn't find response body"); foundIt: diff --git a/src/Akavache.Tests/Performance/ReadTests.cs b/src/Akavache.Tests/Performance/ReadTests.cs index edee615d3..56b5bf392 100644 --- a/src/Akavache.Tests/Performance/ReadTests.cs +++ b/src/Akavache.Tests/Performance/ReadTests.cs @@ -108,8 +108,7 @@ private async Task GeneratePerfRangesForBlock(Func var results = new Dictionary(); var dbName = default(string); - var dirPath = default(string); - using (Utility.WithEmptyDirectory(out dirPath)) + using (Utility.WithEmptyDirectory(out var dirPath)) using (var cache = await GenerateAGiantDatabase(dirPath).ConfigureAwait(false)) { var keys = await cache.GetAllKeys(); @@ -151,4 +150,4 @@ private async Task GenerateAGiantDatabase(string path) return cache; } -} \ No newline at end of file +} diff --git a/src/Akavache.Tests/Performance/WriteTests.cs b/src/Akavache.Tests/Performance/WriteTests.cs index d6f29182a..cf1ab1af0 100644 --- a/src/Akavache.Tests/Performance/WriteTests.cs +++ b/src/Akavache.Tests/Performance/WriteTests.cs @@ -89,8 +89,7 @@ private async Task GeneratePerfRangesForBlock(Func> var results = new Dictionary(); var dbName = default(string); - var dirPath = default(string); - using (Utility.WithEmptyDirectory(out dirPath)) + using (Utility.WithEmptyDirectory(out var dirPath)) using (var cache = CreateBlobCache(dirPath)) { dbName = cache.GetType().Name; @@ -107,4 +106,4 @@ private async Task GeneratePerfRangesForBlock(Func> Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value); } } -} \ No newline at end of file +} diff --git a/src/Akavache.Tests/UtilityTests.cs b/src/Akavache.Tests/UtilityTests.cs index 34bb23f1c..ed247925a 100644 --- a/src/Akavache.Tests/UtilityTests.cs +++ b/src/Akavache.Tests/UtilityTests.cs @@ -72,9 +72,7 @@ public void UtilitySplitsAbsolutePaths() [Fact] public void UtilityResolvesAndSplitsRelativePaths() { - string path; - - path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"foo\bar" : "foo/bar"; + var path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"foo\bar" : "foo/bar"; var components = new DirectoryInfo(path).SplitFullPath().ToList(); Assert.True(components.Count > 2); diff --git a/src/Akavache/Akavache.csproj b/src/Akavache/Akavache.csproj index 3bba8722f..186489d84 100644 --- a/src/Akavache/Akavache.csproj +++ b/src/Akavache/Akavache.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid10.0;tizen40;net6.0 + netstandard2.0;netstandard2.1;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;MonoAndroid10.0;tizen40;net6.0;net6.0-android;net6.0-ios;net6.0-tvos;net6.0-macos;net6.0-maccatalyst $(TargetFrameworks);net462;uap10.0.16299 Akavache Akavache @@ -15,6 +15,10 @@ + + + + diff --git a/src/Akavache/LinkerPreserve.cs b/src/Akavache/LinkerPreserve.cs index a144c251c..821c32e43 100644 --- a/src/Akavache/LinkerPreserve.cs +++ b/src/Akavache/LinkerPreserve.cs @@ -3,6 +3,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System; using System.Diagnostics.CodeAnalysis; using Akavache.Core; @@ -23,5 +24,5 @@ public static class LinkerPreserve /// /// A exception due to this being in the non-derived assembly. [SuppressMessage("FxCop.Design", "CA1065: Don't throw in constructors", Justification = "Shim class, should not happen.")] - static LinkerPreserve() => throw new(typeof(SQLitePersistentBlobCache).FullName); + static LinkerPreserve() => throw new InvalidOperationException(typeof(SQLitePersistentBlobCache).FullName); } diff --git a/src/Directory.build.props b/src/Directory.build.props index 8dc07e2ca..43c3d840d 100644 --- a/src/Directory.build.props +++ b/src/Directory.build.props @@ -24,6 +24,7 @@ True latest enable + enable diff --git a/src/Directory.build.targets b/src/Directory.build.targets index a65cad9f1..be00ad844 100644 --- a/src/Directory.build.targets +++ b/src/Directory.build.targets @@ -15,19 +15,31 @@ $(DefineConstants);MONO;COCOA + + $(DefineConstants);MONO;COCOA + $(DefineConstants);MONO;UIKIT;COCOA;XAMARIN_MOBILE + + $(DefineConstants);MONO;UIKIT;COCOA;XAMARIN_MOBILE + + + $(DefineConstants);MONO;UIKIT;COCOA;XAMARIN_MOBILE + $(DefineConstants);MONO;UIKIT;COCOA;XAMARIN_MOBILE $(DefineConstants);MONO;ANDROID;XAMARIN_MOBILE + + $(DefineConstants);MONO;ANDROID;XAMARIN_MOBILE + $(DefineConstants);TIZEN;XAMARIN_MOBILE $(DefineConstants);PORTABLE - \ No newline at end of file +