Add Explorer Workbench with hierarchical, off-UI Storage analysis.
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>
This commit is contained in:
151
src/Explorer.Application/BrowseService.cs
Normal file
151
src/Explorer.Application/BrowseService.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Explorer.Domain;
|
||||
using Explorer.Domain.Abstractions;
|
||||
|
||||
namespace Explorer.Application;
|
||||
|
||||
public sealed class BrowseService
|
||||
{
|
||||
private readonly IFileSystemEnumerator _enumerator;
|
||||
private readonly IVolumeService _volumes;
|
||||
private readonly IIndexStore _store;
|
||||
private readonly SourceManager _sources;
|
||||
private readonly StorageProviderRegistry _providers;
|
||||
|
||||
public BrowseService(
|
||||
IFileSystemEnumerator enumerator,
|
||||
IVolumeService volumes,
|
||||
IIndexStore store,
|
||||
SourceManager sources,
|
||||
StorageProviderRegistry providers)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
_volumes = volumes;
|
||||
_store = store;
|
||||
_sources = sources;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
public async Task<FolderListing> ListThisPcAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sources = await _sources.RefreshOnlineStateAsync(cancellationToken).ConfigureAwait(false);
|
||||
var items = new List<FileSystemItem>();
|
||||
foreach (var source in sources.OrderBy(s => s.Kind).ThenBy(s => s.DisplayName, StringComparer.CurrentCultureIgnoreCase))
|
||||
{
|
||||
IndexEntry? root = null;
|
||||
if (source.IsIndexed)
|
||||
{
|
||||
root = await _store.Entries.GetRootAsync(source.Id, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
items.Add(new FileSystemItem
|
||||
{
|
||||
FullPath = source.LastRootPath ?? source.DisplayName,
|
||||
Name = source.Status == SourceStatus.Offline
|
||||
? $"{source.DisplayName} (Offline)"
|
||||
: source.DisplayName,
|
||||
IsDirectory = true,
|
||||
SizeBytes = root?.AggregateSize ?? 0,
|
||||
Attributes = AttributeFlags.Directory
|
||||
});
|
||||
}
|
||||
|
||||
return new FolderListing { Path = "This PC", Items = items };
|
||||
}
|
||||
|
||||
public async Task<FolderListing> ListAsync(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var source = await _sources.FindByPathAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
var reachable = _volumes.IsPathReachable(path);
|
||||
|
||||
if (reachable)
|
||||
{
|
||||
var items = _enumerator.EnumerateChildrenSafe(path, out var error);
|
||||
var listing = (await _providers.EnrichAsync(items.ToList(), cancellationToken).ConfigureAwait(false)).ToList();
|
||||
if (source is { IsIndexed: true })
|
||||
{
|
||||
listing = await OverlayFolderSizesAsync(source, path, listing, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new FolderListing { Path = path, IsOffline = false, Items = listing, Error = error };
|
||||
}
|
||||
|
||||
if (source is { IsIndexed: true })
|
||||
{
|
||||
var rel = source.LastRootPath is null ? "" : PathRules.MakeRelative(source.LastRootPath, path);
|
||||
var dir = await _store.Entries.GetByPathAsync(source.Id, rel, cancellationToken).ConfigureAwait(false)
|
||||
?? await _store.Entries.GetRootAsync(source.Id, cancellationToken).ConfigureAwait(false);
|
||||
if (dir is null)
|
||||
{
|
||||
return new FolderListing { Path = path, IsOffline = true, Error = "Not available" };
|
||||
}
|
||||
|
||||
var children = await _store.Entries.GetChildrenAsync(source.Id, dir.Id, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var items = children
|
||||
.Where(c => c.Status is EntryStatus.Present or EntryStatus.Offline)
|
||||
.Select(c => new FileSystemItem
|
||||
{
|
||||
FullPath = PathRules.Combine(source.LastRootPath ?? source.DisplayName, c.PathRel),
|
||||
Name = c.Name,
|
||||
IsDirectory = c.IsDirectory,
|
||||
SizeBytes = c.IsDirectory ? c.AggregateSize : c.SizeBytes,
|
||||
CreatedUtc = c.CreatedUtc,
|
||||
ModifiedUtc = c.ModifiedUtc,
|
||||
Attributes = c.Attributes,
|
||||
FileId = c.FileId,
|
||||
ReparseTag = c.ReparseTag,
|
||||
AllocatedSizeBytes = c.AllocatedSizeBytes,
|
||||
Cloud = c.CloudAvailability is { } availability
|
||||
? new CloudPresence(null, availability, c.SizeBytes, c.AllocatedSizeBytes, availability == CloudAvailability.OnlineOnly)
|
||||
: null
|
||||
})
|
||||
.ToList();
|
||||
return new FolderListing { Path = path, IsOffline = true, Items = items };
|
||||
}
|
||||
|
||||
return new FolderListing { Path = path, IsOffline = true, Error = "Path not found" };
|
||||
}
|
||||
|
||||
private async Task<List<FileSystemItem>> OverlayFolderSizesAsync(
|
||||
Source source,
|
||||
string path,
|
||||
List<FileSystemItem> listing,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var rel = PathRules.MakeRelative(source.LastRootPath ?? path, path);
|
||||
var indexed = await _store.Entries.GetByPathAsync(source.Id, rel, cancellationToken).ConfigureAwait(false)
|
||||
?? (string.IsNullOrEmpty(rel)
|
||||
? await _store.Entries.GetRootAsync(source.Id, cancellationToken).ConfigureAwait(false)
|
||||
: null);
|
||||
if (indexed is null)
|
||||
{
|
||||
return listing;
|
||||
}
|
||||
|
||||
var children = await _store.Entries.GetChildrenAsync(source.Id, indexed.Id, EntryStatus.Present, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var byName = children.ToDictionary(c => c.NameNorm, StringComparer.Ordinal);
|
||||
return listing.Select(i =>
|
||||
{
|
||||
if (!i.IsDirectory || !byName.TryGetValue(NameNormalizer.Normalize(i.Name), out var e))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
return new FileSystemItem
|
||||
{
|
||||
FullPath = i.FullPath,
|
||||
Name = i.Name,
|
||||
IsDirectory = true,
|
||||
SizeBytes = e.AggregateSize,
|
||||
CreatedUtc = i.CreatedUtc,
|
||||
ModifiedUtc = i.ModifiedUtc,
|
||||
Attributes = i.Attributes,
|
||||
FileId = i.FileId,
|
||||
ReparseTag = i.ReparseTag,
|
||||
AllocatedSizeBytes = i.AllocatedSizeBytes ?? e.AllocatedSizeBytes,
|
||||
Cloud = i.Cloud
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user