Compare commits
No commits in common. "main" and "v2.0.135" have entirely different histories.
17 changed files with 48 additions and 328 deletions
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#!/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
|
|
||||||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -6,21 +6,6 @@ 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
|
## [2.0.135] - 2026-05-27
|
||||||
### Changed
|
### 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.
|
- 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.
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -218,17 +218,6 @@ 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.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<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="2.1.0" />
|
<PackageReference Include="TorBox.NET" Version="2.0.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>
|
||||||
|
|
|
||||||
|
|
@ -366,64 +366,6 @@ 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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ public class TorBoxDebridClientTest
|
||||||
{
|
{
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = 12345,
|
|
||||||
Hash = "hash1",
|
Hash = "hash1",
|
||||||
Name = "torrent1",
|
Name = "torrent1",
|
||||||
Size = 1000,
|
Size = 1000,
|
||||||
|
|
@ -79,7 +78,7 @@ public class TorBoxDebridClientTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(2, result.Count);
|
Assert.Equal(2, result.Count);
|
||||||
|
|
||||||
var torrentResult = result.FirstOrDefault(r => r.Id == "12345");
|
var torrentResult = result.FirstOrDefault(r => r.Id == "hash1");
|
||||||
Assert.NotNull(torrentResult);
|
Assert.NotNull(torrentResult);
|
||||||
Assert.Equal(DownloadType.Torrent, torrentResult.Type);
|
Assert.Equal(DownloadType.Torrent, torrentResult.Type);
|
||||||
|
|
||||||
|
|
@ -201,12 +200,12 @@ public class TorBoxDebridClientTest
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Delete_CallsTorrentsControlById_WhenTypeIsTorrent()
|
public async Task Delete_CallsTorrentsControl_WhenTypeIsTorrent()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
RdId = "12345",
|
RdId = "torrent-id",
|
||||||
Type = DownloadType.Torrent
|
Type = DownloadType.Torrent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -221,7 +220,7 @@ public class TorBoxDebridClientTest
|
||||||
await clientMock.Object.Delete(torrent);
|
await clientMock.Object.Delete(torrent);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
torrentsApiMock.Verify(m => m.ControlByIdAsync(12345, "delete", It.IsAny<CancellationToken>()), Times.Once);
|
torrentsApiMock.Verify(m => m.ControlAsync("torrent-id", "delete", It.IsAny<CancellationToken>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -376,7 +375,6 @@ 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)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -387,6 +385,12 @@ public class TorBoxDebridClientTest
|
||||||
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, 1000, 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.Current.Provider.PreferZippedDownloads = false;
|
_settings.Current.Provider.PreferZippedDownloads = false;
|
||||||
|
|
@ -418,7 +422,6 @@ 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
|
||||||
|
|
@ -433,6 +436,12 @@ public class TorBoxDebridClientTest
|
||||||
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, 1000, 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
|
||||||
|
|
|
||||||
|
|
@ -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="2.1.0" />
|
<PackageReference Include="TorBox.NET" Version="2.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ public class TorBoxDebridClient(
|
||||||
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
||||||
as_queued: asQueued);
|
as_queued: asQueued);
|
||||||
|
|
||||||
return result.Data?.TorrentId?.ToString() ?? throw new InvalidOperationException("TorBox API did not return torrent ID.");
|
return result.Data!.Hash!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class TorBoxDebridClient(
|
||||||
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
||||||
as_queued: asQueued);
|
as_queued: asQueued);
|
||||||
|
|
||||||
return result.Data?.TorrentId?.ToString() ?? throw new InvalidOperationException("TorBox API did not return torrent ID");
|
return result.Data!.Hash!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ public class TorBoxDebridClient(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await GetClient().Torrents.ControlByIdAsync(Int32.Parse(torrent.RdId), "delete");
|
await GetClient().Torrents.ControlAsync(torrent.RdId, "delete");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -331,12 +331,8 @@ public class TorBoxDebridClient(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (torrent.RdId == null)
|
var torrentId = await HandleErrors(() => GetClient().Torrents.GetHashInfoAsync(torrent.Hash, true));
|
||||||
{
|
id = torrentId?.Id;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
id = Int32.Parse(torrent.RdId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == null)
|
if (id == null)
|
||||||
|
|
@ -460,7 +456,7 @@ public class TorBoxDebridClient(
|
||||||
{
|
{
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Id = torrent.Id.ToString(),
|
Id = torrent.Hash,
|
||||||
Filename = torrent.Name,
|
Filename = torrent.Name,
|
||||||
OriginalFilename = torrent.Name,
|
OriginalFilename = torrent.Name,
|
||||||
Hash = torrent.Hash,
|
Hash = torrent.Hash,
|
||||||
|
|
@ -615,7 +611,7 @@ public class TorBoxDebridClient(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var result = await GetClient().Torrents.GetIdInfoAsync(Int32.Parse(id), true);
|
var result = await GetClient().Torrents.GetHashInfoAsync(id, true);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -359,11 +359,6 @@ public class QBittorrent(ILogger<QBittorrent> logger, ISettings settings, Authen
|
||||||
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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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, Boolean deleteFiles = false)
|
public virtual async Task Delete(String hash)
|
||||||
{
|
{
|
||||||
var torrent = await torrents.GetByHash(hash);
|
var torrent = await torrents.GetByHash(hash);
|
||||||
|
|
||||||
|
|
@ -189,18 +189,18 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
||||||
switch (settings.Current.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, {Files}", deleteFiles ? "with files" : "no files");
|
logger.LogDebug("Removing nzb from debrid provider and RDT-Client, no files");
|
||||||
await torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
|
await torrents.Delete(torrent.TorrentId, true, true, true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TorrentFinishedAction.RemoveRealDebrid:
|
case TorrentFinishedAction.RemoveRealDebrid:
|
||||||
logger.LogDebug("Removing nzb from debrid provider, {Files}", deleteFiles ? "with files" : "no files");
|
logger.LogDebug("Removing nzb from debrid provider, no files");
|
||||||
await torrents.Delete(torrent.TorrentId, false, true, deleteFiles);
|
await torrents.Delete(torrent.TorrentId, false, true, true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TorrentFinishedAction.RemoveClient:
|
case TorrentFinishedAction.RemoveClient:
|
||||||
logger.LogDebug("Removing nzb from client, {Files}", deleteFiles ? "with files" : "no files");
|
logger.LogDebug("Removing nzb from client, no files");
|
||||||
await torrents.Delete(torrent.TorrentId, true, false, deleteFiles);
|
await torrents.Delete(torrent.TorrentId, true, false, true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TorrentFinishedAction.None:
|
case TorrentFinishedAction.None:
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,6 @@ public class Settings(IServiceScopeFactory serviceScopeFactory) : ISettings
|
||||||
{
|
{
|
||||||
var downloadPath = settings.DownloadClient.MappedPath;
|
var downloadPath = settings.DownloadClient.MappedPath;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(downloadPath))
|
|
||||||
{
|
|
||||||
downloadPath = settings.DownloadClient.DownloadPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadPath = downloadPath.TrimEnd('\\')
|
downloadPath = downloadPath.TrimEnd('\\')
|
||||||
.TrimEnd('/');
|
.TrimEnd('/');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -664,38 +664,6 @@ 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.Current.Provider.AutoImport)
|
if (torrent == null && settings.Current.Provider.AutoImport)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -108,30 +108,6 @@ 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()
|
||||||
{
|
{
|
||||||
|
|
@ -153,26 +129,6 @@ 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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,22 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
||||||
|
|
||||||
if (name == "delete")
|
if (name == "delete")
|
||||||
{
|
{
|
||||||
return await DeleteDownloads();
|
var value = GetParam("value");
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -70,12 +85,6 @@ 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
|
||||||
{
|
{
|
||||||
|
|
@ -174,31 +183,6 @@ 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();
|
||||||
|
|
@ -210,9 +194,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue