From 047326a30fe4c7e1c8685efaee305cd781a60f40 Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:51:04 +0200 Subject: [PATCH 01/12] Add Synology Download Station --- .../add-new-torrent.component.html | 1 + server/RdtClient.Data/Enums/DownloadClient.cs | 3 + .../Models/Internal/DbSettings.cs | 21 +- .../RdtClient.Service.csproj | 1 + .../Services/DownloadClient.cs | 1 + .../Downloaders/DownloadStationDownloader.cs | 189 ++++++++++++++++++ .../Services/TorrentRunner.cs | 37 ++-- 7 files changed, 238 insertions(+), 15 deletions(-) create mode 100644 server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs diff --git a/client/src/app/add-new-torrent/add-new-torrent.component.html b/client/src/app/add-new-torrent/add-new-torrent.component.html index ed6a853..7d939e6 100644 --- a/client/src/app/add-new-torrent/add-new-torrent.component.html +++ b/client/src/app/add-new-torrent/add-new-torrent.component.html @@ -50,6 +50,7 @@ +

diff --git a/server/RdtClient.Data/Enums/DownloadClient.cs b/server/RdtClient.Data/Enums/DownloadClient.cs index 9f43867..dd31e51 100644 --- a/server/RdtClient.Data/Enums/DownloadClient.cs +++ b/server/RdtClient.Data/Enums/DownloadClient.cs @@ -15,4 +15,7 @@ public enum DownloadClient [Description("Symlink Downloader")] Symlink, + + [Description("Synology DownloadStation")] + DownloadStation, } \ No newline at end of file diff --git a/server/RdtClient.Data/Models/Internal/DbSettings.cs b/server/RdtClient.Data/Models/Internal/DbSettings.cs index 10381fd..0461d9a 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -1,5 +1,5 @@ -using System.ComponentModel; -using RdtClient.Data.Enums; +using RdtClient.Data.Enums; +using System.ComponentModel; // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global @@ -123,7 +123,7 @@ http://127.0.0.1:6800/jsonrpc.")] [DisplayName("Aria2c Secret (only used for the Aria2c Downloader)")] [Description("The secret of your Aria2c instance. Optional.")] public String Aria2cSecret { get; set; } = "mysecret123"; - + [DisplayName("Aria2c Download Path")] [Description("The root path to download the file to on the Aria2c host, if empty use the Download path setting.")] public String? Aria2cDownloadPath { get; set; } = null; @@ -132,6 +132,21 @@ http://127.0.0.1:6800/jsonrpc.")] [Description("Path where Rclone is mounted. Required for Symlink Downloader. Suffix this path with a * to search subdirectories too.")] public String RcloneMountPath { get; set; } = "/mnt/rd/"; + [DisplayName("Synology DownloadStation URL")] + [Description("The URL to the Synology DownloadStation. A common URL is http://127.0.0.1:5000")] + public String DownloadStationUrl { get; set; } = "http://127.0.0.1:5000"; + + [DisplayName("Synology DownloadStation Username")] + [Description("The username to use when connecting to the Synology DownloadStation.")] + public String? DownloadStationUsername { get; set; } = null; + [DisplayName("Synology DownloadStation Password")] + [Description("The password to use when connecting to the Synology DownloadStation.")] + public String? DownloadStationPassword { get; set; } = null; + + [DisplayName("Synology Download Station Download Path")] + [Description("The root path to doawnload the file on the Synology DownloadStation host, if empty use the default DownloadStation path but won't create catagory folders.")] + public String? DownloadStationDownloadPath { get; set; } = null; + [DisplayName("Log level")] [Description("Only set when trying to debug a download client, can generate a lot of logs.")] public DownloadClientLogLevel LogLevel { get; set; } = DownloadClientLogLevel.None; diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 86ce058..dd334a4 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -22,6 +22,7 @@ + diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs index 3ab400b..049bde1 100644 --- a/server/RdtClient.Service/Services/DownloadClient.cs +++ b/server/RdtClient.Service/Services/DownloadClient.cs @@ -52,6 +52,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath), Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath, downloadPath, category), Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath, downloadPath), + Data.Enums.DownloadClient.DownloadStation => await DownloadStationDownloader.Init(download.RemoteId, download.Link, filePath, downloadPath, category), _ => throw new($"Unknown download client {Type}") }; diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs new file mode 100644 index 0000000..0bf53c9 --- /dev/null +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -0,0 +1,189 @@ + +using Serilog; +using Synology.Api.Client; +using Synology.Api.Client.Apis.DownloadStation.Task.Models; + +namespace RdtClient.Service.Services.Downloaders; + +public class DownloadStationDownloader : IDownloader +{ + public event EventHandler? DownloadComplete; + public event EventHandler? DownloadProgress; + + private const Int32 RetryCount = 5; + + private readonly SynologyClient _synologyClient; + + private readonly ILogger _logger; + private readonly String _uri; + private readonly String _filePath; + private readonly String? _remotePath; + + private String? _gid; + private DownloadStationDownloader(String? gid, String uri, String filePath, String downloadPath, String? category) + { + _logger = Log.ForContext(); + _logger.Debug($"Instantiated new DownloadStation Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}"); + + _gid = gid; + _uri = uri; + _filePath = filePath; + + _remotePath = !String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.DownloadStationDownloadPath) + ? String.IsNullOrWhiteSpace(category) + ? Path.Combine(ToCurrentPath(Settings.Get.DownloadClient.DownloadStationDownloadPath)) + : Path.Combine(ToCurrentPath(Settings.Get.DownloadClient.DownloadStationDownloadPath), category) + : null; + + _synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl); + } + + public static async Task Init(String? gid, String uri, String filePath, String downloadPath, String? category) + { + var result = new DownloadStationDownloader(gid, uri, filePath, downloadPath, category); + if (Settings.Get.DownloadClient.DownloadStationUsername != null && Settings.Get.DownloadClient.DownloadStationPassword != null) + await result._synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword); + + return result; + } + + public async Task Cancel() + { + if (_gid != null) + { + _logger.Debug($"Remove download {_uri} {_gid} from Synology DownloadStation"); + + await _synologyClient.DownloadStationApi().TaskEndpoint().DeleteAsync(new DownloadStationTaskDeleteRequest + { + Ids = new List { _gid }, + ForceComplete = false + }); + } + } + + public async Task Download() + { + var path = _remotePath != null ? ToUnixPath(_remotePath) : null; + _logger.Debug($"Starting download of {_uri}, writing to path: {path}"); + + if (_gid != null) + { + if (GetTask() != null) + { + throw new($"The download link {_uri} has already been added to DownloadStation"); + } + } + + + var retryCount = 0; + while (retryCount < 5) + { + _gid = await GetGidFromUri(); + if (_gid != null) + { + _logger.Debug($"Download with ID {_gid} found in DownloadStation"); + return _gid; + } + + var createResult = await _synologyClient + .DownloadStationApi() + .TaskEndpoint() + .CreateAsync(new DownloadStationTaskCreateRequest(_uri, path)); + + _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); + + if (createResult.Success) + { + _gid = await GetGidFromUri(); + if (_gid != null) + { + _logger.Debug($"Download with ID {_gid} found in DownloadStation"); + return _gid; + } + else + { + retryCount++; + _logger.Debug($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); + await Task.Delay(retryCount * 1000); + } + } + else + { + retryCount++; + _logger.Debug($"Error starting download: {createResult.Error.Code}. Retrying {retryCount}/{RetryCount}"); + await Task.Delay(retryCount * 1000); + } + } + + throw new Exception($"Unable to download file"); + } + + private async Task GetGidFromUri() + { + var tasks = await _synologyClient.DownloadStationApi().TaskEndpoint().ListAsync(); + return tasks.Tasks.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id; + } + + public async Task Pause() + { + _logger.Debug($"Pausing download {_uri} {_gid}"); + if (_gid != null) + await _synologyClient.DownloadStationApi().TaskEndpoint().PauseAsync(_gid); + } + + public async Task Resume() + { + _logger.Debug($"Resuming download {_uri} {_gid}"); + if (_gid != null) + await _synologyClient.DownloadStationApi().TaskEndpoint().ResumeAsync(_gid); + } + + public async Task Update() + { + if (_gid == null) + return; + + var task = await GetTask(); + + if (task == null) + { + DownloadComplete?.Invoke(this, new() { Error = "Task not found" }); + return; + } + + if (task.Status == "finished") + { + DownloadComplete?.Invoke(this, new() { Error = null }); + return; + } + + DownloadProgress?.Invoke(this, new() + { + BytesDone = task.Additional.Transfer.SizeDownloaded, + BytesTotal = task.Size, + Speed = task.Additional.Transfer.SpeedDownload + }); + } + + private static string ToUnixPath(string path) + { + return path.Replace(Path.DirectorySeparatorChar, '/'); + } + + private static string ToCurrentPath(string path) + { + return path.Replace('/', Path.DirectorySeparatorChar); + } + + private async Task GetTask() + { + try + { + return await _synologyClient.DownloadStationApi().TaskEndpoint().GetInfoAsync(_gid); + } + catch + { + return null; + } + } +} diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 6b55c83..06507f4 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -1,14 +1,14 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Text.Json; -using System.Web; -using Aria2NET; +using Aria2NET; using Microsoft.Extensions.Logging; using RdtClient.Data.Enums; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; using RdtClient.Service.Services.Downloaders; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Text.Json; +using System.Web; namespace RdtClient.Service.Services; @@ -38,7 +38,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow // When starting up reset any pending downloads or unpackings so that they are restarted. var allTorrents = await torrents.Get(); - + allTorrents = allTorrents.Where(m => m.Completed == null).ToList(); Log($"Found {allTorrents.Count} not completed torrents"); @@ -73,7 +73,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow Log($"No RealDebridApiKey set in settings"); return; } - + var settingDownloadLimit = Settings.Get.General.DownloadLimit; if (settingDownloadLimit < 1) { @@ -122,6 +122,19 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow Log("Finished updating Aria2 status"); } + if (ActiveDownloadClients.Any(m => m.Value.Type == Data.Enums.DownloadClient.DownloadStation)) + { + Log("Updating DownloadStation status"); + + foreach (var activeDownload in ActiveDownloadClients) + { + if (activeDownload.Value.Downloader is DownloadStationDownloader downloadStationDownloader) + { + await downloadStationDownloader.Update(); + } + } + } + // Check if any torrents are finished downloading to the host, remove them from the active download list. var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList(); @@ -149,7 +162,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow // Retry the download if an error is encountered. LogError($"Download reported an error: {downloadClient.Error}", download, download.Torrent); Log($"Download retry count {download.RetryCount}/{download.Torrent!.DownloadRetryAttempts}, torrent retry count {download.Torrent.RetryCount}/{download.Torrent.TorrentRetryAttempts}", download, download.Torrent); - + if (download.RetryCount < download.Torrent.DownloadRetryAttempts) { Log($"Retrying download", download, download.Torrent); @@ -202,7 +215,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow if (unpackClient.Error != null) { Log($"Unpack reported an error: {unpackClient.Error}", download, download.Torrent); - + await downloads.UpdateError(downloadId, unpackClient.Error); await downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow); } @@ -287,7 +300,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow await torrents.Delete(torrent.TorrentId, true, true, true); } - + // Process torrent lifetime foreach (var torrent in allTorrents.Where(m => m.Downloads.Count == 0 && m.Completed == null && m.Lifetime > 0)) { @@ -536,7 +549,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow } // Check if torrent is complete, or if we don't want to download any files to the host. - if ((torrent.Downloads.Count > 0) || + if ((torrent.Downloads.Count > 0) || torrent.RdStatus == TorrentStatus.Finished && torrent.HostDownloadAction == TorrentHostDownloadAction.DownloadNone) { var completeCount = torrent.Downloads.Count(m => m.Completed != null); @@ -622,7 +635,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow await torrents.UpdateComplete(torrent.TorrentId, ex.Message, DateTimeOffset.UtcNow, true); } } - + await remoteService.Update(); sw.Stop(); From fcbd9d8096450acb84fa5674fe6d25356f7154ea Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:50:46 +0200 Subject: [PATCH 02/12] Correct Path using qbittorrent --- .../Models/Internal/DbSettings.cs | 2 +- .../RdtClient.Service.csproj | 2 +- .../Downloaders/DownloadStationDownloader.cs | 118 ++++++++++++------ 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/server/RdtClient.Data/Models/Internal/DbSettings.cs b/server/RdtClient.Data/Models/Internal/DbSettings.cs index 0461d9a..21a0f34 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -144,7 +144,7 @@ http://127.0.0.1:6800/jsonrpc.")] public String? DownloadStationPassword { get; set; } = null; [DisplayName("Synology Download Station Download Path")] - [Description("The root path to doawnload the file on the Synology DownloadStation host, if empty use the default DownloadStation path but won't create catagory folders.")] + [Description("The root path to doawnload the file on the Synology DownloadStation host, if empty use the default DownloadStation path.")] public String? DownloadStationDownloadPath { get; set; } = null; [DisplayName("Log level")] diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index dd334a4..d1b47e2 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -22,7 +22,7 @@ - + diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index 0bf53c9..3a11918 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -1,7 +1,7 @@ - -using Serilog; +using Serilog; using Synology.Api.Client; using Synology.Api.Client.Apis.DownloadStation.Task.Models; +using Synology.Api.Client.Apis.FileStation.List.Models; namespace RdtClient.Service.Services.Downloaders; @@ -15,36 +15,55 @@ public class DownloadStationDownloader : IDownloader private readonly SynologyClient _synologyClient; private readonly ILogger _logger; - private readonly String _uri; private readonly String _filePath; + private readonly String _uri; private readonly String? _remotePath; private String? _gid; - private DownloadStationDownloader(String? gid, String uri, String filePath, String downloadPath, String? category) + private DownloadStationDownloader(String? gid, String uri, String? remotePath, String filePath, String downloadPath, String? category, SynologyClient synologyClient) { _logger = Log.ForContext(); _logger.Debug($"Instantiated new DownloadStation Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}"); _gid = gid; - _uri = uri; _filePath = filePath; - - _remotePath = !String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.DownloadStationDownloadPath) - ? String.IsNullOrWhiteSpace(category) - ? Path.Combine(ToCurrentPath(Settings.Get.DownloadClient.DownloadStationDownloadPath)) - : Path.Combine(ToCurrentPath(Settings.Get.DownloadClient.DownloadStationDownloadPath), category) - : null; - - _synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl); + _uri = uri; + _remotePath = remotePath; + _synologyClient = synologyClient; } public static async Task Init(String? gid, String uri, String filePath, String downloadPath, String? category) { - var result = new DownloadStationDownloader(gid, uri, filePath, downloadPath, category); + var synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl); if (Settings.Get.DownloadClient.DownloadStationUsername != null && Settings.Get.DownloadClient.DownloadStationPassword != null) - await result._synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword); + await synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword); - return result; + String? remotePath = null; + String? rootPath; + if (!String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.DownloadStationDownloadPath)) + { + rootPath = Settings.Get.DownloadClient.DownloadStationDownloadPath; + } + else + { + var config = await synologyClient.DownloadStationApi().InfoEndpoint().GetConfigAsync(); + rootPath = config.DefaultDestination; + } + + if (rootPath != null) + { + + if (String.IsNullOrWhiteSpace(category)) + { + remotePath = Path.Combine(rootPath, downloadPath).Replace('\\', '/'); + } + else + { + remotePath = Path.Combine(downloadPath, category, downloadPath).Replace('\\', '/'); + } + } + + return new DownloadStationDownloader(gid, uri, remotePath, filePath, downloadPath, category, synologyClient); } public async Task Cancel() @@ -63,7 +82,8 @@ public class DownloadStationDownloader : IDownloader public async Task Download() { - var path = _remotePath != null ? ToUnixPath(_remotePath) : null; + var path = Path.GetDirectoryName(_remotePath)?.Replace('\\', '/') ?? throw new($"Invalid file path {_filePath}"); + if (!path.StartsWith('/')) path = '/' + path; _logger.Debug($"Starting download of {_uri}, writing to path: {path}"); if (_gid != null) @@ -84,33 +104,43 @@ public class DownloadStationDownloader : IDownloader _logger.Debug($"Download with ID {_gid} found in DownloadStation"); return _gid; } - - var createResult = await _synologyClient - .DownloadStationApi() - .TaskEndpoint() - .CreateAsync(new DownloadStationTaskCreateRequest(_uri, path)); - - _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); - - if (createResult.Success) + try { - _gid = await GetGidFromUri(); - if (_gid != null) + await CheckFolderOrCreate(path); + + var createResult = await _synologyClient + .DownloadStationApi() + .TaskEndpoint() + .CreateAsync(new DownloadStationTaskCreateRequest(_uri, path.Substring(1))); + + _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); + + if (createResult.Success) { - _logger.Debug($"Download with ID {_gid} found in DownloadStation"); - return _gid; + _gid = await GetGidFromUri(); + if (_gid != null) + { + _logger.Debug($"Download with ID {_gid} found in DownloadStation"); + return _gid; + } + else + { + retryCount++; + _logger.Debug($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); + await Task.Delay(retryCount * 1000); + } } else { retryCount++; - _logger.Debug($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); + _logger.Debug($"Error starting download: {createResult.Error.Code}. Retrying {retryCount}/{RetryCount}"); await Task.Delay(retryCount * 1000); } } - else + catch (Exception e) { retryCount++; - _logger.Debug($"Error starting download: {createResult.Error.Code}. Retrying {retryCount}/{RetryCount}"); + _logger.Debug($"Error starting download: {e.Message}. Retrying {retryCount}/{RetryCount}"); await Task.Delay(retryCount * 1000); } } @@ -165,14 +195,22 @@ public class DownloadStationDownloader : IDownloader }); } - private static string ToUnixPath(string path) + private async Task CheckFolderOrCreate(string path) { - return path.Replace(Path.DirectorySeparatorChar, '/'); - } - - private static string ToCurrentPath(string path) - { - return path.Replace('/', Path.DirectorySeparatorChar); + if (path != null) + { + try + { + await _synologyClient.FileStationApi().ListEndpoint() + .ListAsync(new FileStationListRequest(path)); + } + catch // if not exists create it + { + await _synologyClient.FileStationApi().CreateFolderEndpoint() + .CreateAsync(new[] { path }, true); + //if error handle by the caller + } + } } private async Task GetTask() From c6beedf67529ca4906ef77d9f139c7c1339294e6 Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:07:34 +0200 Subject: [PATCH 03/12] Correct RealDebrid renaming sometimes --- client/src/app/torrent-table/torrent-table.component.html | 1 + client/src/app/torrent/torrent.component.html | 2 ++ server/RdtClient.Service/Helpers/DownloadHelper.cs | 7 +++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/app/torrent-table/torrent-table.component.html b/client/src/app/torrent-table/torrent-table.component.html index 42ed4d1..22cf5e8 100644 --- a/client/src/app/torrent-table/torrent-table.component.html +++ b/client/src/app/torrent-table/torrent-table.component.html @@ -191,6 +191,7 @@ +

