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:
2026-08-23 12:16:09 +02:00
parent e9aba73552
commit 09a8cfafa3
57 changed files with 3130 additions and 119 deletions

View File

@@ -162,6 +162,53 @@ public sealed class SourceManager
return source;
}
public bool IsPresentInWindows(Source source)
{
var online = _volumes.EnumerateOnlineVolumes();
foreach (var fp in online)
{
var match = VolumeIdentityMatcher.Match(fp, [source]);
if (match.Source is not null)
{
return true;
}
if (source.LastRootPath is not null && RootsEqual(fp.RootPath, source.LastRootPath))
{
return true;
}
}
return source.LastRootPath is not null && _volumes.IsPathReachable(source.LastRootPath);
}
public bool CanForget(Source source) => !IsPresentInWindows(source);
public async Task<bool> CanForgetPathAsync(string path, CancellationToken cancellationToken = default)
{
var source = await FindSourceRootAsync(path, cancellationToken).ConfigureAwait(false);
return source is not null && CanForget(source);
}
public async Task<bool> ForgetDisconnectedAsync(string path, CancellationToken cancellationToken = default)
{
var source = await FindSourceRootAsync(path, cancellationToken).ConfigureAwait(false);
if (source is null || !CanForget(source))
{
return false;
}
await _store.RunWriteAsync(store => store.Sources.DeleteAsync(source.Id, cancellationToken), cancellationToken)
.ConfigureAwait(false);
if (source.LastRootPath is not null && PathRules.IsUnc(source.LastRootPath))
{
ForgetUnc(PathRules.CanonicalUncRoot(source.LastRootPath));
}
_logger.LogInformation("Forgot disconnected source {DisplayName} ({Path})", source.DisplayName, source.LastRootPath);
return true;
}
public async Task<Source?> FindByPathAsync(string path, CancellationToken cancellationToken = default)
{
var sources = await _store.Sources.GetAllAsync(cancellationToken).ConfigureAwait(false);
@@ -194,7 +241,7 @@ public sealed class SourceManager
public async Task<Source?> EnsureForPathAsync(string path, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(path) || path == "This PC")
if (string.IsNullOrWhiteSpace(path) || LocationRoots.IsVirtual(path))
{
return null;
}
@@ -255,6 +302,50 @@ public sealed class SourceManager
return created;
}
private async Task<Source?> FindSourceRootAsync(string path, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(path) || LocationRoots.IsVirtual(path))
{
return null;
}
var source = await FindByPathAsync(path, cancellationToken).ConfigureAwait(false);
if (source?.LastRootPath is null)
{
return null;
}
return RootsEqual(source.LastRootPath, path) ? source : null;
}
private static bool RootsEqual(string a, string b)
{
var left = PathRules.EnsureDirectoryTrailingSlashIfRoot(PathRules.FromExtended(a).TrimEnd('\\'));
var right = PathRules.EnsureDirectoryTrailingSlashIfRoot(PathRules.FromExtended(b).TrimEnd('\\'));
return left.Equals(right, StringComparison.OrdinalIgnoreCase);
}
private void ForgetUnc(string root)
{
try
{
var file = Path.Combine(_env.DataDirectory, "recents.txt");
if (!File.Exists(file))
{
return;
}
var lines = LoadRecents()
.Where(l => !PathRules.CanonicalUncRoot(l).Equals(root, StringComparison.OrdinalIgnoreCase))
.ToList();
File.WriteAllLines(file, lines);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed updating recents");
}
}
private IReadOnlyList<string> LoadRecents()
{
var file = Path.Combine(_env.DataDirectory, "recents.txt");