Browse Source

Gracefully fall back to th next storage provider (#15929)

release/11.1.3
Nikita Tsukanov 2 years ago
committed by Max Katz
parent
commit
d2fbbc4a71
  1. 95
      src/Avalonia.Base/Platform/Storage/FallbackStorageProvider.cs
  2. 3
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  3. 82
      src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs
  4. 12
      src/Avalonia.X11/X11Window.cs

95
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<Task<IStorageProvider?>>[] _factories;
private readonly List<IStorageProvider> _providers = new();
private int _nextProviderFactory = 0;
public FallbackStorageProvider(Func<Task<IStorageProvider?>>[] factories)
{
_factories = factories;
}
async IAsyncEnumerable<IStorageProvider> 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<IStorageProvider> GetFor(Func<IStorageProvider, bool> 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<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
return await (await GetFor(p => p.CanOpen)).OpenFilePickerAsync(options);
}
public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
{
return await (await GetFor(p => p.CanSave)).SaveFilePickerAsync(options);
}
public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
return await (await GetFor(p => p.CanPickFolder)).OpenFolderPickerAsync(options);
}
async Task<TResult?> FirstNotNull<TArg, TResult>(TArg arg, Func<IStorageProvider, TArg, Task<TResult?>> 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<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark) =>
FirstNotNull(bookmark, (p, a) => p.OpenFileBookmarkAsync(a));
public Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark) =>
FirstNotNull(bookmark, (p, a) => p.OpenFolderBookmarkAsync(a));
public Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath) =>
FirstNotNull(filePath, (p, a) => p.TryGetFileFromPathAsync(filePath));
public Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath)
=> FirstNotNull(folderPath, (p, a) => p.TryGetFolderFromPathAsync(a));
public Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder) =>
FirstNotNull(wellKnownFolder, (p, a) => p.TryGetWellKnownFolderAsync(a));
}

3
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -16,7 +16,8 @@
<ItemGroup>
<!-- For managed dialogs dev testing -->
<InternalsVisibleTo Include="ControlCatalog, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="ControlCatalog, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
<Import Project="..\..\build\DevAnalyzers.props" />

82
src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs

@ -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<Func<Task<IStorageProvider?>>> _factories;
public CompositeStorageProvider(IEnumerable<Func<Task<IStorageProvider?>>> factories)
{
_factories = factories;
}
public bool CanOpen => true;
public bool CanSave => true;
public bool CanPickFolder => true;
private async Task<IStorageProvider> 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<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFilePickerAsync(options).ConfigureAwait(false);
}
public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.SaveFilePickerAsync(options).ConfigureAwait(false);
}
public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFolderPickerAsync(options).ConfigureAwait(false);
}
public async Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFileBookmarkAsync(bookmark).ConfigureAwait(false);
}
public async Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFolderBookmarkAsync(bookmark).ConfigureAwait(false);
}
public async Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.TryGetFileFromPathAsync(filePath).ConfigureAwait(false);
}
public async Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.TryGetFolderFromPathAsync(folderPath).ConfigureAwait(false);
}
public async Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.TryGetWellKnownFolderAsync(wellKnownFolder).ConfigureAwait(false);
}
}

12
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<IStorageProvider?>(null),
() => GtkSystemDialog.TryCreate(this)
() => _platform.Options.UseDBusFilePicker
? DBusSystemDialog.TryCreateAsync(Handle)
: Task.FromResult<IStorageProvider?>(null),
() => GtkSystemDialog.TryCreate(this),
() => Task.FromResult(InputRoot is TopLevel tl
? (IStorageProvider?)new ManagedStorageProvider(tl)
: null)
});
platform.X11Screens.Changed += OnScreensChanged;

Loading…
Cancel
Save