diff --git a/CHANGELOG.md b/CHANGELOG.md index da04f8e..2577c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.94] - 2025-01-05 +### Changed +- AllDebrid path fixes. + +## [2.0.93] - 2025-01-03 +### Changed +- Torbox fixes. + +## [2.0.92] - 2024-12-18 +### Changed +- Torbox fixes. + +## [2.0.91] - 2024-12-11 +### Changed +- Torbox fixes. + ## [2.0.90] - 2024-12-06 ### Changed - Download individual files from Torbox instead of a zip file. diff --git a/client/src/app/navbar/navbar.component.html b/client/src/app/navbar/navbar.component.html index 55ee2b7..c620099 100644 --- a/client/src/app/navbar/navbar.component.html +++ b/client/src/app/navbar/navbar.component.html @@ -55,7 +55,7 @@ Profile Logout - Version 2.0.90 + Version 2.0.94 diff --git a/client/src/app/setup/setup.component.html b/client/src/app/setup/setup.component.html index 643e618..28c81e3 100644 --- a/client/src/app/setup/setup.component.html +++ b/client/src/app/setup/setup.component.html @@ -55,6 +55,7 @@ Use this link to sign up to Premiumize. +
Use this link to sign up to TorBox.
diff --git a/package.json b/package.json index 8c177b6..69cd004 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rdt-client", - "version": "2.0.90", + "version": "2.0.94", "description": "This is a web interface to manage your torrents on Real-Debrid.", "main": "index.js", "dependencies": { diff --git a/server/RdtClient.Data/Data/DownloadData.cs b/server/RdtClient.Data/Data/DownloadData.cs index 4488b9d..9d141f9 100644 --- a/server/RdtClient.Data/Data/DownloadData.cs +++ b/server/RdtClient.Data/Data/DownloadData.cs @@ -67,6 +67,23 @@ public class DownloadData(DataContext dataContext) await TorrentData.VoidCache(); } + public async Task UpdateFileName(Guid downloadId, String fileName) + { + var dbDownload = await dataContext.Downloads + .FirstOrDefaultAsync(m => m.DownloadId == downloadId); + + if (dbDownload == null) + { + return; + } + + dbDownload.FileName = fileName; + + await dataContext.SaveChangesAsync(); + + await TorrentData.VoidCache(); + } + public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) { var dbDownload = await dataContext.Downloads diff --git a/server/RdtClient.Data/Migrations/20241210071621_Downloads_Add_FileName.Designer.cs b/server/RdtClient.Data/Migrations/20241210071621_Downloads_Add_FileName.Designer.cs new file mode 100644 index 0000000..f7c9a3c --- /dev/null +++ b/server/RdtClient.Data/Migrations/20241210071621_Downloads_Add_FileName.Designer.cs @@ -0,0 +1,476 @@ +// +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("20241210071621_Downloads_Add_FileName")] + partial class Downloads_Add_FileName + { + /// + 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("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/20241210071621_Downloads_Add_FileName.cs b/server/RdtClient.Data/Migrations/20241210071621_Downloads_Add_FileName.cs new file mode 100644 index 0000000..94a8666 --- /dev/null +++ b/server/RdtClient.Data/Migrations/20241210071621_Downloads_Add_FileName.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RdtClient.Data.Migrations +{ + /// + public partial class Downloads_Add_FileName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FileName", + table: "Downloads", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FileName", + table: "Downloads"); + } + } +} diff --git a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs index a3edd7b..ce27f40 100644 --- a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs +++ b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace RdtClient.Data.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.3"); + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -233,6 +233,9 @@ namespace RdtClient.Data.Migrations b.Property("Error") .HasColumnType("TEXT"); + b.Property("FileName") + .HasColumnType("TEXT"); + b.Property("Link") .HasColumnType("TEXT"); diff --git a/server/RdtClient.Data/Models/Data/Download.cs b/server/RdtClient.Data/Models/Data/Download.cs index e8e54b1..d078356 100644 --- a/server/RdtClient.Data/Models/Data/Download.cs +++ b/server/RdtClient.Data/Models/Data/Download.cs @@ -31,6 +31,8 @@ public class Download public String? RemoteId { get; set; } + public String? FileName { get; set; } + [NotMapped] public Int64 BytesTotal { get; set; } diff --git a/server/RdtClient.Data/Models/Internal/DbSettings.cs b/server/RdtClient.Data/Models/Internal/DbSettings.cs index eb62b6b..dba1ff3 100644 --- a/server/RdtClient.Data/Models/Internal/DbSettings.cs +++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs @@ -167,11 +167,11 @@ At this point only 1 provider can be used at the time.")] [Description(@"You can find your API key here: https://real-debrid.com/apitoken or -https://alldebrid.com/apikeys/ +https://alldebrid.com/apikeys/ or https://www.premiumize.me/account/ or -https://torbox.app/settings/")] +https://torbox.app/settings/")] public String ApiKey { get; set; } = ""; [DisplayName("Automatically import and process torrents added to provider")] diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index 29e4b5d..846b9be 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -17,21 +17,53 @@ public static class DownloadHelper var directory = RemoveInvalidPathChars(torrent.RdName); var uri = new Uri(fileUrl); - var torrentPath = Path.Combine(downloadPath, directory); - var fileName = uri.Segments.Last(); + var fileName = download.FileName; - fileName = HttpUtility.UrlDecode(fileName); + if (String.IsNullOrWhiteSpace(fileName)) + { + fileName = uri.Segments.Last(); + + fileName = HttpUtility.UrlDecode(fileName); + } fileName = FileHelper.RemoveInvalidFileNameChars(fileName); - var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList(); + var torrentPath = downloadPath; - if (matchingTorrentFiles.Count > 0) + if (torrent.Files.Count > 1) { - var matchingTorrentFile = matchingTorrentFiles[0]; + torrentPath = Path.Combine(downloadPath, directory); - var subPath = Path.GetDirectoryName(matchingTorrentFile.Path); + var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList(); + + if (matchingTorrentFiles.Count > 0) + { + var matchingTorrentFile = matchingTorrentFiles[0]; + + var subPath = Path.GetDirectoryName(matchingTorrentFile.Path); + + if (!String.IsNullOrWhiteSpace(subPath)) + { + subPath = subPath.Trim('/').Trim('\\'); + + 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) + { + var torrentFile = torrent.Files[0]; + var subPath = Path.GetDirectoryName(torrentFile.Path); if (!String.IsNullOrWhiteSpace(subPath)) { @@ -41,7 +73,7 @@ public static class DownloadHelper } } - if (!Directory.Exists(torrentPath)) + if (!String.IsNullOrWhiteSpace(torrentPath) && !Directory.Exists(torrentPath)) { Directory.CreateDirectory(torrentPath); } @@ -53,41 +85,7 @@ public static class DownloadHelper public static String? GetDownloadPath(Torrent torrent, Download download) { - var fileUrl = download.Link; - - if (String.IsNullOrWhiteSpace(fileUrl) || torrent.RdName == null) - { - return null; - } - - var uri = new Uri(fileUrl); - var torrentPath = RemoveInvalidPathChars(torrent.RdName); - - var fileName = uri.Segments.Last(); - - fileName = HttpUtility.UrlDecode(fileName); - - fileName = FileHelper.RemoveInvalidFileNameChars(fileName); - - var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList(); - - if (matchingTorrentFiles.Count > 0) - { - var matchingTorrentFile = matchingTorrentFiles[0]; - - var subPath = Path.GetDirectoryName(matchingTorrentFile.Path); - - if (!String.IsNullOrWhiteSpace(subPath)) - { - subPath = subPath.Trim('/').Trim('\\'); - - torrentPath = Path.Combine(torrentPath, subPath); - } - } - - var filePath = Path.Combine(torrentPath, fileName); - - return filePath; + return GetDownloadPath("", torrent, download); } private static String RemoveInvalidPathChars(String path) diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 30f2067..98249af 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -22,7 +22,7 @@ - + diff --git a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs index 1b2d172..8ab4cf3 100644 --- a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs @@ -203,6 +203,13 @@ public class Aria2cDownloader : IDownloader var retryCount = 0; while (true) { + DownloadProgress?.Invoke(this, new() + { + BytesDone = download.CompletedLength, + BytesTotal = download.TotalLength, + Speed = download.DownloadSpeed + }); + if (retryCount >= 10) { DownloadComplete?.Invoke(this, new() diff --git a/server/RdtClient.Service/Services/Downloads.cs b/server/RdtClient.Service/Services/Downloads.cs index e66954f..020b36d 100644 --- a/server/RdtClient.Service/Services/Downloads.cs +++ b/server/RdtClient.Service/Services/Downloads.cs @@ -30,6 +30,11 @@ public class Downloads(DownloadData downloadData) await downloadData.UpdateUnrestrictedLink(downloadId, unrestrictedLink); } + public async Task UpdateFileName(Guid downloadId, String fileName) + { + await downloadData.UpdateFileName(downloadId, fileName); + } + public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime) { await downloadData.UpdateDownloadStarted(downloadId, dateTime); diff --git a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs index bc0102c..6e9e178 100644 --- a/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs @@ -1,10 +1,13 @@ -using AllDebridNET; +using System.Text.RegularExpressions; +using AllDebridNET; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; +using System.Web; using File = AllDebridNET.File; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; using Torrent = RdtClient.Data.Models.Data.Torrent; namespace RdtClient.Service.Services.TorrentClients; @@ -61,7 +64,7 @@ public class AllDebridTorrentClient(ILogger logger, IHtt Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate), Files = torrent.Links.Select((m, i) => new TorrentClientFile { - Path = m.Filename, + Path = GetFiles(m.Files), Bytes = m.Size, Id = i, Selected = true, @@ -241,6 +244,53 @@ public class AllDebridTorrentClient(ILogger logger, IHtt Log($"Found {links.Count} files that match the minimum file size criterea", torrent); } + + if (!String.IsNullOrWhiteSpace(torrent.IncludeRegex)) + { + Log($"Using regular expression {torrent.IncludeRegex} to include only files matching this regex", torrent); + + var newLinks = new List(); + foreach (var link in links) + { + var path = GetFiles(link.Files); + if (Regex.IsMatch(path, torrent.IncludeRegex)) + { + Log($"* Including {path}", torrent); + newLinks.Add(link); + } + else + { + Log($"* Excluding {path}", torrent); + } + } + + links = newLinks; + + Log($"Found {newLinks.Count} files that match the regex", torrent); + } + else if (!String.IsNullOrWhiteSpace(torrent.ExcludeRegex)) + { + Log($"Using regular expression {torrent.IncludeRegex} to ignore files matching this regex", torrent); + + var newLinks = new List(); + foreach (var link in links) + { + var path = GetFiles(link.Files); + if (!Regex.IsMatch(path, torrent.ExcludeRegex)) + { + Log($"* Including {path}", torrent); + newLinks.Add(link); + } + else + { + Log($"* Excluding {path}", torrent); + } + } + + links = newLinks; + + Log($"Found {newLinks.Count} files that match the regex", torrent); + } if (links.Count == 0) { @@ -249,19 +299,31 @@ public class AllDebridTorrentClient(ILogger logger, IHtt links = magnet.Links; } - Log($"Selecting links:"); - - foreach (var link in links) + if (logger.IsEnabled(LogLevel.Debug)) { - var fileList = GetFiles(link.Files, ""); + Log($"Selecting links:"); - Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains files:{Environment.NewLine}{String.Join(Environment.NewLine, fileList)}"); + foreach (var link in links) + { + Log($"{GetFiles(link.Files)} ({link.Size}b) {link.LinkUrl}"); + } } - + Log("", torrent); return links.Select(m => m.LinkUrl.ToString()).ToList(); } + public Task GetFileName(String downloadUrl) + { + if (String.IsNullOrWhiteSpace(downloadUrl)) + { + return Task.FromResult(""); + } + + var uri = new Uri(downloadUrl); + + return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last())); + } private async Task GetInfo(String torrentId) { @@ -270,7 +332,7 @@ public class AllDebridTorrentClient(ILogger logger, IHtt return Map(result); } - private static List GetFiles(IList files, String parent) + private static String GetFiles(IList files) { var result = new List(); @@ -278,19 +340,24 @@ public class AllDebridTorrentClient(ILogger logger, IHtt { if (!String.IsNullOrWhiteSpace(file.N)) { - result.Add($"{parent}/{file.N}"); + result.Add(file.N); } if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0) { - result.AddRange(GetFiles(file.E.Value.PurpleEArray, file.N)); + if (file.E.Value.PurpleEArray.Count != 1) + { + throw new("Unexpected number of nested files"); + } + + result.AddRange(GetFiles(file.E.Value.PurpleEArray)); } } - return result; + return String.Join("/", result); } - private static List GetFiles(IList files, String parent) + private static List GetFiles(IList files) { var result = new List(); @@ -298,19 +365,19 @@ public class AllDebridTorrentClient(ILogger logger, IHtt { if (!String.IsNullOrWhiteSpace(file.N)) { - result.Add($"{parent}/{file.N}"); + result.Add(file.N); } if (file.E != null && file.E.Count > 0) { - result.AddRange(GetFiles(file.E, file.N)); + result.AddRange(GetFiles(file.E)); } } return result; } - private static List GetFiles(IList files, String parent) + private static List GetFiles(IList files) { var result = new List(); @@ -318,7 +385,7 @@ public class AllDebridTorrentClient(ILogger logger, IHtt { if (!String.IsNullOrWhiteSpace(file.N)) { - result.Add($"{parent}/{file.N}"); + result.Add(file.N); } } diff --git a/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs index da4aa39..38121ae 100644 --- a/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/ITorrentClient.cs @@ -15,4 +15,5 @@ public interface ITorrentClient Task Unrestrict(String link); Task UpdateData(Torrent torrent, TorrentClientTorrent? torrentClientTorrent); Task?> GetDownloadLinks(Torrent torrent); + Task GetFileName(String downloadUrl); } \ 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 cb170c2..4a1745b 100644 --- a/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/PremiumizeTorrentClient.cs @@ -4,6 +4,7 @@ using PremiumizeNET; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; +using System.Web; using Torrent = RdtClient.Data.Models.Data.Torrent; namespace RdtClient.Service.Services.TorrentClients; @@ -241,6 +242,18 @@ public class PremiumizeTorrentClient(ILogger logger, IH return downloadLinks; } + public Task GetFileName(String downloadUrl) + { + if (String.IsNullOrWhiteSpace(downloadUrl)) + { + return Task.FromResult(""); + } + + var uri = new Uri(downloadUrl); + + return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last())); + } + private async Task GetInfo(String id) { var results = await GetClient().Transfers.ListAsync(); diff --git a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs index 9cc8ad0..42b06d2 100644 --- a/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/RealDebridTorrentClient.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using System.Web; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using RDNET; @@ -395,6 +396,18 @@ public class RealDebridTorrentClient(ILogger logger, IH return null; } + public Task GetFileName(String downloadUrl) + { + if (String.IsNullOrWhiteSpace(downloadUrl)) + { + return Task.FromResult(""); + } + + var uri = new Uri(downloadUrl); + + return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last())); + } + private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) { if (_offset == null) diff --git a/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs b/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs index 088c928..8775072 100644 --- a/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs +++ b/server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using TorBoxNET; using RdtClient.Data.Enums; using RdtClient.Data.Models.TorrentClient; +using System.Web; namespace RdtClient.Service.Services.TorrentClients; @@ -39,20 +40,20 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien { foreach (var inner in ae.InnerExceptions) { - logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}"); + 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 RealDebrid has timed out: {ex.Message}"); + logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}"); throw; } catch (TaskCanceledException ex) { - logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}"); + logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}"); throw; } @@ -70,7 +71,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien OriginalBytes = torrent.Size, Host = torrent.DownloadPresent.ToString(), Split = 0, - Progress = (Int64)((torrent.Progress) * 100.0), + Progress = (Int64)(torrent.Progress * 100.0), Status = torrent.DownloadState, Added = ChangeTimeZone(torrent.CreatedAt)!.Value, Files = (torrent.Files ?? []).Select(m => new TorrentClientFile @@ -119,13 +120,9 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien public async Task AddMagnet(String magnetLink) { - // var seeding = Settings.Get.Integrations.Default.FinishedAction; + var user = await GetClient().User.GetAsync(true); - // Line is not working right now, will disable seeding and fix in december when I have time again. - // var seed = (seeding == TorrentFinishedAction.RemoveAllTorrents || seeding == TorrentFinishedAction.RemoveRealDebrid) ? 3 : 2; - - var seed = 3; - var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, seed, false); + var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, false); if (result.Error == "ACTIVE_LIMIT") { @@ -140,11 +137,9 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien public async Task AddFile(Byte[] bytes) { - // Line is not working right now, will disable seeding and fix in december when I have time again. - // var seed = (seeding == TorrentFinishedAction.RemoveAllTorrents || seeding == TorrentFinishedAction.RemoveRealDebrid) ? 3 : 2; - const Int32 seed = 3; + var user = await GetClient().User.GetAsync(true); - var result = await GetClient().Torrents.AddFileAsync(bytes, seed); + var result = await GetClient().Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3); if (result.Error == "ACTIVE_LIMIT") { using var stream = new MemoryStream(bytes); @@ -160,14 +155,13 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien { var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, listFiles: true); - if (availability.Data != null) + 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 []; @@ -257,6 +251,7 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien "checking" => TorrentStatus.Processing, "checkingResumeData" => TorrentStatus.Processing, "paused" => TorrentStatus.Downloading, + "stalledDL" => TorrentStatus.Downloading, "downloading" => TorrentStatus.Downloading, "completed" => TorrentStatus.Downloading, "uploading" => TorrentStatus.Downloading, @@ -300,6 +295,32 @@ public class TorBoxTorrentClient(ILogger logger, IHttpClien return files; } + public async Task GetFileName(String downloadUrl) + { + if (String.IsNullOrWhiteSpace(downloadUrl)) + { + return ""; + } + + var uri = new Uri(downloadUrl); + + using (HttpClient client = new()) + { + var request = new HttpRequestMessage(HttpMethod.Head, uri); + var response = await client.SendAsync(request); + if (response.Content.Headers.ContentDisposition != null) + { + var fileName = response.Content.Headers.ContentDisposition.FileName; + if (!String.IsNullOrWhiteSpace(fileName)) + { + return fileName.Trim('"'); + } + } + } + + return HttpUtility.UrlDecode(uri.Segments.Last()); + } + private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) { if (_offset == null) diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 06507f4..6151100 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -362,6 +362,9 @@ public class TorrentRunner(ILogger logger, Torrents torrents, Dow var downloadLink = await torrents.UnrestrictLink(download.DownloadId); download.Link = downloadLink; + + var fileName = await torrents.RetrieveFileName(download.DownloadId); + download.FileName = fileName; } } catch (Exception ex) diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs index 25817cf..28a45b2 100644 --- a/server/RdtClient.Service/Services/Torrents.cs +++ b/server/RdtClient.Service/Services/Torrents.cs @@ -370,6 +370,19 @@ public class Torrents( return unrestrictedLink; } + public async Task RetrieveFileName(Guid downloadId) + { + var download = await downloads.GetById(downloadId) ?? throw new($"Download with ID {downloadId} not found"); + + Log($"Retrieving filename for", download, download.Torrent); + + var fileName = await TorrentClient.GetFileName(download.Link!); + + await downloads.UpdateFileName(downloadId, fileName); + + return fileName; + } + public async Task GetProfile() { var user = await TorrentClient.GetUser(); @@ -546,7 +559,7 @@ public class Torrents( } var downloadPath = DownloadPath(download.Torrent!); - + var filePath = DownloadHelper.GetDownloadPath(downloadPath, download.Torrent!, download); if (filePath != null) diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index adfbda3..a0eface 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -4,7 +4,7 @@ net9.0 Exe 94c24cba-f03f-4453-a671-3640b517c573 - 2.0.90 + 2.0.94 enable enable latest @@ -39,7 +39,7 @@ - +