Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Sam Heinz 2024-09-08 23:57:33 +10:00
commit 0987b1f18c
18 changed files with 101 additions and 96 deletions

View file

@ -4,6 +4,34 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.86] - 2024-09-03
### Changed
- Add potential fix for BASE_PATH.
- Fixed creation of empty folders.
## [2.0.85] - 2024-09-03
### Changed
- Reverted: Prevent the creation of download folders when using symlink.
## [2.0.84] - 2024-09-02
### Changed
- Replace docker libssl1.1 with libssl3
## [2.0.83] - 2024-09-02
### Changed
- Fixed progress reporting to the qBittorrent API endpoint.
- Prevent the creation of download folders when using symlink.
## [2.0.82] - 2024-09-02
### Changed
- Update packages and docker alpine version.
## [2.0.81] - 2024-07-28
### Changed
- Improved handling of infringed torrents from real debrid.
- Force update of torrent data from real-debrid when no filename is found in the local DB.
- Fixed real-debrid deserialization issue when checking for instant available files.
## [2.0.80] - 2024-07-13 ## [2.0.80] - 2024-07-13
### Changed ### Changed
- Add rate limiter to retry requests that are rate limited from Real-Debrid. - Add rate limiter to retry requests that are rate limited from Real-Debrid.

View file

@ -38,7 +38,7 @@ RUN \
dotnet restore --no-cache RdtClient.sln && dotnet publish --no-restore -c Release -o out ; dotnet restore --no-cache RdtClient.sln && dotnet publish --no-restore -c Release -o out ;
# Stage 3 - Build runtime image # Stage 3 - Build runtime image
FROM ghcr.io/linuxserver/baseimage-alpine:3.18 FROM ghcr.io/linuxserver/baseimage-alpine:3.20
ARG TARGETPLATFORM ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64} ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
ARG BUILDPLATFORM ARG BUILDPLATFORM
@ -60,7 +60,7 @@ RUN \
echo "**** Updating package information ****" && \ echo "**** Updating package information ****" && \
apk update && \ apk update && \
echo "**** Install pre-reqs ****" && \ echo "**** Install pre-reqs ****" && \
apk add bash icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib && \ apk add bash icu-libs krb5-libs libgcc libintl libssl3 libstdc++ zlib && \
echo "**** Installing dotnet ****" && \ echo "**** Installing dotnet ****" && \
mkdir -p /usr/share/dotnet mkdir -p /usr/share/dotnet

View file

