Standardize the behavior of CreateFile/CreateFolder across different OS#20850
Standardize the behavior of CreateFile/CreateFolder across different OS#20850Frederisk wants to merge 4 commits intoAvaloniaUI:masterfrom
Conversation
To maintain consistency with other operating system APIs, see: AvaloniaUI#20804 & AvaloniaUI#20807
|
I've just tried writing some basic unit tests to test these features, but I seem to be finding that there aren't any platform-specific test projects in the current solution? Or am I missing something? |
|
Thanks. File I/O is really neeeded to Avalonia to be cross-platform. |
| } | ||
|
|
||
| public Task<IStorageFile?> CreateFileAsync(string name) | ||
| public async Task<IStorageFile?> CreateFileAsync(string name) |
There was a problem hiding this comment.
For CreateFileAsync i would have it truncated content. And separate method AppendFIleAsync - that will behave like CreateFileAsync, but append on top of existing data.
That will help to have retries for fetching files. and Append - for logs.
There was a problem hiding this comment.
For write actions, after #20804, when you try to call OpenWriteAsync, it will already truncate the file correctly. The question here is what happens when the Create method is called on an existing file. Just post that file; or truncate the existing file first and post it.
This can cause differences in behavior if someone tries to write some weird code:
var parentFolder = await GetParentFolderAsync();
byte[] bytes = [1, 2, 3];
// Create a file and write to it.
var file = await parentFolder.CreateFileAsync('file.txt');
var stream = await file.OpenWriteAsync();
await stream.WriteAsync(bytes);
await stream.FlushAsync();
strean.Dispose();
// Contents of file file.txt: 123
// Get and write to it.
file = await parentFolder.GetFileAsync('file.txt');
stream = await file.OpenWriteAsync();
await stream.WriteAsync(bytes);
await stream.FlushAsync();
strean.Dispose();
// Contents of file file.txt: 123
// Try creating an existing file and appending content:
file = await parentFolder.CreateFileAsync('file.txt'); // The file may or may not be truncated here, depending on the operating system.
// Contents of file file.txt: empty or 123However, you did remind me that we do need a new method to handle file appending. AppendFileAsync seems to be confused with GetFileAsync, so perhaps IStorageFile.OpenAppendAsync or another solution would be a better choice. (Of course, that's another topic.)
|
You can test this PR using the following package version. |
|
You can test this PR using the following package version. |
|
You can test this PR using the following package version. |
What does the pull request do?
Currently, Avalonia only provides a very simple wrapper for system I/O. This results in significant behavioral differences in the same I/O method across different operating systems, which may lead to unexpected operations.
This PR aims to standardize the differences in method behavior across different platforms, starting with creating files and folders. Specifically, the approach here targets .NET's
FileInfo.Create()andDirectoryInfo.CreateSubdirectory()methods to achieve similar interface behavior. The consideration is that this approach may be closer to the user experience of most .NET developers. This is also a default implementation of Avalonia:Avalonia/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageItem.cs
Lines 162 to 172 in f3df9b1
What is the current behavior?
Currently, this behavior exhibits significant inconsistencies across different operating systems:
trueif the directory was created,trueifcreateIntermediatesis set and the directory already exists, orfalseif an error occurred.Currently,createIntermediatesis set tofalse:Avalonia/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
Line 302 in d8a0008
create: true: When set totrueif the file is not found, one with the specified name will be created and returned.However, there are still a few issues that may need to be discussed to decide how to handle this. The first is whether the file should be truncated when attempting to call the CreateFileAsync method if it already exists. This question is somewhat similar to another recent PR.
Another issue is that I noticed the original code heavily used
Task.FromResultand didn't put methods likeCreateFile, which would cause I/O blocking, intoTask.Runto avoid thread blocking. I'm not sure what the purpose of this is, or if it needs fixing. I'd be happy to improve this in another PR if needed (to avoid including too much irrelevant content in this PR).What is the updated/expected behavior with this PR?
Now,
{ keepExistingData: false }, the file will be truncated first when attempting to write;In addition, I have reserved code to truncate existing files, but it has been commented out and we can decide whether to keep it based on the discussion.
How was the solution implemented (if it's not obvious)?
Before:
filealready existsfolderalready existsfile (1)folder (1)fileNSErrorExceptionfilefolderfilefolderAfter:
filealready existsfolderalready existsfilefolderfilefolderfilefolderfilefolderChecklist
Breaking changes
Aside from desktop environments such as Windows and Linux, the behavior of Create will change significantly on other operating systems.
Obsoletions / Deprecations
Fixed issues
Fixes #20580