Skip to content

Commit

Permalink
Merge branch 'release/v2.3.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
bezzad committed May 3, 2022
2 parents 8392c89 + dd2f7d4 commit 7be75fb
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 17 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Downloader is compatible with .NET Standard 2.0 and above, running on Windows, L
- Download files without storing on disk and get a memory stream for each downloaded file.
- Serializable download package (to/from `JSON` or `Binary`)
- Live streaming support, suitable for playing music at the same time as downloading.
- Ability to download just a certain range of bytes of a large file.

---

Expand All @@ -58,6 +59,19 @@ Downloader is compatible with .NET Standard 2.0 and above, running on Windows, L

## **Step 1**: Create your custom configuration

### Simple Configuration

```csharp
var downloadOpt = new DownloadConfiguration()
{
ChunkCount = 8, // file parts to download, default value is 1
OnTheFlyDownload = true, // caching in-memory or not? default values is true
ParallelDownload = true // download parts of file as parallel or not. Default value is false
};
```

### Complex Configuration

```csharp
var downloadOpt = new DownloadConfiguration()
{
Expand All @@ -69,13 +83,16 @@ var downloadOpt = new DownloadConfiguration()
ParallelDownload = true, // download parts of file as parallel or not. Default value is false
TempDirectory = "C:\\temp", // Set the temp path for buffering chunk files, the default path is Path.GetTempPath()
Timeout = 1000, // timeout (millisecond) per stream block reader, default values is 1000
RangeDownload = true, // set true if you want to download just a certain range of bytes of a large file
RangeLow = 273618157, // floor offset of download range of a large file
RangeHigh = 305178560, // ceiling offset of download range of a large file
RequestConfiguration = // config and customize request headers
{
Accept = "*/*",
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
CookieContainer = new CookieContainer(), // Add your cookies
Headers = new WebHeaderCollection(), // Add your custom headers
KeepAlive = false,
KeepAlive = false, // default value is false
ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1
UseDefaultCredentials = false,
UserAgent = $"DownloaderSample/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"
Expand Down
7 changes: 5 additions & 2 deletions src/Downloader.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,18 @@ private static DownloadConfiguration GetDownloadConfiguration()
ParallelDownload = true, // download parts of file as parallel or not. Default value is false
TempDirectory = "C:\\temp", // Set the temp path for buffering chunk files, the default path is Path.GetTempPath()
Timeout = 1000, // timeout (millisecond) per stream block reader, default values is 1000
RangeDownload = false,
RangeLow = 0,
RangeHigh = 0,
RequestConfiguration = {
// config and customize request headers
Accept = "*/*",
CookieContainer = cookies,
Headers = new WebHeaderCollection(), // { Add your custom headers }
KeepAlive = true,
KeepAlive = true, // default value is false
ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1
UseDefaultCredentials = false,
UserAgent = $"DownloaderSample/{version}",
UserAgent = $"DownloaderSample/{version}"
//Proxy = new WebProxy() {
// Address = new Uri("http://YourProxyServer/proxy.pac"),
// UseDefaultCredentials = false,
Expand Down
46 changes: 46 additions & 0 deletions src/Downloader.Test/IntegrationTests/DownloadIntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -396,5 +396,51 @@ public void TestContentWhenDownloadOnMemoryStream()
// assert
Assert.IsTrue(DummyFileHelper.File1Kb.SequenceEqual(stream.ToArray()));
}

[TestMethod]
public void Download256BytesRangeOf1KbFileTest()
{
// arrange
Config.RangeDownload = true;
Config.RangeLow = 256;
Config.RangeHigh = 511;
var totalSize = Config.RangeHigh - Config.RangeLow + 1;
var downloader = new DownloadService(Config);

// act
using var stream = (MemoryStream)downloader.DownloadFileTaskAsync(DummyFileHelper.GetFileUrl(DummyFileHelper.FileSize1Kb)).Result;
var bytes = stream.ToArray();

// assert
Assert.IsNotNull(stream);
Assert.AreEqual(totalSize, stream.Length);
Assert.AreEqual(totalSize, downloader.Package.TotalFileSize);
Assert.AreEqual(100.0, downloader.Package.SaveProgress);
for (int i = 0; i < totalSize; i++)
Assert.AreEqual((byte)i, bytes[i]);
}

[TestMethod]
public void DownloadNegetiveRangeOf1KbFileTest()
{
// arrange
Config.RangeDownload = true;
Config.RangeLow = -256;
Config.RangeHigh = 255;
var totalSize = 256;
var downloader = new DownloadService(Config);

// act
using var stream = (MemoryStream)downloader.DownloadFileTaskAsync(DummyFileHelper.GetFileUrl(DummyFileHelper.FileSize1Kb)).Result;
var bytes = stream.ToArray();

// assert
Assert.IsNotNull(stream);
Assert.AreEqual(totalSize, stream.Length);
Assert.AreEqual(totalSize, downloader.Package.TotalFileSize);
Assert.AreEqual(100.0, downloader.Package.SaveProgress);
for (int i = 0; i < totalSize; i++)
Assert.AreEqual((byte)i, bytes[i]);
}
}
}
39 changes: 39 additions & 0 deletions src/Downloader.Test/UnitTests/ChunkHubTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,45 @@ public void ChunkFileSizeTest()
Assert.AreEqual(fileSize, chunks.Sum(chunk => chunk.Length));
}

[TestMethod]
public void ChunkFileRangeSizeTest()
{
// arrange
int fileSize = 10679630;
int parts = 64;
long rangeLow = 1024;
long rangeHigh = 9679630;
long totalBytes = rangeHigh - rangeLow + 1;
var chunkHub = new ChunkHub(_configuration);

// act
Chunk[] chunks = chunkHub.ChunkFile(totalBytes, parts, rangeLow);

// assert
Assert.AreEqual(totalBytes, chunks.Sum(chunk => chunk.Length));
Assert.IsTrue(fileSize >= chunks.Sum(chunk => chunk.Length));
Assert.AreEqual(chunks.Last().End, rangeHigh);
}

[TestMethod]
public void ChunkFileRangeBelowZeroTest()
{
// arrange
int parts = 64;
long rangeLow = -4096;
long rangeHigh = 2048;
long actualTotalSize = rangeHigh+1;
var chunkHub = new ChunkHub(_configuration);

// act
Chunk[] chunks = chunkHub.ChunkFile(actualTotalSize, parts, rangeLow);

// assert
Assert.AreEqual(actualTotalSize, chunks.Sum(chunk => chunk.Length));
Assert.AreEqual(chunks.First().Start, 0);
Assert.AreEqual(chunks.Last().End, rangeHigh);
}

[TestMethod]
public void ChunkFileZeroSizeTest()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Downloader/ChunkDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private void SetRequestRange(HttpWebRequest request)
{
// has limited range
if (Chunk.End > 0 &&
(Configuration.ChunkCount > 1 || Chunk.Position > 0))
(Configuration.ChunkCount > 1 || Chunk.Position > 0 || Configuration.RangeDownload))
{
request.AddRange(Chunk.Start + Chunk.Position, Chunk.End);
}
Expand Down
13 changes: 9 additions & 4 deletions src/Downloader/ChunkHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ public ChunkHub(DownloadConfiguration config)
_configuration = config;
}

public Chunk[] ChunkFile(long fileSize, long parts)
public Chunk[] ChunkFile(long fileSize, long parts, long start = 0)
{
if (start < 0)
{
start = 0;
}

if (fileSize < parts)
{
parts = fileSize;
Expand All @@ -31,11 +36,11 @@ public Chunk[] ChunkFile(long fileSize, long parts)
Chunk[] chunks = new Chunk[parts];
for (int i = 0; i < parts; i++)
{
bool isLastChunk = i == parts - 1;
long startPosition = i * chunkSize;
long endPosition = (isLastChunk ? fileSize : startPosition + chunkSize) - 1;
long startPosition = start + (i * chunkSize);
long endPosition = startPosition + chunkSize - 1;
chunks[i] = GetChunk(i.ToString(), startPosition, endPosition);
}
chunks.Last().End += fileSize % parts; // add remaining bytes to last chunk

return chunks;
}
Expand Down
49 changes: 47 additions & 2 deletions src/Downloader/DownloadConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public class DownloadConfiguration : ICloneable, INotifyPropertyChanged
private string _tempDirectory;
private string _tempFilesExtension = ".dsc";
private int _timeout;
private bool _rangeDownload;
private long _rangeLow;
private long _rangeHigh;

public event PropertyChangedEventHandler PropertyChanged = delegate { };

Expand All @@ -29,9 +32,12 @@ public DownloadConfiguration()
OnTheFlyDownload = true; // caching in-memory mode
BufferBlockSize = 1024; // usually, hosts support max to 8000 bytes
MaximumBytesPerSecond = ThrottledStream.Infinite; // No-limitation in download speed
RequestConfiguration = new RequestConfiguration(); // Default requests configuration
TempDirectory = Path.GetTempPath(); // Default chunks path
RequestConfiguration = new RequestConfiguration(); // default requests configuration
TempDirectory = Path.GetTempPath(); // default chunks path
CheckDiskSizeBeforeDownload = true; // check disk size for temp and file path
RangeDownload = false; // enable ranged download
RangeLow = 0; // starting byte offset
RangeHigh = 0; // ending byte offset
}

/// <summary>
Expand Down Expand Up @@ -142,6 +148,45 @@ public bool ParallelDownload
}
}

/// <summary>
/// Download a range of byte
/// </summary>
public bool RangeDownload
{
get => _rangeDownload;
set
{
_rangeDownload=value;
OnPropertyChanged();
}
}

/// <summary>
/// The starting byte offset for ranged download
/// </summary>
public long RangeLow
{
get => _rangeLow;
set
{
_rangeLow=value;
OnPropertyChanged();
}
}

/// <summary>
/// The ending byte offset for ranged download
/// </summary>
public long RangeHigh
{
get => _rangeHigh;
set
{
_rangeHigh=value;
OnPropertyChanged();
}
}

/// <summary>
/// Custom body of your requests
/// </summary>
Expand Down
29 changes: 27 additions & 2 deletions src/Downloader/DownloadService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private async Task<Stream> StartDownload()
Package.TotalFileSize = await _requestInstance.GetFileSize().ConfigureAwait(false);
OnDownloadStarted(new DownloadStartedEventArgs(Package.FileName, Package.TotalFileSize));
ValidateBeforeChunking();
Package.Chunks ??= _chunkHub.ChunkFile(Package.TotalFileSize, Options.ChunkCount);
Package.Chunks ??= _chunkHub.ChunkFile(Package.TotalFileSize, Options.ChunkCount, Options.RangeLow);
Package.Validate();

if (Options.ParallelDownload)
Expand Down Expand Up @@ -196,8 +196,33 @@ private async Task StoreDownloadedFile(CancellationToken cancellationToken)

private void ValidateBeforeChunking()
{
SetRangedSizes();
CheckUnlimitedDownload();
CheckSizes();
CheckSizes();
}

private void SetRangedSizes()
{
if (Options.RangeDownload)
{
if (Options.RangeHigh < Options.RangeLow)
Options.RangeLow = Options.RangeHigh - 1;

if (Options.RangeLow < 0)
Options.RangeLow = 0;

if (Options.RangeHigh < 0)
Options.RangeHigh = Options.RangeLow;

if (Package.TotalFileSize > 0)
Options.RangeHigh = Math.Min(Package.TotalFileSize, Options.RangeHigh);

Package.TotalFileSize = Options.RangeHigh - Options.RangeLow + 1;
}
else
{
Options.RangeHigh = Options.RangeLow = 0; // reset range options
}
}

private void CheckSizes()
Expand Down
23 changes: 18 additions & 5 deletions src/Downloader/Downloader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net452;net6.0</TargetFrameworks>
<LangVersion>latestMajor</LangVersion>
<Version>2.3.3</Version>
<Version>2.3.4</Version>
<Title>Downloader</Title>
<Authors>Behzad Khosravifar</Authors>
<Company>bezzad</Company>
<Description>Fast and reliable multipart downloader with asynchronous progress events for .NET</Description>
<PackageProjectUrl>https://github.com/bezzad/Downloader</PackageProjectUrl>
<RepositoryUrl>https://github.com/bezzad/Downloader</RepositoryUrl>
<PackageTags>download-manager, downloader, download-file, stream-downloader, multipart-download</PackageTags>
<PackageReleaseNotes>* Fixed JSON Serialization. issue #81</PackageReleaseNotes>
<PackageReleaseNotes>* Added new feature for ranged downloads #85</PackageReleaseNotes>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Downloader.snk</AssemblyOriginatorKeyFile>
<Copyright>Copyright (C) 2020 Behzad Khosravifar</Copyright>
<Copyright>Copyright (C) 2019-2022 Behzad Khosravifar</Copyright>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseExpression></PackageLicenseExpression>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageIcon>downloader.png</PackageIcon>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>2.3.3</AssemblyVersion>
<FileVersion>2.3.3</FileVersion>
<AssemblyVersion>2.3.4</AssemblyVersion>
<FileVersion>2.3.4</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
Expand All @@ -33,8 +34,20 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>

<ItemGroup>
<None Remove="downloader.png" />
<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="downloader.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
Expand Down

0 comments on commit 7be75fb

Please sign in to comment.