@ -20,64 +20,24 @@ This is a web interface to manage your torrents on Real-Debrid, AllDebrid or Pre
## Docker Setup ## Docker Setup
You can run the docker container on Windows, Linux. To get started either use _Docker Run_ or _Docker Compose_. Please see our separate Docker setup Read Me.
### Docker Run [Readme for Docker](README-DOCKER.md)
```console
docker run --pull=always
--volume /your/download/path/:/data/downloads \
--volume /your/storage/path/:/data/db \
--log-driver json-file \
--log-opt max-size=10m \
-p 6500:6500 \
--name rdtclient \
rogerfar/rdtclient:latest
```
Replace `/your/download/path/` with your local path to download files to. For Windows i.e. `C:/Downloads`.
Replace `/your/storage/path/` with your local path to store persistent database and log files in. For Windows i.e. `C:/Docker/rdt-client`.
### Docker Compose
You can use the provided docker compose to run:
```yaml
version: '3.3'
services:
rdtclient:
container_name: rdtclient
volumes:
- 'D:/Downloads/:/data/downloads'
- 'D:/Docker/rdt-client/:/data/db'
image: rogerfar/rdtclient
restart: always
logging:
driver: json-file
options:
max-size: 10m
ports:
- '6500:6500'
```
And to run:
```console
docker-compose up -d
```
Replace the paths in `volumes` as in the above step.
## Run as a Service ## Run as a Service
Instead of running in Docker you can install it as a service in Windows or Linux. Instead of running in Docker you can install it as a service in Windows or Linux.
## Windows Service ## Windows Service
1. Make sure you have the ASP.NET Core Runtime 8 installed: [https://dotnet.microsoft.com/download/dotnet/8.0](https://dotnet.microsoft.com/download/dotnet/8.0) 1. Make sure you have the ASP.NET Core Runtime 8 installed: [https://dotnet.microsoft.com/download/dotnet/8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
1. Get the latest zip file from the Releases page and extract it to your host. 2. Get the latest zip file from the Releases page and extract it to your host.
1. Open the `appsettings.json` file and replace the `LogLevel` `Path` to a path on your host. 3. Open the `appsettings.json` file and replace the `LogLevel` `Path` to a path on your host.
1. In `appsettings.json` replace the `Database` `Path` to a path on your host. 4. In `appsettings.json` replace the `Database` `Path` to a path on your host.
1. When using Windows paths, make sure to escape the slashes. For example: `D:\\RdtClient\\db\\rdtclient.db` 5. When using Windows paths, make sure to escape the slashes. For example: `D:\\RdtClient\\db\\rdtclient.db`
6. Do one of these:
* Run `RdtClient.Web.exe` to start the client.
* Run `service-install.bat` to install the client as a service. This will install `RdtClient.Web.exe` as a service which make the client start in the backgorund when the computer starts. (You probably want to do this if you are going to use this with Sonarr etc...)
## Linux Service ## Linux Service

View file

@ -55,7 +55,7 @@
<a class="navbar-item" routerLink="profile"> Profile </a> <a class="navbar-item" routerLink="profile"> Profile </a>
<a class="navbar-item" (click)="logout()"> Logout </a> <a class="navbar-item" (click)="logout()"> Logout </a>
<hr class="navbar-divider" /> <hr class="navbar-divider" />
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.80</a> <a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.86</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
{ {
"name": "rdt-client", "name": "rdt-client",
"version": "2.0.80", "version": "2.0.86",
"description": "This is a web interface to manage your torrents on Real-Debrid.", "description": "This is a web interface to manage your torrents on Real-Debrid.",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {

View file

@ -8,15 +8,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.7" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -118,11 +118,6 @@ public static class DownloadHelper
} }
} }
if (!Directory.Exists(torrentPath))
{
Directory.CreateDirectory(torrentPath);
}
var filePath = Path.Combine(torrentPath, fileName); var filePath = Path.Combine(torrentPath, fileName);
return filePath; return filePath;

View file

@ -13,15 +13,15 @@
<PackageReference Include="Aria2.NET" Version="1.0.5" /> <PackageReference Include="Aria2.NET" Version="1.0.5" />
<PackageReference Include="Downloader" Version="3.1.2" /> <PackageReference Include="Downloader" Version="3.1.2" />
<PackageReference Include="Downloader.NET" Version="1.0.12" /> <PackageReference Include="Downloader.NET" Version="1.0.12" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.7" /> <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.7.0" /> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.8.0" />
<PackageReference Include="MonoTorrent" Version="2.0.7" /> <PackageReference Include="MonoTorrent" Version="3.0.2" />
<PackageReference Include="Polly" Version="8.4.1" /> <PackageReference Include="Polly" Version="8.4.1" />
<PackageReference Include="Premiumize.NET" Version="1.0.4" /> <PackageReference Include="Premiumize.NET" Version="1.0.4" />
<PackageReference Include="RD.NET" Version="2.1.4" /> <PackageReference Include="RD.NET" Version="2.1.6" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SharpCompress" Version="0.37.2" /> <PackageReference Include="SharpCompress" Version="0.38.0" />
<PackageReference Include="TorBox.NET" Version="1.0.0.40" /> <PackageReference Include="TorBox.NET" Version="1.0.0.40" />
</ItemGroup> </ItemGroup>

View file

