Skip to content

Commit 409f6d5

Browse files
authored
Merge pull request litedb-org#2459 from SpaceCheetah/FixSeek
Fix LiteFileStream.SetReadStreamPosition
2 parents e60b6ae + 4d58c69 commit 409f6d5

File tree

2 files changed

+98
-7
lines changed

2 files changed

+98
-7
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.IO;
3+
using Xunit;
4+
5+
namespace LiteDB.Tests.Issues;
6+
7+
public class Issue2458_Tests
8+
{
9+
[Fact]
10+
public void NegativeSeekFails()
11+
{
12+
using var db = new LiteDatabase(":memory:");
13+
var fs = db.FileStorage;
14+
AddTestFile("test", 1, fs);
15+
using Stream stream = fs.OpenRead("test");
16+
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Position = -1);
17+
}
18+
19+
//https://learn.microsoft.com/en-us/dotnet/api/system.io.stream.position?view=net-8.0 says seeking to a position
20+
//beyond the end of a stream is supported, so implementations should support it (error on read).
21+
[Fact]
22+
public void SeekPastFileSucceds()
23+
{
24+
using var db = new LiteDatabase(":memory:");
25+
var fs = db.FileStorage;
26+
AddTestFile("test", 1, fs);
27+
using Stream stream = fs.OpenRead("test");
28+
stream.Position = Int32.MaxValue;
29+
}
30+
31+
[Fact]
32+
public void SeekShortChunks()
33+
{
34+
using var db = new LiteDatabase(":memory:");
35+
var fs = db.FileStorage;
36+
using(Stream writeStream = fs.OpenWrite("test", "test"))
37+
{
38+
writeStream.WriteByte(0);
39+
writeStream.Flush(); //Create single-byte chunk just containing a 0
40+
writeStream.WriteByte(1);
41+
writeStream.Flush();
42+
writeStream.WriteByte(2);
43+
}
44+
using Stream readStream = fs.OpenRead("test");
45+
readStream.Position = 2;
46+
Assert.Equal(2, readStream.ReadByte());
47+
}
48+
49+
private void AddTestFile(string id, long length, ILiteStorage<string> fs)
50+
{
51+
using Stream writeStream = fs.OpenWrite(id, id);
52+
writeStream.Write(new byte[length]);
53+
}
54+
}

LiteDB/Client/Storage/LiteFileStream.Read.cs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using static LiteDB.Constants;
@@ -7,9 +8,14 @@ namespace LiteDB
78
{
89
public partial class LiteFileStream<TFileId> : Stream
910
{
11+
private Dictionary<int, long> _chunkLengths = new Dictionary<int, long>();
1012
public override int Read(byte[] buffer, int offset, int count)
1113
{
1214
if (_mode != FileAccess.Read) throw new NotSupportedException();
15+
if (_streamPosition == Length)
16+
{
17+
return 0;
18+
}
1319

1420
var bytesLeft = count;
1521

@@ -42,23 +48,54 @@ private byte[] GetChunkData(int index)
4248
.FindOne("_id = { f: @0, n: @1 }", _fileId, index);
4349

4450
// if chunk is null there is no more chunks
45-
return chunk?["data"].AsBinary;
51+
byte[] result = chunk?["data"].AsBinary;
52+
if (result != null)
53+
{
54+
_chunkLengths[index] = result.Length;
55+
}
56+
return result;
4657
}
4758

4859
private void SetReadStreamPosition(long newPosition)
4960
{
50-
if (newPosition < 0 && newPosition > Length)
61+
if (newPosition < 0)
5162
{
5263
throw new ArgumentOutOfRangeException();
5364
}
65+
if (newPosition >= Length)
66+
{
67+
_streamPosition = Length;
68+
return;
69+
}
5470
_streamPosition = newPosition;
5571

5672
// calculate new chunk position
57-
_currentChunkIndex = (int)_streamPosition / MAX_CHUNK_SIZE;
58-
_positionInChunk = (int)_streamPosition % MAX_CHUNK_SIZE;
59-
60-
// get current chunk
61-
_currentChunkData = this.GetChunkData(_currentChunkIndex);
73+
long seekStreamPosition = 0;
74+
int loadedChunk = _currentChunkIndex;
75+
int newChunkIndex = 0;
76+
while (seekStreamPosition <= _streamPosition)
77+
{
78+
if (_chunkLengths.TryGetValue(newChunkIndex, out long length))
79+
{
80+
seekStreamPosition += length;
81+
}
82+
else
83+
{
84+
loadedChunk = newChunkIndex;
85+
_currentChunkData = GetChunkData(newChunkIndex);
86+
seekStreamPosition += _currentChunkData.Length;
87+
}
88+
newChunkIndex++;
89+
}
90+
91+
newChunkIndex--;
92+
seekStreamPosition -= _chunkLengths[newChunkIndex];
93+
_positionInChunk = (int)(_streamPosition - seekStreamPosition);
94+
_currentChunkIndex = newChunkIndex;
95+
if (loadedChunk != _currentChunkIndex)
96+
{
97+
_currentChunkData = GetChunkData(_currentChunkIndex);
98+
}
6299
}
63100
}
64101
}

0 commit comments

Comments
 (0)