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
|
||||
|
||||
## [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.
|
||||
|
|
|
|||
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
|
||||
- (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. 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.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<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.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.runner.visualstudio" Version="3.1.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
|||
|
|
@ -366,64 +366,6 @@ public class SabnzbdTest
|
|||
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]
|
||||
public void GetConfig_ShouldReturnCorrectConfig()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ public class TorBoxDebridClientTest
|
|||
{
|
||||
new()
|
||||
{
|
||||
Id = 12345,
|
||||
Hash = "hash1",
|
||||
Name = "torrent1",
|
||||
Size = 1000,
|
||||
|
|
@ -79,7 +78,7 @@ public class TorBoxDebridClientTest
|
|||
// Assert
|
||||
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.Equal(DownloadType.Torrent, torrentResult.Type);
|
||||
|
||||
|
|
@ -201,12 +200,12 @@ public class TorBoxDebridClientTest
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_CallsTorrentsControlById_WhenTypeIsTorrent()
|
||||
public async Task Delete_CallsTorrentsControl_WhenTypeIsTorrent()
|
||||
{
|
||||
// Arrange
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "12345",
|
||||
RdId = "torrent-id",
|
||||
Type = DownloadType.Torrent
|
||||
};
|
||||
|
||||
|
|
@ -221,7 +220,7 @@ public class TorBoxDebridClientTest
|
|||
await clientMock.Object.Delete(torrent);
|
||||
|
||||
// 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]
|
||||
|
|
@ -376,7 +375,6 @@ public class TorBoxDebridClientTest
|
|||
var torrent = new Torrent
|
||||
{
|
||||
Hash = "test-hash",
|
||||
RdId = "12345",
|
||||
RdFiles = JsonConvert.SerializeObject(files)
|
||||
};
|
||||
|
||||
|
|
@ -387,6 +385,12 @@ public class TorBoxDebridClientTest
|
|||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.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);
|
||||
|
||||
_settings.Current.Provider.PreferZippedDownloads = false;
|
||||
|
|
@ -418,7 +422,6 @@ public class TorBoxDebridClientTest
|
|||
var torrent = new Torrent
|
||||
{
|
||||
Hash = "test-hash",
|
||||
RdId = "12345",
|
||||
RdName = "TestTorrent",
|
||||
RdFiles = JsonConvert.SerializeObject(files),
|
||||
DownloadClient = DownloadClient.Aria2c
|
||||
|
|
@ -433,6 +436,12 @@ public class TorBoxDebridClientTest
|
|||
torBoxClientMock.Setup(m => m.Torrents).Returns(torrentsApiMock.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);
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<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.Wrappers" Version="22.1.1" />
|
||||
<PackageReference Include="TorBox.NET" Version="2.1.0" />
|
||||
<PackageReference Include="TorBox.NET" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class TorBoxDebridClient(
|
|||
allowZip: Settings.Get.Provider.PreferZippedDownloads,
|
||||
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,
|
||||
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
|
||||
{
|
||||
await GetClient().Torrents.ControlByIdAsync(Int32.Parse(torrent.RdId), "delete");
|
||||
await GetClient().Torrents.ControlAsync(torrent.RdId, "delete");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -331,12 +331,8 @@ public class TorBoxDebridClient(
|
|||
}
|
||||
else
|
||||
{
|
||||
if (torrent.RdId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
id = Int32.Parse(torrent.RdId);
|
||||
var torrentId = await HandleErrors(() => GetClient().Torrents.GetHashInfoAsync(torrent.Hash, true));
|
||||
id = torrentId?.Id;
|
||||
}
|
||||
|
||||
if (id == null)
|
||||
|
|
@ -460,7 +456,7 @@ public class TorBoxDebridClient(
|
|||
{
|
||||
return new()
|
||||
{
|
||||
Id = torrent.Id.ToString(),
|
||||
Id = torrent.Hash,
|
||||
Filename = torrent.Name,
|
||||
OriginalFilename = torrent.Name,
|
||||
Hash = torrent.Hash,
|
||||
|
|
@ -615,7 +611,7 @@ public class TorBoxDebridClient(
|
|||
}
|
||||
else
|
||||
{
|
||||
var result = await GetClient().Torrents.GetIdInfoAsync(Int32.Parse(id), true);
|
||||
var result = await GetClient().Torrents.GetHashInfoAsync(id, true);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -359,11 +359,6 @@ public class QBittorrent(ILogger<QBittorrent> logger, ISettings settings, Authen
|
|||
var selectedFileName = topLevelSelectedFiles[0]!;
|
||||
var selectedFileBaseName = Path.GetFileNameWithoutExtension(selectedFileName);
|
||||
|
||||
if (torrent.ClientKind == Provider.TorBox)
|
||||
{
|
||||
return selectedFileBaseName;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(selectedFileBaseName) &&
|
||||
selectedFileBaseName.Equals(torrent.RdName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
|||
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);
|
||||
|
||||
|
|
@ -189,18 +189,18 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
|
|||
switch (settings.Current.Integrations.Default.FinishedAction)
|
||||
{
|
||||
case TorrentFinishedAction.RemoveAllTorrents:
|
||||
logger.LogDebug("Removing nzb from debrid provider and RDT-Client, {Files}", deleteFiles ? "with files" : "no files");
|
||||
await torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
|
||||
logger.LogDebug("Removing nzb from debrid provider and RDT-Client, no files");
|
||||
await torrents.Delete(torrent.TorrentId, true, true, true);
|
||||
|
||||
break;
|
||||
case TorrentFinishedAction.RemoveRealDebrid:
|
||||
logger.LogDebug("Removing nzb from debrid provider, {Files}", deleteFiles ? "with files" : "no files");
|
||||
await torrents.Delete(torrent.TorrentId, false, true, deleteFiles);
|
||||
logger.LogDebug("Removing nzb from debrid provider, no files");
|
||||
await torrents.Delete(torrent.TorrentId, false, true, true);
|
||||
|
||||
break;
|
||||
case TorrentFinishedAction.RemoveClient:
|
||||
logger.LogDebug("Removing nzb from client, {Files}", deleteFiles ? "with files" : "no files");
|
||||
await torrents.Delete(torrent.TorrentId, true, false, deleteFiles);
|
||||
logger.LogDebug("Removing nzb from client, no files");
|
||||
await torrents.Delete(torrent.TorrentId, true, false, true);
|
||||
|
||||
break;
|
||||
case TorrentFinishedAction.None:
|
||||
|
|
|
|||
|
|
@ -41,11 +41,6 @@ public class Settings(IServiceScopeFactory serviceScopeFactory) : ISettings
|
|||
{
|
||||
var downloadPath = settings.DownloadClient.MappedPath;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(downloadPath))
|
||||
{
|
||||
downloadPath = settings.DownloadClient.DownloadPath;
|
||||
}
|
||||
|
||||
downloadPath = downloadPath.TrimEnd('\\')
|
||||
.TrimEnd('/');
|
||||
|
||||
|
|
|
|||
|
|
@ -664,38 +664,6 @@ public class Torrents(
|
|||
{
|
||||
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
|
||||
if (torrent == null && settings.Current.Provider.AutoImport)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -108,30 +108,6 @@ public class SabnzbdControllerTest
|
|||
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]
|
||||
public async Task GetHistory_ReturnsOk()
|
||||
{
|
||||
|
|
@ -153,26 +129,6 @@ public class SabnzbdControllerTest
|
|||
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]
|
||||
public void GetVersion_HasAllowAnonymousAttribute()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,7 +56,22 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
|||
|
||||
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
|
||||
|
|
@ -70,12 +85,6 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
|||
public async Task<ActionResult> History()
|
||||
{
|
||||
logger.LogDebug("Sabnzbd mode: history");
|
||||
var name = GetParam("name");
|
||||
|
||||
if (name == "delete")
|
||||
{
|
||||
return await DeleteDownloads();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var value = Request.Query[name].ToString();
|
||||
|
|
@ -210,9 +194,4 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
|
|||
|
||||
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