diff --git a/src/Downloader/ConcurrentStream.cs b/src/Downloader/ConcurrentStream.cs
index 520f587..1a1eec9 100644
--- a/src/Downloader/ConcurrentStream.cs
+++ b/src/Downloader/ConcurrentStream.cs
@@ -6,7 +6,10 @@
namespace Downloader;
-public class ConcurrentStream : TaskStateManagement, IDisposable
+///
+/// Represents a stream that supports concurrent read and write operations with optional memory buffering.
+///
+public class ConcurrentStream : TaskStateManagement, IDisposable, IAsyncDisposable
{
private ConcurrentPacketBuffer _inputBuffer;
private volatile bool _disposed;
@@ -14,6 +17,9 @@ public class ConcurrentStream : TaskStateManagement, IDisposable
private string _path;
private CancellationTokenSource _watcherCancelSource;
+ ///
+ /// Gets or sets the path of the file associated with the stream.
+ ///
public string Path
{
get => _path;
@@ -26,6 +32,10 @@ public string Path
}
}
}
+
+ ///
+ /// Gets the data of the stream as a byte array if the stream is a MemoryStream.
+ ///
public byte[] Data
{
get
@@ -49,38 +59,86 @@ public byte[] Data
}
}
}
+
+ ///
+ /// Gets a value indicating whether the stream supports reading.
+ ///
public bool CanRead => _stream?.CanRead == true;
+
+ ///
+ /// Gets a value indicating whether the stream supports seeking.
+ ///
public bool CanSeek => _stream?.CanSeek == true;
+
+ ///
+ /// Gets a value indicating whether the stream supports writing.
+ ///
public bool CanWrite => _stream?.CanWrite == true;
+
+ ///
+ /// Gets the length of the stream in bytes.
+ ///
public long Length => _stream?.Length ?? 0;
+
+ ///
+ /// Gets or sets the current position within the stream.
+ ///
public long Position
{
get => _stream?.Position ?? 0;
set => _stream.Position = value;
}
+
+ ///
+ /// Gets or sets the maximum amount of memory, in bytes, that the stream is allowed to allocate for buffering.
+ ///
public long MaxMemoryBufferBytes
{
get => _inputBuffer.BufferSize;
set => _inputBuffer.BufferSize = value;
}
- // parameterless constructor for deserialization
- public ConcurrentStream() : this(0, null) { }
+ ///
+ /// Initializes a new instance of the class with default settings.
+ ///
+ public ConcurrentStream() : this(0) { }
+ ///
+ /// Initializes a new instance of the class with the specified logger.
+ ///
+ /// The logger to use for logging.
public ConcurrentStream(ILogger logger) : this(0, logger) { }
+ ///
+ /// Initializes a new instance of the class with the specified maximum memory buffer size and logger.
+ ///
+ /// The maximum amount of memory, in bytes, that the stream is allowed to allocate for buffering.
+ /// The logger to use for logging.
public ConcurrentStream(long maxMemoryBufferBytes = 0, ILogger logger = null) : base(logger)
{
_stream = new MemoryStream();
Initial(maxMemoryBufferBytes);
}
+ ///
+ /// Initializes a new instance of the class with the specified stream and maximum memory buffer size.
+ ///
+ /// The stream to use.
+ /// The maximum amount of memory, in bytes, that the stream is allowed to allocate for buffering.
+ /// The logger to use for logging.
public ConcurrentStream(Stream stream, long maxMemoryBufferBytes = 0, ILogger logger = null) : base(logger)
{
_stream = stream;
Initial(maxMemoryBufferBytes);
}
+ ///
+ /// Initializes a new instance of the class with the specified file path, initial size, and maximum memory buffer size.
+ ///
+ /// The path of the file to use.
+ /// The initial size of the file.
+ /// The maximum amount of memory, in bytes, that the stream is allowed to allocate for buffering.
+ /// The logger to use for logging.
public ConcurrentStream(string filename, long initSize, long maxMemoryBufferBytes = 0, ILogger logger = null) : base(logger)
{
_path = filename;
@@ -92,6 +150,11 @@ public ConcurrentStream(string filename, long initSize, long maxMemoryBufferByte
Initial(maxMemoryBufferBytes);
}
+ ///
+ /// Initializes the stream with the specified maximum memory buffer size.
+ ///
+ /// The maximum amount of memory, in bytes, that the stream is allowed to allocate for buffering.
+ /// The logger to use for logging.
private void Initial(long maxMemoryBufferBytes, ILogger logger = null)
{
_inputBuffer = new ConcurrentPacketBuffer(maxMemoryBufferBytes, logger);
@@ -106,18 +169,37 @@ private void Initial(long maxMemoryBufferBytes, ILogger logger = null)
task.Unwrap();
}
+ ///
+ /// Opens the stream for reading.
+ ///
+ /// The stream for reading.
public Stream OpenRead()
{
Seek(0, SeekOrigin.Begin);
return _stream;
}
+ ///
+ /// Reads a sequence of bytes from the stream and advances the position within the stream by the number of bytes read.
+ ///
+ /// An array of bytes to store the read data.
+ /// The zero-based byte offset in buffer at which to begin storing the data read from the stream.
+ /// The maximum number of bytes to be read from the stream.
+ /// The total number of bytes read into the buffer.
public int Read(byte[] buffer, int offset, int count)
{
var stream = OpenRead();
return stream.Read(buffer, offset, count);
}
+ ///
+ /// Writes a sequence of bytes to the stream asynchronously at the specified position.
+ ///
+ /// The position within the stream to write the data.
+ /// The data to write to the stream.
+ /// The number of bytes to write.
+ /// A value indicating whether to wait for the write operation to complete.
+ /// A task that represents the asynchronous write operation.
public async Task WriteAsync(long position, byte[] bytes, int length, bool fireAndForget = true)
{
if (bytes.Length < length)
@@ -135,6 +217,10 @@ public async Task WriteAsync(long position, byte[] bytes, int length, bool fireA
}
}
+ ///
+ /// Watches for incoming packets and writes them to the stream.
+ ///
+ /// A task that represents the asynchronous watch operation.
private async Task Watcher()
{
try
@@ -161,6 +247,12 @@ private async Task Watcher()
}
}
+ ///
+ /// Sets the position within the current stream.
+ ///
+ /// A byte offset relative to the origin parameter.
+ /// A value of type SeekOrigin indicating the reference point used to obtain the new position.
+ /// The new position within the current stream.
public long Seek(long offset, SeekOrigin origin)
{
if (offset != Position && CanSeek)
@@ -171,11 +263,20 @@ public long Seek(long offset, SeekOrigin origin)
return Position;
}
+ ///
+ /// Sets the length of the current stream.
+ ///
+ /// The desired length of the current stream in bytes.
public void SetLength(long value)
{
_stream.SetLength(value);
}
+ ///
+ /// Writes a packet to the stream.
+ ///
+ /// The packet to write.
+ /// A task that represents the asynchronous write operation.
private async Task WritePacketOnFile(Packet packet)
{
// seek with SeekOrigin.Begin is so faster than SeekOrigin.Current
@@ -184,6 +285,10 @@ private async Task WritePacketOnFile(Packet packet)
packet.Dispose();
}
+ ///
+ /// Flushes the stream asynchronously.
+ ///
+ /// A task that represents the asynchronous flush operation.
public async Task FlushAsync()
{
await _inputBuffer.WaitToComplete().ConfigureAwait(false);
@@ -196,9 +301,12 @@ public async Task FlushAsync()
GC.Collect();
}
+ ///
+ /// Releases the unmanaged resources used by the ConcurrentStream and optionally releases the managed resources.
+ ///
public void Dispose()
{
- if (_disposed == false)
+ if (!_disposed)
{
_disposed = true;
_watcherCancelSource.Cancel(); // request the cancellation
@@ -206,4 +314,19 @@ public void Dispose()
_inputBuffer.Dispose();
}
}
-}
+
+ ///
+ /// Asynchronously releases the unmanaged resources used by the ConcurrentStream.
+ ///
+ /// A task that represents the asynchronous dispose operation.
+ public async ValueTask DisposeAsync()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ await _watcherCancelSource.CancelAsync().ConfigureAwait(false); // request the cancellation
+ await _stream.DisposeAsync().ConfigureAwait(false);
+ _inputBuffer.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Downloader/Download.cs b/src/Downloader/Download.cs
index e9a1f1e..4afbc69 100644
--- a/src/Downloader/Download.cs
+++ b/src/Downloader/Download.cs
@@ -20,26 +20,26 @@ internal class Download : IDownload
public event EventHandler ChunkDownloadProgressChanged
{
- add { _downloadService.ChunkDownloadProgressChanged += value; }
- remove { _downloadService.ChunkDownloadProgressChanged -= value; }
+ add => _downloadService.ChunkDownloadProgressChanged += value;
+ remove => _downloadService.ChunkDownloadProgressChanged -= value;
}
public event EventHandler DownloadFileCompleted
{
- add { _downloadService.DownloadFileCompleted += value; }
- remove { _downloadService.DownloadFileCompleted -= value; }
+ add => _downloadService.DownloadFileCompleted += value;
+ remove => _downloadService.DownloadFileCompleted -= value;
}
public event EventHandler DownloadProgressChanged
{
- add { _downloadService.DownloadProgressChanged += value; }
- remove { _downloadService.DownloadProgressChanged -= value; }
+ add => _downloadService.DownloadProgressChanged += value;
+ remove => _downloadService.DownloadProgressChanged -= value;
}
public event EventHandler DownloadStarted
{
- add { _downloadService.DownloadStarted += value; }
- remove { _downloadService.DownloadStarted -= value; }
+ add => _downloadService.DownloadStarted += value;
+ remove => _downloadService.DownloadStarted -= value;
}
public Download(string url, string path, string filename, DownloadConfiguration configuration)
@@ -72,26 +72,26 @@ public async Task StartAsync(CancellationToken cancellationToken = defau
{
return await _downloadService.DownloadFileTaskAsync(Url, cancellationToken).ConfigureAwait(false);
}
- else if (string.IsNullOrWhiteSpace(Filename))
- {
- await _downloadService.DownloadFileTaskAsync(Url, new DirectoryInfo(Folder), cancellationToken).ConfigureAwait(false);
- return null;
- }
- else
+
+ if (string.IsNullOrWhiteSpace(Filename))
{
- // with Folder and Filename
- await _downloadService.DownloadFileTaskAsync(Url, Path.Combine(Folder, Filename), cancellationToken).ConfigureAwait(false);
+ await _downloadService.DownloadFileTaskAsync(Url, new DirectoryInfo(Folder!), cancellationToken)
+ .ConfigureAwait(false);
return null;
}
+
+ // with Folder and Filename
+ await _downloadService.DownloadFileTaskAsync(Url, Path.Combine(Folder!, Filename), cancellationToken)
+ .ConfigureAwait(false);
+ return null;
}
- else if(string.IsNullOrWhiteSpace(Url))
+
+ if (string.IsNullOrWhiteSpace(Url))
{
return await _downloadService.DownloadFileTaskAsync(Package, cancellationToken).ConfigureAwait(false);
}
- else
- {
- return await _downloadService.DownloadFileTaskAsync(Package, Url, cancellationToken).ConfigureAwait(false);
- }
+
+ return await _downloadService.DownloadFileTaskAsync(Package, Url, cancellationToken).ConfigureAwait(false);
}
public void Stop()
@@ -123,9 +123,15 @@ public override int GetHashCode()
return hashCode;
}
- public async void Dispose()
+ public async ValueTask DisposeAsync()
{
await _downloadService.Clear().ConfigureAwait(false);
Package = null;
}
-}
+
+ public void Dispose()
+ {
+ _downloadService.Clear().Wait();
+ Package = null;
+ }
+}
\ No newline at end of file
diff --git a/src/Downloader/DownloadPackage.cs b/src/Downloader/DownloadPackage.cs
index 415891d..0041c5d 100644
--- a/src/Downloader/DownloadPackage.cs
+++ b/src/Downloader/DownloadPackage.cs
@@ -5,21 +5,74 @@
namespace Downloader;
-public class DownloadPackage : IDisposable
+///
+/// Represents a package containing information about a download operation.
+///
+public class DownloadPackage : IDisposable, IAsyncDisposable
{
+ ///
+ /// Gets or sets a value indicating whether the package is currently being saved.
+ ///
public bool IsSaving { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the save operation is complete.
+ ///
public bool IsSaveComplete { get; set; }
+
+ ///
+ /// Gets or sets the progress of the save operation.
+ ///
public double SaveProgress { get; set; }
+
+ ///
+ /// Gets or sets the status of the download operation.
+ ///
public DownloadStatus Status { get; set; } = DownloadStatus.None;
+
+ ///
+ /// Gets or sets the URLs from which the file is being downloaded.
+ ///
public string[] Urls { get; set; }
+
+ ///
+ /// Gets or sets the total size of the file to be downloaded.
+ ///
public long TotalFileSize { get; set; }
+
+ ///
+ /// Gets or sets the name of the file to be saved.
+ ///
public string FileName { get; set; }
+
+ ///
+ /// Gets or sets the chunks of the file being downloaded.
+ ///
public Chunk[] Chunks { get; set; }
+
+ ///
+ /// Gets the total size of the received bytes.
+ ///
public long ReceivedBytesSize => Chunks?.Sum(chunk => chunk.Position) ?? 0;
+
+ ///
+ /// Gets or sets a value indicating whether the download supports range requests.
+ ///
public bool IsSupportDownloadInRange { get; set; } = true;
+
+ ///
+ /// Gets a value indicating whether the download is being stored in memory.
+ ///
public bool InMemoryStream => string.IsNullOrWhiteSpace(FileName);
+
+ ///
+ /// Gets or sets the storage for the download.
+ ///
public ConcurrentStream Storage { get; set; }
+ ///
+ /// Clears the chunks and resets the package.
+ ///
public void Clear()
{
if (Chunks != null)
@@ -27,15 +80,23 @@ public void Clear()
foreach (Chunk chunk in Chunks)
chunk.Clear();
}
+
Chunks = null;
}
+ ///
+ /// Flushes the storage asynchronously.
+ ///
+ /// A task that represents the asynchronous flush operation.
public async Task FlushAsync()
{
if (Storage?.CanWrite == true)
await Storage.FlushAsync().ConfigureAwait(false);
}
+ ///
+ /// Flushes the storage synchronously.
+ ///
[Obsolete("This method has been deprecated. Please use FlushAsync instead.")]
public void Flush()
{
@@ -43,6 +104,9 @@ public void Flush()
Storage?.FlushAsync().Wait();
}
+ ///
+ /// Validates the chunks and ensures they are in the correct position.
+ ///
public void Validate()
{
foreach (var chunk in Chunks)
@@ -61,17 +125,37 @@ public void Validate()
}
}
+ ///
+ /// Builds the storage for the download package.
+ ///
+ /// Indicates whether to reserve the file size.
+ /// The maximum size of the memory buffer in bytes.
+ /// The logger to use for logging.
public void BuildStorage(bool reserveFileSize, long maxMemoryBufferBytes = 0, ILogger logger = null)
{
- if (string.IsNullOrWhiteSpace(FileName))
- Storage = new ConcurrentStream(maxMemoryBufferBytes, logger);
- else
- Storage = new ConcurrentStream(FileName, reserveFileSize ? TotalFileSize : 0, maxMemoryBufferBytes, logger);
+ Storage = string.IsNullOrWhiteSpace(FileName)
+ ? new ConcurrentStream(maxMemoryBufferBytes, logger)
+ : new ConcurrentStream(FileName, reserveFileSize ? TotalFileSize : 0, maxMemoryBufferBytes, logger);
}
+ ///
+ /// Disposes of the download package, clearing the chunks and disposing of the storage.
+ ///
public void Dispose()
{
Clear();
Storage?.Dispose();
}
+
+ ///
+ /// Disposes of the download package, clearing the chunks and disposing of the storage.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ Clear();
+ if (Storage is not null)
+ {
+ await Storage.DisposeAsync().ConfigureAwait(false);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Downloader/IDownload.cs b/src/Downloader/IDownload.cs
index 742d60f..3306547 100644
--- a/src/Downloader/IDownload.cs
+++ b/src/Downloader/IDownload.cs
@@ -1,28 +1,90 @@
-using System.ComponentModel;
-using System;
-using System.Threading.Tasks;
+using System;
+using System.ComponentModel;
using System.IO;
using System.Threading;
+using System.Threading.Tasks;
namespace Downloader;
-public interface IDownload : IDisposable
+///
+/// Represents an interface for managing file downloads.
+///
+public interface IDownload : IDisposable, IAsyncDisposable
{
+ ///
+ /// Gets the URL of the file to be downloaded.
+ ///
public string Url { get; }
+
+ ///
+ /// Gets the folder where the downloaded file will be saved.
+ ///
public string Folder { get; }
+
+ ///
+ /// Gets the name of the file to be saved.
+ ///
public string Filename { get; }
+
+ ///
+ /// Gets the size of the downloaded portion of the file.
+ ///
public long DownloadedFileSize { get; }
+
+ ///
+ /// Gets the total size of the file to be downloaded.
+ ///
public long TotalFileSize { get; }
+
+ ///
+ /// Gets the download package containing information about the download.
+ ///
public DownloadPackage Package { get; }
+
+ ///
+ /// Gets the current status of the download.
+ ///
public DownloadStatus Status { get; }
+ ///
+ /// Occurs when the progress of a chunk download changes.
+ ///
public event EventHandler ChunkDownloadProgressChanged;
+
+ ///
+ /// Occurs when the download file operation is completed.
+ ///
public event EventHandler DownloadFileCompleted;
+
+ ///
+ /// Occurs when the overall download progress changes.
+ ///
public event EventHandler DownloadProgressChanged;
+
+ ///
+ /// Occurs when the download operation starts.
+ ///
public event EventHandler DownloadStarted;
+ ///
+ /// Starts the download operation asynchronously.
+ ///
+ /// 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 StartAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Stops the download operation.
+ ///
public void Stop();
+
+ ///
+ /// Pauses the download operation.
+ ///
public void Pause();
+
+ ///
+ /// Resumes the paused download operation.
+ ///
public void Resume();
-}
+}
\ No newline at end of file