diff --git a/src/Toimik.WarcProtocol/LineReader.cs b/src/Toimik.WarcProtocol/LineReader.cs index 7923386..202fb6e 100644 --- a/src/Toimik.WarcProtocol/LineReader.cs +++ b/src/Toimik.WarcProtocol/LineReader.cs @@ -37,14 +37,24 @@ public class LineReader(Stream stream, CancellationToken cancellationToken) public async Task Offset(long byteOffset) { - // NOTE: This is naively done because seek is unsupported by the underlying class - for (long i = 0; i < byteOffset; i++) + if (Stream.CanSeek) { - var readCount = await Stream.ReadAsync(buffer: (new byte[1]).AsMemory(start: 0, length: 1)).ConfigureAwait(false); - var isEofEncountered = readCount == 0; - if (isEofEncountered) + Stream.Seek(byteOffset, SeekOrigin.Begin); + } + else + { + long bytePosition = 0; + var buffer = new byte[1024]; + while (bytePosition < byteOffset) { - throw new ArgumentException("Offset exceeds file size.", nameof(byteOffset)); + var remainingCount = (int)Math.Min(buffer.Length, byteOffset - bytePosition); + var byteCount = await Stream.ReadAsync(buffer.AsMemory(0, remainingCount)).ConfigureAwait(false); + if (byteCount == 0) + { + break; + } + + bytePosition += byteCount; } } } diff --git a/src/Toimik.WarcProtocol/Toimik.WarcProtocol.csproj b/src/Toimik.WarcProtocol/Toimik.WarcProtocol.csproj index b9da7ab..315cb78 100644 --- a/src/Toimik.WarcProtocol/Toimik.WarcProtocol.csproj +++ b/src/Toimik.WarcProtocol/Toimik.WarcProtocol.csproj @@ -4,9 +4,9 @@ net8.0 enable Toimik.WarcProtocol - 0.10.5 + 0.10.6 Nurhafiz - 0.10.5 + 0.10.6 true Toimik diff --git a/tests/Toimik.WarcProtocol.Tests/LineReaderTest.cs b/tests/Toimik.WarcProtocol.Tests/LineReaderTest.cs new file mode 100644 index 0000000..805b1cc --- /dev/null +++ b/tests/Toimik.WarcProtocol.Tests/LineReaderTest.cs @@ -0,0 +1,26 @@ +namespace Toimik.WarcProtocol.Tests; + +using Moq; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +public class LineReaderTest +{ + [Fact] + public async Task OffsetNaively() + { + var streamMock = new Mock(); + streamMock.Setup(s => s.CanSeek) + .Returns(false); + streamMock.SetupSequence(s => s.ReadAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(1) + .ReturnsAsync(0); + + var lineReader = new LineReader(streamMock.Object, CancellationToken.None); + await lineReader.Offset(2); + return; + } +} \ No newline at end of file diff --git a/tests/Toimik.WarcProtocol.Tests/Toimik.WarcProtocol.Tests.csproj b/tests/Toimik.WarcProtocol.Tests/Toimik.WarcProtocol.Tests.csproj index 6e32196..d7e3b6e 100644 --- a/tests/Toimik.WarcProtocol.Tests/Toimik.WarcProtocol.Tests.csproj +++ b/tests/Toimik.WarcProtocol.Tests/Toimik.WarcProtocol.Tests.csproj @@ -12,6 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/tests/Toimik.WarcProtocol.Tests/WarcParserTest.cs b/tests/Toimik.WarcProtocol.Tests/WarcParserTest.cs index ffd993c..3ca6dbe 100644 --- a/tests/Toimik.WarcProtocol.Tests/WarcParserTest.cs +++ b/tests/Toimik.WarcProtocol.Tests/WarcParserTest.cs @@ -338,16 +338,21 @@ public async Task MultilineHeaderValues() Assert.Equal("A", Encoding.UTF8.GetString(record.RecordBlock!)); } + // NOTE: This is no longer needed due to the inclusion of Stream.Seek(...) in LineReader that + // allows for an offset beyond the length of the stream. + /* [Fact] public async Task OffsetOverLimit() { var parser = new WarcParser(); var path = $"{DirectoryForInvalidRecords}incorrect_content_length.warc"; - var exception = await Assert.ThrowsAsync(async () => await parser.Parse(path, byteOffset: 1000).ToListAsync()); + var exception = await Assert.ThrowsAsync(async () => await + parser.Parse(path, byteOffset: 1000).ToListAsync()); Assert.Contains("Offset exceeds file size", exception.Message); } + */ [Fact] public async Task OffsetUnderLimit()