Browse Source

Merge pull request #10603 from AvaloniaUI/update-storage-provider-one-last-time

Change StorageFolder.GetItemsAsync to return IAsyncEnumerable
pull/10607/head
Max Katz 3 years ago
committed by GitHub
parent
commit
123a2ed8fe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  2. 8
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  3. 16
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  4. 12
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  5. 2
      src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
  6. 16
      src/Browser/Avalonia.Browser/Interop/GeneralHelpers.cs
  7. 6
      src/Browser/Avalonia.Browser/Interop/StorageHelper.cs
  8. 47
      src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs
  9. 7
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/generalHelpers.ts
  10. 10
      src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts
  11. 9
      src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs

4
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -324,9 +324,9 @@ namespace ControlCatalog.Pages
mappedResults.Add("+> " + FullPathOrName(selectedItem));
if (selectedItem is IStorageFolder folder)
{
foreach (var innerItems in await folder.GetItemsAsync())
await foreach (var innerItem in folder.GetItemsAsync())
{
mappedResults.Add("++> " + FullPathOrName(innerItems));
mappedResults.Add("++> " + FullPathOrName(innerItem));
}
}
}

8
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -104,8 +104,12 @@ namespace ControlCatalog.Pages
}
else if (item is IStorageFolder folder)
{
var items = await folder.GetItemsAsync();
contentStr += $"Folder {item.Name}: items {items.Count}{Environment.NewLine}{Environment.NewLine}";
var childrenCount = 0;
await foreach (var _ in folder.GetItemsAsync())
{
childrenCount++;
}
contentStr += $"Folder {item.Name}: items {childrenCount}{Environment.NewLine}{Environment.NewLine}";
}
}

16
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -131,19 +131,17 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
return Task.FromResult(new StorageItemProperties());
}
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
if (!await EnsureExternalFilesPermission(false))
{
return Array.Empty<IStorageItem>();
yield break;
}
List<IStorageItem> files = new List<IStorageItem>();
var contentResolver = Activity.ContentResolver;
if (contentResolver == null)
{
return files;
yield break;
}
var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(Uri!, DocumentsContract.GetTreeDocumentId(Uri));
@ -168,12 +166,10 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
continue;
}
files.Add(mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) :
new AndroidStorageFile(Activity, uri));
yield return mime == DocumentsContract.Document.MimeTypeDir ? new AndroidStorageFolder(Activity, uri, false) :
new AndroidStorageFile(Activity, uri);
}
}
return files;
}
}

12
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs

@ -57,14 +57,16 @@ internal class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult<IStorageFolder?>(null);
}
public Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
var items = DirectoryInfo.GetDirectories()
var items = DirectoryInfo.EnumerateDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d))
.Concat(DirectoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
.ToArray();
.Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)));
return Task.FromResult<IReadOnlyList<IStorageItem>>(items);
foreach (var item in items)
{
yield return item;
}
}
public virtual Task<string?> SaveBookmarkAsync()

2
src/Avalonia.Base/Platform/Storage/IStorageFolder.cs

@ -16,5 +16,5 @@ public interface IStorageFolder : IStorageItem
/// <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();
IAsyncEnumerable<IStorageItem> GetItemsAsync();
}

16
src/Browser/Avalonia.Browser/Interop/GeneralHelpers.cs

@ -1,4 +1,5 @@
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
namespace Avalonia.Browser.Interop;
@ -8,15 +9,22 @@ internal static partial class GeneralHelpers
public static partial JSObject[] ItemsArrayAt(JSObject jsObject, string key);
public static JSObject[] GetPropertyAsJSObjectArray(this JSObject jsObject, string key) => ItemsArrayAt(jsObject, key);
[JSImport("GeneralHelpers.itemAt", AvaloniaModule.MainModuleName)]
public static partial JSObject ItemAtInt(JSObject jsObject, int key);
public static JSObject GetArrayItem(this JSObject jsObject, int key) => ItemAtInt(jsObject, key);
[JSImport("GeneralHelpers.itemsArrayAt", AvaloniaModule.MainModuleName)]
public static partial string[] ItemsArrayAtAsStrings(JSObject jsObject, string key);
public static string[] GetPropertyAsStringArray(this JSObject jsObject, string key) => ItemsArrayAtAsStrings(jsObject, key);
[JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)]
public static partial string IntCallMethodString(JSObject jsObject, string name);
public static partial string IntCallMethodStr(JSObject jsObject, string name);
[JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)]
public static partial string IntCallMethodStrStr(JSObject jsObject, string name, string arg1);
[JSImport("GeneralHelpers.callMethod", AvaloniaModule.MainModuleName)]
public static partial string IntCallMethodStringString(JSObject jsObject, string name, string arg1);
public static partial Task<JSObject?> IntCallMethodPromiseObj(JSObject jsObject, string name);
public static string CallMethodString(this JSObject jsObject, string name) => IntCallMethodString(jsObject, name);
public static string CallMethodString(this JSObject jsObject, string name, string arg1) => IntCallMethodStringString(jsObject, name, arg1);
public static string CallMethodString(this JSObject jsObject, string name) => IntCallMethodStr(jsObject, name);
public static string CallMethodString(this JSObject jsObject, string name, string arg1) => IntCallMethodStrStr(jsObject, name, arg1);
public static Task<JSObject?> CallMethodObjectAsync(this JSObject jsObject, string name) => IntCallMethodPromiseObj(jsObject, name);
}

