Compare commits
33 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
858bd7c95c | ||
|
|
2822653d3b | ||
|
|
e8a86cf806 | ||
|
|
38530cd6fd | ||
|
|
a7fb1e3e9d | ||
|
|
5c8a078927 | ||
|
|
f369e97d6e | ||
|
|
a9013e0c2a | ||
|
|
c6f6008591 | ||
|
|
b8e467652b | ||
|
|
e1635a43a9 | ||
|
|
f5cee6e2f3 | ||
|
|
767038528a | ||
|
|
a82e895a03 | ||
|
|
68edd4d80d | ||
|
|
d6f747c8d8 | ||
|
|
4eb34cbef9 | ||
|
|
5b7b526390 | ||
|
|
49ab603708 | ||
|
|
1166e9ba99 | ||
|
|
a86056a3ef | ||
|
|
1645320199 | ||
|
|
0e00062992 | ||
|
|
157fc8b2f2 | ||
|
|
45279115dc | ||
|
|
489bf6d7e0 | ||
|
|
5518e7f06b | ||
|
|
84cbd5056f | ||
|
|
c769bd08f0 | ||
|
|
8142225ee9 | ||
|
|
284ef44e30 | ||
|
|
fe6a2da266 | ||
|
|
8e49e500ea |
55 changed files with 1468 additions and 652 deletions
14
.devcontainer/Dockerfile
Normal file
14
.devcontainer/Dockerfile
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
FROM mcr.microsoft.com/devcontainers/dotnet:2-10.0-noble
|
||||||
|
|
||||||
|
ARG NODE_MAJOR=22
|
||||||
|
ARG ANGULAR_CLI_VERSION=21
|
||||||
|
|
||||||
|
RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs sqlite3 \
|
||||||
|
&& npm install -g "@angular/cli@^${ANGULAR_CLI_VERSION}" \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir -p /data/db /data/downloads \
|
||||||
|
&& chown -R vscode:vscode /data
|
||||||
51
.devcontainer/devcontainer.json
Normal file
51
.devcontainer/devcontainer.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"name": "rdt-client",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"NODE_MAJOR": "22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remoteUser": "vscode",
|
||||||
|
"runArgs": ["--init"],
|
||||||
|
"containerEnv": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"BASE_PATH": "",
|
||||||
|
"DOTNET_USE_POLLING_FILE_WATCHER": "1",
|
||||||
|
"CHOKIDAR_USEPOLLING": "true"
|
||||||
|
},
|
||||||
|
"forwardPorts": [4200, 6500],
|
||||||
|
"portsAttributes": {
|
||||||
|
"4200": {
|
||||||
|
"label": "Angular client",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"6500": {
|
||||||
|
"label": "RdtClient backend",
|
||||||
|
"onAutoForward": "silent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mounts": [
|
||||||
|
"source=rdtclient-data-db,target=/data/db,type=volume",
|
||||||
|
"source=rdtclient-data-downloads,target=/data/downloads,type=volume",
|
||||||
|
"source=rdtclient-nuget,target=/home/vscode/.nuget,type=volume",
|
||||||
|
"source=rdtclient-npm-cache,target=/home/vscode/.npm,type=volume"
|
||||||
|
],
|
||||||
|
"postCreateCommand": "/bin/bash .devcontainer/post-create.sh",
|
||||||
|
"postStartCommand": "/bin/bash .devcontainer/post-start.sh",
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"ms-dotnettools.csdevkit",
|
||||||
|
"Angular.ng-template",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"dotnet.defaultSolution": "server/RdtClient.sln",
|
||||||
|
"terminal.integrated.defaultProfile.linux": "zsh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
.devcontainer/post-create.sh
Executable file
16
.devcontainer/post-create.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
sudo install -d -m 0775 -o vscode -g vscode \
|
||||||
|
/data/db \
|
||||||
|
/data/downloads \
|
||||||
|
/home/vscode/.nuget \
|
||||||
|
/home/vscode/.nuget/NuGet \
|
||||||
|
/home/vscode/.nuget/packages \
|
||||||
|
/home/vscode/.npm
|
||||||
|
|
||||||
|
sudo chown -R vscode:vscode /data /home/vscode/.nuget /home/vscode/.npm
|
||||||
|
|
||||||
|
dotnet restore server/RdtClient.sln
|
||||||
|
npm install --prefix client
|
||||||
13
.devcontainer/post-start.sh
Executable file
13
.devcontainer/post-start.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
sudo install -d -m 0775 -o vscode -g vscode \
|
||||||
|
/data/db \
|
||||||
|
/data/downloads \
|
||||||
|
/home/vscode/.nuget \
|
||||||
|
/home/vscode/.nuget/NuGet \
|
||||||
|
/home/vscode/.nuget/packages \
|
||||||
|
/home/vscode/.npm
|
||||||
|
|
||||||
|
sudo chown -R vscode:vscode /data /home/vscode/.nuget /home/vscode/.npm
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -8,3 +8,4 @@ server/RdtClient.Web/appsettings.Development.json
|
||||||
data
|
data
|
||||||
Dockerfile.dev
|
Dockerfile.dev
|
||||||
test.bat
|
test.bat
|
||||||
|
.DS_Store
|
||||||
|
|
|
||||||
25
CHANGELOG.md
25
CHANGELOG.md
|
|
@ -6,6 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased
|
## [Unreleased
|
||||||
|
|
||||||
|
## [2.0.138] - 2026-06-05
|
||||||
|
### Fixed
|
||||||
|
- SABnzbd fixes.
|
||||||
|
|
||||||
|
## [2.0.137] - 2026-06-05
|
||||||
|
### Fixed
|
||||||
|
- Fix torbox save path mapping for single file downloads.
|
||||||
|
|
||||||
|
## [2.0.136] - 2026-05-30
|
||||||
|
### Added
|
||||||
|
- Added devcontainer development workflow.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Upgraded torbox.net dependency.
|
||||||
|
|
||||||
|
## [2.0.135] - 2026-05-27
|
||||||
|
### Changed
|
||||||
|
- When adding a torrent through the qBittorrent endpoints, it will wait to see if the torrent gets added properly or errors out, resulting in a better experience for infringing files and Sonarr / Radarr.
|
||||||
|
- Upgraded torbox.net dependency.
|
||||||
|
- Support SABnzbd API key auth, thanks to @ALenfant!
|
||||||
|
|
||||||
|
## [2.0.134] - 2026-05-22
|
||||||
|
### Changed
|
||||||
|
- Upgraded torbox.net dependency.
|
||||||
|
|
||||||
## [2.0.133] - 2026-05-17
|
## [2.0.133] - 2026-05-17
|
||||||
### Added
|
### Added
|
||||||
- Added NZB support for Premiumize, thanks to @ALenfant!
|
- Added NZB support for Premiumize, thanks to @ALenfant!
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -3,7 +3,7 @@
|
||||||
This is a web interface to manage your torrents on Real-Debrid, AllDebrid, Premiumize, TorBox or DebridLink. It supports the following features:
|
This is a web interface to manage your torrents on Real-Debrid, AllDebrid, Premiumize, TorBox or DebridLink. It supports the following features:
|
||||||
|
|
||||||
- Add new torrents through magnets or files
|
- Add new torrents through magnets or files
|
||||||
- Add usenet downloads through NZB files (TorBox only)
|
- Add usenet downloads through NZB files (TorBox and Premiumize only)
|
||||||
- Download all files from Real-Debrid, AllDebrid, Premiumize or TorBox to your local machine automatically
|
- Download all files from Real-Debrid, AllDebrid, Premiumize or TorBox to your local machine automatically
|
||||||
- Unpack all files when finished downloading
|
- Unpack all files when finished downloading
|
||||||
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr, Radarr or Couchpotato.
|
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr, Radarr or Couchpotato.
|
||||||
|
|
@ -166,7 +166,9 @@ It has the following options:
|
||||||
|
|
||||||
### Connecting Sonarr/Radarr
|
### Connecting Sonarr/Radarr
|
||||||
|
|
||||||
RdtClient emulates the qBittorrent web protocol and allow applications to use those APIs. This way you can use Sonarr and Radarr to download directly from RealDebrid.
|
RdtClient emulates the qBittorrent web protocol and allows applications to use those APIs. This way you can use Sonarr and Radarr to download directly from RealDebrid.
|
||||||
|
|
||||||
|
#### Torrents
|
||||||
|
|
||||||
1. Login to Sonarr or Radarr and click `Settings`.
|
1. Login to Sonarr or Radarr and click `Settings`.
|
||||||
1. Go to the `Download Client` tab and click the plus to add.
|
1. Go to the `Download Client` tab and click the plus to add.
|
||||||
|
|
@ -183,6 +185,24 @@ When downloading files it will append the `category` setting in the Sonarr/Radar
|
||||||
|
|
||||||
Notice: the progress and ETA reported in Sonarr's Activity tab will not be accurate, but it will report the torrent as completed so it can be processed after it is done downloading.
|
Notice: the progress and ETA reported in Sonarr's Activity tab will not be accurate, but it will report the torrent as completed so it can be processed after it is done downloading.
|
||||||
|
|
||||||
|
#### Usenet/NZB
|
||||||
|
|
||||||
|
RdtClient also emulates part of the SABnzbd API so Sonarr and Radarr can add NZB downloads. This requires a provider that supports Usenet/NZB downloads, currently TorBox or Premiumize.
|
||||||
|
|
||||||
|
1. Login to Sonarr or Radarr and click `Settings`.
|
||||||
|
1. Go to the `Download Client` tab and click the plus to add.
|
||||||
|
1. Click `SABnzbd` in the list.
|
||||||
|
1. Enter the IP or hostname of RdtClient in the `Host` field.
|
||||||
|
1. Enter `6500` in the `Port` field.
|
||||||
|
1. Enable `Use SSL` only if you access RdtClient through HTTPS.
|
||||||
|
1. Leave `URL Base` empty unless RdtClient is configured with a `BasePath`, for example `/rdt`.
|
||||||
|
1. If RdtClient authentication is enabled, leave `API Key` empty and enter your RdtClient username and password. If your client only supports an API key, enter `{username}:{password}` in `API Key`.
|
||||||
|
1. If RdtClient authentication is disabled, enter any value in `API Key`, for example `rdtclient`, and leave username/password empty.
|
||||||
|
1. Set the category to `sonarr` for Sonarr or `radarr` for Radarr.
|
||||||
|
1. Hit `Test` and then `Save` if all is well.
|
||||||
|
|
||||||
|
When importing completed NZB downloads, Sonarr/Radarr must be able to access the path reported by RdtClient. In Docker setups this may require a Remote Path Mapping from the RdtClient download path to the path mounted inside Sonarr/Radarr.
|
||||||
|
|
||||||
### Running within a folder
|
### Running within a folder
|
||||||
|
|
||||||
By default the application runs in the root of your hosted address (i.e. https://rdt.myserver.com/), but if you want to run it as a relative folder (i.e. https://myserver.com/rdt) you will have to change the `BasePath` setting in the `appsettings.json` file. You can set the `BASE_PATH` environment variable for docker enviroments.
|
By default the application runs in the root of your hosted address (i.e. https://rdt.myserver.com/), but if you want to run it as a relative folder (i.e. https://myserver.com/rdt) you will have to change the `BasePath` setting in the `appsettings.json` file. You can set the `BASE_PATH` environment variable for docker enviroments.
|
||||||
|
|
@ -198,6 +218,17 @@ By default the application runs in the root of your hosted address (i.e. https:/
|
||||||
- Visual Studio 2025
|
- Visual Studio 2025
|
||||||
- (optional) Resharper
|
- (optional) Resharper
|
||||||
|
|
||||||
|
### Dev Container
|
||||||
|
|
||||||
|
The repository includes a dev container under `.devcontainer/` for the split development workflow used by this project.
|
||||||
|
|
||||||
|
It installs .NET 10 and Node 22, forwards ports `4200` and `6500`, and persists `/data/db` and `/data/downloads` in named volumes so the local SQLite database, logs, and downloads survive container rebuilds.
|
||||||
|
|
||||||
|
1. Open the repository in the dev container.
|
||||||
|
1. In one terminal run `dotnet watch run --project server/RdtClient.Web`.
|
||||||
|
1. In another terminal run `cd client && npm start`.
|
||||||
|
1. Open `http://localhost:4200`. The Angular dev server proxies `/Api` and `/hub` to the backend running on `6500`.
|
||||||
|
|
||||||
1. Open the client folder project in VS Code and run `npm install`.
|
1. Open the client folder project in VS Code and run `npm install`.
|
||||||
1. To debug run `ng serve`, to build run `ng build -c production`.
|
1. To debug run `ng serve`, to build run `ng build -c production`.
|
||||||
1. Open the Visual Studio 2025 project `RdtClient.sln` and `Publish` the `RdtClient.Web` to the given `PublishFolder` target.
|
1. Open the Visual Studio 2025 project `RdtClient.sln` and `Publish` the `RdtClient.Web` to the given `PublishFolder` target.
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ export function getTorrentStatus(torrent: Torrent): string {
|
||||||
return 'Finished';
|
return 'Finished';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prefix = torrent.type === 1 ? 'NZB' : 'Torrent';
|
||||||
|
|
||||||
switch (torrent.rdStatus) {
|
switch (torrent.rdStatus) {
|
||||||
case RealDebridStatus.Queued:
|
case RealDebridStatus.Queued:
|
||||||
return 'Not Yet Added to Provider';
|
return 'Not Yet Added to Provider';
|
||||||
|
|
@ -106,17 +108,17 @@ export function getTorrentStatus(torrent: Torrent): string {
|
||||||
return 'Torrent stalled';
|
return 'Torrent stalled';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Torrent downloading (${torrent.rdProgress}% - ${fileSizePipe.transform(torrent.rdSpeed, 'filesize') as string}/s)`;
|
return `${prefix} downloading (${torrent.rdProgress}% - ${fileSizePipe.transform(torrent.rdSpeed, 'filesize') as string}/s)`;
|
||||||
case RealDebridStatus.Processing:
|
case RealDebridStatus.Processing:
|
||||||
return 'Torrent processing';
|
return `${prefix} processing`;
|
||||||
case RealDebridStatus.WaitingForFileSelection:
|
case RealDebridStatus.WaitingForFileSelection:
|
||||||
return 'Torrent waiting for file selection';
|
return `${prefix} waiting for file selection`;
|
||||||
case RealDebridStatus.Error:
|
case RealDebridStatus.Error:
|
||||||
return `Torrent error: ${torrent.rdStatusRaw}`;
|
return `${prefix} error: ${torrent.rdStatusRaw}`;
|
||||||
case RealDebridStatus.Finished:
|
case RealDebridStatus.Finished:
|
||||||
return 'Torrent finished, waiting for download links';
|
return `${prefix} finished, waiting for download links`;
|
||||||
case RealDebridStatus.Uploading:
|
case RealDebridStatus.Uploading:
|
||||||
return 'Torrent uploading';
|
return `${prefix} uploading`;
|
||||||
default:
|
default:
|
||||||
return 'Unknown status';
|
return 'Unknown status';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ public class DownloadData(DataContext dataContext, ILogger<DownloadData>? logger
|
||||||
|
|
||||||
return DownloadAddResult.Added;
|
return DownloadAddResult.Added;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These shouldn't be possible any longer, but added for safety and until confirmed.
|
// These shouldn't be possible any longer, but added for safety and until confirmed.
|
||||||
catch (DbUpdateException ex)
|
catch (DbUpdateException ex)
|
||||||
{
|
{
|
||||||
|
|
@ -88,7 +89,9 @@ public class DownloadData(DataContext dataContext, ILogger<DownloadData>? logger
|
||||||
|
|
||||||
if (IsForeignKeyViolation(ex) && !await dataContext.Torrents.AsNoTracking().AnyAsync(m => m.TorrentId == torrentId))
|
if (IsForeignKeyViolation(ex) && !await dataContext.Torrents.AsNoTracking().AnyAsync(m => m.TorrentId == torrentId))
|
||||||
{
|
{
|
||||||
logger?.LogDebug("Skipped download creation after the torrent was deleted concurrently. TorrentId: {torrentId}, Path: {path}", torrentId, downloadInfo.RestrictedLink);
|
logger?.LogDebug("Skipped download creation after the torrent was deleted concurrently. TorrentId: {torrentId}, Path: {path}",
|
||||||
|
torrentId,
|
||||||
|
downloadInfo.RestrictedLink);
|
||||||
|
|
||||||
return DownloadAddResult.TorrentMissing;
|
return DownloadAddResult.TorrentMissing;
|
||||||
}
|
}
|
||||||
|
|
@ -273,7 +276,7 @@ public class DownloadData(DataContext dataContext, ILogger<DownloadData>? logger
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Reset(Guid downloadId)
|
public async Task Reset(Guid downloadId, DateTimeOffset? downloadQueued = null)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId)
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId)
|
||||||
|
|
@ -282,7 +285,7 @@ public class DownloadData(DataContext dataContext, ILogger<DownloadData>? logger
|
||||||
dbDownload.RetryCount = 0;
|
dbDownload.RetryCount = 0;
|
||||||
dbDownload.Link = null;
|
dbDownload.Link = null;
|
||||||
dbDownload.Added = DateTimeOffset.UtcNow;
|
dbDownload.Added = DateTimeOffset.UtcNow;
|
||||||
dbDownload.DownloadQueued = DateTimeOffset.UtcNow;
|
dbDownload.DownloadQueued = downloadQueued ?? DateTimeOffset.UtcNow;
|
||||||
dbDownload.DownloadStarted = null;
|
dbDownload.DownloadStarted = null;
|
||||||
dbDownload.DownloadFinished = null;
|
dbDownload.DownloadFinished = null;
|
||||||
dbDownload.UnpackingQueued = null;
|
dbDownload.UnpackingQueued = null;
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ public class TorrentData(DataContext dataContext, ILogger<TorrentData>? logger =
|
||||||
public async Task<IList<Torrent>> Get()
|
public async Task<IList<Torrent>> Get()
|
||||||
{
|
{
|
||||||
var torrents = await dataContext.Torrents
|
var torrents = await dataContext.Torrents
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Include(m => m.Downloads)
|
.Include(m => m.Downloads)
|
||||||
.OrderBy(m => m.Priority ?? 9999)
|
.OrderBy(m => m.Priority ?? 9999)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return torrents.OrderBy(m => m.Priority ?? 9999)
|
return torrents.OrderBy(m => m.Priority ?? 9999)
|
||||||
.ThenBy(m => m.Added)
|
.ThenBy(m => m.Added)
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ public class Torrent
|
||||||
public DateTimeOffset? RdEnded { get; set; }
|
public DateTimeOffset? RdEnded { get; set; }
|
||||||
public Int64? RdSpeed { get; set; }
|
public Int64? RdSpeed { get; set; }
|
||||||
public Int64? RdSeeders { get; set; }
|
public Int64? RdSeeders { get; set; }
|
||||||
|
|
||||||
public String? RdFiles
|
public String? RdFiles
|
||||||
{
|
{
|
||||||
get => _rdFiles;
|
get => _rdFiles;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ using System.Reflection;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using RdtClient.Data.Data;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.Internal;
|
|
||||||
using RdtClient.Service.BackgroundServices;
|
using RdtClient.Service.BackgroundServices;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
|
|
||||||
|
|
@ -17,6 +15,7 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
private readonly Mock<IServiceProvider> _serviceProviderMock;
|
private readonly Mock<IServiceProvider> _serviceProviderMock;
|
||||||
private readonly Mock<IServiceScope> _serviceScopeMock;
|
private readonly Mock<IServiceScope> _serviceScopeMock;
|
||||||
private readonly String _testPath;
|
private readonly String _testPath;
|
||||||
|
private readonly TestSettings _settings;
|
||||||
private readonly Mock<Torrents> _torrentsServiceMock;
|
private readonly Mock<Torrents> _torrentsServiceMock;
|
||||||
|
|
||||||
public WatchFolderCheckerTests()
|
public WatchFolderCheckerTests()
|
||||||
|
|
@ -25,7 +24,18 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
_serviceProviderMock = new();
|
_serviceProviderMock = new();
|
||||||
_serviceScopeMock = new();
|
_serviceScopeMock = new();
|
||||||
_scopeServiceProviderMock = new();
|
_scopeServiceProviderMock = new();
|
||||||
_torrentsServiceMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
|
|
||||||
|
_settings = new(new()
|
||||||
|
{
|
||||||
|
Watch = new()
|
||||||
|
{
|
||||||
|
Interval = 0,
|
||||||
|
Default = new()
|
||||||
|
},
|
||||||
|
DownloadClient = new()
|
||||||
|
});
|
||||||
|
|
||||||
|
_torrentsServiceMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
|
||||||
|
|
||||||
_serviceProviderMock
|
_serviceProviderMock
|
||||||
.Setup(x => x.GetService(typeof(IServiceScopeFactory)))
|
.Setup(x => x.GetService(typeof(IServiceScopeFactory)))
|
||||||
|
|
@ -42,10 +52,8 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
_testPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
_testPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
Directory.CreateDirectory(_testPath);
|
Directory.CreateDirectory(_testPath);
|
||||||
|
|
||||||
// Reset Settings and Startup
|
|
||||||
SetStartupReady(true);
|
SetStartupReady(true);
|
||||||
ResetSettings();
|
_settings.Current.Watch.Path = _testPath;
|
||||||
Settings.Get.Watch.Path = _testPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -62,22 +70,6 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
property?.SetValue(null, ready);
|
property?.SetValue(null, ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ResetSettings()
|
|
||||||
{
|
|
||||||
var settings = new DbSettings
|
|
||||||
{
|
|
||||||
Watch = new()
|
|
||||||
{
|
|
||||||
Interval = 0,
|
|
||||||
Default = new()
|
|
||||||
},
|
|
||||||
DownloadClient = new()
|
|
||||||
};
|
|
||||||
|
|
||||||
var property = typeof(SettingData).GetProperty("Get", BindingFlags.Public | BindingFlags.Static);
|
|
||||||
property?.SetValue(null, settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ResetPrevCheck(WatchFolderChecker checker)
|
private static void ResetPrevCheck(WatchFolderChecker checker)
|
||||||
{
|
{
|
||||||
var field = typeof(WatchFolderChecker).GetField("_prevCheck", BindingFlags.NonPublic | BindingFlags.Instance);
|
var field = typeof(WatchFolderChecker).GetField("_prevCheck", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
@ -92,7 +84,7 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
var content = "torrent content"u8.ToArray();
|
var content = "torrent content"u8.ToArray();
|
||||||
await File.WriteAllBytesAsync(filePath, content);
|
await File.WriteAllBytesAsync(filePath, content);
|
||||||
|
|
||||||
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object);
|
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings);
|
||||||
ResetPrevCheck(checker);
|
ResetPrevCheck(checker);
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
|
|
@ -131,7 +123,7 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
var content = "magnet:?xt=urn:btih:123";
|
var content = "magnet:?xt=urn:btih:123";
|
||||||
await File.WriteAllTextAsync(filePath, content);
|
await File.WriteAllTextAsync(filePath, content);
|
||||||
|
|
||||||
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object);
|
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings);
|
||||||
ResetPrevCheck(checker);
|
ResetPrevCheck(checker);
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
|
|
@ -170,7 +162,7 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
var content = "nzb content"u8.ToArray();
|
var content = "nzb content"u8.ToArray();
|
||||||
await File.WriteAllBytesAsync(filePath, content);
|
await File.WriteAllBytesAsync(filePath, content);
|
||||||
|
|
||||||
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object);
|
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings);
|
||||||
ResetPrevCheck(checker);
|
ResetPrevCheck(checker);
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
|
|
@ -209,7 +201,7 @@ public class WatchFolderCheckerTests : IDisposable
|
||||||
var filePath = Path.Combine(_testPath, "test.txt");
|
var filePath = Path.Combine(_testPath, "test.txt");
|
||||||
await File.WriteAllTextAsync(filePath, "ignore me");
|
await File.WriteAllTextAsync(filePath, "ignore me");
|
||||||
|
|
||||||
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object);
|
var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings);
|
||||||
ResetPrevCheck(checker);
|
ResetPrevCheck(checker);
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
|
|
|
||||||
1
server/RdtClient.Service.Test/GlobalTestConfig.cs
Normal file
1
server/RdtClient.Service.Test/GlobalTestConfig.cs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||||
|
|
@ -5,23 +5,24 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
<AssemblyName>RdtClient.Service.Test</AssemblyName>
|
<AssemblyName>RdtClient.Service.Test</AssemblyName>
|
||||||
<RootNamespace>RdtClient.Service.Test</RootNamespace>
|
<RootNamespace>RdtClient.Service.Test</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AllDebrid.NET" Version="1.0.18" />
|
<PackageReference Include="AllDebrid.NET" Version="1.0.18" />
|
||||||
<PackageReference Include="coverlet.collector" Version="10.0.0">
|
<PackageReference Include="coverlet.collector" Version="10.0.1">
|
||||||
<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.NET.Test.Sdk" Version="18.5.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="SharpCompress" Version="[0.42.1]" />
|
<PackageReference Include="SharpCompress" Version="[0.42.1]" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.1.1" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.1.1" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="22.1.1" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="22.1.1" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.1.1" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.1.1" />
|
||||||
<PackageReference Include="TorBox.NET" Version="1.7.0" />
|
<PackageReference Include="TorBox.NET" Version="2.1.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
@ -37,10 +38,4 @@
|
||||||
<ProjectReference Include="..\RdtClient.Service\RdtClient.Service.csproj" />
|
<ProjectReference Include="..\RdtClient.Service\RdtClient.Service.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="TorBoxNET">
|
|
||||||
<HintPath>..\..\..\torbox-net\TorBoxNET\bin\Release\netstandard2.0\TorBoxNET.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ public class TorrentDownloadRaceTests : IAsyncLifetime
|
||||||
var torrentId = Guid.NewGuid();
|
var torrentId = Guid.NewGuid();
|
||||||
|
|
||||||
await using var context = CreateContext();
|
await using var context = CreateContext();
|
||||||
|
|
||||||
context.Torrents.Add(new()
|
context.Torrents.Add(new()
|
||||||
{
|
{
|
||||||
TorrentId = torrentId,
|
TorrentId = torrentId,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Text;
|
||||||
using Moq;
|
using Moq;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
|
using TorrentRunnerState = RdtClient.Service.Services.TorrentRunnerState;
|
||||||
using TorrentsService = RdtClient.Service.Services.Torrents;
|
using TorrentsService = RdtClient.Service.Services.Torrents;
|
||||||
|
|
||||||
namespace RdtClient.Service.Test.Services;
|
namespace RdtClient.Service.Test.Services;
|
||||||
|
|
@ -28,7 +29,9 @@ public class NzbTorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using RdtClient.Data.Data;
|
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.DebridClient;
|
using RdtClient.Data.Models.DebridClient;
|
||||||
|
|
@ -14,15 +13,19 @@ public class QBittorrentTest
|
||||||
private readonly Mock<Authentication> _authenticationMock;
|
private readonly Mock<Authentication> _authenticationMock;
|
||||||
private readonly Mock<ILogger<QBittorrent>> _loggerMock;
|
private readonly Mock<ILogger<QBittorrent>> _loggerMock;
|
||||||
private readonly QBittorrent _qBittorrent;
|
private readonly QBittorrent _qBittorrent;
|
||||||
|
private readonly TestSettings _settings;
|
||||||
|
private readonly TorrentRunnerState _runnerState;
|
||||||
private readonly Mock<Torrents> _torrentsMock;
|
private readonly Mock<Torrents> _torrentsMock;
|
||||||
|
|
||||||
public QBittorrentTest()
|
public QBittorrentTest()
|
||||||
{
|
{
|
||||||
_loggerMock = new();
|
_loggerMock = new();
|
||||||
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
|
_settings = new();
|
||||||
|
_runnerState = new();
|
||||||
|
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, _runnerState);
|
||||||
_authenticationMock = new(null!, null!, null!);
|
_authenticationMock = new(null!, null!, null!);
|
||||||
|
|
||||||
_qBittorrent = new(_loggerMock.Object, null!, _authenticationMock.Object, _torrentsMock.Object, null!);
|
_qBittorrent = new(_loggerMock.Object, _settings, _authenticationMock.Object, _torrentsMock.Object, null!, _runnerState);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -174,19 +177,20 @@ public class QBittorrentTest
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
|
|
||||||
Assert.Collection(result!,
|
Assert.Collection(result!,
|
||||||
first =>
|
first =>
|
||||||
{
|
{
|
||||||
Assert.Equal(0, first.Index);
|
Assert.Equal(0, first.Index);
|
||||||
Assert.Equal("good.mkv", first.Name);
|
Assert.Equal("good.mkv", first.Name);
|
||||||
Assert.Equal(1, first.Priority);
|
Assert.Equal(1, first.Priority);
|
||||||
},
|
},
|
||||||
second =>
|
second =>
|
||||||
{
|
{
|
||||||
Assert.Equal(1, second.Index);
|
Assert.Equal(1, second.Index);
|
||||||
Assert.Equal("dangerous.exe", second.Name);
|
Assert.Equal("dangerous.exe", second.Name);
|
||||||
Assert.Equal(0, second.Priority);
|
Assert.Equal(0, second.Priority);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -214,8 +218,8 @@ public class QBittorrentTest
|
||||||
public async Task TorrentInfo_ShouldUseSelectedTopLevelFileName_WhenRootFileAddsOnlyTheExtension()
|
public async Task TorrentInfo_ShouldUseSelectedTopLevelFileName_WhenRootFileAddsOnlyTheExtension()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var previousMappedPath = SettingData.Get.DownloadClient.MappedPath;
|
var previousMappedPath = _settings.Current.DownloadClient.MappedPath;
|
||||||
SettingData.Get.DownloadClient.MappedPath = "/data/downloads";
|
_settings.Current.DownloadClient.MappedPath = "/data/downloads";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -248,7 +252,11 @@ public class QBittorrentTest
|
||||||
Type = DownloadType.Torrent
|
Type = DownloadType.Torrent
|
||||||
};
|
};
|
||||||
|
|
||||||
_torrentsMock.Setup(m => m.Get()).ReturnsAsync(new List<Torrent> { torrent });
|
_torrentsMock.Setup(m => m.Get())
|
||||||
|
.ReturnsAsync(new List<Torrent>
|
||||||
|
{
|
||||||
|
torrent
|
||||||
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _qBittorrent.TorrentInfo();
|
var result = await _qBittorrent.TorrentInfo();
|
||||||
|
|
@ -260,16 +268,16 @@ public class QBittorrentTest
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
SettingData.Get.DownloadClient.MappedPath = previousMappedPath;
|
_settings.Current.DownloadClient.MappedPath = previousMappedPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TorrentInfo_ShouldUseExistingCompletedContentRoot_WhenSingleFileDirectoryDiffersFromRdName()
|
public async Task TorrentInfo_ShouldUseExistingCompletedContentRoot_WhenSingleFileDirectoryDiffersFromRdName()
|
||||||
{
|
{
|
||||||
var previousMappedPath = SettingData.Get.DownloadClient.MappedPath;
|
var previousMappedPath = _settings.Current.DownloadClient.MappedPath;
|
||||||
var mappedPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
var mappedPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||||
SettingData.Get.DownloadClient.MappedPath = mappedPath;
|
_settings.Current.DownloadClient.MappedPath = mappedPath;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -308,7 +316,11 @@ public class QBittorrentTest
|
||||||
Type = DownloadType.Torrent
|
Type = DownloadType.Torrent
|
||||||
};
|
};
|
||||||
|
|
||||||
_torrentsMock.Setup(m => m.Get()).ReturnsAsync(new List<Torrent> { torrent });
|
_torrentsMock.Setup(m => m.Get())
|
||||||
|
.ReturnsAsync(new List<Torrent>
|
||||||
|
{
|
||||||
|
torrent
|
||||||
|
});
|
||||||
|
|
||||||
var result = await _qBittorrent.TorrentInfo();
|
var result = await _qBittorrent.TorrentInfo();
|
||||||
|
|
||||||
|
|
@ -317,7 +329,7 @@ public class QBittorrentTest
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
SettingData.Get.DownloadClient.MappedPath = previousMappedPath;
|
_settings.Current.DownloadClient.MappedPath = previousMappedPath;
|
||||||
|
|
||||||
if (Directory.Exists(mappedPath))
|
if (Directory.Exists(mappedPath))
|
||||||
{
|
{
|
||||||
|
|
@ -329,9 +341,9 @@ public class QBittorrentTest
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TorrentInfo_ShouldUseExistingCompletedContentRoot_WhenSelectedFileIsNestedUnderDifferentRoot()
|
public async Task TorrentInfo_ShouldUseExistingCompletedContentRoot_WhenSelectedFileIsNestedUnderDifferentRoot()
|
||||||
{
|
{
|
||||||
var previousMappedPath = SettingData.Get.DownloadClient.MappedPath;
|
var previousMappedPath = _settings.Current.DownloadClient.MappedPath;
|
||||||
var mappedPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
var mappedPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||||
SettingData.Get.DownloadClient.MappedPath = mappedPath;
|
_settings.Current.DownloadClient.MappedPath = mappedPath;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -373,7 +385,11 @@ public class QBittorrentTest
|
||||||
Type = DownloadType.Torrent
|
Type = DownloadType.Torrent
|
||||||
};
|
};
|
||||||
|
|
||||||
_torrentsMock.Setup(m => m.Get()).ReturnsAsync(new List<Torrent> { torrent });
|
_torrentsMock.Setup(m => m.Get())
|
||||||
|
.ReturnsAsync(new List<Torrent>
|
||||||
|
{
|
||||||
|
torrent
|
||||||
|
});
|
||||||
|
|
||||||
var result = await _qBittorrent.TorrentInfo();
|
var result = await _qBittorrent.TorrentInfo();
|
||||||
|
|
||||||
|
|
@ -382,7 +398,7 @@ public class QBittorrentTest
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
SettingData.Get.DownloadClient.MappedPath = previousMappedPath;
|
_settings.Current.DownloadClient.MappedPath = previousMappedPath;
|
||||||
|
|
||||||
if (Directory.Exists(mappedPath))
|
if (Directory.Exists(mappedPath))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using RdtClient.Data.Data;
|
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.Internal;
|
using RdtClient.Data.Models.Internal;
|
||||||
|
|
@ -16,11 +15,13 @@ public class SabnzbdTest
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Mock<ILogger<Sabnzbd>> _loggerMock = new();
|
private readonly Mock<ILogger<Sabnzbd>> _loggerMock = new();
|
||||||
|
private readonly TestSettings _settings;
|
||||||
private readonly Mock<Torrents> _torrentsMock;
|
private readonly Mock<Torrents> _torrentsMock;
|
||||||
|
|
||||||
public SabnzbdTest()
|
public SabnzbdTest()
|
||||||
{
|
{
|
||||||
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
|
_settings = new();
|
||||||
|
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(new List<Torrent>());
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(new List<Torrent>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@ public class SabnzbdTest
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
_torrentsMock.Setup(t => t.GetDownloadStats(It.IsAny<Guid>())).Returns((0, 1000, 500));
|
_torrentsMock.Setup(t => t.GetDownloadStats(It.IsAny<Guid>())).Returns((0, 1000, 500));
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetQueue();
|
var result = await sabnzbd.GetQueue();
|
||||||
|
|
@ -90,7 +91,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetQueue();
|
var result = await sabnzbd.GetQueue();
|
||||||
|
|
@ -134,7 +135,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetQueue();
|
var result = await sabnzbd.GetQueue();
|
||||||
|
|
@ -186,7 +187,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetQueue();
|
var result = await sabnzbd.GetQueue();
|
||||||
|
|
@ -224,7 +225,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetQueue();
|
var result = await sabnzbd.GetQueue();
|
||||||
|
|
@ -260,7 +261,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetHistory();
|
var result = await sabnzbd.GetHistory();
|
||||||
|
|
@ -275,7 +276,7 @@ public class SabnzbdTest
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var savePath = @"C:\Downloads";
|
var savePath = @"C:\Downloads";
|
||||||
SettingData.Get.DownloadClient.MappedPath = savePath;
|
_settings.Current.DownloadClient.MappedPath = savePath;
|
||||||
|
|
||||||
var torrentList = new List<Torrent>
|
var torrentList = new List<Torrent>
|
||||||
{
|
{
|
||||||
|
|
@ -292,7 +293,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetHistory();
|
var result = await sabnzbd.GetHistory();
|
||||||
|
|
@ -308,7 +309,7 @@ public class SabnzbdTest
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var savePath = @"C:\Downloads";
|
var savePath = @"C:\Downloads";
|
||||||
SettingData.Get.DownloadClient.MappedPath = savePath;
|
_settings.Current.DownloadClient.MappedPath = savePath;
|
||||||
|
|
||||||
var torrentList = new List<Torrent>
|
var torrentList = new List<Torrent>
|
||||||
{
|
{
|
||||||
|
|
@ -325,7 +326,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetHistory();
|
var result = await sabnzbd.GetHistory();
|
||||||
|
|
@ -355,7 +356,7 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sabnzbd.GetHistory();
|
var result = await sabnzbd.GetHistory();
|
||||||
|
|
@ -365,11 +366,69 @@ public class SabnzbdTest
|
||||||
Assert.Equal("Failed", result.Slots[0].Status);
|
Assert.Equal("Failed", result.Slots[0].Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(TorrentFinishedAction.RemoveAllTorrents, true, true, true, true)]
|
||||||
|
[InlineData(TorrentFinishedAction.RemoveAllTorrents, false, true, true, false)]
|
||||||
|
[InlineData(TorrentFinishedAction.RemoveRealDebrid, true, false, true, true)]
|
||||||
|
[InlineData(TorrentFinishedAction.RemoveRealDebrid, false, false, true, false)]
|
||||||
|
[InlineData(TorrentFinishedAction.RemoveClient, true, true, false, true)]
|
||||||
|
[InlineData(TorrentFinishedAction.RemoveClient, false, true, false, false)]
|
||||||
|
public async Task Delete_ShouldRespectFinishedActionAndDeleteFiles(
|
||||||
|
TorrentFinishedAction finishedAction,
|
||||||
|
Boolean deleteFiles,
|
||||||
|
Boolean expectedDeleteData,
|
||||||
|
Boolean expectedDeleteRdTorrent,
|
||||||
|
Boolean expectedDeleteLocalFiles)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var torrentId = Guid.NewGuid();
|
||||||
|
_settings.Current.Integrations.Default.FinishedAction = finishedAction;
|
||||||
|
|
||||||
|
_torrentsMock.Setup(t => t.GetByHash("hash1"))
|
||||||
|
.ReturnsAsync(new Torrent
|
||||||
|
{
|
||||||
|
TorrentId = torrentId,
|
||||||
|
Hash = "hash1",
|
||||||
|
Type = DownloadType.Nzb
|
||||||
|
});
|
||||||
|
|
||||||
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sabnzbd.Delete("hash1", deleteFiles);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_torrentsMock.Verify(t => t.Delete(torrentId, expectedDeleteData, expectedDeleteRdTorrent, expectedDeleteLocalFiles), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Delete_ShouldNotDelete_WhenFinishedActionIsNone()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.Integrations.Default.FinishedAction = TorrentFinishedAction.None;
|
||||||
|
|
||||||
|
_torrentsMock.Setup(t => t.GetByHash("hash1"))
|
||||||
|
.ReturnsAsync(new Torrent
|
||||||
|
{
|
||||||
|
TorrentId = Guid.NewGuid(),
|
||||||
|
Hash = "hash1",
|
||||||
|
Type = DownloadType.Nzb
|
||||||
|
});
|
||||||
|
|
||||||
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sabnzbd.Delete("hash1", true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_torrentsMock.Verify(t => t.Delete(It.IsAny<Guid>(), It.IsAny<Boolean>(), It.IsAny<Boolean>(), It.IsAny<Boolean>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetConfig_ShouldReturnCorrectConfig()
|
public void GetConfig_ShouldReturnCorrectConfig()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = sabnzbd.GetConfig();
|
var result = sabnzbd.GetConfig();
|
||||||
|
|
@ -399,9 +458,9 @@ public class SabnzbdTest
|
||||||
|
|
||||||
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
|
||||||
|
|
||||||
SettingData.Get.General.Categories = "TV, Music, *";
|
_settings.Current.General.Categories = "TV, Music, *";
|
||||||
|
|
||||||
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);
|
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = sabnzbd.GetCategories();
|
var result = sabnzbd.GetCategories();
|
||||||
|
|
|
||||||
|
|
@ -8,30 +8,20 @@ using RdtClient.Service.Services.DebridClients;
|
||||||
|
|
||||||
namespace RdtClient.Service.Test.Services.TorrentClients;
|
namespace RdtClient.Service.Test.Services.TorrentClients;
|
||||||
|
|
||||||
[CollectionDefinition(nameof(PremiumizeSettingsCollection), DisableParallelization = true)]
|
public class PremiumizeDebridClientTest
|
||||||
public class PremiumizeSettingsCollection;
|
|
||||||
|
|
||||||
[Collection(nameof(PremiumizeSettingsCollection))]
|
|
||||||
public class PremiumizeDebridClientTest : IDisposable
|
|
||||||
{
|
{
|
||||||
private readonly Mock<IDownloadableFileFilter> _fileFilterMock;
|
private readonly Mock<IDownloadableFileFilter> _fileFilterMock;
|
||||||
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
|
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
|
||||||
private readonly Mock<ILogger<PremiumizeDebridClient>> _loggerMock;
|
private readonly Mock<ILogger<PremiumizeDebridClient>> _loggerMock;
|
||||||
private readonly String _originalApiKey;
|
private readonly TestSettings _settings;
|
||||||
|
|
||||||
public PremiumizeDebridClientTest()
|
public PremiumizeDebridClientTest()
|
||||||
{
|
{
|
||||||
_loggerMock = new();
|
_loggerMock = new();
|
||||||
_httpClientFactoryMock = new();
|
_httpClientFactoryMock = new();
|
||||||
_fileFilterMock = new();
|
_fileFilterMock = new();
|
||||||
_originalApiKey = Settings.Get.Provider.ApiKey ?? String.Empty;
|
_settings = new();
|
||||||
|
_settings.Current.Provider.ApiKey = "test-api-key";
|
||||||
Settings.Get.Provider.ApiKey = "test-api-key";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Settings.Get.Provider.ApiKey = _originalApiKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -208,7 +198,7 @@ public class PremiumizeDebridClientTest : IDisposable
|
||||||
{
|
{
|
||||||
_httpClientFactoryMock.Setup(m => m.CreateClient(It.IsAny<String>())).Returns(new HttpClient(handler));
|
_httpClientFactoryMock.Setup(m => m.CreateClient(It.IsAny<String>())).Returns(new HttpClient(handler));
|
||||||
|
|
||||||
return new(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object);
|
return new(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpResponseMessage JsonResponse(String json, HttpStatusCode statusCode = HttpStatusCode.OK)
|
private static HttpResponseMessage JsonResponse(String json, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class TorBoxDebridClientTest
|
||||||
private readonly Mock<IDownloadableFileFilter> _fileFilterMock;
|
private readonly Mock<IDownloadableFileFilter> _fileFilterMock;
|
||||||
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
|
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
|
||||||
private readonly Mock<ILogger<TorBoxDebridClient>> _loggerMock;
|
private readonly Mock<ILogger<TorBoxDebridClient>> _loggerMock;
|
||||||
|
private readonly TestSettings _settings;
|
||||||
|
|
||||||
public TorBoxDebridClientTest()
|
public TorBoxDebridClientTest()
|
||||||
{
|
{
|
||||||
|
|
@ -28,12 +29,13 @@ public class TorBoxDebridClientTest
|
||||||
_httpClientFactoryMock = new();
|
_httpClientFactoryMock = new();
|
||||||
_fileFilterMock = new();
|
_fileFilterMock = new();
|
||||||
_coordinatorMock = new();
|
_coordinatorMock = new();
|
||||||
|
_settings = new();
|
||||||
|
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
_httpClientFactoryMock.Setup(m => m.CreateClient(It.IsAny<String>())).Returns(httpClient);
|
_httpClientFactoryMock.Setup(m => m.CreateClient(It.IsAny<String>())).Returns(httpClient);
|
||||||
|
|
||||||
Settings.Get.Provider.ApiKey = "test-api-key";
|
_settings.Current.Provider.ApiKey = "test-api-key";
|
||||||
Settings.Get.Provider.Timeout = 100;
|
_settings.Current.Provider.Timeout = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -44,6 +46,7 @@ public class TorBoxDebridClientTest
|
||||||
{
|
{
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
|
Id = 12345,
|
||||||
Hash = "hash1",
|
Hash = "hash1",
|
||||||
Name = "torrent1",
|
Name = "torrent1",
|
||||||
Size = 1000,
|
Size = 1000,
|
||||||
|
|
@ -64,7 +67,7 @@ public class TorBoxDebridClientTest
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
clientMock.Protected().Setup<Task<IEnumerable<TorrentInfoResult>?>>("GetCurrentTorrents").ReturnsAsync(torrents);
|
clientMock.Protected().Setup<Task<IEnumerable<TorrentInfoResult>?>>("GetCurrentTorrents").ReturnsAsync(torrents);
|
||||||
clientMock.Protected().Setup<Task<IEnumerable<TorrentInfoResult>?>>("GetQueuedTorrents").ReturnsAsync(new List<TorrentInfoResult>());
|
clientMock.Protected().Setup<Task<IEnumerable<TorrentInfoResult>?>>("GetQueuedTorrents").ReturnsAsync(new List<TorrentInfoResult>());
|
||||||
clientMock.Protected().Setup<Task<IEnumerable<UsenetInfoResult>?>>("GetCurrentUsenet").ReturnsAsync(nzbs);
|
clientMock.Protected().Setup<Task<IEnumerable<UsenetInfoResult>?>>("GetCurrentUsenet").ReturnsAsync(nzbs);
|
||||||
|
|
@ -76,7 +79,7 @@ public class TorBoxDebridClientTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(2, result.Count);
|
Assert.Equal(2, result.Count);
|
||||||
|
|
||||||
var torrentResult = result.FirstOrDefault(r => r.Id == "hash1");
|
var torrentResult = result.FirstOrDefault(r => r.Id == "12345");
|
||||||
Assert.NotNull(torrentResult);
|
Assert.NotNull(torrentResult);
|
||||||
Assert.Equal(DownloadType.Torrent, torrentResult.Type);
|
Assert.Equal(DownloadType.Torrent, torrentResult.Type);
|
||||||
|
|
||||||
|
|
@ -114,7 +117,7 @@ public class TorBoxDebridClientTest
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(availability);
|
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(availability);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -157,7 +160,7 @@ public class TorBoxDebridClientTest
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(torrentAvailability);
|
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(torrentAvailability);
|
||||||
clientMock.Protected().Setup<Task<Response<List<AvailableUsenet?>>>>("GetUsenetAvailability", hash).ReturnsAsync(usenetAvailability);
|
clientMock.Protected().Setup<Task<Response<List<AvailableUsenet?>>>>("GetUsenetAvailability", hash).ReturnsAsync(usenetAvailability);
|
||||||
|
|
||||||
|
|
@ -186,7 +189,7 @@ public class TorBoxDebridClientTest
|
||||||
Data = new()
|
Data = new()
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(torrentAvailability);
|
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(torrentAvailability);
|
||||||
clientMock.Protected().Setup<Task<Response<List<AvailableUsenet?>>>>("GetUsenetAvailability", hash).ReturnsAsync(usenetAvailability);
|
clientMock.Protected().Setup<Task<Response<List<AvailableUsenet?>>>>("GetUsenetAvailability", hash).ReturnsAsync(usenetAvailability);
|
||||||
|
|
||||||
|
|
@ -198,17 +201,17 @@ public class TorBoxDebridClientTest
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Delete_CallsTorrentsControl_WhenTypeIsTorrent()
|
public async Task Delete_CallsTorrentsControlById_WhenTypeIsTorrent()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
RdId = "torrent-id",
|
RdId = "12345",
|
||||||
Type = DownloadType.Torrent
|
Type = DownloadType.Torrent
|
||||||
};
|
};
|
||||||
|
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
||||||
|
|
@ -218,7 +221,7 @@ public class TorBoxDebridClientTest
|
||||||
await clientMock.Object.Delete(torrent);
|
await clientMock.Object.Delete(torrent);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
torrentsApiMock.Verify(m => m.ControlAsync("torrent-id", "delete", It.IsAny<CancellationToken>()), Times.Once);
|
torrentsApiMock.Verify(m => m.ControlByIdAsync(12345, "delete", It.IsAny<CancellationToken>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -232,7 +235,7 @@ public class TorBoxDebridClientTest
|
||||||
};
|
};
|
||||||
|
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
||||||
|
|
@ -258,7 +261,7 @@ public class TorBoxDebridClientTest
|
||||||
var link = "https://torbox.app/d/123/456";
|
var link = "https://torbox.app/d/123/456";
|
||||||
|
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
||||||
|
|
@ -291,7 +294,7 @@ public class TorBoxDebridClientTest
|
||||||
var link = "https://torbox.app/d/123/456";
|
var link = "https://torbox.app/d/123/456";
|
||||||
|
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
||||||
|
|
@ -323,7 +326,7 @@ public class TorBoxDebridClientTest
|
||||||
var name = "test-nzb";
|
var name = "test-nzb";
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -373,25 +376,20 @@ public class TorBoxDebridClientTest
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
Hash = "test-hash",
|
Hash = "test-hash",
|
||||||
|
RdId = "12345",
|
||||||
RdFiles = JsonConvert.SerializeObject(files)
|
RdFiles = JsonConvert.SerializeObject(files)
|
||||||
};
|
};
|
||||||
|
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
||||||
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient", ItExpr.IsAny<String>()).Returns(torBoxClientMock.Object);
|
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient", ItExpr.IsAny<String>()).Returns(torBoxClientMock.Object);
|
||||||
|
|
||||||
torrentsApiMock.Setup(m => m.GetHashInfoAsync("test-hash", true, It.IsAny<CancellationToken>()))
|
|
||||||
.ReturnsAsync(new TorrentInfoResult
|
|
||||||
{
|
|
||||||
Id = 12345
|
|
||||||
});
|
|
||||||
|
|
||||||
_fileFilterMock.Setup(m => m.IsDownloadable(torrent, It.IsAny<String>(), It.IsAny<Int64>())).Returns(true);
|
_fileFilterMock.Setup(m => m.IsDownloadable(torrent, It.IsAny<String>(), It.IsAny<Int64>())).Returns(true);
|
||||||
|
|
||||||
Settings.Get.Provider.PreferZippedDownloads = false;
|
_settings.Current.Provider.PreferZippedDownloads = false;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await clientMock.Object.GetDownloadInfos(torrent);
|
var result = await clientMock.Object.GetDownloadInfos(torrent);
|
||||||
|
|
@ -420,26 +418,21 @@ public class TorBoxDebridClientTest
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
Hash = "test-hash",
|
Hash = "test-hash",
|
||||||
|
RdId = "12345",
|
||||||
RdName = "TestTorrent",
|
RdName = "TestTorrent",
|
||||||
RdFiles = JsonConvert.SerializeObject(files),
|
RdFiles = JsonConvert.SerializeObject(files),
|
||||||
DownloadClient = DownloadClient.Aria2c
|
DownloadClient = DownloadClient.Aria2c
|
||||||
};
|
};
|
||||||
|
|
||||||
Settings.Get.Provider.PreferZippedDownloads = true;
|
_settings.Current.Provider.PreferZippedDownloads = true;
|
||||||
|
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
||||||
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient", ItExpr.IsAny<String>()).Returns(torBoxClientMock.Object);
|
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient", ItExpr.IsAny<String>()).Returns(torBoxClientMock.Object);
|
||||||
|
|
||||||
torrentsApiMock.Setup(m => m.GetHashInfoAsync("test-hash", true, It.IsAny<CancellationToken>()))
|
|
||||||
.ReturnsAsync(new TorrentInfoResult
|
|
||||||
{
|
|
||||||
Id = 12345
|
|
||||||
});
|
|
||||||
|
|
||||||
_fileFilterMock.Setup(m => m.IsDownloadable(torrent, It.IsAny<String>(), It.IsAny<Int64>())).Returns(true);
|
_fileFilterMock.Setup(m => m.IsDownloadable(torrent, It.IsAny<String>(), It.IsAny<Int64>())).Returns(true);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -464,7 +457,7 @@ public class TorBoxDebridClientTest
|
||||||
var link = "https://torbox.app/fakedl/12345/6789";
|
var link = "https://torbox.app/fakedl/12345/6789";
|
||||||
|
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
||||||
|
|
@ -496,7 +489,7 @@ public class TorBoxDebridClientTest
|
||||||
var link = "https://torbox.app/fakedl/12345/zip";
|
var link = "https://torbox.app/fakedl/12345/zip";
|
||||||
|
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.Object);
|
||||||
|
|
@ -538,13 +531,13 @@ public class TorBoxDebridClientTest
|
||||||
};
|
};
|
||||||
|
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
||||||
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient", ItExpr.IsAny<String>()).Returns(torBoxClientMock.Object);
|
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient", ItExpr.IsAny<String>()).Returns(torBoxClientMock.Object);
|
||||||
|
|
||||||
usenetApiMock.Setup(m => m.GetCurrentAsync(true, It.IsAny<CancellationToken>()))
|
usenetApiMock.Setup(m => m.GetCurrentAsync(true, 1000, It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync(new List<UsenetInfoResult>
|
.ReturnsAsync(new List<UsenetInfoResult>
|
||||||
{
|
{
|
||||||
new()
|
new()
|
||||||
|
|
@ -556,7 +549,7 @@ public class TorBoxDebridClientTest
|
||||||
|
|
||||||
_fileFilterMock.Setup(m => m.IsDownloadable(torrent, It.IsAny<String>(), It.IsAny<Int64>())).Returns(true);
|
_fileFilterMock.Setup(m => m.IsDownloadable(torrent, It.IsAny<String>(), It.IsAny<Int64>())).Returns(true);
|
||||||
|
|
||||||
Settings.Get.Provider.PreferZippedDownloads = false;
|
_settings.Current.Provider.PreferZippedDownloads = false;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await clientMock.Object.GetDownloadInfos(torrent);
|
var result = await clientMock.Object.GetDownloadInfos(torrent);
|
||||||
|
|
@ -579,7 +572,7 @@ public class TorBoxDebridClientTest
|
||||||
var link = "https://torbox.app/fakedl/98765/4321";
|
var link = "https://torbox.app/fakedl/98765/4321";
|
||||||
|
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
var torBoxClientMock = new Mock<ITorBoxNetClient>();
|
||||||
|
|
||||||
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
torBoxClientMock.Setup(m => m.Usenet).Returns(usenetApiMock.Object);
|
||||||
|
|
@ -607,7 +600,7 @@ public class TorBoxDebridClientTest
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var userApiMock = new Mock<IUserApi>();
|
var userApiMock = new Mock<IUserApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -647,7 +640,7 @@ public class TorBoxDebridClientTest
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var userApiMock = new Mock<IUserApi>();
|
var userApiMock = new Mock<IUserApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -685,7 +678,7 @@ public class TorBoxDebridClientTest
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var userApiMock = new Mock<IUserApi>();
|
var userApiMock = new Mock<IUserApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -724,7 +717,7 @@ public class TorBoxDebridClientTest
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var userApiMock = new Mock<IUserApi>();
|
var userApiMock = new Mock<IUserApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -767,7 +760,7 @@ public class TorBoxDebridClientTest
|
||||||
var torrentsApiMock = new Mock<ITorrentsApi>();
|
var torrentsApiMock = new Mock<ITorrentsApi>();
|
||||||
var userApiMock = new Mock<IUserApi>();
|
var userApiMock = new Mock<IUserApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -806,7 +799,7 @@ public class TorBoxDebridClientTest
|
||||||
var nzbLink = "https://example.com/test.nzb";
|
var nzbLink = "https://example.com/test.nzb";
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -837,7 +830,7 @@ public class TorBoxDebridClientTest
|
||||||
var name = "test.nzb";
|
var name = "test.nzb";
|
||||||
var usenetApiMock = new Mock<IUsenetApi>();
|
var usenetApiMock = new Mock<IUsenetApi>();
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object)
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings)
|
||||||
{
|
{
|
||||||
CallBase = true
|
CallBase = true
|
||||||
};
|
};
|
||||||
|
|
@ -872,7 +865,7 @@ public class TorBoxDebridClientTest
|
||||||
Filename = "test-file"
|
Filename = "test-file"
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
|
var result = await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
|
||||||
|
|
@ -898,7 +891,7 @@ public class TorBoxDebridClientTest
|
||||||
Filename = "test-torrent"
|
Filename = "test-torrent"
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
|
await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
|
||||||
|
|
@ -929,7 +922,7 @@ public class TorBoxDebridClientTest
|
||||||
Filename = "test-torrent"
|
Filename = "test-torrent"
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object);
|
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _coordinatorMock.Object, _settings);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
|
var result = await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
|
||||||
|
|
|
||||||
|
|
@ -13,76 +13,65 @@ public class TorrentRunnerTest
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Tick_ShouldNotRequeueCompletedErrorTorrent()
|
public async Task Tick_ShouldNotRequeueCompletedErrorTorrent()
|
||||||
{
|
{
|
||||||
var originalApiKey = Settings.Get.Provider.ApiKey;
|
var testSettings = new TestSettings();
|
||||||
var originalProvider = Settings.Get.Provider.Provider;
|
var runnerState = new TorrentRunnerState();
|
||||||
var originalMaxParallelDownloads = Settings.Get.Provider.MaxParallelDownloads;
|
|
||||||
var originalDownloadPath = Settings.Get.DownloadClient.DownloadPath;
|
|
||||||
|
|
||||||
TorrentRunner.ActiveDownloadClients.Clear();
|
testSettings.Current.Provider.ApiKey = "test-api-key";
|
||||||
TorrentRunner.ActiveUnpackClients.Clear();
|
testSettings.Current.Provider.Provider = Provider.RealDebrid;
|
||||||
|
testSettings.Current.Provider.MaxParallelDownloads = 1;
|
||||||
|
testSettings.Current.DownloadClient.DownloadPath = "/downloads";
|
||||||
|
|
||||||
try
|
var erroredTorrent = new Torrent
|
||||||
{
|
{
|
||||||
Settings.Get.Provider.ApiKey = "test-api-key";
|
TorrentId = Guid.NewGuid(),
|
||||||
Settings.Get.Provider.Provider = Provider.RealDebrid;
|
Hash = "hash-1",
|
||||||
Settings.Get.Provider.MaxParallelDownloads = 1;
|
RdName = "Torrent 1",
|
||||||
Settings.Get.DownloadClient.DownloadPath = "/downloads";
|
FileOrMagnet = "magnet:?xt=urn:btih:hash-1",
|
||||||
|
Type = DownloadType.Torrent,
|
||||||
|
RdStatus = TorrentStatus.Queued,
|
||||||
|
DeleteOnError = 10,
|
||||||
|
Error = "Could not add to provider: Infringing file",
|
||||||
|
Completed = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||||
|
Downloads = new List<Download>()
|
||||||
|
};
|
||||||
|
|
||||||
var erroredTorrent = new Torrent
|
var torrentDataMock = new Mock<ITorrentData>(MockBehavior.Strict);
|
||||||
{
|
|
||||||
TorrentId = Guid.NewGuid(),
|
|
||||||
Hash = "hash-1",
|
|
||||||
RdName = "Torrent 1",
|
|
||||||
FileOrMagnet = "magnet:?xt=urn:btih:hash-1",
|
|
||||||
Type = DownloadType.Torrent,
|
|
||||||
RdStatus = TorrentStatus.Queued,
|
|
||||||
DeleteOnError = 10,
|
|
||||||
Error = "Could not add to provider: Infringing file",
|
|
||||||
Completed = DateTimeOffset.UtcNow.AddMinutes(-5),
|
|
||||||
Downloads = new List<Download>()
|
|
||||||
};
|
|
||||||
|
|
||||||
var torrentDataMock = new Mock<ITorrentData>(MockBehavior.Strict);
|
torrentDataMock.Setup(m => m.Get())
|
||||||
torrentDataMock.Setup(m => m.Get()).ReturnsAsync(new List<Torrent>
|
.ReturnsAsync(new List<Torrent>
|
||||||
{
|
{
|
||||||
erroredTorrent
|
erroredTorrent
|
||||||
});
|
});
|
||||||
|
|
||||||
var torrents = new Torrents(Mock.Of<ILogger<Torrents>>(),
|
var torrents = new Torrents(Mock.Of<ILogger<Torrents>>(),
|
||||||
torrentDataMock.Object,
|
torrentDataMock.Object,
|
||||||
Mock.Of<IDownloads>(),
|
Mock.Of<IDownloads>(),
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
testSettings,
|
||||||
|
runnerState);
|
||||||
|
|
||||||
var torrentRunner = new TorrentRunner(Mock.Of<ILogger<TorrentRunner>>(),
|
var torrentRunner = new TorrentRunner(Mock.Of<ILogger<TorrentRunner>>(),
|
||||||
torrents,
|
torrents,
|
||||||
new(null!),
|
new(null!),
|
||||||
new(null!, torrents),
|
new(null!, torrents),
|
||||||
Mock.Of<IHttpClientFactory>(),
|
Mock.Of<IHttpClientFactory>(),
|
||||||
new RateLimitCoordinator());
|
new RateLimitCoordinator(),
|
||||||
|
testSettings,
|
||||||
|
runnerState);
|
||||||
|
|
||||||
await torrentRunner.Tick();
|
await torrentRunner.Tick();
|
||||||
|
|
||||||
torrentDataMock.Verify(m => m.UpdateComplete(It.IsAny<Guid>(),
|
torrentDataMock.Verify(m => m.UpdateComplete(It.IsAny<Guid>(),
|
||||||
It.IsAny<String?>(),
|
It.IsAny<String?>(),
|
||||||
It.IsAny<DateTimeOffset?>(),
|
It.IsAny<DateTimeOffset?>(),
|
||||||
It.IsAny<Boolean>()),
|
It.IsAny<Boolean>()),
|
||||||
Times.Never);
|
Times.Never);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Settings.Get.Provider.ApiKey = originalApiKey;
|
|
||||||
Settings.Get.Provider.Provider = originalProvider;
|
|
||||||
Settings.Get.Provider.MaxParallelDownloads = originalMaxParallelDownloads;
|
|
||||||
Settings.Get.DownloadClient.DownloadPath = originalDownloadPath;
|
|
||||||
TorrentRunner.ActiveDownloadClients.Clear();
|
|
||||||
TorrentRunner.ActiveUnpackClients.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class TorrentsTest
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
Settings.Get.DownloadClient.DownloadPath = settings.DownloadClient.DownloadPath = @"C:\Downloads";
|
settings.DownloadClient.DownloadPath = @"C:\Downloads";
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadPath = Path.Combine(settings.DownloadClient.DownloadPath, category);
|
var downloadPath = Path.Combine(settings.DownloadClient.DownloadPath, category);
|
||||||
|
|
@ -120,7 +120,9 @@ public class TorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
|
|
||||||
mocks.ProcessMock.Setup(p => p.WaitForExit(It.IsAny<Int32>())).Returns(true);
|
mocks.ProcessMock.Setup(p => p.WaitForExit(It.IsAny<Int32>())).Returns(true);
|
||||||
|
|
||||||
|
|
@ -165,9 +167,9 @@ public class TorrentsTest
|
||||||
mocks.TorrentDataMock.Setup(t => t.GetById(torrent.TorrentId)).Returns(Task.FromResult<Torrent?>(torrent));
|
mocks.TorrentDataMock.Setup(t => t.GetById(torrent.TorrentId)).Returns(Task.FromResult<Torrent?>(torrent));
|
||||||
mocks.DownloadsMock.Setup(d => d.GetForTorrent(torrent.TorrentId)).ReturnsAsync(downloads);
|
mocks.DownloadsMock.Setup(d => d.GetForTorrent(torrent.TorrentId)).ReturnsAsync(downloads);
|
||||||
|
|
||||||
var downloadPath = $"{settings.DownloadClient.DownloadPath}/{torrent.Category}";
|
var downloadPath = Path.Combine(settings.DownloadClient.DownloadPath, torrent.Category ?? "");
|
||||||
var torrentPath = $"{downloadPath}/{torrent.RdName}";
|
var torrentPath = Path.Combine(downloadPath, torrent.RdName ?? "");
|
||||||
var filePath = $"{torrentPath}/{downloads[0].FileName}";
|
var filePath = Path.Combine(torrentPath, downloads[0].FileName ?? "");
|
||||||
|
|
||||||
var fileSystemMock = new MockFileSystem(new Dictionary<String, MockFileData>
|
var fileSystemMock = new MockFileSystem(new Dictionary<String, MockFileData>
|
||||||
{
|
{
|
||||||
|
|
@ -186,7 +188,9 @@ public class TorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
await torrents.RunTorrentComplete(torrent.TorrentId, settings);
|
await torrents.RunTorrentComplete(torrent.TorrentId, settings);
|
||||||
|
|
@ -213,9 +217,9 @@ public class TorrentsTest
|
||||||
mocks.TorrentDataMock.Setup(t => t.GetById(torrent.TorrentId)).Returns(Task.FromResult<Torrent?>(torrent));
|
mocks.TorrentDataMock.Setup(t => t.GetById(torrent.TorrentId)).Returns(Task.FromResult<Torrent?>(torrent));
|
||||||
mocks.DownloadsMock.Setup(d => d.GetForTorrent(torrent.TorrentId)).ReturnsAsync(downloads);
|
mocks.DownloadsMock.Setup(d => d.GetForTorrent(torrent.TorrentId)).ReturnsAsync(downloads);
|
||||||
|
|
||||||
var downloadPath = $"{settings.DownloadClient.DownloadPath}/{torrent.Category}";
|
var downloadPath = Path.Combine(settings.DownloadClient.DownloadPath, torrent.Category ?? "");
|
||||||
var torrentPath = $"{downloadPath}/{torrent.RdName}";
|
var torrentPath = Path.Combine(downloadPath, torrent.RdName ?? "");
|
||||||
var filePath = $"{torrentPath}/{downloads[0].FileName}";
|
var filePath = Path.Combine(torrentPath, downloads[0].FileName ?? "");
|
||||||
|
|
||||||
var fileSystemMock = new MockFileSystem(new Dictionary<String, MockFileData>
|
var fileSystemMock = new MockFileSystem(new Dictionary<String, MockFileData>
|
||||||
{
|
{
|
||||||
|
|
@ -234,7 +238,9 @@ public class TorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
|
|
||||||
mocks.ProcessMock.Setup(p => p.WaitForExit(It.IsAny<Int32>()))
|
mocks.ProcessMock.Setup(p => p.WaitForExit(It.IsAny<Int32>()))
|
||||||
.Callback(() =>
|
.Callback(() =>
|
||||||
|
|
@ -280,9 +286,9 @@ public class TorrentsTest
|
||||||
mocks.TorrentDataMock.Setup(t => t.GetById(torrent.TorrentId)).Returns(Task.FromResult<Torrent?>(torrent));
|
mocks.TorrentDataMock.Setup(t => t.GetById(torrent.TorrentId)).Returns(Task.FromResult<Torrent?>(torrent));
|
||||||
mocks.DownloadsMock.Setup(d => d.GetForTorrent(torrent.TorrentId)).ReturnsAsync(downloads);
|
mocks.DownloadsMock.Setup(d => d.GetForTorrent(torrent.TorrentId)).ReturnsAsync(downloads);
|
||||||
|
|
||||||
var downloadPath = $"{settings.DownloadClient.DownloadPath}/{torrent.Category}";
|
var downloadPath = Path.Combine(settings.DownloadClient.DownloadPath, torrent.Category ?? "");
|
||||||
var torrentPath = $"{downloadPath}/{torrent.RdName}";
|
var torrentPath = Path.Combine(downloadPath, torrent.RdName ?? "");
|
||||||
var filePath = $"{torrentPath}/{downloads[0].FileName}";
|
var filePath = Path.Combine(torrentPath, downloads[0].FileName ?? "");
|
||||||
|
|
||||||
var fileSystemMock = new MockFileSystem(new Dictionary<String, MockFileData>
|
var fileSystemMock = new MockFileSystem(new Dictionary<String, MockFileData>
|
||||||
{
|
{
|
||||||
|
|
@ -301,7 +307,9 @@ public class TorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
|
|
||||||
mocks.ProcessMock.Setup(p => p.WaitForExit(It.IsAny<Int32>()))
|
mocks.ProcessMock.Setup(p => p.WaitForExit(It.IsAny<Int32>()))
|
||||||
.Callback(() =>
|
.Callback(() =>
|
||||||
|
|
@ -364,7 +372,9 @@ public class TorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await torrents.AddNzbFileToDebridQueue(bytes, "filename.nzb", torrent);
|
await torrents.AddNzbFileToDebridQueue(bytes, "filename.nzb", torrent);
|
||||||
|
|
@ -412,7 +422,9 @@ public class TorrentsTest
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
null!);
|
null!,
|
||||||
|
new TestSettings(),
|
||||||
|
new TorrentRunnerState());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await torrents.AddNzbLinkToDebridQueue(link, torrent);
|
await torrents.AddNzbLinkToDebridQueue(link, torrent);
|
||||||
|
|
|
||||||
59
server/RdtClient.Service.Test/TestSettings.cs
Normal file
59
server/RdtClient.Service.Test/TestSettings.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using RdtClient.Data.Models.Internal;
|
||||||
|
using RdtClient.Service.Services;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Test;
|
||||||
|
|
||||||
|
internal sealed class TestSettings(DbSettings? settings = null) : ISettings
|
||||||
|
{
|
||||||
|
public DbSettings Current { get; } = settings ?? new();
|
||||||
|
|
||||||
|
public String DefaultSavePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var downloadPath = Current.DownloadClient.MappedPath.TrimEnd('\\').TrimEnd('/');
|
||||||
|
|
||||||
|
return downloadPath + Path.DirectorySeparatorChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<SettingProperty> GetAll()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Update(IList<SettingProperty> settings)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Update(String settingId, Object? value)
|
||||||
|
{
|
||||||
|
SetValue(Current, settingId.Split(':'), 0, value);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetValue(Object target, IReadOnlyList<String> path, Int32 index, Object? value)
|
||||||
|
{
|
||||||
|
var property = target.GetType().GetProperty(path[index]) ?? throw new($"Unknown setting {String.Join(":", path)}");
|
||||||
|
|
||||||
|
if (index < path.Count - 1)
|
||||||
|
{
|
||||||
|
SetValue(property.GetValue(target)!, path, index + 1, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
property.SetValue(target, null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
||||||
|
var convertedValue = propertyType.IsEnum ? Enum.Parse(propertyType, value.ToString()!) : Convert.ChangeType(value, propertyType);
|
||||||
|
property.SetValue(target, convertedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ using DownloadClient = RdtClient.Data.Enums.DownloadClient;
|
||||||
|
|
||||||
namespace RdtClient.Service.BackgroundServices;
|
namespace RdtClient.Service.BackgroundServices;
|
||||||
|
|
||||||
public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider serviceProvider) : BackgroundService
|
public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider serviceProvider, ISettings settings, ITorrentRunnerState runnerState) : BackgroundService
|
||||||
{
|
{
|
||||||
private static DiskSpaceStatus? _lastStatus;
|
private static DiskSpaceStatus? _lastStatus;
|
||||||
private Boolean _isPausedForLowDiskSpace;
|
private Boolean _isPausedForLowDiskSpace;
|
||||||
|
|
@ -34,7 +34,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var minimumFreeSpaceGB = Settings.Get.DownloadClient.MinimumFreeSpaceGB;
|
var minimumFreeSpaceGB = settings.Current.DownloadClient.MinimumFreeSpaceGB;
|
||||||
|
|
||||||
if (minimumFreeSpaceGB <= 0)
|
if (minimumFreeSpaceGB <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -43,14 +43,14 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var intervalMinutes = Settings.Get.DownloadClient.DiskSpaceCheckIntervalMinutes;
|
var intervalMinutes = settings.Current.DownloadClient.DiskSpaceCheckIntervalMinutes;
|
||||||
|
|
||||||
if (intervalMinutes < 1)
|
if (intervalMinutes < 1)
|
||||||
{
|
{
|
||||||
intervalMinutes = 1;
|
intervalMinutes = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var downloadPath = settings.Current.DownloadClient.DownloadPath;
|
||||||
logger.LogDebug($"Checking disk space for path: {downloadPath}");
|
logger.LogDebug($"Checking disk space for path: {downloadPath}");
|
||||||
|
|
||||||
if (!Directory.Exists(downloadPath))
|
if (!Directory.Exists(downloadPath))
|
||||||
|
|
@ -78,7 +78,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
|
|
||||||
var pausedCount = 0;
|
var pausedCount = 0;
|
||||||
|
|
||||||
foreach (var download in TorrentRunner.ActiveDownloadClients)
|
foreach (var download in runnerState.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (download.Value.Type == DownloadClient.Bezzad)
|
if (download.Value.Type == DownloadClient.Bezzad)
|
||||||
{
|
{
|
||||||
|
|
@ -90,7 +90,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
|
|
||||||
logger.LogInformation($"Paused {pausedCount} active Bezzad downloads");
|
logger.LogInformation($"Paused {pausedCount} active Bezzad downloads");
|
||||||
|
|
||||||
TorrentRunner.IsPausedForLowDiskSpace = true;
|
runnerState.IsPausedForLowDiskSpace = true;
|
||||||
_isPausedForLowDiskSpace = true;
|
_isPausedForLowDiskSpace = true;
|
||||||
|
|
||||||
var status = new DiskSpaceStatus
|
var status = new DiskSpaceStatus
|
||||||
|
|
@ -125,7 +125,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
|
|
||||||
var resumedCount = 0;
|
var resumedCount = 0;
|
||||||
|
|
||||||
foreach (var download in TorrentRunner.ActiveDownloadClients)
|
foreach (var download in runnerState.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (download.Value.Type == DownloadClient.Bezzad)
|
if (download.Value.Type == DownloadClient.Bezzad)
|
||||||
{
|
{
|
||||||
|
|
@ -137,7 +137,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
|
|
||||||
logger.LogInformation($"Resumed {resumedCount} Bezzad downloads");
|
logger.LogInformation($"Resumed {resumedCount} Bezzad downloads");
|
||||||
|
|
||||||
TorrentRunner.IsPausedForLowDiskSpace = false;
|
runnerState.IsPausedForLowDiskSpace = false;
|
||||||
_isPausedForLowDiskSpace = false;
|
_isPausedForLowDiskSpace = false;
|
||||||
|
|
||||||
var status = new DiskSpaceStatus
|
var status = new DiskSpaceStatus
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using RdtClient.Service.Services;
|
||||||
|
|
||||||
namespace RdtClient.Service.BackgroundServices;
|
namespace RdtClient.Service.BackgroundServices;
|
||||||
|
|
||||||
public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider serviceProvider) : BackgroundService
|
public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider serviceProvider, ISettings settings) : BackgroundService
|
||||||
{
|
{
|
||||||
private static DateTime _nextUpdate = DateTime.UtcNow;
|
private static DateTime _nextUpdate = DateTime.UtcNow;
|
||||||
|
|
||||||
|
|
@ -30,11 +30,11 @@ public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider s
|
||||||
{
|
{
|
||||||
var torrents = await torrentService.Get();
|
var torrents = await torrentService.Get();
|
||||||
|
|
||||||
if (_nextUpdate < DateTime.UtcNow && (Settings.Get.Provider.AutoImport || torrents.Any(t => t.RdStatus != TorrentStatus.Finished) || RdtHub.HasConnections))
|
if (_nextUpdate < DateTime.UtcNow && (settings.Current.Provider.AutoImport || torrents.Any(t => t.RdStatus != TorrentStatus.Finished) || RdtHub.HasConnections))
|
||||||
{
|
{
|
||||||
logger.LogDebug($"Updating torrent info from debrid provider");
|
logger.LogDebug($"Updating torrent info from debrid provider");
|
||||||
|
|
||||||
var updateTime = Settings.Get.Provider.CheckInterval * 3;
|
var updateTime = settings.Current.Provider.CheckInterval * 3;
|
||||||
|
|
||||||
if (updateTime < 30)
|
if (updateTime < 30)
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +43,7 @@ public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider s
|
||||||
|
|
||||||
if (RdtHub.HasConnections)
|
if (RdtHub.HasConnections)
|
||||||
{
|
{
|
||||||
updateTime = Settings.Get.Provider.CheckInterval;
|
updateTime = settings.Current.Provider.CheckInterval;
|
||||||
|
|
||||||
if (updateTime < 5)
|
if (updateTime < 5)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||||
|
|
||||||
namespace RdtClient.Service.BackgroundServices;
|
namespace RdtClient.Service.BackgroundServices;
|
||||||
|
|
||||||
public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProvider serviceProvider) : BackgroundService
|
public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProvider serviceProvider, ISettings settings) : BackgroundService
|
||||||
{
|
{
|
||||||
private DateTime _prevCheck = DateTime.MinValue;
|
private DateTime _prevCheck = DateTime.MinValue;
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var nextCheck = _prevCheck.AddSeconds(Math.Max(Settings.Get.Watch.Interval, 10));
|
var nextCheck = _prevCheck.AddSeconds(Math.Max(settings.Current.Watch.Interval, 10));
|
||||||
|
|
||||||
if (DateTime.Now < nextCheck)
|
if (DateTime.Now < nextCheck)
|
||||||
{
|
{
|
||||||
|
|
@ -38,25 +38,25 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
|
|
||||||
_prevCheck = DateTime.Now;
|
_prevCheck = DateTime.Now;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(Settings.Get.Watch.Path))
|
if (String.IsNullOrWhiteSpace(settings.Current.Watch.Path))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var processedStorePath = Path.Combine(Settings.Get.Watch.Path, "processed");
|
var processedStorePath = Path.Combine(settings.Current.Watch.Path, "processed");
|
||||||
var errorStorePath = Path.Combine(Settings.Get.Watch.Path, "error");
|
var errorStorePath = Path.Combine(settings.Current.Watch.Path, "error");
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(Settings.Get.Watch.ProcessedPath))
|
if (!String.IsNullOrWhiteSpace(settings.Current.Watch.ProcessedPath))
|
||||||
{
|
{
|
||||||
processedStorePath = Settings.Get.Watch.ProcessedPath;
|
processedStorePath = settings.Current.Watch.ProcessedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(Settings.Get.Watch.ErrorPath))
|
if (!String.IsNullOrWhiteSpace(settings.Current.Watch.ErrorPath))
|
||||||
{
|
{
|
||||||
errorStorePath = Settings.Get.Watch.ErrorPath;
|
errorStorePath = settings.Current.Watch.ErrorPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
var torrentFiles = Directory.GetFiles(Settings.Get.Watch.Path, "*.*", SearchOption.TopDirectoryOnly);
|
var torrentFiles = Directory.GetFiles(settings.Current.Watch.Path, "*.*", SearchOption.TopDirectoryOnly);
|
||||||
|
|
||||||
foreach (var torrentFile in torrentFiles)
|
foreach (var torrentFile in torrentFiles)
|
||||||
{
|
{
|
||||||
|
|
@ -78,22 +78,22 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
|
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client,
|
||||||
Category = Settings.Get.Watch.Default.Category,
|
Category = settings.Current.Watch.Default.Category,
|
||||||
HostDownloadAction = Settings.Get.Watch.Default.HostDownloadAction,
|
HostDownloadAction = settings.Current.Watch.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Watch.Default.FinishedActionDelay,
|
FinishedActionDelay = settings.Current.Watch.Default.FinishedActionDelay,
|
||||||
DownloadAction = Settings.Get.Watch.Default.OnlyDownloadAvailableFiles
|
DownloadAction = settings.Current.Watch.Default.OnlyDownloadAvailableFiles
|
||||||
? TorrentDownloadAction.DownloadAvailableFiles
|
? TorrentDownloadAction.DownloadAvailableFiles
|
||||||
: TorrentDownloadAction.DownloadAll,
|
: TorrentDownloadAction.DownloadAll,
|
||||||
FinishedAction = Settings.Get.Watch.Default.FinishedAction,
|
FinishedAction = settings.Current.Watch.Default.FinishedAction,
|
||||||
DownloadMinSize = Settings.Get.Watch.Default.MinFileSize,
|
DownloadMinSize = settings.Current.Watch.Default.MinFileSize,
|
||||||
IncludeRegex = Settings.Get.Watch.Default.IncludeRegex,
|
IncludeRegex = settings.Current.Watch.Default.IncludeRegex,
|
||||||
ExcludeRegex = Settings.Get.Watch.Default.ExcludeRegex,
|
ExcludeRegex = settings.Current.Watch.Default.ExcludeRegex,
|
||||||
TorrentRetryAttempts = Settings.Get.Watch.Default.TorrentRetryAttempts,
|
TorrentRetryAttempts = settings.Current.Watch.Default.TorrentRetryAttempts,
|
||||||
DownloadRetryAttempts = Settings.Get.Watch.Default.DownloadRetryAttempts,
|
DownloadRetryAttempts = settings.Current.Watch.Default.DownloadRetryAttempts,
|
||||||
DeleteOnError = Settings.Get.Watch.Default.DeleteOnError,
|
DeleteOnError = settings.Current.Watch.Default.DeleteOnError,
|
||||||
Lifetime = Settings.Get.Watch.Default.TorrentLifetime,
|
Lifetime = settings.Current.Watch.Default.TorrentLifetime,
|
||||||
Priority = Settings.Get.Watch.Default.Priority > 0 ? Settings.Get.Watch.Default.Priority : null
|
Priority = settings.Current.Watch.Default.Priority > 0 ? settings.Current.Watch.Default.Priority : null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fileInfo.Extension == ".torrent")
|
if (fileInfo.Extension == ".torrent")
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ public static class DiConfig
|
||||||
services.AddScoped<AllDebridDebridClient>();
|
services.AddScoped<AllDebridDebridClient>();
|
||||||
|
|
||||||
services.AddSingleton<IRateLimitCoordinator, RateLimitCoordinator>();
|
services.AddSingleton<IRateLimitCoordinator, RateLimitCoordinator>();
|
||||||
|
services.AddSingleton(TorrentRunner.SharedState);
|
||||||
services.AddSingleton<IProcessFactory, ProcessFactory>();
|
services.AddSingleton<IProcessFactory, ProcessFactory>();
|
||||||
services.AddSingleton<IFileSystem, FileSystem>();
|
services.AddSingleton<IFileSystem, FileSystem>();
|
||||||
|
|
||||||
|
|
@ -41,7 +42,8 @@ public static class DiConfig
|
||||||
services.AddScoped<Sabnzbd>();
|
services.AddScoped<Sabnzbd>();
|
||||||
services.AddScoped<RemoteService>();
|
services.AddScoped<RemoteService>();
|
||||||
services.AddScoped<RealDebridDebridClient>();
|
services.AddScoped<RealDebridDebridClient>();
|
||||||
services.AddScoped<Settings>();
|
services.AddSingleton<Settings>();
|
||||||
|
services.AddSingleton<ISettings>(serviceProvider => serviceProvider.GetRequiredService<Settings>());
|
||||||
services.AddScoped<TorBoxDebridClient>();
|
services.AddScoped<TorBoxDebridClient>();
|
||||||
services.AddScoped<Torrents>();
|
services.AddScoped<Torrents>();
|
||||||
services.AddScoped<TorrentRunner>();
|
services.AddScoped<TorrentRunner>();
|
||||||
|
|
@ -51,7 +53,7 @@ public static class DiConfig
|
||||||
services.AddSingleton<ITrackerListGrabber, TrackerListGrabber>();
|
services.AddSingleton<ITrackerListGrabber, TrackerListGrabber>();
|
||||||
services.AddSingleton<IEnricher, Enricher>();
|
services.AddSingleton<IEnricher, Enricher>();
|
||||||
|
|
||||||
services.AddSingleton<IAuthorizationHandler, AuthSettingHandler>();
|
services.AddScoped<IAuthorizationHandler, AuthSettingHandler>();
|
||||||
services.AddScoped<IAuthorizationHandler, SabnzbdHandler>();
|
services.AddScoped<IAuthorizationHandler, SabnzbdHandler>();
|
||||||
|
|
||||||
services.AddHostedService<DiskSpaceMonitor>();
|
services.AddHostedService<DiskSpaceMonitor>();
|
||||||
|
|
|
||||||
|
|
@ -214,16 +214,18 @@ public static class TorrentDtoMapper
|
||||||
return "Finished";
|
return "Finished";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prefix = torrent.Type == DownloadType.Nzb ? "NZB" : "Torrent";
|
||||||
|
|
||||||
return torrent.RdStatus switch
|
return torrent.RdStatus switch
|
||||||
{
|
{
|
||||||
TorrentStatus.Queued => "Not Yet Added to Provider",
|
TorrentStatus.Queued => "Not Yet Added to Provider",
|
||||||
TorrentStatus.Downloading when torrent.RdSeeders < 1 && torrent.Type != DownloadType.Nzb => "Torrent stalled",
|
TorrentStatus.Downloading when torrent.RdSeeders < 1 && torrent.Type != DownloadType.Nzb => "Torrent stalled",
|
||||||
TorrentStatus.Downloading => $"Torrent downloading ({torrent.RdProgress}% - {FileSizeHelper.FormatSize(torrent.RdSpeed)}/s)",
|
TorrentStatus.Downloading => $"{prefix} downloading ({torrent.RdProgress}% - {FileSizeHelper.FormatSize(torrent.RdSpeed)}/s)",
|
||||||
TorrentStatus.Processing => "Torrent processing",
|
TorrentStatus.Processing => $"{prefix} processing",
|
||||||
TorrentStatus.WaitingForFileSelection => "Torrent waiting for file selection",
|
TorrentStatus.WaitingForFileSelection => $"{prefix} waiting for file selection",
|
||||||
TorrentStatus.Error => $"Torrent error: {torrent.RdStatusRaw}",
|
TorrentStatus.Error => $"{prefix} error: {torrent.RdStatusRaw}",
|
||||||
TorrentStatus.Finished => "Torrent finished, waiting for download links",
|
TorrentStatus.Finished => $"{prefix} finished, waiting for download links",
|
||||||
TorrentStatus.Uploading => "Torrent uploading",
|
TorrentStatus.Uploading => $"{prefix} uploading",
|
||||||
_ => "Unknown status"
|
_ => "Unknown status"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ public class AuthSettingRequirement : IAuthorizationRequirement
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthSettingHandler : AuthorizationHandler<AuthSettingRequirement>
|
public class AuthSettingHandler(ISettings settings) : AuthorizationHandler<AuthSettingRequirement>
|
||||||
{
|
{
|
||||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthSettingRequirement requirement)
|
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthSettingRequirement requirement)
|
||||||
{
|
{
|
||||||
|
|
@ -17,7 +17,7 @@ public class AuthSettingHandler : AuthorizationHandler<AuthSettingRequirement>
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
|
if (settings.Current.General.AuthenticationType == AuthenticationType.None)
|
||||||
{
|
{
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ public class SabnzbdRequirement : IAuthorizationRequirement
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor httpContextAccessor) : AuthorizationHandler<SabnzbdRequirement>
|
public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor httpContextAccessor, ISettings settings) : AuthorizationHandler<SabnzbdRequirement>
|
||||||
{
|
{
|
||||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SabnzbdRequirement requirement)
|
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SabnzbdRequirement requirement)
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +22,7 @@ public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
|
if (settings.Current.General.AuthenticationType == AuthenticationType.None)
|
||||||
{
|
{
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
|
|
||||||
|
|
@ -65,6 +65,36 @@ public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var apiKey = GetParam("apikey");
|
||||||
|
|
||||||
|
if (!String.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
var separatorIndex = apiKey.IndexOf(':');
|
||||||
|
|
||||||
|
if (separatorIndex <= 0 || separatorIndex == apiKey.Length - 1)
|
||||||
|
{
|
||||||
|
context.Fail();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var username = apiKey[..separatorIndex];
|
||||||
|
var password = apiKey[(separatorIndex + 1)..];
|
||||||
|
|
||||||
|
var loginResult = await authentication.Login(username, password);
|
||||||
|
|
||||||
|
if (loginResult.Succeeded)
|
||||||
|
{
|
||||||
|
context.Succeed(requirement);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Fail();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Authentication required but missing credentials
|
// Authentication required but missing credentials
|
||||||
context.Fail();
|
context.Fail();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<PackageReference Include="Synology.Api.Client" Version="[0.3.93]" />
|
<PackageReference Include="Synology.Api.Client" Version="[0.3.93]" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.1.1" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.1.1" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.1.1" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.1.1" />
|
||||||
<PackageReference Include="TorBox.NET" Version="1.7.0" />
|
<PackageReference Include="TorBox.NET" Version="2.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ public interface IAllDebridNetClientFactory
|
||||||
public IAllDebridNETClient GetClient();
|
public IAllDebridNETClient GetClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AllDebridNetClientFactory(ILogger<AllDebridNetClientFactory> logger, IHttpClientFactory httpClientFactory) : IAllDebridNetClientFactory
|
public class AllDebridNetClientFactory(ILogger<AllDebridNetClientFactory> logger, IHttpClientFactory httpClientFactory, ISettings settings) : IAllDebridNetClientFactory
|
||||||
{
|
{
|
||||||
public IAllDebridNETClient GetClient()
|
public IAllDebridNETClient GetClient()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
var apiKey = settings.Current.Provider.ApiKey;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Torrent = DebridLinkFrNET.Models.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.DebridClients;
|
namespace RdtClient.Service.Services.DebridClients;
|
||||||
|
|
||||||
public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : IDebridClient
|
public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter, ISettings settings) : IDebridClient
|
||||||
{
|
{
|
||||||
public async Task<IList<DebridClientTorrent>> GetDownloads()
|
public async Task<IList<DebridClientTorrent>> GetDownloads()
|
||||||
{
|
{
|
||||||
|
|
@ -228,7 +228,7 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
var apiKey = settings.Current.Provider.ApiKey;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
|
|
@ -236,7 +236,7 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
httpClient.Timeout = TimeSpan.FromSeconds(settings.Current.Provider.Timeout);
|
||||||
|
|
||||||
var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
|
var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.DebridClients;
|
namespace RdtClient.Service.Services.DebridClients;
|
||||||
|
|
||||||
public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : IDebridClient
|
public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter, ISettings settings)
|
||||||
|
: IDebridClient
|
||||||
{
|
{
|
||||||
private const String TransferCreateUrl = "https://www.premiumize.me/api/transfer/create";
|
private const String TransferCreateUrl = "https://www.premiumize.me/api/transfer/create";
|
||||||
|
|
||||||
|
|
@ -216,9 +217,9 @@ public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHtt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String GetApiKey()
|
private String GetApiKey()
|
||||||
{
|
{
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
var apiKey = settings.Current.Provider.ApiKey;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
|
|
@ -331,11 +332,11 @@ public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHtt
|
||||||
|
|
||||||
private static String FormatPremiumizeError(RawTransferCreateResponse result)
|
private static String FormatPremiumizeError(RawTransferCreateResponse result)
|
||||||
{
|
{
|
||||||
return String.Join(": ", new[]
|
return String.Join(": ",
|
||||||
{
|
new[]
|
||||||
result.Code,
|
{
|
||||||
result.Message
|
result.Code, result.Message
|
||||||
}.Where(m => !String.IsNullOrWhiteSpace(m)));
|
}.Where(m => !String.IsNullOrWhiteSpace(m)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Boolean IsRateLimitMessage(String message)
|
private static Boolean IsRateLimitMessage(String message)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ using Torrent = RDNET.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.DebridClients;
|
namespace RdtClient.Service.Services.DebridClients;
|
||||||
|
|
||||||
public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : IDebridClient
|
public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter, ISettings settings)
|
||||||
|
: IDebridClient
|
||||||
{
|
{
|
||||||
private TimeSpan? _offset;
|
private TimeSpan? _offset;
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var timeoutCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(Settings.Get.Provider.Timeout));
|
var timeoutCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(settings.Current.Provider.Timeout));
|
||||||
|
|
||||||
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, timeoutCancellationToken.Token);
|
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, timeoutCancellationToken.Token);
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var timeoutCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(Settings.Get.Provider.Timeout));
|
var timeoutCancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(settings.Current.Provider.Timeout));
|
||||||
|
|
||||||
var result = await GetClient().Torrents.AddFileAsync(bytes, timeoutCancellationToken.Token);
|
var result = await GetClient().Torrents.AddFileAsync(bytes, timeoutCancellationToken.Token);
|
||||||
|
|
||||||
|
|
@ -314,7 +315,7 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
var apiKey = settings.Current.Provider.ApiKey;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
|
|
@ -322,9 +323,9 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
|
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
httpClient.Timeout = TimeSpan.FromSeconds(settings.Current.Provider.Timeout);
|
||||||
|
|
||||||
var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname);
|
var rdtNetClient = new RdNetClient(null, httpClient, 5, settings.Current.Provider.ApiHostname);
|
||||||
rdtNetClient.UseApiAuthentication(apiKey);
|
rdtNetClient.UseApiAuthentication(apiKey);
|
||||||
|
|
||||||
// Get the server time to fix up the timezones on results
|
// Get the server time to fix up the timezones on results
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,12 @@ using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.DebridClients;
|
namespace RdtClient.Service.Services.DebridClients;
|
||||||
|
|
||||||
public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter, IRateLimitCoordinator coordinator)
|
public class TorBoxDebridClient(
|
||||||
|
ILogger<TorBoxDebridClient> logger,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IDownloadableFileFilter fileFilter,
|
||||||
|
IRateLimitCoordinator coordinator,
|
||||||
|
ISettings settings)
|
||||||
: IDebridClient
|
: IDebridClient
|
||||||
{
|
{
|
||||||
private const String TorBoxApiHost = "api.torbox.app";
|
private const String TorBoxApiHost = "api.torbox.app";
|
||||||
|
|
@ -69,9 +74,14 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
return await HandleAddTorrentErrors(async asQueued =>
|
return await HandleAddTorrentErrors(async asQueued =>
|
||||||
{
|
{
|
||||||
var user = await GetClient().User.GetAsync(true);
|
var user = await GetClient().User.GetAsync(true);
|
||||||
var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW).Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued);
|
|
||||||
|
|
||||||
return result.Data!.Hash!;
|
var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW)
|
||||||
|
.Torrents.AddMagnetAsync(magnetLink,
|
||||||
|
user.Data?.Settings?.SeedTorrents ?? 3,
|
||||||
|
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
||||||
|
as_queued: asQueued);
|
||||||
|
|
||||||
|
return result.Data?.TorrentId?.ToString() ?? throw new InvalidOperationException("TorBox API did not return torrent ID.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,9 +90,14 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
return await HandleAddTorrentErrors(async asQueued =>
|
return await HandleAddTorrentErrors(async asQueued =>
|
||||||
{
|
{
|
||||||
var user = await GetClient().User.GetAsync(true);
|
var user = await GetClient().User.GetAsync(true);
|
||||||
var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW).Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued);
|
|
||||||
|
|
||||||
return result.Data!.Hash!;
|
var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW)
|
||||||
|
.Torrents.AddFileAsync(bytes,
|
||||||
|
user.Data?.Settings?.SeedTorrents ?? 3,
|
||||||
|
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
||||||
|
as_queued: asQueued);
|
||||||
|
|
||||||
|
return result.Data?.TorrentId?.ToString() ?? throw new InvalidOperationException("TorBox API did not return torrent ID");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +171,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await GetClient().Torrents.ControlAsync(torrent.RdId, "delete");
|
await GetClient().Torrents.ControlByIdAsync(Int32.Parse(torrent.RdId), "delete");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -316,8 +331,12 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var torrentId = await HandleErrors(() => GetClient().Torrents.GetHashInfoAsync(torrent.Hash, true));
|
if (torrent.RdId == null)
|
||||||
id = torrentId?.Id;
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = Int32.Parse(torrent.RdId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == null)
|
if (id == null)
|
||||||
|
|
@ -327,7 +346,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
|
|
||||||
var downloadableFiles = torrent.Files.Where(file => fileFilter.IsDownloadable(torrent, file.Path, file.Bytes)).ToList();
|
var downloadableFiles = torrent.Files.Where(file => fileFilter.IsDownloadable(torrent, file.Path, file.Bytes)).ToList();
|
||||||
|
|
||||||
if (downloadableFiles.Count == torrent.Files.Count && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink && Settings.Get.Provider.PreferZippedDownloads)
|
if (downloadableFiles.Count == torrent.Files.Count && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink && settings.Current.Provider.PreferZippedDownloads)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Downloading files from TorBox as a zip.");
|
logger.LogDebug("Downloading files from TorBox as a zip.");
|
||||||
|
|
||||||
|
|
@ -364,7 +383,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
var apiKey = settings.Current.Provider.ApiKey;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
|
|
@ -372,7 +391,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient(clientId);
|
var httpClient = httpClientFactory.CreateClient(clientId);
|
||||||
var torBoxNetClient = new TorBoxNetClient(null, httpClient);
|
var torBoxNetClient = new TorBoxNetClient(null, httpClient, retryCount: 5);
|
||||||
torBoxNetClient.UseApiAuthentication(apiKey);
|
torBoxNetClient.UseApiAuthentication(apiKey);
|
||||||
|
|
||||||
// Get the server time to fix up the timezones on results
|
// Get the server time to fix up the timezones on results
|
||||||
|
|
@ -441,7 +460,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
{
|
{
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Id = torrent.Hash,
|
Id = torrent.Id.ToString(),
|
||||||
Filename = torrent.Name,
|
Filename = torrent.Name,
|
||||||
OriginalFilename = torrent.Name,
|
OriginalFilename = torrent.Name,
|
||||||
Hash = torrent.Hash,
|
Hash = torrent.Hash,
|
||||||
|
|
@ -513,7 +532,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
{
|
{
|
||||||
throw rateLimitException;
|
throw rateLimitException;
|
||||||
}
|
}
|
||||||
catch (TorBoxException ex) when ("active_limit".Equals(ex.Error, StringComparison.OrdinalIgnoreCase))
|
catch (TorBoxException ex) when (IsRateLimit(ex))
|
||||||
{
|
{
|
||||||
coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2));
|
coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2));
|
||||||
|
|
||||||
|
|
@ -541,7 +560,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
{
|
{
|
||||||
throw rateLimitException;
|
throw rateLimitException;
|
||||||
}
|
}
|
||||||
catch (TorBoxException ex) when ("active_limit".Equals(ex.Error, StringComparison.OrdinalIgnoreCase))
|
catch (TorBoxException ex) when (IsRateLimit(ex))
|
||||||
{
|
{
|
||||||
coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2));
|
coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2));
|
||||||
|
|
||||||
|
|
@ -560,6 +579,12 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
return await HandleErrors(() => action(false));
|
return await HandleErrors(() => action(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Boolean IsRateLimit(TorBoxException exception)
|
||||||
|
{
|
||||||
|
return exception.Error.Equals("RATE_LIMIT", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| exception.Error.Equals("ACTIVE_LIMIT", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<String> HandleAddUsenetErrors(Func<Boolean, Task<String>> action)
|
private async Task<String> HandleAddUsenetErrors(Func<Boolean, Task<String>> action)
|
||||||
{
|
{
|
||||||
return await HandleErrors(() => action(false));
|
return await HandleErrors(() => action(false));
|
||||||
|
|
@ -590,7 +615,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var result = await GetClient().Torrents.GetHashInfoAsync(id, true);
|
var result = await GetClient().Torrents.GetIdInfoAsync(Int32.Parse(id), true);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,8 @@ public class Downloads(DownloadData downloadData) : IDownloads
|
||||||
await downloadData.DeleteForTorrent(torrentId);
|
await downloadData.DeleteForTorrent(torrentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Reset(Guid downloadId)
|
public async Task Reset(Guid downloadId, DateTimeOffset? downloadQueued = null)
|
||||||
{
|
{
|
||||||
await downloadData.Reset(downloadId);
|
await downloadData.Reset(downloadId, downloadQueued);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,5 @@ public interface IDownloads
|
||||||
Task UpdateRetryCount(Guid downloadId, Int32 retryCount);
|
Task UpdateRetryCount(Guid downloadId, Int32 retryCount);
|
||||||
Task UpdateRemoteId(Guid downloadId, String remoteId);
|
Task UpdateRemoteId(Guid downloadId, String remoteId);
|
||||||
Task DeleteForTorrent(Guid torrentId);
|
Task DeleteForTorrent(Guid torrentId);
|
||||||
Task Reset(Guid downloadId);
|
Task Reset(Guid downloadId, DateTimeOffset? downloadQueued = null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using RdtClient.Data.Models.QBittorrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services;
|
namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authentication authentication, Torrents torrents, Downloads downloads)
|
public class QBittorrent(ILogger<QBittorrent> logger, ISettings settings, Authentication authentication, Torrents torrents, Downloads downloads, ITorrentRunnerState runnerState)
|
||||||
{
|
{
|
||||||
public async Task<Boolean> AuthLogin(String userName, String password)
|
public async Task<Boolean> AuthLogin(String userName, String password)
|
||||||
{
|
{
|
||||||
|
|
@ -168,7 +168,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
WebUiUsername = ""
|
WebUiUsername = ""
|
||||||
};
|
};
|
||||||
|
|
||||||
var savePath = Settings.AppDefaultSavePath;
|
var savePath = settings.DefaultSavePath;
|
||||||
|
|
||||||
preferences.SavePath = savePath;
|
preferences.SavePath = savePath;
|
||||||
preferences.TempPath = $"{savePath}temp{Path.DirectorySeparatorChar}";
|
preferences.TempPath = $"{savePath}temp{Path.DirectorySeparatorChar}";
|
||||||
|
|
@ -185,7 +185,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
|
|
||||||
public virtual async Task<IList<TorrentInfo>> TorrentInfo()
|
public virtual async Task<IList<TorrentInfo>> TorrentInfo()
|
||||||
{
|
{
|
||||||
var savePath = Settings.AppDefaultSavePath;
|
var savePath = settings.DefaultSavePath;
|
||||||
|
|
||||||
var results = new List<TorrentInfo>();
|
var results = new List<TorrentInfo>();
|
||||||
|
|
||||||
|
|
@ -346,19 +346,24 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
}
|
}
|
||||||
|
|
||||||
var topLevelSelectedFiles = torrent.Files
|
var topLevelSelectedFiles = torrent.Files
|
||||||
.Where(m => m.Selected && !String.IsNullOrWhiteSpace(m.Path))
|
.Where(m => m.Selected && !String.IsNullOrWhiteSpace(m.Path))
|
||||||
.Select(m => m.Path.Trim('/').Trim('\\'))
|
.Select(m => m.Path.Trim('/').Trim('\\'))
|
||||||
.Where(m => m.IndexOfAny(['/', '\\']) < 0)
|
.Where(m => m.IndexOfAny(['/', '\\']) < 0)
|
||||||
.Select(Path.GetFileName)
|
.Select(Path.GetFileName)
|
||||||
.Where(m => !String.IsNullOrWhiteSpace(m))
|
.Where(m => !String.IsNullOrWhiteSpace(m))
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (topLevelSelectedFiles.Count == 1)
|
if (topLevelSelectedFiles.Count == 1)
|
||||||
{
|
{
|
||||||
var selectedFileName = topLevelSelectedFiles[0]!;
|
var selectedFileName = topLevelSelectedFiles[0]!;
|
||||||
var selectedFileBaseName = Path.GetFileNameWithoutExtension(selectedFileName);
|
var selectedFileBaseName = Path.GetFileNameWithoutExtension(selectedFileName);
|
||||||
|
|
||||||
|
if (torrent.ClientKind == Provider.TorBox)
|
||||||
|
{
|
||||||
|
return selectedFileBaseName;
|
||||||
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(selectedFileBaseName) &&
|
if (!String.IsNullOrWhiteSpace(selectedFileBaseName) &&
|
||||||
selectedFileBaseName.Equals(torrent.RdName, StringComparison.OrdinalIgnoreCase))
|
selectedFileBaseName.Equals(torrent.RdName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -501,21 +506,21 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
var progress = torrent.Completed.HasValue || torrent.RdStatus == TorrentStatus.Finished ? 1f : 0f;
|
var progress = torrent.Completed.HasValue || torrent.RdStatus == TorrentStatus.Finished ? 1f : 0f;
|
||||||
|
|
||||||
return torrent.Files
|
return torrent.Files
|
||||||
.Select((file, index) => new TorrentFileItem
|
.Select((file, index) => new TorrentFileItem
|
||||||
{
|
{
|
||||||
Index = index,
|
Index = index,
|
||||||
Name = file.Path,
|
Name = file.Path,
|
||||||
Size = file.Bytes,
|
Size = file.Bytes,
|
||||||
Progress = file.Selected ? progress : 0f,
|
Progress = file.Selected ? progress : 0f,
|
||||||
Priority = file.Selected ? 1 : 0,
|
Priority = file.Selected ? 1 : 0,
|
||||||
IsSeed = false
|
IsSeed = false
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TorrentProperties?> TorrentProperties(String hash)
|
public async Task<TorrentProperties?> TorrentProperties(String hash)
|
||||||
{
|
{
|
||||||
var savePath = Settings.AppDefaultSavePath;
|
var savePath = settings.DefaultSavePath;
|
||||||
|
|
||||||
var torrent = await torrents.GetByHash(hash);
|
var torrent = await torrents.GetByHash(hash);
|
||||||
|
|
||||||
|
|
@ -605,7 +610,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (Settings.Get.Integrations.Default.FinishedAction)
|
switch (settings.Current.Integrations.Default.FinishedAction)
|
||||||
{
|
{
|
||||||
case TorrentFinishedAction.RemoveAllTorrents:
|
case TorrentFinishedAction.RemoveAllTorrents:
|
||||||
logger.LogDebug("Removing torrents from debrid provider and RDT-Client, no files");
|
logger.LogDebug("Removing torrents from debrid provider and RDT-Client, no files");
|
||||||
|
|
@ -633,54 +638,54 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TorrentsAddMagnet(String magnetLink, String? category, Int32? priority)
|
public async Task<Torrent> TorrentsAddMagnet(String magnetLink, String? category, Int32? priority)
|
||||||
{
|
{
|
||||||
logger.LogDebug($"Add magnet {category}");
|
logger.LogDebug($"Add magnet {category}");
|
||||||
|
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
Category = category,
|
Category = category,
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client,
|
||||||
HostDownloadAction = Settings.Get.Integrations.Default.HostDownloadAction,
|
HostDownloadAction = settings.Current.Integrations.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Integrations.Default.FinishedActionDelay,
|
FinishedActionDelay = settings.Current.Integrations.Default.FinishedActionDelay,
|
||||||
DownloadAction = Settings.Get.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
DownloadAction = settings.Current.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
||||||
FinishedAction = TorrentFinishedAction.None,
|
FinishedAction = TorrentFinishedAction.None,
|
||||||
DownloadMinSize = Settings.Get.Integrations.Default.MinFileSize,
|
DownloadMinSize = settings.Current.Integrations.Default.MinFileSize,
|
||||||
IncludeRegex = Settings.Get.Integrations.Default.IncludeRegex,
|
IncludeRegex = settings.Current.Integrations.Default.IncludeRegex,
|
||||||
ExcludeRegex = Settings.Get.Integrations.Default.ExcludeRegex,
|
ExcludeRegex = settings.Current.Integrations.Default.ExcludeRegex,
|
||||||
TorrentRetryAttempts = Settings.Get.Integrations.Default.TorrentRetryAttempts,
|
TorrentRetryAttempts = settings.Current.Integrations.Default.TorrentRetryAttempts,
|
||||||
DownloadRetryAttempts = Settings.Get.Integrations.Default.DownloadRetryAttempts,
|
DownloadRetryAttempts = settings.Current.Integrations.Default.DownloadRetryAttempts,
|
||||||
DeleteOnError = Settings.Get.Integrations.Default.DeleteOnError,
|
DeleteOnError = settings.Current.Integrations.Default.DeleteOnError,
|
||||||
Lifetime = Settings.Get.Integrations.Default.TorrentLifetime,
|
Lifetime = settings.Current.Integrations.Default.TorrentLifetime,
|
||||||
Priority = priority ?? (Settings.Get.Integrations.Default.Priority > 0 ? Settings.Get.Integrations.Default.Priority : null)
|
Priority = priority ?? (settings.Current.Integrations.Default.Priority > 0 ? settings.Current.Integrations.Default.Priority : null)
|
||||||
};
|
};
|
||||||
|
|
||||||
await torrents.AddMagnetToDebridQueue(magnetLink, torrent);
|
return await torrents.AddMagnetToDebridQueue(magnetLink, torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TorrentsAddFile(Byte[] fileBytes, String? category, Int32? priority)
|
public async Task<Torrent> TorrentsAddFile(Byte[] fileBytes, String? category, Int32? priority)
|
||||||
{
|
{
|
||||||
logger.LogDebug($"Add file {category}");
|
logger.LogDebug($"Add file {category}");
|
||||||
|
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
Category = category,
|
Category = category,
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client,
|
||||||
HostDownloadAction = Settings.Get.Integrations.Default.HostDownloadAction,
|
HostDownloadAction = settings.Current.Integrations.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Integrations.Default.FinishedActionDelay,
|
FinishedActionDelay = settings.Current.Integrations.Default.FinishedActionDelay,
|
||||||
DownloadAction = Settings.Get.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
DownloadAction = settings.Current.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
||||||
FinishedAction = TorrentFinishedAction.None,
|
FinishedAction = TorrentFinishedAction.None,
|
||||||
DownloadMinSize = Settings.Get.Integrations.Default.MinFileSize,
|
DownloadMinSize = settings.Current.Integrations.Default.MinFileSize,
|
||||||
IncludeRegex = Settings.Get.Integrations.Default.IncludeRegex,
|
IncludeRegex = settings.Current.Integrations.Default.IncludeRegex,
|
||||||
ExcludeRegex = Settings.Get.Integrations.Default.ExcludeRegex,
|
ExcludeRegex = settings.Current.Integrations.Default.ExcludeRegex,
|
||||||
TorrentRetryAttempts = Settings.Get.Integrations.Default.TorrentRetryAttempts,
|
TorrentRetryAttempts = settings.Current.Integrations.Default.TorrentRetryAttempts,
|
||||||
DownloadRetryAttempts = Settings.Get.Integrations.Default.DownloadRetryAttempts,
|
DownloadRetryAttempts = settings.Current.Integrations.Default.DownloadRetryAttempts,
|
||||||
DeleteOnError = Settings.Get.Integrations.Default.DeleteOnError,
|
DeleteOnError = settings.Current.Integrations.Default.DeleteOnError,
|
||||||
Lifetime = Settings.Get.Integrations.Default.TorrentLifetime,
|
Lifetime = settings.Current.Integrations.Default.TorrentLifetime,
|
||||||
Priority = priority ?? (Settings.Get.Integrations.Default.Priority > 0 ? Settings.Get.Integrations.Default.Priority : null)
|
Priority = priority ?? (settings.Current.Integrations.Default.Priority > 0 ? settings.Current.Integrations.Default.Priority : null)
|
||||||
};
|
};
|
||||||
|
|
||||||
await torrents.AddFileToDebridQueue(fileBytes, torrent);
|
return await torrents.AddFileToDebridQueue(fileBytes, torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TorrentsSetCategory(String hash, String? category)
|
public async Task TorrentsSetCategory(String hash, String? category)
|
||||||
|
|
@ -696,7 +701,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
.Select(m => m.Category!.ToLower())
|
.Select(m => m.Category!.ToLower())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var categoryList = (Settings.Get.General.Categories ?? "")
|
var categoryList = (settings.Current.General.Categories ?? "")
|
||||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Distinct(StringComparer.CurrentCultureIgnoreCase)
|
.Distinct(StringComparer.CurrentCultureIgnoreCase)
|
||||||
.Select(m => m.Trim())
|
.Select(m => m.Trim())
|
||||||
|
|
@ -713,7 +718,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
m => new TorrentCategory
|
m => new TorrentCategory
|
||||||
{
|
{
|
||||||
Name = m,
|
Name = m,
|
||||||
SavePath = Path.Combine(Settings.AppDefaultSavePath, m)
|
SavePath = Path.Combine(settings.DefaultSavePath, m)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -729,7 +734,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
|
|
||||||
category = category.Trim();
|
category = category.Trim();
|
||||||
|
|
||||||
var categoriesSetting = Settings.Get.General.Categories;
|
var categoriesSetting = settings.Current.General.Categories;
|
||||||
|
|
||||||
var categoryList = (categoriesSetting ?? "")
|
var categoryList = (categoriesSetting ?? "")
|
||||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
|
@ -756,7 +761,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
|
|
||||||
category = category.Trim();
|
category = category.Trim();
|
||||||
|
|
||||||
var categoriesSetting = Settings.Get.General.Categories;
|
var categoriesSetting = settings.Current.General.Categories;
|
||||||
|
|
||||||
var categoryList = (categoriesSetting ?? "")
|
var categoryList = (categoriesSetting ?? "")
|
||||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
|
@ -796,7 +801,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
|
|
||||||
foreach (var download in downloadsForTorrent)
|
foreach (var download in downloadsForTorrent)
|
||||||
{
|
{
|
||||||
if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
if (runnerState.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||||
{
|
{
|
||||||
await downloadClient.Pause();
|
await downloadClient.Pause();
|
||||||
}
|
}
|
||||||
|
|
@ -816,7 +821,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
|
|
||||||
foreach (var download in downloadsForTorrent)
|
foreach (var download in downloadsForTorrent)
|
||||||
{
|
{
|
||||||
if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
if (runnerState.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||||
{
|
{
|
||||||
await downloadClient.Resume();
|
await downloadClient.Resume();
|
||||||
}
|
}
|
||||||
|
|
@ -829,7 +834,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
|
|
||||||
var categories = await TorrentsCategories();
|
var categories = await TorrentsCategories();
|
||||||
|
|
||||||
var activeDownloads = TorrentRunner.ActiveDownloadClients.Sum(m => m.Value.Speed);
|
var activeDownloads = runnerState.ActiveDownloadClients.Sum(m => m.Value.Speed);
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
|
|
@ -847,16 +852,16 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TransferInfo TransferInfo()
|
public virtual TransferInfo TransferInfo()
|
||||||
{
|
{
|
||||||
var activeDownloads = TorrentRunner.ActiveDownloadClients.Sum(m => m.Value.Speed);
|
var activeDownloads = runnerState.ActiveDownloadClients.Sum(m => m.Value.Speed);
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
ConnectionStatus = "connected",
|
ConnectionStatus = "connected",
|
||||||
DlInfoData = DownloadClient.GetTotalBytesDownloadedThisSession(),
|
DlInfoData = DownloadClient.GetTotalBytesDownloadedThisSession(),
|
||||||
DlInfoSpeed = activeDownloads,
|
DlInfoSpeed = activeDownloads,
|
||||||
DlRateLimit = Settings.Get.DownloadClient.MaxSpeed
|
DlRateLimit = settings.Current.DownloadClient.MaxSpeed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using RdtClient.Service.Helpers;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services;
|
namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings appSettings)
|
public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings appSettings, ISettings settings)
|
||||||
{
|
{
|
||||||
public virtual async Task<SabnzbdQueue> GetQueue()
|
public virtual async Task<SabnzbdQueue> GetQueue()
|
||||||
{
|
{
|
||||||
|
|
@ -87,7 +87,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
var allTorrents = await torrents.Get();
|
var allTorrents = await torrents.Get();
|
||||||
var completedTorrents = allTorrents.Where(t => t.Type == DownloadType.Nzb && t.Completed != null).ToList();
|
var completedTorrents = allTorrents.Where(t => t.Type == DownloadType.Nzb && t.Completed != null).ToList();
|
||||||
|
|
||||||
var savePath = Settings.AppDefaultSavePath;
|
var savePath = settings.DefaultSavePath;
|
||||||
|
|
||||||
var history = new SabnzbdHistory
|
var history = new SabnzbdHistory
|
||||||
{
|
{
|
||||||
|
|
@ -130,19 +130,19 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
Category = category,
|
Category = category,
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client,
|
||||||
HostDownloadAction = Settings.Get.Integrations.Default.HostDownloadAction,
|
HostDownloadAction = settings.Current.Integrations.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Integrations.Default.FinishedActionDelay,
|
FinishedActionDelay = settings.Current.Integrations.Default.FinishedActionDelay,
|
||||||
DownloadAction = Settings.Get.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
DownloadAction = settings.Current.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
||||||
FinishedAction = TorrentFinishedAction.None,
|
FinishedAction = TorrentFinishedAction.None,
|
||||||
DownloadMinSize = Settings.Get.Integrations.Default.MinFileSize,
|
DownloadMinSize = settings.Current.Integrations.Default.MinFileSize,
|
||||||
IncludeRegex = Settings.Get.Integrations.Default.IncludeRegex,
|
IncludeRegex = settings.Current.Integrations.Default.IncludeRegex,
|
||||||
ExcludeRegex = Settings.Get.Integrations.Default.ExcludeRegex,
|
ExcludeRegex = settings.Current.Integrations.Default.ExcludeRegex,
|
||||||
TorrentRetryAttempts = Settings.Get.Integrations.Default.TorrentRetryAttempts,
|
TorrentRetryAttempts = settings.Current.Integrations.Default.TorrentRetryAttempts,
|
||||||
DownloadRetryAttempts = Settings.Get.Integrations.Default.DownloadRetryAttempts,
|
DownloadRetryAttempts = settings.Current.Integrations.Default.DownloadRetryAttempts,
|
||||||
DeleteOnError = Settings.Get.Integrations.Default.DeleteOnError,
|
DeleteOnError = settings.Current.Integrations.Default.DeleteOnError,
|
||||||
Lifetime = Settings.Get.Integrations.Default.TorrentLifetime,
|
Lifetime = settings.Current.Integrations.Default.TorrentLifetime,
|
||||||
Priority = (priority ?? Settings.Get.Integrations.Default.Priority) > 0 ? 1 : null
|
Priority = (priority ?? settings.Current.Integrations.Default.Priority) > 0 ? 1 : null
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await torrents.AddNzbFileToDebridQueue(fileBytes, fileName, torrent);
|
var result = await torrents.AddNzbFileToDebridQueue(fileBytes, fileName, torrent);
|
||||||
|
|
@ -157,19 +157,19 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
Category = category,
|
Category = category,
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client,
|
||||||
HostDownloadAction = Settings.Get.Integrations.Default.HostDownloadAction,
|
HostDownloadAction = settings.Current.Integrations.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Integrations.Default.FinishedActionDelay,
|
FinishedActionDelay = settings.Current.Integrations.Default.FinishedActionDelay,
|
||||||
DownloadAction = Settings.Get.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
DownloadAction = settings.Current.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
||||||
FinishedAction = TorrentFinishedAction.None,
|
FinishedAction = TorrentFinishedAction.None,
|
||||||
DownloadMinSize = Settings.Get.Integrations.Default.MinFileSize,
|
DownloadMinSize = settings.Current.Integrations.Default.MinFileSize,
|
||||||
IncludeRegex = Settings.Get.Integrations.Default.IncludeRegex,
|
IncludeRegex = settings.Current.Integrations.Default.IncludeRegex,
|
||||||
ExcludeRegex = Settings.Get.Integrations.Default.ExcludeRegex,
|
ExcludeRegex = settings.Current.Integrations.Default.ExcludeRegex,
|
||||||
TorrentRetryAttempts = Settings.Get.Integrations.Default.TorrentRetryAttempts,
|
TorrentRetryAttempts = settings.Current.Integrations.Default.TorrentRetryAttempts,
|
||||||
DownloadRetryAttempts = Settings.Get.Integrations.Default.DownloadRetryAttempts,
|
DownloadRetryAttempts = settings.Current.Integrations.Default.DownloadRetryAttempts,
|
||||||
DeleteOnError = Settings.Get.Integrations.Default.DeleteOnError,
|
DeleteOnError = settings.Current.Integrations.Default.DeleteOnError,
|
||||||
Lifetime = Settings.Get.Integrations.Default.TorrentLifetime,
|
Lifetime = settings.Current.Integrations.Default.TorrentLifetime,
|
||||||
Priority = priority ?? (Settings.Get.Integrations.Default.Priority > 0 ? Settings.Get.Integrations.Default.Priority : null)
|
Priority = priority ?? (settings.Current.Integrations.Default.Priority > 0 ? settings.Current.Integrations.Default.Priority : null)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await torrents.AddNzbLinkToDebridQueue(url, torrent);
|
var result = await torrents.AddNzbLinkToDebridQueue(url, torrent);
|
||||||
|
|
@ -177,7 +177,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
return result.Hash;
|
return result.Hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task Delete(String hash)
|
public virtual async Task Delete(String hash, Boolean deleteFiles = false)
|
||||||
{
|
{
|
||||||
var torrent = await torrents.GetByHash(hash);
|
var torrent = await torrents.GetByHash(hash);
|
||||||
|
|
||||||
|
|
@ -186,21 +186,21 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (Settings.Get.Integrations.Default.FinishedAction)
|
switch (settings.Current.Integrations.Default.FinishedAction)
|
||||||
{
|
{
|
||||||
case TorrentFinishedAction.RemoveAllTorrents:
|
case TorrentFinishedAction.RemoveAllTorrents:
|
||||||
logger.LogDebug("Removing nzb from debrid provider and RDT-Client, no files");
|
logger.LogDebug("Removing nzb from debrid provider and RDT-Client, {Files}", deleteFiles ? "with files" : "no files");
|
||||||
await torrents.Delete(torrent.TorrentId, true, true, true);
|
await torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TorrentFinishedAction.RemoveRealDebrid:
|
case TorrentFinishedAction.RemoveRealDebrid:
|
||||||
logger.LogDebug("Removing nzb from debrid provider, no files");
|
logger.LogDebug("Removing nzb from debrid provider, {Files}", deleteFiles ? "with files" : "no files");
|
||||||
await torrents.Delete(torrent.TorrentId, false, true, true);
|
await torrents.Delete(torrent.TorrentId, false, true, deleteFiles);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TorrentFinishedAction.RemoveClient:
|
case TorrentFinishedAction.RemoveClient:
|
||||||
logger.LogDebug("Removing nzb from client, no files");
|
logger.LogDebug("Removing nzb from client, {Files}", deleteFiles ? "with files" : "no files");
|
||||||
await torrents.Delete(torrent.TorrentId, true, false, true);
|
await torrents.Delete(torrent.TorrentId, true, false, deleteFiles);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TorrentFinishedAction.None:
|
case TorrentFinishedAction.None:
|
||||||
|
|
@ -216,7 +216,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
|
|
||||||
public virtual List<String> GetCategories()
|
public virtual List<String> GetCategories()
|
||||||
{
|
{
|
||||||
var categoryList = (Settings.Get.General.Categories ?? "")
|
var categoryList = (settings.Current.General.Categories ?? "")
|
||||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(m => m.Trim())
|
.Select(m => m.Trim())
|
||||||
.Where(m => m != "*")
|
.Where(m => m != "*")
|
||||||
|
|
@ -230,7 +230,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
|
|
||||||
public virtual SabnzbdConfig GetConfig()
|
public virtual SabnzbdConfig GetConfig()
|
||||||
{
|
{
|
||||||
var savePath = Settings.AppDefaultSavePath;
|
var savePath = settings.DefaultSavePath;
|
||||||
|
|
||||||
var categoryList = GetCategories();
|
var categoryList = GetCategories();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,88 @@
|
||||||
using RdtClient.Data.Data;
|
using RdtClient.Data.Data;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Internal;
|
using RdtClient.Data.Models.Internal;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services;
|
namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
public class Settings(SettingData settingData)
|
public interface ISettings
|
||||||
|
{
|
||||||
|
DbSettings Current { get; }
|
||||||
|
|
||||||
|
String DefaultSavePath { get; }
|
||||||
|
|
||||||
|
IList<SettingProperty> GetAll();
|
||||||
|
|
||||||
|
Task Update(IList<SettingProperty> settings);
|
||||||
|
|
||||||
|
Task Update(String settingId, Object? value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Settings(IServiceScopeFactory serviceScopeFactory) : ISettings
|
||||||
{
|
{
|
||||||
public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Debug);
|
public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Debug);
|
||||||
|
|
||||||
public static DbSettings Get => SettingData.Get;
|
public static DbSettings Get => SettingData.Get;
|
||||||
|
|
||||||
public static String AppDefaultSavePath
|
public DbSettings Current => Get;
|
||||||
|
|
||||||
|
public static String AppDefaultSavePath => GetDefaultSavePath(Get);
|
||||||
|
|
||||||
|
public String DefaultSavePath => GetDefaultSavePath(Current);
|
||||||
|
|
||||||
|
public IList<SettingProperty> GetAll()
|
||||||
{
|
{
|
||||||
get
|
return SettingData.GetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String GetDefaultSavePath(DbSettings settings)
|
||||||
|
{
|
||||||
|
var downloadPath = settings.DownloadClient.MappedPath;
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(downloadPath))
|
||||||
{
|
{
|
||||||
var downloadPath = Get.DownloadClient.MappedPath;
|
downloadPath = settings.DownloadClient.DownloadPath;
|
||||||
|
|
||||||
downloadPath = downloadPath.TrimEnd('\\')
|
|
||||||
.TrimEnd('/');
|
|
||||||
|
|
||||||
downloadPath += Path.DirectorySeparatorChar;
|
|
||||||
|
|
||||||
return downloadPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadPath = downloadPath.TrimEnd('\\')
|
||||||
|
.TrimEnd('/');
|
||||||
|
|
||||||
|
downloadPath += Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
|
return downloadPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Update(IList<SettingProperty> settings)
|
public async Task Update(IList<SettingProperty> settings)
|
||||||
{
|
{
|
||||||
|
using var scope = serviceScopeFactory.CreateScope();
|
||||||
|
var settingData = scope.ServiceProvider.GetRequiredService<SettingData>();
|
||||||
|
|
||||||
await settingData.Update(settings);
|
await settingData.Update(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Update(String settingId, Object? value)
|
public async Task Update(String settingId, Object? value)
|
||||||
{
|
{
|
||||||
|
using var scope = serviceScopeFactory.CreateScope();
|
||||||
|
var settingData = scope.ServiceProvider.GetRequiredService<SettingData>();
|
||||||
|
|
||||||
await settingData.Update(settingId, value);
|
await settingData.Update(settingId, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Seed()
|
public async Task Seed()
|
||||||
{
|
{
|
||||||
|
using var scope = serviceScopeFactory.CreateScope();
|
||||||
|
var settingData = scope.ServiceProvider.GetRequiredService<SettingData>();
|
||||||
|
|
||||||
await settingData.Seed();
|
await settingData.Seed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ResetCache()
|
public async Task ResetCache()
|
||||||
{
|
{
|
||||||
|
using var scope = serviceScopeFactory.CreateScope();
|
||||||
|
var settingData = scope.ServiceProvider.GetRequiredService<SettingData>();
|
||||||
|
|
||||||
await settingData.ResetCache();
|
await settingData.ResetCache();
|
||||||
|
|
||||||
LoggingLevelSwitch.MinimumLevel = Get.General.LogLevel switch
|
LoggingLevelSwitch.MinimumLevel = Get.General.LogLevel switch
|
||||||
|
|
|
||||||
|
|
@ -17,34 +17,34 @@ public class TorrentRunner(
|
||||||
Downloads downloads,
|
Downloads downloads,
|
||||||
RemoteService remoteService,
|
RemoteService remoteService,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
IRateLimitCoordinator coordinator)
|
IRateLimitCoordinator coordinator,
|
||||||
|
ISettings settings,
|
||||||
|
ITorrentRunnerState runnerState)
|
||||||
{
|
{
|
||||||
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
|
public static readonly ITorrentRunnerState SharedState = new TorrentRunnerState();
|
||||||
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
|
|
||||||
|
public static ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients => SharedState.ActiveDownloadClients;
|
||||||
|
|
||||||
|
public static ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients => SharedState.ActiveUnpackClients;
|
||||||
|
|
||||||
private DateTimeOffset? _lastNextAllowedAt;
|
private DateTimeOffset? _lastNextAllowedAt;
|
||||||
|
|
||||||
public static Boolean IsPausedForLowDiskSpace { get; set; }
|
public static Boolean IsPausedForLowDiskSpace
|
||||||
|
{
|
||||||
|
get => SharedState.IsPausedForLowDiskSpace;
|
||||||
|
set => SharedState.IsPausedForLowDiskSpace = value;
|
||||||
|
}
|
||||||
|
|
||||||
public static (Int64 Speed, Int64 BytesTotal, Int64 BytesDone) GetStats(Guid downloadId)
|
public static (Int64 Speed, Int64 BytesTotal, Int64 BytesDone) GetStats(Guid downloadId)
|
||||||
{
|
{
|
||||||
if (ActiveDownloadClients.TryGetValue(downloadId, out var downloadClient))
|
return SharedState.GetStats(downloadId);
|
||||||
{
|
|
||||||
return (downloadClient.Speed, downloadClient.BytesTotal, downloadClient.BytesDone);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ActiveUnpackClients.TryGetValue(downloadId, out var unpackClient))
|
|
||||||
{
|
|
||||||
return (0, 100, unpackClient.Progess);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
Log("Initializing TorrentRunner");
|
Log("Initializing TorrentRunner");
|
||||||
|
|
||||||
var settingsCopy = JsonSerializer.Deserialize<DbSettings>(JsonSerializer.Serialize(Settings.Get));
|
var settingsCopy = JsonSerializer.Deserialize<DbSettings>(JsonSerializer.Serialize(settings.Current));
|
||||||
|
|
||||||
if (settingsCopy != null)
|
if (settingsCopy != null)
|
||||||
{
|
{
|
||||||
|
|
@ -87,28 +87,28 @@ public class TorrentRunner(
|
||||||
|
|
||||||
public async Task Tick()
|
public async Task Tick()
|
||||||
{
|
{
|
||||||
if (String.IsNullOrWhiteSpace(Settings.Get.Provider.ApiKey))
|
if (String.IsNullOrWhiteSpace(settings.Current.Provider.ApiKey))
|
||||||
{
|
{
|
||||||
Log($"No RealDebridApiKey set in settings");
|
Log($"No RealDebridApiKey set in settings");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingDownloadLimit = Settings.Get.General.DownloadLimit;
|
var settingDownloadLimit = settings.Current.General.DownloadLimit;
|
||||||
|
|
||||||
if (settingDownloadLimit < 1)
|
if (settingDownloadLimit < 1)
|
||||||
{
|
{
|
||||||
settingDownloadLimit = 1;
|
settingDownloadLimit = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingUnpackLimit = Settings.Get.General.UnpackLimit;
|
var settingUnpackLimit = settings.Current.General.UnpackLimit;
|
||||||
|
|
||||||
if (settingUnpackLimit < 0)
|
if (settingUnpackLimit < 0)
|
||||||
{
|
{
|
||||||
settingUnpackLimit = 0;
|
settingUnpackLimit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingDownloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var settingDownloadPath = settings.Current.DownloadClient.DownloadPath;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(settingDownloadPath))
|
if (String.IsNullOrWhiteSpace(settingDownloadPath))
|
||||||
{
|
{
|
||||||
|
|
@ -141,25 +141,25 @@ public class TorrentRunner(
|
||||||
_lastNextAllowedAt = currentNextAllowedAt;
|
_lastNextAllowedAt = currentNextAllowedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ActiveDownloadClients.IsEmpty || !ActiveUnpackClients.IsEmpty)
|
if (!runnerState.ActiveDownloadClients.IsEmpty || !runnerState.ActiveUnpackClients.IsEmpty)
|
||||||
{
|
{
|
||||||
Log($"TorrentRunner Tick Start, {ActiveDownloadClients.Count} active downloads, {ActiveUnpackClients.Count} active unpacks");
|
Log($"TorrentRunner Tick Start, {runnerState.ActiveDownloadClients.Count} active downloads, {runnerState.ActiveUnpackClients.Count} active unpacks");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ActiveDownloadClients.Any(m => m.Value.Type == Data.Enums.DownloadClient.Aria2c))
|
if (runnerState.ActiveDownloadClients.Any(m => m.Value.Type == Data.Enums.DownloadClient.Aria2c))
|
||||||
{
|
{
|
||||||
Log("Updating Aria2 status");
|
Log("Updating Aria2 status");
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
var aria2NetClient = new Aria2NetClient(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 1);
|
var aria2NetClient = new Aria2NetClient(settings.Current.DownloadClient.Aria2cUrl, settings.Current.DownloadClient.Aria2cSecret, httpClient, 1);
|
||||||
|
|
||||||
var allDownloads = await aria2NetClient.TellAllAsync();
|
var allDownloads = await aria2NetClient.TellAllAsync();
|
||||||
|
|
||||||
Log($"Found {allDownloads.Count} Aria2 downloads");
|
Log($"Found {allDownloads.Count} Aria2 downloads");
|
||||||
|
|
||||||
foreach (var activeDownload in ActiveDownloadClients)
|
foreach (var activeDownload in runnerState.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (activeDownload.Value.Downloader is Aria2cDownloader aria2Downloader)
|
if (activeDownload.Value.Downloader is Aria2cDownloader aria2Downloader)
|
||||||
{
|
{
|
||||||
|
|
@ -170,11 +170,11 @@ public class TorrentRunner(
|
||||||
Log("Finished updating Aria2 status");
|
Log("Finished updating Aria2 status");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ActiveDownloadClients.Any(m => m.Value.Type == Data.Enums.DownloadClient.DownloadStation))
|
if (runnerState.ActiveDownloadClients.Any(m => m.Value.Type == Data.Enums.DownloadClient.DownloadStation))
|
||||||
{
|
{
|
||||||
Log("Updating DownloadStation status");
|
Log("Updating DownloadStation status");
|
||||||
|
|
||||||
foreach (var activeDownload in ActiveDownloadClients)
|
foreach (var activeDownload in runnerState.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (activeDownload.Value.Downloader is DownloadStationDownloader downloadStationDownloader)
|
if (activeDownload.Value.Downloader is DownloadStationDownloader downloadStationDownloader)
|
||||||
{
|
{
|
||||||
|
|
@ -184,7 +184,7 @@ public class TorrentRunner(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any torrents are finished downloading to the host, remove them from the active download list.
|
// Check if any torrents are finished downloading to the host, remove them from the active download list.
|
||||||
var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList();
|
var completedActiveDownloads = runnerState.ActiveDownloadClients.Where(m => m.Value.Finished).ToList();
|
||||||
|
|
||||||
if (completedActiveDownloads.Count > 0)
|
if (completedActiveDownloads.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -196,7 +196,7 @@ public class TorrentRunner(
|
||||||
|
|
||||||
if (download == null)
|
if (download == null)
|
||||||
{
|
{
|
||||||
ActiveDownloadClients.TryRemove(downloadId, out _);
|
runnerState.ActiveDownloadClients.TryRemove(downloadId, out _);
|
||||||
|
|
||||||
Log($"Download with ID {downloadId} not found! Removed from download queue");
|
Log($"Download with ID {downloadId} not found! Removed from download queue");
|
||||||
|
|
||||||
|
|
@ -237,14 +237,14 @@ public class TorrentRunner(
|
||||||
await downloads.UpdateUnpackingQueued(downloadId, DateTimeOffset.UtcNow);
|
await downloads.UpdateUnpackingQueued(downloadId, DateTimeOffset.UtcNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveDownloadClients.TryRemove(downloadId, out _);
|
runnerState.ActiveDownloadClients.TryRemove(downloadId, out _);
|
||||||
|
|
||||||
Log($"Removed from ActiveDownloadClients", download, download.Torrent);
|
Log($"Removed from ActiveDownloadClients", download, download.Torrent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any torrents are finished unpacking, remove them from the active unpack list.
|
// Check if any torrents are finished unpacking, remove them from the active unpack list.
|
||||||
var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList();
|
var completedUnpacks = runnerState.ActiveUnpackClients.Where(m => m.Value.Finished).ToList();
|
||||||
|
|
||||||
if (completedUnpacks.Count > 0)
|
if (completedUnpacks.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -256,7 +256,7 @@ public class TorrentRunner(
|
||||||
|
|
||||||
if (download == null)
|
if (download == null)
|
||||||
{
|
{
|
||||||
ActiveUnpackClients.TryRemove(downloadId, out _);
|
runnerState.ActiveUnpackClients.TryRemove(downloadId, out _);
|
||||||
|
|
||||||
Log($"Download with ID {downloadId} not found! Removed from unpack queue");
|
Log($"Download with ID {downloadId} not found! Removed from unpack queue");
|
||||||
|
|
||||||
|
|
@ -278,7 +278,7 @@ public class TorrentRunner(
|
||||||
|
|
||||||
await downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow);
|
await downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
ActiveUnpackClients.TryRemove(downloadId, out _);
|
runnerState.ActiveUnpackClients.TryRemove(downloadId, out _);
|
||||||
|
|
||||||
Log($"Removed from ActiveUnpackClients", download, download.Torrent);
|
Log($"Removed from ActiveUnpackClients", download, download.Torrent);
|
||||||
}
|
}
|
||||||
|
|
@ -288,23 +288,23 @@ public class TorrentRunner(
|
||||||
var downloadsById = allTorrents.SelectMany(m => m.Downloads).ToDictionary(m => m.DownloadId, m => m);
|
var downloadsById = allTorrents.SelectMany(m => m.Downloads).ToDictionary(m => m.DownloadId, m => m);
|
||||||
|
|
||||||
// Check for deleted torrents that are stuck in the ActiveDownloads or ActiveUnpacks
|
// Check for deleted torrents that are stuck in the ActiveDownloads or ActiveUnpacks
|
||||||
foreach (var activeDownload in ActiveDownloadClients)
|
foreach (var activeDownload in runnerState.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (!downloadsById.ContainsKey(activeDownload.Key))
|
if (!downloadsById.ContainsKey(activeDownload.Key))
|
||||||
{
|
{
|
||||||
await activeDownload.Value.Cancel();
|
await activeDownload.Value.Cancel();
|
||||||
ActiveDownloadClients.TryRemove(activeDownload.Key, out _);
|
runnerState.ActiveDownloadClients.TryRemove(activeDownload.Key, out _);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var activeUnpacks in ActiveUnpackClients)
|
foreach (var activeUnpacks in runnerState.ActiveUnpackClients)
|
||||||
{
|
{
|
||||||
if (!downloadsById.ContainsKey(activeUnpacks.Key))
|
if (!downloadsById.ContainsKey(activeUnpacks.Key))
|
||||||
{
|
{
|
||||||
activeUnpacks.Value.Cancel();
|
activeUnpacks.Value.Cancel();
|
||||||
ActiveUnpackClients.TryRemove(activeUnpacks.Key, out _);
|
runnerState.ActiveUnpackClients.TryRemove(activeUnpacks.Key, out _);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -367,8 +367,10 @@ public class TorrentRunner(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process torrents in DebridQueue
|
// Process torrents in DebridQueue
|
||||||
var torrentsToAddToProvider = allTorrents.Where(m => m.Completed == null && m.Error == null && m.RdId == null && m.RdAdded == null && m.FileOrMagnet != null && m.RdStatus == TorrentStatus.Queued)
|
var torrentsToAddToProvider = allTorrents
|
||||||
.ToList();
|
.Where(m => m.Completed == null && m.Error == null && m.RdId == null && m.RdAdded == null && m.FileOrMagnet != null &&
|
||||||
|
m.RdStatus == TorrentStatus.Queued)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
if (torrentsToAddToProvider.Count != 0)
|
if (torrentsToAddToProvider.Count != 0)
|
||||||
{
|
{
|
||||||
|
|
@ -382,7 +384,7 @@ public class TorrentRunner(
|
||||||
{
|
{
|
||||||
var downloadingTorrentsCount = allTorrents.Count(m => m.RdStatus is not (TorrentStatus.Queued or TorrentStatus.Finished or TorrentStatus.Error));
|
var downloadingTorrentsCount = allTorrents.Count(m => m.RdStatus is not (TorrentStatus.Queued or TorrentStatus.Finished or TorrentStatus.Error));
|
||||||
|
|
||||||
var maxParallelDownloads = Settings.Get.Provider.MaxParallelDownloads;
|
var maxParallelDownloads = settings.Current.Provider.MaxParallelDownloads;
|
||||||
|
|
||||||
logger.LogDebug("Currently downloading {downloadingTorrentCount}/{maxParallelDownloads} torrents, {queuedCount} queued.",
|
logger.LogDebug("Currently downloading {downloadingTorrentCount}/{maxParallelDownloads} torrents, {queuedCount} queued.",
|
||||||
downloadingTorrentsCount,
|
downloadingTorrentsCount,
|
||||||
|
|
@ -477,31 +479,35 @@ public class TorrentRunner(
|
||||||
{
|
{
|
||||||
// Check if there are any downloads that are queued and can be started.
|
// Check if there are any downloads that are queued and can be started.
|
||||||
var queuedDownloads = torrent.Downloads
|
var queuedDownloads = torrent.Downloads
|
||||||
.Where(m => m.Completed == null && m.DownloadQueued != null && m.DownloadStarted == null && m.Error == null)
|
.Where(m => m.Completed == null
|
||||||
|
&& m.DownloadQueued != null
|
||||||
|
&& m.DownloadQueued <= DateTimeOffset.UtcNow
|
||||||
|
&& m.DownloadStarted == null
|
||||||
|
&& m.Error == null)
|
||||||
.OrderBy(m => m.DownloadQueued)
|
.OrderBy(m => m.DownloadQueued)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent);
|
Log($"Currently {queuedDownloads.Count} queued downloads and {runnerState.ActiveDownloadClients.Count} total active downloads", torrent);
|
||||||
|
|
||||||
foreach (var download in queuedDownloads)
|
foreach (var download in queuedDownloads)
|
||||||
{
|
{
|
||||||
Log($"Processing to download", download, torrent);
|
Log($"Processing to download", download, torrent);
|
||||||
|
|
||||||
if (ActiveDownloadClients.Count >= settingDownloadLimit && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink)
|
if (runnerState.ActiveDownloadClients.Count >= settingDownloadLimit && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink)
|
||||||
{
|
{
|
||||||
Log($"Not starting download because there are already the max number of downloads active", download, torrent);
|
Log($"Not starting download because there are already the max number of downloads active", download, torrent);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsPausedForLowDiskSpace && torrent.DownloadClient == Data.Enums.DownloadClient.Bezzad)
|
if (runnerState.IsPausedForLowDiskSpace && torrent.DownloadClient == Data.Enums.DownloadClient.Bezzad)
|
||||||
{
|
{
|
||||||
logger.LogInformation($"Not starting Bezzad download because of low disk space {download.ToLog()} {torrent.ToLog()}");
|
logger.LogInformation($"Not starting Bezzad download because of low disk space {download.ToLog()} {torrent.ToLog()}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ActiveDownloadClients.ContainsKey(download.DownloadId))
|
if (runnerState.ActiveDownloadClients.ContainsKey(download.DownloadId))
|
||||||
{
|
{
|
||||||
Log($"Not starting download because this download is already active", download, torrent);
|
Log($"Not starting download because this download is already active", download, torrent);
|
||||||
|
|
||||||
|
|
@ -528,10 +534,24 @@ public class TorrentRunner(
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Cannot unrestrict link: {ex.Message}", ex.Message);
|
logger.LogError(ex, "Cannot unrestrict link: {ex.Message}", ex.Message);
|
||||||
|
|
||||||
await downloads.UpdateError(download.DownloadId, ex.Message);
|
if (download.RetryCount < torrent.DownloadRetryAttempts)
|
||||||
await downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
|
{
|
||||||
download.Error = ex.Message;
|
var retryCount = download.RetryCount + 1;
|
||||||
download.Completed = DateTimeOffset.UtcNow;
|
var retryDelay = GetDownloadLinkRetryDelay(retryCount);
|
||||||
|
var retryAt = DateTimeOffset.UtcNow.Add(retryDelay);
|
||||||
|
|
||||||
|
Log($"Retrying download link generation {retryCount}/{torrent.DownloadRetryAttempts} at {retryAt:u}", download, torrent);
|
||||||
|
|
||||||
|
await downloads.Reset(download.DownloadId, retryAt);
|
||||||
|
await downloads.UpdateRetryCount(download.DownloadId, retryCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await downloads.UpdateError(download.DownloadId, ex.Message);
|
||||||
|
await downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
|
||||||
|
download.Error = ex.Message;
|
||||||
|
download.Completed = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -553,7 +573,7 @@ public class TorrentRunner(
|
||||||
// Start the download process
|
// Start the download process
|
||||||
var downloadClient = new DownloadClient(download, torrent, downloadPath, torrent.Category);
|
var downloadClient = new DownloadClient(download, torrent, downloadPath, torrent.Category);
|
||||||
|
|
||||||
if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient))
|
if (runnerState.ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient))
|
||||||
{
|
{
|
||||||
Log($"Starting download", download, torrent);
|
Log($"Starting download", download, torrent);
|
||||||
|
|
||||||
|
|
@ -573,7 +593,7 @@ public class TorrentRunner(
|
||||||
await downloads.UpdateRemoteId(download.DownloadId, remoteId);
|
await downloads.UpdateRemoteId(download.DownloadId, remoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsPausedForLowDiskSpace && downloadClient.Type == Data.Enums.DownloadClient.Bezzad)
|
if (runnerState.IsPausedForLowDiskSpace && downloadClient.Type == Data.Enums.DownloadClient.Bezzad)
|
||||||
{
|
{
|
||||||
logger.LogInformation($"Pausing new Bezzad download due to low disk space {download.ToLog()} {torrent.ToLog()}");
|
logger.LogInformation($"Pausing new Bezzad download due to low disk space {download.ToLog()} {torrent.ToLog()}");
|
||||||
await downloadClient.Pause();
|
await downloadClient.Pause();
|
||||||
|
|
@ -633,14 +653,14 @@ public class TorrentRunner(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have reached the download limit, if so queue the download, but don't start it.
|
// Check if we have reached the download limit, if so queue the download, but don't start it.
|
||||||
if (ActiveUnpackClients.Count >= settingUnpackLimit)
|
if (runnerState.ActiveUnpackClients.Count >= settingUnpackLimit)
|
||||||
{
|
{
|
||||||
Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent);
|
Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ActiveUnpackClients.ContainsKey(download.DownloadId))
|
if (runnerState.ActiveUnpackClients.ContainsKey(download.DownloadId))
|
||||||
{
|
{
|
||||||
Log($"Not starting unpack because this download is already active", download, torrent);
|
Log($"Not starting unpack because this download is already active", download, torrent);
|
||||||
|
|
||||||
|
|
@ -662,7 +682,7 @@ public class TorrentRunner(
|
||||||
// Start the unpacking process
|
// Start the unpacking process
|
||||||
var unpackClient = new UnpackClient(download, downloadPath);
|
var unpackClient = new UnpackClient(download, downloadPath);
|
||||||
|
|
||||||
if (ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient))
|
if (runnerState.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient))
|
||||||
{
|
{
|
||||||
Log($"Starting unpack", download, torrent);
|
Log($"Starting unpack", download, torrent);
|
||||||
|
|
||||||
|
|
@ -720,8 +740,8 @@ public class TorrentRunner(
|
||||||
|
|
||||||
var completePerc = 0;
|
var completePerc = 0;
|
||||||
|
|
||||||
var totalDownloadBytes = torrent.Downloads.Sum(m => GetStats(m.DownloadId).BytesTotal);
|
var totalDownloadBytes = torrent.Downloads.Sum(m => runnerState.GetStats(m.DownloadId).BytesTotal);
|
||||||
var totalDoneBytes = torrent.Downloads.Sum(m => GetStats(m.DownloadId).BytesDone);
|
var totalDoneBytes = torrent.Downloads.Sum(m => runnerState.GetStats(m.DownloadId).BytesDone);
|
||||||
|
|
||||||
if (totalDownloadBytes > 0)
|
if (totalDownloadBytes > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -782,6 +802,19 @@ public class TorrentRunner(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TimeSpan GetDownloadLinkRetryDelay(Int32 retryCount)
|
||||||
|
{
|
||||||
|
var seconds = retryCount switch
|
||||||
|
{
|
||||||
|
<= 1 => 15,
|
||||||
|
2 => 30,
|
||||||
|
3 => 60,
|
||||||
|
_ => 120
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeSpan.FromSeconds(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
private void Log(String message, Download? download, Torrent? torrent)
|
private void Log(String message, Download? download, Torrent? torrent)
|
||||||
{
|
{
|
||||||
if (download != null)
|
if (download != null)
|
||||||
|
|
|
||||||
47
server/RdtClient.Service/Services/TorrentRunnerState.cs
Normal file
47
server/RdtClient.Service/Services/TorrentRunnerState.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
|
public interface ITorrentRunnerState
|
||||||
|
{
|
||||||
|
ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients { get; }
|
||||||
|
|
||||||
|
ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients { get; }
|
||||||
|
|
||||||
|
Boolean IsPausedForLowDiskSpace { get; set; }
|
||||||
|
|
||||||
|
(Int64 Speed, Int64 BytesTotal, Int64 BytesDone) GetStats(Guid downloadId);
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TorrentRunnerState : ITorrentRunnerState
|
||||||
|
{
|
||||||
|
public ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients { get; } = new();
|
||||||
|
|
||||||
|
public ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients { get; } = new();
|
||||||
|
|
||||||
|
public Boolean IsPausedForLowDiskSpace { get; set; }
|
||||||
|
|
||||||
|
public (Int64 Speed, Int64 BytesTotal, Int64 BytesDone) GetStats(Guid downloadId)
|
||||||
|
{
|
||||||
|
if (ActiveDownloadClients.TryGetValue(downloadId, out var downloadClient))
|
||||||
|
{
|
||||||
|
return (downloadClient.Speed, downloadClient.BytesTotal, downloadClient.BytesDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActiveUnpackClients.TryGetValue(downloadId, out var unpackClient))
|
||||||
|
{
|
||||||
|
return (0, 100, unpackClient.Progess);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
ActiveDownloadClients.Clear();
|
||||||
|
ActiveUnpackClients.Clear();
|
||||||
|
IsPausedForLowDiskSpace = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,9 @@ public class Torrents(
|
||||||
PremiumizeDebridClient premiumizeDebridClient,
|
PremiumizeDebridClient premiumizeDebridClient,
|
||||||
RealDebridDebridClient realDebridDebridClient,
|
RealDebridDebridClient realDebridDebridClient,
|
||||||
DebridLinkClient debridLinkClient,
|
DebridLinkClient debridLinkClient,
|
||||||
TorBoxDebridClient torBoxDebridClient)
|
TorBoxDebridClient torBoxDebridClient,
|
||||||
|
ISettings settings,
|
||||||
|
ITorrentRunnerState runnerState)
|
||||||
{
|
{
|
||||||
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
||||||
|
|
||||||
|
|
@ -47,7 +49,7 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Settings.Get.Provider.Provider switch
|
return settings.Current.Provider.Provider switch
|
||||||
{
|
{
|
||||||
Provider.Premiumize => premiumizeDebridClient,
|
Provider.Premiumize => premiumizeDebridClient,
|
||||||
Provider.RealDebrid => realDebridDebridClient,
|
Provider.RealDebrid => realDebridDebridClient,
|
||||||
|
|
@ -61,7 +63,7 @@ public class Torrents(
|
||||||
|
|
||||||
public virtual (Int64 Speed, Int64 BytesTotal, Int64 BytesDone) GetDownloadStats(Guid downloadId)
|
public virtual (Int64 Speed, Int64 BytesTotal, Int64 BytesDone) GetDownloadStats(Guid downloadId)
|
||||||
{
|
{
|
||||||
return TorrentRunner.GetStats(downloadId);
|
return runnerState.GetStats(downloadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<IList<Torrent>> Get()
|
public virtual async Task<IList<Torrent>> Get()
|
||||||
|
|
@ -197,9 +199,9 @@ public class Torrents(
|
||||||
throw new($"{ex.Message}, trying to parse {magnetLink}");
|
throw new($"{ex.Message}, trying to parse {magnetLink}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(Settings.Get.General.BannedTrackers))
|
if (!String.IsNullOrWhiteSpace(settings.Current.General.BannedTrackers))
|
||||||
{
|
{
|
||||||
var bannedTrackers = Settings.Get.General.BannedTrackers.Split(',');
|
var bannedTrackers = settings.Current.General.BannedTrackers.Split(',');
|
||||||
|
|
||||||
foreach (var bannedTracker in bannedTrackers)
|
foreach (var bannedTracker in bannedTrackers)
|
||||||
{
|
{
|
||||||
|
|
@ -264,9 +266,9 @@ public class Torrents(
|
||||||
throw new($"{ex.Message}, trying to parse {fileAsBase64}");
|
throw new($"{ex.Message}, trying to parse {fileAsBase64}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(Settings.Get.General.BannedTrackers))
|
if (!String.IsNullOrWhiteSpace(settings.Current.General.BannedTrackers))
|
||||||
{
|
{
|
||||||
var bannedTrackers = Settings.Get.General.BannedTrackers.Split(',');
|
var bannedTrackers = settings.Current.General.BannedTrackers.Split(',');
|
||||||
|
|
||||||
foreach (var bannedTracker in bannedTrackers)
|
foreach (var bannedTracker in bannedTrackers)
|
||||||
{
|
{
|
||||||
|
|
@ -312,16 +314,16 @@ public class Torrents(
|
||||||
|
|
||||||
private async Task CopyAddedTorrent(Torrent torrent)
|
private async Task CopyAddedTorrent(Torrent torrent)
|
||||||
{
|
{
|
||||||
if (String.IsNullOrWhiteSpace(Settings.Get.General.CopyAddedTorrents) || String.IsNullOrWhiteSpace(torrent.FileOrMagnet) || String.IsNullOrWhiteSpace(torrent.RdName))
|
if (String.IsNullOrWhiteSpace(settings.Current.General.CopyAddedTorrents) || String.IsNullOrWhiteSpace(torrent.FileOrMagnet) || String.IsNullOrWhiteSpace(torrent.RdName))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!fileSystem.Directory.Exists(Settings.Get.General.CopyAddedTorrents))
|
if (!fileSystem.Directory.Exists(settings.Current.General.CopyAddedTorrents))
|
||||||
{
|
{
|
||||||
fileSystem.Directory.CreateDirectory(Settings.Get.General.CopyAddedTorrents);
|
fileSystem.Directory.CreateDirectory(settings.Current.General.CopyAddedTorrents);
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = torrent.Type switch
|
var extension = torrent.Type switch
|
||||||
|
|
@ -331,7 +333,7 @@ public class Torrents(
|
||||||
_ => throw new ArgumentException("Unexpected DownloadType")
|
_ => throw new ArgumentException("Unexpected DownloadType")
|
||||||
};
|
};
|
||||||
|
|
||||||
var copyFileName = Path.Combine(Settings.Get.General.CopyAddedTorrents, FileHelper.RemoveInvalidFileNameChars(torrent.RdName));
|
var copyFileName = Path.Combine(settings.Current.General.CopyAddedTorrents, FileHelper.RemoveInvalidFileNameChars(torrent.RdName));
|
||||||
|
|
||||||
if (!copyFileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
|
if (!copyFileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -355,7 +357,7 @@ public class Torrents(
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, $"Unable to create torrent blackhole directory: {Settings.Get.General.CopyAddedTorrents}: {ex.Message}");
|
logger.LogError(ex, $"Unable to create torrent blackhole directory: {settings.Current.General.CopyAddedTorrents}: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -511,7 +513,7 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
var retry = 10;
|
var retry = 10;
|
||||||
|
|
||||||
while (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
while (runnerState.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||||
{
|
{
|
||||||
Log($"Cancelling download", download, torrent);
|
Log($"Cancelling download", download, torrent);
|
||||||
|
|
||||||
|
|
@ -529,7 +531,7 @@ public class Torrents(
|
||||||
|
|
||||||
retry = 10;
|
retry = 10;
|
||||||
|
|
||||||
while (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
while (runnerState.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||||
{
|
{
|
||||||
Log($"Cancelling unpack", download, torrent);
|
Log($"Cancelling unpack", download, torrent);
|
||||||
|
|
||||||
|
|
@ -569,7 +571,7 @@ public class Torrents(
|
||||||
|
|
||||||
if (deleteLocalFiles && !String.IsNullOrWhiteSpace(torrent.RdName))
|
if (deleteLocalFiles && !String.IsNullOrWhiteSpace(torrent.RdName))
|
||||||
{
|
{
|
||||||
var downloadPath = DownloadPath(torrent);
|
var downloadPath = DownloadPath(torrent, settings.Current);
|
||||||
downloadPath = Path.Combine(downloadPath, torrent.RdName);
|
downloadPath = Path.Combine(downloadPath, torrent.RdName);
|
||||||
|
|
||||||
Log($"Deleting local files in {downloadPath}", torrent);
|
Log($"Deleting local files in {downloadPath}", torrent);
|
||||||
|
|
@ -634,13 +636,13 @@ public class Torrents(
|
||||||
|
|
||||||
var profile = new Profile
|
var profile = new Profile
|
||||||
{
|
{
|
||||||
Provider = Enum.GetName(Settings.Get.Provider.Provider),
|
Provider = Enum.GetName(settings.Current.Provider.Provider),
|
||||||
UserName = user.Username,
|
UserName = user.Username,
|
||||||
Expiration = user.Expiration,
|
Expiration = user.Expiration,
|
||||||
CurrentVersion = UpdateChecker.CurrentVersion,
|
CurrentVersion = UpdateChecker.CurrentVersion,
|
||||||
LatestVersion = UpdateChecker.LatestVersion,
|
LatestVersion = UpdateChecker.LatestVersion,
|
||||||
IsInsecure = UpdateChecker.IsInsecure,
|
IsInsecure = UpdateChecker.IsInsecure,
|
||||||
DisableUpdateNotification = Settings.Get.General.DisableUpdateNotifications
|
DisableUpdateNotification = settings.Current.General.DisableUpdateNotifications
|
||||||
};
|
};
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
|
|
@ -662,26 +664,58 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
torrentsByRdId.TryGetValue(rdTorrent.Id, out var torrent);
|
torrentsByRdId.TryGetValue(rdTorrent.Id, out var torrent);
|
||||||
|
|
||||||
|
// TorBox migration from storing torrent hash in RdId to torrent ids.
|
||||||
|
if (torrent == null
|
||||||
|
&& Settings.Get.Provider.Provider == Provider.TorBox
|
||||||
|
&& rdTorrent.Type == DownloadType.Torrent
|
||||||
|
&& !String.IsNullOrWhiteSpace(rdTorrent.Hash)
|
||||||
|
&& !String.IsNullOrWhiteSpace(rdTorrent.Id))
|
||||||
|
{
|
||||||
|
torrent = torrents.FirstOrDefault(localTorrent => localTorrent is { Type: DownloadType.Torrent, ClientKind: null or Provider.TorBox }
|
||||||
|
&& !String.IsNullOrWhiteSpace(localTorrent.Hash)
|
||||||
|
&& !String.IsNullOrWhiteSpace(localTorrent.RdId)
|
||||||
|
&& localTorrent.RdId.Equals(localTorrent.Hash, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& localTorrent.Hash.Equals(rdTorrent.Hash, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (torrent != null)
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrWhiteSpace(torrent.RdId))
|
||||||
|
{
|
||||||
|
torrentsByRdId.Remove(torrent.RdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await torrentData.UpdateRdId(torrent, rdTorrent.Id);
|
||||||
|
torrent.RdId = rdTorrent.Id;
|
||||||
|
torrent.ClientKind = Provider.TorBox;
|
||||||
|
torrentsByRdId[rdTorrent.Id] = torrent;
|
||||||
|
|
||||||
|
logger.LogInformation("Migrated TorBox torrent RdId from hash to torrent id for {TorrentName} ({Hash}) -> {RdId}",
|
||||||
|
torrent.RdName ?? rdTorrent.Filename,
|
||||||
|
rdTorrent.Hash,
|
||||||
|
rdTorrent.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Auto import torrents only torrents that have their files selected
|
// Auto import torrents only torrents that have their files selected
|
||||||
if (torrent == null && Settings.Get.Provider.AutoImport)
|
if (torrent == null && settings.Current.Provider.AutoImport)
|
||||||
{
|
{
|
||||||
var newTorrent = new Torrent
|
var newTorrent = new Torrent
|
||||||
{
|
{
|
||||||
Category = Settings.Get.Provider.Default.Category,
|
Category = settings.Current.Provider.Default.Category,
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client,
|
||||||
DownloadAction =
|
DownloadAction =
|
||||||
Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
settings.Current.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
||||||
HostDownloadAction = Settings.Get.Provider.Default.HostDownloadAction,
|
HostDownloadAction = settings.Current.Provider.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Provider.Default.FinishedActionDelay,
|
FinishedActionDelay = settings.Current.Provider.Default.FinishedActionDelay,
|
||||||
FinishedAction = Settings.Get.Provider.Default.FinishedAction,
|
FinishedAction = settings.Current.Provider.Default.FinishedAction,
|
||||||
DownloadMinSize = Settings.Get.Provider.Default.MinFileSize,
|
DownloadMinSize = settings.Current.Provider.Default.MinFileSize,
|
||||||
IncludeRegex = Settings.Get.Provider.Default.IncludeRegex,
|
IncludeRegex = settings.Current.Provider.Default.IncludeRegex,
|
||||||
ExcludeRegex = Settings.Get.Provider.Default.ExcludeRegex,
|
ExcludeRegex = settings.Current.Provider.Default.ExcludeRegex,
|
||||||
TorrentRetryAttempts = Settings.Get.Provider.Default.TorrentRetryAttempts,
|
TorrentRetryAttempts = settings.Current.Provider.Default.TorrentRetryAttempts,
|
||||||
DownloadRetryAttempts = Settings.Get.Provider.Default.DownloadRetryAttempts,
|
DownloadRetryAttempts = settings.Current.Provider.Default.DownloadRetryAttempts,
|
||||||
DeleteOnError = Settings.Get.Provider.Default.DeleteOnError,
|
DeleteOnError = settings.Current.Provider.Default.DeleteOnError,
|
||||||
Lifetime = Settings.Get.Provider.Default.TorrentLifetime,
|
Lifetime = settings.Current.Provider.Default.TorrentLifetime,
|
||||||
Priority = Settings.Get.Provider.Default.Priority > 0 ? Settings.Get.Provider.Default.Priority : null,
|
Priority = settings.Current.Provider.Default.Priority > 0 ? settings.Current.Provider.Default.Priority : null,
|
||||||
RdId = rdTorrent.Id
|
RdId = rdTorrent.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -690,7 +724,7 @@ public class Torrents(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent = await torrentData.Add(rdTorrent.Id, rdTorrent.Hash, null, false, DownloadType.Torrent, Settings.Get.DownloadClient.Client, newTorrent);
|
torrent = await torrentData.Add(rdTorrent.Id, rdTorrent.Hash, null, false, DownloadType.Torrent, settings.Current.DownloadClient.Client, newTorrent);
|
||||||
torrentsByRdId[rdTorrent.Id] = torrent;
|
torrentsByRdId[rdTorrent.Id] = torrent;
|
||||||
torrents.Add(torrent);
|
torrents.Add(torrent);
|
||||||
|
|
||||||
|
|
@ -706,7 +740,7 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
var rdTorrent = torrent.RdId != null && providerTorrentsById.TryGetValue(torrent.RdId, out var providerTorrent) ? providerTorrent : null;
|
var rdTorrent = torrent.RdId != null && providerTorrentsById.TryGetValue(torrent.RdId, out var providerTorrent) ? providerTorrent : null;
|
||||||
|
|
||||||
if (rdTorrent == null && Settings.Get.Provider.AutoDelete && torrent.RdStatus != TorrentStatus.Queued)
|
if (rdTorrent == null && settings.Current.Provider.AutoDelete && torrent.RdStatus != TorrentStatus.Queued)
|
||||||
{
|
{
|
||||||
await Delete(torrent.TorrentId, true, false, true);
|
await Delete(torrent.TorrentId, true, false, true);
|
||||||
}
|
}
|
||||||
|
|
@ -744,14 +778,14 @@ public class Torrents(
|
||||||
|
|
||||||
foreach (var download in torrent.Downloads)
|
foreach (var download in torrent.Downloads)
|
||||||
{
|
{
|
||||||
while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient))
|
while (runnerState.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient))
|
||||||
{
|
{
|
||||||
await downloadClient.Cancel();
|
await downloadClient.Cancel();
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient))
|
while (runnerState.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient))
|
||||||
{
|
{
|
||||||
unpackClient.Cancel();
|
unpackClient.Cancel();
|
||||||
|
|
||||||
|
|
@ -814,21 +848,21 @@ public class Torrents(
|
||||||
|
|
||||||
Log($"Retrying Download", download, download.Torrent);
|
Log($"Retrying Download", download, download.Torrent);
|
||||||
|
|
||||||
while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient))
|
while (runnerState.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient))
|
||||||
{
|
{
|
||||||
await downloadClient.Cancel();
|
await downloadClient.Cancel();
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient))
|
while (runnerState.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient))
|
||||||
{
|
{
|
||||||
unpackClient.Cancel();
|
unpackClient.Cancel();
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadPath = DownloadPath(download.Torrent!);
|
var downloadPath = DownloadPath(download.Torrent!, settings.Current);
|
||||||
|
|
||||||
var filePath = DownloadHelper.GetDownloadPath(downloadPath, download.Torrent!, download);
|
var filePath = DownloadHelper.GetDownloadPath(downloadPath, download.Torrent!, download);
|
||||||
|
|
||||||
|
|
@ -929,9 +963,9 @@ public class Torrents(
|
||||||
return torrent;
|
return torrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String DownloadPath(Torrent torrent)
|
private static String DownloadPath(Torrent torrent, DbSettings settings)
|
||||||
{
|
{
|
||||||
var settingDownloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var settingDownloadPath = settings.DownloadClient.DownloadPath;
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(torrent.Category))
|
if (!String.IsNullOrWhiteSpace(torrent.Category))
|
||||||
{
|
{
|
||||||
|
|
@ -970,11 +1004,11 @@ public class Torrents(
|
||||||
await torrentData.Update(torrent);
|
await torrentData.Update(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunTorrentComplete(Guid torrentId, DbSettings? settings = null)
|
public async Task RunTorrentComplete(Guid torrentId, DbSettings? runSettings = null)
|
||||||
{
|
{
|
||||||
settings ??= Settings.Get;
|
runSettings ??= settings.Current;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(settings.General.RunOnTorrentCompleteFileName))
|
if (String.IsNullOrWhiteSpace(runSettings.General.RunOnTorrentCompleteFileName))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -983,12 +1017,12 @@ public class Torrents(
|
||||||
|
|
||||||
var downloadsForTorrent = await downloads.GetForTorrent(torrentId);
|
var downloadsForTorrent = await downloads.GetForTorrent(torrentId);
|
||||||
|
|
||||||
var fileName = settings.General.RunOnTorrentCompleteFileName;
|
var fileName = runSettings.General.RunOnTorrentCompleteFileName;
|
||||||
var arguments = settings.General.RunOnTorrentCompleteArguments ?? "";
|
var arguments = runSettings.General.RunOnTorrentCompleteArguments ?? "";
|
||||||
|
|
||||||
Log($"Parsing external program {fileName} with arguments {arguments}", torrent);
|
Log($"Parsing external program {fileName} with arguments {arguments}", torrent);
|
||||||
|
|
||||||
var downloadPath = DownloadPath(torrent);
|
var downloadPath = DownloadPath(torrent, runSettings);
|
||||||
var torrentPath = Path.Combine(downloadPath, torrent.RdName ?? "Unknown");
|
var torrentPath = Path.Combine(downloadPath, torrent.RdName ?? "Unknown");
|
||||||
|
|
||||||
var filePath = torrentPath;
|
var filePath = torrentPath;
|
||||||
|
|
@ -1083,7 +1117,7 @@ public class Torrents(
|
||||||
await torrentData.UpdateRdData(torrent);
|
await torrentData.UpdateRdData(torrent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
@ -1135,18 +1169,19 @@ public class Torrents(
|
||||||
torrent.RdFiles);
|
torrent.RdFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly record struct TorrentRdState(String? RdName,
|
private readonly record struct TorrentRdState(
|
||||||
Int64? RdSize,
|
String? RdName,
|
||||||
String? RdHost,
|
Int64? RdSize,
|
||||||
Int64? RdSplit,
|
String? RdHost,
|
||||||
Int64? RdProgress,
|
Int64? RdSplit,
|
||||||
TorrentStatus? RdStatus,
|
Int64? RdProgress,
|
||||||
String? RdStatusRaw,
|
TorrentStatus? RdStatus,
|
||||||
DateTimeOffset? RdAdded,
|
String? RdStatusRaw,
|
||||||
DateTimeOffset? RdEnded,
|
DateTimeOffset? RdAdded,
|
||||||
Int64? RdSpeed,
|
DateTimeOffset? RdEnded,
|
||||||
Int64? RdSeeders,
|
Int64? RdSpeed,
|
||||||
String? RdFiles);
|
Int64? RdSeeders,
|
||||||
|
String? RdFiles);
|
||||||
|
|
||||||
private void Log(String message, Download? download, Torrent? torrent)
|
private void Log(String message, Download? download, Torrent? torrent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,20 @@ public class QBittorrentControllerTest
|
||||||
{
|
{
|
||||||
private readonly QBittorrentController _controller;
|
private readonly QBittorrentController _controller;
|
||||||
private readonly Mock<QBittorrent> _qBittorrentMock;
|
private readonly Mock<QBittorrent> _qBittorrentMock;
|
||||||
|
private readonly Mock<Torrents> _torrentsMock;
|
||||||
|
private readonly TestSettings _settings;
|
||||||
|
|
||||||
public QBittorrentControllerTest()
|
public QBittorrentControllerTest()
|
||||||
{
|
{
|
||||||
_qBittorrentMock = new(new Mock<ILogger<QBittorrent>>().Object, null!, null!, null!, null!);
|
_settings = new();
|
||||||
|
_qBittorrentMock = new(new Mock<ILogger<QBittorrent>>().Object, _settings, null!, null!, null!, new TorrentRunnerState());
|
||||||
|
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
|
||||||
|
|
||||||
_controller = new(
|
_controller = new(new Mock<ILogger<QBittorrentController>>().Object,
|
||||||
new Mock<ILogger<QBittorrentController>>().Object,
|
_qBittorrentMock.Object,
|
||||||
_qBittorrentMock.Object,
|
new Mock<IHttpClientFactory>().Object,
|
||||||
new Mock<IHttpClientFactory>().Object);
|
_settings,
|
||||||
|
_torrentsMock.Object);
|
||||||
|
|
||||||
_controller.ControllerContext = new()
|
_controller.ControllerContext = new()
|
||||||
{
|
{
|
||||||
|
|
@ -32,15 +37,16 @@ public class QBittorrentControllerTest
|
||||||
public async Task TorrentsInfo_FilterAll_DoesNotFilterOutResults()
|
public async Task TorrentsInfo_FilterAll_DoesNotFilterOutResults()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_qBittorrentMock.Setup(q => q.TorrentInfo()).ReturnsAsync(new List<TorrentInfo>
|
_qBittorrentMock.Setup(q => q.TorrentInfo())
|
||||||
{
|
.ReturnsAsync(new List<TorrentInfo>
|
||||||
new()
|
{
|
||||||
{
|
new()
|
||||||
Hash = "hash1",
|
{
|
||||||
State = "pausedUP",
|
Hash = "hash1",
|
||||||
Progress = 1f
|
State = "pausedUP",
|
||||||
}
|
Progress = 1f
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _controller.TorrentsInfo(new()
|
var result = await _controller.TorrentsInfo(new()
|
||||||
|
|
@ -60,21 +66,22 @@ public class QBittorrentControllerTest
|
||||||
public async Task TorrentsInfo_FilterCompleted_MatchesPausedUploadTorrents()
|
public async Task TorrentsInfo_FilterCompleted_MatchesPausedUploadTorrents()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_qBittorrentMock.Setup(q => q.TorrentInfo()).ReturnsAsync(new List<TorrentInfo>
|
_qBittorrentMock.Setup(q => q.TorrentInfo())
|
||||||
{
|
.ReturnsAsync(new List<TorrentInfo>
|
||||||
new()
|
{
|
||||||
{
|
new()
|
||||||
Hash = "hash1",
|
{
|
||||||
State = "pausedUP",
|
Hash = "hash1",
|
||||||
Progress = 1f
|
State = "pausedUP",
|
||||||
},
|
Progress = 1f
|
||||||
new()
|
},
|
||||||
{
|
new()
|
||||||
Hash = "hash2",
|
{
|
||||||
State = "downloading",
|
Hash = "hash2",
|
||||||
Progress = 0.4f
|
State = "downloading",
|
||||||
}
|
Progress = 0.4f
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _controller.TorrentsInfo(new()
|
var result = await _controller.TorrentsInfo(new()
|
||||||
|
|
@ -88,4 +95,4 @@ public class QBittorrentControllerTest
|
||||||
Assert.Single(payload);
|
Assert.Single(payload);
|
||||||
Assert.Equal("hash1", payload[0].Hash);
|
Assert.Equal("hash1", payload[0].Hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using RdtClient.Data.Data;
|
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Sabnzbd;
|
using RdtClient.Data.Models.Sabnzbd;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
|
|
@ -17,15 +16,17 @@ public class SabnzbdControllerTest
|
||||||
private readonly Mock<Authentication> _authenticationMock;
|
private readonly Mock<Authentication> _authenticationMock;
|
||||||
private readonly SabnzbdController _controller;
|
private readonly SabnzbdController _controller;
|
||||||
private readonly Mock<Sabnzbd> _sabnzbdMock;
|
private readonly Mock<Sabnzbd> _sabnzbdMock;
|
||||||
|
private readonly TestSettings _settings;
|
||||||
|
|
||||||
public SabnzbdControllerTest()
|
public SabnzbdControllerTest()
|
||||||
{
|
{
|
||||||
SettingData.Get.General.AuthenticationType = AuthenticationType.None;
|
_settings = new();
|
||||||
SettingData.Get.Provider.ApiKey = "test-api-key";
|
_settings.Current.General.AuthenticationType = AuthenticationType.None;
|
||||||
|
_settings.Current.Provider.ApiKey = "test-api-key";
|
||||||
|
|
||||||
var torrentsMock = new Mock<Torrents>(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
|
var torrentsMock = new Mock<Torrents>(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
|
||||||
var sabnzbdLoggerMock = new Mock<ILogger<Sabnzbd>>();
|
var sabnzbdLoggerMock = new Mock<ILogger<Sabnzbd>>();
|
||||||
_sabnzbdMock = new(sabnzbdLoggerMock.Object, torrentsMock.Object, null!);
|
_sabnzbdMock = new(sabnzbdLoggerMock.Object, torrentsMock.Object, null!, _settings);
|
||||||
var loggerMock = new Mock<ILogger<SabnzbdController>>();
|
var loggerMock = new Mock<ILogger<SabnzbdController>>();
|
||||||
_authenticationMock = new(null!, null!, null!);
|
_authenticationMock = new(null!, null!, null!);
|
||||||
|
|
||||||
|
|
@ -64,7 +65,7 @@ public class SabnzbdControllerTest
|
||||||
public async Task GetQueue_Unauthorized_ReturnsOk_BecauseFilterIsSkippedInUnitTests()
|
public async Task GetQueue_Unauthorized_ReturnsOk_BecauseFilterIsSkippedInUnitTests()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
var queue = new SabnzbdQueue
|
var queue = new SabnzbdQueue
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +87,7 @@ public class SabnzbdControllerTest
|
||||||
public async Task GetQueue_WithMaAuth_ReturnsOk()
|
public async Task GetQueue_WithMaAuth_ReturnsOk()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
httpContext.Request.QueryString = new("?ma_username=user&ma_password=pass");
|
httpContext.Request.QueryString = new("?ma_username=user&ma_password=pass");
|
||||||
_controller.ControllerContext.HttpContext = httpContext;
|
_controller.ControllerContext.HttpContext = httpContext;
|
||||||
|
|
@ -107,6 +108,30 @@ public class SabnzbdControllerTest
|
||||||
Assert.IsType<OkObjectResult>(result);
|
Assert.IsType<OkObjectResult>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("?name=delete&value=hash1&del_files=1", true)]
|
||||||
|
[InlineData("?name=delete&value=hash1&del_files=true", true)]
|
||||||
|
[InlineData("?name=delete&value=hash1&del_files=0", false)]
|
||||||
|
[InlineData("?name=delete&value=hash1", false)]
|
||||||
|
public async Task Queue_Delete_PassesDeleteFilesFlag(String queryString, Boolean expectedDeleteFiles)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.QueryString = new(queryString);
|
||||||
|
_controller.ControllerContext.HttpContext = httpContext;
|
||||||
|
|
||||||
|
_sabnzbdMock.Setup(s => s.Delete("hash1", expectedDeleteFiles)).Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.Queue();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var response = Assert.IsType<SabnzbdResponse>(okResult.Value);
|
||||||
|
Assert.True(response.Status);
|
||||||
|
_sabnzbdMock.Verify(s => s.Delete("hash1", expectedDeleteFiles), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetHistory_ReturnsOk()
|
public async Task GetHistory_ReturnsOk()
|
||||||
{
|
{
|
||||||
|
|
@ -128,6 +153,26 @@ public class SabnzbdControllerTest
|
||||||
Assert.Equal(1, response.History.NoOfSlots);
|
Assert.Equal(1, response.History.NoOfSlots);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task History_Delete_PassesDeleteFilesFlag()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.QueryString = new("?name=delete&value=hash1&del_files=1");
|
||||||
|
_controller.ControllerContext.HttpContext = httpContext;
|
||||||
|
|
||||||
|
_sabnzbdMock.Setup(s => s.Delete("hash1", true)).Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.History();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var response = Assert.IsType<SabnzbdResponse>(okResult.Value);
|
||||||
|
Assert.True(response.Status);
|
||||||
|
_sabnzbdMock.Verify(s => s.Delete("hash1", true), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetVersion_HasAllowAnonymousAttribute()
|
public void GetVersion_HasAllowAnonymousAttribute()
|
||||||
{
|
{
|
||||||
|
|
@ -146,7 +191,7 @@ public class SabnzbdControllerTest
|
||||||
public void GetVersion_ReturnsOk()
|
public void GetVersion_ReturnsOk()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = _controller.Version();
|
var result = _controller.Version();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Moq;
|
using Moq;
|
||||||
using RdtClient.Data.Data;
|
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Service.Middleware;
|
using RdtClient.Service.Middleware;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
|
|
@ -15,20 +14,22 @@ public class SabnzbdHandlerTest
|
||||||
private readonly Mock<Authentication> _authenticationMock;
|
private readonly Mock<Authentication> _authenticationMock;
|
||||||
private readonly SabnzbdHandler _handler;
|
private readonly SabnzbdHandler _handler;
|
||||||
private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock;
|
private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock;
|
||||||
|
private readonly TestSettings _settings;
|
||||||
|
|
||||||
public SabnzbdHandlerTest()
|
public SabnzbdHandlerTest()
|
||||||
{
|
{
|
||||||
_authenticationMock = new(null!, null!, null!);
|
_authenticationMock = new(null!, null!, null!);
|
||||||
_httpContextAccessorMock = new();
|
_httpContextAccessorMock = new();
|
||||||
_handler = new(_authenticationMock.Object, _httpContextAccessorMock.Object);
|
_settings = new();
|
||||||
SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_handler = new(_authenticationMock.Object, _httpContextAccessorMock.Object, _settings);
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandleAsync_AuthNone_Succeeds()
|
public async Task HandleAsync_AuthNone_Succeeds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SettingData.Get.General.AuthenticationType = AuthenticationType.None;
|
_settings.Current.General.AuthenticationType = AuthenticationType.None;
|
||||||
var context = CreateContext();
|
var context = CreateContext();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -42,7 +43,8 @@ public class SabnzbdHandlerTest
|
||||||
public async Task HandleAsync_ValidCredentials_Succeeds()
|
public async Task HandleAsync_ValidCredentials_Succeeds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
Settings.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext
|
var httpContext = new DefaultHttpContext
|
||||||
{
|
{
|
||||||
Request =
|
Request =
|
||||||
|
|
@ -63,11 +65,119 @@ public class SabnzbdHandlerTest
|
||||||
Assert.True(context.HasSucceeded, "HasSucceeded should be true for valid credentials");
|
Assert.True(context.HasSucceeded, "HasSucceeded should be true for valid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleAsync_ValidApiKeyCredentials_Succeeds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
QueryString = new("?apikey=user:pass")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
var context = CreateContext(httpContext);
|
||||||
|
_authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(SignInResult.Success);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.HasSucceeded, "HasSucceeded should be true for valid api key credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleAsync_ValidApiKeyCredentialsFromForm_Succeeds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
|
||||||
|
|
||||||
|
httpContext.Request.Form = new FormCollection(new()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"apikey", "user:pass"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
var context = CreateContext(httpContext);
|
||||||
|
_authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(SignInResult.Success);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.HasSucceeded, "HasSucceeded should be true for valid form api key credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleAsync_ApiKeyPasswordContainingColon_Succeeds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
QueryString = new("?apikey=user:pass:with:colons")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
var context = CreateContext(httpContext);
|
||||||
|
_authenticationMock.Setup(a => a.Login("user", "pass:with:colons")).ReturnsAsync(SignInResult.Success);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.HasSucceeded, "HasSucceeded should be true when the password contains colons");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleAsync_MaCredentialsTakePrecedenceOverApiKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
QueryString = new("?ma_username=user&ma_password=wrong&apikey=user:pass")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
var context = CreateContext(httpContext);
|
||||||
|
_authenticationMock.Setup(a => a.Login("user", "wrong")).ReturnsAsync(SignInResult.Failed);
|
||||||
|
_authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(SignInResult.Success);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(context.HasSucceeded, "HasSucceeded should be false because ma_username/ma_password take precedence");
|
||||||
|
_authenticationMock.Verify(a => a.Login("user", "wrong"), Times.Once);
|
||||||
|
_authenticationMock.Verify(a => a.Login("user", "pass"), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandleAsync_AlreadyAuthenticated_Succeeds()
|
public async Task HandleAsync_AlreadyAuthenticated_Succeeds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
Settings.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity("TestAuth"));
|
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity("TestAuth"));
|
||||||
httpContext.User = claimsPrincipal;
|
httpContext.User = claimsPrincipal;
|
||||||
|
|
@ -86,7 +196,8 @@ public class SabnzbdHandlerTest
|
||||||
public async Task HandleAsync_InvalidCredentials_DoesNotSucceed()
|
public async Task HandleAsync_InvalidCredentials_DoesNotSucceed()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
Settings.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext
|
var httpContext = new DefaultHttpContext
|
||||||
{
|
{
|
||||||
Request =
|
Request =
|
||||||
|
|
@ -107,11 +218,66 @@ public class SabnzbdHandlerTest
|
||||||
Assert.False(context.HasSucceeded, "HasSucceeded should be false for invalid credentials");
|
Assert.False(context.HasSucceeded, "HasSucceeded should be false for invalid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleAsync_InvalidApiKeyCredentials_DoesNotSucceed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
QueryString = new("?apikey=user:wrong")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
var context = CreateContext(httpContext);
|
||||||
|
_authenticationMock.Setup(a => a.Login("user", "wrong")).ReturnsAsync(SignInResult.Failed);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(context.HasSucceeded, "HasSucceeded should be false for invalid api key credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("missingdelimiter")]
|
||||||
|
[InlineData(":pass")]
|
||||||
|
[InlineData("user:")]
|
||||||
|
public async Task HandleAsync_MalformedApiKey_DoesNotSucceed(String apiKey)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
QueryString = new($"?apikey={apiKey}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
var context = CreateContext(httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(context.HasSucceeded, "HasSucceeded should be false for malformed api keys");
|
||||||
|
_authenticationMock.Verify(a => a.Login(It.IsAny<String>(), It.IsAny<String>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandleAsync_MissingCredentials_DoesNotSucceed()
|
public async Task HandleAsync_MissingCredentials_DoesNotSucceed()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
Settings.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
_settings.Current.General.AuthenticationType = AuthenticationType.UserNamePassword;
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public class TorrentsControllerNzbTest
|
||||||
|
|
||||||
public TorrentsControllerNzbTest()
|
public TorrentsControllerNzbTest()
|
||||||
{
|
{
|
||||||
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
|
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, new TestSettings(), new TorrentRunnerState());
|
||||||
_loggerMock = new();
|
_loggerMock = new();
|
||||||
_coordinatorMock = new();
|
_coordinatorMock = new();
|
||||||
_controller = new(_loggerMock.Object, _torrentsMock.Object, null!, _coordinatorMock.Object);
|
_controller = new(_loggerMock.Object, _torrentsMock.Object, null!, _coordinatorMock.Object);
|
||||||
|
|
|
||||||
1
server/RdtClient.Web.Test/GlobalTestConfig.cs
Normal file
1
server/RdtClient.Web.Test/GlobalTestConfig.cs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||||
|
|
@ -5,12 +5,13 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
<AssemblyName>RdtClient.Web.Test</AssemblyName>
|
<AssemblyName>RdtClient.Web.Test</AssemblyName>
|
||||||
<RootNamespace>RdtClient.Web.Test</RootNamespace>
|
<RootNamespace>RdtClient.Web.Test</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="SharpCompress" Version="[0.42.1]" />
|
<PackageReference Include="SharpCompress" Version="[0.42.1]" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
|
|
||||||
59
server/RdtClient.Web.Test/TestSettings.cs
Normal file
59
server/RdtClient.Web.Test/TestSettings.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using RdtClient.Data.Models.Internal;
|
||||||
|
using RdtClient.Service.Services;
|
||||||
|
|
||||||
|
namespace RdtClient.Web.Test;
|
||||||
|
|
||||||
|
internal sealed class TestSettings(DbSettings? settings = null) : ISettings
|
||||||
|
{
|
||||||
|
public DbSettings Current { get; } = settings ?? new();
|
||||||
|
|
||||||
|
public String DefaultSavePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var downloadPath = Current.DownloadClient.MappedPath.TrimEnd('\\').TrimEnd('/');
|
||||||
|
|
||||||
|
return downloadPath + Path.DirectorySeparatorChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<SettingProperty> GetAll()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Update(IList<SettingProperty> settings)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Update(String settingId, Object? value)
|
||||||
|
{
|
||||||
|
SetValue(Current, settingId.Split(':'), 0, value);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetValue(Object target, IReadOnlyList<String> path, Int32 index, Object? value)
|
||||||
|
{
|
||||||
|
var property = target.GetType().GetProperty(path[index]) ?? throw new($"Unknown setting {String.Join(":", path)}");
|
||||||
|
|
||||||
|
if (index < path.Count - 1)
|
||||||
|
{
|
||||||
|
SetValue(property.GetValue(target)!, path, index + 1, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
property.SetValue(target, null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
|
||||||
|
var convertedValue = propertyType.IsEnum ? Enum.Parse(propertyType, value.ToString()!) : Convert.ChangeType(value, propertyType);
|
||||||
|
property.SetValue(target, convertedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,14 +6,14 @@ using RdtClient.Service.Services;
|
||||||
namespace RdtClient.Web.Controllers;
|
namespace RdtClient.Web.Controllers;
|
||||||
|
|
||||||
[Route("Api/Authentication")]
|
[Route("Api/Authentication")]
|
||||||
public class AuthController(Authentication authentication, Settings settings) : Controller
|
public class AuthController(Authentication authentication, ISettings settings) : Controller
|
||||||
{
|
{
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[Route("IsLoggedIn")]
|
[Route("IsLoggedIn")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult> IsLoggedIn()
|
public async Task<ActionResult> IsLoggedIn()
|
||||||
{
|
{
|
||||||
if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
|
if (settings.Current.General.AuthenticationType == AuthenticationType.None)
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ public class AuthController(Authentication authentication, Settings settings) :
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(Settings.Get.Provider.ApiKey))
|
if (!String.IsNullOrEmpty(settings.Current.Provider.ApiKey))
|
||||||
{
|
{
|
||||||
return StatusCode(401);
|
return StatusCode(401);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.QBittorrent;
|
using RdtClient.Data.Models.QBittorrent;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using RealDebridException = RDNET.RealDebridException;
|
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Web.Controllers;
|
namespace RdtClient.Web.Controllers;
|
||||||
|
|
||||||
|
|
@ -14,7 +14,8 @@ namespace RdtClient.Web.Controllers;
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/v2")]
|
[Route("api/v2")]
|
||||||
[Route("qbittorrent/api/v2")]
|
[Route("qbittorrent/api/v2")]
|
||||||
public class QBittorrentController(ILogger<QBittorrentController> logger, QBittorrent qBittorrent, IHttpClientFactory httpClientFactory) : Controller
|
public class QBittorrentController(ILogger<QBittorrentController> logger, QBittorrent qBittorrent, IHttpClientFactory httpClientFactory, ISettings settings, Torrents torrents)
|
||||||
|
: Controller
|
||||||
{
|
{
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[Route("/version/api")]
|
[Route("/version/api")]
|
||||||
|
|
@ -33,7 +34,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
{
|
{
|
||||||
logger.LogDebug($"Auth login");
|
logger.LogDebug($"Auth login");
|
||||||
|
|
||||||
if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
|
if (settings.Current.General.AuthenticationType == AuthenticationType.None)
|
||||||
{
|
{
|
||||||
return Content("Ok.", "text/plain");
|
return Content("Ok.", "text/plain");
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +147,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public ActionResult<AppPreferences> AppDefaultSavePath()
|
public ActionResult<AppPreferences> AppDefaultSavePath()
|
||||||
{
|
{
|
||||||
var result = Settings.AppDefaultSavePath;
|
var result = settings.DefaultSavePath;
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
@ -368,30 +369,28 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
|
|
||||||
foreach (var url in urls)
|
foreach (var url in urls)
|
||||||
{
|
{
|
||||||
try
|
Torrent? torrent;
|
||||||
|
|
||||||
|
if (url.StartsWith("magnet"))
|
||||||
{
|
{
|
||||||
if (url.StartsWith("magnet"))
|
torrent = await qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
|
||||||
{
|
|
||||||
await qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
|
|
||||||
}
|
|
||||||
else if (url.StartsWith("http"))
|
|
||||||
{
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
|
||||||
var result = await httpClient.GetByteArrayAsync(url);
|
|
||||||
await qBittorrent.TorrentsAddFile(result, request.Category, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return BadRequest($"Invalid torrent link format {url}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (RealDebridException ex)
|
else if (url.StartsWith("http"))
|
||||||
{
|
{
|
||||||
// Infringing file.
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
if (ex.ErrorCode == 35)
|
var result = await httpClient.GetByteArrayAsync(url);
|
||||||
{
|
torrent = await qBittorrent.TorrentsAddFile(result, request.Category, null);
|
||||||
return Ok("Fails.");
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
return BadRequest($"Invalid torrent link format {url}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var addResult = await WaitForTorrent(torrent.TorrentId);
|
||||||
|
|
||||||
|
if (!addResult)
|
||||||
|
{
|
||||||
|
return Ok("Fails.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,7 +411,14 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
await file.CopyToAsync(target);
|
await file.CopyToAsync(target);
|
||||||
var fileBytes = target.ToArray();
|
var fileBytes = target.ToArray();
|
||||||
|
|
||||||
await qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
|
var torrent = await qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
|
||||||
|
|
||||||
|
var addResult = await WaitForTorrent(torrent.TorrentId);
|
||||||
|
|
||||||
|
if (!addResult)
|
||||||
|
{
|
||||||
|
return Ok("Fails.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,11 +441,11 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileIds = request.Id
|
var fileIds = request.Id
|
||||||
.Split('|', StringSplitOptions.RemoveEmptyEntries)
|
.Split('|', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(value => Int32.TryParse(value, out var parsedValue) ? parsedValue : (Int32?)null)
|
.Select(value => Int32.TryParse(value, out var parsedValue) ? parsedValue : (Int32?)null)
|
||||||
.Where(value => value.HasValue)
|
.Where(value => value.HasValue)
|
||||||
.Select(value => value!.Value)
|
.Select(value => value!.Value)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (fileIds.Count == 0)
|
if (fileIds.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -613,7 +619,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult TransferInfo()
|
public ActionResult TransferInfo()
|
||||||
{
|
{
|
||||||
return Ok(QBittorrent.TransferInfo());
|
return Ok(qBittorrent.TransferInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
|
|
@ -671,6 +677,31 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
_ => String.Equals(torrent.State, filter, StringComparison.OrdinalIgnoreCase)
|
_ => String.Equals(torrent.State, filter, StringComparison.OrdinalIgnoreCase)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Boolean> WaitForTorrent(Guid torrentId)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var torrent = await torrents.GetById(torrentId);
|
||||||
|
|
||||||
|
if (torrent == null)
|
||||||
|
{
|
||||||
|
throw new($"Failed to add torrent: Not Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.RdStatus == TorrentStatus.Error || torrent.Error != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.RdStatus != TorrentStatus.Queued)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QBAuthLoginRequest
|
public class QBAuthLoginRequest
|
||||||
|
|
@ -686,7 +717,6 @@ public class QBTorrentsInfoRequest
|
||||||
public String? Hashes { get; set; }
|
public String? Hashes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class QBTorrentsCountRequest
|
public class QBTorrentsCountRequest
|
||||||
{
|
{
|
||||||
public String? Filter { get; set; }
|
public String? Filter { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -56,22 +56,7 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
||||||
|
|
||||||
if (name == "delete")
|
if (name == "delete")
|
||||||
{
|
{
|
||||||
var value = GetParam("value");
|
return await DeleteDownloads();
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
return BadRequest(new SabnzbdResponse
|
|
||||||
{
|
|
||||||
Error = "No value specified for delete operation"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await sabnzbd.Delete(value ?? "");
|
|
||||||
|
|
||||||
return Ok(new SabnzbdResponse
|
|
||||||
{
|
|
||||||
Status = true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new SabnzbdResponse
|
return Ok(new SabnzbdResponse
|
||||||
|
|
@ -85,6 +70,12 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
||||||
public async Task<ActionResult> History()
|
public async Task<ActionResult> History()
|
||||||
{
|
{
|
||||||
logger.LogDebug("Sabnzbd mode: history");
|
logger.LogDebug("Sabnzbd mode: history");
|
||||||
|
var name = GetParam("name");
|
||||||
|
|
||||||
|
if (name == "delete")
|
||||||
|
{
|
||||||
|
return await DeleteDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(new SabnzbdResponse
|
return Ok(new SabnzbdResponse
|
||||||
{
|
{
|
||||||
|
|
@ -183,6 +174,31 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<ActionResult> DeleteDownloads()
|
||||||
|
{
|
||||||
|
var value = GetParam("value");
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return BadRequest(new SabnzbdResponse
|
||||||
|
{
|
||||||
|
Error = "No value specified for delete operation"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteFiles = IsTrue(GetParam("del_files"));
|
||||||
|
|
||||||
|
foreach (var hash in value.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||||
|
{
|
||||||
|
await sabnzbd.Delete(hash, deleteFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new SabnzbdResponse
|
||||||
|
{
|
||||||
|
Status = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private String? GetParam(String name)
|
private String? GetParam(String name)
|
||||||
{
|
{
|
||||||
var value = Request.Query[name].ToString();
|
var value = Request.Query[name].ToString();
|
||||||
|
|
@ -194,4 +210,9 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Boolean IsTrue(String? value)
|
||||||
|
{
|
||||||
|
return value is "1" || Boolean.TryParse(value, out var result) && result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Reflection;
|
||||||
using Aria2NET;
|
using Aria2NET;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using RdtClient.Data.Data;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.Internal;
|
using RdtClient.Data.Models.Internal;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
|
|
@ -15,13 +14,13 @@ namespace RdtClient.Web.Controllers;
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
[Route("Api/Settings")]
|
[Route("Api/Settings")]
|
||||||
public class SettingsController(Settings settings, Torrents torrents) : Controller
|
public class SettingsController(ISettings settings, Torrents torrents) : Controller
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("")]
|
[Route("")]
|
||||||
public ActionResult Get()
|
public ActionResult Get()
|
||||||
{
|
{
|
||||||
var result = SettingData.GetAll();
|
var result = settings.GetAll();
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +94,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
[Route("TestDownloadSpeed")]
|
[Route("TestDownloadSpeed")]
|
||||||
public async Task<ActionResult> TestDownloadSpeed(CancellationToken cancellationToken)
|
public async Task<ActionResult> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var downloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var downloadPath = settings.Current.DownloadClient.DownloadPath;
|
||||||
|
|
||||||
var testFilePath = Path.Combine(downloadPath, "testDefault.rar");
|
var testFilePath = Path.Combine(downloadPath, "testDefault.rar");
|
||||||
|
|
||||||
|
|
@ -106,7 +105,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar",
|
Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar",
|
||||||
Torrent = new()
|
Torrent = new()
|
||||||
{
|
{
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client == DownloadClient.Symlink ? DownloadClient.Bezzad : Settings.Get.DownloadClient.Client,
|
DownloadClient = settings.Current.DownloadClient.Client == DownloadClient.Symlink ? DownloadClient.Bezzad : settings.Current.DownloadClient.Client,
|
||||||
RdName = "testDefault.rar"
|
RdName = "testDefault.rar"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -131,7 +130,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
|
|
||||||
if (downloadClient.Downloader is Aria2cDownloader aria2Downloader)
|
if (downloadClient.Downloader is Aria2cDownloader aria2Downloader)
|
||||||
{
|
{
|
||||||
var aria2NetClient = new Aria2NetClient(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 1);
|
var aria2NetClient = new Aria2NetClient(settings.Current.DownloadClient.Aria2cUrl, settings.Current.DownloadClient.Aria2cSecret, httpClient, 1);
|
||||||
|
|
||||||
var allDownloads = await aria2NetClient.TellAllAsync(cancellationToken);
|
var allDownloads = await aria2NetClient.TellAllAsync(cancellationToken);
|
||||||
|
|
||||||
|
|
@ -155,7 +154,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
[Route("TestWriteSpeed")]
|
[Route("TestWriteSpeed")]
|
||||||
public async Task<ActionResult> TestWriteSpeed()
|
public async Task<ActionResult> TestWriteSpeed()
|
||||||
{
|
{
|
||||||
var downloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var downloadPath = settings.Current.DownloadClient.DownloadPath;
|
||||||
|
|
||||||
var testFilePath = Path.Combine(downloadPath, "test.tmp");
|
var testFilePath = Path.Combine(downloadPath, "test.tmp");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue