Storage queries run in the background with cancellation and covering indexes so switching views no longer freezes the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
141 lines
4.5 KiB
C#
141 lines
4.5 KiB
C#
using Explorer.Plugin.Abstractions;
|
|
using Explorer.Windows;
|
|
|
|
namespace Explorer.Plugin.OneDrive;
|
|
|
|
public sealed class OneDriveStorageProvider : IStorageProvider
|
|
{
|
|
public const string ProviderId = "onedrive";
|
|
|
|
private readonly object _gate = new();
|
|
private IReadOnlyList<ProviderPlace>? _places;
|
|
|
|
public ProviderManifest Manifest { get; } = new(
|
|
ProviderId,
|
|
"OneDrive",
|
|
"1.0.0",
|
|
ProviderIsolation.InProcess);
|
|
|
|
public ProviderCapability GetCapabilities()
|
|
=> ProviderCapability.CloudState | ProviderCapability.Pin | ProviderCapability.Dehydrate;
|
|
|
|
public bool TryMatchRoot(string path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var root in GetPlaces())
|
|
{
|
|
if (IsUnder(root.Path, path))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return CloudFilesNative.IsCloudSyncRoot(path);
|
|
}
|
|
|
|
public Task<IReadOnlyList<ProviderItemState>> GetItemStatesAsync(
|
|
IReadOnlyList<string> paths,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var results = new List<ProviderItemState>(paths.Count);
|
|
foreach (var path in paths)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
if (!TryMatchRoot(path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
var attrs = (int)File.GetAttributes(path);
|
|
var isDir = (attrs & (int)FileAttributes.Directory) != 0;
|
|
long logical = 0;
|
|
if (!isDir)
|
|
{
|
|
try { logical = new FileInfo(path).Length; } catch { /* locked */ }
|
|
}
|
|
|
|
var allocated = CloudFilesNative.TryGetAllocatedSize(path);
|
|
var placeholder = CloudFilesNative.TryGetPlaceholderState(attrs, 0);
|
|
results.Add(OneDrivePlaceholderState.FromLocalSignals(
|
|
ProviderId, path, attrs, 0, logical, allocated, placeholder));
|
|
}
|
|
catch
|
|
{
|
|
// fail-open for this item
|
|
}
|
|
}
|
|
|
|
return Task.FromResult<IReadOnlyList<ProviderItemState>>(results);
|
|
}
|
|
|
|
public Task<ProviderActionResult> TryInvokeAsync(ProviderActionRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
var capabilities = GetCapabilities();
|
|
var needed = request.Action switch
|
|
{
|
|
ProviderAction.Pin or ProviderAction.Unpin => ProviderCapability.Pin,
|
|
ProviderAction.Dehydrate => ProviderCapability.Dehydrate,
|
|
_ => ProviderCapability.None
|
|
};
|
|
|
|
if (needed == ProviderCapability.None || (capabilities & needed) == 0)
|
|
{
|
|
return Task.FromResult(new ProviderActionResult(ProviderActionStatus.Unsupported, "This action is not available."));
|
|
}
|
|
|
|
var pin = request.Action switch
|
|
{
|
|
ProviderAction.Pin => CloudFilesNative.PinPinned,
|
|
ProviderAction.Unpin or ProviderAction.Dehydrate => CloudFilesNative.PinUnpinned,
|
|
_ => CloudFilesNative.PinUnspecified
|
|
};
|
|
|
|
var failed = 0;
|
|
foreach (var path in request.Paths)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
if (!CloudFilesNative.TrySetPinState(path, pin))
|
|
{
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
if (failed == 0)
|
|
{
|
|
return Task.FromResult(new ProviderActionResult(ProviderActionStatus.Succeeded));
|
|
}
|
|
|
|
if (failed == request.Paths.Count)
|
|
{
|
|
return Task.FromResult(new ProviderActionResult(ProviderActionStatus.Failed, "OneDrive could not change the pin state."));
|
|
}
|
|
|
|
return Task.FromResult(new ProviderActionResult(ProviderActionStatus.Failed, $"OneDrive could not change the pin state for {failed} item(s)."));
|
|
}
|
|
|
|
public Task<ProviderQuota?> TryGetQuotaAsync(string rootPath, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<ProviderQuota?>(null);
|
|
|
|
public IReadOnlyList<ProviderPlace> GetPlaces()
|
|
{
|
|
lock (_gate)
|
|
{
|
|
return _places ??= OneDrivePlaceDiscovery.Discover();
|
|
}
|
|
}
|
|
|
|
private static bool IsUnder(string root, string path)
|
|
{
|
|
var a = root.TrimEnd('\\');
|
|
var b = path.TrimEnd('\\');
|
|
return b.Equals(a, StringComparison.OrdinalIgnoreCase)
|
|
|| b.StartsWith(a + "\\", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|