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:
2026-08-24 01:52:30 +02:00
parent d79605cde9
commit 9bf451932f
41 changed files with 2855 additions and 275 deletions

View File

@@ -30,6 +30,8 @@ public class BrowseServiceTests
var listing = await browse.ListThisPcAsync();
var item = Assert.Single(listing.Items);
Assert.Equal(1_073_741_824, item.SizeBytes);
Assert.Equal(250_000_000, item.FreeSpaceBytes);
Assert.Equal(1_000_000_000, item.CapacityBytes);
Assert.True(item.IsDirectory);
}
@@ -65,6 +67,7 @@ public class BrowseServiceTests
var listing = await browse.ListAsync(@"C:\");
var movies = Assert.Single(listing.Items, i => i.Name == "Movies");
Assert.Equal(200, movies.SizeBytes);
Assert.Equal(250_000_000, movies.FreeSpaceBytes);
var loose = Assert.Single(listing.Items, i => i.Name == "loose.txt");
Assert.Equal(10, loose.SizeBytes);
}
@@ -83,7 +86,9 @@ public class BrowseServiceTests
Kind = SourceKind.NtfsLocal,
RootPath = root,
DisplayName = display,
VolumeSerial = 1
VolumeSerial = 1,
CapacityBytes = 1_000_000_000,
FreeBytes = 250_000_000
}
]
};
@@ -128,6 +133,11 @@ file sealed class BrowseVolumes : IVolumeService
=> Online.FirstOrDefault(v => path.StartsWith(v.RootPath.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase));
public bool IsPathReachable(string path)
=> Online.Any(v => path.StartsWith(v.RootPath.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase));
public VolumeSpace GetSpace(string path)
{
var fp = Probe(path);
return new VolumeSpace(fp?.CapacityBytes, fp?.FreeBytes);
}
}
file sealed class BrowseEnv : IAppEnvironment

View File

@@ -136,6 +136,39 @@ public class SourceManagerTests
Assert.Null(await store.Entries.GetRootAsync(source.Id));
}
[Fact]
public async Task Stale_scanning_status_clears_when_no_job_is_running()
{
var db = Path.Combine(Path.GetTempPath(), "ew-app", Guid.NewGuid().ToString("N"), "index.db");
await using var store = new SqliteIndexStore(db, NullLogger<SqliteIndexStore>.Instance);
await store.OpenAsync();
var volumes = new FakeVolumes
{
Online =
[
new VolumeFingerprint
{
Kind = SourceKind.NtfsLocal,
RootPath = @"D:\",
DisplayName = "Games (D:)",
VolumeSerial = 42,
CapacityBytes = 1000
}
]
};
var env = new FakeEnv(Path.GetDirectoryName(db)!);
var mgr = new SourceManager(store, volumes, env, new SystemClock(), NullLogger<SourceManager>.Instance);
await mgr.InitializeAsync();
var source = (await store.Sources.GetAllAsync()).Single();
await store.Sources.UpdateStatusAsync(source.Id, SourceStatus.Scanning, null);
await store.Sources.UpdateIndexedAsync(source.Id, DateTimeOffset.UtcNow, 1);
await mgr.RefreshOnlineStateAsync();
source = (await store.Sources.GetAllAsync()).Single();
Assert.Equal(SourceStatus.Online, source.Status);
Assert.True(source.IsIndexed);
}
[Fact]
public async Task Forget_removes_unc_from_recents()
{
@@ -166,6 +199,11 @@ file sealed class FakeVolumes : IVolumeService
|| v.RootPath.Equals(path, StringComparison.OrdinalIgnoreCase));
}
public bool IsPathReachable(string path) => Online.Any(v => path.StartsWith(v.RootPath.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase));
public VolumeSpace GetSpace(string path)
{
var fp = Probe(path);
return new VolumeSpace(fp?.CapacityBytes, fp?.FreeBytes);
}
}
file sealed class FakeEnv : IAppEnvironment

