diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index 47d95d8da19..5d04496feec 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -35,7 +35,7 @@ protected AndroidStorageItem(Activity activity, AndroidUri uri, bool needsExtern } internal AndroidUri Uri { get; set; } - + protected Activity Activity => _activity ?? throw new ObjectDisposedException(nameof(AndroidStorageItem)); public virtual string Name => GetColumnValue(Activity, Uri, DocumentsContract.Document.ColumnDisplayName) @@ -67,7 +67,7 @@ public async Task ReleaseBookmarkAsync() Activity.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); } - + public abstract Task GetBasicPropertiesAsync(); protected static string? GetColumnValue(Context context, AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null) @@ -124,12 +124,12 @@ protected async Task EnsureExternalFilesPermission(bool write) return await _activity!.CheckPermission(Manifest.Permission.ReadExternalStorage); } - + public void Dispose() { _activity = null; } - + internal AndroidUri? PermissionRoot => _permissionRoot; public abstract Task DeleteAsync(); @@ -156,29 +156,45 @@ public AndroidStorageFolder(Activity activity, AndroidUri uri, bool needsExterna { } - public Task CreateFileAsync(string name) + public async Task CreateFileAsync(string name) { - var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream"; + // Try to return an existing file to avoid creating file (1). + var existingFile = await GetItemAsync(name, false) as IStorageFile; + if (existingFile != null) + { + // Uncommenting the following code will cause the file to be truncated when it is created: + // using var _ = await existingFile.OpenWriteAsync(); + return existingFile; + } + // Create new one and return it. var treeUri = GetTreeUri().treeUri; + var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream"; var newFile = DocumentsContract.CreateDocument(Activity.ContentResolver!, treeUri!, mimeType, name); if(newFile == null) { - return Task.FromResult(null); + return null; } - return Task.FromResult(new AndroidStorageFile(Activity, newFile, this)); + return new AndroidStorageFile(Activity, newFile, this); } - public Task CreateFolderAsync(string name) + public async Task CreateFolderAsync(string name) { + // Try to return an existing folder to avoid creating folder (1). + var existingFolder = await GetItemAsync(name, true) as IStorageFolder; + if (existingFolder != null) + { + return existingFolder; + } + // Create new one and return it. var treeUri = GetTreeUri().treeUri; var newFolder = DocumentsContract.CreateDocument(Activity.ContentResolver!, treeUri!, DocumentsContract.Document.MimeTypeDir, name); if (newFolder == null) { - return Task.FromResult(null); + return null; } - return Task.FromResult(new AndroidStorageFolder(Activity, newFolder, false, this, PermissionRoot)); + return new AndroidStorageFolder(Activity, newFolder, false, this, PermissionRoot); } public override async Task DeleteAsync() @@ -332,7 +348,7 @@ public async IAsyncEnumerable GetItemsAsync() if (fileName != name) { continue; - } + } bool mineDirectory = mime == DocumentsContract.Document.MimeTypeDir; if (isDirectory != mineDirectory) diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs index b6d8fd4dafb..ac4dd8cd2c6 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs @@ -37,16 +37,20 @@ public interface IStorageFolder : IStorageItem Task GetFileAsync(string name); /// - /// Creates a file with specified name as a child of the current storage folder + /// Creates, or truncates and overwrites, a file with specified name as a child of the current storage folder. /// /// The display name - /// A new pointing to the moved file. If not null, the current storage item becomes invalid + /// + /// A that provides read/write access to the file specified in name. + /// Task CreateFileAsync(string name); /// - /// Creates a folder with specified name as a child of the current storage folder + /// Creates a folder with specified name as a child of the current storage folder unless they already exist. /// /// The display name - /// A new pointing to the moved file. If not null, the current storage item becomes invalid + /// + /// A that represents the directory at the specified name. This object is returned regardless of whether a directory at the specified name already exists. + /// Task CreateFolderAsync(string name); } diff --git a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs index b5873fdb271..59d039582b6 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Avalonia.Metadata; diff --git a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts index f30e6f59161..ffdf595b5e6 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts @@ -65,7 +65,7 @@ export class StorageItem { await item.verityPermissions("readwrite"); - return await (item.handle as FileSystemFileHandle).createWritable({ keepExistingData: true }); + return await (item.handle as FileSystemFileHandle).createWritable({ keepExistingData: false }); } public static async getProperties(item: StorageItem): Promise<{ Size: number; LastModified: number; Type: string } | null> { @@ -103,7 +103,11 @@ export class StorageItem { } await item.verityPermissions("readwrite"); - + // Uncommenting the following code will cause the file to be truncated when it is created: + // const fileHandle = ((item.handle as any).getFileHandle(name, { create: true }); + // const writable = await fileHandle.createWritable({ keepExistingData: false }); + // await writable.close(); + // return fileHandle; return await ((item.handle as any).getFileHandle(name, { create: true }) as Promise); } diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index 663b1fdd209..60360fa34df 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -299,7 +299,7 @@ public async IAsyncEnumerable GetItemsAsync() var path = System.IO.Path.Combine(FilePath, name); NSFileAttributes? attributes = null; - if (NSFileManager.DefaultManager.CreateDirectory(path, false, attributes, out var error)) + if (NSFileManager.DefaultManager.CreateDirectory(path, true, attributes, out var error)) { return Task.FromResult(new IOSStorageFolder(new NSUrl(path, true), SecurityScopedAncestorUrl)); }