Use shared archive entry views for completeness checks

This commit is contained in:
Adam Hathcock
2026-11-13 10:55:26 +00:00
parent 2b9295ce36
commit 331808d004
3 changed files with 75 additions and 2 deletions

View File

@@ -47,7 +47,7 @@ public abstract partial class AbstractArchive<TEntry, TVolume>
private async ValueTask EnsureEntriesLoadedAsync()
{
await _lazyEntriesAsync.EnsureFullyLoaded().ConfigureAwait(false);
await foreach (var entry in EntriesAsync.ConfigureAwait(false)) { }
await _lazyVolumesAsync.EnsureFullyLoaded().ConfigureAwait(false);
}

View File

@@ -99,7 +99,8 @@ public abstract partial class AbstractArchive<TEntry, TVolume> : IArchive, IAsyn
private void EnsureEntriesLoaded()
{
_lazyEntries.EnsureFullyLoaded();
// Writable archives expose a shared view rather than this base cache.
Entries.ForEach(_ => { });
_lazyVolumes.EnsureFullyLoaded();
}

View File

@@ -51,6 +51,56 @@ public class GZipArchiveCacheTests : TestBase
Assert.True(input.CanRead);
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task Completeness_Uses_The_Current_Entry_Cache(bool useAsync, bool partial)
{
using var input = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
using var archive = GZipArchive.OpenArchive(input, ReaderOptions.ForExternalStream);
var asyncArchive = (IAsyncArchive)archive;
if (partial)
{
if (useAsync)
{
await asyncArchive.EntriesAsync.FirstAsync();
}
else
{
_ = archive.Entries.First();
}
}
Assert.True(useAsync ? await asyncArchive.IsCompleteAsync() : archive.IsComplete);
var entry = Assert.Single(archive.Entries);
Assert.Same(entry, Assert.Single(await asyncArchive.EntriesAsync.ToListAsync()));
Assert.True(archive.IsComplete);
Assert.True(await asyncArchive.IsCompleteAsync());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Completeness_Does_Not_Reparse_Already_Cached_Entries(bool useAsync)
{
using var input = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
using var archive = GZipArchive.OpenArchive(input, ReaderOptions.ForExternalStream);
var asyncArchive = (IAsyncArchive)archive;
if (useAsync)
{
Assert.Single(await asyncArchive.EntriesAsync.ToListAsync());
}
else
{
Assert.Single(archive.Entries);
}
// Completeness needs only cached metadata and must not move the source.
input.Position = input.Length;
Assert.True(useAsync ? await asyncArchive.IsCompleteAsync() : archive.IsComplete);
Assert.Equal(input.Length, input.Position);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
@@ -77,4 +127,26 @@ public class GZipArchiveCacheTests : TestBase
: await asyncArchive.EntriesAsync.ToListAsync();
Assert.Same(first, Assert.Single(entries));
}
[Fact]
public async Task Resuming_And_Checking_Completeness_Do_Not_Use_Synchronous_Io()
{
using var input = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"));
using var asyncOnly = new AsyncOnlyStream(input, disposeStream: false);
await using (
var archive = await GZipArchive.OpenAsyncArchive(
asyncOnly,
ReaderOptions.ForExternalStream,
CancellationToken.None
)
)
{
var entry = await archive.EntriesAsync.FirstAsync();
Assert.True(await archive.IsCompleteAsync());
Assert.Same(entry, Assert.Single(await archive.EntriesAsync.ToListAsync()));
using var payload = await entry.OpenEntryStreamAsync(CancellationToken.None);
await payload.CopyToAsync(Stream.Null, 81920, CancellationToken.None);
}
Assert.True(input.CanRead);
}
}