From d19e3cd89808592461af5f2d085d8321eba2b94f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 10 Mar 2023 13:49:43 +0900 Subject: [PATCH] Add wasm and ios implementations --- .../Avalonia.Browser/Interop/StorageHelper.cs | 12 ++++ .../Storage/BrowserStorageProvider.cs | 63 +++++++++++++++++++ .../webapp/modules/storage/storageItem.ts | 43 +++++++++++++ .../Avalonia.iOS/Storage/IOSStorageItem.cs | 62 ++++++++++++++++++ 4 files changed, 180 insertions(+) diff --git a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs index dc3372d2d0..c56023c0f7 100644 --- a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs @@ -52,4 +52,16 @@ internal static partial class StorageHelper [JSImport("StorageProvider.createAcceptType", AvaloniaModule.StorageModuleName)] public static partial JSObject CreateAcceptType(string description, string[] mimeTypes, string[]? extensions); + + [JSImport("StorageProvider.deleteAsync", AvaloniaModule.StorageModuleName)] + public static partial Task DeleteAsync(JSObject fileHandle); + + [JSImport("StorageProvider.moveAsync", AvaloniaModule.StorageModuleName)] + public static partial Task MoveAsync(JSObject fileHandle, JSObject destinationFolder); + + [JSImport("StorageProvider.createFile", AvaloniaModule.StorageModuleName)] + public static partial Task CreateFile(JSObject folderHandle, string name); + + [JSImport("StorageProvider.createFolder", AvaloniaModule.StorageModuleName)] + public static partial Task CreateFolder(JSObject folderHandle, string name); } diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index fcb956f294..e6849f2fcc 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -199,6 +199,33 @@ internal abstract class JSStorageItem : IStorageBookmarkItem return Task.FromResult(null); } + public Task DeleteAsync() + { + return StorageHelper.DeleteAsync(FileHandle); + } + + public async Task MoveAsync(IStorageFolder destination) + { + if (destination is not JSStorageFolder folder) + { + throw new InvalidOperationException("Destination folder must be initialized the StorageProvider API."); + } + + var storageItem = await StorageHelper.MoveAsync(FileHandle, folder.FileHandle); + if (storageItem is null) + { + return null; + } + + var kind = storageItem.GetPropertyAsString("kind"); + return kind switch + { + "directory" => new JSStorageFolder(storageItem), + "file" => new JSStorageFile(storageItem), + _ => this + }; + } + public Task ReleaseBookmarkAsync() { if (!CanBookmark) @@ -299,4 +326,40 @@ internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder } } } + + public async Task CreateFileAsync(string name) + { + try + { + var storageFile = await StorageHelper.CreateFile(FileHandle, name); + if (storageFile is null) + { + return null; + } + + return new JSStorageFile(storageFile); + } + catch (JSException ex) when (ex.Message == BrowserStorageProvider.NoPermissionsMessage) + { + throw new UnauthorizedAccessException("User denied permissions to open the file", ex); + } + } + + public async Task CreateFolderAsync(string name) + { + try + { + var storageFile = await StorageHelper.CreateFolder(FileHandle, name); + if (storageFile is null) + { + return null; + } + + return new JSStorageFolder(storageFile); + } + catch (JSException ex) when (ex.Message == BrowserStorageProvider.NoPermissionsMessage) + { + throw new UnauthorizedAccessException("User denied permissions to open the file", ex); + } + } } diff --git a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts index 399e268915..384a56f4aa 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts @@ -97,6 +97,49 @@ export class StorageItem { return (item.handle as any).entries(); } + public static async createFile(item: StorageItem, name: string) : Promise { + if (item.kind !== "directory" || !item.handle) { + throw new TypeError("Unable to create item in the requested directory"); + } + + await item.verityPermissions("readwrite"); + + return await (item.handle as any).getFileHandle(name, { create: true }); + } + + public static async createFolder(item: StorageItem, name: string) : Promise { + if (item.kind !== "directory" || !item.handle) { + throw new TypeError("Unable to create item in the requested directory"); + } + + await item.verityPermissions("readwrite"); + + return await (item.handle as any).getDirectoryHandle(name, { create: true }); + } + + public static async deleteAsync(item: StorageItem) : Promise { + if (!item.handle) { + return null; + } + + await item.verityPermissions("readwrite"); + + return await (item.handle as any).remove({recursive: true}); + } + + public static async moveAsync(item: StorageItem, destination: StorageItem) : Promise { + if (!item.handle) { + return null; + } + if (destination.kind !== "directory" || !destination.handle) { + throw new TypeError("Unable to move item to the requested directory"); + } + + await item.verityPermissions("readwrite"); + + return await (item.handle as any).move(destination /*, newName */); + } + private async verityPermissions(mode: "read" | "readwrite"): Promise { if (!this.handle) { return; diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index baf31ebc73..7ab593e121 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -32,6 +32,7 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem } internal NSUrl Url { get; } + internal string FilePath => _filePath; public bool CanBookmark => true; @@ -54,6 +55,38 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem return Task.FromResult(new IOSStorageFolder(Url.RemoveLastPathComponent())); } + public Task DeleteAsync() + { + return NSFileManager.DefaultManager.Remove(Url, out var error) + ? Task.CompletedTask + : Task.FromException(new NSErrorException(error)); + } + + public async Task MoveAsync(IStorageFolder destination) + { + if (destination is not IOSStorageFolder folder) + { + throw new InvalidOperationException("Destination folder must be initialized the StorageProvider API."); + } + + var isDir = this is IStorageFolder; + var newPath = new NSUrl(System.IO.Path.Combine(folder.FilePath, Name), isDir); + + if (NSFileManager.DefaultManager.Move(folder.Url, newPath, out var error)) + { + return isDir + ? new IOSStorageFolder(newPath) + : new IOSStorageFile(newPath); + } + + if (error is not null) + { + throw new NSErrorException(error); + } + + return null; + } + public Task ReleaseBookmarkAsync() { // no-op @@ -149,4 +182,33 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder yield return item; } } + + public Task CreateFileAsync(string name) + { + var path = System.IO.Path.Combine(FilePath, name); + NSFileAttributes? attributes = null; + if (NSFileManager.DefaultManager.CreateFile(path, null, attributes)) + { + return Task.FromResult(new IOSStorageFile(new NSUrl(path, false))); + } + + return Task.FromResult(null); + } + + public Task CreateFolderAsync(string name) + { + var path = System.IO.Path.Combine(FilePath, name); + NSFileAttributes? attributes = null; + if (NSFileManager.DefaultManager.CreateDirectory(path, false, attributes, out var error)) + { + return Task.FromResult(new IOSStorageFolder(new NSUrl(path, true))); + } + + if (error is not null) + { + throw new NSErrorException(error); + } + + return Task.FromResult(null); + } }