diff --git a/server/RdtClient.Data/Data/DownloadData.cs b/server/RdtClient.Data/Data/DownloadData.cs index cdb5a9f..9817130 100644 --- a/server/RdtClient.Data/Data/DownloadData.cs +++ b/server/RdtClient.Data/Data/DownloadData.cs @@ -186,6 +186,24 @@ public class DownloadData(DataContext dataContext) await TorrentData.VoidCache(); } + public async Task UpdateErrors(Dictionary 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) { var dbDownload = await dataContext.Downloads @@ -218,6 +236,22 @@ public class DownloadData(DataContext dataContext) await dataContext.SaveChangesAsync(); } + public async Task UpdateRemoteIds(Dictionary 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) { var downloads = await dataContext.Downloads @@ -234,12 +268,8 @@ public class DownloadData(DataContext dataContext) public async Task Reset(Guid downloadId) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); - - if (dbDownload == null) - { - throw new Exception($"Cannot find download with ID {downloadId}"); - } + .FirstOrDefaultAsync(m => m.DownloadId == downloadId) + ?? throw new Exception($"Cannot find download with ID {downloadId}"); dbDownload.RetryCount = 0; dbDownload.Link = null; diff --git a/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs b/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs index 8f4bd04..79e5145 100644 --- a/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs +++ b/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs @@ -24,11 +24,11 @@ public class RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory logge 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}"))}"; } - 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); requestLog += $", Body: {body}"; diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs index 38016b7..16ab063 100644 --- a/server/RdtClient.Service/Services/DownloadClient.cs +++ b/server/RdtClient.Service/Services/DownloadClient.cs @@ -6,11 +6,6 @@ namespace RdtClient.Service.Services; 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 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 BytesDone { get; private set; } - public async Task Start() + public async Task Start() { BytesDone = 0; BytesTotal = 0; @@ -31,13 +26,13 @@ public class DownloadClient(Download download, Torrent torrent, String destinati try { - if (_download.Link == null) + if (download.Link == null) { throw new Exception($"Invalid download link"); } - var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download); - var downloadPath = DownloadHelper.GetDownloadPath(_torrent, _download); + var filePath = DownloadHelper.GetDownloadPath(destinationPath, torrent, download); + var downloadPath = DownloadHelper.GetDownloadPath(torrent, download); if (filePath == null || downloadPath == null) { @@ -50,10 +45,10 @@ public class DownloadClient(Download download, Torrent torrent, String destinati Downloader = Settings.Get.DownloadClient.Client switch { - Data.Enums.DownloadClient.Internal => new InternalDownloader(_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.Symlink => new SymlinkDownloader(_download.Link, filePath), + Data.Enums.DownloadClient.Internal => new InternalDownloader(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.Symlink => new SymlinkDownloader(download.Link, filePath), _ => 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(); } - Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; Finished = true; - return null; + throw new($"An unexpected error occurred preparing download {download.Link} for torrent {torrent.RdName}: {ex.Message}"); } } diff --git a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs index 05bfc0a..d199064 100644 --- a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs @@ -45,7 +45,7 @@ public class Aria2cDownloader : IDownloader _aria2NetClient = new Aria2NetClient(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10); } - public async Task Download() + public async Task Download() { var path = Path.GetDirectoryName(_remotePath) ?? throw new Exception($"Invalid file path {_filePath}"); var fileName = Path.GetFileName(_filePath); diff --git a/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs b/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs index 6a57ab3..603e04d 100644 --- a/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs @@ -97,7 +97,7 @@ public class BezzadDownloader : IDownloader }; } - public Task Download() + public Task Download() { _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); @@ -108,7 +108,7 @@ public class BezzadDownloader : IDownloader _ = Task.Run(StartTimer); - return Task.FromResult(Guid.NewGuid().ToString()); + return Task.FromResult(Guid.NewGuid().ToString()); } public Task Cancel() diff --git a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs index 2205e23..b34690d 100644 --- a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs @@ -16,7 +16,7 @@ public interface IDownloader { event EventHandler? DownloadComplete; event EventHandler? DownloadProgress; - Task Download(); + Task Download(); Task Cancel(); Task Pause(); Task Resume(); diff --git a/server/RdtClient.Service/Services/Downloaders/InternalDownloader.cs b/server/RdtClient.Service/Services/Downloaders/InternalDownloader.cs index 7993bca..e51029b 100644 --- a/server/RdtClient.Service/Services/Downloaders/InternalDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/InternalDownloader.cs @@ -65,7 +65,7 @@ public class InternalDownloader : IDownloader }; } - public async Task Download() + public async Task Download() { _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); diff --git a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs index d0e24a3..03a6036 100644 --- a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs @@ -2,76 +2,96 @@ namespace RdtClient.Service.Services.Downloaders; -public class SymlinkDownloader : IDownloader +public class SymlinkDownloader(String uri, String path) : IDownloader { public event EventHandler? DownloadComplete; public event EventHandler? DownloadProgress; - private readonly String _filePath; - private readonly String _uri; - private readonly CancellationTokenSource _cancellationToken = new(); - - private readonly ILogger _logger; - - public SymlinkDownloader(String uri, String filePath) + + private readonly ILogger _logger = Log.ForContext(); + + public async Task Download() { - _logger = Log.ForContext(); - _logger.Debug($"Instantiated new Symlink Downloader for URI {uri} to filePath {filePath}"); + _logger.Debug($"Starting symlink resolving of {uri}, writing to path: {path}"); - _uri = uri; - _filePath = filePath; - } + var filePath = new DirectoryInfo(path); - public Task Download() - { - _logger.Debug($"Starting symlink resolving of {_uri}, writing to path: {_filePath}"); + var fileName = filePath.Name; + var fileExtension = filePath.Extension; + 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 unWantedExtensions = + [ + "zip", + "rar", + "tar" + ]; + + if (unWantedExtensions.Any(m => fileExtension == m)) { - BytesDone = 0, - BytesTotal = 0, - Speed = 0 - }); + throw new($"Cant handle compressed files with symlink downloader"); + } - _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. - var foundFiles = Directory.GetFiles(Settings.Get.DownloadClient.RcloneMountPath, fileName, SearchOption.AllDirectories).ToList(); + FileInfo? file = null; - 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. - 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(actualFilePath); + tries++; } } - _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(null); } public Task Cancel() { - _logger.Debug($"Cancelling download {_uri}"); - _cancellationToken.Cancel(false); return Task.CompletedTask; @@ -91,22 +111,23 @@ public class SymlinkDownloader : IDownloader { try { - _logger.Information($"Creating symbolic link from {sourcePath} to {symlinkPath}"); - 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}"); + return true; } _logger.Error($"Failed to create symbolic link from {sourcePath} to {symlinkPath}"); + return false; } catch (Exception ex) { _logger.Error($"Error creating symbolic link from {sourcePath} to {symlinkPath}: {ex.Message}"); + return false; } } diff --git a/server/RdtClient.Service/Services/Downloads.cs b/server/RdtClient.Service/Services/Downloads.cs index d89e434..2db0282 100644 --- a/server/RdtClient.Service/Services/Downloads.cs +++ b/server/RdtClient.Service/Services/Downloads.cs @@ -65,6 +65,11 @@ public class Downloads(DownloadData downloadData) await downloadData.UpdateError(downloadId, error); } + public async Task UpdateErrors(Dictionary downloadIds) + { + await downloadData.UpdateErrors(downloadIds); + } + public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) { await downloadData.UpdateRetryCount(downloadId, retryCount); @@ -75,6 +80,11 @@ public class Downloads(DownloadData downloadData) await downloadData.UpdateRemoteId(downloadId, remoteId); } + public async Task UpdateRemoteIds(Dictionary downloadIds) + { + await downloadData.UpdateRemoteIds(downloadIds); + } + public async Task DeleteForTorrent(Guid torrentId) { await downloadData.DeleteForTorrent(torrentId); diff --git a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs index aecc2ab..b8c6c4e 100644 --- a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs @@ -76,7 +76,7 @@ public class RealDebridTorrentClient(ILogger logger, IH Progress = torrent.Progress, Status = torrent.Status, Added = ChangeTimeZone(torrent.Added)!.Value, - Files = (torrent.Files ?? new List()).Select(m => new TorrentClientFile + Files = (torrent.Files ?? []).Select(m => new TorrentClientFile { Path = m.Path, Bytes = m.Bytes, @@ -173,7 +173,7 @@ public class RealDebridTorrentClient(ILogger logger, IH else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll) { Log("Selecting all files", torrent); - files = torrent.Files.ToList(); + files = [.. torrent.Files]; } else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual) { @@ -258,7 +258,7 @@ public class RealDebridTorrentClient(ILogger logger, IH Log("", torrent); - await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, fileIds.ToArray()); + await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, [.. fileIds]); } public async Task Delete(String torrentId) @@ -287,12 +287,7 @@ public class RealDebridTorrentClient(ILogger logger, IH return torrent; } - var rdTorrent = await GetInfo(torrent.RdId); - - if (rdTorrent == null) - { - throw new Exception($"Resource not found"); - } + var rdTorrent = await GetInfo(torrent.RdId) ?? throw new Exception($"Resource not found"); if (!String.IsNullOrWhiteSpace(rdTorrent.Filename)) { diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 805341b..ef3ce20 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -17,6 +17,9 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow public static readonly ConcurrentDictionary ActiveDownloadClients = new(); public static readonly ConcurrentDictionary ActiveUnpackClients = new(); + private readonly Dictionary _aggregatedDownloadResults = []; + private readonly Dictionary _aggregatedDownloadErrors = []; + private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) @@ -37,13 +40,13 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow } // 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) { @@ -93,6 +96,16 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow 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(); sw.Start(); @@ -220,12 +233,12 @@ public class TorrentRunner(ILogger 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 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) { @@ -237,7 +250,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow 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) { @@ -248,7 +261,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow } // Process torrent retries - foreach (var torrent in torrents1.Where(m => m.Retry != null)) + foreach (var torrent in allTorrents.Where(m => m.Retry != null)) { try { @@ -271,7 +284,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow } // 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) { @@ -289,7 +302,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow } // 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) { @@ -302,16 +315,16 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow 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 { @@ -321,6 +334,9 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow .OrderBy(m => m.DownloadQueued) .ToList(); + _aggregatedDownloadResults.Clear(); + _aggregatedDownloadErrors.Clear(); + Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent); foreach (var download in queuedDownloads) @@ -343,10 +359,13 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow try { - Log($"Unrestricting links", download, torrent); + if (download.Link == null) + { + Log($"Unrestricting links", download, torrent); - var downloadLink = await torrents.UnrestrictLink(download.DownloadId); - download.Link = downloadLink; + var downloadLink = await torrents.UnrestrictLink(download.DownloadId); + download.Link = downloadLink; + } } catch (Exception ex) { @@ -360,6 +379,11 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow continue; } + Log($"Marking download as started", download, torrent); + + download.DownloadStarted = DateTime.UtcNow; + await downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted); + var downloadPath = settingDownloadPath; if (!String.IsNullOrWhiteSpace(torrent.Category)) @@ -372,29 +396,49 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow // Start the download process var downloadClient = new DownloadClient(download, torrent, downloadPath); - Log($"Starting download", download, torrent); - - var remoteId = await downloadClient.Start(); - - if (String.IsNullOrWhiteSpace(remoteId)) + if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient)) { - Log($"No ID received", download, torrent); - continue; + Log($"Starting download", download, torrent); + + 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. var queuedUnpacks = torrent.Downloads .Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null) @@ -425,7 +469,8 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow 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);