Skip to content

Commit

Permalink
add xml docs to some classes
Browse files Browse the repository at this point in the history
  • Loading branch information
bezzad committed Sep 18, 2024
1 parent 5061c5a commit d821e80
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 44 deletions.
42 changes: 27 additions & 15 deletions src/Downloader/AbstractDownloadService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -129,7 +128,8 @@ protected AbstractDownloadService(DownloadConfiguration options)

// This property selects the version of the Secure Sockets Layer (SSL) or
// existing connections aren't changed.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

// Accept the request for POST, PUT and PATCH verbs
ServicePointManager.Expect100Continue = false;
Expand All @@ -143,8 +143,7 @@ protected AbstractDownloadService(DownloadConfiguration options)
// garbage collection and cannot be used by the ServicePointManager object.
ServicePointManager.MaxServicePointIdleTime = 10000;

ServicePointManager.ServerCertificateValidationCallback =
new RemoteCertificateValidationCallback(ExceptionHelper.CertificateValidationCallBack);
ServicePointManager.ServerCertificateValidationCallback = ExceptionHelper.CertificateValidationCallBack;
}

/// <summary>
Expand All @@ -165,7 +164,8 @@ public Task<Stream> DownloadFileTaskAsync(DownloadPackage package, CancellationT
/// <param name="address">The URL address of the file to download.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the download.</param>
/// <returns>A task that represents the asynchronous download operation. The task result contains the downloaded stream.</returns>
public Task<Stream> DownloadFileTaskAsync(DownloadPackage package, string address, CancellationToken cancellationToken = default)
public Task<Stream> DownloadFileTaskAsync(DownloadPackage package, string address,
CancellationToken cancellationToken = default)
{
return DownloadFileTaskAsync(package, new[] { address }, cancellationToken);
}
Expand All @@ -177,7 +177,8 @@ public Task<Stream> DownloadFileTaskAsync(DownloadPackage package, string addres
/// <param name="urls">The array of URL addresses of the file to download.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the download.</param>
/// <returns>A task that represents the asynchronous download operation. The task result contains the downloaded stream.</returns>
public virtual async Task<Stream> DownloadFileTaskAsync(DownloadPackage package, string[] urls, CancellationToken cancellationToken = default)
public virtual async Task<Stream> DownloadFileTaskAsync(DownloadPackage package, string[] urls,
CancellationToken cancellationToken = default)
{
Package = package;
await InitialDownloader(cancellationToken, urls).ConfigureAwait(false);
Expand All @@ -201,7 +202,8 @@ public Task<Stream> DownloadFileTaskAsync(string address, CancellationToken canc
/// <param name="urls">The array of URL addresses of the file to download.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the download.</param>
/// <returns>A task that represents the asynchronous download operation. The task result contains the downloaded stream.</returns>
public virtual async Task<Stream> DownloadFileTaskAsync(string[] urls, CancellationToken cancellationToken = default)
public virtual async Task<Stream> DownloadFileTaskAsync(string[] urls,
CancellationToken cancellationToken = default)
{
await InitialDownloader(cancellationToken, urls).ConfigureAwait(false);
return await StartDownload().ConfigureAwait(false);
Expand All @@ -226,7 +228,8 @@ public Task DownloadFileTaskAsync(string address, string fileName, CancellationT
/// <param name="fileName">The name of the file to save the download as.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the download.</param>
/// <returns>A task that represents the asynchronous download operation.</returns>
public virtual async Task DownloadFileTaskAsync(string[] urls, string fileName, CancellationToken cancellationToken = default)
public virtual async Task DownloadFileTaskAsync(string[] urls, string fileName,
CancellationToken cancellationToken = default)
{
await InitialDownloader(cancellationToken, urls).ConfigureAwait(false);
await StartDownload(fileName).ConfigureAwait(false);
Expand All @@ -239,7 +242,8 @@ public virtual async Task DownloadFileTaskAsync(string[] urls, string fileName,
/// <param name="folder">The directory to save the downloaded file in.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the download.</param>
/// <returns>A task that represents the asynchronous download operation.</returns>
public Task DownloadFileTaskAsync(string address, DirectoryInfo folder, CancellationToken cancellationToken = default)
public Task DownloadFileTaskAsync(string address, DirectoryInfo folder,
CancellationToken cancellationToken = default)
{
return DownloadFileTaskAsync(new[] { address }, folder, cancellationToken);
}
Expand All @@ -251,7 +255,8 @@ public Task DownloadFileTaskAsync(string address, DirectoryInfo folder, Cancella
/// <param name="folder">The directory to save the downloaded file in.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the download.</param>
/// <returns>A task that represents the asynchronous download operation.</returns>
public virtual async Task DownloadFileTaskAsync(string[] urls, DirectoryInfo folder, CancellationToken cancellationToken = default)
public virtual async Task DownloadFileTaskAsync(string[] urls, DirectoryInfo folder,
CancellationToken cancellationToken = default)
{
await InitialDownloader(cancellationToken, urls).ConfigureAwait(false);
var name = await RequestInstances.First().GetFileName().ConfigureAwait(false);
Expand Down Expand Up @@ -357,12 +362,19 @@ protected async Task InitialDownloader(CancellationToken cancellationToken, para
/// <returns>A task that represents the asynchronous download operation.</returns>
protected async Task StartDownload(string fileName)
{
Package.FileName = fileName;
Directory.CreateDirectory(Path.GetDirectoryName(fileName)); // ensure the folder is exist

if (File.Exists(fileName))
if (!string.IsNullOrWhiteSpace(fileName))
{
File.Delete(fileName);
Package.FileName = fileName;
string dirName = Path.GetDirectoryName(fileName);
if (dirName != null)
{
Directory.CreateDirectory(dirName); // ensure the folder is existing
}

if (File.Exists(fileName))
{
File.Delete(fileName);
}
}

await StartDownload().ConfigureAwait(false);
Expand Down
36 changes: 34 additions & 2 deletions src/Downloader/Bandwidth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,45 @@

namespace Downloader;

/// <summary>
/// Represents a class for calculating and managing bandwidth usage during a download operation.
/// </summary>
public class Bandwidth
{
private const double OneSecond = 1000; // millisecond
private long _count;
private int _lastSecondCheckpoint;
private long _lastTransferredBytesCount;
private int _speedRetrieveTime;

/// <summary>
/// Gets the current download speed in bytes per second.
/// </summary>
public double Speed { get; private set; }

/// <summary>
/// Gets the average download speed in bytes per second.
/// </summary>
public double AverageSpeed { get; private set; }

/// <summary>
/// Gets or sets the bandwidth limit in bytes per second.
/// </summary>
public long BandwidthLimit { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="Bandwidth"/> class with default settings.
/// </summary>
public Bandwidth()
{
BandwidthLimit = long.MaxValue;
Reset();
}

/// <summary>
/// Calculates the current download speed based on the received bytes count.
/// </summary>
/// <param name="receivedBytesCount">The number of bytes received since the last calculation.</param>
public void CalculateSpeed(long receivedBytesCount)
{
int elapsedTime = Environment.TickCount - _lastSecondCheckpoint + 1;
Expand All @@ -36,16 +58,23 @@ public void CalculateSpeed(long receivedBytesCount)

if (momentSpeed >= BandwidthLimit)
{
var expectedTime = receivedBytesCount * OneSecond / BandwidthLimit;
double expectedTime = receivedBytesCount * OneSecond / BandwidthLimit;
Interlocked.Add(ref _speedRetrieveTime, (int)expectedTime - elapsedTime);
}
}

/// <summary>
/// Retrieves and resets the speed retrieve time.
/// </summary>
/// <returns>The speed retrieve time in milliseconds.</returns>
public int PopSpeedRetrieveTime()
{
return Interlocked.Exchange(ref _speedRetrieveTime, 0);
}

/// <summary>
/// Resets the bandwidth calculation.
/// </summary>
public void Reset()
{
SecondCheckpoint();
Expand All @@ -54,9 +83,12 @@ public void Reset()
AverageSpeed = 0;
}

/// <summary>
/// Sets the last second checkpoint to the current time and resets the transferred bytes count.
/// </summary>
private void SecondCheckpoint()
{
Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount);
Interlocked.Exchange(ref _lastTransferredBytesCount, 0);
}
}
}
50 changes: 36 additions & 14 deletions src/Downloader/Chunk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,106 @@
namespace Downloader;

/// <summary>
/// Chunk data structure
/// Represents a chunk of data in a file download operation.
/// </summary>
public class Chunk
{
/// <summary>
/// Chunk unique identity name
/// Gets or sets the unique identifier for the chunk.
/// </summary>
public string Id { get; set; }

/// <summary>
/// Start offset of the chunk in the file bytes
/// Gets or sets the start offset of the chunk in the file bytes.
/// </summary>
public long Start { get; set; }

/// <summary>
/// End offset of the chunk in the file bytes
/// Gets or sets the end offset of the chunk in the file bytes.
/// </summary>
public long End { get; set; }

/// <summary>
/// Current write offset of the chunk
/// Gets or sets the current write offset of the chunk.
/// </summary>
public long Position { get; set; }

/// <summary>
/// How many times to try again after the error
/// Gets or sets the maximum number of times to try again after an error.
/// </summary>
public int MaxTryAgainOnFailover { get; set; }

/// <summary>
/// How many milliseconds to wait for a response from the server?
/// Gets or sets the timeout in milliseconds to wait for a response from the server.
/// </summary>
public int Timeout { get; set; }

/// <summary>
/// How many times has downloading the chunk failed?
/// Gets the number of times downloading the chunk has failed.
/// </summary>
public int FailoverCount { get; private set; }

/// <summary>
/// Length of current chunk.
/// Gets the length of the 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.
/// </summary>
public long Length => End - Start + 1;

/// <summary>
/// Unused length of current chunk.
/// Gets the unused length of the 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.
/// </summary>
public long EmptyLength => Length > 0 ? Length - Position : long.MaxValue;

/// <summary>
/// Can write more data on this chunk according to the chunk situations?
/// Gets a value indicating whether more data can be written to this chunk according to the chunk's situation.
/// </summary>
public bool CanWrite => Length > 0 ? Start + Position < End : true;

public bool CanWrite => Length <= 0 || Start + Position < End;

/// <summary>
/// Initializes a new instance of the <see cref="Chunk"/> class with default values.
/// </summary>
public Chunk()
{
Timeout = 1000;
Id = Guid.NewGuid().ToString("N");
}

/// <summary>
/// Initializes a new instance of the <see cref="Chunk"/> class with the specified start and end positions.
/// </summary>
/// <param name="start">The start offset of the chunk in the file bytes.</param>
/// <param name="end">The end offset of the chunk in the file bytes.</param>
public Chunk(long start, long end) : this()
{
Start = start;
End = end;
}

/// <summary>
/// Determines whether the chunk can be retried on failover.
/// </summary>
/// <returns>True if the chunk can be retried; otherwise, false.</returns>
public bool CanTryAgainOnFailover()
{
return FailoverCount++ < MaxTryAgainOnFailover;
}

/// <summary>
/// Clears the chunk's position and failover count.
/// </summary>
public void Clear()
{
Position = 0;
FailoverCount = 0;
}

/// <summary>
/// Determines whether the download of the chunk is completed.
/// </summary>
/// <returns>True if the download is completed; otherwise, false.</returns>
public bool IsDownloadCompleted()
{
var isNoneEmptyFile = Length > 0;
Expand All @@ -93,6 +111,10 @@ public bool IsDownloadCompleted()
return isNoneEmptyFile && isChunkedFilledWithBytes;
}

/// <summary>
/// Determines whether the current position of the chunk is valid.
/// </summary>
/// <returns>True if the position is valid; otherwise, false.</returns>
public bool IsValidPosition()
{
return Length == 0 || (Position >= 0 && Position <= Length);
Expand Down
20 changes: 11 additions & 9 deletions src/Downloader/ChunkDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ internal class ChunkDownloader
private readonly DownloadConfiguration _configuration;
private readonly int _timeoutIncrement = 10;
private ThrottledStream _sourceStream;
private ConcurrentStream _storage;
private readonly ConcurrentStream _storage;
internal Chunk Chunk { get; set; }

public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;

public ChunkDownloader(Chunk chunk, DownloadConfiguration config, ConcurrentStream storage, ILogger logger = null)
Expand Down Expand Up @@ -102,7 +101,8 @@ private async Task DownloadChunk(Request downloadRequest, PauseToken pauseToken,
downloadResponse?.StatusCode == HttpStatusCode.Accepted ||
downloadResponse?.StatusCode == HttpStatusCode.ResetContent)
{
_logger?.LogDebug($"DownloadChunk of the chunk {Chunk.Id} with response status: {downloadResponse.StatusCode}");
_logger?.LogDebug(
$"DownloadChunk of the chunk {Chunk.Id} with response status: {downloadResponse.StatusCode}");
_configuration.RequestConfiguration.CookieContainer = request.CookieContainer;
await using Stream responseStream = downloadResponse.GetResponseStream();
await using (_sourceStream = new ThrottledStream(responseStream, _configuration.MaximumSpeedPerChunk))
Expand All @@ -112,8 +112,8 @@ private async Task DownloadChunk(Request downloadRequest, PauseToken pauseToken,
}
else
{
throw new WebException($"Download response status of the chunk {Chunk.Id} was {downloadResponse?.StatusCode}: " +
downloadResponse?.StatusDescription);
throw new WebException($"Download response status of the chunk {Chunk.Id} was " +
$"{downloadResponse?.StatusCode}: " + downloadResponse?.StatusDescription);
}
}
}
Expand Down Expand Up @@ -160,7 +160,8 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio
readSize = (int)Math.Min(Chunk.EmptyLength, readSize);
if (readSize > 0)
{
await _storage.WriteAsync(Chunk.Start + Chunk.Position - _configuration.RangeLow, buffer, readSize).ConfigureAwait(false);
await _storage.WriteAsync(Chunk.Start + Chunk.Position - _configuration.RangeLow, buffer, readSize)
.ConfigureAwait(false);
_logger?.LogDebug($"Write {readSize}bytes in the chunk {Chunk.Id}");
Chunk.Position += readSize;
_logger?.LogDebug($"The chunk {Chunk.Id} current position is: {Chunk.Position} of {Chunk.Length}");
Expand All @@ -169,16 +170,17 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio
TotalBytesToReceive = Chunk.Length,
ReceivedBytesSize = Chunk.Position,
ProgressedByteSize = readSize,
ReceivedBytes = _configuration.EnableLiveStreaming
? buffer.Take(readSize).ToArray()
ReceivedBytes = _configuration.EnableLiveStreaming
? buffer.Take(readSize).ToArray()
: []
});
}
}
}
catch (ObjectDisposedException exp) // When closing stream manually, ObjectDisposedException will be thrown
{
_logger?.LogError(exp, $"ReadAsync of the chunk {Chunk.Id} stream was canceled or closed forcibly from server");
_logger?.LogError(exp,
$"ReadAsync of the chunk {Chunk.Id} stream was canceled or closed forcibly from server");
cancelToken.ThrowIfCancellationRequested();
if (innerToken?.IsCancellationRequested == true)
{
Expand Down
Loading

0 comments on commit d821e80

Please sign in to comment.