From ee9e80f7d2d595688b0817a40eeb0a9448653864 Mon Sep 17 00:00:00 2001 From: Sculas Date: Mon, 6 Jan 2025 12:29:41 +0100 Subject: [PATCH 1/6] fix: Only apply AllDebrid-specific path fixes to AllDebrid --- ...6112750_Torrent_Add_ClientKind.Designer.cs | 479 ++++++++++++++++++ .../20250106112750_Torrent_Add_ClientKind.cs | 28 + .../Migrations/DataContextModelSnapshot.cs | 3 + server/RdtClient.Data/Models/Data/Torrent.cs | 9 + .../Helpers/DownloadHelper.cs | 22 +- .../TorrentClients/AllDebridTorrentClient.cs | 1 + .../TorrentClients/PremiumizeTorrentClient.cs | 1 + .../TorrentClients/RealDebridTorrentClient.cs | 1 + .../TorrentClients/TorBoxTorrentClient.cs | 8 +- 9 files changed, 538 insertions(+), 14 deletions(-) create mode 100644 server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.Designer.cs create mode 100644 server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.cs diff --git a/server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.Designer.cs b/server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.Designer.cs new file mode 100644 index 0000000..fd24225 --- /dev/null +++ b/server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.Designer.cs @@ -0,0 +1,479 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RdtClient.Data.Data; + +#nullable disable + +namespace RdtClient.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250106112750_Torrent_Add_ClientKind")] + partial class Torrent_Add_ClientKind + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b => + { + b.Property("DownloadId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Added") + .HasColumnType("TEXT"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("DownloadFinished") + .HasColumnType("TEXT"); + + b.Property("DownloadQueued") + .HasColumnType("TEXT"); + + b.Property("DownloadStarted") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RemoteId") + .HasColumnType("TEXT"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("TorrentId") + .HasColumnType("TEXT"); + + b.Property("UnpackingFinished") + .HasColumnType("TEXT"); + + b.Property("UnpackingQueued") + .HasColumnType("TEXT"); + + b.Property("UnpackingStarted") + .HasColumnType("TEXT"); + + b.HasKey("DownloadId"); + + b.HasIndex("TorrentId"); + + b.ToTable("Downloads"); + }); + + modelBuilder.Entity("RdtClient.Data.Models.Data.Setting", b => + { + b.Property("SettingId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("SettingId"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b => + { + b.Property("TorrentId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Added") + .HasColumnType("TEXT"); + + b.Property("Category") + .HasColumnType("TEXT"); + + b.Property("ClientKind") + .HasColumnType("INTEGER"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("DeleteOnError") + .HasColumnType("INTEGER"); + + b.Property("DownloadAction") + .HasColumnType("INTEGER"); + + b.Property("DownloadClient") + .HasColumnType("INTEGER"); + + b.Property("DownloadManualFiles") + .HasColumnType("TEXT"); + + b.Property("DownloadMinSize") + .HasColumnType("INTEGER"); + + b.Property("DownloadRetryAttempts") + .HasColumnType("INTEGER"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("ExcludeRegex") + .HasColumnType("TEXT"); + + b.Property("FileOrMagnet") + .HasColumnType("TEXT"); + + b.Property("FilesSelected") + .HasColumnType("TEXT"); + + b.Property("FinishedAction") + .HasColumnType("INTEGER"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HostDownloadAction") + .HasColumnType("INTEGER"); + + b.Property("IncludeRegex") + .HasColumnType("TEXT"); + + b.Property("IsFile") + .HasColumnType("INTEGER"); + + b.Property("Lifetime") + .HasColumnType("INTEGER"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RdAdded") + .HasColumnType("TEXT"); + + b.Property("RdEnded") + .HasColumnType("TEXT"); + + b.Property("RdFiles") + .HasColumnType("TEXT"); + + b.Property("RdHost") + .HasColumnType("TEXT"); + + b.Property("RdId") + .HasColumnType("TEXT"); + + b.Property("RdName") + .HasColumnType("TEXT"); + + b.Property("RdProgress") + .HasColumnType("INTEGER"); + + b.Property("RdSeeders") + .HasColumnType("INTEGER"); + + b.Property("RdSize") + .HasColumnType("INTEGER"); + + b.Property("RdSpeed") + .HasColumnType("INTEGER"); + + b.Property("RdSplit") + .HasColumnType("INTEGER"); + + b.Property("RdStatus") + .HasColumnType("INTEGER"); + + b.Property("RdStatusRaw") + .HasColumnType("TEXT"); + + b.Property("Retry") + .HasColumnType("TEXT"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("TorrentRetryAttempts") + .HasColumnType("INTEGER"); + + b.HasKey("TorrentId"); + + b.ToTable("Torrents"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b => + { + b.HasOne("RdtClient.Data.Models.Data.Torrent", "Torrent") + .WithMany("Downloads") + .HasForeignKey("TorrentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Torrent"); + }); + + modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b => + { + b.Navigation("Downloads"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.cs b/server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.cs new file mode 100644 index 0000000..9cbc1b2 --- /dev/null +++ b/server/RdtClient.Data/Migrations/20250106112750_Torrent_Add_ClientKind.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RdtClient.Data.Migrations +{ + /// + public partial class Torrent_Add_ClientKind : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ClientKind", + table: "Torrents", + type: "INTEGER", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ClientKind", + table: "Torrents"); + } + } +} diff --git a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs index ce27f40..f9a2002 100644 --- a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs +++ b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs @@ -293,6 +293,9 @@ namespace RdtClient.Data.Migrations b.Property("Category") .HasColumnType("TEXT"); + b.Property("ClientKind") + .HasColumnType("INTEGER"); + b.Property("Completed") .HasColumnType("TEXT"); diff --git a/server/RdtClient.Data/Models/Data/Torrent.cs b/server/RdtClient.Data/Models/Data/Torrent.cs index 3ffe5b3..f928e3e 100644 --- a/server/RdtClient.Data/Models/Data/Torrent.cs +++ b/server/RdtClient.Data/Models/Data/Torrent.cs @@ -44,6 +44,7 @@ public class Torrent [InverseProperty("Torrent")] public IList Downloads { get; set; } = []; + public TorrentClientKind? ClientKind { get; set; } public String? RdId { get; set; } public String? RdName { get; set; } public Int64? RdSize { get; set; } @@ -92,4 +93,12 @@ public class Torrent return DownloadManualFiles.Split(","); } } + + public enum TorrentClientKind + { + AllDebrid, + Premiumize, + RealDebrid, + TorBox + } } \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index 7a5d794..bac745f 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -40,7 +40,6 @@ public static class DownloadHelper if (matchingTorrentFiles.Count > 0) { var matchingTorrentFile = matchingTorrentFiles[0]; - var subPath = Path.GetDirectoryName(matchingTorrentFile.Path); if (!String.IsNullOrWhiteSpace(subPath)) @@ -49,22 +48,23 @@ public static class DownloadHelper torrentPath = Path.Combine(torrentPath, subPath); } - else if (torrent.Files.Count == 1) - { - if (directory != fileName) - { - throw new($"Torrent path {torrentPath} does not match file name {fileName}. This is a requirement for single file torrents."); - } - - return torrentPath; - } } } else if (torrent.Files.Count == 1) { + // Debrid servers such as RealDebrid store single file torrents in a subfolder, but AllDebrid doesn't. + // We should replicate this behavior so that both folder structures are equal. + // See issue: https://github.com/rogerfar/rdt-client/issues/648 + if (torrent.ClientKind != Torrent.TorrentClientKind.AllDebrid) + { + torrentPath = Path.Combine(downloadPath, directory); + } + var torrentFile = torrent.Files[0]; var subPath = Path.GetDirectoryName(torrentFile.Path); - + + // What we think is a single file torrent may also be a folder with a single file in it. + // So make sure we handle that here. If this is not the case, torrentPath will be empty below. if (!String.IsNullOrWhiteSpace(subPath)) { subPath = subPath.Trim('/').Trim('\\'); diff --git a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs index 6e9e178..4f6c0aa 100644 --- a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs @@ -174,6 +174,7 @@ public class AllDebridTorrentClient(ILogger logger, IHtt torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); } + torrent.ClientKind = Torrent.TorrentClientKind.AllDebrid; torrent.RdHost = torrentClientTorrent.Host; torrent.RdSplit = torrentClientTorrent.Split; torrent.RdProgress = torrentClientTorrent.Progress; diff --git a/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs index 4a1745b..438fe29 100644 --- a/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs @@ -162,6 +162,7 @@ public class PremiumizeTorrentClient(ILogger logger, IH torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); } + torrent.ClientKind = Torrent.TorrentClientKind.Premiumize; torrent.RdHost = torrentClientTorrent.Host; torrent.RdSplit = torrentClientTorrent.Split; torrent.RdProgress = torrentClientTorrent.Progress; diff --git a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs index 42b06d2..29493fd 100644 --- a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs @@ -295,6 +295,7 @@ public class RealDebridTorrentClient(ILogger logger, IH torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files); } + torrent.ClientKind = Data.Models.Data.Torrent.TorrentClientKind.RealDebrid; torrent.RdHost = torrentClientTorrent.Host; torrent.RdSplit = torrentClientTorrent.Split; torrent.RdProgress = torrentClientTorrent.Progress; diff --git a/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs index 8775072..45f562b 100644 --- a/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs @@ -4,6 +4,7 @@ using TorBoxNET; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; using System.Web; +using RdtClient.Data.Models.Data; namespace RdtClient.Service.Services.TorrentClients; @@ -167,7 +168,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien return []; } - public Task SelectFiles(Data.Models.Data.Torrent torrent) + public Task SelectFiles(Torrent torrent) { return Task.CompletedTask; } @@ -194,7 +195,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien return result.Data!; } - public async Task UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent? torrentClientTorrent) + public async Task UpdateData(Torrent torrent, TorrentClientTorrent? torrentClientTorrent) { try { @@ -229,6 +230,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files); } + torrent.ClientKind = Torrent.TorrentClientKind.TorBox; torrent.RdHost = rdTorrent.Host; torrent.RdSplit = rdTorrent.Split; torrent.RdProgress = rdTorrent.Progress; @@ -280,7 +282,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien return torrent; } - public async Task?> GetDownloadLinks(Data.Models.Data.Torrent torrent) + public async Task?> GetDownloadLinks(Torrent torrent) { var files = new List(); From 8fe277fa5265379affe95e819df8c43969c79d56 Mon Sep 17 00:00:00 2001 From: Sculas Date: Tue, 7 Jan 2025 14:26:26 +0100 Subject: [PATCH 2/6] fix(SymlinkDownloader): Add search path to potential file paths --- .../Services/Downloaders/SymlinkDownloader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs index 9739d0f..318b7ee 100644 --- a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs @@ -16,7 +16,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path) public async Task Download() { - _logger.Debug($"Starting symlink resolving of {uri}, writing to path: {path}"); + _logger.Debug($"Starting symlink resolving of {path} (uri = {uri}), writing to path: {destinationPath}"); try { @@ -57,7 +57,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path) Speed = 0 }); - var potentialFilePaths = new List(); + var potentialFilePaths = new List { searchPath }; var directoryInfo = new DirectoryInfo(searchPath); while (directoryInfo.Parent != null) From 38a3bc1537be3c76367017d4f787c54e3e6149ff Mon Sep 17 00:00:00 2001 From: Sculas Date: Tue, 7 Jan 2025 14:34:02 +0100 Subject: [PATCH 3/6] perf(SymlinkDownloader): Skip file search when using Alldebrid --- .../Services/DownloadClient.cs | 2 +- .../Services/Downloaders/SymlinkDownloader.cs | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs index 3ab400b..f35ba1c 100644 --- a/server/RdtClient.Service/Services/DownloadClient.cs +++ b/server/RdtClient.Service/Services/DownloadClient.cs @@ -51,7 +51,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath), Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath), Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath, downloadPath, category), - Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath, downloadPath), + Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath, downloadPath, torrent.ClientKind), _ => throw new($"Unknown download client {Type}") }; diff --git a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs index 318b7ee..406f5ed 100644 --- a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs @@ -1,9 +1,10 @@ -using RdtClient.Service.Helpers; +using RdtClient.Data.Models.Data; +using RdtClient.Service.Helpers; using Serilog; namespace RdtClient.Service.Services.Downloaders; -public class SymlinkDownloader(String uri, String destinationPath, String path) : IDownloader +public class SymlinkDownloader(String uri, String destinationPath, String path, Torrent.TorrentClientKind? clientKind) : IDownloader { public event EventHandler? DownloadComplete; public event EventHandler? DownloadProgress; @@ -56,6 +57,22 @@ public class SymlinkDownloader(String uri, String destinationPath, String path) BytesTotal = 0, Speed = 0 }); + + String? file = null; + + // When resolving symlinks for AllDebrid, we know the exact file path, so we can skip the search. + if (clientKind == Torrent.TorrentClientKind.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)) + { + file = potentialFilePath; + goto skipFileSearch; + } + } var potentialFilePaths = new List { searchPath }; @@ -78,8 +95,6 @@ public class SymlinkDownloader(String uri, String destinationPath, String path) potentialFilePaths = potentialFilePaths.Distinct().ToList(); - String? file = null; - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { DownloadProgress?.Invoke(this, @@ -135,6 +150,9 @@ public class SymlinkDownloader(String uri, String destinationPath, String path) throw new("Could not find file from rclone mount!"); } + + // Used by Alldebrid since we know the exact file path. + skipFileSearch: _logger.Debug($"Creating symbolic link from {file} to {destinationPath}"); From 56934e31831dae4b3824bfa581d32ddfa2a7f1e6 Mon Sep 17 00:00:00 2001 From: Sculas Date: Tue, 7 Jan 2025 15:15:31 +0100 Subject: [PATCH 4/6] refactor: Improve logging --- .../Services/Downloaders/SymlinkDownloader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs index 406f5ed..be3fa0c 100644 --- a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs @@ -69,9 +69,13 @@ public class SymlinkDownloader(String uri, String destinationPath, String path, // If this somehow fails, fallback to the search below. if (File.Exists(potentialFilePath)) { + _logger.Debug($"Found file {path} at {potentialFilePath} using direct search"); file = potentialFilePath; goto skipFileSearch; } + + // Log if the file wasn't found and continue searching. + _logger.Warning($"Expected file {path} to be at {potentialFilePath} but it wasn't found. Continuing search (this will probably fail)."); } var potentialFilePaths = new List { searchPath }; From d6305c26f864f0a1ac5af754ae6be95d1c2be808 Mon Sep 17 00:00:00 2001 From: Sculas Date: Wed, 8 Jan 2025 13:41:47 +0100 Subject: [PATCH 5/6] fix: Only add path separator in specific cases --- server/RdtClient.Service/Services/QBittorrent.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/RdtClient.Service/Services/QBittorrent.cs b/server/RdtClient.Service/Services/QBittorrent.cs index e20f7c1..0260742 100644 --- a/server/RdtClient.Service/Services/QBittorrent.cs +++ b/server/RdtClient.Service/Services/QBittorrent.cs @@ -208,7 +208,13 @@ public class QBittorrent(ILogger logger, Settings settings, Authent var torrentPath = downloadPath; if (!String.IsNullOrWhiteSpace(torrent.RdName)) { - torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar; + torrentPath = Path.Combine(downloadPath, torrent.RdName); + + // Alldebrid stores single file torrents at the root folder. + if (torrent.ClientKind != Torrent.TorrentClientKind.AllDebrid || torrent.Files.Count > 1) + { + torrentPath += Path.DirectorySeparatorChar; + } } Int64 bytesDone = 0; From 00e499593ef2482be45bce967d4761486a26fb26 Mon Sep 17 00:00:00 2001 From: Sculas Date: Thu, 9 Jan 2025 16:23:56 +0100 Subject: [PATCH 6/6] fix: Fix Alldebrid path again --- server/RdtClient.Service/Services/QBittorrent.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/RdtClient.Service/Services/QBittorrent.cs b/server/RdtClient.Service/Services/QBittorrent.cs index 0260742..b53d7dc 100644 --- a/server/RdtClient.Service/Services/QBittorrent.cs +++ b/server/RdtClient.Service/Services/QBittorrent.cs @@ -208,12 +208,13 @@ public class QBittorrent(ILogger logger, Settings settings, Authent var torrentPath = downloadPath; if (!String.IsNullOrWhiteSpace(torrent.RdName)) { - torrentPath = Path.Combine(downloadPath, torrent.RdName); - // Alldebrid stores single file torrents at the root folder. - if (torrent.ClientKind != Torrent.TorrentClientKind.AllDebrid || torrent.Files.Count > 1) + if (torrent is { ClientKind: Torrent.TorrentClientKind.AllDebrid, Files.Count: 1 }) { - torrentPath += Path.DirectorySeparatorChar; + torrentPath = Path.Combine(downloadPath, torrent.Files[0].Path); + } else + { + torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar; } }