瀏覽代碼

Add folder.GetItemsAsync API

Max Katz 3 年之前
父節點
當前提交
97a5a9e1f6

+ 14 - 2
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -243,8 +243,8 @@ namespace ControlCatalog.Pages
             async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
             {
                 items ??= Array.Empty<IStorageItem>();
-                var mappedResults = items.Select(FullPathOrName).ToList();
                 bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark";
+                var mappedResults = new List<string>();
 
                 if (items.FirstOrDefault() is IStorageItem item)
                 {
@@ -293,7 +293,19 @@ Content:
                     lastSelectedDirectory = await item.GetParentAsync();
                     if (lastSelectedDirectory is not null)
                     {
-                        mappedResults.Insert(0,  "Parent: " + FullPathOrName(lastSelectedDirectory));
+                        mappedResults.Add(FullPathOrName(lastSelectedDirectory));
+                    }
+
+                    foreach (var selectedItem in items)
+                    {
+                        mappedResults.Add("+> " + FullPathOrName(selectedItem));
+                        if (selectedItem is IStorageFolder folder)
+                        {
+                            foreach (var innerItems in await folder.GetItemsAsync())
+                            {
+                                mappedResults.Add("++> " + FullPathOrName(innerItems));
+                            }
+                        }
                     }
                 }
 

+ 25 - 0
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
@@ -106,6 +107,30 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
     {
         return Task.FromResult(new StorageItemProperties());
     }
+
+    public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
+    {
+        using var javaFile = new JavaFile(Uri.Path!);
+
+        // Java file represents files AND directories. Don't be confused.
+        var files = await javaFile.ListFilesAsync().ConfigureAwait(false);
+        if (files is null)
+        {
+            return Array.Empty<IStorageItem>();
+        }
+
+        return files
+            .Select(f => (file: f, uri: AndroidUri.FromFile(f)))
+            .Where(t => t.uri is not null)
+            .Select(t => t.file switch
+            {
+                { IsFile: true } => (IStorageItem)new AndroidStorageFile(Context, t.uri!),
+                { IsDirectory: true } => new AndroidStorageFolder(Context, t.uri!),
+                _ => null
+            })
+            .Where(i => i is not null)
+            .ToArray()!;
+    }
 }
 
 internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile

+ 12 - 0
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs

@@ -1,6 +1,8 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
+using System.Linq;
 using System.Security;
 using System.Threading.Tasks;
 using Avalonia.Metadata;
@@ -43,6 +45,16 @@ public class BclStorageFolder : IStorageBookmarkFolder
         return Task.FromResult<IStorageFolder?>(null);
     }
 
+    public Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
+    {
+         var items = _directoryInfo.GetDirectories()
+            .Select(d => (IStorageItem)new BclStorageFolder(d))
+            .Concat(_directoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
+            .ToArray();
+
+         return Task.FromResult<IReadOnlyList<IStorageItem>>(items);
+    }
+
     public virtual Task<string?> SaveBookmark()
     {
         return Task.FromResult<string?>(_directoryInfo.FullName);

+ 10 - 1
src/Avalonia.Base/Platform/Storage/IStorageFolder.cs

@@ -1,4 +1,6 @@
-using Avalonia.Metadata;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
 
 namespace Avalonia.Platform.Storage;
 
@@ -8,4 +10,11 @@ namespace Avalonia.Platform.Storage;
 [NotClientImplementable]
 public interface IStorageFolder : IStorageItem
 {
+    /// <summary>
+    /// Gets the files and subfolders in the current folder.
+    /// </summary>
+    /// <returns>
+    /// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an <see cref="IStorageItem"/> implementation object.
+    /// </returns>
+    Task<IReadOnlyList<IStorageItem>> GetItemsAsync();
 }

+ 25 - 0
src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs

@@ -196,5 +196,30 @@ namespace Avalonia.Web.Blazor.Interop.Storage
         public JSStorageFolder(IJSInProcessObjectReference fileHandle) : base(fileHandle)
         {
         }
+
+        public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
+        {
+            var items = await FileHandle.InvokeAsync<IJSInProcessObjectReference?>("getItems");
+            if (items is null)
+            {
+                return Array.Empty<IStorageItem>();
+            }
+
+            var count = items.Invoke<int>("count");
+
+            return Enumerable.Range(0, count)
+                .Select(index =>
+                {
+                    var reference = items.Invoke<IJSInProcessObjectReference>("at", index);
+                    return reference.Invoke<string>("getKind") switch
+                    {
+                        "directory" => (IStorageItem)new JSStorageFolder(reference),
+                        "file" => new JSStorageFile(reference),
+                        _ => null
+                    };
+                })
+                .Where(i => i is not null)
+                .ToArray()!;
+        }
     }
 }

+ 31 - 14
src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts

@@ -14,6 +14,8 @@ declare global {
 
         queryPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">;
         requestPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">;
+
+        entries(): AsyncIterableIterator<[string, FileSystemFileHandle]>;
     }
     type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; 
     type StartInDirectory =  WellKnownDirectory | FileSystemFileHandle;
