Files
Explorer-Workbench/src/Explorer.Application/StorageProviderRegistry.cs

240 lines
7.0 KiB
C#

using Explorer.Domain;
using Explorer.Plugin.Abstractions;
using Microsoft.Extensions.Logging;
namespace Explorer.Application;
public sealed class StorageProviderRegistry
{
private readonly IReadOnlyList<IStorageProvider> _providers;
private readonly HashSet<string> _disabled = new(StringComparer.OrdinalIgnoreCase);
private readonly ILogger<StorageProviderRegistry> _logger;
public StorageProviderRegistry(IEnumerable<IStorageProvider> providers, ILogger<StorageProviderRegistry> logger)
{
_providers = providers.ToList();
_logger = logger;
}
public IReadOnlyList<IStorageProvider> Providers => _providers;
public void SetEnabled(string providerId, bool enabled)
{
if (enabled)
{
_disabled.Remove(providerId);
}
else
{
_disabled.Add(providerId);
}
}
public IStorageProvider? Find(string path)
{
foreach (var provider in _providers)
{
if (_disabled.Contains(provider.Manifest.Id))
{
continue;
}
try
{
if (provider.TryMatchRoot(path))
{
return provider;
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Provider {Id} failed matching {Path}", provider.Manifest.Id, path);
}
}
return null;
}
public async Task<IReadOnlyList<FileSystemItem>> EnrichAsync(
IReadOnlyList<FileSystemItem> items,
CancellationToken cancellationToken = default)
{
if (items.Count == 0 || _providers.Count == 0)
{
return items;
}
var groups = new Dictionary<IStorageProvider, List<int>>();
for (var i = 0; i < items.Count; i++)
{
var provider = Find(items[i].FullPath);
if (provider is null)
{
continue;
}
if (!groups.TryGetValue(provider, out var list))
{
list = [];
groups[provider] = list;
}
list.Add(i);
}
if (groups.Count == 0)
{
return items;
}
var copy = items.ToArray();
foreach (var (provider, indexes) in groups)
{
try
{
var paths = indexes.Select(i => copy[i].FullPath).ToList();
var states = await provider.GetItemStatesAsync(paths, cancellationToken).ConfigureAwait(false);
var byPath = states.ToDictionary(s => s.Path, StringComparer.OrdinalIgnoreCase);
foreach (var index in indexes)
{
if (byPath.TryGetValue(copy[index].FullPath, out var state))
{
copy[index] = CloudPresenceMapper.Apply(copy[index], state);
}
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Provider {Id} failed enriching items", provider.Manifest.Id);
}
}
return copy;
}
public bool HasCapability(string path, ProviderCapability capability)
{
var provider = Find(path);
return provider is not null && (provider.GetCapabilities() & capability) != 0;
}
public IReadOnlyList<ProviderPlace> GetPlaces()
{
var places = new List<ProviderPlace>();
foreach (var provider in _providers)
{
if (_disabled.Contains(provider.Manifest.Id))
{
continue;
}
try
{
places.AddRange(provider.GetPlaces());
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Provider {Id} failed listing places", provider.Manifest.Id);
}
}
return places;
}
public async Task<ProviderActionResult> InvokeAsync(
ProviderAction action,
IReadOnlyList<string> paths,
CancellationToken cancellationToken = default)
{
if (paths.Count == 0)
{
return new ProviderActionResult(ProviderActionStatus.Failed, "Nothing selected.");
}
var provider = Find(paths[0]);
if (provider is null)
{
return new ProviderActionResult(ProviderActionStatus.Unsupported, "No cloud provider is available for this location.");
}
try
{
return await provider.TryInvokeAsync(new ProviderActionRequest(action, paths), cancellationToken)
.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Provider {Id} failed invoking {Action}", provider.Manifest.Id, action);
return new ProviderActionResult(ProviderActionStatus.Failed, "The cloud provider could not complete this action.");
}
}
public async Task<ProviderItemState?> GetStateAsync(string path, CancellationToken cancellationToken = default)
{
var provider = Find(path);
if (provider is null)
{
return null;
}
try
{
var states = await provider.GetItemStatesAsync([path], cancellationToken).ConfigureAwait(false);
return states.FirstOrDefault();
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Provider {Id} failed reading state for {Path}", provider.Manifest.Id, path);
return null;
}
}
public async Task<ProviderQuota?> TryGetQuotaAsync(string rootPath, CancellationToken cancellationToken = default)
{
var provider = Find(rootPath);
if (provider is null)
{
return null;
}
try
{
return await provider.TryGetQuotaAsync(rootPath, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Provider {Id} failed reading quota for {Path}", provider.Manifest.Id, rootPath);
return null;
}
}
}
public static class CloudPresenceMapper
{
public static FileSystemItem Apply(FileSystemItem item, ProviderItemState state)
=> item.Overlay(
allocatedSizeBytes: state.AllocatedSizeBytes ?? item.AllocatedSizeBytes,
cloud: ToPresence(state),
hydration: item.Hydration | ItemHydrationFlags.Provider);
public static CloudPresence ToPresence(ProviderItemState state)
=> new(
state.ProviderId,
(Domain.CloudAvailability)(int)state.Availability,
state.LogicalSizeBytes,
state.AllocatedSizeBytes,
state.MayHydrateOnRead,
state.StatusText);
public static void ApplyToEntry(IndexEntry entry, CloudPresence? cloud)
{
if (cloud is null)
{
return;
}
entry.AllocatedSizeBytes = cloud.AllocatedSizeBytes;
entry.CloudAvailability = cloud.Availability;
}
}