Browse Source

Merge pull request #8458 from AvaloniaUI/folder-get-items

IStorageFolder.GetItemsAsync() + use Async suffix in storage provider methods
pull/8657/head
Max Katz 4 years ago
committed by GitHub
parent
commit
b4e45cccf2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  2. 33
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  3. 8
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  4. 16
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  5. 2
      src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs
  6. 4
      src/Avalonia.Base/Platform/Storage/IStorageFile.cs
  7. 11
      src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
  8. 2
      src/Avalonia.Base/Platform/Storage/IStorageItem.cs
  9. 33
      src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs
  10. 45
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts
  11. 25
      src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs

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

@ -195,10 +195,10 @@ namespace ControlCatalog.Pages
{
// Sync disposal of StreamWriter is not supported on WASM
#if NET6_0_OR_GREATER
await using var stream = await file.OpenWrite();
await using var stream = await file.OpenWriteAsync();
await using var reader = new System.IO.StreamWriter(stream);
#else
using var stream = await file.OpenWrite();
using var stream = await file.OpenWriteAsync();
using var reader = new System.IO.StreamWriter(stream);
#endif
await reader.WriteLineAsync(openedFileContent.Text);
@ -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";
bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmarkAsync() : "Can't bookmark";
var mappedResults = new List<string>();
if (items.FirstOrDefault() is IStorageItem item)
{
@ -267,9 +267,9 @@ Content:
if (file.CanOpenRead)
{
#if NET6_0_OR_GREATER
await using var stream = await file.OpenRead();
await using var stream = await file.OpenReadAsync();
#else
using var stream = await file.OpenRead();
using var stream = await file.OpenReadAsync();
#endif
using var reader = new System.IO.StreamReader(stream);
@ -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));
}
}
}
}

33
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;
@ -35,13 +36,13 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
public bool CanBookmark => true;
public Task<string?> SaveBookmark()
public Task<string?> SaveBookmarkAsync()
{
Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.FromResult(Uri.ToString());
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.CompletedTask;
@ -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
@ -118,10 +143,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public bool CanOpenWrite => true;
public Task<Stream> OpenRead() => Task.FromResult(OpenContentStream(Context, Uri, false)
public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Context, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));
public Task<Stream> OpenWrite() => Task.FromResult(OpenContentStream(Context, Uri, true)
public Task<Stream> OpenWriteAsync() => Task.FromResult(OpenContentStream(Context, Uri, true)
?? throw new InvalidOperationException("Failed to open content stream"));
private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput)

8
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@ -47,22 +47,22 @@ public class BclStorageFile : IStorageBookmarkFile
return Task.FromResult<IStorageFolder?>(null);
}
public Task<Stream> OpenRead()
public Task<Stream> OpenReadAsync()
{
return Task.FromResult<Stream>(_fileInfo.OpenRead());
}
public Task<Stream> OpenWrite()
public Task<Stream> OpenWriteAsync()
{
return Task.FromResult<Stream>(_fileInfo.OpenWrite());
}
public virtual Task<string?> SaveBookmark()
public virtual Task<string?> SaveBookmarkAsync()
{
return Task.FromResult<string?>(_fileInfo.FullName);
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
// No-op
return Task.CompletedTask;

16
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,12 +45,22 @@ public class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult<IStorageFolder?>(null);
}
public virtual Task<string?> SaveBookmark()
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?> SaveBookmarkAsync()
{
return Task.FromResult<string?>(_directoryInfo.FullName);
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
// No-op
return Task.CompletedTask;

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

@ -6,7 +6,7 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable]
public interface IStorageBookmarkItem : IStorageItem
{
Task ReleaseBookmark();
Task ReleaseBookmarkAsync();
}
[NotClientImplementable]

4
src/Avalonia.Base/Platform/Storage/IStorageFile.cs

@ -18,7 +18,7 @@ public interface IStorageFile : IStorageItem
/// <summary>
/// Opens a stream for read access.
/// </summary>
Task<Stream> OpenRead();
Task<Stream> OpenReadAsync();
/// <summary>
/// Returns true, if file is writeable.
@ -28,5 +28,5 @@ public interface IStorageFile : IStorageItem
/// <summary>
/// Opens stream for writing to the file.
/// </summary>
Task<Stream> OpenWrite();
Task<Stream> OpenWriteAsync();
}

11
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();
}

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

@ -44,7 +44,7 @@ public interface IStorageItem : IDisposable
/// <returns>
/// Returns identifier of a bookmark. Can be null if OS denied request.
/// </returns>
Task<string?> SaveBookmark();
Task<string?> SaveBookmarkAsync();
/// <summary>
/// Gets the parent folder of the current storage item.

33
src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs

@ -145,7 +145,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage
public bool CanBookmark => true;
public Task<string?> SaveBookmark()
public Task<string?> SaveBookmarkAsync()
{
return FileHandle.InvokeAsync<string?>("saveBookmark").AsTask();
}
@ -155,7 +155,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage
return Task.FromResult<IStorageFolder?>(null);
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
return FileHandle.InvokeAsync<string?>("deleteBookmark").AsTask();
}
@ -174,7 +174,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage
}
public bool CanOpenRead => true;
public async Task<Stream> OpenRead()
public async Task<Stream> OpenReadAsync()
{
var stream = await FileHandle.InvokeAsync<IJSStreamReference>("openRead");
// Remove maxAllowedSize limit, as developer can decide if they read only small part or everything.
@ -182,7 +182,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage
}
public bool CanOpenWrite => true;
public async Task<Stream> OpenWrite()
public async Task<Stream> OpenWriteAsync()
{
var properties = await FileHandle.InvokeAsync<FileProperties?>("getProperties");
var streamWriter = await FileHandle.InvokeAsync<IJSInProcessObjectReference>("openWrite");
@ -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()!;
}
}
}

45
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)

25
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;
@ -49,13 +51,13 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
return Task.FromResult<IStorageFolder?>(new IOSStorageFolder(Url.RemoveLastPathComponent()));
}
public Task ReleaseBookmark()
public Task ReleaseBookmarkAsync()
{
// no-op
return Task.CompletedTask;
}
public Task<string?> SaveBookmark()
public Task<string?> SaveBookmarkAsync()
{
try
{
@ -102,12 +104,12 @@ internal sealed class IOSStorageFile : IOSStorageItem, IStorageBookmarkFile
public bool CanOpenWrite => true;
public Task<Stream> OpenRead()
public Task<Stream> OpenReadAsync()
{
return Task.FromResult<Stream>(new IOSSecurityScopedStream(Url, FileAccess.Read));
}
public Task<Stream> OpenWrite()
public Task<Stream> OpenWriteAsync()
{
return Task.FromResult<Stream>(new IOSSecurityScopedStream(Url, FileAccess.Write));
}
@ -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);
}
}

Loading…
Cancel
Save