@ -39,10 +39,13 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
throw new("Invalid download path"); throw new("Invalid download path");
} }
await FileHelper.Delete(filePath.Result!);
Type = torrent.DownloadClient; Type = torrent.DownloadClient;
if (Type != Data.Enums.DownloadClient.Symlink)
{
await FileHelper.Delete(filePath);
}
Downloader = Type switch Downloader = Type switch
{ {
Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath.Result!), Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath.Result!),

View file

@ -73,6 +73,8 @@ public class SymlinkDownloader(String uri, String destinationPath, String path)
potentialFilePaths.Add(fileName); potentialFilePaths.Add(fileName);
potentialFilePaths.Add(fileNameWithoutExtension); potentialFilePaths.Add(fileNameWithoutExtension);
// add an empty path so we can check for the new file in the base directory
potentialFilePaths.Add("");
potentialFilePaths = potentialFilePaths.Distinct().ToList(); potentialFilePaths = potentialFilePaths.Distinct().ToList();

View file

@ -194,7 +194,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
var prio = 0; var prio = 0;
Decimal downloadProgress = 0; Double downloadProgress = 0;
foreach (var torrent in allTorrents) foreach (var torrent in allTorrents)
{ {
@ -214,12 +214,12 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
Int64 bytesDone = 0; Int64 bytesDone = 0;
Int64 bytesTotal = 0; Int64 bytesTotal = 0;
Int64 speed = 0; Int64 speed = 0;
Int64 rdProgress = 0; Double rdProgress = 0;
try try
{ {
rdProgress = (Int64) ((torrent.RdProgress ?? 0) / 100.0m); rdProgress = (torrent.RdProgress ?? 0.0) / 100.0;
bytesTotal = torrent.RdSize ?? 1; bytesTotal = torrent.RdSize ?? 1;
bytesDone = bytesTotal * rdProgress; bytesDone = (Int64) (bytesTotal * rdProgress);
speed = torrent.RdSpeed ?? 0; speed = torrent.RdSpeed ?? 0;
} }
catch (Exception ex) catch (Exception ex)
@ -237,10 +237,10 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
bytesDone = torrent.Downloads.Sum(m => m.BytesDone); bytesDone = torrent.Downloads.Sum(m => m.BytesDone);
bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal); bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal);
speed = (Int32) torrent.Downloads.Average(m => m.Speed); speed = (Int32) torrent.Downloads.Average(m => m.Speed);
downloadProgress = bytesTotal > 0 ? (Decimal) bytesDone / bytesTotal : 0; downloadProgress = bytesTotal > 0 ? (Double) bytesDone / bytesTotal : 0;
} }
var progress = (Int64) ((rdProgress + downloadProgress) / 2m); var progress = (rdProgress + downloadProgress) / 2.0;
var result = new TorrentInfo var result = new TorrentInfo
{ {

View file

@ -287,7 +287,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
return torrent; return torrent;
} }
if (torrentClientTorrent == null || torrentClientTorrent.Ended == null) if (torrentClientTorrent == null || torrentClientTorrent.Ended == null || String.IsNullOrEmpty(torrentClientTorrent.Filename))
{ {
torrentClientTorrent = await GetInfo(torrent.RdId) ?? throw new($"Resource not found"); torrentClientTorrent = await GetInfo(torrent.RdId) ?? throw new($"Resource not found");
} }

View file

