Add fixes for the symlink downloader from https://github.com/Pukabyte/rdtclient .

This commit is contained in:
Roger Far 2024-04-06 14:52:11 -06:00
parent 45c805eb00
commit e5dfb0e027
11 changed files with 218 additions and 123 deletions

View file

@ -186,6 +186,24 @@ public class DownloadData(DataContext dataContext)
await TorrentData.VoidCache(); await TorrentData.VoidCache();
} }
public async Task UpdateErrors(Dictionary<Guid, String> downloadIds)
{
foreach (var entry in downloadIds)
{
var dbDownload = await dataContext.Downloads
.FirstOrDefaultAsync(m => m.DownloadId == entry.Key);
if (dbDownload == null)
{
continue;
}
dbDownload.Error = entry.Value;
}
await dataContext.SaveChangesAsync();
}
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
{ {
var dbDownload = await dataContext.Downloads var dbDownload = await dataContext.Downloads
@ -218,6 +236,22 @@ public class DownloadData(DataContext dataContext)
await dataContext.SaveChangesAsync(); await dataContext.SaveChangesAsync();
} }
public async Task UpdateRemoteIds(Dictionary<Guid, String> remoteIds)
{
foreach (var entry in remoteIds)
{
var dbDownload = await dataContext.Downloads.FirstOrDefaultAsync(m => m.DownloadId == entry.Key);
if (dbDownload == null)
{
continue;
}
dbDownload.RemoteId = entry.Value;
}
await dataContext.SaveChangesAsync();
}
public async Task DeleteForTorrent(Guid torrentId) public async Task DeleteForTorrent(Guid torrentId)
{ {
var downloads = await dataContext.Downloads var downloads = await dataContext.Downloads
@ -234,12 +268,8 @@ public class DownloadData(DataContext dataContext)
public async Task Reset(Guid downloadId) public async Task Reset(Guid downloadId)
{ {
var dbDownload = await dataContext.Downloads var dbDownload = await dataContext.Downloads
.FirstOrDefaultAsync(m => m.DownloadId == downloadId); .FirstOrDefaultAsync(m => m.DownloadId == downloadId)
?? throw new Exception($"Cannot find download with ID {downloadId}");
if (dbDownload == null)
{
throw new Exception($"Cannot find download with ID {downloadId}");
}
dbDownload.RetryCount = 0; dbDownload.RetryCount = 0;
dbDownload.Link = null; dbDownload.Link = null;

View file

@ -24,11 +24,11 @@ public class RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory logge
requestLog += $", QueryString: {context.Request.QueryString}"; requestLog += $", QueryString: {context.Request.QueryString}";
} }
if (context.Request.HasFormContentType && context.Request.Form.Any()) if (context.Request.HasFormContentType && context.Request.Form.Count == 0)
{ {
requestLog += $", Form: {String.Join(", ", context.Request.Form.Select(f => $"{f.Key}: {f.Value}"))}"; requestLog += $", Form: {String.Join(", ", context.Request.Form.Select(f => $"{f.Key}: {f.Value}"))}";
} }
else if (context.Request.ContentType?.ToLower().Contains("application/json") == true) else if (context.Request.ContentType?.Contains("application/json", StringComparison.CurrentCultureIgnoreCase) == true)
{ {
var body = await ReadRequestBodyAsync(context.Request); var body = await ReadRequestBodyAsync(context.Request);
requestLog += $", Body: {body}"; requestLog += $", Body: {body}";

View file

@ -6,11 +6,6 @@ namespace RdtClient.Service.Services;
public class DownloadClient(Download download, Torrent torrent, String destinationPath) public class DownloadClient(Download download, Torrent torrent, String destinationPath)
{ {
private readonly String _destinationPath = destinationPath;
private readonly Download _download = download;
private readonly Torrent _torrent = torrent;
public IDownloader? Downloader; public IDownloader? Downloader;
public Data.Enums.DownloadClient Type { get; private set; } public Data.Enums.DownloadClient Type { get; private set; }
@ -23,7 +18,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
public Int64 BytesTotal { get; private set; } public Int64 BytesTotal { get; private set; }
public Int64 BytesDone { get; private set; } public Int64 BytesDone { get; private set; }
public async Task<String?> Start() public async Task<String> Start()
{ {
BytesDone = 0; BytesDone = 0;
BytesTotal = 0; BytesTotal = 0;
@ -31,13 +26,13 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
try try
{ {
if (_download.Link == null) if (download.Link == null)
{ {
throw new Exception($"Invalid download link"); throw new Exception($"Invalid download link");
} }
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download); var filePath = DownloadHelper.GetDownloadPath(destinationPath, torrent, download);
var downloadPath = DownloadHelper.GetDownloadPath(_torrent, _download); var downloadPath = DownloadHelper.GetDownloadPath(torrent, download);
if (filePath == null || downloadPath == null) if (filePath == null || downloadPath == null)
{ {
@ -50,10 +45,10 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
Downloader = Settings.Get.DownloadClient.Client switch Downloader = Settings.Get.DownloadClient.Client switch
{ {
Data.Enums.DownloadClient.Internal => new InternalDownloader(_download.Link, filePath), Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath),
Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(_download.Link, filePath), Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath),
Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(_download.RemoteId, _download.Link, filePath, downloadPath), Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath, downloadPath),
Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(_download.Link, filePath), Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath),
_ => throw new Exception($"Unknown download client {Settings.Get.DownloadClient}") _ => throw new Exception($"Unknown download client {Settings.Get.DownloadClient}")
}; };
@ -83,10 +78,9 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
await Downloader.Cancel(); await Downloader.Cancel();
} }
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
Finished = true; Finished = true;
return null; throw new($"An unexpected error occurred preparing download {download.Link} for torrent {torrent.RdName}: {ex.Message}");
} }
} }

View file

@ -45,7 +45,7 @@ public class Aria2cDownloader : IDownloader
_aria2NetClient = new Aria2NetClient(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10); _aria2NetClient = new Aria2NetClient(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10);
} }
public async Task<String?> Download() public async Task<String> Download()
{ {
var path = Path.GetDirectoryName(_remotePath) ?? throw new Exception($"Invalid file path {_filePath}"); var path = Path.GetDirectoryName(_remotePath) ?? throw new Exception($"Invalid file path {_filePath}");
var fileName = Path.GetFileName(_filePath); var fileName = Path.GetFileName(_filePath);

View file

@ -97,7 +97,7 @@ public class BezzadDownloader : IDownloader
}; };
} }
public Task<String?> Download() public Task<String> Download()
{ {
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
@ -108,7 +108,7 @@ public class BezzadDownloader : IDownloader
_ = Task.Run(StartTimer); _ = Task.Run(StartTimer);
return Task.FromResult<String?>(Guid.NewGuid().ToString()); return Task.FromResult(Guid.NewGuid().ToString());
} }
public Task Cancel() public Task Cancel()

