diff --git a/server/RdtClient.Data/Data/DataContext.cs b/server/RdtClient.Data/Data/DataContext.cs index 3f96a84..189259b 100644 --- a/server/RdtClient.Data/Data/DataContext.cs +++ b/server/RdtClient.Data/Data/DataContext.cs @@ -15,7 +15,7 @@ public class DataContext(DbContextOptions options) : IdentityDbContext(options) protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); - + var cascadeFKs = builder.Model.GetEntityTypes() .SelectMany(t => t.GetForeignKeys()) .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade); @@ -25,4 +25,4 @@ public class DataContext(DbContextOptions options) : IdentityDbContext(options) fk.DeleteBehavior = DeleteBehavior.Restrict; } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Data/DownloadData.cs b/server/RdtClient.Data/Data/DownloadData.cs index 3b9e303..93b850c 100644 --- a/server/RdtClient.Data/Data/DownloadData.cs +++ b/server/RdtClient.Data/Data/DownloadData.cs @@ -9,25 +9,25 @@ public class DownloadData(DataContext dataContext) public async Task> GetForTorrent(Guid torrentId) { return await dataContext.Downloads - .AsNoTracking() - .Where(m => m.TorrentId == torrentId) - .ToListAsync(); + .AsNoTracking() + .Where(m => m.TorrentId == torrentId) + .ToListAsync(); } public async Task GetById(Guid downloadId) { return await dataContext.Downloads - .Include(m => m.Torrent) - .AsNoTracking() - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .Include(m => m.Torrent) + .AsNoTracking() + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); } public async Task Get(Guid torrentId, String path) { return await dataContext.Downloads - .Include(m => m.Torrent) - .AsNoTracking() - .FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path); + .Include(m => m.Torrent) + .AsNoTracking() + .FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path); } public async Task Add(Guid torrentId, DownloadInfo downloadInfo) @@ -55,7 +55,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -72,7 +72,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateFileName(Guid downloadId, String fileName) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -89,7 +89,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -106,13 +106,13 @@ public class DownloadData(DataContext dataContext) public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { return; } - + dbDownload.DownloadFinished = dateTime; await dataContext.SaveChangesAsync(); @@ -123,7 +123,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -140,7 +140,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -157,7 +157,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -170,11 +170,11 @@ public class DownloadData(DataContext dataContext) await TorrentData.VoidCache(); } - + public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -191,7 +191,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateError(Guid downloadId, String? error) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -204,11 +204,11 @@ public class DownloadData(DataContext dataContext) await TorrentData.VoidCache(); } - + public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -225,7 +225,7 @@ public class DownloadData(DataContext dataContext) public async Task UpdateRemoteId(Guid downloadId, String remoteId) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); if (dbDownload == null) { @@ -240,8 +240,8 @@ public class DownloadData(DataContext dataContext) public async Task DeleteForTorrent(Guid torrentId) { var downloads = await dataContext.Downloads - .Where(m => m.TorrentId == torrentId) - .ToListAsync(); + .Where(m => m.TorrentId == torrentId) + .ToListAsync(); dataContext.Downloads.RemoveRange(downloads); @@ -253,7 +253,7 @@ public class DownloadData(DataContext dataContext) public async Task Reset(Guid downloadId) { var dbDownload = await dataContext.Downloads - .FirstOrDefaultAsync(m => m.DownloadId == downloadId) + .FirstOrDefaultAsync(m => m.DownloadId == downloadId) ?? throw new($"Cannot find download with ID {downloadId}"); dbDownload.RetryCount = 0; @@ -272,4 +272,4 @@ public class DownloadData(DataContext dataContext) await TorrentData.VoidCache(); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Data/SettingData.cs b/server/RdtClient.Data/Data/SettingData.cs index 7f3d4c8..51c0d59 100644 --- a/server/RdtClient.Data/Data/SettingData.cs +++ b/server/RdtClient.Data/Data/SettingData.cs @@ -67,11 +67,14 @@ public class SettingData(DataContext dataContext, ILogger logger) { var dbSettings = await dataContext.Settings.AsNoTracking().ToListAsync(); - var expectedSettings = GetSettings(Get, null).Where(m => m.Type != "Object").Select(m => new Setting - { - SettingId = m.Key, - Value = m.Value?.ToString() - }).ToList(); + var expectedSettings = GetSettings(Get, null) + .Where(m => m.Type != "Object") + .Select(m => new Setting + { + SettingId = m.Key, + Value = m.Value?.ToString() + }) + .ToList(); var newSettings = expectedSettings.Where(m => dbSettings.All(p => p.SettingId != m.SettingId)).ToList(); @@ -215,4 +218,4 @@ public class SettingData(DataContext dataContext, ILogger logger) } } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Data/TorrentData.cs b/server/RdtClient.Data/Data/TorrentData.cs index e3484a1..55f00ce 100644 --- a/server/RdtClient.Data/Data/TorrentData.cs +++ b/server/RdtClient.Data/Data/TorrentData.cs @@ -17,9 +17,9 @@ public class TorrentData(DataContext dataContext) : ITorrentData try { _torrentCache ??= await dataContext.Torrents - .AsNoTracking() - .Include(m => m.Downloads) - .ToListAsync(); + .AsNoTracking() + .Include(m => m.Downloads) + .ToListAsync(); return [.. _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added)]; } @@ -32,9 +32,9 @@ public class TorrentData(DataContext dataContext) : ITorrentData public async Task GetById(Guid torrentId) { var dbTorrent = await dataContext.Torrents - .AsNoTracking() - .Include(m => m.Downloads) - .FirstOrDefaultAsync(m => m.TorrentId == torrentId); + .AsNoTracking() + .Include(m => m.Downloads) + .FirstOrDefaultAsync(m => m.TorrentId == torrentId); if (dbTorrent == null) { @@ -54,9 +54,9 @@ public class TorrentData(DataContext dataContext) : ITorrentData hash = hash.ToLower(); var dbTorrent = await dataContext.Torrents - .AsNoTracking() - .Include(m => m.Downloads) - .FirstOrDefaultAsync(m => m.Hash == hash); + .AsNoTracking() + .Include(m => m.Downloads) + .FirstOrDefaultAsync(m => m.Hash == hash); if (dbTorrent == null) { @@ -135,7 +135,7 @@ public class TorrentData(DataContext dataContext) : ITorrentData dbTorrent.RdSpeed = torrent.RdSpeed; dbTorrent.RdSeeders = torrent.RdSeeders; dbTorrent.RdFiles = torrent.RdFiles; - + await dataContext.SaveChangesAsync(); await VoidCache(); @@ -149,7 +149,7 @@ public class TorrentData(DataContext dataContext) : ITorrentData { return; } - + dbTorrent.RdId = rdId; await dataContext.SaveChangesAsync(); @@ -327,4 +327,4 @@ public class TorrentData(DataContext dataContext) : ITorrentData TorrentCacheLock.Release(); } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Data/UserData.cs b/server/RdtClient.Data/Data/UserData.cs index c5f841b..ee1f10f 100644 --- a/server/RdtClient.Data/Data/UserData.cs +++ b/server/RdtClient.Data/Data/UserData.cs @@ -9,4 +9,4 @@ public class UserData(DataContext dataContext) { return await dataContext.Users.OrderBy(m => m.Id).FirstOrDefaultAsync(); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/DiConfig.cs b/server/RdtClient.Data/DiConfig.cs index 1a1b29e..d5a7823 100644 --- a/server/RdtClient.Data/DiConfig.cs +++ b/server/RdtClient.Data/DiConfig.cs @@ -22,4 +22,4 @@ public static class DiConfig services.AddScoped(); services.AddScoped(); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/AuthenticationType.cs b/server/RdtClient.Data/Enums/AuthenticationType.cs index e2f03f5..5bedce5 100644 --- a/server/RdtClient.Data/Enums/AuthenticationType.cs +++ b/server/RdtClient.Data/Enums/AuthenticationType.cs @@ -9,4 +9,4 @@ public enum AuthenticationType [Description("No Authentication")] None -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/DownloadClient.cs b/server/RdtClient.Data/Enums/DownloadClient.cs index 7b2c9d0..c31bd66 100644 --- a/server/RdtClient.Data/Enums/DownloadClient.cs +++ b/server/RdtClient.Data/Enums/DownloadClient.cs @@ -14,5 +14,5 @@ public enum DownloadClient Symlink, [Description("Synology DownloadStation")] - DownloadStation, -} \ No newline at end of file + DownloadStation +} diff --git a/server/RdtClient.Data/Enums/DownloadClientLogLevel.cs b/server/RdtClient.Data/Enums/DownloadClientLogLevel.cs index 0f65400..e3d4d32 100644 --- a/server/RdtClient.Data/Enums/DownloadClientLogLevel.cs +++ b/server/RdtClient.Data/Enums/DownloadClientLogLevel.cs @@ -21,4 +21,4 @@ public enum DownloadClientLogLevel [Description("None")] None -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/LogLevel.cs b/server/RdtClient.Data/Enums/LogLevel.cs index a2d4099..0e81dcc 100644 --- a/server/RdtClient.Data/Enums/LogLevel.cs +++ b/server/RdtClient.Data/Enums/LogLevel.cs @@ -18,4 +18,4 @@ public enum LogLevel [Description("Error")] Error -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/Provider.cs b/server/RdtClient.Data/Enums/Provider.cs index 4c6084b..fabb4be 100644 --- a/server/RdtClient.Data/Enums/Provider.cs +++ b/server/RdtClient.Data/Enums/Provider.cs @@ -18,4 +18,4 @@ public enum Provider [Description("DebridLink")] DebridLink -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/TorrentDownloadAction.cs b/server/RdtClient.Data/Enums/TorrentDownloadAction.cs index c9ff757..177a07e 100644 --- a/server/RdtClient.Data/Enums/TorrentDownloadAction.cs +++ b/server/RdtClient.Data/Enums/TorrentDownloadAction.cs @@ -12,4 +12,4 @@ public enum TorrentDownloadAction [Description("Manually Select Files")] DownloadManual = 2 -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/TorrentFinishedAction.cs b/server/RdtClient.Data/Enums/TorrentFinishedAction.cs index e610af5..355b15f 100644 --- a/server/RdtClient.Data/Enums/TorrentFinishedAction.cs +++ b/server/RdtClient.Data/Enums/TorrentFinishedAction.cs @@ -12,7 +12,7 @@ public enum TorrentFinishedAction [Description("Remove Torrent From Provider")] RemoveRealDebrid = 2, - + [Description("Remove Torrent From Client")] RemoveClient = 3 -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Enums/TorrentHostDownloadAction.cs b/server/RdtClient.Data/Enums/TorrentHostDownloadAction.cs index 5b0afcf..de6f774 100644 --- a/server/RdtClient.Data/Enums/TorrentHostDownloadAction.cs +++ b/server/RdtClient.Data/Enums/TorrentHostDownloadAction.cs @@ -8,5 +8,5 @@ public enum TorrentHostDownloadAction DownloadAll = 0, [Description("Don't download any files to host")] - DownloadNone = 1, -} \ No newline at end of file + DownloadNone = 1 +} diff --git a/server/RdtClient.Data/Enums/TorrentStatus.cs b/server/RdtClient.Data/Enums/TorrentStatus.cs index 0d22a86..37e49fd 100644 --- a/server/RdtClient.Data/Enums/TorrentStatus.cs +++ b/server/RdtClient.Data/Enums/TorrentStatus.cs @@ -11,4 +11,4 @@ public enum TorrentStatus Uploading = 5, Error = 99 -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/Data/Download.cs b/server/RdtClient.Data/Models/Data/Download.cs index 6720415..3b1f15b 100644 --- a/server/RdtClient.Data/Models/Data/Download.cs +++ b/server/RdtClient.Data/Models/Data/Download.cs @@ -44,17 +44,19 @@ public class Download } /// -/// Used to create s +/// Used to create s /// public class DownloadInfo { /// - /// The name of the file. Should not include directory. - /// If the filename is not known, set tn null and `GetFileName` will be called with the unrestricted link. + /// The name of the file. Should not include directory. + /// If the filename is not known, set tn null and `GetFileName` will be called with the unrestricted link. /// public required String? FileName; + /// - /// The restricted link to download this download. If the debrid serice in question does not have restricted links, use either a fake or the unrestricted link + /// The restricted link to download this download. If the debrid serice in question does not have restricted links, use + /// either a fake or the unrestricted link /// public required String RestrictedLink; -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/Data/Setting.cs b/server/RdtClient.Data/Models/Data/Setting.cs index 2408663..4a65584 100644 --- a/server/RdtClient.Data/Models/Data/Setting.cs +++ b/server/RdtClient.Data/Models/Data/Setting.cs @@ -8,4 +8,4 @@ public class Setting public String SettingId { get; set; } = null!; public String? Value { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/Data/Torrent.cs b/server/RdtClient.Data/Models/Data/Torrent.cs index 6c51b50..e20b4aa 100644 --- a/server/RdtClient.Data/Models/Data/Torrent.cs +++ b/server/RdtClient.Data/Models/Data/Torrent.cs @@ -14,10 +14,10 @@ public class Torrent public String Hash { get; set; } = null!; public String? Category { get; set; } - + public TorrentDownloadAction DownloadAction { get; set; } public TorrentFinishedAction FinishedAction { get; set; } - public Int32 FinishedActionDelay { get; set; } + public Int32 FinishedActionDelay { get; set; } public TorrentHostDownloadAction HostDownloadAction { get; set; } public Int32 DownloadMinSize { get; set; } public String? IncludeRegex { get; set; } @@ -94,4 +94,4 @@ public class Torrent return DownloadManualFiles.Split(","); } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/Internal/AppSettings.cs b/server/RdtClient.Data/Models/Internal/AppSettings.cs index 5212ed1..8609964 100644 --- a/server/RdtClient.Data/Models/Internal/AppSettings.cs +++ b/server/RdtClient.Data/Models/Internal/AppSettings.cs @@ -4,7 +4,7 @@ public class AppSettings { public AppSettingsLogging? Logging { get; set; } public AppSettingsDatabase? Database { get; set; } - + public Int32 Port { get; set; } public String? BasePath { get; set; } } @@ -13,7 +13,7 @@ public class AppSettingsLogging { public AppSettingsLoggingFile? File { get; set; } } - + public class AppSettingsLoggingFile { public String? Path { get; set; } @@ -24,4 +24,4 @@ public class AppSettingsLoggingFile public class AppSettingsDatabase { public String? Path { get; set; } -} \ 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 d0076fc..0080bc2 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -1,5 +1,5 @@ -using RdtClient.Data.Enums; -using System.ComponentModel; +using System.ComponentModel; +using RdtClient.Data.Enums; // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global @@ -155,6 +155,7 @@ http://127.0.0.1:6800/jsonrpc.")] [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; @@ -202,7 +203,7 @@ or public String ApiKey { get; set; } = ""; /// - /// API hostname to use for Real Debrid only + /// API hostname to use for Real Debrid only /// [DisplayName("API Hostname (RD only)")] [Description("Use this instead of the normal hostname for Real Debrid API requests. Only used by Real Debrid. Leave blank to use default.")] @@ -281,7 +282,7 @@ public class DbSettingsDefaultsWithCategory : DbSettingsDefaults [DisplayName("Post Download Action")] [Description("When all files are downloaded from the provider to the host, perform this action. Does not apply when using the symlink downloader.")] public TorrentFinishedAction FinishedAction { get; set; } = TorrentFinishedAction.RemoveAllTorrents; - + [DisplayName("Finished Action Delay")] [Description("When all files are downloaded from the provider to the host, wait this many minutes before performing the action above.")] public Int32 FinishedActionDelay { get; set; } = 0; @@ -324,4 +325,4 @@ public class DbSettingsDefaults [DisplayName("Priority")] [Description("Set the priority of a torrent, 1 = highest, 0 = disabled.")] public Int32 Priority { get; set; } = 0; -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/Internal/Profile.cs b/server/RdtClient.Data/Models/Internal/Profile.cs index 0f5ae9c..6297725 100644 --- a/server/RdtClient.Data/Models/Internal/Profile.cs +++ b/server/RdtClient.Data/Models/Internal/Profile.cs @@ -9,4 +9,4 @@ public class Profile public String? LatestVersion { get; set; } public Boolean? IsInsecure { get; set; } public Boolean? DisableUpdateNotification { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/Internal/SettingProperty.cs b/server/RdtClient.Data/Models/Internal/SettingProperty.cs index 82e0d9b..096850e 100644 --- a/server/RdtClient.Data/Models/Internal/SettingProperty.cs +++ b/server/RdtClient.Data/Models/Internal/SettingProperty.cs @@ -8,4 +8,4 @@ public class SettingProperty public String? Description { get; set; } public String Type { get; set; } = default!; public Dictionary? EnumValues { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs b/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs index 28547a2..baad62e 100644 --- a/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs +++ b/server/RdtClient.Data/Models/QBittorrent/AppBuildInfo.cs @@ -21,4 +21,4 @@ public class AppBuildInfo [JsonPropertyName("zlib")] public String? Zlib { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs b/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs index 208361b..4f39544 100644 --- a/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs +++ b/server/RdtClient.Data/Models/QBittorrent/AppPreferences.cs @@ -427,4 +427,4 @@ public class AppPreferences public class ScanDirs { -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs b/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs index d644d73..0d264b1 100644 --- a/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs +++ b/server/RdtClient.Data/Models/QBittorrent/SyncMetaData.cs @@ -99,4 +99,4 @@ public class SyncMetaDataServerState [JsonPropertyName("write_cache_overload")] public String? WriteCacheOverload { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs index c458fed..73fe371 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentCategory.cs @@ -9,4 +9,4 @@ public class TorrentCategory [JsonPropertyName("savePath")] public String? SavePath { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs index d6345a2..530196d 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentFileItem.cs @@ -6,4 +6,4 @@ public class TorrentFileItem { [JsonPropertyName("name")] public String? Name { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs index 40dd329..08ba963 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentInfo.cs @@ -24,7 +24,7 @@ public class TorrentInfo [JsonPropertyName("completion_on")] public Int64? CompletionOn { get; set; } - + [JsonPropertyName("content_path")] public String? ContentPath { get; set; } @@ -135,4 +135,4 @@ public class TorrentInfo [JsonPropertyName("upspeed")] public Int64? Upspeed { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs b/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs index f4ec0bd..ea59bac 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TorrentProperties.cs @@ -102,4 +102,4 @@ public class TorrentProperties [JsonPropertyName("up_speed_avg")] public Int64? UpSpeedAvg { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/QBittorrent/TransferInfo.cs b/server/RdtClient.Data/Models/QBittorrent/TransferInfo.cs index 8af9f38..9aeac3a 100644 --- a/server/RdtClient.Data/Models/QBittorrent/TransferInfo.cs +++ b/server/RdtClient.Data/Models/QBittorrent/TransferInfo.cs @@ -27,4 +27,4 @@ public class TransferInfo [JsonPropertyName("up_rate_limit")] public Int64 UpRateLimit { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs index 6629ecb..1ac01a3 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientAvailableFile.cs @@ -5,4 +5,4 @@ public class TorrentClientAvailableFile public String Filename { get; set; } = default!; public Int64 Filesize { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs index 7f5ccbd..351ed80 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientFile.cs @@ -7,4 +7,4 @@ public class TorrentClientFile public Int64 Bytes { get; set; } public Boolean Selected { get; set; } public String? DownloadLink { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs index a2a6fa1..53e90cc 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientTorrent.cs @@ -20,4 +20,4 @@ public class TorrentClientTorrent public DateTimeOffset? Ended { get; set; } public Int64? Speed { get; set; } public Int64? Seeders { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs b/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs index c1d8355..abe2963 100644 --- a/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs +++ b/server/RdtClient.Data/Models/TorrentClient/TorrentClientUser.cs @@ -4,4 +4,4 @@ public class TorrentClientUser { public String? Username { get; set; } public DateTimeOffset? Expiration { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service.Test/GlobalSuppressions.cs b/server/RdtClient.Service.Test/GlobalSuppressions.cs new file mode 100644 index 0000000..d9eb4aa --- /dev/null +++ b/server/RdtClient.Service.Test/GlobalSuppressions.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: + SuppressMessage("Usage", + "xUnit1045:Avoid using TheoryData type arguments that might not be serializable", + Justification = "It is serializable.", + Scope = "NamespaceAndDescendants", + Target = "N:RdtClient.Service.Test")] +[assembly: + SuppressMessage("Performance", + "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", + Justification = "We don't care for unit tests.", + Scope = "NamespaceAndDescendants", + Target = "N:RdtClient.Service.Test")] diff --git a/server/RdtClient.Service.Test/Services/DownloadableFileFilterTest.cs b/server/RdtClient.Service.Test/Services/DownloadableFileFilterTest.cs index 3858ee2..9d54df6 100644 --- a/server/RdtClient.Service.Test/Services/DownloadableFileFilterTest.cs +++ b/server/RdtClient.Service.Test/Services/DownloadableFileFilterTest.cs @@ -17,7 +17,7 @@ public class DownloadableFileFilterTest { RdId = "1" }; - + var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object); // Act @@ -28,10 +28,11 @@ public class DownloadableFileFilterTest } [Theory] + // downloadMinSize is in MB, fileSize is in B [InlineData(100, 20 * 1024 * 1024)] - [InlineData(2, 2 * 1024 * 1024)] - [InlineData(2, 2 * (1000 * 1000 + 1))] // mostly to show we use 1024 not 1000 for conversion + [InlineData(2, 2 * 1024 * 1024)] + [InlineData(2, 2 * ((1000 * 1000) + 1))] // mostly to show we use 1024 not 1000 for conversion public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadBelowSize_ReturnsFalse(Int32 downloadMinSize, Int64 fileSize) { // Arrange @@ -51,10 +52,10 @@ public class DownloadableFileFilterTest // Assert Assert.False(result); } - + [Theory] [InlineData(100, 110 * 1024 * 1024)] - [InlineData(2, 2 * 1024 * 1024 + 1)] + [InlineData(2, (2 * 1024 * 1024) + 1)] public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadAboveSize_ReturnsTrue(Int32 downloadMinSize, Int64 fileSize) { // Arrange @@ -74,7 +75,7 @@ public class DownloadableFileFilterTest // Assert Assert.True(result); } - + [Theory] [InlineData("file", "no-match")] [InlineData("file", "even/in/a/subdirectory.txt")] @@ -124,7 +125,7 @@ public class DownloadableFileFilterTest // Assert Assert.True(result); } - + [Theory] [InlineData("file", "no-match")] [InlineData("file", "even/in/a/subdirectory.txt")] @@ -202,23 +203,22 @@ public class DownloadableFileFilterTest } [Theory] - [InlineData(10, "file", 10 * 1024 * 1024 + 1, "no-match.txt")] - public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadAboveSizeAndDoesNotMatchRegex_ReturnsFalse( - Int32 minSize, + [InlineData(10, "file", (10 * 1024 * 1024) + 1, "no-match.txt")] + public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadAboveSizeAndDoesNotMatchRegex_ReturnsFalse(Int32 minSize, String includeRegex, Int64 fileSize, String filePath) { // Arrange var mocks = new Mocks(); - + var torrent = new Torrent { RdId = "1", IncludeRegex = includeRegex, DownloadMinSize = minSize }; - + var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object); // Act @@ -227,25 +227,24 @@ public class DownloadableFileFilterTest // Assert Assert.False(result); } - + [Theory] - [InlineData(10, "file", 10 * 1024 * 1024 - 1, "file.txt")] - public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadBelowSizeAndMatchesRegex_ReturnsFalse( - Int32 minSize, + [InlineData(10, "file", (10 * 1024 * 1024) - 1, "file.txt")] + public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadBelowSizeAndMatchesRegex_ReturnsFalse(Int32 minSize, String includeRegex, Int64 fileSize, String filePath) { // Arrange var mocks = new Mocks(); - + var torrent = new Torrent { RdId = "1", IncludeRegex = includeRegex, DownloadMinSize = minSize }; - + var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object); // Act @@ -254,8 +253,9 @@ public class DownloadableFileFilterTest // Assert Assert.False(result); } + private class Mocks { public readonly Mock> LoggerMock = new(); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs b/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs index 19c4cf3..e992403 100644 --- a/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs +++ b/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs @@ -7,7 +7,7 @@ using Synology.Api.Client.Apis.DownloadStation.Task.Models; namespace RdtClient.Service.Test.Services.Downloaders; -class Mocks +internal class Mocks { public readonly String Gid; public readonly Mock SynologyClientMock = new(); @@ -22,7 +22,7 @@ class Mocks } } -class FakeDelayProvider : IDelayProvider +internal class FakeDelayProvider : IDelayProvider { public Task Delay(Int32 milliseconds) { diff --git a/server/RdtClient.Service.Test/Services/EnricherTest.cs b/server/RdtClient.Service.Test/Services/EnricherTest.cs index 48d38ae..46406d9 100644 --- a/server/RdtClient.Service.Test/Services/EnricherTest.cs +++ b/server/RdtClient.Service.Test/Services/EnricherTest.cs @@ -1,14 +1,18 @@ +using System.Web; using Microsoft.Extensions.Logging; +using MonoTorrent.BEncoding; using Moq; using RdtClient.Service.Services; -using MonoTorrent.BEncoding; namespace RdtClient.Service.Test.Services; public class EnricherTest : IDisposable { - private readonly MockRepository _mockRepository; + private const String TestMagnetLink = + "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=TestFile&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce"; + private readonly Mock> _loggerMock; + private readonly MockRepository _mockRepository; private readonly Mock _trackerListGrabberMock; public EnricherTest() @@ -23,9 +27,6 @@ public class EnricherTest : IDisposable _mockRepository.VerifyAll(); } - private const String TestMagnetLink = - "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=TestFile&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce"; - // Helper methods for creating BEncodedDictionary objects for torrents private static BEncodedDictionary CreateStandardTorrentDict(String announceUrl, List? announceListTier = null) { @@ -216,7 +217,7 @@ public class EnricherTest : IDisposable var result = await enricher.EnrichMagnetLink(magnetLink); // Assert - var queryParams = System.Web.HttpUtility.ParseQueryString(new Uri(result).Query); + var queryParams = HttpUtility.ParseQueryString(new Uri(result).Query); Assert.Equal("urn:btih:HASH", queryParams["xt"]); Assert.Equal("MyFile", queryParams["dn"]); } @@ -239,7 +240,7 @@ public class EnricherTest : IDisposable var result = await enricher.EnrichMagnetLink(magnetLink); // Assert - var queryParams = System.Web.HttpUtility.ParseQueryString(new Uri(result).Query); + var queryParams = HttpUtility.ParseQueryString(new Uri(result).Query); var trValues = queryParams.GetValues("tr")?.ToList() ?? new List(); Assert.Contains("udp://existing", trValues); diff --git a/server/RdtClient.Service.Test/Services/TorrentClients/AllDebridTorrentClientTest.cs b/server/RdtClient.Service.Test/Services/TorrentClients/AllDebridTorrentClientTest.cs index 5a0ec59..f494aca 100644 --- a/server/RdtClient.Service.Test/Services/TorrentClients/AllDebridTorrentClientTest.cs +++ b/server/RdtClient.Service.Test/Services/TorrentClients/AllDebridTorrentClientTest.cs @@ -522,8 +522,8 @@ public class AllDebridTorrentClientTest { public readonly Mock AllDebridClientFactoryMock; public readonly Mock AllDebridClientMock; - public readonly Mock> LoggerMock; public readonly Mock FileFilterMock; + public readonly Mock> LoggerMock; public Mocks() { diff --git a/server/RdtClient.Service.Test/Services/TorrentsTest.cs b/server/RdtClient.Service.Test/Services/TorrentsTest.cs index 054a62f..3b5f844 100644 --- a/server/RdtClient.Service.Test/Services/TorrentsTest.cs +++ b/server/RdtClient.Service.Test/Services/TorrentsTest.cs @@ -13,14 +13,14 @@ using TorrentsService = RdtClient.Service.Services.Torrents; namespace RdtClient.Service.Test.Services; -class Mocks +internal class Mocks { + public readonly Mock DownloadsMock; + public readonly Mock EnricherMock; public readonly Mock ProcessFactoryMock; public readonly Mock ProcessMock; - public readonly Mock> TorrentsLoggerMock; - public readonly Mock DownloadsMock; public readonly Mock TorrentDataMock; - public readonly Mock EnricherMock; + public readonly Mock> TorrentsLoggerMock; public Mocks() { @@ -63,8 +63,7 @@ public class TorrentsTest return new() { { - torrent, - downloads + torrent, downloads } }; } @@ -96,7 +95,7 @@ public class TorrentsTest { { filePath, new("Test file") - }, + } }); var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object, @@ -162,7 +161,7 @@ public class TorrentsTest { { filePath, new("Test file") - }, + } }); var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object, @@ -210,7 +209,7 @@ public class TorrentsTest { { filePath, new("Test file") - }, + } }); var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object, @@ -277,7 +276,7 @@ public class TorrentsTest { { filePath, new("Test file") - }, + } }); var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object, diff --git a/server/RdtClient.Service/BackgroundServices/DiskSpaceMonitor.cs b/server/RdtClient.Service/BackgroundServices/DiskSpaceMonitor.cs index a29680c..ec1e218 100644 --- a/server/RdtClient.Service/BackgroundServices/DiskSpaceMonitor.cs +++ b/server/RdtClient.Service/BackgroundServices/DiskSpaceMonitor.cs @@ -10,8 +10,8 @@ namespace RdtClient.Service.BackgroundServices; public class DiskSpaceMonitor(ILogger logger, IServiceProvider serviceProvider) : BackgroundService { - private Boolean _isPausedForLowDiskSpace; private static DiskSpaceStatus? _lastStatus; + private Boolean _isPausedForLowDiskSpace; public static DiskSpaceStatus? GetCurrentStatus() { @@ -39,10 +39,12 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider if (minimumFreeSpaceGB <= 0) { await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + continue; } var intervalMinutes = Settings.Get.DownloadClient.DiskSpaceCheckIntervalMinutes; + if (intervalMinutes < 1) { intervalMinutes = 1; @@ -50,30 +52,32 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider var downloadPath = Settings.Get.DownloadClient.DownloadPath; logger.LogDebug($"Checking disk space for path: {downloadPath}"); - + if (!Directory.Exists(downloadPath)) { logger.LogWarning($"Download path does not exist: {downloadPath}"); await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + continue; } - + var availableSpaceGB = FileHelper.GetAvailableFreeSpaceGB(downloadPath); logger.LogDebug($"Disk space check: {availableSpaceGB} GB available (threshold: {minimumFreeSpaceGB} GB, resume: {minimumFreeSpaceGB * 2} GB, isPaused: {_isPausedForLowDiskSpace})"); - + if (availableSpaceGB == 0) { logger.LogWarning($"Failed to get disk space for path: {downloadPath}"); } - + var shouldPause = availableSpaceGB > 0 && availableSpaceGB < minimumFreeSpaceGB; - var shouldResume = availableSpaceGB >= (minimumFreeSpaceGB * 2); + var shouldResume = availableSpaceGB >= minimumFreeSpaceGB * 2; if (shouldPause && !_isPausedForLowDiskSpace) { logger.LogWarning($"Pausing Bezzad downloads: {availableSpaceGB} GB available, threshold is {minimumFreeSpaceGB} GB"); var pausedCount = 0; + foreach (var download in TorrentRunner.ActiveDownloadClients) { if (download.Value.Type == DownloadClient.Bezzad) @@ -83,7 +87,7 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider pausedCount++; } } - + logger.LogInformation($"Paused {pausedCount} active Bezzad downloads"); TorrentRunner.IsPausedForLowDiskSpace = true; @@ -96,13 +100,14 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider ThresholdGB = minimumFreeSpaceGB, LastCheckTime = DateTimeOffset.UtcNow }; + _lastStatus = status; await remoteService.UpdateDiskSpaceStatus(status); } else if (shouldPause && _isPausedForLowDiskSpace) { logger.LogDebug($"Still paused: {availableSpaceGB} GB available (need {minimumFreeSpaceGB * 2} GB to resume)"); - + var status = new DiskSpaceStatus { IsPaused = true, @@ -110,6 +115,7 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider ThresholdGB = minimumFreeSpaceGB, LastCheckTime = DateTimeOffset.UtcNow }; + _lastStatus = status; await remoteService.UpdateDiskSpaceStatus(status); } @@ -118,6 +124,7 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider logger.LogInformation($"Resuming Bezzad downloads: {availableSpaceGB} GB available, resume threshold is {minimumFreeSpaceGB * 2} GB"); var resumedCount = 0; + foreach (var download in TorrentRunner.ActiveDownloadClients) { if (download.Value.Type == DownloadClient.Bezzad) @@ -127,7 +134,7 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider resumedCount++; } } - + logger.LogInformation($"Resumed {resumedCount} Bezzad downloads"); TorrentRunner.IsPausedForLowDiskSpace = false; @@ -140,10 +147,11 @@ public class DiskSpaceMonitor(ILogger logger, IServiceProvider ThresholdGB = minimumFreeSpaceGB, LastCheckTime = DateTimeOffset.UtcNow }; + _lastStatus = status; await remoteService.UpdateDiskSpaceStatus(status); } - + await Task.Delay(TimeSpan.FromMinutes(intervalMinutes), stoppingToken); } catch (Exception ex) diff --git a/server/RdtClient.Service/BackgroundServices/ProviderUpdater.cs b/server/RdtClient.Service/BackgroundServices/ProviderUpdater.cs index a9a5eba..f32b8cc 100644 --- a/server/RdtClient.Service/BackgroundServices/ProviderUpdater.cs +++ b/server/RdtClient.Service/BackgroundServices/ProviderUpdater.cs @@ -19,7 +19,7 @@ public class ProviderUpdater(ILogger logger, IServiceProvider s using var scope = serviceProvider.CreateScope(); var torrentService = scope.ServiceProvider.GetRequiredService(); - + logger.LogInformation("ProviderUpdater started."); while (!stoppingToken.IsCancellationRequested) @@ -31,7 +31,7 @@ public class ProviderUpdater(ILogger logger, IServiceProvider s if (_nextUpdate < DateTime.UtcNow && (Settings.Get.Provider.AutoImport || torrents.Any(t => t.RdStatus != TorrentStatus.Finished))) { logger.LogDebug($"Updating torrent info from debrid provider"); - + var updateTime = Settings.Get.Provider.CheckInterval * 3; if (updateTime < 30) @@ -66,4 +66,4 @@ public class ProviderUpdater(ILogger logger, IServiceProvider s logger.LogInformation("ProviderUpdater stopped."); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/BackgroundServices/Startup.cs b/server/RdtClient.Service/BackgroundServices/Startup.cs index d5c359a..db890ac 100644 --- a/server/RdtClient.Service/BackgroundServices/Startup.cs +++ b/server/RdtClient.Service/BackgroundServices/Startup.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Logging; using RdtClient.Data.Data; using RdtClient.Service.Services; - namespace RdtClient.Service.BackgroundServices; public class Startup(IServiceProvider serviceProvider) : IHostedService @@ -16,7 +15,7 @@ public class Startup(IServiceProvider serviceProvider) : IHostedService public async Task StartAsync(CancellationToken cancellationToken) { var version = Assembly.GetEntryAssembly()?.GetName().Version; - + using var scope = serviceProvider.CreateScope(); var logger = scope.ServiceProvider.GetRequiredService>(); @@ -32,5 +31,8 @@ public class Startup(IServiceProvider serviceProvider) : IHostedService Ready = true; } - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; -} \ No newline at end of file + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/server/RdtClient.Service/BackgroundServices/TaskRunner.cs b/server/RdtClient.Service/BackgroundServices/TaskRunner.cs index 5f4769e..7d1f556 100644 --- a/server/RdtClient.Service/BackgroundServices/TaskRunner.cs +++ b/server/RdtClient.Service/BackgroundServices/TaskRunner.cs @@ -18,11 +18,11 @@ public class TaskRunner(ILogger logger, IServiceProvider serviceProv using var scope = serviceProvider.CreateScope(); var torrentRunner = scope.ServiceProvider.GetRequiredService(); - + logger.LogInformation("TaskRunner started."); await torrentRunner.Initialize(); - + while (!stoppingToken.IsCancellationRequested) { try @@ -60,4 +60,4 @@ public class TaskRunner(ILogger logger, IServiceProvider serviceProv logger.LogInformation("TaskRunner stopped."); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs b/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs index 568abb1..3ae9bfb 100644 --- a/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs +++ b/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs @@ -7,12 +7,11 @@ namespace RdtClient.Service.BackgroundServices; public class UpdateChecker(ILogger logger) : BackgroundService { + private static readonly List KnownGhsaIds = []; public static String? CurrentVersion { get; private set; } public static String? LatestVersion { get; private set; } - - public static Boolean? IsInsecure { get; private set; } - private static readonly List KnownGhsaIds = []; + public static Boolean? IsInsecure { get; private set; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -45,6 +44,7 @@ public class UpdateChecker(ILogger logger) : BackgroundService if (latestRelease == null) { logger.LogWarning($"Unable to find latest version on GitHub"); + return; } @@ -58,10 +58,11 @@ public class UpdateChecker(ILogger logger) : BackgroundService var gitHubSecurityAdvisories = await GitHubRequest>("/repos/rogerfar/rdt-client/security-advisories", stoppingToken); var unseenGhsaIds = gitHubSecurityAdvisories?.Where(advisory => !KnownGhsaIds.Contains(advisory.GhsaId)); - + if (unseenGhsaIds == null) { logger.LogWarning($"Unable to find security advisories on GitHub"); + return; } @@ -80,15 +81,15 @@ public class UpdateChecker(ILogger logger) : BackgroundService private static async Task GitHubRequest(String endpoint, CancellationToken cancellationToken) { - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion)); - var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken); - - return JsonConvert.DeserializeObject(response); + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion)); + var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken); + + return JsonConvert.DeserializeObject(response); } } -public class GitHubReleasesResponse +public class GitHubReleasesResponse { [JsonProperty("name")] public String? Name { get; set; } @@ -97,5 +98,5 @@ public class GitHubReleasesResponse public class GitHubSecurityAdvisoriesResponse { [JsonProperty("ghsa_id")] - public required String GhsaId { get; set; } -} \ No newline at end of file + public required String GhsaId { get; set; } +} diff --git a/server/RdtClient.Service/BackgroundServices/WatchFolderChecker.cs b/server/RdtClient.Service/BackgroundServices/WatchFolderChecker.cs index 4518c12..7160a59 100644 --- a/server/RdtClient.Service/BackgroundServices/WatchFolderChecker.cs +++ b/server/RdtClient.Service/BackgroundServices/WatchFolderChecker.cs @@ -21,7 +21,7 @@ public class WatchFolderChecker(ILogger logger, IServiceProv using var scope = serviceProvider.CreateScope(); var torrentService = scope.ServiceProvider.GetRequiredService(); - + logger.LogInformation("WatchFolderChecker started."); while (!stoppingToken.IsCancellationRequested) @@ -112,7 +112,7 @@ public class WatchFolderChecker(ILogger logger, IServiceProv { Directory.CreateDirectory(processedStorePath); } - + var processedPath = Path.Combine(processedStorePath, fileInfo.Name); if (File.Exists(processedPath)) @@ -147,6 +147,7 @@ public class WatchFolderChecker(ILogger logger, IServiceProv fileInfo.Name, errorStorePath); } + File.Move(torrentFile, processedPath); } } @@ -169,6 +170,7 @@ public class WatchFolderChecker(ILogger logger, IServiceProv { return true; } + return false; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/BackgroundServices/WebsocketsUpdater.cs b/server/RdtClient.Service/BackgroundServices/WebsocketsUpdater.cs index 10237d4..91c7da0 100644 --- a/server/RdtClient.Service/BackgroundServices/WebsocketsUpdater.cs +++ b/server/RdtClient.Service/BackgroundServices/WebsocketsUpdater.cs @@ -34,4 +34,4 @@ public class WebsocketsUpdater(ILogger logger, IServiceProvid logger.LogInformation("WebsocketsUpdater stopped."); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/DiConfig.cs b/server/RdtClient.Service/DiConfig.cs index c93f5e5..fa8f6d6 100644 --- a/server/RdtClient.Service/DiConfig.cs +++ b/server/RdtClient.Service/DiConfig.cs @@ -61,9 +61,10 @@ public static class DiConfig var retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests) - .WaitAndRetryAsync(retryCount: 5, sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); + .WaitAndRetryAsync(5, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); services.AddHttpClient(); + services.ConfigureHttpClientDefaults(builder => { builder.ConfigureHttpClient(httpClient => diff --git a/server/RdtClient.Service/GlobalSuppressions.cs b/server/RdtClient.Service/GlobalSuppressions.cs index eac4adf..3d498f0 100644 --- a/server/RdtClient.Service/GlobalSuppressions.cs +++ b/server/RdtClient.Service/GlobalSuppressions.cs @@ -5,5 +5,6 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Bug in Serilog", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")] -[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Code style", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")] \ No newline at end of file +[assembly: + SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Bug in Serilog", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")] +[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Code style", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")] diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index 3e2653d..36c20cd 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -1,6 +1,6 @@ using System.IO.Abstractions; -using RdtClient.Data.Models.Data; using System.Web; +using RdtClient.Data.Models.Data; namespace RdtClient.Service.Helpers; @@ -16,7 +16,7 @@ public static class DownloadHelper } var directory = RemoveInvalidPathChars(torrent.RdName); - + var torrentPath = Path.Combine(downloadPath, directory); var fileName = GetFileName(download); diff --git a/server/RdtClient.Service/Helpers/FileHelper.cs b/server/RdtClient.Service/Helpers/FileHelper.cs index df03d16..08af6ae 100644 --- a/server/RdtClient.Service/Helpers/FileHelper.cs +++ b/server/RdtClient.Service/Helpers/FileHelper.cs @@ -80,11 +80,12 @@ public static class FileHelper { return String.Concat(filename.Split(Path.GetInvalidFileNameChars())); } - + public static String GetDirectoryContents(String path) { var stringBuilder = new StringBuilder(); GetDirectoryContents(path, stringBuilder, ""); + return stringBuilder.ToString(); } @@ -93,6 +94,7 @@ public static class FileHelper var directoryInfo = new DirectoryInfo(path); var directories = directoryInfo.GetDirectories(); + foreach (var directory in directories) { stringBuilder.AppendLine($"{indent}{directory.Name}"); @@ -100,6 +102,7 @@ public static class FileHelper } var files = directoryInfo.GetFiles(); + foreach (var file in files) { stringBuilder.AppendLine($"{indent}{file.Name}"); @@ -112,13 +115,16 @@ public static class FileHelper { return 0; } + try { if (!Directory.Exists(path)) { return 0; } + var driveInfo = new DriveInfo(path); + return driveInfo.AvailableFreeSpace / (1024 * 1024 * 1024); } catch @@ -126,4 +132,4 @@ public static class FileHelper return 0; } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Helpers/JsonModelBinder.cs b/server/RdtClient.Service/Helpers/JsonModelBinder.cs index 1a7c176..953704f 100644 --- a/server/RdtClient.Service/Helpers/JsonModelBinder.cs +++ b/server/RdtClient.Service/Helpers/JsonModelBinder.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; +using Newtonsoft.Json; namespace RdtClient.Service.Helpers; @@ -9,19 +10,22 @@ public class JsonModelBinder : IModelBinder ArgumentNullException.ThrowIfNull(bindingContext); var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult != ValueProviderResult.None) { bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); var valueAsString = valueProviderResult.FirstValue ?? ""; - var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType); + var result = JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType); + if (result != null) { bindingContext.Result = ModelBindingResult.Success(result); + return Task.CompletedTask; } } return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Helpers/Logger.cs b/server/RdtClient.Service/Helpers/Logger.cs index 8b341d4..a231577 100644 --- a/server/RdtClient.Service/Helpers/Logger.cs +++ b/server/RdtClient.Service/Helpers/Logger.cs @@ -16,7 +16,7 @@ public static class Logger fileName = HttpUtility.UrlDecode(fileName); } - var done = (Int32)((Double)download.BytesDone / download.BytesTotal * 100); + var done = (Int32)(((Double)download.BytesDone / download.BytesTotal) * 100); if (done < 0) { @@ -30,4 +30,4 @@ public static class Logger { return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})"; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Middleware/AuthSettingHandler.cs b/server/RdtClient.Service/Middleware/AuthSettingHandler.cs index e3a552a..2cb8a12 100644 --- a/server/RdtClient.Service/Middleware/AuthSettingHandler.cs +++ b/server/RdtClient.Service/Middleware/AuthSettingHandler.cs @@ -6,12 +6,11 @@ namespace RdtClient.Service.Middleware; public class AuthSettingRequirement : IAuthorizationRequirement { - } public class AuthSettingHandler : AuthorizationHandler { - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthSettingRequirement requirement) + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthSettingRequirement requirement) { if (context.User.Identity?.IsAuthenticated == true) { @@ -23,6 +22,6 @@ public class AuthSettingHandler : AuthorizationHandler context.Succeed(requirement); } - return Task.CompletedTask; + return Task.CompletedTask; } } diff --git a/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs b/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs index 48c6b96..0791fe5 100644 --- a/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs +++ b/server/RdtClient.Service/Middleware/AuthorizeMiddleware.cs @@ -6,7 +6,7 @@ namespace RdtClient.Service.Middleware; public class AuthorizeMiddleware(RequestDelegate next) { /// - /// Return a 403 instead of a 401, it's quirk that QBittorrent has. + /// Return a 403 instead of a 401, it's quirk that QBittorrent has. /// /// /// @@ -14,9 +14,9 @@ public class AuthorizeMiddleware(RequestDelegate next) { await next(context); - if (context.Response.StatusCode == (Int32) HttpStatusCode.Unauthorized) + if (context.Response.StatusCode == (Int32)HttpStatusCode.Unauthorized) { - context.Response.StatusCode = (Int32) HttpStatusCode.Forbidden; + context.Response.StatusCode = (Int32)HttpStatusCode.Forbidden; } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs b/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs index cd64549..ecc44ac 100644 --- a/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs +++ b/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs @@ -5,6 +5,8 @@ namespace RdtClient.Service.Middleware; public partial class BaseHrefMiddleware(RequestDelegate next, String basePath) { + private readonly String _basePath = $"/{basePath.TrimStart('/').TrimEnd('/')}/"; + [GeneratedRegex(@")")] private partial Regex LinkRegex(); - private readonly String _basePath = $"/{basePath.TrimStart('/').TrimEnd('/')}/"; - public async Task InvokeAsync(HttpContext context) { var originalBody = context.Response.Body; @@ -55,4 +55,4 @@ public partial class BaseHrefMiddleware(RequestDelegate next, String basePath) context.Response.Body = originalBody; } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs b/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs index 3f46326..3787466 100644 --- a/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs +++ b/server/RdtClient.Service/Middleware/ExceptionMiddlewareExtensions.cs @@ -13,10 +13,11 @@ public static class ExceptionMiddlewareExtensions { appError.Run(async context => { - context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError; + context.Response.StatusCode = (Int32)HttpStatusCode.InternalServerError; context.Response.ContentType = "application/json"; var contextFeature = context.Features.Get(); + if (contextFeature != null) { await context.Response.WriteAsync(contextFeature.Error.Message); @@ -24,4 +25,4 @@ public static class ExceptionMiddlewareExtensions }); }); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs b/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs index 1944dab..eb7c2f7 100644 --- a/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs +++ b/server/RdtClient.Service/Middleware/RequestLoggingMiddleware.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Http; +using System.Text; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using System.Text; namespace RdtClient.Service.Middleware; @@ -43,7 +43,7 @@ public class RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory logge { request.EnableBuffering(); - using var reader = new StreamReader(request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true); + using var reader = new StreamReader(request.Body, Encoding.UTF8, false, leaveOpen: true); var body = await reader.ReadToEndAsync(); request.Body.Position = 0; diff --git a/server/RdtClient.Service/Services/Authentication.cs b/server/RdtClient.Service/Services/Authentication.cs index 8d28009..bf396b0 100644 --- a/server/RdtClient.Service/Services/Authentication.cs +++ b/server/RdtClient.Service/Services/Authentication.cs @@ -57,4 +57,4 @@ public class Authentication(SignInManager signInManager, UserManag return IdentityResult.Success; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs index 6cf2499..5d84ec8 100644 --- a/server/RdtClient.Service/Services/DownloadClient.cs +++ b/server/RdtClient.Service/Services/DownloadClient.cs @@ -117,6 +117,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati { return; } + await Downloader.Cancel(); } @@ -126,6 +127,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati { return; } + await Downloader.Pause(); } @@ -135,6 +137,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati { return; } + await Downloader.Resume(); } @@ -153,4 +156,4 @@ public class DownloadClient(Download download, Torrent torrent, String destinati _totalBytesDownloadedThisSession += bytes; } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/DownloadableFileFilter.cs b/server/RdtClient.Service/Services/DownloadableFileFilter.cs index 810ec63..0620b5d 100644 --- a/server/RdtClient.Service/Services/DownloadableFileFilter.cs +++ b/server/RdtClient.Service/Services/DownloadableFileFilter.cs @@ -21,7 +21,7 @@ public class DownloadableFileFilter(ILogger logger) : ID { logger.LogDebug("File {filePath} was included after filtering", filePath); } - + return isDownloadable; } diff --git a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs index 8ab4cf3..950b377 100644 --- a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs @@ -5,17 +5,14 @@ namespace RdtClient.Service.Services.Downloaders; public class Aria2cDownloader : IDownloader { - public event EventHandler? DownloadComplete; - public event EventHandler? DownloadProgress; - private const Int32 RetryCount = 5; private readonly Aria2NetClient _aria2NetClient; + private readonly String _filePath; private readonly ILogger _logger; - private readonly String _uri; - private readonly String _filePath; private readonly String _remotePath; + private readonly String _uri; private String? _gid; @@ -49,7 +46,10 @@ public class Aria2cDownloader : IDownloader _aria2NetClient = new(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10); } - + + public event EventHandler? DownloadComplete; + public event EventHandler? DownloadProgress; + public async Task Download() { var path = Path.GetDirectoryName(_remotePath) ?? throw new($"Invalid file path {_filePath}"); @@ -68,7 +68,8 @@ public class Aria2cDownloader : IDownloader } var retryCount = 0; - while(true) + + while (true) { try { @@ -85,7 +86,7 @@ public class Aria2cDownloader : IDownloader _gid = null; } } - + _gid ??= await _aria2NetClient.AddUriAsync([ _uri ], @@ -177,20 +178,25 @@ public class Aria2cDownloader : IDownloader if (download == null) { - DownloadComplete?.Invoke(this, new() - { - Error = $"Download was not found in Aria2" - }); + DownloadComplete?.Invoke(this, + new() + { + Error = $"Download was not found in Aria2" + }); + return; } if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error") { await Remove(); - DownloadComplete?.Invoke(this, new() - { - Error = $"{download.ErrorCode}: {download.ErrorMessage}" - }); + + DownloadComplete?.Invoke(this, + new() + { + Error = $"{download.ErrorCode}: {download.ErrorMessage}" + }); + return; } @@ -201,42 +207,49 @@ public class Aria2cDownloader : IDownloader await Remove(); var retryCount = 0; + while (true) { - DownloadProgress?.Invoke(this, new() - { - BytesDone = download.CompletedLength, - BytesTotal = download.TotalLength, - Speed = download.DownloadSpeed - }); + DownloadProgress?.Invoke(this, + new() + { + BytesDone = download.CompletedLength, + BytesTotal = download.TotalLength, + Speed = download.DownloadSpeed + }); if (retryCount >= 10) { - DownloadComplete?.Invoke(this, new() - { - Error = $"File not found at {_filePath} (on aria2 {_remotePath})" - }); + DownloadComplete?.Invoke(this, + new() + { + Error = $"File not found at {_filePath} (on aria2 {_remotePath})" + }); + break; } if (File.Exists(_filePath)) { DownloadComplete?.Invoke(this, new()); + break; } await Task.Delay(1000 * retryCount); retryCount++; } + return; } - DownloadProgress?.Invoke(this, new() - { - BytesDone = download.CompletedLength, - BytesTotal = download.TotalLength, - Speed = download.DownloadSpeed - }); + DownloadProgress?.Invoke(this, + new() + { + BytesDone = download.CompletedLength, + BytesTotal = download.TotalLength, + Speed = download.DownloadSpeed + }); } private async Task Remove() @@ -284,4 +297,4 @@ public class Aria2cDownloader : IDownloader return false; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs b/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs index 15963ca..41bbe7d 100644 --- a/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/BezzadDownloader.cs @@ -6,16 +6,14 @@ namespace RdtClient.Service.Services.Downloaders; public class BezzadDownloader : IDownloader { - public event EventHandler? DownloadComplete; - public event EventHandler? DownloadProgress; - - private readonly DownloadService _downloadService; private readonly DownloadConfiguration _downloadConfiguration; + private readonly DownloadService _downloadService; + private readonly String _filePath; - private readonly String _uri; private readonly ILogger _logger; + private readonly String _uri; private Boolean _finished; @@ -65,12 +63,12 @@ public class BezzadDownloader : IDownloader } DownloadProgress.Invoke(this, - new() - { - Speed = (Int64)args.BytesPerSecondSpeed, - BytesDone = args.ReceivedBytesSize, - BytesTotal = args.TotalBytesToReceive - }); + new() + { + Speed = (Int64)args.BytesPerSecondSpeed, + BytesDone = args.ReceivedBytesSize, + BytesTotal = args.TotalBytesToReceive + }); }; _downloadService.DownloadFileCompleted += (_, args) => @@ -96,6 +94,9 @@ public class BezzadDownloader : IDownloader }; } + public event EventHandler? DownloadComplete; + public event EventHandler? DownloadProgress; + public Task Download() { _logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}"); @@ -123,6 +124,7 @@ public class BezzadDownloader : IDownloader { _logger.Debug($"Pausing download {_uri}"); _downloadService.Pause(); + return Task.CompletedTask; } @@ -130,6 +132,7 @@ public class BezzadDownloader : IDownloader { _logger.Debug($"Resuming download {_uri}"); _downloadService.Resume(); + return Task.CompletedTask; } @@ -166,7 +169,7 @@ public class BezzadDownloader : IDownloader { _downloadConfiguration.ChunkCount = Settings.Get.DownloadClient.ChunkCount; } - + _downloadConfiguration.MaximumBytesPerSecond = settingDownloadMaxSpeed; _downloadConfiguration.ParallelDownload = settingParallelCount > 1; _downloadConfiguration.ParallelCount = settingParallelCount; @@ -188,4 +191,4 @@ public class BezzadDownloader : IDownloader SetSettings(); } } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs index 1931499..5353162 100644 --- a/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/DownloadStationDownloader.cs @@ -5,7 +5,7 @@ using Synology.Api.Client.Apis.DownloadStation.Task.Models; namespace RdtClient.Service.Services.Downloaders; -class DelayProvider : IDelayProvider +internal class DelayProvider : IDelayProvider { public Task Delay(Int32 delay) { @@ -15,22 +15,25 @@ class DelayProvider : IDelayProvider public class DownloadStationDownloader : IDownloader { - public event EventHandler? DownloadComplete; - public event EventHandler? DownloadProgress; - private const Int32 RetryCount = 5; - - private readonly ISynologyClient _synologyClient; + private readonly IDelayProvider _delayProvider; + private readonly String _filePath; private readonly ILogger _logger; - private readonly String _filePath; - private readonly String _uri; private readonly String? _remotePath; - private readonly IDelayProvider _delayProvider; + + private readonly ISynologyClient _synologyClient; + private readonly String _uri; private String? _gid; - public DownloadStationDownloader(String? gid, String uri, String? remotePath, String filePath, String downloadPath, ISynologyClient synologyClient, IDelayProvider? delayProvider = null) + public DownloadStationDownloader(String? gid, + String uri, + String? remotePath, + String filePath, + String downloadPath, + ISynologyClient synologyClient, + IDelayProvider? delayProvider = null) { _logger = Log.ForContext(); _logger.Debug($"Instantiated new DownloadStation Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}"); @@ -43,48 +46,8 @@ public class DownloadStationDownloader : IDownloader _delayProvider = delayProvider ?? new DelayProvider(); } - 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); - 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; - } - 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(rootPath, category, downloadPath).Replace('\\', '/'); - } - } - - return new(gid, uri, remotePath, filePath, downloadPath, synologyClient); - } + public event EventHandler? DownloadComplete; + public event EventHandler? DownloadProgress; public async Task Cancel() { @@ -173,13 +136,6 @@ public class DownloadStationDownloader : IDownloader 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; - } - public async Task Pause() { _logger.Debug($"Pausing download {_uri} {_gid}"); @@ -200,6 +156,56 @@ 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); + 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; + } + 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(rootPath, category, downloadPath).Replace('\\', '/'); + } + } + + return new(gid, uri, remotePath, filePath, downloadPath, synologyClient); + } + + private async Task GetGidFromUri() + { + var tasks = await _synologyClient.DownloadStationApi().TaskEndpoint().ListAsync(); + + return tasks.Task?.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id; + } + public async Task Update() { if (_gid == null) diff --git a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs index b34690d..bcf3832 100644 --- a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs @@ -20,4 +20,4 @@ public interface IDownloader Task Cancel(); Task Pause(); Task Resume(); -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs index 4ea8f79..7bfd9db 100644 --- a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs @@ -6,14 +6,13 @@ namespace RdtClient.Service.Services.Downloaders; public class SymlinkDownloader(String uri, String destinationPath, String path, Provider? clientKind) : IDownloader { - public event EventHandler? DownloadComplete; - public event EventHandler? DownloadProgress; + private const Int32 MaxRetries = 10; private readonly CancellationTokenSource _cancellationToken = new(); private readonly ILogger _logger = Log.ForContext(); - - private const Int32 MaxRetries = 10; + public event EventHandler? DownloadComplete; + public event EventHandler? DownloadProgress; public async Task Download() { @@ -23,9 +22,9 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, { var filePath = new FileInfo(path); - var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd(['\\', '/']); + var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd('\\', '/'); var searchSubDirectories = rcloneMountPath.EndsWith('*'); - rcloneMountPath = rcloneMountPath.TrimEnd('*').TrimEnd(['\\', '/']); + rcloneMountPath = rcloneMountPath.TrimEnd('*').TrimEnd('\\', '/'); if (!Directory.Exists(rcloneMountPath)) { @@ -35,7 +34,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, var fileName = filePath.Name; var fileExtension = filePath.Extension; var fileNameWithoutExtension = fileName.Replace(fileExtension, ""); - var pathWithoutFileName = path.Replace(fileName, "").TrimEnd(['\\', '/']); + var pathWithoutFileName = path.Replace(fileName, "").TrimEnd('\\', '/'); var searchPath = Path.Combine(rcloneMountPath, pathWithoutFileName); List unWantedExtensions = @@ -57,7 +56,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, BytesTotal = 0, Speed = 0 }); - + String? file = null; var shouldSearch = true; @@ -65,7 +64,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, if (clientKind == Provider.AllDebrid) { var potentialFilePath = Path.Combine(rcloneMountPath, path); - + // Make sure the file exists before making any assumptions. // If this somehow fails, fallback to the search below. if (File.Exists(potentialFilePath)) @@ -95,7 +94,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, potentialFilePaths.Add(directoryInfo.Name); directoryInfo = directoryInfo.Parent; - if (directoryInfo.FullName.TrimEnd(['\\', '/']) == rcloneMountPath) + if (directoryInfo.FullName.TrimEnd('\\', '/') == rcloneMountPath) { break; } @@ -182,10 +181,11 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, } catch (Exception ex) { - DownloadComplete?.Invoke(this, new() - { - Error = ex.Message - }); + DownloadComplete?.Invoke(this, + new() + { + Error = ex.Message + }); throw; } diff --git a/server/RdtClient.Service/Services/Downloads.cs b/server/RdtClient.Service/Services/Downloads.cs index 76677c9..c610b26 100644 --- a/server/RdtClient.Service/Services/Downloads.cs +++ b/server/RdtClient.Service/Services/Downloads.cs @@ -70,7 +70,7 @@ public class Downloads(DownloadData downloadData) : IDownloads { await downloadData.UpdateError(downloadId, error); } - + public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount) { await downloadData.UpdateRetryCount(downloadId, retryCount); @@ -80,14 +80,14 @@ public class Downloads(DownloadData downloadData) : IDownloads { await downloadData.UpdateRemoteId(downloadId, remoteId); } - + public async Task DeleteForTorrent(Guid torrentId) { await downloadData.DeleteForTorrent(torrentId); } - + public async Task Reset(Guid downloadId) { await downloadData.Reset(downloadId); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/Enricher.cs b/server/RdtClient.Service/Services/Enricher.cs index a79c67d..e831852 100644 --- a/server/RdtClient.Service/Services/Enricher.cs +++ b/server/RdtClient.Service/Services/Enricher.cs @@ -12,12 +12,12 @@ public interface IEnricher } /// -/// Enriches magnet links and torrents by adding trackers from the tracker list grabber. +/// Enriches magnet links and torrents by adding trackers from the tracker list grabber. /// public sealed class Enricher(ILogger logger, ITrackerListGrabber trackerListGrabber) : IEnricher { /// - /// Add trackers from the tracker list grabber to the magnet link. + /// Add trackers from the tracker list grabber to the magnet link. /// /// Magnet link to add trackers to. Is not modified /// Magnet link with additional trackers @@ -127,7 +127,7 @@ public sealed class Enricher(ILogger logger, ITrackerListGrabber track } /// - /// Add trackers from the tracker list grabber to the .torrent file bytes. + /// Add trackers from the tracker list grabber to the .torrent file bytes. /// /// Torrent file bytes to add trackers to. Is not modified /// Torrent file bytes with additional trackers @@ -223,4 +223,4 @@ public sealed class Enricher(ILogger logger, ITrackerListGrabber track return torrentDict.Encode(); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/ITrackerListGrabber.cs b/server/RdtClient.Service/Services/ITrackerListGrabber.cs index d3ccc08..6b1223b 100644 --- a/server/RdtClient.Service/Services/ITrackerListGrabber.cs +++ b/server/RdtClient.Service/Services/ITrackerListGrabber.cs @@ -3,4 +3,4 @@ namespace RdtClient.Service.Services; public interface ITrackerListGrabber { Task GetTrackers(); -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/QBittorrent.cs b/server/RdtClient.Service/Services/QBittorrent.cs index 4cd1513..b866bec 100644 --- a/server/RdtClient.Service/Services/QBittorrent.cs +++ b/server/RdtClient.Service/Services/QBittorrent.cs @@ -206,13 +206,15 @@ public class QBittorrent(ILogger logger, Settings settings, Authent } var torrentPath = downloadPath; + if (!String.IsNullOrWhiteSpace(torrent.RdName)) { // Alldebrid stores single file torrents at the root folder. if (torrent.ClientKind == Provider.AllDebrid && torrent.Files.Count == 1) { torrentPath = Path.Combine(downloadPath, torrent.Files[0].Path); - } else + } + else { torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar; } @@ -222,29 +224,31 @@ public class QBittorrent(ILogger logger, Settings settings, Authent Int64 bytesTotal = 0; Int64 speed = 0; Double rdProgress = 0; + try { rdProgress = (torrent.RdProgress ?? 0.0) / 100.0; bytesTotal = torrent.RdSize ?? 1; - bytesDone = (Int64) (bytesTotal * rdProgress); + bytesDone = (Int64)(bytesTotal * rdProgress); speed = torrent.RdSpeed ?? 0; } catch (Exception ex) { - logger.LogError(ex, $""" - Error calculating progress: - RdProgress: {torrent.RdProgress} - RdSize: {torrent.RdSize} - RdSpeed: {torrent.RdSpeed} - """); + logger.LogError(ex, + $""" + Error calculating progress: + RdProgress: {torrent.RdProgress} + RdSize: {torrent.RdSize} + RdSpeed: {torrent.RdSpeed} + """); } if (torrent.Downloads.Count > 0) { bytesDone = torrent.Downloads.Sum(m => m.BytesDone); bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal); - speed = (Int32) torrent.Downloads.Average(m => m.Speed); - downloadProgress = bytesTotal > 0 ? (Double) bytesDone / bytesTotal : 0; + speed = (Int32)torrent.Downloads.Average(m => m.Speed); + downloadProgress = bytesTotal > 0 ? (Double)bytesDone / bytesTotal : 0; } var progress = (rdProgress + downloadProgress) / 2.0; @@ -288,7 +292,7 @@ public class QBittorrent(ILogger logger, Settings settings, Authent State = "downloading", SuperSeeding = false, Tags = "", - TimeActive = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, + TimeActive = (Int64)(DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, TotalSize = bytesTotal, Tracker = "udp://tracker.opentrackr.org:1337", UpLimit = -1, @@ -364,7 +368,7 @@ public class QBittorrent(ILogger logger, Settings settings, Authent { bytesDone = torrent.Downloads.Sum(m => m.BytesDone); bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal); - speed = (Int32) torrent.Downloads.Average(m => m.Speed); + speed = (Int32)torrent.Downloads.Average(m => m.Speed); } var result = new TorrentProperties @@ -392,7 +396,7 @@ public class QBittorrent(ILogger logger, Settings settings, Authent Seeds = 100, SeedsTotal = 100, ShareRatio = 9999, - TimeElapsed = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, + TimeElapsed = (Int64)(DateTimeOffset.UtcNow - torrent.Added).TotalSeconds, TotalDownloaded = bytesDone, TotalDownloadedSession = bytesDone, TotalSize = bytesTotal, @@ -513,8 +517,8 @@ public class QBittorrent(ILogger logger, Settings settings, Authent var allTorrents = await torrents.Get(); var torrentsToGroup = allTorrents.Where(m => !String.IsNullOrWhiteSpace(m.Category)) - .Select(m => m.Category!.ToLower()) - .ToList(); + .Select(m => m.Category!.ToLower()) + .ToList(); var categoryList = (Settings.Get.General.Categories ?? "") .Split(",", StringSplitOptions.RemoveEmptyEntries) @@ -672,4 +676,4 @@ public class QBittorrent(ILogger logger, Settings settings, Authent DlRateLimit = Settings.Get.DownloadClient.MaxSpeed }; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/RdtHub.cs b/server/RdtClient.Service/Services/RdtHub.cs index f7f2367..c2783ee 100644 --- a/server/RdtClient.Service/Services/RdtHub.cs +++ b/server/RdtClient.Service/Services/RdtHub.cs @@ -20,4 +20,4 @@ public class RdtHub : Hub Users.TryRemove(Context.ConnectionId, out _); await base.OnDisconnectedAsync(exception); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/RemoteService.cs b/server/RdtClient.Service/Services/RemoteService.cs index 8eea62c..e655da0 100644 --- a/server/RdtClient.Service/Services/RemoteService.cs +++ b/server/RdtClient.Service/Services/RemoteService.cs @@ -7,13 +7,13 @@ public class RemoteService(IHubContext hub, Torrents torrents) public async Task Update() { var allTorrents = await torrents.Get(); - + // Prevent infinite recursion when serializing foreach (var file in allTorrents.SelectMany(torrent => torrent.Downloads)) { file.Torrent = null; } - + await hub.Clients.All.SendCoreAsync("update", [ allTorrents @@ -24,4 +24,4 @@ public class RemoteService(IHubContext hub, Torrents torrents) { await hub.Clients.All.SendCoreAsync("diskSpaceStatus", [status]); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/Settings.cs b/server/RdtClient.Service/Services/Settings.cs index fcbbc7d..035a56a 100644 --- a/server/RdtClient.Service/Services/Settings.cs +++ b/server/RdtClient.Service/Services/Settings.cs @@ -46,7 +46,7 @@ public class Settings(SettingData settingData) { await settingData.ResetCache(); - LoggingLevelSwitch.MinimumLevel = Settings.Get.General.LogLevel switch + LoggingLevelSwitch.MinimumLevel = Get.General.LogLevel switch { LogLevel.Verbose => LogEventLevel.Verbose, LogLevel.Debug => LogEventLevel.Debug, @@ -56,4 +56,4 @@ public class Settings(SettingData settingData) _ => LogEventLevel.Warning }; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs index 31eacee..fb2d68d 100644 --- a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs @@ -3,9 +3,9 @@ using AllDebridNET; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RdtClient.Data.Enums; +using RdtClient.Data.Models.Data; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; -using RdtClient.Data.Models.Data; using File = AllDebridNET.File; using Torrent = RdtClient.Data.Models.Data.Torrent; @@ -51,36 +51,12 @@ public class AllDebridNetClientFactory(ILogger logger } } -public class AllDebridTorrentClient(ILogger logger, IAllDebridNetClientFactory allDebridNetClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient +public class AllDebridTorrentClient(ILogger logger, IAllDebridNetClientFactory allDebridNetClientFactory, IDownloadableFileFilter fileFilter) + : ITorrentClient { private static readonly Int64 SessionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); private static List _cache = []; - private static Int64 _sessionCounter = 0; - - private static TorrentClientTorrent Map(Magnet torrent) - { - var files = GetFiles(torrent.Files); - return new() - { - Id = torrent.Id.ToString(), - Filename = torrent.Filename ?? "", - OriginalFilename = torrent.Filename, - Hash = torrent.Hash ?? "", - Bytes = torrent.Size ?? 0, - OriginalBytes = torrent.Size ?? 0, - Host = null, - Split = 0, - Progress = (Int64)Math.Round((torrent.Downloaded ?? 0) * 100.0 / (torrent.Size ?? 1)), - Status = torrent.Status, - StatusCode = torrent.StatusCode ?? 0, - Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0), - Files = files, - Links = [], - Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0), - Speed = torrent.DownloadSpeed, - Seeders = torrent.Seeders - }; - } + private static Int64 _sessionCounter; public async Task> GetTorrents() { @@ -98,10 +74,12 @@ public class AllDebridTorrentClient(ILogger logger, IAll foreach (var result in results.Magnets ?? []) { var existing = _cache.FirstOrDefault(m => m.Id == result.Id.ToString()); + if (existing != null) { _cache.Remove(existing); } + _cache.Add(Map(result)); } } @@ -252,18 +230,20 @@ public class AllDebridTorrentClient(ILogger logger, IAll { return null; } - + Log($"Getting download links", torrent); - + var allFiles = await allDebridNetClientFactory.GetClient().Magnet.FilesAsync(Int64.Parse(torrent.RdId)); var files = GetFiles(allFiles); - - return files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes) && f.DownloadLink != null).Select(f => new DownloadInfo - { - FileName = Path.GetFileName(f.Path), - RestrictedLink = f.DownloadLink! - }).ToList(); + + return files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes) && f.DownloadLink != null) + .Select(f => new DownloadInfo + { + FileName = Path.GetFileName(f.Path), + RestrictedLink = f.DownloadLink! + }) + .ToList(); } /// @@ -271,17 +251,43 @@ public class AllDebridTorrentClient(ILogger logger, IAll { // FileName is set in GetDownlaadInfos Debug.Assert(download.FileName != null); - + return Task.FromResult(download.FileName); } + private static TorrentClientTorrent Map(Magnet torrent) + { + var files = GetFiles(torrent.Files); + + return new() + { + Id = torrent.Id.ToString(), + Filename = torrent.Filename ?? "", + OriginalFilename = torrent.Filename, + Hash = torrent.Hash ?? "", + Bytes = torrent.Size ?? 0, + OriginalBytes = torrent.Size ?? 0, + Host = null, + Split = 0, + Progress = (Int64)Math.Round(((torrent.Downloaded ?? 0) * 100.0) / (torrent.Size ?? 1)), + Status = torrent.Status, + StatusCode = torrent.StatusCode ?? 0, + Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0), + Files = files, + Links = [], + Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0), + Speed = torrent.DownloadSpeed, + Seeders = torrent.Seeders + }; + } + private async Task GetInfo(String torrentId) { var result = await allDebridNetClientFactory.GetClient().Magnet.StatusAsync(torrentId) ?? throw new($"Unable to find magnet with ID {torrentId}"); return Map(result); } - + private static List GetFiles(List? files, String parentPath = "") { if (files == null) @@ -290,32 +296,33 @@ public class AllDebridTorrentClient(ILogger logger, IAll } return files.SelectMany(file => - { - var currentPath = String.IsNullOrEmpty(parentPath) - ? file.FolderOrFileName - : Path.Combine(parentPath, file.FolderOrFileName); + { + var currentPath = String.IsNullOrEmpty(parentPath) + ? file.FolderOrFileName + : Path.Combine(parentPath, file.FolderOrFileName); - var result = new List(); + var result = new List(); - // If it's a file (has size) - if (file.Size.HasValue) - { - result.Add(new() - { - Path = currentPath, - Bytes = file.Size.Value, - DownloadLink = file.DownloadLink - }); - } + // If it's a file (has size) + if (file.Size.HasValue) + { + result.Add(new() + { + Path = currentPath, + Bytes = file.Size.Value, + DownloadLink = file.DownloadLink + }); + } - // Process sub-nodes if they exist - if (file.SubNodes != null) - { - result.AddRange(GetFiles(file.SubNodes, currentPath)); - } + // Process sub-nodes if they exist + if (file.SubNodes != null) + { + result.AddRange(GetFiles(file.SubNodes, currentPath)); + } - return result; - }).ToList(); + return result; + }) + .ToList(); } private void Log(String message, Torrent? torrent = null) @@ -336,7 +343,7 @@ public class AllDebridTorrentClient(ILogger logger, IAll { return null; } - + var directory = DownloadHelper.RemoveInvalidPathChars(torrent.RdName); var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList(); diff --git a/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs index 567b777..064321c 100644 --- a/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/DebridLinkTorrentClient.cs @@ -1,11 +1,11 @@ using System.Diagnostics; +using DebridLinkFrNET; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using DebridLinkFrNET; using RdtClient.Data.Enums; +using RdtClient.Data.Models.Data; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; -using RdtClient.Data.Models.Data; using Download = RdtClient.Data.Models.Data.Download; using Torrent = DebridLinkFrNET.Models.Torrent; @@ -13,78 +13,6 @@ namespace RdtClient.Service.Services.TorrentClients; public class DebridLinkClient(ILogger logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient { - private DebridLinkFrNETClient GetClient() - { - try - { - var apiKey = Settings.Get.Provider.ApiKey; - - if (String.IsNullOrWhiteSpace(apiKey)) - { - throw new("DebridLink API Key not set in the settings"); - } - - var httpClient = httpClientFactory.CreateClient(); - httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); - - var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient); - - return debridLinkClient; - } - catch (AggregateException ae) - { - foreach (var inner in ae.InnerExceptions) - { - logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}"); - } - - throw; - } - catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) - { - logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}"); - - throw; - } - catch (TaskCanceledException ex) - { - logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}"); - - throw; - } - } - - private TorrentClientTorrent Map(Torrent torrent) - { - return new() - { - Id = torrent.Id ?? "", - Filename = torrent.Name ?? "", - OriginalFilename = torrent.Name ?? "", - Hash = torrent.HashString ?? "", - Bytes = torrent.TotalSize, - OriginalBytes = 0, - Host = torrent.ServerId ?? "", - Split = 0, - Progress = torrent.DownloadPercent, - Status = torrent.Status.ToString(), - Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created), - Files = (torrent.Files ?? []).Select((m, i) => new TorrentClientFile - { - Path = m.Name ?? "", - Bytes = m.Size, - Id = i, - Selected = true, - DownloadLink = m.DownloadUrl - }) - .ToList(), - Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(), - Ended = null, - Speed = torrent.UploadSpeed, - Seeders = torrent.PeersConnected, - }; - } - public async Task> GetTorrents() { var page = 0; @@ -92,7 +20,7 @@ public class DebridLinkClient(ILogger logger, IHttpClientFacto while (true) { - var pagedResults = await GetClient().Seedbox.ListAsync(null,page, 50); + var pagedResults = await GetClient().Seedbox.ListAsync(null, page, 50); results.AddRange(pagedResults); @@ -110,7 +38,7 @@ public class DebridLinkClient(ILogger logger, IHttpClientFacto public async Task GetUser() { var user = await GetClient().Account.Infos(); - + return new() { Username = user.Username, @@ -198,16 +126,16 @@ public class DebridLinkClient(ILogger logger, IHttpClientFacto torrent.RdSeeders = rdTorrent.Seeders; torrent.RdStatusRaw = rdTorrent.Status; - /* - 0 Torrent is stopped - 1 Torrent is queued to verify local data - 2 Torrent is verifying local data - 3 Torrent is queued to download - 4 Torrent is downloading - 5 Torrent is queued to seed - 6 Torrent is seeding - 100 Torrent is stored - */ + /* + 0 Torrent is stopped + 1 Torrent is queued to verify local data + 2 Torrent is verifying local data + 3 Torrent is queued to download + 4 Torrent is downloading + 5 Torrent is queued to seed + 6 Torrent is seeding + 100 Torrent is stored + */ torrent.RdStatus = rdTorrent.Status switch { @@ -260,6 +188,87 @@ public class DebridLinkClient(ILogger logger, IHttpClientFacto .ToList(); } + /// + public Task GetFileName(Download download) + { + // FileName is set in GetDownlaadInfos + Debug.Assert(download.FileName != null); + + return Task.FromResult(download.FileName); + } + + private DebridLinkFrNETClient GetClient() + { + try + { + var apiKey = Settings.Get.Provider.ApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) + { + throw new("DebridLink API Key not set in the settings"); + } + + var httpClient = httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); + + var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient); + + return debridLinkClient; + } + catch (AggregateException ae) + { + foreach (var inner in ae.InnerExceptions) + { + logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}"); + } + + throw; + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) + { + logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}"); + + throw; + } + catch (TaskCanceledException ex) + { + logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}"); + + throw; + } + } + + private TorrentClientTorrent Map(Torrent torrent) + { + return new() + { + Id = torrent.Id ?? "", + Filename = torrent.Name ?? "", + OriginalFilename = torrent.Name ?? "", + Hash = torrent.HashString ?? "", + Bytes = torrent.TotalSize, + OriginalBytes = 0, + Host = torrent.ServerId ?? "", + Split = 0, + Progress = torrent.DownloadPercent, + Status = torrent.Status.ToString(), + Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created), + Files = (torrent.Files ?? []).Select((m, i) => new TorrentClientFile + { + Path = m.Name ?? "", + Bytes = m.Size, + Id = i, + Selected = true, + DownloadLink = m.DownloadUrl + }) + .ToList(), + Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(), + Ended = null, + Speed = torrent.UploadSpeed, + Seeders = torrent.PeersConnected + }; + } + private async Task GetInfo(String torrentId) { var result = await GetClient().Seedbox.ListAsync(torrentId); @@ -277,15 +286,6 @@ public class DebridLinkClient(ILogger logger, IHttpClientFacto logger.LogDebug(message); } - /// - public Task GetFileName(Download download) - { - // FileName is set in GetDownlaadInfos - Debug.Assert(download.FileName != null); - - return Task.FromResult(download.FileName); - } - public static String? GetSymlinkPath(Data.Models.Data.Torrent torrent, Download download) { if (torrent.RdName == null || download.FileName == null) @@ -301,4 +301,4 @@ public class DebridLinkClient(ILogger logger, IHttpClientFacto return Path.Combine(torrent.RdName, download.FileName); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs index 8cd0792..9f58084 100644 --- a/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs @@ -10,24 +10,28 @@ public interface ITorrentClient Task AddMagnet(String magnetLink); Task AddFile(Byte[] bytes); Task> GetAvailableFiles(String hash); + /// - /// Tell the debrid provider which files to download. + /// Tell the debrid provider which files to download. /// /// - /// Not all providers support this feature. + /// Not all providers support this feature. /// /// The torrent to select files for /// Number of files selected Task SelectFiles(Torrent torrent); + Task Delete(String torrentId); Task Unrestrict(String link); Task UpdateData(Torrent torrent, TorrentClientTorrent? torrentClientTorrent); Task?> GetDownloadInfos(Torrent torrent); + /// - /// To be called only when . is not set by - /// + /// To be called only when . + /// is not set by + /// /// /// The download to get the filename of /// The filename of the download Task GetFileName(Download download); -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs index 1c7014d..f79c36a 100644 --- a/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs @@ -3,78 +3,19 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using PremiumizeNET; using RdtClient.Data.Enums; +using RdtClient.Data.Models.Data; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; -using RdtClient.Data.Models.Data; using Torrent = RdtClient.Data.Models.Data.Torrent; namespace RdtClient.Service.Services.TorrentClients; public class PremiumizeTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient { - private PremiumizeNETClient GetClient() - { - try - { - var apiKey = Settings.Get.Provider.ApiKey; - - if (String.IsNullOrWhiteSpace(apiKey)) - { - throw new("Premiumize API Key not set in the settings"); - } - - var httpClient = httpClientFactory.CreateClient(); - httpClient.Timeout = TimeSpan.FromSeconds(10); - - var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient); - - return premiumizeNetClient; - } - catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) - { - logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}"); - - throw; - } - catch (TaskCanceledException ex) - { - logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}"); - - throw; - } - } - - private static TorrentClientTorrent Map(Transfer transfer) - { - return new() - { - Id = transfer.Id, - Filename = transfer.Name, - OriginalFilename = transfer.Name, - Hash = transfer.Src, - Bytes = 0, - OriginalBytes = 0, - Host = null, - Split = 0, - Progress = (Int64) ((transfer.Progress ?? 1.0) * 100.0), - Status = transfer.Status, - Message = transfer.Message, - StatusCode = 0, - Added = null, - Files = [], - Links = - [ - transfer.FolderId - ], - Ended = null, - Speed = 0, - Seeders = 0 - }; - } - public async Task> GetTorrents() { var results = await GetClient().Transfers.ListAsync(); + return results.Select(Map).ToList(); } @@ -217,7 +158,7 @@ public class PremiumizeTorrentClient(ILogger logger, IH Log($"Found transfer {transfer.Name} ({transfer.Id})", torrent); var downloadInfos = await GetAllDownloadInfos(torrent, transfer.FolderId); - + if (!String.IsNullOrWhiteSpace(transfer.FileId)) { var file = await GetClient().Items.DetailsAsync(transfer.FileId); @@ -229,7 +170,11 @@ public class PremiumizeTorrentClient(ILogger logger, IH Log($"File {file.Name} ({file.Id}) does not contain a link", torrent); } - downloadInfos.Add(new () {RestrictedLink = file.Link, FileName = file.Name }); + downloadInfos.Add(new() + { + RestrictedLink = file.Link, + FileName = file.Name + }); } foreach (var info in downloadInfos) @@ -245,10 +190,70 @@ public class PremiumizeTorrentClient(ILogger logger, IH { // FileName is set in GetDownlaadInfos Debug.Assert(download.FileName != null); - + return Task.FromResult(download.FileName); } + private PremiumizeNETClient GetClient() + { + try + { + var apiKey = Settings.Get.Provider.ApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) + { + throw new("Premiumize API Key not set in the settings"); + } + + var httpClient = httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(10); + + var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient); + + return premiumizeNetClient; + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) + { + logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}"); + + throw; + } + catch (TaskCanceledException ex) + { + logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}"); + + throw; + } + } + + private static TorrentClientTorrent Map(Transfer transfer) + { + return new() + { + Id = transfer.Id, + Filename = transfer.Name, + OriginalFilename = transfer.Name, + Hash = transfer.Src, + Bytes = 0, + OriginalBytes = 0, + Host = null, + Split = 0, + Progress = (Int64)((transfer.Progress ?? 1.0) * 100.0), + Status = transfer.Status, + Message = transfer.Message, + StatusCode = 0, + Added = null, + Files = [], + Links = + [ + transfer.FolderId + ], + Ended = null, + Speed = 0, + Seeders = 0 + }; + } + private async Task GetInfo(String id) { var results = await GetClient().Transfers.ListAsync(); @@ -259,7 +264,6 @@ public class PremiumizeTorrentClient(ILogger logger, IH private async Task> GetAllDownloadInfos(Torrent torrent, String folderId) { - if (String.IsNullOrWhiteSpace(folderId)) { return []; @@ -270,6 +274,7 @@ public class PremiumizeTorrentClient(ILogger logger, IH if (folder.Content == null) { Log($"Found no items in folder {folder.Name} ({folderId})", torrent); + return []; } @@ -295,7 +300,11 @@ public class PremiumizeTorrentClient(ILogger logger, IH Log($"Found item {item.Name} in folder {folder.Name} ({folderId})", torrent); - downloadInfos.Add(new () { RestrictedLink = item.Link, FileName = item.Name}); + downloadInfos.Add(new() + { + RestrictedLink = item.Link, + FileName = item.Name + }); } else if (item.Type == "folder") { @@ -327,4 +336,4 @@ public class PremiumizeTorrentClient(ILogger logger, IH logger.LogDebug(message); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs index f5b2289..df27d90 100644 --- a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs @@ -15,84 +15,6 @@ public class RealDebridTorrentClient(ILogger logger, IH { private TimeSpan? _offset; - private RdNetClient GetClient() - { - try - { - var apiKey = Settings.Get.Provider.ApiKey; - - if (String.IsNullOrWhiteSpace(apiKey)) - { - throw new("Real-Debrid API Key not set in the settings"); - } - - var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT); - httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); - - var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname); - rdtNetClient.UseApiAuthentication(apiKey); - - // Get the server time to fix up the timezones on results - if (_offset == null) - { - var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result; - _offset = serverTime.Offset; - } - - return rdtNetClient; - } - catch (AggregateException ae) - { - foreach (var inner in ae.InnerExceptions) - { - logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}"); - } - - throw; - } - catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) - { - logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}"); - - throw; - } - catch (TaskCanceledException ex) - { - logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}"); - - throw; - } - } - - private TorrentClientTorrent Map(Torrent torrent) - { - return new() - { - Id = torrent.Id, - Filename = torrent.Filename, - OriginalFilename = torrent.OriginalFilename, - Hash = torrent.Hash, - Bytes = torrent.Bytes, - OriginalBytes = torrent.OriginalBytes, - Host = torrent.Host, - Split = torrent.Split, - Progress = torrent.Progress, - Status = torrent.Status, - Added = ChangeTimeZone(torrent.Added)!.Value, - Files = (torrent.Files ?? []).Select(m => new TorrentClientFile - { - Path = m.Path, - Bytes = m.Bytes, - Id = m.Id, - Selected = m.Selected - }).ToList(), - Links = torrent.Links, - Ended = ChangeTimeZone(torrent.Ended), - Speed = torrent.Speed, - Seeders = torrent.Seeders, - }; - } - public async Task> GetTorrents() { var offset = 0; @@ -118,7 +40,7 @@ public class RealDebridTorrentClient(ILogger logger, IH public async Task GetUser() { var user = await GetClient().User.GetAsync(); - + return new() { Username = user.Username, @@ -167,7 +89,6 @@ public class RealDebridTorrentClient(ILogger logger, IH files = [.. torrent.Files]; } - files = files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes)).ToList(); Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent); @@ -310,8 +231,9 @@ public class RealDebridTorrentClient(ILogger logger, IH Log($"{link}", torrent); } - Log($"Torrent has {torrent.Files.Count(m => m.Selected)} selected files out of {torrent.Files.Count} files, found {downloadLinks.Count} links, torrent ended: {torrent.RdEnded}", torrent); - + Log($"Torrent has {torrent.Files.Count(m => m.Selected)} selected files out of {torrent.Files.Count} files, found {downloadLinks.Count} links, torrent ended: {torrent.RdEnded}", + torrent); + // Check if all the links are set that have been selected if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count) { @@ -344,7 +266,7 @@ public class RealDebridTorrentClient(ILogger logger, IH } Log($"Did not find any suiteable download links", torrent); - + return null; } @@ -361,6 +283,85 @@ public class RealDebridTorrentClient(ILogger logger, IH return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last())); } + private RdNetClient GetClient() + { + try + { + var apiKey = Settings.Get.Provider.ApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) + { + throw new("Real-Debrid API Key not set in the settings"); + } + + var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT); + httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); + + var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname); + rdtNetClient.UseApiAuthentication(apiKey); + + // Get the server time to fix up the timezones on results + if (_offset == null) + { + var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result; + _offset = serverTime.Offset; + } + + return rdtNetClient; + } + catch (AggregateException ae) + { + foreach (var inner in ae.InnerExceptions) + { + logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}"); + } + + throw; + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) + { + logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}"); + + throw; + } + catch (TaskCanceledException ex) + { + logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}"); + + throw; + } + } + + private TorrentClientTorrent Map(Torrent torrent) + { + return new() + { + Id = torrent.Id, + Filename = torrent.Filename, + OriginalFilename = torrent.OriginalFilename, + Hash = torrent.Hash, + Bytes = torrent.Bytes, + OriginalBytes = torrent.OriginalBytes, + Host = torrent.Host, + Split = torrent.Split, + Progress = torrent.Progress, + Status = torrent.Status, + Added = ChangeTimeZone(torrent.Added)!.Value, + Files = (torrent.Files ?? []).Select(m => new TorrentClientFile + { + Path = m.Path, + Bytes = m.Bytes, + Id = m.Id, + Selected = m.Selected + }) + .ToList(), + Links = torrent.Links, + Ended = ChangeTimeZone(torrent.Ended), + Speed = torrent.Speed, + Seeders = torrent.Seeders + }; + } + private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) { if (_offset == null) @@ -387,4 +388,4 @@ public class RealDebridTorrentClient(ILogger logger, IH logger.LogDebug(message); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs index 4994e72..a28d42e 100644 --- a/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs @@ -1,106 +1,33 @@ using System.Diagnostics; using Microsoft.Extensions.Logging; +using MonoTorrent; using Newtonsoft.Json; -using TorBoxNET; using RdtClient.Data.Enums; -using RdtClient.Data.Models.TorrentClient; using RdtClient.Data.Models.Data; +using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; +using TorBoxNET; +using Torrent = RdtClient.Data.Models.Data.Torrent; namespace RdtClient.Service.Services.TorrentClients; public class TorBoxTorrentClient(ILogger logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient { private TimeSpan? _offset; - private TorBoxNetClient GetClient() - { - try - { - var apiKey = Settings.Get.Provider.ApiKey; - - if (String.IsNullOrWhiteSpace(apiKey)) - { - throw new("TorBox API Key not set in the settings"); - } - - var httpClient = httpClientFactory.CreateClient(); - httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); - - var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5); - torBoxNetClient.UseApiAuthentication(apiKey); - - // Get the server time to fix up the timezones on results - if (_offset == null) - { - var serverTime = DateTimeOffset.UtcNow; - _offset = serverTime.Offset; - } - - return torBoxNetClient; - } - catch (AggregateException ae) - { - foreach (var inner in ae.InnerExceptions) - { - logger.LogError(inner, $"The connection to TorBox has failed: {inner.Message}"); - } - - throw; - } - catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) - { - logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}"); - - throw; - } - catch (TaskCanceledException ex) - { - logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}"); - - throw; - } - } - - private TorrentClientTorrent Map(TorrentInfoResult torrent) - { - return new() - { - Id = torrent.Hash, - Filename = torrent.Name, - OriginalFilename = torrent.Name, - Hash = torrent.Hash, - Bytes = torrent.Size, - OriginalBytes = torrent.Size, - Host = torrent.DownloadPresent.ToString(), - Split = 0, - Progress = (Int64)(torrent.Progress * 100.0), - Status = torrent.DownloadState, - Added = ChangeTimeZone(torrent.CreatedAt)!.Value, - Files = (torrent.Files ?? []).Select(m => new TorrentClientFile - { - Path = String.Join("/", m.Name.Split('/').Skip(1)), - Bytes = m.Size, - Id = m.Id, - Selected = true - }).ToList(), - Links = [], - Ended = ChangeTimeZone(torrent.UpdatedAt), - Speed = torrent.DownloadSpeed, - Seeders = torrent.Seeds, - }; - } public async Task> GetTorrents() { var torrents = new List(); var currentTorrents = await GetClient().Torrents.GetCurrentAsync(true); + if (currentTorrents != null) { torrents.AddRange(currentTorrents); } var queuedTorrents = await GetClient().Torrents.GetQueuedAsync(true); + if (queuedTorrents != null) { torrents.AddRange(queuedTorrents); @@ -124,11 +51,12 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien { var user = await GetClient().User.GetAsync(true); - var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, false); + var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3); if (result.Error == "ACTIVE_LIMIT") { - var magnetLinkInfo = MonoTorrent.MagnetLink.Parse(magnetLink); + var magnetLinkInfo = MagnetLink.Parse(magnetLink); + return magnetLinkInfo.InfoHashes.V1!.ToHex().ToLowerInvariant(); } @@ -140,11 +68,13 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien var user = await GetClient().User.GetAsync(true); var result = await GetClient().Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3); + if (result.Error == "ACTIVE_LIMIT") { using var stream = new MemoryStream(bytes); var torrent = await MonoTorrent.Torrent.LoadAsync(stream); + return torrent.InfoHashes.V1!.ToHex().ToLowerInvariant(); } @@ -153,15 +83,16 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien public async Task> GetAvailableFiles(String hash) { - var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, listFiles: true); + var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, true); if (availability.Data != null && availability.Data.Count > 0) { return (availability.Data[0]?.Files ?? []).Select(file => new TorrentClientAvailableFile - { - Filename = file.Name, - Filesize = file.Size - }).ToList(); + { + Filename = file.Name, + Filesize = file.Size + }) + .ToList(); } return []; @@ -265,7 +196,6 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien _ => TorrentStatus.Error }; } - } catch (Exception ex) { @@ -284,7 +214,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien public async Task?> GetDownloadInfos(Torrent torrent) { - var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, skipCache: true); + var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, true); var downloadableFiles = torrent.Files.Where(file => fileFilter.IsDownloadable(torrent, file.Path, file.Bytes)).ToList(); if (downloadableFiles.Count == torrent.Files.Count && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink && Settings.Get.Provider.PreferZippedDownloads) @@ -316,10 +246,89 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien { // FileName is set in GetDownlaadInfos Debug.Assert(download.FileName != null); - + return Task.FromResult(download.FileName); } + private TorBoxNetClient GetClient() + { + try + { + var apiKey = Settings.Get.Provider.ApiKey; + + if (String.IsNullOrWhiteSpace(apiKey)) + { + throw new("TorBox API Key not set in the settings"); + } + + var httpClient = httpClientFactory.CreateClient(); + httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout); + + var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5); + torBoxNetClient.UseApiAuthentication(apiKey); + + // Get the server time to fix up the timezones on results + if (_offset == null) + { + var serverTime = DateTimeOffset.UtcNow; + _offset = serverTime.Offset; + } + + return torBoxNetClient; + } + catch (AggregateException ae) + { + foreach (var inner in ae.InnerExceptions) + { + logger.LogError(inner, $"The connection to TorBox has failed: {inner.Message}"); + } + + throw; + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) + { + logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}"); + + throw; + } + catch (TaskCanceledException ex) + { + logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}"); + + throw; + } + } + + private TorrentClientTorrent Map(TorrentInfoResult torrent) + { + return new() + { + Id = torrent.Hash, + Filename = torrent.Name, + OriginalFilename = torrent.Name, + Hash = torrent.Hash, + Bytes = torrent.Size, + OriginalBytes = torrent.Size, + Host = torrent.DownloadPresent.ToString(), + Split = 0, + Progress = (Int64)(torrent.Progress * 100.0), + Status = torrent.DownloadState, + Added = ChangeTimeZone(torrent.CreatedAt)!.Value, + Files = (torrent.Files ?? []).Select(m => new TorrentClientFile + { + Path = String.Join("/", m.Name.Split('/').Skip(1)), + Bytes = m.Size, + Id = m.Id, + Selected = true + }) + .ToList(), + Links = [], + Ended = ChangeTimeZone(torrent.UpdatedAt), + Speed = torrent.DownloadSpeed, + Seeders = torrent.Seeds + }; + } + private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) { if (_offset == null) @@ -332,7 +341,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien private async Task GetInfo(String torrentHash) { - var result = await GetClient().Torrents.GetHashInfoAsync(torrentHash, skipCache: true); + var result = await GetClient().Torrents.GetHashInfoAsync(torrentHash, true); return Map(result!); } @@ -346,6 +355,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien var innerFolder = Directory.GetDirectories(hashDir)[0]; var moveDir = extractPath; + if (!extractPath.EndsWith(_torrent.RdName!)) { moveDir = hashDir; diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 50e87ed..1a4e1d2 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -1,13 +1,13 @@ -using Aria2NET; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Text.Json; +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; namespace RdtClient.Service.Services; @@ -16,13 +16,13 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow public static readonly ConcurrentDictionary ActiveDownloadClients = new(); public static readonly ConcurrentDictionary ActiveUnpackClients = new(); - public static Boolean IsPausedForLowDiskSpace { get; set; } - private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) }; + public static Boolean IsPausedForLowDiskSpace { get; set; } + public async Task Initialize() { Log("Initializing TorrentRunner"); @@ -73,25 +73,30 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow if (String.IsNullOrWhiteSpace(Settings.Get.Provider.ApiKey)) { Log($"No RealDebridApiKey set in settings"); + return; } var settingDownloadLimit = Settings.Get.General.DownloadLimit; + if (settingDownloadLimit < 1) { settingDownloadLimit = 1; } var settingUnpackLimit = Settings.Get.General.UnpackLimit; + if (settingUnpackLimit < 0) { settingUnpackLimit = 0; } var settingDownloadPath = Settings.Get.DownloadClient.DownloadPath; + if (String.IsNullOrWhiteSpace(settingDownloadPath)) { logger.LogError("No DownloadPath set in settings"); + return; } @@ -163,7 +168,10 @@ 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); + + 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) { @@ -246,6 +254,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow { await activeDownload.Value.Cancel(); ActiveDownloadClients.TryRemove(activeDownload.Key, out _); + break; } } @@ -258,6 +267,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow { activeUnpacks.Value.Cancel(); ActiveUnpackClients.TryRemove(activeUnpacks.Key, out _); + break; } } @@ -273,6 +283,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow { await torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount); Log($"Torrent reach max retry count"); + continue; } @@ -567,14 +578,14 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow } // Check if we have reached the download limit, if so queue the download, but don't start it. - if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit) + if (ActiveUnpackClients.Count >= settingUnpackLimit) { Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent); continue; } - if (TorrentRunner.ActiveUnpackClients.ContainsKey(download.DownloadId)) + if (ActiveUnpackClients.ContainsKey(download.DownloadId)) { Log($"Not starting unpack because this download is already active", download, torrent); @@ -596,7 +607,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow // Start the unpacking process var unpackClient = new UnpackClient(download, downloadPath); - if (TorrentRunner.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient)) + if (ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient)) { Log($"Starting unpack", download, torrent); @@ -647,8 +658,8 @@ 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) || - torrent.RdStatus == TorrentStatus.Finished && torrent.HostDownloadAction == TorrentHostDownloadAction.DownloadNone) + if (torrent.Downloads.Count > 0 || + (torrent.RdStatus == TorrentStatus.Finished && torrent.HostDownloadAction == TorrentHostDownloadAction.DownloadNone)) { var completeCount = torrent.Downloads.Count(m => m.Completed != null); @@ -659,7 +670,7 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow if (totalDownloadBytes > 0) { - completePerc = (Int32)((Double)totalDoneBytes / totalDownloadBytes * 100); + completePerc = (Int32)(((Double)totalDoneBytes / totalDownloadBytes) * 100); } if (completeCount == torrent.Downloads.Count) @@ -737,4 +748,4 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow logger.LogError(message); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs index 1d75498..66dd092 100644 --- a/server/RdtClient.Service/Services/Torrents.cs +++ b/server/RdtClient.Service/Services/Torrents.cs @@ -38,6 +38,8 @@ public class Torrents( ReferenceHandler = ReferenceHandler.IgnoreCycles }; + private static readonly SemaphoreSlim TorrentResetLock = new(1, 1); + private ITorrentClient TorrentClient { get @@ -54,8 +56,6 @@ public class Torrents( } } - private static readonly SemaphoreSlim TorrentResetLock = new(1, 1); - public async Task> Get() { var torrents = await torrentData.Get(); @@ -112,6 +112,7 @@ public class Torrents( { var enriched = await enricher.EnrichMagnetLink(magnetLink); MagnetLink magnet; + try { magnet = MagnetLink.Parse(magnetLink); @@ -119,6 +120,7 @@ public class Torrents( catch (Exception ex) { logger.LogError(ex, "{ex.Message}, trying to parse {magnetLink}", ex.Message, magnetLink); + throw new($"{ex.Message}, trying to parse {magnetLink}"); } @@ -142,6 +144,7 @@ public class Torrents( if (bannedUrls.Count > 0) { var bannedUrlsString = String.Join(", ", bannedUrls); + throw new($"Cannot add torrent, the torrent contains banned trackers: {bannedUrlsString}."); } } @@ -213,12 +216,13 @@ public class Torrents( if (bannedUrls.Count > 0) { var bannedUrlsString = String.Join(", ", bannedUrls); + throw new($"Cannot add torrent, the torrent contains banned trackers: {bannedUrlsString}."); } } } } - + torrent.RdStatus = TorrentStatus.Queued; torrent.RdName = monoTorrent.Name; @@ -262,9 +266,11 @@ public class Torrents( { case String magnetLink: await File.WriteAllTextAsync(copyFileName, magnetLink); + break; case Byte[] torrentFile: await File.WriteAllBytesAsync(copyFileName, torrentFile); + break; } } @@ -276,7 +282,7 @@ public class Torrents( } /// - /// Adds torrent in database to debrid provider and updates database accordingly. + /// Adds torrent in database to debrid provider and updates database accordingly. /// /// The torrent from the database to upload to the debrid provider /// Updated torrent @@ -373,7 +379,7 @@ public class Torrents( } /// - /// Logs a message to the console, sets the error on the torrent and ensures it is not retried. + /// Logs a message to the console, sets the error on the torrent and ensures it is not retried. /// /// The torrent to mark as "All files excluded" private async Task MarkAllFilesExcluded(Torrent torrent) @@ -511,8 +517,9 @@ public class Torrents( } /// - /// To be called only when . is not set by - /// + /// To be called only when . + /// is not set by + /// /// public async Task RetrieveFileName(Guid downloadId) { @@ -566,7 +573,8 @@ public class Torrents( { Category = Settings.Get.Provider.Default.Category, DownloadClient = Settings.Get.DownloadClient.Client, - DownloadAction = Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, + DownloadAction = + Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, HostDownloadAction = Settings.Get.Provider.Default.HostDownloadAction, FinishedActionDelay = Settings.Get.Provider.Default.FinishedActionDelay, FinishedAction = Settings.Get.Provider.Default.FinishedAction, @@ -887,6 +895,7 @@ public class Torrents( outputSb.AppendLine(data.Trim()); }; + process.ErrorDataReceived += (_, data) => { if (data == null) @@ -967,4 +976,4 @@ public class Torrents( logger.LogDebug(message); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Services/TrackerListGrabber.cs b/server/RdtClient.Service/Services/TrackerListGrabber.cs index 88b560f..b93e48d 100644 --- a/server/RdtClient.Service/Services/TrackerListGrabber.cs +++ b/server/RdtClient.Service/Services/TrackerListGrabber.cs @@ -1,6 +1,6 @@ +using System.Reflection; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; -using System.Reflection; namespace RdtClient.Service.Services; @@ -8,10 +8,10 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac { private const String CacheKey = "TrackerList"; - private Int32? _lastExpirationMinutes; - private static readonly SemaphoreSlim Semaphore = new(1, 1); + private Int32? _lastExpirationMinutes; + public async Task GetTrackers() { var trackerUrlList = Settings.Get.General.TrackerEnrichmentList; @@ -148,6 +148,7 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac { logger.LogDebug("Rejected tracker: {TrackerUrl} - Reason: Invalid format or unsupported scheme.", t); trackerRejectionCount++; + return false; } diff --git a/server/RdtClient.Service/Services/UnpackClient.cs b/server/RdtClient.Service/Services/UnpackClient.cs index 5efe3b0..911c95f 100644 --- a/server/RdtClient.Service/Services/UnpackClient.cs +++ b/server/RdtClient.Service/Services/UnpackClient.cs @@ -1,4 +1,5 @@ -using RdtClient.Data.Models.Data; +using RdtClient.Data.Enums; +using RdtClient.Data.Models.Data; using RdtClient.Service.Helpers; using RdtClient.Service.Services.TorrentClients; using SharpCompress.Archives; @@ -10,16 +11,15 @@ namespace RdtClient.Service.Services; public class UnpackClient(Download download, String destinationPath) { + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private readonly Torrent _torrent = download.Torrent ?? throw new($"Torrent is null"); public Boolean Finished { get; private set; } public String? Error { get; private set; } public Int32 Progess { get; private set; } - private readonly Torrent _torrent = download.Torrent ?? throw new($"Torrent is null"); - - private readonly CancellationTokenSource _cancellationTokenSource = new(); - public void Start() { Progess = 0; @@ -104,7 +104,7 @@ public class UnpackClient(Download download, String destinationPath) await FileHelper.Delete(filePath); } - if (_torrent.ClientKind == Data.Enums.Provider.TorBox) + if (_torrent.ClientKind == Provider.TorBox) { TorBoxTorrentClient.MoveHashDirContents(extractPath, _torrent); } @@ -119,7 +119,6 @@ public class UnpackClient(Download download, String destinationPath) } } - private static async Task> GetArchiveFiles(String filePath) { await using Stream stream = File.OpenRead(filePath); @@ -127,6 +126,7 @@ public class UnpackClient(Download download, String destinationPath) var extension = Path.GetExtension(filePath); IArchive archive; + if (extension == ".zip") { archive = ZipArchive.OpenArchive(stream); @@ -155,6 +155,7 @@ public class UnpackClient(Download download, String destinationPath) var extension = Path.GetExtension(filePath); IArchive archive; + if (extension == ".zip") { archive = ZipArchive.OpenArchive(fi); @@ -174,4 +175,4 @@ public class UnpackClient(Download download, String destinationPath) GC.Collect(); } -} \ No newline at end of file +} diff --git a/server/RdtClient.Service/Wrappers/IProcess.cs b/server/RdtClient.Service/Wrappers/IProcess.cs index 84f4cfe..49c0e05 100644 --- a/server/RdtClient.Service/Wrappers/IProcess.cs +++ b/server/RdtClient.Service/Wrappers/IProcess.cs @@ -4,11 +4,10 @@ namespace RdtClient.Service.Wrappers; public interface IProcess : IDisposable { + public ProcessStartInfo StartInfo { get; set; } event EventHandler? OutputDataReceived; event EventHandler? ErrorDataReceived; - public ProcessStartInfo StartInfo { get; set; } - void BeginOutputReadLine(); void BeginErrorReadLine(); Boolean WaitForExit(Int32 milliseconds); diff --git a/server/RdtClient.Service/Wrappers/ProcessFactory.cs b/server/RdtClient.Service/Wrappers/ProcessFactory.cs index ba8814f..43c996b 100644 --- a/server/RdtClient.Service/Wrappers/ProcessFactory.cs +++ b/server/RdtClient.Service/Wrappers/ProcessFactory.cs @@ -1,6 +1,6 @@ namespace RdtClient.Service.Wrappers; -public class ProcessFactory: IProcessFactory +public class ProcessFactory : IProcessFactory { public IProcess NewProcess() { diff --git a/server/RdtClient.Web/Controllers/AuthController.cs b/server/RdtClient.Web/Controllers/AuthController.cs index f97566b..5e5d8de 100644 --- a/server/RdtClient.Web/Controllers/AuthController.cs +++ b/server/RdtClient.Web/Controllers/AuthController.cs @@ -26,10 +26,10 @@ public class AuthController(Authentication authentication, Settings settings) : { return StatusCode(402, "Setup required"); } - + return StatusCode(403); } - + return Ok(); } @@ -49,7 +49,7 @@ public class AuthController(Authentication authentication, Settings settings) : { return StatusCode(401); } - + if (String.IsNullOrEmpty(request.UserName) || String.IsNullOrEmpty(request.Password)) { return BadRequest("Invalid UserName or Password"); @@ -61,7 +61,7 @@ public class AuthController(Authentication authentication, Settings settings) : { return BadRequest(registerResult.Errors.First().Description); } - + await authentication.Login(request.UserName, request.Password); return Ok(); @@ -119,15 +119,16 @@ public class AuthController(Authentication authentication, Settings settings) : return Ok(); } - + [Route("Logout")] [HttpPost] public async Task Logout() { await authentication.Logout(); + return Ok(); } - + [Route("Update")] [HttpPost] [Authorize(Policy = "AuthSetting")] @@ -170,4 +171,4 @@ public class AuthControllerUpdateRequest { public String? UserName { get; set; } public String? Password { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Web/Controllers/QBittorrentController.cs b/server/RdtClient.Web/Controllers/QBittorrentController.cs index 3ca48e2..4298924 100644 --- a/server/RdtClient.Web/Controllers/QBittorrentController.cs +++ b/server/RdtClient.Web/Controllers/QBittorrentController.cs @@ -3,13 +3,13 @@ using Microsoft.AspNetCore.Mvc; using RdtClient.Data.Enums; using RdtClient.Data.Models.QBittorrent; using RdtClient.Service.Services; - +using RealDebridException = RDNET.RealDebridException; namespace RdtClient.Web.Controllers; /// -/// This API behaves as a regular QBittorrent 4+ API -/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1) +/// This API behaves as a regular QBittorrent 4+ API +/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1) /// [ApiController] [Route("api/v2")] @@ -41,7 +41,7 @@ public class QBittorrentController(ILogger logger, QBitto return Ok("Fails."); } - + [AllowAnonymous] [Route("auth/login")] [HttpPost] @@ -59,6 +59,7 @@ public class QBittorrentController(ILogger logger, QBitto logger.LogDebug($"Auth logout"); await qBittorrent.AuthLogout(); + return Ok(); } @@ -92,6 +93,7 @@ public class QBittorrentController(ILogger logger, QBitto Qt = "5.15.2", Zlib = "1.2.11" }; + return Ok(result); } @@ -111,6 +113,7 @@ public class QBittorrentController(ILogger logger, QBitto public async Task> AppPreferences() { var result = await qBittorrent.AppPreferences(); + return Ok(result); } @@ -130,9 +133,10 @@ public class QBittorrentController(ILogger logger, QBitto public ActionResult AppDefaultSavePath() { var result = Settings.AppDefaultSavePath; + return Ok(result); } - + [Authorize(Policy = "AuthSetting")] [Route("torrents/info")] [HttpGet] @@ -339,7 +343,7 @@ public class QBittorrentController(ILogger logger, QBitto return BadRequest($"Invalid torrent link format {url}"); } } - catch (RDNET.RealDebridException ex) + catch (RealDebridException ex) { // Infringing file. if (ex.ErrorCode == 35) @@ -369,7 +373,7 @@ public class QBittorrentController(ILogger logger, QBitto await qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority); } } - + if (request.Urls != null) { return await TorrentsAdd(request); @@ -386,7 +390,7 @@ public class QBittorrentController(ILogger logger, QBitto { return Ok(); } - + [Authorize(Policy = "AuthSetting")] [Route("torrents/setCategory")] [HttpGet] @@ -450,7 +454,7 @@ public class QBittorrentController(ILogger logger, QBitto { return Ok(); } - + [Authorize(Policy = "AuthSetting")] [Route("torrents/removeCategories")] [HttpGet] @@ -480,7 +484,7 @@ public class QBittorrentController(ILogger logger, QBitto { return Ok(); } - + [Authorize(Policy = "AuthSetting")] [Route("torrents/tags")] [HttpGet] @@ -535,7 +539,7 @@ public class QBittorrentController(ILogger logger, QBitto { return await SyncMainData(); } - + [Authorize(Policy = "AuthSetting")] [Route("transfer/info")] [HttpGet] @@ -563,7 +567,7 @@ public class QBTorrentsInfoRequest { public String? Category { get; set; } } - + public class QBTorrentsHashRequest { public String? Hash { get; set; } @@ -587,7 +591,7 @@ public class QBTorrentsSetCategoryRequest public String? Hashes { get; set; } public String? Category { get; set; } } - + public class QBTorrentsCreateCategoryRequest { public String? Category { get; set; } @@ -601,4 +605,4 @@ public class QBTorrentsRemoveCategoryRequest public class QBTorrentsHashesRequest { public String? Hashes { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Web/Controllers/SettingsController.cs b/server/RdtClient.Web/Controllers/SettingsController.cs index 7d6bbcf..548861f 100644 --- a/server/RdtClient.Web/Controllers/SettingsController.cs +++ b/server/RdtClient.Web/Controllers/SettingsController.cs @@ -9,6 +9,7 @@ using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; using RdtClient.Service.Services; using RdtClient.Service.Services.Downloaders; +using DownloadClient = RdtClient.Data.Enums.DownloadClient; namespace RdtClient.Web.Controllers; @@ -21,6 +22,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll public ActionResult Get() { var result = SettingData.GetAll(); + return Ok(result); } @@ -34,7 +36,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll } await settings.Update(settings1); - + return Ok(); } @@ -43,6 +45,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll public async Task> Profile() { var profile = await torrents.GetProfile(); + return Ok(profile); } @@ -57,7 +60,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll Version = version }); } - + [HttpPost] [Route("TestPath")] public async Task TestPath([FromBody] SettingsControllerTestPathRequest? request) @@ -82,12 +85,12 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll var testFile = $"{path}/test.txt"; await System.IO.File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file."); - + await FileHelper.Delete(testFile); return Ok(); } - + [HttpGet] [Route("TestDownloadSpeed")] public async Task TestDownloadSpeed(CancellationToken cancellationToken) @@ -103,12 +106,12 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar", Torrent = new() { - DownloadClient = Settings.Get.DownloadClient.Client == Data.Enums.DownloadClient.Symlink ? Data.Enums.DownloadClient.Bezzad : Settings.Get.DownloadClient.Client, + DownloadClient = Settings.Get.DownloadClient.Client == DownloadClient.Symlink ? DownloadClient.Bezzad : Settings.Get.DownloadClient.Client, RdName = "testDefault.rar" } }; - var downloadClient = new DownloadClient(download, download.Torrent, downloadPath, null); + var downloadClient = new Service.Services.DownloadClient(download, download.Torrent, downloadPath, null); await downloadClient.Start(); @@ -134,7 +137,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll await aria2Downloader.Update(allDownloads); } - + if (downloadClient.BytesDone > 1024 * 1024 * 50) { await downloadClient.Cancel(); @@ -147,7 +150,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll return Ok(downloadClient.Speed); } - + [HttpGet] [Route("TestWriteSpeed")] public async Task TestWriteSpeed() @@ -176,15 +179,15 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll await fileStream.WriteAsync(buffer.AsMemory(0, buffer.Length)); } - + watch.Stop(); var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds; - + fileStream.Close(); await FileHelper.Delete(testFilePath); - + return Ok(writeSpeed); } @@ -219,4 +222,4 @@ public class SettingsControllerTestAria2cConnectionRequest { public String? Url { get; set; } public String? Secret { get; set; } -} \ No newline at end of file +} diff --git a/server/RdtClient.Web/Controllers/TorrentsController.cs b/server/RdtClient.Web/Controllers/TorrentsController.cs index a1ba703..9f2a09b 100644 --- a/server/RdtClient.Web/Controllers/TorrentsController.cs +++ b/server/RdtClient.Web/Controllers/TorrentsController.cs @@ -52,6 +52,7 @@ public class TorrentsController(ILogger logger, Torrents tor public ActionResult GetDiskSpaceStatus() { var status = DiskSpaceMonitor.GetCurrentStatus(); + return Ok(status); } @@ -107,7 +108,7 @@ public class TorrentsController(ILogger logger, Torrents tor { return BadRequest(); } - + if (String.IsNullOrEmpty(request.MagnetLink)) { return BadRequest("Invalid magnet link"); @@ -208,7 +209,7 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } - + [HttpPut] [Route("Update")] public async Task Update([FromBody] Torrent? torrent) @@ -280,7 +281,7 @@ public class TorrentsController(ILogger logger, Torrents tor includeError = ex.Message; } } - } + } else if (!String.IsNullOrWhiteSpace(request.ExcludeRegex)) { foreach (var availableFile in availableFiles) @@ -339,5 +340,5 @@ public class TorrentControllerVerifyRegexRequest { public String? IncludeRegex { get; set; } public String? ExcludeRegex { get; set; } - public String? MagnetLink { get; set;} -} \ No newline at end of file + public String? MagnetLink { get; set; } +} diff --git a/server/RdtClient.Web/Program.cs b/server/RdtClient.Web/Program.cs index d1d676a..824ad9f 100644 --- a/server/RdtClient.Web/Program.cs +++ b/server/RdtClient.Web/Program.cs @@ -8,7 +8,9 @@ using RdtClient.Service; using RdtClient.Service.Middleware; using RdtClient.Service.Services; using Serilog; +using Serilog.Debugging; using Serilog.Events; +using DiConfig = RdtClient.Data.DiConfig; var builder = WebApplication.CreateBuilder(new WebApplicationOptions { @@ -51,7 +53,7 @@ if (appSettings.Logging?.File?.Path != null) .MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning)); } -Serilog.Debugging.SelfLog.Enable(msg => +SelfLog.Enable(msg => { Debug.Print(msg); Debugger.Break(); @@ -69,12 +71,12 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc options.SlidingExpiration = true; }); - -builder.Services.AddAuthorizationBuilder().AddPolicy("AuthSetting", policyCorrectUser => -{ - policyCorrectUser.Requirements.Add(new AuthSettingRequirement()); -}); - +builder.Services.AddAuthorizationBuilder() + .AddPolicy("AuthSetting", + policyCorrectUser => + { + policyCorrectUser.Requirements.Add(new AuthSettingRequirement()); + }); builder.Services.AddIdentity(options => { @@ -93,8 +95,10 @@ builder.Services.ConfigureApplicationCookie(options => options.Events.OnRedirectToLogin = context => { context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.CompletedTask; }; + options.Cookie.Name = "SID"; }); @@ -132,7 +136,7 @@ builder.Services.AddSignalR(hubOptions => builder.Host.UseWindowsService(); -RdtClient.Data.DiConfig.Config(builder.Services, appSettings); +DiConfig.Config(builder.Services, appSettings); builder.Services.RegisterRdtServices(); try @@ -160,7 +164,8 @@ try } }); - var basePath = !String.IsNullOrWhiteSpace(appSettings.BasePath) ? appSettings.BasePath : !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BASE_PATH")) ? Environment.GetEnvironmentVariable("BASE_PATH") : null; + var basePath = !String.IsNullOrWhiteSpace(appSettings.BasePath) ? appSettings.BasePath : + !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BASE_PATH")) ? Environment.GetEnvironmentVariable("BASE_PATH") : null; if (basePath != null) { @@ -180,16 +185,18 @@ try app.MapControllers(); - app.UseWhen(x => !x.Request.Path.StartsWithSegments("/api"), routeBuilder => - { - routeBuilder.UseSpaStaticFiles(); - routeBuilder.UseSpa(spa => - { - spa.Options.SourcePath = "wwwroot"; - spa.Options.DefaultPage = "/index.html"; - }); - }); - + app.UseWhen(x => !x.Request.Path.StartsWithSegments("/api"), + routeBuilder => + { + routeBuilder.UseSpaStaticFiles(); + + routeBuilder.UseSpa(spa => + { + spa.Options.SourcePath = "wwwroot"; + spa.Options.DefaultPage = "/index.html"; + }); + }); + // Run the app app.Run(); } @@ -200,4 +207,4 @@ catch (Exception ex) finally { Log.CloseAndFlush(); -} \ No newline at end of file +} diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index f5b2caf..ea8be9a 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -5,7 +5,7 @@ Exe 94c24cba-f03f-4453-a671-3640b517c573 1.0.0 - 1.0.0 + 1.0.0 enable enable latest @@ -38,7 +38,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -54,4 +53,4 @@ - + \ No newline at end of file