@ -117,7 +117,7 @@ public class Torrents(
var id = await TorrentClient.AddMagnet(magnetLink); var id = await TorrentClient.AddMagnet(magnetLink);
var hash = magnet.InfoHash.ToHex(); var hash = magnet.InfoHashes.V1OrV2.ToHex();
var newTorrent = await Add(id, hash, magnetLink, false, torrent); var newTorrent = await Add(id, hash, magnetLink, false, torrent);
@ -132,7 +132,7 @@ public class Torrents(
Directory.CreateDirectory(Settings.Get.General.CopyAddedTorrents); Directory.CreateDirectory(Settings.Get.General.CopyAddedTorrents);
} }
var copyFileName = Path.Combine(Settings.Get.General.CopyAddedTorrents, $"{FileHelper.RemoveInvalidFileNameChars(magnet.Name)}.magnet"); var copyFileName = Path.Combine(Settings.Get.General.CopyAddedTorrents, $"{FileHelper.RemoveInvalidFileNameChars(magnet.Name!)}.magnet");
if (File.Exists(copyFileName)) if (File.Exists(copyFileName))
{ {
@ -168,7 +168,7 @@ public class Torrents(
var id = await TorrentClient.AddFile(bytes); var id = await TorrentClient.AddFile(bytes);
var hash = monoTorrent.InfoHash.ToHex(); var hash = monoTorrent.InfoHashes.V1OrV2.ToHex();
var newTorrent = await Add(id, hash, fileAsBase64, true, torrent); var newTorrent = await Add(id, hash, fileAsBase64, true, torrent);

View file

@ -316,6 +316,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
foreach (var url in urls) foreach (var url in urls)
{ {
try{
if (url.StartsWith("magnet")) if (url.StartsWith("magnet"))
{ {
await qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null); await qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
@ -330,6 +331,15 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
{ {
return BadRequest($"Invalid torrent link format {url}"); return BadRequest($"Invalid torrent link format {url}");
} }
}
catch (RDNET.RealDebridException ex)
{
// Infringing file.
if (ex.ErrorCode == 35)
{
return Ok("Fails.");
}
}
} }
return Ok(); return Ok();

View file

@ -134,7 +134,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes); var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
var result = await torrents.GetAvailableFiles(torrent.InfoHash.ToHex()); var result = await torrents.GetAvailableFiles(torrent.InfoHashes.V1OrV2.ToHex());
return Ok(result); return Ok(result);
} }
@ -148,9 +148,14 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
return BadRequest(); return BadRequest();
} }
if (String.IsNullOrEmpty(request.MagnetLink))
{
return BadRequest("MagnetLink cannot be null or empty");
}
var magnet = MagnetLink.Parse(request.MagnetLink); var magnet = MagnetLink.Parse(request.MagnetLink);
var result = await torrents.GetAvailableFiles(magnet.InfoHash.ToHex()); var result = await torrents.GetAvailableFiles(magnet.InfoHashes.V1OrV2.ToHex());
return Ok(result); return Ok(result);
} }
@ -226,7 +231,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
{ {
var magnet = MagnetLink.Parse(request.MagnetLink); var magnet = MagnetLink.Parse(request.MagnetLink);
availableFiles = await torrents.GetAvailableFiles(magnet.InfoHash.ToHex()); availableFiles = await torrents.GetAvailableFiles(magnet.InfoHashes.V1OrV2.ToHex());
} }
else if (file != null) else if (file != null)
{ {
@ -240,7 +245,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes); var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
availableFiles = await torrents.GetAvailableFiles(torrent.InfoHash.ToHex()); availableFiles = await torrents.GetAvailableFiles(torrent.InfoHashes.V1OrV2.ToHex());
} }
else else
{ {

View file

@ -168,6 +168,7 @@ try
if (basePath != null) if (basePath != null)
{ {
app.UseMiddleware<BaseHrefMiddleware>(basePath); app.UseMiddleware<BaseHrefMiddleware>(basePath);
app.UsePathBase($"/{basePath.TrimStart('/').TrimEnd('/')}/");
} }
app.UseMiddleware<RequestLoggingMiddleware>(); app.UseMiddleware<RequestLoggingMiddleware>();

View file

@ -11,7 +11,8 @@
"RdtClient.Web": { "RdtClient.Web": {
"commandName": "Project", "commandName": "Project",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development",
"BASE_PATH": "test"
}, },
"applicationUrl": "http://localhost:6500" "applicationUrl": "http://localhost:6500"
}, },

View file

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId> <UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
<Version>2.0.80</Version> <Version>2.0.86</Version>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
@ -29,17 +29,17 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.7" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="8.0.7" /> <PackageReference Include="Microsoft.Extensions.Identity.Core" Version="8.0.8" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup> </ItemGroup>