6
src/Browser/Avalonia.Browser/Interop/StorageHelper.cs

@ -40,9 +40,9 @@ internal static partial class StorageHelper
[JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)]
public static partial Task<JSObject> OpenRead(JSObject item);
[JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)]
[return: JSMarshalAs<JSType.Promise<JSType.Object>>]
public static partial Task<JSObject?> GetItems(JSObject item);
[JSImport("StorageItem.getItemsIterator", AvaloniaModule.StorageModuleName)]
[return: JSMarshalAs<JSType.Object>]
public static partial JSObject? GetItemsIterator(JSObject item);
[JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)]
public static partial JSObject[] ItemsArray(JSObject item);

47
src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs

@ -258,24 +258,45 @@ internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder
{
}
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
using var items = await StorageHelper.GetItems(FileHandle);
if (items is null)
using var itemsIterator = StorageHelper.GetItemsIterator(FileHandle);
if (itemsIterator is null)
{
return Array.Empty<IStorageItem>();
yield break;
}
var itemsArray = StorageHelper.ItemsArray(items);
while (true)
{
var nextResult = await itemsIterator.CallMethodObjectAsync("next");
if (nextResult is null)
{
yield break;
}
var isDone = nextResult.GetPropertyAsBoolean("done");
if (isDone)
{
yield break;
}
return itemsArray
.Select(reference => reference.GetPropertyAsString("kind") switch
var valArray = nextResult.GetPropertyAsJSObject("value");
var storageItem = valArray?.GetArrayItem(1); // 0 - item name, 1 - item instance
if (storageItem is null)
{
"directory" => (IStorageItem)new JSStorageFolder(reference),
"file" => new JSStorageFile(reference),
_ => null
})
.Where(i => i is not null)
.ToArray()!;
yield break;
}
var kind = storageItem.GetPropertyAsString("kind");
switch (kind)
{
case "directory":
yield return new JSStorageFolder(storageItem);
break;
case "file":
yield return new JSStorageFile(storageItem);
break;
}
}
}
}

7
src/Browser/Avalonia.Browser/webapp/modules/avalonia/generalHelpers.ts

@ -1,5 +1,5 @@
export class GeneralHelpers {
public static itemsArrayAt(instance: any, key: string): any[] {
public static itemsArrayAt(instance: any, key: any): any[] {
const items = instance[key];
if (!items) {
return [];
@ -12,6 +12,11 @@ export class GeneralHelpers {
return retItems;
}
public static itemAt(instance: any, key: any): any {
const item = instance[key];
return item;
}
public static callMethod(instance: any, name: string /*, args */): any {
const args = Array.prototype.slice.call(arguments, 2);
return instance[name].apply(instance, args);

10
src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts

@ -89,16 +89,12 @@ export class StorageItem {
}
}
public static async getItems(item: StorageItem): Promise<StorageItems> {
public static getItemsIterator(item: StorageItem): any | null {
if (item.kind !== "directory" || !item.handle) {
return new StorageItems([]);
return null;
}
const items: StorageItem[] = [];
for await (const [, value] of (item.handle as any).entries()) {
items.push(new StorageItem(value));
}
return new StorageItems(items);
return (item.handle as any).entries();
}
private async verityPermissions(mode: "read" | "readwrite"): Promise<void | never> {

9
src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs

@ -114,8 +114,9 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
{
}
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
// TODO: find out if it can be lazily enumerated.
var tcs = new TaskCompletionSource<IReadOnlyList<IStorageItem>>();
new NSFileCoordinator().CoordinateRead(Url,
@ -142,6 +143,10 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
throw new NSErrorException(error);
}
return await tcs.Task;
var items = await tcs.Task;
foreach (var item in items)
{
yield return item;
}
}
}

Loading…
Cancel
Save