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:
2026-08-22 12:43:05 +02:00
commit e9aba73552
130 changed files with 15110 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
namespace Explorer.Domain;
public sealed class Source
{
public long Id { get; set; }
public required string StableKey { get; set; }
public SourceKind Kind { get; set; }
public required string DisplayName { get; set; }
public string? VolumeGuid { get; set; }
public long? VolumeSerial { get; set; }
public string? Filesystem { get; set; }
public string? Label { get; set; }
public long? CapacityBytes { get; set; }
public string? DeviceInstanceId { get; set; }
public string? LastRootPath { get; set; }
public SourceStatus Status { get; set; } = SourceStatus.Offline;
public DateTimeOffset? LastSeenUtc { get; set; }
public DateTimeOffset? LastIndexedUtc { get; set; }
public long? UsnJournalId { get; set; }
public long? UsnNext { get; set; }
public long ScanGeneration { get; set; }
public string? LastError { get; set; }
public bool IsIndexed => LastIndexedUtc is not null;
}
public sealed class VolumeFingerprint
{
public SourceKind Kind { get; init; }
public string? VolumeGuid { get; init; }
public long? VolumeSerial { get; init; }
public string? Filesystem { get; init; }
public string? Label { get; init; }
public long? CapacityBytes { get; init; }
public string? DeviceInstanceId { get; init; }
public required string RootPath { get; init; }
public string? DisplayName { get; init; }
}
public sealed class IndexEntry
{
public long Id { get; set; }
public long SourceId { get; set; }
public long? ParentId { get; set; }
public required string Name { get; set; }
public required string NameNorm { get; set; }
public string? Extension { get; set; }
public bool IsDirectory { get; set; }
public long SizeBytes { get; set; }
public long AggregateSize { get; set; }
public int ChildFileCount { get; set; }
public int ChildDirCount { get; set; }
public DateTimeOffset? CreatedUtc { get; set; }
public DateTimeOffset? ModifiedUtc { get; set; }
public DateTimeOffset LastSeenUtc { get; set; }
public DateTimeOffset? LastIndexedUtc { get; set; }
public int Attributes { get; set; }
public long? FileId { get; set; }
public long? ParentFileId { get; set; }
public int ReparseTag { get; set; }
public EntryStatus Status { get; set; } = EntryStatus.Present;
public DateTimeOffset? DeletedUtc { get; set; }
public required string PathRel { get; set; }
public byte[]? ContentHash { get; set; }
public HashState HashState { get; set; }
public long ScanGeneration { get; set; }
public long? AllocatedSizeBytes { get; set; }
public CloudAvailability? CloudAvailability { get; set; }
}
public sealed class ExcludeRule
{
public long Id { get; set; }
public string Scope { get; set; } = "global";
public long? SourceId { get; set; }
public ExcludeKind Kind { get; set; }
public required string Pattern { get; set; }
public bool Enabled { get; set; } = true;
}
public sealed class ScanJob
{
public long Id { get; set; }
public long SourceId { get; set; }
public ScanKind Kind { get; set; }
public ScanJobStatus Status { get; set; }
public DateTimeOffset? StartedUtc { get; set; }
public DateTimeOffset? FinishedUtc { get; set; }
public long FilesSeen { get; set; }
public long DirsSeen { get; set; }
public long BytesSeen { get; set; }
public string? ResumePath { get; set; }
public int ErrorCount { get; set; }
public string? LastError { get; set; }
public string? FolderPathRel { get; set; }
}
public sealed class ScanError
{
public long Id { get; set; }
public long JobId { get; set; }
public string? Path { get; set; }
public string? Kind { get; set; }
public string? Message { get; set; }
public DateTimeOffset Utc { get; set; }
}
public sealed class TransferJob
{
public long Id { get; set; }
public TransferOp Op { get; set; }
public required string SourcePath { get; set; }
public string? DestinationPath { get; set; }
public TransferStatus Status { get; set; }
public long? BytesTotal { get; set; }
public long BytesDone { get; set; }
public DateTimeOffset CreatedUtc { get; set; }
public string? Error { get; set; }
public IReadOnlyList<string> AdditionalSources { get; init; } = [];
}
public sealed class FileSystemItem
{
public required string FullPath { get; init; }
public required string Name { get; init; }
public bool IsDirectory { get; init; }
public long SizeBytes { get; init; }
public DateTimeOffset? CreatedUtc { get; init; }
public DateTimeOffset? ModifiedUtc { get; init; }
public int Attributes { get; init; }
public long? FileId { get; init; }
public int ReparseTag { get; init; }
public long? AllocatedSizeBytes { get; init; }
public CloudPresence? Cloud { get; init; }
public bool IsReparsePoint => (Attributes & AttributeFlags.ReparsePoint) != 0;
}
public sealed record CloudPresence(
string? ProviderId,
CloudAvailability Availability,
long LogicalSizeBytes,
long? AllocatedSizeBytes,
bool MayHydrateOnRead,
string? StatusText = null);
public sealed record ScanProgress
{
public long JobId { get; init; }
public long SourceId { get; init; }
public required string CurrentPath { get; init; }
public long FilesSeen { get; init; }
public long DirsSeen { get; init; }
public long BytesSeen { get; init; }
public int ErrorCount { get; init; }
public ScanJobStatus Status { get; init; }
}
public sealed class FolderListing
{
public required string Path { get; init; }
public bool IsOffline { get; init; }
public IReadOnlyList<FileSystemItem> Items { get; init; } = [];
public string? Error { get; init; }
}