diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Controls/SampleAppMarkdownRenderer.cs b/Microsoft.Toolkit.Uwp.SampleApp/Controls/SampleAppMarkdownRenderer.cs index dc8271a7a49..de1fd087f53 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Controls/SampleAppMarkdownRenderer.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Controls/SampleAppMarkdownRenderer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Toolkit.Helpers; using Microsoft.Toolkit.Parsers.Markdown; using Microsoft.Toolkit.Parsers.Markdown.Blocks; using Microsoft.Toolkit.Parsers.Markdown.Inlines; @@ -407,19 +408,19 @@ public string DesiredLang { get { - return storage.Read(DesiredLangKey); + return settingsStorage.Read(DesiredLangKey); } set { - storage.Save(DesiredLangKey, value); + settingsStorage.Save(DesiredLangKey, value); } } /// - /// The Local Storage Helper. + /// The local app data storage helper for storing settings. /// - private LocalObjectStorageHelper storage = new LocalObjectStorageHelper(new SystemSerializer()); + private readonly ApplicationDataStorageHelper settingsStorage = ApplicationDataStorageHelper.GetCurrent(); /// /// DocFX note types and styling info, keyed by identifier. diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs b/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs index 9a55745ea11..6e512819c70 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs @@ -20,6 +20,7 @@ // TODO Reintroduce graph controls // using Microsoft.Toolkit.Graph.Converters; // using Microsoft.Toolkit.Graph.Providers; +using Microsoft.Toolkit.Helpers; using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.Toolkit.Uwp.Input.GazeInteraction; using Microsoft.Toolkit.Uwp.SampleApp.Models; @@ -45,7 +46,7 @@ public class Sample public static async void EnsureCacheLatest() { - var settingsStorage = new LocalObjectStorageHelper(new SystemSerializer()); + var settingsStorage = ApplicationDataStorageHelper.GetCurrent(); var onlineDocsSHA = await GetDocsSHA(); var cacheSHA = settingsStorage.Read(_cacheSHAKey); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Models/Samples.cs b/Microsoft.Toolkit.Uwp.SampleApp/Models/Samples.cs index 8105e859b78..a05c2c19f22 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Models/Samples.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Models/Samples.cs @@ -9,6 +9,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Toolkit.Helpers; using Microsoft.Toolkit.Uwp.Helpers; namespace Microsoft.Toolkit.Uwp.SampleApp @@ -21,7 +22,7 @@ public static class Samples private static SemaphoreSlim _semaphore = new SemaphoreSlim(1); private static LinkedList _recentSamples; - private static LocalObjectStorageHelper _localObjectStorageHelper = new LocalObjectStorageHelper(new SystemSerializer()); + private static ApplicationDataStorageHelper _settingsStorage = ApplicationDataStorageHelper.GetCurrent(); public static async Task GetCategoryBySample(Sample sample) { @@ -98,7 +99,7 @@ public static async Task> GetRecentSamples() if (_recentSamples == null) { _recentSamples = new LinkedList(); - var savedSamples = _localObjectStorageHelper.Read(_recentSamplesStorageKey); + var savedSamples = _settingsStorage.Read(_recentSamplesStorageKey); if (savedSamples != null) { @@ -144,7 +145,7 @@ private static void SaveRecentSamples() } var str = string.Join(";", _recentSamples.Take(10).Select(s => s.Name).ToArray()); - _localObjectStorageHelper.Save(_recentSamplesStorageKey, str); + _settingsStorage.Save(_recentSamplesStorageKey, str); } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStorageCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStorageCode.bind index 50d77bc5e3e..0b0c4d9263e 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStorageCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStorageCode.bind @@ -1,25 +1,24 @@ -var localObjectStorageHelper = new LocalObjectStorageHelper(); -var roamingObjectStorageHelper = new RoamingObjectStorageHelper(); +ApplicationDataStorageHelper appDataStorageHelper = ApplicationDataStorageHelper.GetCurrent(new Toolkit.Helpers.SystemSerializer()); // Read and Save with simple objects string keySimpleObject = "simple"; -string result = localObjectStorageHelper.Read(keySimpleObject); -localObjectStorageHelper.Save(keySimpleObject, 47); +string result = appDataStorageHelper.Read(keySimpleObject); +appDataStorageHelper.Save(keySimpleObject, 47); // Read and Save with complex/large objects -string keyLargeObject = "large"; -var result = localObjectStorageHelper.ReadFileAsync(keyLargeObject); +string complexObjectKey = "complexObject"; +var complexObject = await appDataStorageHelper.ReadFileAsync(complexObjectKey); -var o = new MyLargeObject +var myComplexObject = new MyComplexObject() { ... }; -localObjectStorageHelper.SaveFileAsync(keySimpleObject, o); +await appDataStorageHelper.SaveFileAsync(complexObjectKey, myComplexObject); // Complex object -public class MyLargeObject +public class MyComplexObject { public string MyContent { get; set; } public List MyContents { get; set; } - public List MyObjects { get; set; } + public List MyObjects { get; set; } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStoragePage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStoragePage.xaml.cs index e2da83f53f1..9bc0b00df04 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStoragePage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Object Storage/ObjectStoragePage.xaml.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Toolkit.Helpers; using Microsoft.Toolkit.Uwp.Helpers; using Windows.UI.Xaml; @@ -9,7 +10,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages { public sealed partial class ObjectStoragePage { - private readonly IObjectStorageHelper localStorageHelper = new LocalObjectStorageHelper(new SystemSerializer()); + private readonly ApplicationDataStorageHelper _settingsStorage = ApplicationDataStorageHelper.GetCurrent(); public ObjectStoragePage() { @@ -24,9 +25,9 @@ private void ReadButton_Click(object sender, RoutedEventArgs e) } // Read from local storage - if (localStorageHelper.KeyExists(KeyTextBox.Text)) + if (_settingsStorage.KeyExists(KeyTextBox.Text)) { - ContentTextBox.Text = localStorageHelper.Read(KeyTextBox.Text); + ContentTextBox.Text = _settingsStorage.Read(KeyTextBox.Text); } } @@ -43,7 +44,7 @@ private void SaveButton_Click(object sender, RoutedEventArgs e) } // Save into local storage - localStorageHelper.Save(KeyTextBox.Text, ContentTextBox.Text); + _settingsStorage.Save(KeyTextBox.Text, ContentTextBox.Text); } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/ApplicationDataStorageHelper.CacheFolder.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/ApplicationDataStorageHelper.CacheFolder.cs new file mode 100644 index 00000000000..78a73a566a9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/ApplicationDataStorageHelper.CacheFolder.cs @@ -0,0 +1,77 @@ +// 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 more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Toolkit.Helpers; +using Windows.Storage; + +namespace Microsoft.Toolkit.Uwp.Helpers +{ + /// + /// An extension of ApplicationDataStorageHelper with additional features for interop with the LocalCacheFolder. + /// + public partial class ApplicationDataStorageHelper + { + /// + /// Gets the local cache folder. + /// + public StorageFolder CacheFolder => AppData.LocalCacheFolder; + + /// + /// Retrieves an object from a file in the LocalCacheFolder. + /// + /// Type of object retrieved. + /// Path to the file that contains the object. + /// Default value of the object. + /// Waiting task until completion with the object in the file. + public Task ReadCacheFileAsync(string filePath, T @default = default) + { + return ReadFileAsync(CacheFolder, filePath, @default); + } + + /// + /// Retrieves the listings for a folder and the item types in the LocalCacheFolder. + /// + /// The path to the target folder. + /// A list of file types and names in the target folder. + public Task> ReadCacheFolderAsync(string folderPath) + { + return ReadFolderAsync(CacheFolder, folderPath); + } + + /// + /// Saves an object inside a file in the LocalCacheFolder. + /// + /// Type of object saved. + /// Path to the file that will contain the object. + /// Object to save. + /// Waiting task until completion. + public Task CreateCacheFileAsync(string filePath, T value) + { + return SaveFileAsync(CacheFolder, filePath, value); + } + + /// + /// Ensure a folder exists at the folder path specified in the LocalCacheFolder. + /// + /// The path and name of the target folder. + /// Waiting task until completion. + public Task CreateCacheFolderAsync(string folderPath) + { + return CreateFolderAsync(CacheFolder, folderPath); + } + + /// + /// Deletes a file or folder item in the LocalCacheFolder. + /// + /// The path to the item for deletion. + /// Waiting task until completion. + public Task DeleteCacheItemAsync(string itemPath) + { + return DeleteItemAsync(CacheFolder, itemPath); + } + } +} diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/ApplicationDataStorageHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/ApplicationDataStorageHelper.cs new file mode 100644 index 00000000000..2cf1c568f54 --- /dev/null +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/ApplicationDataStorageHelper.cs @@ -0,0 +1,326 @@ +// 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 more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Toolkit.Helpers; +using Windows.Storage; +using Windows.System; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.Helpers +{ + /// + /// Storage helper for files and folders living in Windows.Storage.ApplicationData storage endpoints. + /// + public partial class ApplicationDataStorageHelper : IFileStorageHelper, ISettingsStorageHelper + { + /// + /// Initializes a new instance of the class. + /// + /// The data store to interact with. + /// Serializer for converting stored values. Defaults to . + public ApplicationDataStorageHelper(ApplicationData appData, Toolkit.Helpers.IObjectSerializer? objectSerializer = null) + { + this.AppData = appData ?? throw new ArgumentNullException(nameof(appData)); + this.Serializer = objectSerializer ?? new Toolkit.Helpers.SystemSerializer(); + } + + /// + /// Gets the settings container. + /// + public ApplicationDataContainer Settings => this.AppData.LocalSettings; + + /// + /// Gets the storage folder. + /// + public StorageFolder Folder => this.AppData.LocalFolder; + + /// + /// Gets the storage host. + /// + protected ApplicationData AppData { get; } + + /// + /// Gets the serializer for converting stored values. + /// + protected Toolkit.Helpers.IObjectSerializer Serializer { get; } + + /// + /// Get a new instance using ApplicationData.Current and the provided serializer. + /// + /// Serializer for converting stored values. Defaults to . + /// A new instance of ApplicationDataStorageHelper. + public static ApplicationDataStorageHelper GetCurrent(Toolkit.Helpers.IObjectSerializer? objectSerializer = null) + { + var appData = ApplicationData.Current; + return new ApplicationDataStorageHelper(appData, objectSerializer); + } + + /// + /// Get a new instance using the ApplicationData for the provided user and serializer. + /// + /// App data user owner. + /// Serializer for converting stored values. Defaults to . + /// A new instance of ApplicationDataStorageHelper. + public static async Task GetForUserAsync(User user, Toolkit.Helpers.IObjectSerializer? objectSerializer = null) + { + var appData = await ApplicationData.GetForUserAsync(user); + return new ApplicationDataStorageHelper(appData, objectSerializer); + } + + /// + /// Determines whether a setting already exists. + /// + /// Key of the setting (that contains object). + /// True if a value exists. + public bool KeyExists(string key) + { + return this.Settings.Values.ContainsKey(key); + } + + /// + /// Retrieves a single item by its key. + /// + /// Type of object retrieved. + /// Key of the object. + /// Default value of the object. + /// The TValue object. + public T? Read(string key, T? @default = default) + { + if (this.Settings.Values.TryGetValue(key, out var valueObj) && valueObj is string valueString) + { + return this.Serializer.Deserialize(valueString); + } + + return @default; + } + + /// + public bool TryRead(string key, out T? value) + { + if (this.Settings.Values.TryGetValue(key, out var valueObj) && valueObj is string valueString) + { + value = this.Serializer.Deserialize(valueString); + return true; + } + + value = default; + return false; + } + + /// + public void Save(string key, T value) + { + this.Settings.Values[key] = this.Serializer.Serialize(value); + } + + /// + public bool TryDelete(string key) + { + return this.Settings.Values.Remove(key); + } + + /// + public void Clear() + { + this.Settings.Values.Clear(); + } + + /// + /// Determines whether a setting already exists in composite. + /// + /// Key of the composite (that contains settings). + /// Key of the setting (that contains object). + /// True if a value exists. + public bool KeyExists(string compositeKey, string key) + { + if (this.TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + return composite.ContainsKey(key); + } + + return false; + } + + /// + /// Attempts to retrieve a single item by its key in composite. + /// + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// The value of the object retrieved. + /// The T object. + public bool TryRead(string compositeKey, string key, out T? value) + { + if (this.TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + string compositeValue = (string)composite[key]; + if (compositeValue != null) + { + value = this.Serializer.Deserialize(compositeValue); + return true; + } + } + + value = default; + return false; + } + + /// + /// Retrieves a single item by its key in composite. + /// + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// Default value of the object. + /// The T object. + public T? Read(string compositeKey, string key, T? @default = default) + { + if (this.TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + if (composite.TryGetValue(key, out object valueObj) && valueObj is string value) + { + return this.Serializer.Deserialize(value); + } + } + + return @default; + } + + /// + /// Saves a group of items by its key in a composite. + /// This method should be considered for objects that do not exceed 8k bytes during the lifetime of the application + /// and for groups of settings which need to be treated in an atomic way. + /// + /// Type of object saved. + /// Key of the composite (that contains settings). + /// Objects to save. + public void Save(string compositeKey, IDictionary values) + { + if (this.TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + foreach (KeyValuePair setting in values) + { + if (composite.ContainsKey(setting.Key)) + { + composite[setting.Key] = this.Serializer.Serialize(setting.Value); + } + else + { + composite.Add(setting.Key, this.Serializer.Serialize(setting.Value)); + } + } + } + else + { + composite = new ApplicationDataCompositeValue(); + foreach (KeyValuePair setting in values) + { + composite.Add(setting.Key, this.Serializer.Serialize(setting.Value)); + } + + this.Settings.Values[compositeKey] = composite; + } + } + + /// + /// Deletes a single item by its key in composite. + /// + /// Key of the composite (that contains settings). + /// Key of the object. + /// A boolean indicator of success. + public bool TryDelete(string compositeKey, string key) + { + if (this.TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + return composite.Remove(key); + } + + return false; + } + + /// + public Task ReadFileAsync(string filePath, T? @default = default) + { + return this.ReadFileAsync(this.Folder, filePath, @default); + } + + /// + public Task> ReadFolderAsync(string folderPath) + { + return this.ReadFolderAsync(this.Folder, folderPath); + } + + /// + public Task CreateFileAsync(string filePath, T value) + { + return this.SaveFileAsync(this.Folder, filePath, value); + } + + /// + public Task CreateFolderAsync(string folderPath) + { + return this.CreateFolderAsync(this.Folder, folderPath); + } + + /// + public Task DeleteItemAsync(string itemPath) + { + return this.DeleteItemAsync(this.Folder, itemPath); + } + + /// + /// Saves an object inside a file. + /// + /// Type of object saved. + /// Path to the file that will contain the object. + /// Object to save. + /// Waiting task until completion. + public Task SaveFileAsync(string filePath, T value) + { + return this.SaveFileAsync(this.Folder, filePath, value); + } + + private async Task ReadFileAsync(StorageFolder folder, string filePath, T? @default = default) + { + string value = await StorageFileHelper.ReadTextFromFileAsync(folder, filePath); + return (value != null) ? this.Serializer.Deserialize(value) : @default; + } + + private async Task> ReadFolderAsync(StorageFolder folder, string folderPath) + { + var targetFolder = await folder.GetFolderAsync(folderPath); + var items = await targetFolder.GetItemsAsync(); + + return items.Select((item) => + { + var itemType = item.IsOfType(StorageItemTypes.File) ? DirectoryItemType.File + : item.IsOfType(StorageItemTypes.Folder) ? DirectoryItemType.Folder + : DirectoryItemType.None; + + return (itemType, item.Name); + }); + } + + private Task SaveFileAsync(StorageFolder folder, string filePath, T value) + { + return StorageFileHelper.WriteTextToFileAsync(folder, this.Serializer.Serialize(value)?.ToString(), filePath, CreationCollisionOption.ReplaceExisting); + } + + private async Task CreateFolderAsync(StorageFolder folder, string folderPath) + { + await folder.CreateFolderAsync(folderPath, CreationCollisionOption.OpenIfExists); + } + + private async Task DeleteItemAsync(StorageFolder folder, string itemPath) + { + var item = await folder.GetItemAsync(itemPath); + await item.DeleteAsync(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/BaseObjectStorageHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/BaseObjectStorageHelper.cs index 528b2ca1c23..bf111d6aa01 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/BaseObjectStorageHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/BaseObjectStorageHelper.cs @@ -12,6 +12,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers /// /// Shared implementation of ObjectStorageHelper. /// + [Obsolete("BaseObjectStorageHelper is deprecated and has been superceded by ApplicationDataStorageHelper.")] public abstract class BaseObjectStorageHelper : IObjectStorageHelper { private readonly IObjectSerializer serializer; diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectSerializer.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectSerializer.cs index ca8aa14ec68..0f1bfa4d5e4 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectSerializer.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectSerializer.cs @@ -2,11 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; + namespace Microsoft.Toolkit.Uwp.Helpers { /// /// A basic serialization service. /// + [Obsolete("IObjectSerializer has been migrated to the Microsoft.Toolkit (CommunityToolkit.Common) package.")] public interface IObjectSerializer { /// @@ -25,4 +28,4 @@ public interface IObjectSerializer /// The deserialized object. T Deserialize(object value); } -} \ No newline at end of file +} diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectStorageHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectStorageHelper.cs index f4e5c861e2c..ecdb7e8bcc1 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectStorageHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/IObjectStorageHelper.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading.Tasks; using Windows.Storage; @@ -11,48 +12,49 @@ namespace Microsoft.Toolkit.Uwp.Helpers /// /// Service used to store data. /// + [Obsolete("IObjectStorageHelper is deprecated. Please use ISettingsStorageHelper and IFileStorageHelper interfaces instead.")] public interface IObjectStorageHelper { /// /// Determines whether a setting already exists. /// - /// Key of the setting (that contains object) - /// True if a value exists + /// Key of the setting (that contains object). + /// True if a value exists. bool KeyExists(string key); /// /// Determines whether a setting already exists in composite. /// - /// Key of the composite (that contains settings) - /// Key of the setting (that contains object) - /// True if a value exists + /// Key of the composite (that contains settings). + /// Key of the setting (that contains object). + /// True if a value exists. bool KeyExists(string compositeKey, string key); /// /// Retrieves a single item by its key. /// - /// Type of object retrieved - /// Key of the object - /// Default value of the object - /// The T object - T Read(string key, T @default = default(T)); + /// Type of object retrieved. + /// Key of the object. + /// Default value of the object. + /// The T object. + T Read(string key, T @default = default); /// /// Retrieves a single item by its key in composite. /// - /// Type of object retrieved - /// Key of the composite (that contains settings) - /// Key of the object - /// Default value of the object - /// The T object - T Read(string compositeKey, string key, T @default = default(T)); + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// Default value of the object. + /// The T object. + T Read(string compositeKey, string key, T @default = default); /// /// Saves a single item by its key. /// - /// Type of object saved - /// Key of the value saved - /// Object to save + /// Type of object saved. + /// Key of the value saved. + /// Object to save. void Save(string key, T value); /// @@ -61,34 +63,34 @@ public interface IObjectStorageHelper /// (refers to for complex/large objects) and for groups of settings which /// need to be treated in an atomic way. /// - /// Type of object saved - /// Key of the composite (that contains settings) - /// Objects to save + /// Type of object saved. + /// Key of the composite (that contains settings). + /// Objects to save. void Save(string compositeKey, IDictionary values); /// /// Determines whether a file already exists. /// - /// Key of the file (that contains object) - /// True if a value exists + /// Key of the file (that contains object). + /// True if a value exists. Task FileExistsAsync(string filePath); /// /// Retrieves an object from a file. /// - /// Type of object retrieved - /// Path to the file that contains the object - /// Default value of the object - /// Waiting task until completion with the object in the file - Task ReadFileAsync(string filePath, T @default = default(T)); + /// Type of object retrieved. + /// Path to the file that contains the object. + /// Default value of the object. + /// Waiting task until completion with the object in the file. + Task ReadFileAsync(string filePath, T @default = default); /// /// Saves an object inside a file. /// - /// Type of object saved - /// Path to the file that will contain the object - /// Object to save - /// Waiting task until completion + /// Type of object saved. + /// Path to the file that will contain the object. + /// Object to save. + /// Waiting task until completion. Task SaveFileAsync(string filePath, T value); } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/LocalObjectStorageHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/LocalObjectStorageHelper.cs index fedf4b806f8..30df84933e3 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/LocalObjectStorageHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/LocalObjectStorageHelper.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using Windows.Storage; namespace Microsoft.Toolkit.Uwp.Helpers @@ -9,20 +10,21 @@ namespace Microsoft.Toolkit.Uwp.Helpers /// /// Store data in the Local environment (only on the current device). /// + [Obsolete("LocalObjectStorageHelper is deprecated and has been superceded by the ApplicationDataStorageHelper. To upgrade, simply swap any LocalObjectStorageHelper instances with ApplicationDataStorageHelper.GetCurrent(serializer). The underlying interfaces are nearly identical but now with even more features available, such as deletion and access to user specific data stores!")] public class LocalObjectStorageHelper : BaseObjectStorageHelper { /// /// Initializes a new instance of the class, /// which can read and write data using the provided ; /// In 6.1 and older the default Serializer was based on Newtonsoft.Json. - /// To implement an based on System.Text.Json, Newtonsoft.Json, or DataContractJsonSerializer see https://aka.ms/wct/storagehelper-migration + /// To implement an based on System.Text.Json, Newtonsoft.Json, or DataContractJsonSerializer see https://aka.ms/wct/storagehelper-migration. /// /// The serializer to use. public LocalObjectStorageHelper(IObjectSerializer objectSerializer) : base(objectSerializer) { - Settings = ApplicationData.Current.LocalSettings; - Folder = ApplicationData.Current.LocalFolder; + this.Settings = ApplicationData.Current.LocalSettings; + this.Folder = ApplicationData.Current.LocalFolder; } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/SystemSerializer.cs b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/SystemSerializer.cs index 12369315c6b..7c6ad802cf4 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/SystemSerializer.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/ObjectStorage/SystemSerializer.cs @@ -12,6 +12,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers /// A bare-bones serializer which knows how to deal with primitive types and strings only. It will store them directly based on the API. /// It is recommended for more complex scenarios to implement your own based on System.Text.Json, Newtonsoft.Json, or DataContractJsonSerializer see https://aka.ms/wct/storagehelper-migration /// + [Obsolete("SystemSerializer has been migrated to the Microsoft.Toolkit (CommunityToolkit.Common) package.")] public class SystemSerializer : IObjectSerializer { /// @@ -46,4 +47,4 @@ public object Serialize(T value) return value; } } -} \ No newline at end of file +} diff --git a/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs b/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs index 0fa2dc96223..d55ccddccdc 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Microsoft.Toolkit.Helpers; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.Security.ExchangeActiveSyncProvisioning; @@ -23,9 +24,9 @@ namespace Microsoft.Toolkit.Uwp.Helpers public sealed class SystemInformation { /// - /// The instance used to save and retrieve application settings. + /// The instance used to save and retrieve application settings. /// - private readonly LocalObjectStorageHelper _localObjectStorageHelper = new(new SystemSerializer()); + private readonly ApplicationDataStorageHelper _settingsStorage = ApplicationDataStorageHelper.GetCurrent(); /// /// The starting time of the current application session (since app launch or last move to foreground). @@ -216,7 +217,7 @@ public TimeSpan AppUptime if (LaunchCount > 0) { var subSessionLength = DateTime.UtcNow.Subtract(_sessionStart).Ticks; - var uptimeSoFar = _localObjectStorageHelper.Read(nameof(AppUptime)); + var uptimeSoFar = _settingsStorage.Read(nameof(AppUptime)); return new(uptimeSoFar + subSessionLength); } @@ -232,9 +233,9 @@ public TimeSpan AppUptime /// The amount to time to add public void AddToAppUptime(TimeSpan duration) { - var uptimeSoFar = _localObjectStorageHelper.Read(nameof(AppUptime)); + var uptimeSoFar = _settingsStorage.Read(nameof(AppUptime)); - _localObjectStorageHelper.Save(nameof(AppUptime), uptimeSoFar + duration.Ticks); + _settingsStorage.Save(nameof(AppUptime), uptimeSoFar + duration.Ticks); } /// @@ -245,8 +246,8 @@ public void ResetLaunchCount() LastResetTime = DateTime.UtcNow; LaunchCount = 0; - _localObjectStorageHelper.Save(nameof(LastResetTime), LastResetTime.ToFileTimeUtc()); - _localObjectStorageHelper.Save(nameof(LaunchCount), LaunchCount); + _settingsStorage.Save(nameof(LastResetTime), LastResetTime.ToFileTimeUtc()); + _settingsStorage.Save(nameof(LaunchCount), LaunchCount); } /// @@ -258,8 +259,8 @@ public void TrackAppUse(IActivatedEventArgs args, XamlRoot xamlRoot = null) { if (args.PreviousExecutionState is ApplicationExecutionState.ClosedByUser or ApplicationExecutionState.NotRunning) { - LaunchCount = _localObjectStorageHelper.Read(nameof(LaunchCount)) + 1; - TotalLaunchCount = _localObjectStorageHelper.Read(nameof(TotalLaunchCount)) + 1; + LaunchCount = _settingsStorage.Read(nameof(LaunchCount)) + 1; + TotalLaunchCount = _settingsStorage.Read(nameof(TotalLaunchCount)) + 1; // In case we upgraded the properties, make TotalLaunchCount is correct if (TotalLaunchCount < LaunchCount) @@ -267,21 +268,21 @@ public void TrackAppUse(IActivatedEventArgs args, XamlRoot xamlRoot = null) TotalLaunchCount = LaunchCount; } - _localObjectStorageHelper.Save(nameof(LaunchCount), LaunchCount); - _localObjectStorageHelper.Save(nameof(TotalLaunchCount), TotalLaunchCount); + _settingsStorage.Save(nameof(LaunchCount), LaunchCount); + _settingsStorage.Save(nameof(TotalLaunchCount), TotalLaunchCount); LaunchTime = DateTime.UtcNow; - var lastLaunch = _localObjectStorageHelper.Read(nameof(LastLaunchTime)); + var lastLaunch = _settingsStorage.Read(nameof(LastLaunchTime)); LastLaunchTime = lastLaunch != 0 ? DateTime.FromFileTimeUtc(lastLaunch) : LaunchTime; - _localObjectStorageHelper.Save(nameof(LastLaunchTime), LaunchTime.ToFileTimeUtc()); - _localObjectStorageHelper.Save(nameof(AppUptime), 0L); + _settingsStorage.Save(nameof(LastLaunchTime), LaunchTime.ToFileTimeUtc()); + _settingsStorage.Save(nameof(AppUptime), 0L); - var lastResetTime = _localObjectStorageHelper.Read(nameof(LastResetTime)); + var lastResetTime = _settingsStorage.Read(nameof(LastResetTime)); LastResetTime = lastResetTime != 0 ? DateTime.FromFileTimeUtc(lastResetTime) @@ -321,20 +322,20 @@ private void UpdateVisibility(bool visible) else { var subSessionLength = DateTime.UtcNow.Subtract(_sessionStart).Ticks; - var uptimeSoFar = _localObjectStorageHelper.Read(nameof(AppUptime)); + var uptimeSoFar = _settingsStorage.Read(nameof(AppUptime)); - _localObjectStorageHelper.Save(nameof(AppUptime), uptimeSoFar + subSessionLength); + _settingsStorage.Save(nameof(AppUptime), uptimeSoFar + subSessionLength); } } private bool DetectIfFirstUse() { - if (_localObjectStorageHelper.KeyExists(nameof(IsFirstRun))) + if (_settingsStorage.KeyExists(nameof(IsFirstRun))) { return false; } - _localObjectStorageHelper.Save(nameof(IsFirstRun), true); + _settingsStorage.Save(nameof(IsFirstRun), true); return true; } @@ -347,13 +348,13 @@ private bool DetectIfFirstUse() // is ever called. That is, this is either the first time the app has been launched, or the first // time a previously existing app has run this method (or has run it after a new update of the app). // In this case, save the current version and report the same version as previous version installed. - if (!_localObjectStorageHelper.KeyExists(nameof(currentVersion))) + if (!_settingsStorage.KeyExists(nameof(currentVersion))) { - _localObjectStorageHelper.Save(nameof(currentVersion), currentVersion); + _settingsStorage.Save(nameof(currentVersion), currentVersion); } else { - var previousVersion = _localObjectStorageHelper.Read(nameof(currentVersion)); + var previousVersion = _settingsStorage.Read(nameof(currentVersion)); // There are two possible cases if the "currentVersion" key exists: // 1) The previous version is different than the current one. This means that the application @@ -363,7 +364,7 @@ private bool DetectIfFirstUse() // In this case we have nothing to do and just return the previous version installed to be the same. if (currentVersion != previousVersion) { - _localObjectStorageHelper.Save(nameof(currentVersion), currentVersion); + _settingsStorage.Save(nameof(currentVersion), currentVersion); return (true, previousVersion.ToPackageVersion()); } @@ -374,28 +375,28 @@ private bool DetectIfFirstUse() private DateTime DetectFirstUseTime() { - if (_localObjectStorageHelper.KeyExists(nameof(FirstUseTime))) + if (_settingsStorage.KeyExists(nameof(FirstUseTime))) { - var firstUse = _localObjectStorageHelper.Read(nameof(FirstUseTime)); + var firstUse = _settingsStorage.Read(nameof(FirstUseTime)); return DateTime.FromFileTimeUtc(firstUse); } DateTime utcNow = DateTime.UtcNow; - _localObjectStorageHelper.Save(nameof(FirstUseTime), utcNow.ToFileTimeUtc()); + _settingsStorage.Save(nameof(FirstUseTime), utcNow.ToFileTimeUtc()); return utcNow; } private PackageVersion DetectFirstVersionInstalled() { - if (_localObjectStorageHelper.KeyExists(nameof(FirstVersionInstalled))) + if (_settingsStorage.KeyExists(nameof(FirstVersionInstalled))) { - return _localObjectStorageHelper.Read(nameof(FirstVersionInstalled)).ToPackageVersion(); + return _settingsStorage.Read(nameof(FirstVersionInstalled)).ToPackageVersion(); } - _localObjectStorageHelper.Save(nameof(FirstVersionInstalled), ApplicationVersion.ToFormattedString()); + _settingsStorage.Save(nameof(FirstVersionInstalled), ApplicationVersion.ToFormattedString()); return ApplicationVersion; } diff --git a/Microsoft.Toolkit/Extensions/ISettingsStorageHelperExtensions.cs b/Microsoft.Toolkit/Extensions/ISettingsStorageHelperExtensions.cs new file mode 100644 index 00000000000..765408d92ff --- /dev/null +++ b/Microsoft.Toolkit/Extensions/ISettingsStorageHelperExtensions.cs @@ -0,0 +1,84 @@ +// 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 more information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Toolkit.Helpers; + +namespace Microsoft.Toolkit.Extensions +{ + /// + /// Helpers methods for working with implementations. + /// + public static class ISettingsStorageHelperExtensions + { + /// + /// Attempts to read the provided key and return the value. + /// If the key is not found, the fallback value will be used instead. + /// + /// The type of key used to lookup the object. + /// The type of object value expected. + /// The storage helper instance fo read from. + /// The key of the target object. + /// An alternative value returned if the read fails. + /// The value of the target object, or the fallback value. + public static TValue? GetValueOrDefault(this ISettingsStorageHelper storageHelper, TKey key, TValue? fallback = default) + where TKey : notnull + { + if (storageHelper.TryRead(key, out TValue? storedValue)) + { + return storedValue; + } + else + { + return fallback; + } + } + + /// + /// Read the key in the storage helper instance and get the value. + /// + /// The type of key used to lookup the object. + /// The type of object value expected. + /// The storage helper instance fo read from. + /// The key of the target object. + /// The value of the target object + /// Throws when the key is not found in storage. + public static TValue? Read(this ISettingsStorageHelper storageHelper, TKey key) + where TKey : notnull + { + if (storageHelper.TryRead(key, out TValue? value)) + { + return value; + } + else + { + ThrowKeyNotFoundException(key); + return default; + } + } + + /// + /// Deletes a key from storage. + /// + /// The type of key used to lookup the object. + /// The storage helper instance to delete from. + /// The key of the target object. + /// Throws when the key is not found in storage. + public static void Delete(this ISettingsStorageHelper storageHelper, TKey key) + where TKey : notnull + { + if (!storageHelper.TryDelete(key)) + { + ThrowKeyNotFoundException(key); + } + } + + private static void ThrowKeyNotFoundException(TKey key) + { + throw new KeyNotFoundException($"The given key '{key}' was not present"); + } + } +} diff --git a/Microsoft.Toolkit/Helpers/ObjectStorage/DirectoryItemType.cs b/Microsoft.Toolkit/Helpers/ObjectStorage/DirectoryItemType.cs new file mode 100644 index 00000000000..150fa3770ac --- /dev/null +++ b/Microsoft.Toolkit/Helpers/ObjectStorage/DirectoryItemType.cs @@ -0,0 +1,27 @@ +// 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 more information. + +namespace Microsoft.Toolkit.Helpers +{ + /// + /// Represents the types of items available in a directory. + /// + public enum DirectoryItemType + { + /// + /// The item is neither a file or a folder. + /// + None, + + /// + /// Represents a file type item. + /// + File, + + /// + /// Represents a folder type item. + /// + Folder + } +} diff --git a/Microsoft.Toolkit/Helpers/ObjectStorage/IFileStorageHelper.cs b/Microsoft.Toolkit/Helpers/ObjectStorage/IFileStorageHelper.cs new file mode 100644 index 00000000000..df3b1600c39 --- /dev/null +++ b/Microsoft.Toolkit/Helpers/ObjectStorage/IFileStorageHelper.cs @@ -0,0 +1,60 @@ +// 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 more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Helpers +{ + /// + /// Service interface used to store data in a directory/file-system via files and folders. + /// + /// This interface is meant to help abstract file storage operations across platforms in a library, + /// but the actual behavior will be up to the implementer. Such as, we don't provide a sense of a current directory, + /// so an implementor should consider using full paths to support any file operations. Otherwise, a "directory aware" + /// implementation could be achieved with a current directory field and traversal functions, in which case relative paths would be applicable. + /// + public interface IFileStorageHelper + { + /// + /// Retrieves an object from a file. + /// + /// Type of object retrieved. + /// Path to the file that contains the object. + /// Default value of the object. + /// Waiting task until completion with the object in the file. + Task ReadFileAsync(string filePath, T? @default = default); + + /// + /// Retrieves the listings for a folder and the item types. + /// + /// The path to the target folder. + /// A list of item types and names in the target folder. + Task> ReadFolderAsync(string folderPath); + + /// + /// Saves an object inside a file. + /// + /// Type of object saved. + /// Path to the file that will contain the object. + /// Object to save. + /// Waiting task until completion. + Task CreateFileAsync(string filePath, T value); + + /// + /// Ensure a folder exists at the folder path specified. + /// + /// The path and name of the target folder. + /// Waiting task until completion. + Task CreateFolderAsync(string folderPath); + + /// + /// Deletes a file or folder item. + /// + /// The path to the item for deletion. + /// Waiting task until completion. + Task DeleteItemAsync(string itemPath); + } +} diff --git a/Microsoft.Toolkit/Helpers/ObjectStorage/IObjectSerializer.cs b/Microsoft.Toolkit/Helpers/ObjectStorage/IObjectSerializer.cs new file mode 100644 index 00000000000..5a8177b4f78 --- /dev/null +++ b/Microsoft.Toolkit/Helpers/ObjectStorage/IObjectSerializer.cs @@ -0,0 +1,28 @@ +// 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 more information. + +namespace Microsoft.Toolkit.Helpers +{ + /// + /// A basic serialization service. + /// + public interface IObjectSerializer + { + /// + /// Serialize an object into a string. It is recommended to use strings as the final format for objects. + /// + /// The type of the object to serialize. + /// The object to serialize. + /// The serialized object. + string? Serialize(T value); + + /// + /// Deserialize string into an object of the given type. + /// + /// The type of the deserialized object. + /// The string to deserialize. + /// The deserialized object. + T Deserialize(string value); + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit/Helpers/ObjectStorage/ISettingsStorageHelper.cs b/Microsoft.Toolkit/Helpers/ObjectStorage/ISettingsStorageHelper.cs new file mode 100644 index 00000000000..c8f9713b5d8 --- /dev/null +++ b/Microsoft.Toolkit/Helpers/ObjectStorage/ISettingsStorageHelper.cs @@ -0,0 +1,45 @@ +// 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 more information. + +using System.Collections.Generic; + +namespace Microsoft.Toolkit.Helpers +{ + /// + /// Service interface used to store data using key value pairs. + /// + /// The type of keys to use for accessing values. + public interface ISettingsStorageHelper + where TKey : notnull + { + /// + /// Retrieves a single item by its key. + /// + /// Type of object retrieved. + /// Key of the object. + /// The object for key. + /// A boolean indicator of success. + bool TryRead(TKey key, out TValue? value); + + /// + /// Saves a single item by its key. + /// + /// Type of object saved. + /// Key of the value saved. + /// Object to save. + void Save(TKey key, TValue value); + + /// + /// Deletes a single item by its key. + /// + /// Key of the object. + /// A boolean indicator of success. + bool TryDelete(TKey key); + + /// + /// Clear all keys and values from the settings store. + /// + void Clear(); + } +} diff --git a/Microsoft.Toolkit/Helpers/ObjectStorage/SystemSerializer.cs b/Microsoft.Toolkit/Helpers/ObjectStorage/SystemSerializer.cs new file mode 100644 index 00000000000..62d1b681c06 --- /dev/null +++ b/Microsoft.Toolkit/Helpers/ObjectStorage/SystemSerializer.cs @@ -0,0 +1,46 @@ +// 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 more information. + +using System; +using System.Reflection; + +namespace Microsoft.Toolkit.Helpers +{ + /// + /// A bare-bones serializer which knows how to deal with primitive types and strings only. + /// It is recommended for more complex scenarios to implement your own based on System.Text.Json, Newtonsoft.Json, or DataContractJsonSerializer see https://aka.ms/wct/storagehelper-migration + /// + public class SystemSerializer : IObjectSerializer + { + /// + /// Take a primitive value from storage and return it as the requested type using the API. + /// + /// Type to convert value to. + /// Value from storage to convert. + /// Deserialized value or default value. + public T Deserialize(string value) + { + var type = typeof(T); + var typeInfo = type.GetTypeInfo(); + + if (typeInfo.IsPrimitive || type == typeof(string)) + { + return (T)Convert.ChangeType(value, type); + } + + throw new NotSupportedException("This serializer can only handle primitive types and strings. Please implement your own IObjectSerializer for more complex scenarios."); + } + + /// + /// Returns the value so that it can be serialized directly. + /// + /// Type to serialize from. + /// Value to serialize. + /// String representation of value. + public string? Serialize(T value) + { + return value?.ToString(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit/Microsoft.Toolkit.csproj b/Microsoft.Toolkit/Microsoft.Toolkit.csproj index 29cc25a881d..6efb4c97210 100644 --- a/Microsoft.Toolkit/Microsoft.Toolkit.csproj +++ b/Microsoft.Toolkit/Microsoft.Toolkit.csproj @@ -21,9 +21,10 @@ NETSTANDARD2_1_OR_GREATER - + + \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer.cs b/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer.cs index 35551ee7421..0cfcb64d0ec 100644 --- a/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer.cs +++ b/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer.cs @@ -7,11 +7,13 @@ using Microsoft.Toolkit.Uwp.Helpers; using Newtonsoft.Json; -namespace UnitTests.UWP.Helpers +namespace UnitTests.Helpers { /// /// This is a Serializer which should mimic the previous functionality of 6.1.1 release of the Toolkit with Newtonsoft.Json. + /// Based on . /// + [Obsolete] internal class JsonObjectSerializer : IObjectSerializer { public T Deserialize(object value) diff --git a/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer2.cs b/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer2.cs new file mode 100644 index 00000000000..8721acc6920 --- /dev/null +++ b/UnitTests/UnitTests.UWP/Helpers/JsonObjectSerializer2.cs @@ -0,0 +1,48 @@ +// 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 more information. + +using System; +using System.Reflection; +using Microsoft.Toolkit.Helpers; +using Newtonsoft.Json; + +namespace UnitTests.Helpers +{ + /// + /// This is a Serializer which should mimic the previous functionality of 6.1.1 release of the Toolkit with Newtonsoft.Json. + /// Based on . + /// + internal class JsonObjectSerializer2 : IObjectSerializer + { + public T Deserialize(string value) + { + var type = typeof(T); + var typeInfo = type.GetTypeInfo(); + + // Note: If you're creating a new app, you could just use the serializer directly. + // This if/return combo is to maintain compatibility with 6.1.1 + if (typeInfo.IsPrimitive || type == typeof(string)) + { + return (T)Convert.ChangeType(value, type); + } + + return JsonConvert.DeserializeObject(value); + } + + public string Serialize(T value) + { + var type = typeof(T); + var typeInfo = type.GetTypeInfo(); + + // Note: If you're creating a new app, you could just use the serializer directly. + // This if/return combo is to maintain compatibility with 6.1.1 + if (typeInfo.IsPrimitive || type == typeof(string)) + { + return value.ToString(); + } + + return JsonConvert.SerializeObject(value); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer.cs b/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer.cs index 139f16135f8..b4b47f73f78 100644 --- a/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer.cs +++ b/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer.cs @@ -6,11 +6,13 @@ using System.Text.Json; using Microsoft.Toolkit.Uwp.Helpers; -namespace UnitTests.UWP.Helpers +namespace UnitTests.Helpers { /// /// Example class of writing a new that uses System.Text.Json. + /// Based on . /// + [Obsolete] internal class SystemTextJsonSerializer : IObjectSerializer { public T Deserialize(object value) => JsonSerializer.Deserialize(value as string); diff --git a/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer2.cs b/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer2.cs new file mode 100644 index 00000000000..caae64f288c --- /dev/null +++ b/UnitTests/UnitTests.UWP/Helpers/SystemTextJsonSerializer2.cs @@ -0,0 +1,20 @@ +// 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 more information. + +using System.Text.Json; +using Microsoft.Toolkit.Helpers; + +namespace UnitTests.Helpers +{ + /// + /// Example class of writing a new that uses System.Text.Json. + /// Based on . + /// + internal class SystemTextJsonSerializer2 : IObjectSerializer + { + public T Deserialize(string value) => JsonSerializer.Deserialize(value); + + public string Serialize(T value) => JsonSerializer.Serialize(value); + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/Helpers/Test_ApplicationDataStorageHelper.cs b/UnitTests/UnitTests.UWP/Helpers/Test_ApplicationDataStorageHelper.cs new file mode 100644 index 00000000000..3181736f824 --- /dev/null +++ b/UnitTests/UnitTests.UWP/Helpers/Test_ApplicationDataStorageHelper.cs @@ -0,0 +1,181 @@ +// 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 more information. + +using Microsoft.Toolkit.Uwp.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnitTests.Helpers +{ + [TestClass] + public class Test_ApplicationDataStorageHelper + { + private readonly ApplicationDataStorageHelper _settingsStorage_System = ApplicationDataStorageHelper.GetCurrent(); + private readonly ApplicationDataStorageHelper _settingsStorage_JsonCompat = ApplicationDataStorageHelper.GetCurrent(new JsonObjectSerializer2()); + private readonly ApplicationDataStorageHelper _settingsStorage_JsonNew = ApplicationDataStorageHelper.GetCurrent(new SystemTextJsonSerializer2()); + + /// + /// Checks that we're running 10.0.3 version of Newtonsoft.Json package which we used in 6.1.1. + /// + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_CheckNewtonsoftVersion() + { + var version = typeof(Newtonsoft.Json.JsonSerializer).Assembly.GetName().Version; + Assert.AreEqual(10, version.Major); + Assert.AreEqual(0, version.Minor); + Assert.AreEqual(0, version.Revision); // Apparently the file revision was not updated for the updated package + } + + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_LegacyIntTest() + { + string key = "LifeUniverseAndEverything"; + + int input = 42; + + // Use our previous Json layer to store value + _settingsStorage_JsonCompat.Save(key, input); + + // But try and read from our new system to see if it works + int output = _settingsStorage_System.Read(key, 0); + + Assert.AreEqual(input, output); + } + + /// + /// If we try and deserialize a complex type with the , we do a check ourselves and will throw our own exception. + /// + [TestCategory("Helpers")] + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Test_StorageHelper_LegacyDateTestFailure() + { + string key = "ChristmasDay"; + + DateTime input = new DateTime(2017, 12, 25); + + _settingsStorage_JsonCompat.Save(key, input); + + // now read it as int to valid that the change works + _ = _settingsStorage_System.Read(key, DateTime.Today); + } + + /// + /// The doesn't support complex types, since it just passes through directly. + /// We'll get the argument exception from the API. + /// + [TestCategory("Helpers")] + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Test_StorageHelper_DateTestFailure() + { + string key = "Today"; + + _settingsStorage_System.Save(key, DateTime.Today); + _settingsStorage_System.TryRead(key, out _); + } + + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_LegacyInternalClassTest() + { + string key = "Contact"; + + UI.Person input = new UI.Person() { Name = "Joe Bloggs", Age = 42 }; + + // simulate previous version by generating json and manually inserting it as string + _settingsStorage_JsonCompat.Save(key, input); + + // now read it as int to valid that the change works + UI.Person output = _settingsStorage_JsonCompat.Read(key, null); + + Assert.IsNotNull(output); + Assert.AreEqual(input.Name, output.Name); + Assert.AreEqual(input.Age, output.Age); + } + + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_LegacyPublicClassTest() + { + string key = "Contact"; + + // Here's we're serializing a different class which has the same properties as our other class below. + UI.Person input = new UI.Person() { Name = "Joe Bloggs", Age = 42 }; + + // simulate previous version by generating json and manually inserting it as string + _settingsStorage_JsonCompat.Save(key, input); + + // now read it as int to valid that the change works + Person output = _settingsStorage_JsonCompat.Read(key, null); + + Assert.IsNotNull(output); + Assert.AreEqual(input.Name, output.Name); + Assert.AreEqual(input.Age, output.Age); + } + + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_IntTest() + { + string key = "NewLifeUniverseAndEverything"; + + int input = 42; + + _settingsStorage_System.Save(key, input); + + // now read it as int to valid that the change works + int output = _settingsStorage_System.Read(key, 0); + + Assert.AreEqual(input, output); + } + + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_NewDateTest() + { + string key = "NewChristmasDay"; + + DateTime input = new DateTime(2017, 12, 25); + + _settingsStorage_JsonNew.Save(key, input); + + // now read it as int to valid that the change works + DateTime output = _settingsStorage_JsonNew.Read(key, DateTime.Today); + + Assert.AreEqual(input, output); + } + + [TestCategory("Helpers")] + [TestMethod] + public void Test_StorageHelper_NewPersonTest() + { + string key = "Contact"; + + Person input = new Person() { Name = "Joe Bloggs", Age = 42 }; + + _settingsStorage_JsonNew.Save(key, input); + + // now read it as int to valid that the change works + Person output = _settingsStorage_JsonNew.Read(key, null); + + Assert.IsNotNull(output); + Assert.AreEqual(input.Name, output.Name); + Assert.AreEqual(input.Age, output.Age); + } + + public class Person + { + public string Name { get; set; } + + public int Age { get; set; } + } + } +} diff --git a/UnitTests/UnitTests.UWP/Helpers/Test_StorageHelper.cs b/UnitTests/UnitTests.UWP/Helpers/Test_StorageHelper.cs index 823753060e8..c7d66e3dda5 100644 --- a/UnitTests/UnitTests.UWP/Helpers/Test_StorageHelper.cs +++ b/UnitTests/UnitTests.UWP/Helpers/Test_StorageHelper.cs @@ -6,7 +6,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; -using UnitTests.UWP.Helpers; +using UnitTests.Helpers; using Windows.Storage; namespace UnitTests.Helpers @@ -14,10 +14,12 @@ namespace UnitTests.Helpers [TestClass] public class Test_StorageHelper { - private LocalObjectStorageHelper _localStorageHelperSystem = new LocalObjectStorageHelper(new SystemSerializer()); - private LocalObjectStorageHelper _localStorageHelperJsonCompat = new LocalObjectStorageHelper(new JsonObjectSerializer()); - - private LocalObjectStorageHelper _localStorageHelperJsonNew = new LocalObjectStorageHelper(new SystemTextJsonSerializer()); + [Obsolete] + private readonly LocalObjectStorageHelper _localStorageHelperSystem = new LocalObjectStorageHelper(new SystemSerializer()); + [Obsolete] + private readonly LocalObjectStorageHelper _localStorageHelperJsonCompat = new LocalObjectStorageHelper(new JsonObjectSerializer()); + [Obsolete] + private readonly LocalObjectStorageHelper _localStorageHelperJsonNew = new LocalObjectStorageHelper(new SystemTextJsonSerializer()); /// /// Checks that we're running 10.0.3 version of Newtonsoft.Json package which we used in 6.1.1. @@ -34,6 +36,7 @@ public void Test_StorageHelper_CheckNewtonsoftVersion() [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_LegacyIntTest() { string key = "LifeUniverseAndEverything"; @@ -54,6 +57,7 @@ public void Test_StorageHelper_LegacyIntTest() /// [TestCategory("Helpers")] [TestMethod] + [Obsolete] [ExpectedException(typeof(NotSupportedException))] public void Test_StorageHelper_LegacyDateTestFailure() { @@ -73,6 +77,7 @@ public void Test_StorageHelper_LegacyDateTestFailure() /// [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_DateTestFailure() { Exception expectedException = null; @@ -93,6 +98,7 @@ public void Test_StorageHelper_DateTestFailure() [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_LegacyInternalClassTest() { string key = "Contact"; @@ -112,6 +118,7 @@ public void Test_StorageHelper_LegacyInternalClassTest() [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_LegacyPublicClassTest() { string key = "Contact"; @@ -132,6 +139,7 @@ public void Test_StorageHelper_LegacyPublicClassTest() [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_IntTest() { string key = "NewLifeUniverseAndEverything"; @@ -148,6 +156,7 @@ public void Test_StorageHelper_IntTest() [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_NewDateTest() { string key = "NewChristmasDay"; @@ -164,6 +173,7 @@ public void Test_StorageHelper_NewDateTest() [TestCategory("Helpers")] [TestMethod] + [Obsolete] public void Test_StorageHelper_NewPersonTest() { string key = "Contact"; diff --git a/UnitTests/UnitTests.UWP/Helpers/Test_SystemInformation.cs b/UnitTests/UnitTests.UWP/Helpers/Test_SystemInformation.cs index 78f12378913..466398d6809 100644 --- a/UnitTests/UnitTests.UWP/Helpers/Test_SystemInformation.cs +++ b/UnitTests/UnitTests.UWP/Helpers/Test_SystemInformation.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using Microsoft.Toolkit.Helpers; using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; using Windows.ApplicationModel; @@ -57,10 +58,10 @@ public void Test_SystemInformation_ConsistentInfoForStartupWithUpdate() // Simulate a first app startup _ = (SystemInformation)Activator.CreateInstance(typeof(SystemInformation), nonPublic: true); - LocalObjectStorageHelper localObjectStorageHelper = new(new SystemSerializer()); + var settingsStorage = ApplicationDataStorageHelper.GetCurrent(); PackageVersion previousVersion = new() { Build = 42, Major = 1111, Minor = 2222, Revision = 12345 }; - localObjectStorageHelper.Save("currentVersion", previousVersion.ToFormattedString()); + settingsStorage.Save("currentVersion", previousVersion.ToFormattedString()); var systemInformation = (SystemInformation)Activator.CreateInstance(typeof(SystemInformation), nonPublic: true); var currentAppVersion = Package.Current.Id.Version; diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index 0323d553a9a..215c4582b40 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -1,4 +1,4 @@ - + Debug @@ -175,10 +175,13 @@ + + +