Show folder names immediately and refresh stale index sizes without walking the whole drive.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 11:21:20 +02:00
parent 6fc7506eb7
commit e2916aef9c
88 changed files with 5373 additions and 544 deletions

View File

@@ -29,6 +29,8 @@ public class BrowseHydrationTests
Assert.Equal(BrowseHydration.ConstrainedProviderBatch, BrowseHydration.ProviderBatchSize(SourceKind.NtfsLocal, true));
Assert.Equal(BrowseHydration.ConstrainedWorkers, BrowseHydration.WorkerCount(SourceKind.Smb, false));
Assert.Equal(BrowseHydration.LocalWorkers, BrowseHydration.WorkerCount(SourceKind.NtfsLocal, false));
Assert.Equal(1, BrowseHydration.FirstPublish);
Assert.True(BrowseHydration.FirstPublish < BrowseHydration.PublishBatch);
}
[Fact]
@@ -88,6 +90,102 @@ public class BrowseHydrationTests
}
}
[Fact]
public async Task Progressive_listing_publishes_first_names_before_enumeration_finishes()
{
var gate = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var (browse, store, _) = await CreateAsync(enumerator: new GatedEnumerator(gate));
try
{
await using var it = browse.ListProgressiveAsync(@"C:\").GetAsyncEnumerator();
var first = it.MoveNextAsync().AsTask();
var winner = await Task.WhenAny(first, Task.Delay(TimeSpan.FromSeconds(2))).ConfigureAwait(true);
Assert.Same(first, winner);
Assert.True(await first);
Assert.Contains(it.Current.Added, i => i.Name == "first");
Assert.False(it.Current.EnumerationComplete);
Assert.False(gate.Task.IsCompleted);
gate.SetResult();
var sawSecond = it.Current.Added.Any(i => i.Name == "second");
var complete = it.Current.EnumerationComplete;
while (!complete && await it.MoveNextAsync())
{
sawSecond |= it.Current.Added.Any(i => i.Name == "second");
complete = it.Current.EnumerationComplete;
}
Assert.True(sawSecond);
}
finally
{
await store.DisposeAsync();
}
}
[Fact]
public async Task Progressive_listing_applies_index_sizes_after_names()
{
var (browse, store, _) = await CreateAsync();
try
{
var source = (await store.Sources.GetAllAsync()).Single();
source.LastIndexedUtc = DateTimeOffset.UtcNow;
await store.Sources.UpsertAsync(source);
var root = new IndexEntry
{
SourceId = source.Id,
Name = "C:",
NameNorm = "c:",
IsDirectory = true,
PathRel = "",
LastSeenUtc = DateTimeOffset.UtcNow
};
root.Id = await store.Entries.UpsertAsync(root);
await store.Entries.UpsertAsync(new IndexEntry
{
SourceId = source.Id,
ParentId = root.Id,
Name = "Movies",
NameNorm = "movies",
IsDirectory = true,
PathRel = "Movies",
LastSeenUtc = DateTimeOffset.UtcNow,
AggregateSize = 200
});
await using var it = browse.ListProgressiveAsync(@"C:\").GetAsyncEnumerator();
Assert.True(await it.MoveNextAsync());
Assert.NotEmpty(it.Current.Added);
Assert.All(it.Current.Added, item => Assert.Equal(0, (int)(item.Hydration & ItemHydrationFlags.Index)));
var movies = it.Current.Added.FirstOrDefault(i => i.Name == "Movies");
if (movies is not null)
{
Assert.Equal(0, movies.SizeBytes);
}
long? size = movies?.SizeBytes;
var complete = it.Current.HydrationComplete;
while (!complete && await it.MoveNextAsync())
{
movies ??= it.Current.Added.FirstOrDefault(i => i.Name == "Movies");
var updated = it.Current.Updated.FirstOrDefault(i => i.Name == "Movies");
if (updated is not null)
{
size = updated.SizeBytes;
}
complete = it.Current.HydrationComplete;
}
Assert.Equal(200, size);
}
finally
{
await store.DisposeAsync();
}
}
[Fact]
public async Task Progressive_listing_cancels_without_applying_stale_provider_results()
{
@@ -235,6 +333,46 @@ public class BrowseHydrationTests
}
}
file sealed class GatedEnumerator(TaskCompletionSource gate) : IFileSystemEnumerator
{
public IEnumerable<FileSystemItem> EnumerateChildren(string directoryPath)
=> EnumerateChildrenSafe(directoryPath, out _);
public FileSystemItem? GetItem(string path)
=> throw new InvalidOperationException("GetItem must not run during folder listing.");
public IReadOnlyList<FileSystemItem> EnumerateChildrenSafe(string directoryPath, out string? error)
{
error = null;
return
[
new FileSystemItem { FullPath = Path.Combine(directoryPath, "first"), Name = "first", IsDirectory = true },
new FileSystemItem { FullPath = Path.Combine(directoryPath, "second"), Name = "second", IsDirectory = true }
];
}
public IEnumerable<FileSystemItem> EnumerateChildrenStreaming(
string directoryPath,
FileEnumerationSink sink,
CancellationToken cancellationToken)
{
yield return new FileSystemItem
{
FullPath = Path.Combine(directoryPath, "first"),
Name = "first",
IsDirectory = true
};
gate.Task.GetAwaiter().GetResult();
cancellationToken.ThrowIfCancellationRequested();
yield return new FileSystemItem
{
FullPath = Path.Combine(directoryPath, "second"),
Name = "second",
IsDirectory = true
};
}
}
file sealed class GuardedEnumerator : IFileSystemEnumerator
{
public int GetItemCalls { get; private set; }