From d2fbbc4a71729bf2d205b37d89ad7c8513d58644 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 6 Jun 2024 11:59:39 +0300 Subject: [PATCH] Gracefully fall back to th next storage provider (#15929) --- .../Storage/FallbackStorageProvider.cs | 95 +++++++++++++++++++ src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 3 +- .../NativeDialogs/CompositeStorageProvider.cs | 82 ---------------- src/Avalonia.X11/X11Window.cs | 12 ++- 4 files changed, 106 insertions(+), 86 deletions(-) create mode 100644 src/Avalonia.Base/Platform/Storage/FallbackStorageProvider.cs delete mode 100644 src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs diff --git a/src/Avalonia.Base/Platform/Storage/FallbackStorageProvider.cs b/src/Avalonia.Base/Platform/Storage/FallbackStorageProvider.cs new file mode 100644 index 0000000000..082a85f2b6 --- /dev/null +++ b/src/Avalonia.Base/Platform/Storage/FallbackStorageProvider.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Avalonia.Platform.Storage; +#pragma warning disable CA1823 + +internal class FallbackStorageProvider : IStorageProvider +{ + private readonly Func>[] _factories; + private readonly List _providers = new(); + private int _nextProviderFactory = 0; + + public FallbackStorageProvider(Func>[] factories) + { + _factories = factories; + } + + async IAsyncEnumerable GetProviders() + { + foreach (var p in _providers) + yield return p; + for (;_nextProviderFactory < _factories.Length;) + { + var p = await _factories[_nextProviderFactory](); + _nextProviderFactory++; + if (p != null) + { + _providers.Add(p); + yield return p; + } + } + } + + async Task GetFor(Func filter) + { + await foreach (var p in GetProviders()) + if (filter(p)) + return p; + throw new IOException("Unable to select a suitable storage provider"); + } + + + // Those should _really_ have been asynchronous, + // but this class is expected to fall back to the managed implementation anyway + public bool CanOpen => true; + public bool CanSave => true; + public bool CanPickFolder => true; + + public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) + { + return await (await GetFor(p => p.CanOpen)).OpenFilePickerAsync(options); + } + + public async Task SaveFilePickerAsync(FilePickerSaveOptions options) + { + return await (await GetFor(p => p.CanSave)).SaveFilePickerAsync(options); + } + + + public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) + { + return await (await GetFor(p => p.CanPickFolder)).OpenFolderPickerAsync(options); + } + + async Task FirstNotNull(TArg arg, Func> cb) + where TResult : class + { + await foreach (var p in GetProviders()) + { + var res = await cb(p, arg); + if (res != null) + return res; + } + + return null; + } + + public Task OpenFileBookmarkAsync(string bookmark) => + FirstNotNull(bookmark, (p, a) => p.OpenFileBookmarkAsync(a)); + + public Task OpenFolderBookmarkAsync(string bookmark) => + FirstNotNull(bookmark, (p, a) => p.OpenFolderBookmarkAsync(a)); + + public Task TryGetFileFromPathAsync(Uri filePath) => + FirstNotNull(filePath, (p, a) => p.TryGetFileFromPathAsync(filePath)); + + public Task TryGetFolderFromPathAsync(Uri folderPath) + => FirstNotNull(folderPath, (p, a) => p.TryGetFolderFromPathAsync(a)); + + public Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder) => + FirstNotNull(wellKnownFolder, (p, a) => p.TryGetWellKnownFolderAsync(a)); + +} \ No newline at end of file diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index d8d788ca0f..a5010a48bc 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -16,7 +16,8 @@ - + + diff --git a/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs b/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs deleted file mode 100644 index 16416c7e56..0000000000 --- a/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs +++ /dev/null @@ -1,82 +0,0 @@ -#nullable enable -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Avalonia.Platform.Storage; - -namespace Avalonia.X11.NativeDialogs; - -internal class CompositeStorageProvider : IStorageProvider -{ - private readonly IEnumerable>> _factories; - public CompositeStorageProvider(IEnumerable>> factories) - { - _factories = factories; - } - - public bool CanOpen => true; - public bool CanSave => true; - public bool CanPickFolder => true; - - private async Task EnsureStorageProvider() - { - foreach (var factory in _factories) - { - var provider = await factory(); - if (provider is not null) - { - return provider; - } - } - - throw new InvalidOperationException("Neither DBus nor GTK are available on the system"); - } - - public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.OpenFilePickerAsync(options).ConfigureAwait(false); - } - - public async Task SaveFilePickerAsync(FilePickerSaveOptions options) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.SaveFilePickerAsync(options).ConfigureAwait(false); - } - - public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.OpenFolderPickerAsync(options).ConfigureAwait(false); - } - - public async Task OpenFileBookmarkAsync(string bookmark) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.OpenFileBookmarkAsync(bookmark).ConfigureAwait(false); - } - - public async Task OpenFolderBookmarkAsync(string bookmark) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.OpenFolderBookmarkAsync(bookmark).ConfigureAwait(false); - } - - public async Task TryGetFileFromPathAsync(Uri filePath) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.TryGetFileFromPathAsync(filePath).ConfigureAwait(false); - } - - public async Task TryGetFolderFromPathAsync(Uri folderPath) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.TryGetFolderFromPathAsync(folderPath).ConfigureAwait(false); - } - - public async Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder) - { - var provider = await EnsureStorageProvider().ConfigureAwait(false); - return await provider.TryGetWellKnownFolderAsync(wellKnownFolder).ConfigureAwait(false); - } -} diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 85eda99418..2c778c0fa5 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -24,6 +24,7 @@ using Avalonia.X11.NativeDialogs; using static Avalonia.X11.XLib; using Avalonia.Input.Platform; using System.Runtime.InteropServices; +using Avalonia.Dialogs; using Avalonia.Platform.Storage.FileIO; // ReSharper disable IdentifierTypo @@ -237,10 +238,15 @@ namespace Avalonia.X11 _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1); } - _storageProvider = new CompositeStorageProvider(new[] + _storageProvider = new FallbackStorageProvider(new[] { - () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreateAsync(Handle) : Task.FromResult(null), - () => GtkSystemDialog.TryCreate(this) + () => _platform.Options.UseDBusFilePicker + ? DBusSystemDialog.TryCreateAsync(Handle) + : Task.FromResult(null), + () => GtkSystemDialog.TryCreate(this), + () => Task.FromResult(InputRoot is TopLevel tl + ? (IStorageProvider?)new ManagedStorageProvider(tl) + : null) }); platform.X11Screens.Changed += OnScreensChanged;