From d821e800bb0cb3607ccc4788f3df5bf29ef4b8f6 Mon Sep 17 00:00:00 2001 From: bezzad Date: Wed, 18 Sep 2024 20:15:43 +0330 Subject: [PATCH] add xml docs to some classes --- src/Downloader/AbstractDownloadService.cs | 42 ++++++++++++------- src/Downloader/Bandwidth.cs | 36 +++++++++++++++- src/Downloader/Chunk.cs | 50 ++++++++++++++++------- src/Downloader/ChunkDownloader.cs | 20 +++++---- src/Downloader/ChunkHub.cs | 30 ++++++++++++-- 5 files changed, 134 insertions(+), 44 deletions(-) diff --git a/src/Downloader/AbstractDownloadService.cs b/src/Downloader/AbstractDownloadService.cs index 481fb74..2782444 100644 --- a/src/Downloader/AbstractDownloadService.cs +++ b/src/Downloader/AbstractDownloadService.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Net; -using System.Net.Security; using System.Threading; using System.Threading.Tasks; @@ -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; @@ -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; } /// @@ -165,7 +164,8 @@ public Task DownloadFileTaskAsync(DownloadPackage package, CancellationT /// The URL address of the file to download. /// A cancellation token that can be used to cancel the download. /// A task that represents the asynchronous download operation. The task result contains the downloaded stream. - public Task DownloadFileTaskAsync(DownloadPackage package, string address, CancellationToken cancellationToken = default) + public Task DownloadFileTaskAsync(DownloadPackage package, string address, + CancellationToken cancellationToken = default) { return DownloadFileTaskAsync(package, new[] { address }, cancellationToken); } @@ -177,7 +177,8 @@ public Task DownloadFileTaskAsync(DownloadPackage package, string addres /// The array of URL addresses of the file to download. /// A cancellation token that can be used to cancel the download. /// A task that represents the asynchronous download operation. The task result contains the downloaded stream. - public virtual async Task DownloadFileTaskAsync(DownloadPackage package, string[] urls, CancellationToken cancellationToken = default) + public virtual async Task DownloadFileTaskAsync(DownloadPackage package, string[] urls, + CancellationToken cancellationToken = default) { Package = package; await InitialDownloader(cancellationToken, urls).ConfigureAwait(false); @@ -201,7 +202,8 @@ public Task DownloadFileTaskAsync(string address, CancellationToken canc /// The array of URL addresses of the file to download. /// A cancellation token that can be used to cancel the download. /// A task that represents the asynchronous download operation. The task result contains the downloaded stream. - public virtual async Task DownloadFileTaskAsync(string[] urls, CancellationToken cancellationToken = default) + public virtual async Task DownloadFileTaskAsync(string[] urls, + CancellationToken cancellationToken = default) { await InitialDownloader(cancellationToken, urls).ConfigureAwait(false); return await StartDownload().ConfigureAwait(false); @@ -226,7 +228,8 @@ public Task DownloadFileTaskAsync(string address, string fileName, CancellationT /// The name of the file to save the download as. /// A cancellation token that can be used to cancel the download. /// A task that represents the asynchronous download operation. - 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); @@ -239,7 +242,8 @@ public virtual async Task DownloadFileTaskAsync(string[] urls, string fileName, /// The directory to save the downloaded file in. /// A cancellation token that can be used to cancel the download. /// A task that represents the asynchronous download operation. - 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); } @@ -251,7 +255,8 @@ public Task DownloadFileTaskAsync(string address, DirectoryInfo folder, Cancella /// The directory to save the downloaded file in. /// A cancellation token that can be used to cancel the download. /// A task that represents the asynchronous download operation. - 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); @@ -357,12 +362,19 @@ protected async Task InitialDownloader(CancellationToken cancellationToken, para /// A task that represents the asynchronous download operation. 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); diff --git a/src/Downloader/Bandwidth.cs b/src/Downloader/Bandwidth.cs index b98a08b..c7cd534 100644 --- a/src/Downloader/Bandwidth.cs +++ b/src/Downloader/Bandwidth.cs @@ -3,6 +3,9 @@ namespace Downloader; +/// +/// Represents a class for calculating and managing bandwidth usage during a download operation. +/// public class Bandwidth { private const double OneSecond = 1000; // millisecond @@ -10,16 +13,35 @@ public class Bandwidth private int _lastSecondCheckpoint; private long _lastTransferredBytesCount; private int _speedRetrieveTime; + + /// + /// Gets the current download speed in bytes per second. + /// public double Speed { get; private set; } + + /// + /// Gets the average download speed in bytes per second. + /// public double AverageSpeed { get; private set; } + + /// + /// Gets or sets the bandwidth limit in bytes per second. + /// public long BandwidthLimit { get; set; } + /// + /// Initializes a new instance of the class with default settings. + /// public Bandwidth() { BandwidthLimit = long.MaxValue; Reset(); } + /// + /// Calculates the current download speed based on the received bytes count. + /// + /// The number of bytes received since the last calculation. public void CalculateSpeed(long receivedBytesCount) { int elapsedTime = Environment.TickCount - _lastSecondCheckpoint + 1; @@ -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); } } + /// + /// Retrieves and resets the speed retrieve time. + /// + /// The speed retrieve time in milliseconds. public int PopSpeedRetrieveTime() { return Interlocked.Exchange(ref _speedRetrieveTime, 0); } + /// + /// Resets the bandwidth calculation. + /// public void Reset() { SecondCheckpoint(); @@ -54,9 +83,12 @@ public void Reset() AverageSpeed = 0; } + /// + /// Sets the last second checkpoint to the current time and resets the transferred bytes count. + /// private void SecondCheckpoint() { Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount); Interlocked.Exchange(ref _lastTransferredBytesCount, 0); } -} +} \ No newline at end of file diff --git a/src/Downloader/Chunk.cs b/src/Downloader/Chunk.cs index 7ecd3a6..99b74a4 100644 --- a/src/Downloader/Chunk.cs +++ b/src/Downloader/Chunk.cs @@ -3,88 +3,106 @@ namespace Downloader; /// -/// Chunk data structure +/// Represents a chunk of data in a file download operation. /// public class Chunk { /// - /// Chunk unique identity name + /// Gets or sets the unique identifier for the chunk. /// public string Id { get; set; } - + /// - /// Start offset of the chunk in the file bytes + /// Gets or sets the start offset of the chunk in the file bytes. /// public long Start { get; set; } /// - /// End offset of the chunk in the file bytes + /// Gets or sets the end offset of the chunk in the file bytes. /// public long End { get; set; } /// - /// Current write offset of the chunk + /// Gets or sets the current write offset of the chunk. /// public long Position { get; set; } /// - /// How many times to try again after the error + /// Gets or sets the maximum number of times to try again after an error. /// public int MaxTryAgainOnFailover { get; set; } /// - /// 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. /// public int Timeout { get; set; } /// - /// How many times has downloading the chunk failed? + /// Gets the number of times downloading the chunk has failed. /// public int FailoverCount { get; private set; } /// - /// 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. /// public long Length => End - Start + 1; /// - /// 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. /// public long EmptyLength => Length > 0 ? Length - Position : long.MaxValue; /// - /// 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. /// - public bool CanWrite => Length > 0 ? Start + Position < End : true; - + public bool CanWrite => Length <= 0 || Start + Position < End; + /// + /// Initializes a new instance of the class with default values. + /// public Chunk() { Timeout = 1000; Id = Guid.NewGuid().ToString("N"); } + /// + /// Initializes a new instance of the class with the specified start and end positions. + /// + /// The start offset of the chunk in the file bytes. + /// The end offset of the chunk in the file bytes. public Chunk(long start, long end) : this() { Start = start; End = end; } + /// + /// Determines whether the chunk can be retried on failover. + /// + /// True if the chunk can be retried; otherwise, false. public bool CanTryAgainOnFailover() { return FailoverCount++ < MaxTryAgainOnFailover; } + /// + /// Clears the chunk's position and failover count. + /// public void Clear() { Position = 0; FailoverCount = 0; } + /// + /// Determines whether the download of the chunk is completed. + /// + /// True if the download is completed; otherwise, false. public bool IsDownloadCompleted() { var isNoneEmptyFile = Length > 0; @@ -93,6 +111,10 @@ public bool IsDownloadCompleted() return isNoneEmptyFile && isChunkedFilledWithBytes; } + /// + /// Determines whether the current position of the chunk is valid. + /// + /// True if the position is valid; otherwise, false. public bool IsValidPosition() { return Length == 0 || (Position >= 0 && Position <= Length); diff --git a/src/Downloader/ChunkDownloader.cs b/src/Downloader/ChunkDownloader.cs index 0810438..22efca8 100644 --- a/src/Downloader/ChunkDownloader.cs +++ b/src/Downloader/ChunkDownloader.cs @@ -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 DownloadProgressChanged; public ChunkDownloader(Chunk chunk, DownloadConfiguration config, ConcurrentStream storage, ILogger logger = null) @@ -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)) @@ -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); } } } @@ -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}"); @@ -169,8 +170,8 @@ 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() : [] }); } @@ -178,7 +179,8 @@ internal async Task ReadStream(Stream stream, PauseToken pauseToken, Cancellatio } 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) { diff --git a/src/Downloader/ChunkHub.cs b/src/Downloader/ChunkHub.cs index 6747135..c9a08ea 100644 --- a/src/Downloader/ChunkHub.cs +++ b/src/Downloader/ChunkHub.cs @@ -2,18 +2,29 @@ namespace Downloader; +/// +/// Manages the creation and validation of chunks for a download package. +/// public class ChunkHub { private readonly DownloadConfiguration _config; - private int _chunkCount = 0; - private long _chunkSize = 0; - private long _startOffset = 0; + private int _chunkCount; + private long _chunkSize; + private long _startOffset; + /// + /// Initializes a new instance of the class with the specified configuration. + /// + /// The download configuration. public ChunkHub(DownloadConfiguration config) { _config = config; } + /// + /// Sets the file chunks for the specified download package. + /// + /// The download package to set the chunks for. public void SetFileChunks(DownloadPackage package) { Validate(package); @@ -34,6 +45,10 @@ public void SetFileChunks(DownloadPackage package) } } + /// + /// Validates the download package and sets the chunk count, chunk size, and start offset. + /// + /// The download package to validate. private void Validate(DownloadPackage package) { _chunkCount = _config.ChunkCount; @@ -57,9 +72,16 @@ private void Validate(DownloadPackage package) _chunkSize = package.TotalFileSize / _chunkCount; } + /// + /// Creates a new chunk with the specified ID, start position, and end position. + /// + /// The unique identifier for the chunk. + /// The start position of the chunk. + /// The end position of the chunk. + /// A new instance of the class. private Chunk GetChunk(string id, long start, long end) { - var chunk = new Chunk(start, end) { + Chunk chunk = new(start, end) { Id = id, MaxTryAgainOnFailover = _config.MaxTryAgainOnFailover, Timeout = _config.Timeout