From 7e7554f52c5df07d2455aa3d0d66e482dcdf5901 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 12 Jul 2021 14:40:36 -0700 Subject: [PATCH 1/5] wip --- .../CommunityToolkit.Graph.csproj | 1 + .../BaseRoamingSettingsDataStore.cs | 2 +- .../IRoamingSettingsDataStore.cs | 6 +- .../RoamingSettings/OneDriveDataSource.cs | 2 +- .../RoamingSettings/OneDriveDataStore.cs | 2 +- .../RoamingSettings/RoamingSettingsHelper.cs | 37 ++++++++- .../RoamingSettings/UserExtensionDataStore.cs | 2 +- .../UserExtensionsDataSource.cs | 2 +- .../ObjectStorage/IDictionaryStorageHelper.cs | 75 +++++++++++++++++++ .../ObjectStorage/IFileStorageHelper.cs | 65 ++++++++++++++++ 10 files changed, 182 insertions(+), 12 deletions(-) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs (99%) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs (90%) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/OneDriveDataSource.cs (97%) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/OneDriveDataStore.cs (98%) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/RoamingSettingsHelper.cs (84%) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/UserExtensionDataStore.cs (99%) rename {CommunityToolkit.Graph.Uwp => CommunityToolkit.Graph}/Helpers/RoamingSettings/UserExtensionsDataSource.cs (99%) create mode 100644 CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs create mode 100644 CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs diff --git a/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj b/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj index 8d501e3..6b23a18 100644 --- a/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj +++ b/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj @@ -10,6 +10,7 @@ - ProviderExtensions: Extension on IProvider for accessing a pre-configured GraphServiceClient instance. Windows Community Toolkit Graph Provider Extensions + 9.0 diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs similarity index 99% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs index 844b7ee..5dc21cb 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs @@ -10,7 +10,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Windows.Storage; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// A base class for easily building roaming settings helper implementations. diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs similarity index 90% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs index 00186ed..3c91699 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs @@ -5,14 +5,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Toolkit.Uwp.Helpers; +using CommunityToolkit.Graph.ObjectStorage; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// Defines the contract for creating storage containers used for roaming data. /// - public interface IRoamingSettingsDataStore : IObjectStorageHelper + public interface IRoamingSettingsDataStore : IDictionaryStorageHelper, IFileStorageHelper { /// /// Gets a value indicating whether the values should immediately sync or not. diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/OneDriveDataSource.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataSource.cs similarity index 97% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/OneDriveDataSource.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataSource.cs index 9b95c99..d3ed8c2 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/OneDriveDataSource.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataSource.cs @@ -10,7 +10,7 @@ using Microsoft.Graph; using Microsoft.Toolkit.Uwp.Helpers; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// Helpers for interacting with files in the special OneDrive AppRoot folder. diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/OneDriveDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs similarity index 98% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/OneDriveDataStore.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs index b1f95bd..700d525 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/OneDriveDataStore.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs @@ -9,7 +9,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Windows.Storage; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// A DataStore for managing roaming settings in OneDrive. diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/RoamingSettingsHelper.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs similarity index 84% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/RoamingSettingsHelper.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs index badd0c5..fc71e90 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/RoamingSettingsHelper.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs @@ -10,7 +10,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Windows.Storage; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// An enumeration of the available data storage methods for roaming data. @@ -31,7 +31,7 @@ public enum RoamingDataStore /// /// A helper class for syncing data to roaming data store. /// - public class RoamingSettingsHelper : IRoamingSettingsDataStore + public class RoamingSettingsHelper { /// /// Gets the internal data store instance. @@ -64,7 +64,7 @@ public class RoamingSettingsHelper : IRoamingSettingsDataStore /// Whether the values should immediately sync on change or wait until Sync is called explicitly. /// An object serializer for serialization of objects in the data store. /// A new instance of the RoamingSettingsHelper configured for the current user. - public static async Task CreateForCurrentUser(RoamingDataStore dataStore = RoamingDataStore.UserExtensions, bool syncOnInit = true, bool autoSync = true, IObjectSerializer serializer = null) + public static async Task CreateForCurrentUser(RoamingDataStore dataStore = RoamingDataStore.UserExtensions, bool syncOnInit = true, bool autoSync = true)//, IObjectSerializer serializer = null) { var provider = ProviderManager.Instance.GlobalProvider; if (provider == null || provider.State != ProviderState.SignedIn) @@ -73,7 +73,36 @@ public static async Task CreateForCurrentUser(RoamingData } var me = await provider.GetClient().Me.Request().GetAsync(); - return new RoamingSettingsHelper(me.Id, dataStore, syncOnInit, autoSync, serializer); + var userId = me.Id; + + // TODO: Infuse unique identifier from Graph registration into the storage name. + string dataStoreName = "communityToolkit.roamingSettings"; + + // if (serializer == null) + // { + // serializer = new SystemSerializer(); + // } + + IRoamingSettingsDataStore instance = null; + + switch (dataStore) + { + case RoamingDataStore.UserExtensions: + instance = new UserExtensionDataStore(userId, dataStoreName, serializer, autoSync); + break; + + case RoamingDataStore.OneDrive: + instance = new OneDriveDataStore(userId, dataStoreName, serializer, autoSync); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(dataStore)); + } + + if (syncOnInit) + { + _ = instance.Sync(); + } } /// diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/UserExtensionDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs similarity index 99% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/UserExtensionDataStore.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs index d450d81..657ca4f 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/UserExtensionDataStore.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs @@ -10,7 +10,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Windows.Storage; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// An IObjectStorageHelper implementation using open extensions on the Graph User for storing key/value pairs. diff --git a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/UserExtensionsDataSource.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionsDataSource.cs similarity index 99% rename from CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/UserExtensionsDataSource.cs rename to CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionsDataSource.cs index 61218c2..78aa307 100644 --- a/CommunityToolkit.Graph.Uwp/Helpers/RoamingSettings/UserExtensionsDataSource.cs +++ b/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionsDataSource.cs @@ -10,7 +10,7 @@ using CommunityToolkit.Graph.Extensions; using Microsoft.Graph; -namespace CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.RoamingSettings { /// /// Manages Graph interaction with open extensions on the user. diff --git a/CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs b/CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs new file mode 100644 index 0000000..5bc5788 --- /dev/null +++ b/CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; + +namespace CommunityToolkit.Graph.ObjectStorage +{ + /// + /// + /// + public interface IDictionaryStorageHelper + { + /// + /// Determines whether a setting already 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. + 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)); + + /// + /// 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)); + + /// + /// Saves a single item by its key. + /// + /// Type of object saved. + /// Key of the value saved. + /// Object to save. + void Save(string key, T value); + + /// + /// 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. + void Save(string compositeKey, IDictionary values); + + /// + /// + /// + /// + void Delete(string key); + + /// + /// + /// + /// + /// + void Delete(string key, string compositeKey); + } +} diff --git a/CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs b/CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs new file mode 100644 index 0000000..9dc859b --- /dev/null +++ b/CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CommunityToolkit.Graph.ObjectStorage +{ + /// + /// + /// + public interface IFileStorageHelper + { + /// + /// Determines whether a file already exists. + /// + /// Key of the file (that contains object). + /// True if a value exists. + Task FileExistsAsync(string filePath); + + /// + /// Determines whether a folder already exists. + /// + /// Key of the folder. + /// True if a value exists. + Task FolderExistsAsync(string folderPath); + + /// + /// 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)); + + /// + /// Retrieves all file listings for a 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 SaveFileAsync(string filePath, T value); + + /// + /// + /// + /// + /// + Task SaveFolderAsync(string folderPath); + + /// + /// + /// + /// + /// + Task DeleteItemAsync(string itemPath); + } +} From 057b6853f08711079bd45206bd2b87dcb5cb6b82 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Tue, 20 Jul 2021 16:27:14 -0700 Subject: [PATCH 2/5] Renamed RoamingSettings to ObjectStorage, updated to use new storage interfaces from WCT, and migrated to *.Graph package --- .../CommunityToolkit.Graph.csproj | 1 + .../IRemoteSettingsStorageHelper.cs | 39 +++ .../OneDriveDataSource.cs | 55 ++- .../ObjectStorage/OneDriveStorageHelper.cs | 98 ++++++ .../UserExtensionDataSource.cs} | 6 +- .../UserExtensionStorageHelper.cs | 327 ++++++++++++++++++ .../BaseRoamingSettingsDataStore.cs | 303 ---------------- .../IRoamingSettingsDataStore.cs | 65 ---- .../RoamingSettings/OneDriveDataStore.cs | 154 --------- .../RoamingSettings/RoamingSettingsHelper.cs | 217 ------------ .../RoamingSettings/UserExtensionDataStore.cs | 201 ----------- .../ObjectStorage/IDictionaryStorageHelper.cs | 75 ---- .../ObjectStorage/IFileStorageHelper.cs | 65 ---- SampleTest/MainPage.xaml | 3 - SampleTest/SampleTest.csproj | 8 - .../RoamingSettings/RoamingSettingsView.xaml | 79 ----- .../RoamingSettingsView.xaml.cs | 57 --- .../RoamingSettingsViewModel.cs | 195 ----------- .../ObjectStorage/Test_OneDriveDataStore.cs | 75 ++++ .../Test_UserExtensionDataStore.cs | 38 +- .../RoamingSettings/Test_OneDriveDataStore.cs | 154 --------- UnitTests/UnitTests.UWP/UnitTests.UWP.csproj | 13 +- nuget.config | 5 +- 23 files changed, 606 insertions(+), 1627 deletions(-) create mode 100644 CommunityToolkit.Graph/Helpers/ObjectStorage/IRemoteSettingsStorageHelper.cs rename CommunityToolkit.Graph/Helpers/{RoamingSettings => ObjectStorage}/OneDriveDataSource.cs (55%) create mode 100644 CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveStorageHelper.cs rename CommunityToolkit.Graph/Helpers/{RoamingSettings/UserExtensionsDataSource.cs => ObjectStorage/UserExtensionDataSource.cs} (97%) create mode 100644 CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionStorageHelper.cs delete mode 100644 CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs delete mode 100644 CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs delete mode 100644 CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs delete mode 100644 CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs delete mode 100644 CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs delete mode 100644 CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs delete mode 100644 CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs delete mode 100644 SampleTest/Samples/RoamingSettings/RoamingSettingsView.xaml delete mode 100644 SampleTest/Samples/RoamingSettings/RoamingSettingsView.xaml.cs delete mode 100644 SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs create mode 100644 UnitTests/UnitTests.UWP/ObjectStorage/Test_OneDriveDataStore.cs rename UnitTests/UnitTests.UWP/{RoamingSettings => ObjectStorage}/Test_UserExtensionDataStore.cs (73%) delete mode 100644 UnitTests/UnitTests.UWP/RoamingSettings/Test_OneDriveDataStore.cs diff --git a/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj b/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj index 6b23a18..8e105e2 100644 --- a/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj +++ b/CommunityToolkit.Graph/CommunityToolkit.Graph.csproj @@ -15,6 +15,7 @@ + diff --git a/CommunityToolkit.Graph/Helpers/ObjectStorage/IRemoteSettingsStorageHelper.cs b/CommunityToolkit.Graph/Helpers/ObjectStorage/IRemoteSettingsStorageHelper.cs new file mode 100644 index 0000000..68cd438 --- /dev/null +++ b/CommunityToolkit.Graph/Helpers/ObjectStorage/IRemoteSettingsStorageHelper.cs @@ -0,0 +1,39 @@ +// 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 System.Threading.Tasks; +using Microsoft.Toolkit.Helpers; + +namespace CommunityToolkit.Graph.Helpers.ObjectStorage +{ + /// + /// Describes a remote settings storage location with basic sync support. + /// + public interface IRemoteSettingsStorageHelper : ISettingsStorageHelper + { + /// + /// Gets or sets an event that fires whenever a sync request has completed. + /// + EventHandler SyncCompleted { get; set; } + + /// + /// gets or sets an event that fires whenever a remote sync request has failed. + /// + EventHandler SyncFailed { get; set; } + + /// + /// Gets a cache of the stored values, converted using the provided serializer. + /// + IDictionary Cache { get; } + + /// + /// Update the remote extension to match the local cache and retrieve any new keys. Any existing remote values are replaced. + /// + /// The freshly synced user extension. + Task Sync(); + } +} diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataSource.cs b/CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveDataSource.cs similarity index 55% rename from CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataSource.cs rename to CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveDataSource.cs index d3ed8c2..d697571 100644 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataSource.cs +++ b/CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveDataSource.cs @@ -2,15 +2,17 @@ // 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.IO; using System.Text; using System.Threading.Tasks; using CommunityToolkit.Authentication; using CommunityToolkit.Graph.Extensions; using Microsoft.Graph; -using Microsoft.Toolkit.Uwp.Helpers; +using Microsoft.Toolkit.Helpers; -namespace CommunityToolkit.Graph.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.ObjectStorage { /// /// Helpers for interacting with files in the special OneDrive AppRoot folder. @@ -19,23 +21,12 @@ internal static class OneDriveDataSource { private static GraphServiceClient Graph => ProviderManager.Instance.GlobalProvider?.GetClient(); - // Create a new file. - // This fails, because OneDrive doesn't like empty files. Use Update instead. - // public static async Task Create(string fileWithExt) - // { - // var driveItem = new DriveItem() - // { - // Name = fileWithExt, - // }; - // await Graph.Users[userId].Drive.Special.AppRoot.ItemWithPath(fileWithExt).Request().CreateAsync(driveItem); - // } - /// /// Updates or create a new file on the remote with the provided content. /// /// The type of object to save. /// A representing the asynchronous operation. - public static async Task Update(string userId, string fileWithExt, T fileContents, IObjectSerializer serializer) + public static async Task SetFileAsync(string userId, string fileWithExt, T fileContents, IObjectSerializer serializer) { var json = serializer.Serialize(fileContents) as string; using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); @@ -48,7 +39,7 @@ public static async Task Update(string userId, string fileWithExt, /// /// The type of object to return. /// A representing the asynchronous operation. - public static async Task Retrieve(string userId, string fileWithExt, IObjectSerializer serializer) + public static async Task GetFileAsync(string userId, string fileWithExt, IObjectSerializer serializer) { Stream stream = await Graph.Users[userId].Drive.Special.AppRoot.ItemWithPath(fileWithExt).Content.Request().GetAsync(); @@ -61,9 +52,41 @@ public static async Task Retrieve(string userId, string fileWithExt, IObje /// Delete the file from the remote. /// /// A representing the asynchronous operation. - public static async Task Delete(string userId, string fileWithExt) + public static async Task DeleteItemAsync(string userId, string fileWithExt) { await Graph.Users[userId].Drive.Special.AppRoot.ItemWithPath(fileWithExt).Request().DeleteAsync(); } + + public static async Task CreateFolderAsync(string userId, string folderPath) + { + var folderDriveItem = new DriveItem() + { + Name = folderPath, + Folder = new Folder(), + }; + + await Graph.Users[userId].Drive.Special.AppRoot.ItemWithPath(folderPath).Request().CreateAsync(folderDriveItem); + } + + public static async Task>> ReadFolderAsync(string userId, string folderPath) + { + IDriveItemChildrenCollectionPage folderContents = await Graph.Users[userId].Drive.Special.AppRoot.ItemWithPath(folderPath).Children.Request().GetAsync(); + + var results = new List>(); + foreach (var item in folderContents) + { + var itemType = (item.Folder != null) + ? DirectoryItemType.Folder + : item.Size != null + ? DirectoryItemType.File + : DirectoryItemType.None; + + var itemName = item.Name; + + results.Add(new Tuple(itemType, itemName)); + } + + return results; + } } } diff --git a/CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveStorageHelper.cs b/CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveStorageHelper.cs new file mode 100644 index 0000000..6470ea0 --- /dev/null +++ b/CommunityToolkit.Graph/Helpers/ObjectStorage/OneDriveStorageHelper.cs @@ -0,0 +1,98 @@ +// 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.Reflection; +using System.Threading.Tasks; +using CommunityToolkit.Authentication; +using CommunityToolkit.Graph.Extensions; +using Microsoft.Toolkit.Helpers; + +namespace CommunityToolkit.Graph.Helpers.ObjectStorage +{ + /// + /// A base class for easily building roaming settings helper implementations. + /// + public class OneDriveStorageHelper : IFileStorageHelper + { + /// + /// Gets the id of the Graph user. + /// + public string UserId { get; } + + /// + /// Gets an object serializer for converting objects in the data store. + /// + public IObjectSerializer Serializer { get; } + + /// + /// Creates a new instance using the userId retrieved from a Graph "Me" request. + /// + /// A serializer used for converting stored objects. + /// A new instance of the configured for the current Graph user. + public static async Task CreateForCurrentUserAsync(IObjectSerializer objectSerializer = null) + { + var provider = ProviderManager.Instance.GlobalProvider; + if (provider == null || provider.State != ProviderState.SignedIn) + { + throw new InvalidOperationException($"The {nameof(ProviderManager.GlobalProvider)} must be set and signed in to create a new {nameof(OneDriveStorageHelper)} for the current user."); + } + + var me = await provider.GetClient().Me.Request().GetAsync(); + var userId = me.Id; + + return new OneDriveStorageHelper(userId, objectSerializer); + } + + /// + /// Initializes a new instance of the class. + /// + /// The id of the target Graph user. + /// A serializer used for converting stored objects. + public OneDriveStorageHelper(string userId, IObjectSerializer objectSerializer = null) + { + UserId = userId ?? throw new ArgumentNullException(nameof(userId)); + Serializer = objectSerializer ?? new SystemSerializer(); + } + + /// + public async Task ItemExistsAsync(string itemName) + { + var result = await OneDriveDataSource.GetFileAsync(UserId, itemName, Serializer); + return result != null; + } + + /// + public async Task ReadFileAsync(string filePath, T @default = default) + { + return await OneDriveDataSource.GetFileAsync(UserId, filePath, Serializer) ?? @default; + } + + /// + public Task>> ReadFolderAsync(string folderPath) + { + return OneDriveDataSource.ReadFolderAsync(UserId, folderPath); + } + + /// + public async Task CreateFileAsync(string filePath, T value) + { + await OneDriveDataSource.SetFileAsync(UserId, filePath, value, Serializer); + } + + /// + public Task CreateFolderAsync(string folderPath) + { + return OneDriveDataSource.CreateFolderAsync(UserId, folderPath); + } + + /// + public Task DeleteItemAsync(string itemPath) + { + return OneDriveDataSource.DeleteItemAsync(UserId, itemPath); + } + } +} diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionsDataSource.cs b/CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionDataSource.cs similarity index 97% rename from CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionsDataSource.cs rename to CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionDataSource.cs index 78aa307..4665e1d 100644 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionsDataSource.cs +++ b/CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionDataSource.cs @@ -10,12 +10,12 @@ using CommunityToolkit.Graph.Extensions; using Microsoft.Graph; -namespace CommunityToolkit.Graph.Helpers.RoamingSettings +namespace CommunityToolkit.Graph.Helpers.ObjectStorage { /// /// Manages Graph interaction with open extensions on the user. /// - internal static class UserExtensionsDataSource + internal static class UserExtensionDataSource { private static GraphServiceClient Graph => ProviderManager.Instance.GlobalProvider?.GetClient(); @@ -91,7 +91,7 @@ public static async Task CreateExtension(string userId, string extens "\"extensionName\": \"" + extensionId + "\"," + "}"; - HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Post, requestUrl); + HttpRequestMessage hrm = new (HttpMethod.Post, requestUrl); hrm.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); await Graph.AuthenticationProvider.AuthenticateRequestAsync(hrm); HttpResponseMessage response = await Graph.HttpProvider.SendAsync(hrm); diff --git a/CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionStorageHelper.cs b/CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionStorageHelper.cs new file mode 100644 index 0000000..af28392 --- /dev/null +++ b/CommunityToolkit.Graph/Helpers/ObjectStorage/UserExtensionStorageHelper.cs @@ -0,0 +1,327 @@ +// 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.Reflection; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Authentication; +using CommunityToolkit.Graph.Extensions; +using Microsoft.Graph; +using Microsoft.Toolkit.Helpers; + +namespace CommunityToolkit.Graph.Helpers.ObjectStorage +{ + /// + /// An IObjectStorageHelper implementation using open extensions on the Graph User for storing key/value pairs. + /// + public class UserExtensionStorageHelper : IRemoteSettingsStorageHelper + { + private static readonly IList ReservedKeys = new List { "responseHeaders", "statusCode", "@odata.context" }; + private static readonly SemaphoreSlim SyncLock = new (1); + + /// + /// Gets or sets an event that fires whenever a sync request has completed. + /// + public EventHandler SyncCompleted { get; set; } + + /// + /// gets or sets an event that fires whenever a remote sync request has failed. + /// + public EventHandler SyncFailed { get; set; } + + /// + /// Gets the id for the target extension on a Graph user. + /// + public string ExtensionId { get; } + + /// + /// Gets the id of the target Graph user. + /// + public string UserId { get; } + + /// + /// Gets an object serializer for converting objects in the data store. + /// + public IObjectSerializer Serializer { get; } + + /// + /// Gets a cache of the stored values, converted using the provided serializer. + /// + public IDictionary Cache { get; } + + /// + /// Creates a new instance using the userId retrieved from a Graph "Me" request. + /// + /// The id for the target extension on a Graph user. + /// A serializer used for converting stored objects. + /// A new instance of the configured for the current Graph user. + public static async Task CreateForCurrentUserAsync(string extensionId, IObjectSerializer objectSerializer = null) + { + if (extensionId == null) + { + throw new ArgumentNullException(nameof(extensionId)); + } + + var provider = ProviderManager.Instance.GlobalProvider; + if (provider == null || provider.State != ProviderState.SignedIn) + { + throw new InvalidOperationException($"The {nameof(ProviderManager.GlobalProvider)} must be set and signed in to create a new {nameof(UserExtensionStorageHelper)} for the current user."); + } + + var me = await provider.GetClient().Me.Request().GetAsync(); + var userId = me.Id; + + return new UserExtensionStorageHelper(extensionId, userId, objectSerializer); + } + + /// + /// An indexer for easily accessing key values. + /// + /// The key for the desired value. + /// The value found for the provided key. + public object this[string key] + { + get => Read(key); + set => Save(key, value); + } + + /// + /// Initializes a new instance of the class. + /// + /// The id for the target extension on a Graph user. + /// The id of the target Graph user. + /// A serializer used for converting stored objects. + public UserExtensionStorageHelper(string extensionId, string userId, IObjectSerializer objectSerializer = null) + { + ExtensionId = extensionId ?? throw new ArgumentNullException(nameof(extensionId)); + UserId = userId ?? throw new ArgumentNullException(nameof(userId)); + Serializer = objectSerializer ?? new SystemSerializer(); + + Cache = new Dictionary(); + } + + /// + /// Update the remote extension to match the local cache and retrieve any new keys. Any existing remote values are replaced. + /// + /// The freshly synced user extension. + public virtual async Task Sync() + { + await SyncLock.WaitAsync(); + + try + { + IDictionary remoteData = null; + + try + { + // Get the remote + Extension extension = await UserExtensionDataSource.GetExtension(UserId, ExtensionId); + remoteData = extension.AdditionalData; + } + catch + { + } + + if (Cache != null) + { + // Send updates for all local values, overwriting the remote. + foreach (string key in Cache.Keys.ToList()) + { + if (ReservedKeys.Contains(key)) + { + continue; + } + + if (remoteData == null || !remoteData.ContainsKey(key) || !EqualityComparer.Default.Equals(remoteData[key], Cache[key])) + { + Save(key, Cache[key]); + } + } + } + + if (remoteData != null) + { + // Update local cache with additions from remote + foreach (string key in remoteData.Keys.ToList()) + { + if (!Cache.ContainsKey(key)) + { + Cache.Add(key, remoteData[key]); + } + } + } + + SyncCompleted?.Invoke(this, new EventArgs()); + } + catch + { + SyncFailed?.Invoke(this, new EventArgs()); + } + finally + { + SyncLock.Release(); + } + } + + /// + public bool KeyExists(string key) + { + return Cache.ContainsKey(key); + } + + /// + public bool KeyExists(string compositeKey, string key) + { + if (Cache.TryGetValue(compositeKey, out object compositeObj)) + { + var composite = compositeObj as Composite; + return composite != default && composite.ContainsKey(key); + } + + return false; + } + + /// + public T Read(string key, T @default = default) + { + return Cache.TryGetValue(key, out object value) + ? DeserializeValue(value) + : @default; + } + + /// + public T Read(string compositeKey, string key, T @default = default) + { + if (Cache.TryGetValue(compositeKey, out object compositeObj)) + { + var composite = compositeObj as Composite; + if (composite != default && composite.TryGetValue(key, out object valueObj)) + { + return DeserializeValue(valueObj); + } + } + + return @default; + } + + /// + public void Save(string key, T value) + { + Cache[key] = SerializeValue(value); + } + + /// + public void Save(string compositeKey, IDictionary values) + { + if (Cache.TryGetValue(compositeKey, out object compositeObj)) + { + var composite = compositeObj as Composite; + + foreach (KeyValuePair setting in values.ToList()) + { + string key = setting.Key; + object value = SerializeValue(setting.Value); + if (composite.ContainsKey(setting.Key)) + { + composite[key] = value; + } + else + { + composite.Add(key, value); + } + } + + Cache[compositeKey] = composite; + } + else + { + var composite = new Composite(); + foreach (KeyValuePair setting in values.ToList()) + { + string key = setting.Key; + object value = SerializeValue(setting.Value); + composite.Add(key, value); + } + + Cache[compositeKey] = composite; + } + } + + /// + public void Delete(string key) + { + if (!Cache.Remove(key)) + { + throw new KeyNotFoundException($"Key \"{key}\" was not found."); + } + } + + /// + public void Delete(string compositeKey, string key) + { + if (!Cache.TryGetValue(compositeKey, out object compositeObj)) + { + throw new KeyNotFoundException($"Composite key \"{compositeKey}\" was not found."); + } + + var composite = compositeObj as Composite; + + if (!composite.Remove(key)) + { + throw new KeyNotFoundException($"Key \"{key}\" was not found in composite \"{compositeKey}\""); + } + } + + /// + /// Use the serializer to deserialize a value appropriately for the type. + /// + /// The type of object expected. + /// The value to deserialize. + /// An object of type T. + protected T DeserializeValue(object value) + { + try + { + return Serializer.Deserialize((string)value); + } + catch + { + // Primitive types can't be deserialized. + return (T)Convert.ChangeType(value, typeof(T)); + } + } + + /// + /// Use the serializer to serialize a value appropriately for the type. + /// + /// The type of object being serialized. + /// The object to serialize. + /// The serialized object. + protected object SerializeValue(T value) + { + var type = typeof(T); + var typeInfo = type.GetTypeInfo(); + + // Skip serialization for primitives. + if (typeInfo.IsPrimitive || type == typeof(string)) + { + // Update the cache + return value; + } + else + { + // Update the cache + return Serializer.Serialize(value); + } + } + + // A "Composite" is really just a dictionary. + private class Composite : Dictionary + { + } + } +} \ No newline at end of file diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs deleted file mode 100644 index 5dc21cb..0000000 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/BaseRoamingSettingsDataStore.cs +++ /dev/null @@ -1,303 +0,0 @@ -// 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.Reflection; -using System.Threading.Tasks; -using Microsoft.Toolkit.Uwp.Helpers; -using Windows.Storage; - -namespace CommunityToolkit.Graph.Helpers.RoamingSettings -{ - /// - /// A base class for easily building roaming settings helper implementations. - /// - public abstract class BaseRoamingSettingsDataStore : IRoamingSettingsDataStore - { - /// - public EventHandler SyncCompleted { get; set; } - - /// - public EventHandler SyncFailed { get; set; } - - /// - public bool AutoSync { get; } - - /// - public string Id { get; } - - /// - public string UserId { get; } - - /// - public IDictionary Cache { get; private set; } = new Dictionary(); - - /// - /// Gets an object serializer for converting objects in the data store. - /// - protected IObjectSerializer Serializer { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The id of the target Graph user. - /// A unique id for the data store. - /// An IObjectSerializer used for serializing objects. - /// Determines if the data store should sync for every interaction. - public BaseRoamingSettingsDataStore(string userId, string dataStoreId, IObjectSerializer objectSerializer, bool autoSync = true) - { - AutoSync = autoSync; - Id = dataStoreId; - UserId = userId; - Serializer = objectSerializer; - } - - /// - /// Create a new instance of the data storage container. - /// - /// A task. - public abstract Task Create(); - - /// - /// Delete the instance of the data storage container. - /// - /// A task. - public abstract Task Delete(); - - /// - /// Determines whether a setting already exists. - /// - /// Key of the setting (that contains object). - /// True if a value exists. - public bool KeyExists(string key) - { - return Cache?.ContainsKey(key) ?? false; - } - - /// - /// 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 (KeyExists(compositeKey)) - { - ApplicationDataCompositeValue composite = (ApplicationDataCompositeValue)Cache[compositeKey]; - if (composite != null) - { - return composite.ContainsKey(key); - } - } - - return false; - } - - /// - /// Retrieves a single item by its key. - /// - /// Key of the object. - /// Default value of the object. - /// Type of object retrieved. - /// The T object. - public T Read(string key, T @default = default) - { - if (Cache != null && Cache.TryGetValue(key, out object value)) - { - return DeserializeValue(value); - } - - return @default; - } - - /// - /// Retrieves a single item by its key in composite. - /// - /// Key of the composite (that contains settings). - /// Key of the object. - /// Default value of the object. - /// Type of object retrieved. - /// The T object. - public T Read(string compositeKey, string key, T @default = default) - { - if (Cache != null) - { - ApplicationDataCompositeValue composite = (ApplicationDataCompositeValue)Cache[compositeKey]; - if (composite != null) - { - object value = composite[key]; - if (value != null) - { - return DeserializeValue(value); - } - } - } - - return @default; - } - - /// - /// Saves a single item by its key. - /// - /// Key of the value saved. - /// Object to save. - /// Type of object saved. - public void Save(string key, T value) - { - // Update the cache - Cache[key] = SerializeValue(value); - - if (AutoSync) - { - // Update the remote - Task.Run(() => Sync()); - } - } - - /// - /// 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 - /// (refers to Microsoft.Toolkit.Uwp.Helpers.IObjectStorageHelper.SaveFileAsync``1(System.String,``0) - /// for complex/large objects) and for groups of settings which need to be treated - /// in an atomic way. - /// - /// Key of the composite (that contains settings). - /// Objects to save. - /// Type of object saved. - public void Save(string compositeKey, IDictionary values) - { - var type = typeof(T); - var typeInfo = type.GetTypeInfo(); - - if (KeyExists(compositeKey)) - { - ApplicationDataCompositeValue composite = (ApplicationDataCompositeValue)Cache[compositeKey]; - - foreach (KeyValuePair setting in values.ToList()) - { - string key = setting.Key; - object value = SerializeValue(setting.Value); - if (composite.ContainsKey(setting.Key)) - { - composite[key] = value; - } - else - { - composite.Add(key, value); - } - } - - // Update the cache - Cache[compositeKey] = composite; - - if (AutoSync) - { - // Update the remote - Task.Run(() => Sync()); - } - } - else - { - ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue(); - foreach (KeyValuePair setting in values.ToList()) - { - string key = setting.Key; - object value = SerializeValue(setting.Value); - composite.Add(key, value); - } - - // Update the cache - Cache[compositeKey] = composite; - - if (AutoSync) - { - // Update the remote - Task.Run(() => Sync()); - } - } - } - - /// - /// Determines whether a file already exists. - /// - /// Key of the file (that contains object). - /// True if a value exists. - public abstract Task FileExistsAsync(string filePath); - - /// - /// Retrieves an object from a file. - /// - /// Path to the file that contains the object. - /// Default value of the object. - /// Type of object retrieved. - /// Waiting task until completion with the object in the file. - public abstract Task ReadFileAsync(string filePath, T @default = default); - - /// - /// Saves an object inside a file. - /// - /// Path to the file that will contain the object. - /// Object to save. - /// Type of object saved. - /// Waiting task until completion. - public abstract Task SaveFileAsync(string filePath, T value); - - /// - public abstract Task Sync(); - - /// - /// Delete the internal cache. - /// - protected void DeleteCache() - { - Cache.Clear(); - } - - /// - /// Use the serializer to deserialize a value appropriately for the type. - /// - /// The type of object expected. - /// The value to deserialize. - /// An object of type T. - protected T DeserializeValue(object value) - { - try - { - return Serializer.Deserialize((string)value); - } - catch - { - // Primitive types can't be deserialized. - return (T)Convert.ChangeType(value, typeof(T)); - } - } - - /// - /// Use the serializer to serialize a value appropriately for the type. - /// - /// The type of object being serialized. - /// The object to serialize. - /// The serialized object. - protected object SerializeValue(T value) - { - var type = typeof(T); - var typeInfo = type.GetTypeInfo(); - - // Skip serialization for primitives. - if (typeInfo.IsPrimitive || type == typeof(string)) - { - // Update the cache - return value; - } - else - { - // Update the cache - return Serializer.Serialize(value); - } - } - } -} diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs deleted file mode 100644 index 3c91699..0000000 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/IRoamingSettingsDataStore.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 CommunityToolkit.Graph.ObjectStorage; - -namespace CommunityToolkit.Graph.Helpers.RoamingSettings -{ - /// - /// Defines the contract for creating storage containers used for roaming data. - /// - public interface IRoamingSettingsDataStore : IDictionaryStorageHelper, IFileStorageHelper - { - /// - /// Gets a value indicating whether the values should immediately sync or not. - /// - bool AutoSync { get; } - - /// - /// Gets access to the key/value pairs cache directly. - /// - IDictionary Cache { get; } - - /// - /// Gets the id of the data store. - /// - string Id { get; } - - /// - /// Gets the id of the target user. - /// - string UserId { get; } - - /// - /// Gets or sets an event handler for when a remote data sync completes successfully. - /// - EventHandler SyncCompleted { get; set; } - - /// - /// Gets or sets an event handler for when a remote data sync fails. - /// - EventHandler SyncFailed { get; set; } - - /// - /// Create a new storage container. - /// - /// A Task. - Task Create(); - - /// - /// Delete the existing storage container. - /// - /// A Task. - Task Delete(); - - /// - /// Syncronize the internal cache with the remote storage endpoint. - /// - /// A Task. - Task Sync(); - } -} \ No newline at end of file diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs deleted file mode 100644 index 700d525..0000000 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/OneDriveDataStore.cs +++ /dev/null @@ -1,154 +0,0 @@ -// 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.Uwp.Helpers; -using Windows.Storage; - -namespace CommunityToolkit.Graph.Helpers.RoamingSettings -{ - /// - /// A DataStore for managing roaming settings in OneDrive. - /// - public class OneDriveDataStore : BaseRoamingSettingsDataStore - { - /// - /// Retrieve an object stored in a OneDrive file. - /// - /// The type of object to retrieve. - /// The id of the target Graph user. - /// The name of the file. - /// An object serializer for handling deserialization. - /// The deserialized file contents. - public static async Task Get(string userId, string fileName, IObjectSerializer serializer) - { - return await OneDriveDataSource.Retrieve(userId, fileName, serializer); - } - - /// - /// Update the contents of a OneDrive file. - /// - /// The type of object being stored. - /// The id of the target Graph user. - /// The name of the file. - /// The object to store. - /// An object serializer for handling serialization. - /// A task. - public static async Task Set(string userId, string fileName, T fileContents, IObjectSerializer serializer) - { - await OneDriveDataSource.Update(userId, fileName, fileContents, serializer); - } - - /// - /// Delete a file from OneDrive by name. - /// - /// The id of the target Graph user. - /// The name of the file. - /// A task. - public static async Task Delete(string userId, string fileName) - { - await OneDriveDataSource.Delete(userId, fileName); - } - - /// - /// Initializes a new instance of the class. - /// - public OneDriveDataStore(string userId, string syncDataFileName, IObjectSerializer objectSerializer, bool autoSync = true) - : base(userId, syncDataFileName, objectSerializer, autoSync) - { - } - - /// - public override Task Create() - { - return Task.CompletedTask; - } - - /// - public override async Task Delete() - { - // Clear the cache - Cache.Clear(); - - // Delete the remote. - await Delete(UserId, Id); - } - - /// - public override async Task FileExistsAsync(string filePath) - { - var roamingSettings = await Get(UserId, Id, Serializer); - return roamingSettings != null; - } - - /// - public override async Task ReadFileAsync(string filePath, T @default = default) - { - return await Get(UserId, filePath, Serializer) ?? @default; - } - - /// - public override async Task SaveFileAsync(string filePath, T value) - { - await Set(UserId, filePath, value, Serializer); - - // Can't convert DriveItem to StorageFile, so we return null instead. - return null; - } - - /// - public override async Task Sync() - { - try - { - // Get the remote - string fileName = Id; - IDictionary remoteData = null; - try - { - remoteData = await Get>(UserId, fileName, Serializer); - } - catch - { - // If get fails, we know the remote store does not exist. - } - - bool needsUpdate = false; - if (remoteData != null) - { - // Update local cache with additions from remote - foreach (string key in remoteData.Keys.ToList()) - { - // Only insert new values. Existing keys should be overwritten on the remote. - if (!Cache.ContainsKey(key)) - { - Cache.Add(key, remoteData[key]); - needsUpdate = true; - } - } - } - else if (Cache.Count > 0) - { - // The remote does not yet exist, and we have data to save. - needsUpdate = true; - } - - if (needsUpdate) - { - // Send updates for local values, overwriting the remote. - await Set(UserId, fileName, Cache, Serializer); - } - - SyncCompleted?.Invoke(this, new EventArgs()); - } - catch - { - SyncFailed?.Invoke(this, new EventArgs()); - } - } - } -} diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs deleted file mode 100644 index fc71e90..0000000 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/RoamingSettingsHelper.cs +++ /dev/null @@ -1,217 +0,0 @@ -// 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 CommunityToolkit.Authentication; -using CommunityToolkit.Graph.Extensions; -using Microsoft.Toolkit.Uwp.Helpers; -using Windows.Storage; - -namespace CommunityToolkit.Graph.Helpers.RoamingSettings -{ - /// - /// An enumeration of the available data storage methods for roaming data. - /// - public enum RoamingDataStore - { - /// - /// Store data using open extensions on the Graph User. - /// - UserExtensions, - - /// - /// Store data in a Graph User's OneDrive. - /// - OneDrive, - } - - /// - /// A helper class for syncing data to roaming data store. - /// - public class RoamingSettingsHelper - { - /// - /// Gets the internal data store instance. - /// - public IRoamingSettingsDataStore DataStore { get; private set; } - - /// - public EventHandler SyncCompleted { get; set; } - - /// - public EventHandler SyncFailed { get; set; } - - /// - public bool AutoSync => DataStore.AutoSync; - - /// - public IDictionary Cache => DataStore.Cache; - - /// - public string Id => DataStore.Id; - - /// - public string UserId => DataStore.UserId; - - /// - /// Creates a new RoamingSettingsHelper instance for the currently signed in user. - /// - /// Which specific data store is being used. - /// Whether the values should immediately sync or not. - /// Whether the values should immediately sync on change or wait until Sync is called explicitly. - /// An object serializer for serialization of objects in the data store. - /// A new instance of the RoamingSettingsHelper configured for the current user. - public static async Task CreateForCurrentUser(RoamingDataStore dataStore = RoamingDataStore.UserExtensions, bool syncOnInit = true, bool autoSync = true)//, IObjectSerializer serializer = null) - { - var provider = ProviderManager.Instance.GlobalProvider; - if (provider == null || provider.State != ProviderState.SignedIn) - { - throw new InvalidOperationException("The GlobalProvider must be set and signed in to create a new RoamingSettingsHelper for the current user."); - } - - var me = await provider.GetClient().Me.Request().GetAsync(); - var userId = me.Id; - - // TODO: Infuse unique identifier from Graph registration into the storage name. - string dataStoreName = "communityToolkit.roamingSettings"; - - // if (serializer == null) - // { - // serializer = new SystemSerializer(); - // } - - IRoamingSettingsDataStore instance = null; - - switch (dataStore) - { - case RoamingDataStore.UserExtensions: - instance = new UserExtensionDataStore(userId, dataStoreName, serializer, autoSync); - break; - - case RoamingDataStore.OneDrive: - instance = new OneDriveDataStore(userId, dataStoreName, serializer, autoSync); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(dataStore)); - } - - if (syncOnInit) - { - _ = instance.Sync(); - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The id of the target Graph User. - /// Which specific data store is being used. - /// Whether the values should immediately sync or not. - /// Whether the values should immediately sync on change or wait until Sync is called explicitly. - /// An object serializer for serialization of objects in the data store. - public RoamingSettingsHelper(string userId, RoamingDataStore dataStore = RoamingDataStore.UserExtensions, bool syncOnInit = true, bool autoSync = true, IObjectSerializer serializer = null) - { - // TODO: Infuse unique identifier from Graph registration into the storage name. - string dataStoreName = "communityToolkit.roamingSettings"; - - if (serializer == null) - { - serializer = new SystemSerializer(); - } - - switch (dataStore) - { - case RoamingDataStore.UserExtensions: - DataStore = new UserExtensionDataStore(userId, dataStoreName, serializer, autoSync); - break; - - case RoamingDataStore.OneDrive: - DataStore = new OneDriveDataStore(userId, dataStoreName, serializer, autoSync); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(dataStore)); - } - - DataStore.SyncCompleted += (s, e) => SyncCompleted?.Invoke(this, e); - DataStore.SyncFailed += (s, e) => SyncFailed?.Invoke(this, e); - - if (syncOnInit) - { - _ = Sync(); - } - } - - /// - /// An indexer for easily accessing key values. - /// - /// The key for the desired value. - /// The value found for the provided key. - public object this[string key] - { - get => DataStore.Read(key); - set => DataStore.Save(key, value); - } - - /// - public Task FileExistsAsync(string filePath) => DataStore.FileExistsAsync(filePath); - - /// - public bool KeyExists(string key) => DataStore.KeyExists(key); - - /// - public bool KeyExists(string compositeKey, string key) => DataStore.KeyExists(compositeKey, key); - - /// - public T Read(string key, T @default = default) => DataStore.Read(key, @default); - - /// - public T Read(string compositeKey, string key, T @default = default) => DataStore.Read(compositeKey, key, @default); - - /// - public Task ReadFileAsync(string filePath, T @default = default) => DataStore.ReadFileAsync(filePath, @default); - - /// - public void Save(string key, T value) => DataStore.Save(key, value); - - /// - public void Save(string compositeKey, IDictionary values) => DataStore.Save(compositeKey, values); - - /// - public Task SaveFileAsync(string filePath, T value) => DataStore.SaveFileAsync(filePath, value); - - /// - /// Create a new storage container. - /// - /// A Task. - public Task Create() => DataStore.Create(); - - /// - /// Delete the existing storage container. - /// - /// A Task. - public Task Delete() => DataStore.Delete(); - - /// - /// Syncronize the internal cache with the remote storage endpoint. - /// - /// A Task. - public async Task Sync() - { - try - { - await DataStore.Sync(); - } - catch - { - // Sync may fail if the storage container does not yet exist. - await DataStore.Create(); - await DataStore.Sync(); - } - } - } -} \ No newline at end of file diff --git a/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs b/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs deleted file mode 100644 index 657ca4f..0000000 --- a/CommunityToolkit.Graph/Helpers/RoamingSettings/UserExtensionDataStore.cs +++ /dev/null @@ -1,201 +0,0 @@ -// 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.Graph; -using Microsoft.Toolkit.Uwp.Helpers; -using Windows.Storage; - -namespace CommunityToolkit.Graph.Helpers.RoamingSettings -{ - /// - /// An IObjectStorageHelper implementation using open extensions on the Graph User for storing key/value pairs. - /// - public class UserExtensionDataStore : BaseRoamingSettingsDataStore - { - /// - /// Retrieve the value from Graph User extensions and cast the response to the provided type. - /// - /// The type to cast the return result to. - /// The id of the user. - /// The id of the user extension. - /// The key for the desired value. - /// The value from the data store. - public static async Task Get(string userId, string extensionId, string key) - { - return (T)await Get(userId, extensionId, key); - } - - /// - /// Retrieve the value from Graph User extensions by extensionId, userId, and key. - /// - /// The id of the user. - /// The id of the user extension. - /// The key for the desired value. - /// The value from the data store. - public static async Task Get(string userId, string extensionId, string key) - { - var userExtension = await GetExtensionForUser(userId, extensionId); - return userExtension.AdditionalData[key]; - } - - /// - /// Set a value by key in a Graph User's extension. - /// - /// The id of the user. - /// The id of the user extension. - /// The key for the target value. - /// The value to set. - /// A task upon completion. - public static async Task Set(string userId, string extensionId, string key, object value) - { - await UserExtensionsDataSource.SetValue(userId, extensionId, key, value); - } - - /// - /// Creates a new roaming settings extension on a Graph User. - /// - /// The id of the user. - /// The id of the user extension. - /// The newly created user extension. - public static async Task Create(string userId, string extensionId) - { - var userExtension = await UserExtensionsDataSource.CreateExtension(userId, extensionId); - return userExtension; - } - - /// - /// Deletes an extension by id on a Graph User. - /// - /// The id of the user. - /// The id of the user extension. - /// A task upon completion. - public static async Task Delete(string userId, string extensionId) - { - await UserExtensionsDataSource.DeleteExtension(userId, extensionId); - } - - /// - /// Retrieves a user extension. - /// - /// The id of the user. - /// The id of the user extension. - /// The target extension. - public static async Task GetExtensionForUser(string userId, string extensionId) - { - var userExtension = await UserExtensionsDataSource.GetExtension(userId, extensionId); - return userExtension; - } - - private static readonly IList ReservedKeys = new List { "responseHeaders", "statusCode", "@odata.context" }; - - /// - /// Initializes a new instance of the class. - /// - public UserExtensionDataStore(string userId, string extensionId, IObjectSerializer objectSerializer, bool autoSync = true) - : base(userId, extensionId, objectSerializer, autoSync) - { - } - - /// - /// Creates a new roaming settings extension on the Graph User. - /// - /// The newly created Extension object. - public override async Task Create() - { - await Create(UserId, Id); - } - - /// - /// Deletes the roamingSettings extension from the Graph User. - /// - /// A void task. - public override async Task Delete() - { - // Delete the cache - Cache.Clear(); - - // Delete the remote. - await Delete(UserId, Id); - } - - /// - /// Update the remote extension to match the local cache and retrieve any new keys. Any existing remote values are replaced. - /// - /// The freshly synced user extension. - public override async Task Sync() - { - try - { - IDictionary remoteData = null; - - try - { - // Get the remote - Extension extension = await GetExtensionForUser(UserId, Id); - remoteData = extension.AdditionalData; - } - catch - { - } - - if (Cache != null) - { - // Send updates for all local values, overwriting the remote. - foreach (string key in Cache.Keys.ToList()) - { - if (ReservedKeys.Contains(key)) - { - continue; - } - - if (remoteData == null || !remoteData.ContainsKey(key) || !EqualityComparer.Default.Equals(remoteData[key], Cache[key])) - { - Save(key, Cache[key]); - } - } - } - - if (remoteData != null) - { - // Update local cache with additions from remote - foreach (string key in remoteData.Keys.ToList()) - { - if (!Cache.ContainsKey(key)) - { - Cache.Add(key, remoteData[key]); - } - } - } - - SyncCompleted?.Invoke(this, new EventArgs()); - } - catch - { - SyncFailed?.Invoke(this, new EventArgs()); - } - } - - /// - public override Task FileExistsAsync(string filePath) - { - throw new NotImplementedException(); - } - - /// - public override Task ReadFileAsync(string filePath, T @default = default) - { - throw new NotImplementedException(); - } - - /// - public override Task SaveFileAsync(string filePath, T value) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs b/CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs deleted file mode 100644 index 5bc5788..0000000 --- a/CommunityToolkit.Graph/ObjectStorage/IDictionaryStorageHelper.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Collections.Generic; - -namespace CommunityToolkit.Graph.ObjectStorage -{ - /// - /// - /// - public interface IDictionaryStorageHelper - { - /// - /// Determines whether a setting already 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. - 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)); - - /// - /// 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)); - - /// - /// Saves a single item by its key. - /// - /// Type of object saved. - /// Key of the value saved. - /// Object to save. - void Save(string key, T value); - - /// - /// 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. - void Save(string compositeKey, IDictionary values); - - /// - /// - /// - /// - void Delete(string key); - - /// - /// - /// - /// - /// - void Delete(string key, string compositeKey); - } -} diff --git a/CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs b/CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs deleted file mode 100644 index 9dc859b..0000000 --- a/CommunityToolkit.Graph/ObjectStorage/IFileStorageHelper.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace CommunityToolkit.Graph.ObjectStorage -{ - /// - /// - /// - public interface IFileStorageHelper - { - /// - /// Determines whether a file already exists. - /// - /// Key of the file (that contains object). - /// True if a value exists. - Task FileExistsAsync(string filePath); - - /// - /// Determines whether a folder already exists. - /// - /// Key of the folder. - /// True if a value exists. - Task FolderExistsAsync(string folderPath); - - /// - /// 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)); - - /// - /// Retrieves all file listings for a 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 SaveFileAsync(string filePath, T value); - - /// - /// - /// - /// - /// - Task SaveFolderAsync(string folderPath); - - /// - /// - /// - /// - /// - Task DeleteItemAsync(string itemPath); - } -} diff --git a/SampleTest/MainPage.xaml b/SampleTest/MainPage.xaml index a762b91..1487e26 100644 --- a/SampleTest/MainPage.xaml +++ b/SampleTest/MainPage.xaml @@ -49,9 +49,6 @@ - - - MainPage.xaml - - RoamingSettingsView.xaml - - @@ -153,10 +149,6 @@ MSBuild:Compile Designer - - MSBuild:Compile - Designer - diff --git a/SampleTest/Samples/RoamingSettings/RoamingSettingsView.xaml b/SampleTest/Samples/RoamingSettings/RoamingSettingsView.xaml deleted file mode 100644 index 398282c..0000000 --- a/SampleTest/Samples/RoamingSettings/RoamingSettingsView.xaml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - -