Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access single file or folder from IStorageFolder by name #17771

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,79 @@ public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
return destination;
}
}

private async Task<IStorageItem?> GetItemAsync(string name, bool isDirectory)
{
if (!await EnsureExternalFilesPermission(false))
{
return null;
}

var contentResolver = Activity.ContentResolver;
if (contentResolver == null)
{
return null;
}

var root = PermissionRoot ?? Uri;
var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri);
var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId);

var projection = new[]
{
DocumentsContract.Document.ColumnDocumentId,
DocumentsContract.Document.ColumnMimeType,
DocumentsContract.Document.ColumnDisplayName
};

if (childrenUri != null)
{
using var cursor = contentResolver.Query(childrenUri, projection, null, null, null);
if (cursor != null)
{
while (cursor.MoveToNext())
{
var id = cursor.GetString(0);
var mime = cursor.GetString(1);

var fileName = cursor.GetString(2);
if (fileName != name)
{
continue;
}

bool mineDirectory = mime == DocumentsContract.Document.MimeTypeDir;
if (isDirectory != mineDirectory)
{
return null;
}

var uri = DocumentsContract.BuildDocumentUriUsingTree(root, id);
if (uri == null)
{
return null;
}

return isDirectory ? new AndroidStorageFolder(Activity, uri, false, this, root) :
new AndroidStorageFile(Activity, uri, this, root);
}
}
}

return null;
}

public async Task<IStorageFolder?> GetFolderAsync(string name)
{
var folder = await GetItemAsync(name, true);
return (IStorageFolder?)folder;
}

public async Task<IStorageFile?> GetFileAsync(string name)
{
var file = await GetItemAsync(name, false);
return (IStorageFile?)file;
}
}

internal sealed class WellKnownAndroidStorageFolder : AndroidStorageFolder
Expand Down
6 changes: 6 additions & 0 deletions src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ public IAsyncEnumerable<IStorageItem> GetItemsAsync() => GetItemsCore(directoryI

public Task<IStorageFolder?> CreateFolderAsync(string name) => Task.FromResult(
(IStorageFolder?)WrapFileSystemInfo(CreateFolderCore(directoryInfo, name)));

public Task<IStorageFolder?> GetFolderAsync(string name) => Task.FromResult(
(IStorageFolder?)WrapFileSystemInfo(GetFolderCore(directoryInfo, name)));

public Task<IStorageFile?> GetFileAsync(string name) => Task.FromResult(
(IStorageFile?)WrapFileSystemInfo(GetFileCore(directoryInfo, name)));
}
22 changes: 22 additions & 0 deletions src/Avalonia.Base/Platform/Storage/FileIO/BclStorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,28 @@ internal static IEnumerable<FileSystemInfo> GetItemsCore(DirectoryInfo directory
.OfType<FileSystemInfo>()
.Concat(directoryInfo.EnumerateFiles());

internal static FileSystemInfo? GetFolderCore(DirectoryInfo directoryInfo, string name)
{
var path = System.IO.Path.Combine(directoryInfo.FullName, name);
if (Directory.Exists(path))
{
return new DirectoryInfo(path);
}

return null;
}

internal static FileSystemInfo? GetFileCore(DirectoryInfo directoryInfo, string name)
{
var path = System.IO.Path.Combine(directoryInfo.FullName, name);
if (File.Exists(path))
{
return new FileInfo(path);
}

return null;
}

internal static FileInfo CreateFileCore(DirectoryInfo directoryInfo, string name)
{
var fileName = System.IO.Path.Combine(directoryInfo.FullName, name);
Expand Down
18 changes: 18 additions & 0 deletions src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ public interface IStorageFolder : IStorageItem
/// </returns>
IAsyncEnumerable<IStorageItem> GetItemsAsync();

/// <summary>
/// Gets the folder with the specified name from the current folder.
/// </summary>
/// <param name="name">The name of the folder to get</param>
/// <returns>
/// When this method completes successfully, it returns the folder with the specified name from the current folder.
/// </returns>
Task<IStorageFolder?> GetFolderAsync(string name);

/// <summary>
/// Gets the file with the specified name from the current folder.
/// </summary>
/// <param name="name">The name of the file to get</param>
/// <returns>
/// When this method completes successfully, it returns the file with the specified name from the current folder.
/// </returns>
Task<IStorageFile?> GetFileAsync(string name);

/// <summary>
/// Creates a file with specified name as a child of the current storage folder
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/Avalonia.Native/StorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,18 @@ IEnumerable<IStorageItem> GetItems()
var folder = BclStorageItem.CreateFolderCore(directoryInfo, name);
return Task.FromResult((IStorageFolder?)WrapFileSystemInfo(folder, ScopeOwnerUri));
}

public Task<IStorageFolder?> GetFolderAsync(string name)
{
using var scope = OpenScope();
var item = BclStorageItem.GetFolderCore(directoryInfo, name);
return Task.FromResult((IStorageFolder?)WrapFileSystemInfo(item, ScopeOwnerUri));
}

public Task<IStorageFile?> GetFileAsync(string name)
{
using var scope = OpenScope();
var item = BclStorageItem.GetFileCore(directoryInfo, name);
return Task.FromResult((IStorageFile?)WrapFileSystemInfo(item, ScopeOwnerUri));
}
}
6 changes: 6 additions & 0 deletions src/Browser/Avalonia.Browser/Interop/StorageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,10 @@ internal static partial class StorageHelper

