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