Improve file operations, layout memory, and drive status.
Add a sequential file-operations queue with pause, reorder, and optional auto-clear; persist window size and tree width; show free space; and clear leftover indexing status. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,8 @@ public sealed class BrowseService
|
||||
private readonly StorageProviderRegistry _providers;
|
||||
private readonly CloudPlaceStore _cloudPlaces;
|
||||
private readonly UiPreferencesStore _preferences;
|
||||
private readonly IElevatedScanService? _elevation;
|
||||
private readonly IRecycleBinCatalog? _recycle;
|
||||
|
||||
public BrowseService(
|
||||
IFileSystemEnumerator enumerator,
|
||||
@@ -20,7 +22,9 @@ public sealed class BrowseService
|
||||
SourceManager sources,
|
||||
StorageProviderRegistry providers,
|
||||
CloudPlaceStore cloudPlaces,
|
||||
UiPreferencesStore preferences)
|
||||
UiPreferencesStore preferences,
|
||||
IElevatedScanService? elevation = null,
|
||||
IRecycleBinCatalog? recycle = null)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
_volumes = volumes;
|
||||
@@ -29,6 +33,8 @@ public sealed class BrowseService
|
||||
_providers = providers;
|
||||
_cloudPlaces = cloudPlaces;
|
||||
_preferences = preferences;
|
||||
_elevation = elevation;
|
||||
_recycle = recycle;
|
||||
}
|
||||
|
||||
public async Task<FolderListing> ListThisPcAsync(CancellationToken cancellationToken = default)
|
||||
@@ -50,12 +56,15 @@ public sealed class BrowseService
|
||||
.Select(place =>
|
||||
{
|
||||
var exists = Directory.Exists(place.Path);
|
||||
var space = exists ? _volumes.GetSpace(place.Path) : default;
|
||||
return new FileSystemItem
|
||||
{
|
||||
FullPath = place.Path,
|
||||
Name = exists ? place.DisplayName : $"{place.DisplayName} (Offline)",
|
||||
IsDirectory = true,
|
||||
Attributes = AttributeFlags.Directory
|
||||
Attributes = AttributeFlags.Directory,
|
||||
FreeSpaceBytes = space.FreeBytes,
|
||||
CapacityBytes = space.CapacityBytes
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
@@ -70,7 +79,7 @@ public sealed class BrowseService
|
||||
var sources = await _sources.RefreshOnlineStateAsync(cancellationToken).ConfigureAwait(false);
|
||||
var items = new List<FileSystemItem>();
|
||||
foreach (var source in sources.Where(include)
|
||||
.OrderBy(s => s.Kind)
|
||||
.OrderBy(s => PathRules.DriveLetterSortKey(s.LastRootPath))
|
||||
.ThenBy(s => s.DisplayName, StringComparer.CurrentCultureIgnoreCase))
|
||||
{
|
||||
IndexEntry? root = null;
|
||||
@@ -79,6 +88,9 @@ public sealed class BrowseService
|
||||
root = await _store.Entries.GetRootAsync(source.Id, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var space = source.LastRootPath is null
|
||||
? default
|
||||
: _volumes.GetSpace(source.LastRootPath);
|
||||
items.Add(new FileSystemItem
|
||||
{
|
||||
FullPath = source.LastRootPath ?? source.DisplayName,
|
||||
@@ -87,7 +99,9 @@ public sealed class BrowseService
|
||||
: source.DisplayName,
|
||||
IsDirectory = true,
|
||||
SizeBytes = root?.AggregateSize ?? 0,
|
||||
Attributes = AttributeFlags.Directory
|
||||
Attributes = AttributeFlags.Directory,
|
||||
FreeSpaceBytes = space.FreeBytes,
|
||||
CapacityBytes = space.CapacityBytes ?? source.CapacityBytes
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,6 +120,11 @@ public sealed class BrowseService
|
||||
}
|
||||
}
|
||||
|
||||
if (IsRecycleBinPath(path))
|
||||
{
|
||||
return ListRecycleBin(path);
|
||||
}
|
||||
|
||||
var reachable = _volumes.IsPathReachable(path);
|
||||
|
||||
if (reachable)
|
||||
@@ -117,7 +136,7 @@ public sealed class BrowseService
|
||||
listing = await OverlayFolderSizesAsync(source, path, listing, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new FolderListing { Path = path, IsOffline = false, Items = listing, Error = error };
|
||||
return FinishListing(path, AttachVolumeSpace(listing), isOffline: false, error);
|
||||
}
|
||||
|
||||
if (source is { IsIndexed: true })
|
||||
@@ -151,7 +170,7 @@ public sealed class BrowseService
|
||||
: null
|
||||
})
|
||||
.ToList();
|
||||
return new FolderListing { Path = path, IsOffline = true, Items = items };
|
||||
return FinishListing(path, AttachVolumeSpace(items), isOffline: true, error: null);
|
||||
}
|
||||
|
||||
return new FolderListing { Path = path, IsOffline = true, Error = "Path not found" };
|
||||
@@ -204,7 +223,7 @@ public sealed class BrowseService
|
||||
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 };
|
||||
return new FolderListing { Path = path, IsOffline = false, Items = AttachVolumeSpace(items), Error = hint };
|
||||
}
|
||||
|
||||
private async Task<bool> IsUnderArchiveAsync(long sourceId, string pathRel, CancellationToken cancellationToken)
|
||||
@@ -262,8 +281,147 @@ public sealed class BrowseService
|
||||
FileId = i.FileId,
|
||||
ReparseTag = i.ReparseTag,
|
||||
AllocatedSizeBytes = i.AllocatedSizeBytes ?? e.AllocatedSizeBytes,
|
||||
Cloud = i.Cloud
|
||||
Cloud = i.Cloud,
|
||||
Location = i.Location,
|
||||
SizeKnowledge = i.SizeKnowledge,
|
||||
DisplayName = i.DisplayName,
|
||||
FreeSpaceBytes = i.FreeSpaceBytes,
|
||||
CapacityBytes = i.CapacityBytes
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private FolderListing FinishListing(string path, IReadOnlyList<FileSystemItem> items, bool isOffline, string? error)
|
||||
{
|
||||
var prefs = _preferences.Load();
|
||||
var annotated = items.Select(i => Annotate(i, sizeFromIndex: i.SizeBytes > 0, prefs)).ToList();
|
||||
var visible = annotated.Where(i => LocationVisibility.ShouldShow(i.Location, prefs)).ToList();
|
||||
if (visible.Any(i => i.Location.AccessDenied) && _elevation is { IsElevated: false })
|
||||
{
|
||||
error = string.IsNullOrEmpty(error)
|
||||
? _elevation.ProtectedContentHint
|
||||
: error + " " + _elevation.ProtectedContentHint;
|
||||
}
|
||||
|
||||
return new FolderListing { Path = path, IsOffline = isOffline, Items = visible, Error = error };
|
||||
}
|
||||
|
||||
private FileSystemItem Annotate(FileSystemItem item, bool sizeFromIndex, UiPreferences preferences)
|
||||
{
|
||||
var looksRestricted = LocationClassifier.Classify(item.FullPath, item.Name, item.Attributes, item.IsDirectory);
|
||||
var accessDenied = preferences.ShowProtectedSystemLocations
|
||||
&& item.IsDirectory
|
||||
&& (looksRestricted.IsProtected || looksRestricted.IsRecycleBin)
|
||||
&& IsAccessDenied(item.FullPath);
|
||||
var location = LocationClassifier.Classify(item.FullPath, item.Name, item.Attributes, item.IsDirectory, accessDenied);
|
||||
var knowledge = LocationVisibility.ResolveSizeKnowledge(location, item.IsDirectory, item.SizeBytes, sizeFromIndex);
|
||||
return new FileSystemItem
|
||||
{
|
||||
FullPath = item.FullPath,
|
||||
Name = item.Name,
|
||||
IsDirectory = item.IsDirectory,
|
||||
SizeBytes = item.SizeBytes,
|
||||
CreatedUtc = item.CreatedUtc,
|
||||
ModifiedUtc = item.ModifiedUtc,
|
||||
Attributes = item.Attributes,
|
||||
FileId = item.FileId,
|
||||
ReparseTag = item.ReparseTag,
|
||||
AllocatedSizeBytes = item.AllocatedSizeBytes,
|
||||
Cloud = item.Cloud,
|
||||
Location = location,
|
||||
SizeKnowledge = knowledge,
|
||||
DisplayName = location.IsRecycleBin ? "Recycle Bin" : item.DisplayName,
|
||||
FreeSpaceBytes = item.FreeSpaceBytes,
|
||||
CapacityBytes = item.CapacityBytes
|
||||
};
|
||||
}
|
||||
|
||||
private IReadOnlyList<FileSystemItem> AttachVolumeSpace(IReadOnlyList<FileSystemItem> items)
|
||||
{
|
||||
var cache = new Dictionary<string, VolumeSpace>(StringComparer.OrdinalIgnoreCase);
|
||||
return items.Select(item =>
|
||||
{
|
||||
var key = SpaceKey(item.FullPath);
|
||||
if (!cache.TryGetValue(key, out var space))
|
||||
{
|
||||
space = _volumes.GetSpace(item.FullPath);
|
||||
cache[key] = space;
|
||||
}
|
||||
|
||||
if (space.FreeBytes is null && space.CapacityBytes is null)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
return new FileSystemItem
|
||||
{
|
||||
FullPath = item.FullPath,
|
||||
Name = item.Name,
|
||||
IsDirectory = item.IsDirectory,
|
||||
SizeBytes = item.SizeBytes,
|
||||
CreatedUtc = item.CreatedUtc,
|
||||
ModifiedUtc = item.ModifiedUtc,
|
||||
Attributes = item.Attributes,
|
||||
FileId = item.FileId,
|
||||
ReparseTag = item.ReparseTag,
|
||||
AllocatedSizeBytes = item.AllocatedSizeBytes,
|
||||
Cloud = item.Cloud,
|
||||
Location = item.Location,
|
||||
SizeKnowledge = item.SizeKnowledge,
|
||||
DisplayName = item.DisplayName,
|
||||
FreeSpaceBytes = space.FreeBytes,
|
||||
CapacityBytes = space.CapacityBytes
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private static string SpaceKey(string path)
|
||||
{
|
||||
if (PathRules.IsUnc(path))
|
||||
{
|
||||
return PathRules.CanonicalUncRoot(path);
|
||||
}
|
||||
|
||||
var root = Path.GetPathRoot(path);
|
||||
return string.IsNullOrWhiteSpace(root) ? path : root;
|
||||
}
|
||||
|
||||
private FolderListing ListRecycleBin(string path)
|
||||
{
|
||||
var summary = _recycle?.TrySummarize(path);
|
||||
var hint = summary is null
|
||||
? "Recycle Bin contents are managed by Windows."
|
||||
: $"{summary.ItemCount} deleted items · {summary.UsedBytes} bytes used";
|
||||
if (_elevation is { IsElevated: false })
|
||||
{
|
||||
hint += " " + _elevation.ProtectedContentHint;
|
||||
}
|
||||
|
||||
return new FolderListing { Path = path, IsOffline = false, Items = [], Error = hint };
|
||||
}
|
||||
|
||||
private static bool IsRecycleBinPath(string path)
|
||||
=> LocationClassifier.IsRecycleBinName(PathRules.GetFileName(path));
|
||||
|
||||
private static bool IsAccessDenied(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var enumerator = Directory.EnumerateFileSystemEntries(path).GetEnumerator();
|
||||
enumerator.MoveNext();
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
catch (System.Security.SecurityException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user