Skip to content

Commit 546c6ed

Browse files
committed
Port ApplicationDataStorageHelper
very difficult
1 parent 3fedf5c commit 546c6ed

File tree

1 file changed

+344
-0
lines changed

1 file changed

+344
-0
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Threading.Tasks;
10+
using CommunityToolkit.Common.Helpers;
11+
using Windows.Storage;
12+
using Windows.System;
13+
using CommunityToolkit.Helpers;
14+
15+
namespace CommunityToolkit.WinUI.Helpers.ObjectStorage;
16+
17+
/// <summary>
18+
/// Storage helper for files and folders living in Windows.Storage.ApplicationData storage endpoints.
19+
/// </summary>
20+
/// <param name="appData">The data store to interact with.</param>
21+
/// <param name="objectSerializer">Serializer for converting stored values. Defaults to <see cref="SystemSerializer"/>.</param>
22+
public partial class ApplicationDataStorageHelper(ApplicationData appData, IObjectSerializer? objectSerializer = null) : IFileStorageHelper, ISettingsStorageHelper<string>
23+
{
24+
/// <summary>
25+
/// Gets the settings container.
26+
/// </summary>
27+
public ApplicationDataContainer Settings => AppData.LocalSettings;
28+
29+
/// <summary>
30+
/// Gets the storage folder.
31+
/// </summary>
32+
public StorageFolder Folder => AppData.LocalFolder;
33+
34+
/// <summary>
35+
/// Gets the storage host.
36+
/// </summary>
37+
protected ApplicationData AppData { get; } = appData ?? throw new ArgumentNullException(nameof(appData));
38+
39+
/// <summary>
40+
/// Gets the serializer for converting stored values.
41+
/// </summary>
42+
protected IObjectSerializer Serializer { get; } = objectSerializer ?? new SystemSerializer();
43+
44+
/// <summary>
45+
/// Get a new instance using ApplicationData.Current and the provided serializer.
46+
/// </summary>
47+
/// <param name="objectSerializer">Serializer for converting stored values. Defaults to <see cref="SystemSerializer"/>.</param>
48+
/// <returns>A new instance of ApplicationDataStorageHelper.</returns>
49+
public static ApplicationDataStorageHelper GetCurrent(IObjectSerializer? objectSerializer = null)
50+
{
51+
var appData = ApplicationData.Current;
52+
return new ApplicationDataStorageHelper(appData, objectSerializer);
53+
}
54+
55+
#if !HAS_UNO
56+
/// <summary>
57+
/// Get a new instance using the ApplicationData for the provided user and serializer.
58+
/// </summary>
59+
/// <param name="user">App data user owner.</param>
60+
/// <param name="objectSerializer">Serializer for converting stored values. Defaults to <see cref="SystemSerializer"/>.</param>
61+
/// <returns>A new instance of ApplicationDataStorageHelper.</returns>
62+
public static async Task<ApplicationDataStorageHelper> GetForUserAsync(User user, IObjectSerializer? objectSerializer = null)
63+
{
64+
var appData = await ApplicationData.GetForUserAsync(user);
65+
return new ApplicationDataStorageHelper(appData, objectSerializer);
66+
}
67+
#endif
68+
69+
/// <summary>
70+
/// Determines whether a setting already exists.
71+
/// </summary>
72+
/// <param name="key">Key of the setting (that contains object).</param>
73+
/// <returns>True if a value exists.</returns>
74+
public bool KeyExists(string key)
75+
{
76+
return Settings.Values.ContainsKey(key);
77+
}
78+
79+
/// <summary>
80+
/// Retrieves a single item by its key.
81+
/// </summary>
82+
/// <typeparam name="T">Type of object retrieved.</typeparam>
83+
/// <param name="key">Key of the object.</param>
84+
/// <param name="default">Default value of the object.</param>
85+
/// <returns>The TValue object.</returns>
86+
public T? Read<T>(string key, T? @default = default)
87+
{
88+
if (Settings.Values.TryGetValue(key, out var valueObj) && valueObj is string valueString)
89+
{
90+
return Serializer.Deserialize<T>(valueString);
91+
}
92+
93+
return @default;
94+
}
95+
96+
/// <inheritdoc />
97+
public bool TryRead<T>(string key, out T? value)
98+
{
99+
if (Settings.Values.TryGetValue(key, out var valueObj) && valueObj is string valueString)
100+
{
101+
value = Serializer.Deserialize<T>(valueString);
102+
return true;
103+
}
104+
105+
value = default;
106+
return false;
107+
}
108+
109+
/// <inheritdoc />
110+
public void Save<T>(string key, T value)
111+
{
112+
Settings.Values[key] = Serializer.Serialize(value);
113+
}
114+
115+
/// <inheritdoc />
116+
public bool TryDelete(string key)
117+
{
118+
return Settings.Values.Remove(key);
119+
}
120+
121+
/// <inheritdoc />
122+
public void Clear()
123+
{
124+
Settings.Values.Clear();
125+
}
126+
127+
/// <summary>
128+
/// Determines whether a setting already exists in composite.
129+
/// </summary>
130+
/// <param name="compositeKey">Key of the composite (that contains settings).</param>
131+
/// <param name="key">Key of the setting (that contains object).</param>
132+
/// <returns>True if a value exists.</returns>
133+
public bool KeyExists(string compositeKey, string key)
134+
{
135+
if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null)
136+
{
137+
return composite.ContainsKey(key);
138+
}
139+
140+
return false;
141+
}
142+
143+
/// <summary>
144+
/// Attempts to retrieve a single item by its key in composite.
145+
/// </summary>
146+
/// <typeparam name="T">Type of object retrieved.</typeparam>
147+
/// <param name="compositeKey">Key of the composite (that contains settings).</param>
148+
/// <param name="key">Key of the object.</param>
149+
/// <param name="value">The value of the object retrieved.</param>
150+
/// <returns>The T object.</returns>
151+
public bool TryRead<T>(string compositeKey, string key, out T? value)
152+
{
153+
if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null)
154+
{
155+
string compositeValue = (string)composite[key];
156+
if (compositeValue != null)
157+
{
158+
value = Serializer.Deserialize<T>(compositeValue);
159+
return true;
160+
}
161+
}
162+
163+
value = default;
164+
return false;
165+
}
166+
167+
/// <summary>
168+
/// Retrieves a single item by its key in composite.
169+
/// </summary>
170+
/// <typeparam name="T">Type of object retrieved.</typeparam>
171+
/// <param name="compositeKey">Key of the composite (that contains settings).</param>
172+
/// <param name="key">Key of the object.</param>
173+
/// <param name="default">Default value of the object.</param>
174+
/// <returns>The T object.</returns>
175+
public T? Read<T>(string compositeKey, string key, T? @default = default)
176+
{
177+
if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null)
178+
{
179+
if (composite.TryGetValue(key, out var valueObj) && valueObj is string value)
180+
{
181+
return Serializer.Deserialize<T>(value);
182+
}
183+
}
184+
185+
return @default;
186+
}
187+
188+
/// <summary>
189+
/// Saves a group of items by its key in a composite.
190+
/// This method should be considered for objects that do not exceed 8k bytes during the lifetime of the application
191+
/// and for groups of settings which need to be treated in an atomic way.
192+
/// </summary>
193+
/// <typeparam name="T">Type of object saved.</typeparam>
194+
/// <param name="compositeKey">Key of the composite (that contains settings).</param>
195+
/// <param name="values">Objects to save.</param>
196+
public void Save<T>(string compositeKey, IDictionary<string, T> values)
197+
{
198+
if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null)
199+
{
200+
foreach (KeyValuePair<string, T> setting in values)
201+
{
202+
var serializedValue = Serializer.Serialize(setting.Value) ?? string.Empty;
203+
if (composite.ContainsKey(setting.Key))
204+
{
205+
composite[setting.Key] = serializedValue;
206+
}
207+
else
208+
{
209+
composite.Add(setting.Key, serializedValue);
210+
}
211+
}
212+
}
213+
else
214+
{
215+
composite = [];
216+
foreach (KeyValuePair<string, T> setting in values)
217+
{
218+
var serializedValue = Serializer.Serialize(setting.Value) ?? string.Empty;
219+
composite.Add(setting.Key, serializedValue);
220+
}
221+
222+
Settings.Values[compositeKey] = composite;
223+
}
224+
}
225+
226+
/// <summary>
227+
/// Deletes a single item by its key in composite.
228+
/// </summary>
229+
/// <param name="compositeKey">Key of the composite (that contains settings).</param>
230+
/// <param name="key">Key of the object.</param>
231+
/// <returns>A boolean indicator of success.</returns>
232+
public bool TryDelete(string compositeKey, string key)
233+
{
234+
if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null)
235+
{
236+
return composite.Remove(key);
237+
}
238+
239+
return false;
240+
}
241+
242+
/// <inheritdoc />
243+
public Task<T?> ReadFileAsync<T>(string filePath, T? @default = default)
244+
{
245+
return ReadFileAsync<T>(Folder, filePath, @default);
246+
}
247+
248+
/// <inheritdoc />
249+
public Task<IEnumerable<(DirectoryItemType ItemType, string Name)>> ReadFolderAsync(string folderPath)
250+
{
251+
return ReadFolderAsync(Folder, folderPath);
252+
}
253+
254+
/// <inheritdoc />
255+
public Task CreateFileAsync<T>(string filePath, T value)
256+
{
257+
return CreateFileAsync<T>(Folder, filePath, value);
258+
}
259+
260+
/// <inheritdoc />
261+
public Task CreateFolderAsync(string folderPath)
262+
{
263+
return CreateFolderAsync(Folder, folderPath);
264+
}
265+
266+
/// <inheritdoc />
267+
public Task<bool> TryDeleteItemAsync(string itemPath)
268+
{
269+
return TryDeleteItemAsync(Folder, itemPath);
270+
}
271+
272+
/// <inheritdoc />
273+
public Task<bool> TryRenameItemAsync(string itemPath, string newName)
274+
{
275+
return TryRenameItemAsync(Folder, itemPath, newName);
276+
}
277+
278+
private async Task<T?> ReadFileAsync<T>(StorageFolder folder, string filePath, T? @default = default)
279+
{
280+
string value = await StorageFileHelper.ReadTextFromFileAsync(folder, NormalizePath(filePath));
281+
return (value != null) ? Serializer.Deserialize<T>(value) : @default;
282+
}
283+
284+
private async Task<IEnumerable<(DirectoryItemType, string)>> ReadFolderAsync(StorageFolder folder, string folderPath)
285+
{
286+
var targetFolder = await folder.GetFolderAsync(NormalizePath(folderPath));
287+
var items = await targetFolder.GetItemsAsync();
288+
289+
return items.Select((item) =>
290+
{
291+
var itemType = item.IsOfType(StorageItemTypes.File) ? DirectoryItemType.File
292+
: item.IsOfType(StorageItemTypes.Folder) ? DirectoryItemType.Folder
293+
: DirectoryItemType.None;
294+
295+
return (itemType, item.Name);
296+
});
297+
}
298+
299+
private async Task<StorageFile> CreateFileAsync<T>(StorageFolder folder, string filePath, T value)
300+
{
301+
var serializedValue = Serializer.Serialize(value)?.ToString() ?? string.Empty;
302+
return await StorageFileHelper.WriteTextToFileAsync(folder, serializedValue, NormalizePath(filePath), CreationCollisionOption.ReplaceExisting);
303+
}
304+
305+
private async Task CreateFolderAsync(StorageFolder folder, string folderPath)
306+
{
307+
await folder.CreateFolderAsync(NormalizePath(folderPath), CreationCollisionOption.OpenIfExists);
308+
}
309+
310+
private async Task<bool> TryDeleteItemAsync(StorageFolder folder, string itemPath)
311+
{
312+
try
313+
{
314+
var item = await folder.GetItemAsync(NormalizePath(itemPath));
315+
await item.DeleteAsync();
316+
return true;
317+
}
318+
catch
319+
{
320+
return false;
321+
}
322+
}
323+
324+
private async Task<bool> TryRenameItemAsync(StorageFolder folder, string itemPath, string newName)
325+
{
326+
try
327+
{
328+
var item = await folder.GetItemAsync(NormalizePath(itemPath));
329+
await item.RenameAsync(newName, NameCollisionOption.FailIfExists);
330+
return true;
331+
}
332+
catch
333+
{
334+
return false;
335+
}
336+
}
337+
338+
private string NormalizePath(string path)
339+
{
340+
var directoryName = Path.GetDirectoryName(path) ?? string.Empty;
341+
var fileName = Path.GetFileName(path);
342+
return Path.Combine(directoryName, fileName);
343+
}
344+
}

0 commit comments

Comments
 (0)