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:
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user