From ee9e80f7d2d595688b0817a40eeb0a9448653864 Mon Sep 17 00:00:00 2001 From: Sculas Date: Mon, 6 Jan 2025 12:29:41 +0100 Subject: [PATCH] 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();