Add settings, cloud places, and optional archive-content indexing.
Keep official clients in charge of sync while Explorer can group locations, persist UI prefs, and list zip/rar/7z members without extracting them. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,26 +10,68 @@ public sealed class BrowseService
|
||||
private readonly IIndexStore _store;
|
||||
private readonly SourceManager _sources;
|
||||
private readonly StorageProviderRegistry _providers;
|
||||
private readonly CloudPlaceStore _cloudPlaces;
|
||||
private readonly UiPreferencesStore _preferences;
|
||||
|
||||
public BrowseService(
|
||||
IFileSystemEnumerator enumerator,
|
||||
IVolumeService volumes,
|
||||
IIndexStore store,
|
||||
SourceManager sources,
|
||||
StorageProviderRegistry providers)
|
||||
StorageProviderRegistry providers,
|
||||
CloudPlaceStore cloudPlaces,
|
||||
UiPreferencesStore preferences)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
_volumes = volumes;
|
||||
_store = store;
|
||||
_sources = sources;
|
||||
_providers = providers;
|
||||
_cloudPlaces = cloudPlaces;
|
||||
_preferences = preferences;
|
||||
}
|
||||
|
||||
public async Task<FolderListing> ListThisPcAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var groupNetwork = _preferences.Load().GroupNetworkPlaces;
|
||||
return await ListSourcesAsync(
|
||||
LocationRoots.ThisPc,
|
||||
source => !groupNetwork || !source.Kind.IsNetwork(),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<FolderListing> ListNetworkAsync(CancellationToken cancellationToken = default)
|
||||
=> ListSourcesAsync(LocationRoots.Network, source => source.Kind.IsNetwork(), cancellationToken);
|
||||
|
||||
public Task<FolderListing> ListCloudAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = CloudPlaceStore.Merge(_providers.GetPlaces(), _cloudPlaces.Load())
|
||||
.OrderBy(p => p.DisplayName, StringComparer.CurrentCultureIgnoreCase)
|
||||
.Select(place =>
|
||||
{
|
||||
var exists = Directory.Exists(place.Path);
|
||||
return new FileSystemItem
|
||||
{
|
||||
FullPath = place.Path,
|
||||
Name = exists ? place.DisplayName : $"{place.DisplayName} (Offline)",
|
||||
IsDirectory = true,
|
||||
Attributes = AttributeFlags.Directory
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
return Task.FromResult(new FolderListing { Path = LocationRoots.Cloud, Items = items });
|
||||
}
|
||||
|
||||
private async Task<FolderListing> ListSourcesAsync(
|
||||
string path,
|
||||
Func<Source, bool> include,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
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))
|
||||
foreach (var source in sources.Where(include)
|
||||
.OrderBy(s => s.Kind)
|
||||
.ThenBy(s => s.DisplayName, StringComparer.CurrentCultureIgnoreCase))
|
||||
{
|
||||
IndexEntry? root = null;
|
||||
if (source.IsIndexed)
|
||||
@@ -49,12 +91,21 @@ public sealed class BrowseService
|
||||
});
|
||||
}
|
||||
|
||||
return new FolderListing { Path = "This PC", Items = items };
|
||||
return new FolderListing { Path = path, Items = items };
|
||||
}
|
||||
|
||||
public async Task<FolderListing> ListAsync(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var source = await _sources.FindByPathAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
if (source is { IsIndexed: true } && _preferences.Load().IndexArchiveContents)
|
||||
{
|
||||
var archiveListing = await TryListArchiveAsync(source, path, cancellationToken).ConfigureAwait(false);
|
||||
if (archiveListing is not null)
|
||||
{
|
||||
return archiveListing;
|
||||
}
|
||||
}
|
||||
|
||||
var reachable = _volumes.IsPathReachable(path);
|
||||
|
||||
if (reachable)
|
||||
@@ -106,6 +157,73 @@ public sealed class BrowseService
|
||||
return new FolderListing { Path = path, IsOffline = true, Error = "Path not found" };
|
||||
}
|
||||
|
||||
public bool CanBrowseArchive(string name)
|
||||
=> _preferences.Load().IndexArchiveContents && ArchiveFormats.IsArchive(name);
|
||||
|
||||
private async Task<FolderListing?> TryListArchiveAsync(Source source, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
if (source.LastRootPath is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var rel = PathRules.MakeRelative(source.LastRootPath, path);
|
||||
var entry = await _store.Entries.GetByPathAsync(source.Id, rel, cancellationToken).ConfigureAwait(false);
|
||||
if (entry is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var isArchiveFile = !entry.IsDirectory && ArchiveFormats.IsArchive(entry.Name);
|
||||
if (!isArchiveFile && Directory.Exists(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isArchiveFile && !await IsUnderArchiveAsync(source.Id, rel, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var children = await _store.Entries.GetChildrenAsync(source.Id, entry.Id, EntryStatus.Present, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var items = children.Select(c => new FileSystemItem
|
||||
{
|
||||
FullPath = PathRules.Combine(source.LastRootPath, 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
|
||||
}).ToList();
|
||||
|
||||
var hint = isArchiveFile && items.Count == 0
|
||||
? "Archive contents appear after the next scan."
|
||||
: null;
|
||||
return new FolderListing { Path = path, IsOffline = false, Items = items, Error = hint };
|
||||
}
|
||||
|
||||
private async Task<bool> IsUnderArchiveAsync(long sourceId, string pathRel, CancellationToken cancellationToken)
|
||||
{
|
||||
var current = pathRel;
|
||||
while (!string.IsNullOrEmpty(current))
|
||||
{
|
||||
var entry = await _store.Entries.GetByPathAsync(sourceId, current, cancellationToken).ConfigureAwait(false);
|
||||
if (entry is { IsDirectory: false } && ArchiveFormats.IsArchive(entry.Name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
current = PathRules.RelativeParent(current);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<List<FileSystemItem>> OverlayFolderSizesAsync(
|
||||
Source source,
|
||||
string path,
|
||||
|
||||
Reference in New Issue
Block a user