[JSImport("StorageProvider.createFolder", AvaloniaModule.StorageModuleName)]
public static partial Task<JSObject?> CreateFolder(JSObject folderHandle, string name);

[JSImport("StorageItem.getFile", AvaloniaModule.StorageModuleName)]
public static partial Task<JSObject?> GetFile(JSObject folderHandle, string name);

[JSImport("StorageItem.getFolder", AvaloniaModule.StorageModuleName)]
public static partial Task<JSObject?> GetFolder(JSObject folderHandle, string name);
}
36 changes: 36 additions & 0 deletions src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,40 @@ public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
throw new UnauthorizedAccessException("User denied permissions to open the file", ex);
}
}

public async Task<IStorageFolder?> GetFolderAsync(string name)
{
try
{
var storageFile = await StorageHelper.GetFolder(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 folder", ex);
}
}

public async Task<IStorageFile?> GetFileAsync(string name)
{
try
{
var storageFile = await StorageHelper.GetFile(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);
}
}
}
20 changes: 20 additions & 0 deletions src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ export class StorageItem {
return await ((item.handle as any).getFileHandle(name, { create: true }) as Promise<any>);
}

public static async getFile(item: StorageItem, name: string): Promise<any | null> {
if (item.kind !== "directory" || !item.handle) {
throw new TypeError("Unable to create item in the requested directory");
}

await item.verityPermissions("read");

return await ((item.handle as any).getFileHandle(name) as Promise<any>);
}

public static async createFolder(item: StorageItem, name: string): Promise<any | null> {
if (item.kind !== "directory" || !item.handle) {
throw new TypeError("Unable to create item in the requested directory");
Expand All @@ -117,6 +127,16 @@ export class StorageItem {
return await ((item.handle as any).getDirectoryHandle(name, { create: true }) as Promise<any>);
}

public static async getFolder(item: StorageItem, name: string): Promise<any | null> {
if (item.kind !== "directory" || !item.handle) {
throw new TypeError("Unable to create item in the requested directory");
}

await item.verityPermissions("read");

return await ((item.handle as any).getDirectoryHandle(name) as Promise<any>);
}

public static async deleteAsync(item: StorageItem): Promise<any | null> {
if (!item.handle) {
return null;
Expand Down
32 changes: 32 additions & 0 deletions src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,36 @@ public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
SecurityScopedAncestorUrl.StopAccessingSecurityScopedResource();
}
}

private NSUrl? GetItem(string name, bool isDirectory)
{
try
{
SecurityScopedAncestorUrl.StartAccessingSecurityScopedResource();

var path = System.IO.Path.Combine(FilePath, name);
if (NSFileManager.DefaultManager.FileExists(path, ref isDirectory))
{
return new NSUrl(path, isDirectory);
}

return null;
}
finally
{
SecurityScopedAncestorUrl.StopAccessingSecurityScopedResource();
}
}

public Task<IStorageFolder?> GetFolderAsync(string name)
{
var url = GetItem(name, true);
return Task.FromResult<IStorageFolder?>(url is null ? null : new IOSStorageFolder(url));
}

public Task<IStorageFile?> GetFileAsync(string name)
{
var url = GetItem(name, false);
return Task.FromResult<IStorageFile?>(url is null ? null : new IOSStorageFile(url));
}
}
Loading