View file

@ -16,7 +16,7 @@ public interface IDownloader
{ {
event EventHandler<DownloadCompleteEventArgs>? DownloadComplete; event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
event EventHandler<DownloadProgressEventArgs>? DownloadProgress; event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
Task<String?> Download(); Task<String> Download();
Task Cancel(); Task Cancel();
Task Pause(); Task Pause();
Task Resume(); Task Resume();

View file

@ -65,7 +65,7 @@ public class InternalDownloader : IDownloader
}; };
} }
public async Task<String?> Download() public async Task<String> Download()
{ {
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");

View file

@ -2,76 +2,96 @@
namespace RdtClient.Service.Services.Downloaders; namespace RdtClient.Service.Services.Downloaders;
public class SymlinkDownloader : IDownloader public class SymlinkDownloader(String uri, String path) : IDownloader
{ {
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete; public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress; public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
private readonly String _filePath;
private readonly String _uri;
private readonly CancellationTokenSource _cancellationToken = new(); private readonly CancellationTokenSource _cancellationToken = new();
private readonly ILogger _logger; private readonly ILogger _logger = Log.ForContext<SymlinkDownloader>();
public SymlinkDownloader(String uri, String filePath) public async Task<String> Download()
{ {
_logger = Log.ForContext<SymlinkDownloader>(); _logger.Debug($"Starting symlink resolving of {uri}, writing to path: {path}");
_logger.Debug($"Instantiated new Symlink Downloader for URI {uri} to filePath {filePath}");
_uri = uri; var filePath = new DirectoryInfo(path);
_filePath = filePath;
}
public Task<String?> Download() var fileName = filePath.Name;
{ var fileExtension = filePath.Extension;
_logger.Debug($"Starting symlink resolving of {_uri}, writing to path: {_filePath}"); var directoryName = Path.GetDirectoryName(filePath.FullName) ?? throw new($"Cannot get directory name for file {filePath.FullName}");
var fileDirectory = Path.GetFileName(directoryName) ?? throw new($"Cannot get directory name for file {directoryName}");
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName) ?? throw new($"Cannot get directory name for file {fileName}");
var fileDirectoryWithoutExtension = Path.GetFileNameWithoutExtension(fileDirectory) ?? throw new($"Cannot get directory name for file {fileDirectory}");
var fileName = Path.GetFileName(_filePath); String[] folders =
[
fileNameWithoutExtension,
fileDirectoryWithoutExtension,
fileName,
fileDirectory
];
DownloadProgress?.Invoke(this, new DownloadProgressEventArgs List<String> unWantedExtensions =
[
"zip",
"rar",
"tar"
];
if (unWantedExtensions.Any(m => fileExtension == m))
{ {
BytesDone = 0, throw new($"Cant handle compressed files with symlink downloader");
BytesTotal = 0, }
Speed = 0
});
_logger.Debug($"Searching {Settings.Get.DownloadClient.RcloneMountPath} for {fileName}"); DownloadProgress?.Invoke(this, new()
{
BytesDone = 0,
BytesTotal = 0,
Speed = 0
});
// Recursively search for the fileName in the rclone mount location. FileInfo? file = null;
var foundFiles = Directory.GetFiles(Settings.Get.DownloadClient.RcloneMountPath, fileName, SearchOption.AllDirectories).ToList();
if (foundFiles.Count > 0) var tries = 1;
while (file == null && tries <= 10)
{ {
if (foundFiles.Count > 1) _logger.Debug($"Searching {Settings.Get.DownloadClient.RcloneMountPath} for {fileName} (attempt #{tries})...");
var dirInfo = new DirectoryInfo(Settings.Get.DownloadClient.RcloneMountPath);
file = dirInfo.EnumerateDirectories().FirstOrDefault(dir => folders.Contains(dir.Name))?.EnumerateFiles().FirstOrDefault(x => x.Name == fileName);
if (file == null)
{ {
_logger.Warning($"Found {foundFiles.Count} files named {fileName}"); await Task.Delay(1000 * tries);
}
// Assume first matching filename is the one we want. tries++;
var actualFilePath = foundFiles.First();
var result = TryCreateSymbolicLink(actualFilePath, _filePath);
if (result)
{
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
_logger.Information($"File {fileName} found on {Settings.Get.DownloadClient.RcloneMountPath} at {actualFilePath}");
return Task.FromResult<String?>(actualFilePath);
} }
} }
_logger.Information($"File {fileName} not found on {Settings.Get.DownloadClient.RcloneMountPath}!"); if (file == null)
{
throw new("Could not find file from rclone mount!");
}
_logger.Debug($"Found {file.FullName} after #{tries} attempts");
var result = TryCreateSymbolicLink(file.FullName, filePath.FullName);
if (!result)
{
throw new("Could not find file from rclone mount!");
}
DownloadComplete?.Invoke(this, new());
return file.FullName;
// Return null and try again next cycle.
return Task.FromResult<String?>(null);
} }
public Task Cancel() public Task Cancel()
{ {
_logger.Debug($"Cancelling download {_uri}");
_cancellationToken.Cancel(false); _cancellationToken.Cancel(false);
return Task.CompletedTask; return Task.CompletedTask;
@ -91,22 +111,23 @@ public class SymlinkDownloader : IDownloader
{ {
try try
{ {
_logger.Information($"Creating symbolic link from {sourcePath} to {symlinkPath}");
File.CreateSymbolicLink(symlinkPath, sourcePath); File.CreateSymbolicLink(symlinkPath, sourcePath);
if (File.Exists(symlinkPath)) if (File.Exists(symlinkPath)) // Double-check that the link was created
{ {
_logger.Information($"Created symbolic link from {sourcePath} to {symlinkPath}"); _logger.Information($"Created symbolic link from {sourcePath} to {symlinkPath}");
return true; return true;
} }
_logger.Error($"Failed to create symbolic link from {sourcePath} to {symlinkPath}"); _logger.Error($"Failed to create symbolic link from {sourcePath} to {symlinkPath}");
return false; return false;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error($"Error creating symbolic link from {sourcePath} to {symlinkPath}: {ex.Message}"); _logger.Error($"Error creating symbolic link from {sourcePath} to {symlinkPath}: {ex.Message}");
return false; return false;
} }
} }

View file

@ -65,6 +65,11 @@ public class Downloads(DownloadData downloadData)
await downloadData.UpdateError(downloadId, error); await downloadData.UpdateError(downloadId, error);
} }
public async Task UpdateErrors(Dictionary<Guid, String> downloadIds)
{
await downloadData.UpdateErrors(downloadIds);
}
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
{ {
await downloadData.UpdateRetryCount(downloadId, retryCount); await downloadData.UpdateRetryCount(downloadId, retryCount);
@ -75,6 +80,11 @@ public class Downloads(DownloadData downloadData)
await downloadData.UpdateRemoteId(downloadId, remoteId); await downloadData.UpdateRemoteId(downloadId, remoteId);
} }
public async Task UpdateRemoteIds(Dictionary<Guid, String> downloadIds)
{
await downloadData.UpdateRemoteIds(downloadIds);
}
public async Task DeleteForTorrent(Guid torrentId) public async Task DeleteForTorrent(Guid torrentId)
{ {
await downloadData.DeleteForTorrent(torrentId); await downloadData.DeleteForTorrent(torrentId);

View file

@ -76,7 +76,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
Progress = torrent.Progress, Progress = torrent.Progress,
Status = torrent.Status, Status = torrent.Status,
Added = ChangeTimeZone(torrent.Added)!.Value, Added = ChangeTimeZone(torrent.Added)!.Value,
Files = (torrent.Files ?? new List<TorrentFile>()).Select(m => new TorrentClientFile Files = (torrent.Files ?? []).Select(m => new TorrentClientFile
{ {
Path = m.Path, Path = m.Path,
Bytes = m.Bytes, Bytes = m.Bytes,
@ -173,7 +173,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll) else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
{ {
Log("Selecting all files", torrent); Log("Selecting all files", torrent);
files = torrent.Files.ToList(); files = [.. torrent.Files];
} }
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual) else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
{ {
@ -258,7 +258,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
Log("", torrent); Log("", torrent);
await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, fileIds.ToArray()); await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, [.. fileIds]);
} }
public async Task Delete(String torrentId) public async Task Delete(String torrentId)
@ -287,12 +287,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
return torrent; return torrent;
} }
var rdTorrent = await GetInfo(torrent.RdId); var rdTorrent = await GetInfo(torrent.RdId) ?? throw new Exception($"Resource not found");
if (rdTorrent == null)
{
throw new Exception($"Resource not found");
}
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename)) if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
{ {

View file

@ -17,6 +17,9 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new(); public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new(); public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
private readonly Dictionary<Guid, String> _aggregatedDownloadResults = [];
private readonly Dictionary<Guid, String> _aggregatedDownloadErrors = [];
private readonly HttpClient _httpClient = new() private readonly HttpClient _httpClient = new()
{ {
Timeout = TimeSpan.FromSeconds(10) Timeout = TimeSpan.FromSeconds(10)
@ -37,13 +40,13 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
} }
// When starting up reset any pending downloads or unpackings so that they are restarted. // When starting up reset any pending downloads or unpackings so that they are restarted.
var torrents1 = await torrents.Get(); var allTorrents = await torrents.Get();
torrents1 = torrents1.Where(m => m.Completed == null).ToList(); allTorrents = allTorrents.Where(m => m.Completed == null).ToList();
Log($"Found {torrents1.Count} not completed torrents"); Log($"Found {allTorrents.Count} not completed torrents");
foreach (var torrent in torrents1) foreach (var torrent in allTorrents)
{ {
foreach (var download in torrent.Downloads) foreach (var download in torrent.Downloads)
{ {
@ -93,6 +96,16 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
return; return;
} }
if (Settings.Get.DownloadClient.Client == Data.Enums.DownloadClient.Symlink)
{
var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath;
if (!Directory.Exists(rcloneMountPath))
{
throw new($"Rclone mount path ({rcloneMountPath}) was not found!");
}
}
var sw = new Stopwatch(); var sw = new Stopwatch();
sw.Start(); sw.Start();
@ -220,12 +233,12 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
} }
} }
var torrents1 = await torrents.Get(); var allTorrents = await torrents.Get();
// Check for deleted torrents that are stuck in the ActiveDownloads or ActiveUnpacks // Check for deleted torrents that are stuck in the ActiveDownloads or ActiveUnpacks
foreach (var activeDownload in ActiveDownloadClients) foreach (var activeDownload in ActiveDownloadClients)
{ {
var download = torrents1.SelectMany(m => m.Downloads).FirstOrDefault(m => m.DownloadId == activeDownload.Key); var download = allTorrents.SelectMany(m => m.Downloads).FirstOrDefault(m => m.DownloadId == activeDownload.Key);
if (download == null) if (download == null)
{ {
@ -237,7 +250,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
foreach (var activeUnpacks in ActiveUnpackClients) foreach (var activeUnpacks in ActiveUnpackClients)
{ {
var download = torrents1.SelectMany(m => m.Downloads).FirstOrDefault(m => m.DownloadId == activeUnpacks.Key); var download = allTorrents.SelectMany(m => m.Downloads).FirstOrDefault(m => m.DownloadId == activeUnpacks.Key);
if (download == null) if (download == null)
{ {
@ -248,7 +261,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
} }
// Process torrent retries // Process torrent retries
foreach (var torrent in torrents1.Where(m => m.Retry != null)) foreach (var torrent in allTorrents.Where(m => m.Retry != null))
{ {
try try
{ {
@ -271,7 +284,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
} }
// Process torrent errors // Process torrent errors
foreach (var torrent in torrents1.Where(m => m.Error != null && m.DeleteOnError > 0)) foreach (var torrent in allTorrents.Where(m => m.Error != null && m.DeleteOnError > 0))
{ {
if (torrent.Completed == null) if (torrent.Completed == null)
{ {
@ -289,7 +302,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
} }
// Process torrent lifetime // Process torrent lifetime
foreach (var torrent in torrents1.Where(m => m.Downloads.Count == 0 && m.Completed == null && m.Lifetime > 0)) foreach (var torrent in allTorrents.Where(m => m.Downloads.Count == 0 && m.Completed == null && m.Lifetime > 0))
{ {
if (torrent.Added.AddMinutes(torrent.Lifetime) > DateTime.UtcNow) if (torrent.Added.AddMinutes(torrent.Lifetime) > DateTime.UtcNow)
{ {
@ -302,16 +315,16 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
await torrents.UpdateComplete(torrent.TorrentId, $"Torrent lifetime of {torrent.Lifetime} minutes reached", DateTimeOffset.UtcNow, false); await torrents.UpdateComplete(torrent.TorrentId, $"Torrent lifetime of {torrent.Lifetime} minutes reached", DateTimeOffset.UtcNow, false);
} }
torrents1 = await torrents.Get(); allTorrents = await torrents.Get();
torrents1 = torrents1.Where(m => m.Completed == null).ToList(); allTorrents = allTorrents.Where(m => m.Completed == null).ToList();
if (torrents1.Count > 0) if (allTorrents.Count > 0)
{ {
Log($"Processing {torrents1.Count} torrents"); Log($"Processing {allTorrents.Count} torrents");
} }
foreach (var torrent in torrents1) foreach (var torrent in allTorrents)
{ {
try try
{ {
@ -321,6 +334,9 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
.OrderBy(m => m.DownloadQueued) .OrderBy(m => m.DownloadQueued)
.ToList(); .ToList();
_aggregatedDownloadResults.Clear();
_aggregatedDownloadErrors.Clear();
Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent); Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent);
foreach (var download in queuedDownloads) foreach (var download in queuedDownloads)
@ -343,10 +359,13 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
try try
{ {
Log($"Unrestricting links", download, torrent); if (download.Link == null)
{
Log($"Unrestricting links", download, torrent);
var downloadLink = await torrents.UnrestrictLink(download.DownloadId); var downloadLink = await torrents.UnrestrictLink(download.DownloadId);
download.Link = downloadLink; download.Link = downloadLink;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -360,6 +379,11 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
continue; continue;
} }
Log($"Marking download as started", download, torrent);
download.DownloadStarted = DateTime.UtcNow;
await downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted);
var downloadPath = settingDownloadPath; var downloadPath = settingDownloadPath;
if (!String.IsNullOrWhiteSpace(torrent.Category)) if (!String.IsNullOrWhiteSpace(torrent.Category))
@ -372,29 +396,49 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
// Start the download process // Start the download process
var downloadClient = new DownloadClient(download, torrent, downloadPath); var downloadClient = new DownloadClient(download, torrent, downloadPath);
Log($"Starting download", download, torrent); if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient))
var remoteId = await downloadClient.Start();
if (String.IsNullOrWhiteSpace(remoteId))
{ {
Log($"No ID received", download, torrent); Log($"Starting download", download, torrent);
continue;
try
{
var remoteId = await downloadClient.Start();
if (String.IsNullOrWhiteSpace(remoteId))
{
throw new($"No remote ID received from download client");
}
Log($"Received ID {remoteId}", download, torrent);
if (download.RemoteId != remoteId)
{
_aggregatedDownloadResults.Add(download.DownloadId, remoteId);
}
}
catch (Exception ex)
{
LogError($"Unable to start download: {ex.Message}", download, torrent);
_aggregatedDownloadErrors.Add(download.DownloadId, ex.Message);
}
Log($"Started download", download, torrent);
} }
Log($"Received ID {remoteId}", download, torrent);
download.RemoteId = remoteId;
await downloads.UpdateRemoteId(download.DownloadId, remoteId);
Log($"Marking download as started", download, torrent);
download.DownloadStarted = DateTime.UtcNow;
await downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted);
ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient);
} }
if (_aggregatedDownloadResults.Count > 0)
{
await downloads.UpdateRemoteIds(_aggregatedDownloadResults);
}
if (_aggregatedDownloadErrors.Count > 0)
{
await downloads.UpdateErrors(_aggregatedDownloadErrors);
}
_aggregatedDownloadResults.Clear();
_aggregatedDownloadErrors.Clear();
// Check if there are any unpacks that are queued and can be started. // Check if there are any unpacks that are queued and can be started.
var queuedUnpacks = torrent.Downloads var queuedUnpacks = torrent.Downloads
.Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null) .Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null)
@ -425,7 +469,8 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
var extension = Path.GetExtension(fileName); var extension = Path.GetExtension(fileName);
if (extension != ".rar" && extension != ".zip") if ((extension != ".rar" && extension != ".zip") ||
torrent.DownloadClient == Data.Enums.DownloadClient.Symlink)
{ {
Log($"No need to unpack, setting it as unpacked", download, torrent); Log($"No need to unpack, setting it as unpacked", download, torrent);