diff --git a/src/Downloader.Test/UnitTests/ChunkDownloaderOnFileTest.cs b/src/Downloader.Test/UnitTests/ChunkDownloaderOnFileTest.cs
index 59c8e65..af45829 100644
--- a/src/Downloader.Test/UnitTests/ChunkDownloaderOnFileTest.cs
+++ b/src/Downloader.Test/UnitTests/ChunkDownloaderOnFileTest.cs
@@ -17,6 +17,6 @@ public ChunkDownloaderOnFileTest()
MinimumSizeOfChunking = 16,
Timeout = 100,
};
- Storage = new ConcurrentStream(path, DummyFileHelper.FileSize16Kb);
+ Storage = new ConcurrentStream(path, 0);
}
}
diff --git a/src/Downloader.Test/UnitTests/ChunkDownloaderTest.cs b/src/Downloader.Test/UnitTests/ChunkDownloaderTest.cs
index eaa0fee..a473239 100644
--- a/src/Downloader.Test/UnitTests/ChunkDownloaderTest.cs
+++ b/src/Downloader.Test/UnitTests/ChunkDownloaderTest.cs
@@ -169,4 +169,27 @@ public async Task CancelReadStreamTest()
chunkDownloader.Chunk.Clear();
}
+
+ [Fact]
+ public async Task OverflowWhenReadStreamTest()
+ {
+ // arrange
+ var randomlyBytes = DummyData.GenerateRandomBytes(Size);
+ var chunk = new Chunk(0, Size / 2 - 1);
+ var chunkDownloader = new ChunkDownloader(chunk, Configuration, Storage);
+ using var memoryStream = new MemoryStream(randomlyBytes);
+
+ // act
+ await chunkDownloader.ReadStream(memoryStream, new PauseTokenSource().Token, new CancellationToken());
+ Storage.Flush();
+
+ // assert
+ Assert.Equal(expected: Size / 2, actual: chunk.Length);
+ Assert.Equal(expected: chunk.Length, actual: chunk.Position);
+ Assert.Equal(expected: 0, actual: chunk.EmptyLength);
+ Assert.Equal(expected: memoryStream.Position, actual: chunk.Position);
+ Assert.Equal(expected: chunk.Length, actual: Storage.Length);
+
+ Storage.Dispose();
+ }
}
\ No newline at end of file
diff --git a/src/Downloader/Chunk.cs b/src/Downloader/Chunk.cs
index bd15f1a..2eb2c14 100644
--- a/src/Downloader/Chunk.cs
+++ b/src/Downloader/Chunk.cs
@@ -3,29 +3,76 @@
namespace Downloader
{
///
- /// Chunk data structure
+ /// Chunk data structure
///
public class Chunk
{
+ ///
+ /// Chunk unique identity name
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Start offset of the chunk in the file bytes
+ ///
+ public long Start { get; set; }
+
+ ///
+ /// End offset of the chunk in the file bytes
+ ///
+ public long End { get; set; }
+
+ ///
+ /// Current write offset of the chunk
+ ///
+ public long Position { get; set; }
+
+ ///
+ /// How many times to try again after the error
+ ///
+ public int MaxTryAgainOnFailover { get; set; }
+
+ ///
+ /// How many milliseconds to wait for a response from the server?
+ ///
+ public int Timeout { get; set; }
+
+ ///
+ /// How many times has downloading the chunk failed?
+ ///
+ public int FailoverCount { get; private set; }
+
+ ///
+ /// Length of current chunk.
+ /// When the chunk length is zero, the file is open to receive new bytes
+ /// until no more bytes are received from the server.
+ ///
+ public long Length => End - Start + 1;
+
+ ///
+ /// Unused length of current chunk.
+ /// When the chunk length is zero, the file is open to receive new bytes
+ /// until no more bytes are received from the server.
+ ///
+ public long EmptyLength => Length > 0 ? Length - Position : long.MaxValue;
+
+ ///
+ /// Can write more data on this chunk according to the chunk situations?
+ ///
+ public bool CanWrite => Length > 0 ? Start + Position < End : true;
+
+
public Chunk()
{
Id = Guid.NewGuid().ToString("N");
}
+
public Chunk(long start, long end) : this()
{
Start = start;
End = end;
}
- public string Id { get; set; }
- public long Start { get; set; }
- public long End { get; set; }
- public long Position { get; set; }
- public int MaxTryAgainOnFailover { get; set; }
- public int Timeout { get; set; }
- public int FailoverCount { get; private set; }
- public long Length => End - Start + 1;
-
public bool CanTryAgainOnFailover()
{
return FailoverCount++ < MaxTryAgainOnFailover;
diff --git a/src/Downloader/ChunkDownloader.cs b/src/Downloader/ChunkDownloader.cs
index 233be0e..cfe9d30 100644
--- a/src/Downloader/ChunkDownloader.cs
+++ b/src/Downloader/ChunkDownloader.cs
@@ -54,6 +54,11 @@ public async Task Download(Request downloadRequest, PauseToken pause, Can
{
return await ContinueWithDelay(downloadRequest, pause, cancelToken).ConfigureAwait(false);
}
+ catch(Exception error)
+ {
+ // Can't handle this exception
+ throw;
+ }
finally
{
await Task.Yield();
@@ -125,7 +130,7 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio
{
// close stream on cancellation because, it's not work on .Net Framework
using var _ = cancelToken.Register(stream.Close);
- while (readSize > 0)
+ while (readSize > 0 && Chunk.CanWrite)
{
cancelToken.ThrowIfCancellationRequested();
await pauseToken.WaitWhilePausedAsync().ConfigureAwait(false);
@@ -139,6 +144,7 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio
readSize = await stream.ReadAsync(buffer, 0, buffer.Length, innerToken.Value).ConfigureAwait(false);
}
+ readSize = (int)Math.Min(Chunk.EmptyLength, readSize);
if (readSize > 0)
{
await _storage.WriteAsync(Chunk.Start + Chunk.Position - _configuration.RangeLow, buffer, readSize).ConfigureAwait(false);