Redesigned the DiscordBotCore by splitting it into multiple projects. Created a WebUI and preparing to remove the DiscordBot application
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
25
DiscordBotCore.Networking/FileDownloader.cs
Normal file
25
DiscordBotCore.Networking/FileDownloader.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using DiscordBotCore.Networking.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Networking;
|
||||
|
||||
public class FileDownloader
|
||||
{
|
||||
private readonly string _DownloadUrl;
|
||||
private readonly string _DownloadLocation;
|
||||
|
||||
private readonly HttpClient _HttpClient;
|
||||
|
||||
public FileDownloader(string downloadUrl, string downloadLocation)
|
||||
{
|
||||
_DownloadUrl = downloadUrl;
|
||||
_DownloadLocation = downloadLocation;
|
||||
|
||||
_HttpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task DownloadFile(Action<float> progressCallback)
|
||||
{
|
||||
await using var fileStream = new FileStream(_DownloadLocation, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await _HttpClient.DownloadFileAsync(_DownloadUrl, fileStream, new Progress<float>(progressCallback));
|
||||
}
|
||||
}
|
||||
91
DiscordBotCore.Networking/Helpers/OnlineFunctions.cs
Normal file
91
DiscordBotCore.Networking/Helpers/OnlineFunctions.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
namespace DiscordBotCore.Networking.Helpers;
|
||||
|
||||
internal static class OnlineFunctions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Copy one Stream to another <see langword="async" />
|
||||
/// </summary>
|
||||
/// <param name="stream">The base stream</param>
|
||||
/// <param name="destination">The destination stream</param>
|
||||
/// <param name="bufferSize">The buffer to read</param>
|
||||
/// <param name="progress">The progress</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream" /> is empty</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
|
||||
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
|
||||
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
|
||||
private static async Task CopyToOtherStreamAsync(
|
||||
this Stream stream, Stream destination, int bufferSize,
|
||||
IProgress<long>? progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable.");
|
||||
if (!destination.CanWrite)
|
||||
throw new ArgumentException("Destination stream is not writable", nameof(destination));
|
||||
|
||||
var buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a <see cref="Stream" /> and saves it to another <see cref="Stream" />.
|
||||
/// </summary>
|
||||
/// <param name="client">The <see cref="HttpClient" /> that is used to download the file</param>
|
||||
/// <param name="url">The url to the file</param>
|
||||
/// <param name="destination">The <see cref="Stream" /> to save the downloaded data</param>
|
||||
/// <param name="progress">The <see cref="IProgress{T}" /> that is used to track the download progress</param>
|
||||
/// <param name="cancellation">The cancellation token</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task DownloadFileAsync(
|
||||
this HttpClient client, string url, Stream destination,
|
||||
IProgress<float>? progress = null,
|
||||
IProgress<long>? downloadedBytes = null, int bufferSize = 81920,
|
||||
CancellationToken cancellation = default)
|
||||
{
|
||||
using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellation))
|
||||
{
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
using (var download = await response.Content.ReadAsStreamAsync(cancellation))
|
||||
{
|
||||
// Ignore progress reporting when no progress reporter was
|
||||
// passed or when the content length is unknown
|
||||
if (progress == null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination, cancellation);
|
||||
if (!contentLength.HasValue)
|
||||
progress?.Report(100f);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
|
||||
// total ... 100%
|
||||
// downloaded ... x%
|
||||
// x = downloaded * 100 / total => x = downloaded / total * 100
|
||||
var relativeProgress = new Progress<long>(totalBytesDownloaded =>
|
||||
{
|
||||
progress?.Report(totalBytesDownloaded / (float)contentLength.Value * 100);
|
||||
downloadedBytes?.Report(totalBytesDownloaded);
|
||||
}
|
||||
);
|
||||
|
||||
// Use extension method to report progress while downloading
|
||||
await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation);
|
||||
progress.Report(100f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
DiscordBotCore.Networking/ParallelDownloadExecutor.cs
Normal file
89
DiscordBotCore.Networking/ParallelDownloadExecutor.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using DiscordBotCore.Networking.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Networking;
|
||||
|
||||
public class ParallelDownloadExecutor
|
||||
{
|
||||
private readonly List<Task> _listOfTasks;
|
||||
private readonly HttpClient _httpClient;
|
||||
private Action? OnFinishAction { get; set; }
|
||||
|
||||
public ParallelDownloadExecutor(List<Task> listOfTasks)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_listOfTasks = listOfTasks;
|
||||
}
|
||||
|
||||
public ParallelDownloadExecutor()
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_listOfTasks = new List<Task>();
|
||||
}
|
||||
|
||||
public async Task StartTasks()
|
||||
{
|
||||
await Task.WhenAll(_listOfTasks);
|
||||
OnFinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public async Task ExecuteAllTasks(int maxDegreeOfParallelism = 4)
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(maxDegreeOfParallelism);
|
||||
|
||||
var tasks = _listOfTasks.Select(async task =>
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
await task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
OnFinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public void SetFinishAction(Action action)
|
||||
{
|
||||
OnFinishAction = action;
|
||||
}
|
||||
|
||||
public void AddTask(string downloadLink, string downloadLocation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadLink) || string.IsNullOrEmpty(downloadLocation))
|
||||
throw new ArgumentException("Download link or location cannot be null or empty.");
|
||||
|
||||
if (Directory.Exists(Path.GetDirectoryName(downloadLocation)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(downloadLocation));
|
||||
}
|
||||
|
||||
var task = CreateDownloadTask(downloadLink, downloadLocation, null);
|
||||
_listOfTasks.Add(task);
|
||||
}
|
||||
|
||||
public void AddTask(string downloadLink, string downloadLocation, Action<float> progressCallback)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadLink) || string.IsNullOrEmpty(downloadLocation))
|
||||
throw new ArgumentException("Download link or location cannot be null or empty.");
|
||||
|
||||
if (Directory.Exists(Path.GetDirectoryName(downloadLocation)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(downloadLocation));
|
||||
}
|
||||
|
||||
var task = CreateDownloadTask(downloadLink, downloadLocation, new Progress<float>(progressCallback));
|
||||
_listOfTasks.Add(task);
|
||||
}
|
||||
|
||||
private Task CreateDownloadTask(string downloadLink, string downloadLocation, IProgress<float> progress)
|
||||
{
|
||||
var fileStream = new FileStream(downloadLocation, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
return _httpClient.DownloadFileAsync(downloadLink, fileStream, progress);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user