@@ -53,7 +55,7 @@ class IndexedDbWrapper {
     }
 
     public connect(): Promise<InnerDbConnection> {
-        var conn = window.indexedDB.open(this.databaseName, 1);
+        const conn = window.indexedDB.open(this.databaseName, 1);
 
         conn.onupgradeneeded = event => {
             const db = (<IDBRequest<IDBDatabase>>event.target).result;
@@ -85,7 +87,7 @@ class InnerDbConnection {
         const os = this.openStore(store, "readwrite");
 
         return new Promise((resolve, reject) => {
-            var response = os.put(obj, key);
+            const response = os.put(obj, key);
             response.onsuccess = () => {
                 resolve(response.result);
             };
@@ -99,7 +101,7 @@ class InnerDbConnection {
         const os = this.openStore(store, "readonly");
 
         return new Promise((resolve, reject) => {
-            var response = os.get(key);
+            const response = os.get(key);
             response.onsuccess = () => {
                 resolve(response.result);
             };
@@ -113,7 +115,7 @@ class InnerDbConnection {
         const os = this.openStore(store, "readwrite");
 
         return new Promise((resolve, reject) => {
-            var response = os.delete(key);
+            const response = os.delete(key);
             response.onsuccess = () => {
                 resolve();
             };
@@ -134,17 +136,20 @@ const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [
 ])
 
 class StorageItem {
-    constructor(private handle: FileSystemFileHandle, private bookmarkId?: string) { }
+    constructor(public handle: FileSystemFileHandle, private bookmarkId?: string) { }
 
     public getName(): string {
         return this.handle.name
     }
 
+    public getKind(): string {
+        return this.handle.kind;
+    }
+
     public async openRead(): Promise<Blob> {
         await this.verityPermissions('read');
 
-        var file = await this.handle.getFile();
-        return file;
+        return await this.handle.getFile();
     }
 
     public async openWrite(): Promise<FileSystemWritableFileStream> {
@@ -154,7 +159,7 @@ class StorageItem {
     }
 
     public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string }> {
-        var file = this.handle.getFile && await this.handle.getFile();
+        const file = this.handle.getFile && await this.handle.getFile();
         
         return file && {
             Size: file.size,
@@ -163,6 +168,18 @@ class StorageItem {
         }
     }
 
+    public async getItems(): Promise<StorageItems> {
+        if (this.handle.kind !== "directory"){
+            return new StorageItems([]);
+        }
+        
+        const items: StorageItem[] = [];
+        for await (const [key, value] of this.handle.entries()) {
+            items.push(new StorageItem(value));
+        }
+        return new StorageItems(items);
+    }
+    
     private async verityPermissions(mode: PermissionsMode): Promise<void | never> {
         if (await this.handle.queryPermission({ mode }) === 'granted') {
             return;
@@ -235,12 +252,12 @@ export class StorageProvider {
     }
 
     public static async selectFolderDialog(
-        startIn: StartInDirectory | null)
+        startIn: StorageItem | null)
         : Promise<StorageItem> {
 
         // 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined.
         const options: DirectoryPickerOptions = {
-            startIn: (startIn || undefined)
+            startIn: (startIn?.handle || undefined)
         };
 
         const handle = await window.showDirectoryPicker(options);
@@ -248,12 +265,12 @@ export class StorageProvider {
     }
 
     public static async openFileDialog(
-        startIn: StartInDirectory | null, multiple: boolean,
+        startIn: StorageItem | null, multiple: boolean,
         types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean)
         : Promise<StorageItems> {
 
         const options: OpenFilePickerOptions = {
-            startIn: (startIn || undefined),
+            startIn: (startIn?.handle || undefined),
             multiple,
             excludeAcceptAllOption,
             types: (types || undefined)
@@ -264,12 +281,12 @@ export class StorageProvider {
     }
 
     public static async saveFileDialog(
-        startIn: StartInDirectory | null, suggestedName: string | null,
+        startIn: StorageItem | null, suggestedName: string | null,
         types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean)
         : Promise<StorageItem> {
 
         const options: SaveFilePickerOptions = {
-            startIn: (startIn || undefined),
+            startIn: (startIn?.handle || undefined),
             suggestedName: (suggestedName || undefined),
             excludeAcceptAllOption,
             types: (types || undefined)

+ 17 - 0
src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs

@@ -1,6 +1,8 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
+using System.Linq;
 using System.Threading.Tasks;
 using Avalonia.Logging;
 using Avalonia.Platform.Storage;
@@ -118,4 +120,19 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
     public IOSStorageFolder(NSUrl url) : base(url)
     {
     }
+
+    public Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
+    {
+        var content = NSFileManager.DefaultManager.GetDirectoryContent(Url, null, NSDirectoryEnumerationOptions.None, out var error);
+        if (error is not null)
+        {
+            return Task.FromException<IReadOnlyList<IStorageItem>>(new NSErrorException(error));
+        }
+
+        var items = content
+            .Select(u => u.HasDirectoryPath ? (IStorageItem)new IOSStorageFolder(u) : new IOSStorageFile(u))
+            .ToArray();
+
+        return Task.FromResult<IReadOnlyList<IStorageItem>>(items);
+    }
 }