View File

@@ -1,4 +1,5 @@
using Explorer.Application;
using Explorer.Domain;
using Explorer.Domain.Abstractions;
namespace Explorer.Application.Tests;
@@ -13,12 +14,14 @@ public class UiPreferencesStoreTests
"theme=Light",
"group-network=true",
"group-cloud=false",
"index-archives=true"
"index-archives=true",
"auto-clear-queue=true"
]);
Assert.Equal("Light", prefs.Theme);
Assert.True(prefs.GroupNetworkPlaces);
Assert.False(prefs.GroupCloudPlaces);
Assert.True(prefs.IndexArchiveContents);
Assert.True(prefs.AutoClearQueueWhenDone);
}
[Fact]
@@ -29,6 +32,30 @@ public class UiPreferencesStoreTests
Assert.False(prefs.GroupNetworkPlaces);
Assert.False(prefs.GroupCloudPlaces);
Assert.False(prefs.IndexArchiveContents);
Assert.True(prefs.ShowHiddenFiles);
Assert.False(prefs.ShowProtectedSystemLocations);
Assert.False(prefs.AutoClearQueueWhenDone);
}
[Fact]
public void Parse_reads_window_layout()
{
var prefs = UiPreferencesStore.Parse(
[
"theme=Dark",
"window-width=1440.5",
"window-height=900",
"window-left=12",
"window-top=24",
"window-maximized=true",
"tree-width=320"
]);
Assert.Equal(1440.5, prefs.WindowWidth);
Assert.Equal(900, prefs.WindowHeight);
Assert.Equal(12, prefs.WindowLeft);
Assert.Equal(24, prefs.WindowTop);
Assert.True(prefs.WindowMaximized);
Assert.Equal(320, prefs.TreeWidth);
}
[Fact]
@@ -38,12 +65,18 @@ public class UiPreferencesStoreTests
try
{
var store = new UiPreferencesStore(new PrefsEnv(dir));
store.Save(new UiPreferences("Light", true, false, true));
store.Save(new UiPreferences("Light", true, false, true, AutoClearQueueWhenDone: true, WindowWidth: 1100, WindowHeight: 720, TreeWidth: 300));
var loaded = store.Load();
Assert.Equal("Light", loaded.Theme);
Assert.True(loaded.GroupNetworkPlaces);
Assert.False(loaded.GroupCloudPlaces);
Assert.True(loaded.IndexArchiveContents);
Assert.True(loaded.ShowHiddenFiles);
Assert.False(loaded.ShowProtectedSystemLocations);
Assert.True(loaded.AutoClearQueueWhenDone);
Assert.Equal(1100, loaded.WindowWidth);
Assert.Equal(720, loaded.WindowHeight);
Assert.Equal(300, loaded.TreeWidth);
}
finally
{
@@ -52,6 +85,28 @@ public class UiPreferencesStoreTests
}
}
public class LocationVisibilityTests
{
[Fact]
public void Hides_protected_when_setting_is_off()
{
var prefs = UiPreferences.Default;
var svi = LocationClassifier.Classify(
@"C:\System Volume Information", "System Volume Information",
AttributeFlags.Directory | AttributeFlags.Hidden | AttributeFlags.System, true);
Assert.False(LocationVisibility.ShouldShow(svi, prefs));
Assert.True(LocationVisibility.ShouldShow(svi, prefs with { ShowProtectedSystemLocations = true }));
}
[Fact]
public void Unknown_size_when_access_denied_and_zero()
{
var info = new LocationInfo(true, true, true, true, false);
Assert.Equal(SizeKnowledge.Unknown, LocationVisibility.ResolveSizeKnowledge(info, true, 0, true));
Assert.Equal(SizeKnowledge.Partial, LocationVisibility.ResolveSizeKnowledge(info, true, 1000, true));
}
}
file sealed class PrefsEnv : IAppEnvironment
{
public PrefsEnv(string dir)