diff --git a/client/src/app/torrent/torrent.component.html b/client/src/app/torrent/torrent.component.html index 63da110..e6ed4fb 100644 --- a/client/src/app/torrent/torrent.component.html +++ b/client/src/app/torrent/torrent.component.html @@ -58,6 +58,7 @@ Bezadd Aria2c Symlink Downloader + Synology DownloadStation

@@ -498,6 +499,7 @@ +

diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index bbb1775..665c936 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -1,5 +1,5 @@ -using System.Web; -using RdtClient.Data.Models.Data; +using RdtClient.Data.Models.Data; +using System.Web; namespace RdtClient.Service.Helpers; @@ -16,6 +16,9 @@ public static class DownloadHelper var directory = RemoveInvalidPathChars(torrent.RdName); + // RealDebrid sometimes change TorrentName when it's a single file torrent adding the extension of the single file + directory = Path.ChangeExtension(directory, null); + var uri = new Uri(fileUrl); var torrentPath = Path.Combine(downloadPath, directory); From d15b1a2c017317cac4afaf88a4ff8c35307583a1 Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:27:00 +0200 Subject: [PATCH 04/12] some changes for tests --- README.md | 2 +- server/RdtClient.Service/Helpers/DownloadHelper.cs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 402b587..9756351 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ By default the application runs in the root of your hosted address (i.e. https:/ - (optional) Resharper 1. Open the client folder project in VS Code and run `npm install`. -1. To debug run `ng serve`, to build run `ng build --prod`. +1. To debug run `ng serve`, to build run `ng build -c production`. 1. Open the Visual Studio 2019 project `RdtClient.sln` and `Publish` the `RdtClient.Web` to the given `PublishFolder` target. 1. When debugging, make sure to run `RdtClient.Web.dll` and not `IISExpress`. 1. The result is found in `Publish`. diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index 665c936..29e4b5d 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -16,9 +16,6 @@ public static class DownloadHelper var directory = RemoveInvalidPathChars(torrent.RdName); - // RealDebrid sometimes change TorrentName when it's a single file torrent adding the extension of the single file - directory = Path.ChangeExtension(directory, null); - var uri = new Uri(fileUrl); var torrentPath = Path.Combine(downloadPath, directory); From c562e75a97f61ecad03cd13803de49e5b168faf3 Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:48:48 +0200 Subject: [PATCH 05/12] correct Path with category --- .../Services/Downloaders/DownloadStationDownloader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index 3a11918..44375b8 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -59,7 +59,7 @@ public class DownloadStationDownloader : IDownloader } else { - remotePath = Path.Combine(downloadPath, category, downloadPath).Replace('\\', '/'); + remotePath = Path.Combine(rootPath, category, downloadPath).Replace('\\', '/'); } } From 5e52e2dd8cbe1514bd3ff58bc318cf6646380ced Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:32:53 +0200 Subject: [PATCH 06/12] Use local Synology for test --- .../RdtClient.Service.csproj | 2 +- .../Downloaders/DownloadStationDownloader.cs | 30 +++++++------------ server/RdtClient.sln | 6 ++++ 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index d1b47e2..578fdd6 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -22,11 +22,11 @@ - + diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index 44375b8..f2f0c33 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -94,7 +94,6 @@ public class DownloadStationDownloader : IDownloader } } - var retryCount = 0; while (retryCount < 5) { @@ -106,8 +105,6 @@ public class DownloadStationDownloader : IDownloader } try { - await CheckFolderOrCreate(path); - var createResult = await _synologyClient .DownloadStationApi() .TaskEndpoint() @@ -115,32 +112,25 @@ public class DownloadStationDownloader : IDownloader _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); - if (createResult.Success) + _gid = createResult.TaskId?.FirstOrDefault(); + if (_gid == null) _gid = await GetGidFromUri(); + + if (_gid != null) { - _gid = await GetGidFromUri(); - if (_gid != null) - { - _logger.Debug($"Download with ID {_gid} found in DownloadStation"); - return _gid; - } - else - { - retryCount++; - _logger.Debug($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); - await Task.Delay(retryCount * 1000); - } + _logger.Debug($"Download with ID {_gid} found in DownloadStation"); + return _gid; } else { retryCount++; - _logger.Debug($"Error starting download: {createResult.Error.Code}. Retrying {retryCount}/{RetryCount}"); + _logger.Error($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); await Task.Delay(retryCount * 1000); } } catch (Exception e) { retryCount++; - _logger.Debug($"Error starting download: {e.Message}. Retrying {retryCount}/{RetryCount}"); + _logger.Error($"Error starting download: {e.Message}. Retrying {retryCount}/{RetryCount}"); await Task.Delay(retryCount * 1000); } } @@ -151,7 +141,7 @@ public class DownloadStationDownloader : IDownloader private async Task GetGidFromUri() { var tasks = await _synologyClient.DownloadStationApi().TaskEndpoint().ListAsync(); - return tasks.Tasks.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id; + return tasks.Task.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id; } public async Task Pause() @@ -181,7 +171,7 @@ public class DownloadStationDownloader : IDownloader return; } - if (task.Status == "finished") + if (task.Status == DownloadStationTaskStatus.Finished) { DownloadComplete?.Invoke(this, new() { Error = null }); return; diff --git a/server/RdtClient.sln b/server/RdtClient.sln index 58f8ccd..f341cfd 100644 --- a/server/RdtClient.sln +++ b/server/RdtClient.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Service", "RdtCli EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Data", "RdtClient.Data\RdtClient.Data.csproj", "{92EF8817-AD73-4301-93BD-745D7D61DD74}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Synology.Api.Client", "..\..\Synology.Api.Client\src\Synology.Api.Client\Synology.Api.Client.csproj", "{FDAAD49C-126C-4612-81C2-3CEB6D7A1105}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {92EF8817-AD73-4301-93BD-745D7D61DD74}.Debug|Any CPU.Build.0 = Debug|Any CPU {92EF8817-AD73-4301-93BD-745D7D61DD74}.Release|Any CPU.ActiveCfg = Release|Any CPU {92EF8817-AD73-4301-93BD-745D7D61DD74}.Release|Any CPU.Build.0 = Release|Any CPU + {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 346982fe8d6d94d11c90483997d43a319a036e0a Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:05:39 +0100 Subject: [PATCH 07/12] Manage error --- .../Downloaders/DownloadStationDownloader.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index f2f0c33..3c6cfdc 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -1,7 +1,6 @@ using Serilog; using Synology.Api.Client; using Synology.Api.Client.Apis.DownloadStation.Task.Models; -using Synology.Api.Client.Apis.FileStation.List.Models; namespace RdtClient.Service.Services.Downloaders; @@ -161,7 +160,10 @@ public class DownloadStationDownloader : IDownloader public async Task Update() { if (_gid == null) + { + DownloadComplete?.Invoke(this, new() { Error = "Task not found" }); return; + } var task = await GetTask(); @@ -185,24 +187,6 @@ public class DownloadStationDownloader : IDownloader }); } - private async Task CheckFolderOrCreate(string path) - { - if (path != null) - { - try - { - await _synologyClient.FileStationApi().ListEndpoint() - .ListAsync(new FileStationListRequest(path)); - } - catch // if not exists create it - { - await _synologyClient.FileStationApi().CreateFolderEndpoint() - .CreateAsync(new[] { path }, true); - //if error handle by the caller - } - } - } - private async Task GetTask() { try From 735912da5dcdce8954739737fb9d400b1746f6da Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:12:42 +0100 Subject: [PATCH 08/12] Change log position --- .../Services/Downloaders/DownloadStationDownloader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index 3c6cfdc..8441378 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -109,9 +109,10 @@ public class DownloadStationDownloader : IDownloader .TaskEndpoint() .CreateAsync(new DownloadStationTaskCreateRequest(_uri, path.Substring(1))); - _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); _gid = createResult.TaskId?.FirstOrDefault(); + _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); + if (_gid == null) _gid = await GetGidFromUri(); if (_gid != null) From 85e07f3c90c99134dc11e0c27cfa6850c6742414 Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:42:04 +0100 Subject: [PATCH 09/12] Update to nuget Synology project --- server/RdtClient.Service/RdtClient.Service.csproj | 2 +- server/RdtClient.sln | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 98249af..f2e1222 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -22,12 +22,12 @@ + - diff --git a/server/RdtClient.sln b/server/RdtClient.sln index f341cfd..58f8ccd 100644 --- a/server/RdtClient.sln +++ b/server/RdtClient.sln @@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Service", "RdtCli EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Data", "RdtClient.Data\RdtClient.Data.csproj", "{92EF8817-AD73-4301-93BD-745D7D61DD74}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Synology.Api.Client", "..\..\Synology.Api.Client\src\Synology.Api.Client\Synology.Api.Client.csproj", "{FDAAD49C-126C-4612-81C2-3CEB6D7A1105}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,10 +27,6 @@ Global {92EF8817-AD73-4301-93BD-745D7D61DD74}.Debug|Any CPU.Build.0 = Debug|Any CPU {92EF8817-AD73-4301-93BD-745D7D61DD74}.Release|Any CPU.ActiveCfg = Release|Any CPU {92EF8817-AD73-4301-93BD-745D7D61DD74}.Release|Any CPU.Build.0 = Release|Any CPU - {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FDAAD49C-126C-4612-81C2-3CEB6D7A1105}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 062787143f6df937472a2f3876979e9ad0f2722a Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:12:37 +0100 Subject: [PATCH 10/12] Throw an lisible error if not config --- .../Services/Downloaders/DownloadStationDownloader.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index 8441378..ad0821b 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -33,9 +33,16 @@ public class DownloadStationDownloader : IDownloader public static async Task Init(String? gid, String uri, String filePath, String downloadPath, String? category) { + if (Settings.Get.DownloadClient.DownloadStationUrl == null) + { + throw new("No URL specified for Synology download station"); + } + if (Settings.Get.DownloadClient.DownloadStationUsername == null || Settings.Get.DownloadClient.DownloadStationPassword == null) + { + throw new("No username/password specified for Synology download station"); + } var synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl); - if (Settings.Get.DownloadClient.DownloadStationUsername != null && Settings.Get.DownloadClient.DownloadStationPassword != null) - await synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword); + await synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword); String? remotePath = null; String? rootPath; From 1e749a9bfec38bf48b4030e76d55d4fa37c30d86 Mon Sep 17 00:00:00 2001 From: YvesPa <166829028+YvesPa@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:08:32 +0100 Subject: [PATCH 11/12] Add request date on table for sorting --- client/src/app/torrent-table/torrent-table.component.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/app/torrent-table/torrent-table.component.html b/client/src/app/torrent-table/torrent-table.component.html index 42ed4d1..c1f243f 100644 --- a/client/src/app/torrent-table/torrent-table.component.html +++ b/client/src/app/torrent-table/torrent-table.component.html @@ -20,6 +20,7 @@ Files Downloads Size + Requested Status @@ -53,6 +54,9 @@ {{ torrent.rdSize | filesize }} + + {{ torrent.added | date : 'medium' }} + {{ torrent | status }} From f379d4efb851e9fed2a10a84bc3e7cd2fbeff366 Mon Sep 17 00:00:00 2001 From: Roger Far Date: Wed, 29 Jan 2025 18:55:24 -0700 Subject: [PATCH 12/12] DownloadStation cleanup. --- .../Downloaders/DownloadStationDownloader.cs | 111 ++++++++++++------ 1 file changed, 78 insertions(+), 33 deletions(-) diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index ad0821b..2050b4c 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -19,7 +19,8 @@ public class DownloadStationDownloader : IDownloader private readonly String? _remotePath; private String? _gid; - private DownloadStationDownloader(String? gid, String uri, String? remotePath, String filePath, String downloadPath, String? category, SynologyClient synologyClient) + + private DownloadStationDownloader(String? gid, String uri, String? remotePath, String filePath, String downloadPath, SynologyClient synologyClient) { _logger = Log.ForContext(); _logger.Debug($"Instantiated new DownloadStation Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}"); @@ -37,15 +38,18 @@ public class DownloadStationDownloader : IDownloader { throw new("No URL specified for Synology download station"); } + if (Settings.Get.DownloadClient.DownloadStationUsername == null || Settings.Get.DownloadClient.DownloadStationPassword == null) { throw new("No username/password specified for Synology download station"); } + var synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl); await synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword); String? remotePath = null; String? rootPath; + if (!String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.DownloadStationDownloadPath)) { rootPath = Settings.Get.DownloadClient.DownloadStationDownloadPath; @@ -58,7 +62,6 @@ public class DownloadStationDownloader : IDownloader if (rootPath != null) { - if (String.IsNullOrWhiteSpace(category)) { remotePath = Path.Combine(rootPath, downloadPath).Replace('\\', '/'); @@ -69,7 +72,7 @@ public class DownloadStationDownloader : IDownloader } } - return new DownloadStationDownloader(gid, uri, remotePath, filePath, downloadPath, category, synologyClient); + return new(gid, uri, remotePath, filePath, downloadPath, synologyClient); } public async Task Cancel() @@ -78,61 +81,75 @@ public class DownloadStationDownloader : IDownloader { _logger.Debug($"Remove download {_uri} {_gid} from Synology DownloadStation"); - await _synologyClient.DownloadStationApi().TaskEndpoint().DeleteAsync(new DownloadStationTaskDeleteRequest - { - Ids = new List { _gid }, - ForceComplete = false - }); + await _synologyClient.DownloadStationApi() + .TaskEndpoint() + .DeleteAsync(new() + { + Ids = + [ + _gid + ], + ForceComplete = false + }); } } - public async Task Download() + public async Task Download() { var path = Path.GetDirectoryName(_remotePath)?.Replace('\\', '/') ?? throw new($"Invalid file path {_filePath}"); - if (!path.StartsWith('/')) path = '/' + path; + + if (!path.StartsWith('/')) + { + path = '/' + path; + } + _logger.Debug($"Starting download of {_uri}, writing to path: {path}"); if (_gid != null) { - if (GetTask() != null) + var task = await GetTask(); + + if (task != null) { throw new($"The download link {_uri} has already been added to DownloadStation"); } } var retryCount = 0; + while (retryCount < 5) { _gid = await GetGidFromUri(); + if (_gid != null) { _logger.Debug($"Download with ID {_gid} found in DownloadStation"); + return _gid; } + try { var createResult = await _synologyClient - .DownloadStationApi() - .TaskEndpoint() - .CreateAsync(new DownloadStationTaskCreateRequest(_uri, path.Substring(1))); - + .DownloadStationApi() + .TaskEndpoint() + .CreateAsync(new(_uri, path[1..])); _gid = createResult.TaskId?.FirstOrDefault(); _logger.Debug($"Added download to DownloadStation, received ID {_gid}"); - if (_gid == null) _gid = await GetGidFromUri(); + _gid ??= await GetGidFromUri(); if (_gid != null) { _logger.Debug($"Download with ID {_gid} found in DownloadStation"); + return _gid; } - else - { - retryCount++; - _logger.Error($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); - await Task.Delay(retryCount * 1000); - } + + retryCount++; + _logger.Error($"Task not found in DownloadStation after creat Sucess. Retrying {retryCount}/{RetryCount}"); + await Task.Delay(retryCount * 1000); } catch (Exception e) { @@ -142,34 +159,46 @@ public class DownloadStationDownloader : IDownloader } } - throw new Exception($"Unable to download file"); + throw new($"Unable to download file"); } private async Task GetGidFromUri() { var tasks = await _synologyClient.DownloadStationApi().TaskEndpoint().ListAsync(); - return tasks.Task.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id; + + return tasks.Task?.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id; } public async Task Pause() { _logger.Debug($"Pausing download {_uri} {_gid}"); + if (_gid != null) + { await _synologyClient.DownloadStationApi().TaskEndpoint().PauseAsync(_gid); + } } public async Task Resume() { _logger.Debug($"Resuming download {_uri} {_gid}"); + if (_gid != null) + { await _synologyClient.DownloadStationApi().TaskEndpoint().ResumeAsync(_gid); + } } public async Task Update() { if (_gid == null) { - DownloadComplete?.Invoke(this, new() { Error = "Task not found" }); + DownloadComplete?.Invoke(this, + new() + { + Error = "Task not found" + }); + return; } @@ -177,28 +206,44 @@ public class DownloadStationDownloader : IDownloader if (task == null) { - DownloadComplete?.Invoke(this, new() { Error = "Task not found" }); + DownloadComplete?.Invoke(this, + new() + { + Error = "Task not found" + }); + return; } if (task.Status == DownloadStationTaskStatus.Finished) { - DownloadComplete?.Invoke(this, new() { Error = null }); + DownloadComplete?.Invoke(this, + new() + { + Error = null + }); + return; } - DownloadProgress?.Invoke(this, new() - { - BytesDone = task.Additional.Transfer.SizeDownloaded, - BytesTotal = task.Size, - Speed = task.Additional.Transfer.SpeedDownload - }); + DownloadProgress?.Invoke(this, + new() + { + BytesDone = task.Additional?.Transfer?.SizeDownloaded ?? 0, + BytesTotal = task.Size, + Speed = task.Additional?.Transfer?.SpeedDownload ?? 0 + }); } private async Task GetTask() { try { + if (_gid == null) + { + return null; + } + return await _synologyClient.DownloadStationApi().TaskEndpoint().GetInfoAsync(_gid); } catch