rdt-client/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs
2026-02-11 19:44:49 -07:00

300 lines
8.4 KiB
C#

using Aria2NET;
using Serilog;
namespace RdtClient.Service.Services.Downloaders;
public class Aria2cDownloader : IDownloader
{
private const Int32 RetryCount = 5;
private readonly Aria2NetClient _aria2NetClient;
private readonly String _filePath;
private readonly ILogger _logger;
private readonly String _remotePath;
private readonly String _uri;
private String? _gid;
public Aria2cDownloader(String? gid, String uri, String filePath, String downloadPath, String? category)
{
_logger = Log.ForContext<Aria2cDownloader>();
_logger.Debug($"Instantiated new Aria2c Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}");
_gid = gid;
_uri = uri;
_filePath = filePath;
if (!String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.Aria2cDownloadPath))
{
_remotePath = Path.Combine(Settings.Get.DownloadClient.Aria2cDownloadPath, downloadPath).Replace('\\', '/');
if (!String.IsNullOrWhiteSpace(category))
{
_remotePath = Path.Combine(Settings.Get.DownloadClient.Aria2cDownloadPath, category, downloadPath).Replace('\\', '/');
}
}
else
{
_remotePath = _filePath;
}
var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};
_aria2NetClient = new(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10);
}
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
public async Task<String> Download()
{
var path = Path.GetDirectoryName(_remotePath) ?? throw new($"Invalid file path {_filePath}");
var fileName = Path.GetFileName(_filePath);
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath} (on aria2: {_remotePath}), fileName: {fileName}");
if (String.IsNullOrWhiteSpace(_gid))
{
var isAlreadyAdded = await CheckIfAdded();
if (isAlreadyAdded)
{
throw new($"The download link {_uri} has already been added to Aria2");
}
}
var retryCount = 0;
while (true)
{
try
{
if (_gid != null)
{
try
{
await _aria2NetClient.TellStatusAsync(_gid);
return _gid;
}
catch
{
_gid = null;
}
}
_gid ??= await _aria2NetClient.AddUriAsync([
_uri
],
new Dictionary<String, Object>
{
{
"dir", path.Replace('\\', '/')
},
{
"out", fileName
}
});
_logger.Debug($"Added download to Aria2, received ID {_gid}");
await _aria2NetClient.TellStatusAsync(_gid);
_logger.Debug($"Download with ID {_gid} found in Aria2");
return _gid;
}
catch (Exception ex)
{
if (retryCount >= RetryCount)
{
throw;
}
_logger.Debug($"Error starting download: {ex.Message}. Retrying {retryCount}/{RetryCount}");
await Task.Delay(retryCount * 1000);
retryCount++;
}
}
}
public async Task Cancel()
{
await Remove();
}
public async Task Pause()
{
_logger.Debug($"Pausing download {_uri} {_gid}");
if (String.IsNullOrWhiteSpace(_gid))
{
return;
}
try
{
await _aria2NetClient.PauseAsync(_gid);
}
catch
{
// ignored
}
}
public async Task Resume()
{
_logger.Debug($"Resuming download {_uri} {_gid}");
if (String.IsNullOrWhiteSpace(_gid))
{
return;
}
try
{
await _aria2NetClient.UnpauseAsync(_gid);
}
catch
{
// ignored
}
}
public async Task Update(IEnumerable<DownloadStatusResult> allDownloads)
{
if (_gid == null)
{
return;
}
var download = allDownloads.FirstOrDefault(m => m.Gid == _gid);
if (download == null)
{
DownloadComplete?.Invoke(this,
new()
{
Error = $"Download was not found in Aria2"
});
return;
}
if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error")
{
await Remove();
DownloadComplete?.Invoke(this,
new()
{
Error = $"{download.ErrorCode}: {download.ErrorMessage}"
});
return;
}
if (download.Status == "complete" || download.Status == "removed")
{
_logger.Debug($"Aria2 download found as complete {_gid}");
await Remove();
var retryCount = 0;
while (true)
{
DownloadProgress?.Invoke(this,
new()
{
BytesDone = download.CompletedLength,
BytesTotal = download.TotalLength,
Speed = download.DownloadSpeed
});
if (retryCount >= 10)
{
DownloadComplete?.Invoke(this,
new()
{
Error = $"File not found at {_filePath} (on aria2 {_remotePath})"
});
break;
}
if (File.Exists(_filePath))
{
DownloadComplete?.Invoke(this, new());
break;
}
await Task.Delay(1000 * retryCount);
retryCount++;
}
return;
}
DownloadProgress?.Invoke(this,
new()
{
BytesDone = download.CompletedLength,
BytesTotal = download.TotalLength,
Speed = download.DownloadSpeed
});
}
private async Task Remove()
{
if (String.IsNullOrWhiteSpace(_gid))
{
return;
}
_logger.Debug($"Remove download {_uri} {_gid} from Aria2");
try
{
await _aria2NetClient.ForceRemoveAsync(_gid);
}
catch
{
// ignored
}
try
{
await _aria2NetClient.RemoveDownloadResultAsync(_gid);
}
catch
{
// ignored
}
}
private async Task<Boolean> CheckIfAdded()
{
var allDownloads = await _aria2NetClient.TellAllAsync();
foreach (var download in allDownloads)
{
foreach (var file in download.Files)
{
if (file.Uris.Any(uri => uri.Uri == _uri))
{
return true;
}
}
}
return false;
}
}