This commit is contained in:
Roger Far 2026-02-23 20:04:53 -07:00
parent 81dd3dd9f8
commit 41f816ab80
49 changed files with 1416 additions and 952 deletions

View file

@ -9,7 +9,7 @@ namespace RdtClient.Data.Data;
public class SettingData(DataContext dataContext, ILogger<SettingData> logger) public class SettingData(DataContext dataContext, ILogger<SettingData> logger)
{ {
public static DbSettings Get { get; private set; } = new(); public static DbSettings Get { get; } = new();
public static IList<SettingProperty> GetAll() public static IList<SettingProperty> GetAll()
{ {

View file

@ -5,4 +5,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Code style", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Web")] [assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Code style", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Data")]

View file

@ -1,6 +1,6 @@
using RdtClient.Data.Enums; using System.ComponentModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using RdtClient.Data.Enums;
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global

View file

@ -1,22 +1,23 @@
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.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;
using System.Reflection;
namespace RdtClient.Service.Test.BackgroundServices; namespace RdtClient.Service.Test.BackgroundServices;
public class WatchFolderCheckerTests : IDisposable public class WatchFolderCheckerTests : IDisposable
{ {
private readonly Mock<ILogger<WatchFolderChecker>> _loggerMock; private readonly Mock<ILogger<WatchFolderChecker>> _loggerMock;
private readonly Mock<IServiceProvider> _scopeServiceProviderMock;
private readonly Mock<IServiceProvider> _serviceProviderMock; private readonly Mock<IServiceProvider> _serviceProviderMock;
private readonly Mock<IServiceScope> _serviceScopeMock; private readonly Mock<IServiceScope> _serviceScopeMock;
private readonly Mock<IServiceProvider> _scopeServiceProviderMock;
private readonly Mock<Torrents> _torrentsServiceMock;
private readonly String _testPath; private readonly String _testPath;
private readonly Mock<Torrents> _torrentsServiceMock;
public WatchFolderCheckerTests() public WatchFolderCheckerTests()
{ {
@ -63,7 +64,7 @@ public class WatchFolderCheckerTests : IDisposable
private static void ResetSettings() private static void ResetSettings()
{ {
var settings = new Data.Models.Internal.DbSettings var settings = new DbSettings
{ {
Watch = new() Watch = new()
{ {
@ -104,12 +105,19 @@ public class WatchFolderCheckerTests : IDisposable
var task = checker.StartAsync(cts.Token); var task = checker.StartAsync(cts.Token);
await Task.Delay(500); // Give it some time to process await Task.Delay(500); // Give it some time to process
await cts.CancelAsync(); await cts.CancelAsync();
try { await task; } catch (OperationCanceledException) { }
try
{
await task;
}
catch (OperationCanceledException)
{
}
// Assert // Assert
_torrentsServiceMock.Verify(x => x.AddFileToDebridQueue( _torrentsServiceMock.Verify(x => x.AddFileToDebridQueue(It.Is<Byte[]>(b => b.AsEnumerable().SequenceEqual(content)),
It.Is<Byte[]>(b => b.SequenceEqual(content)), It.IsAny<Torrent>()),
It.IsAny<Torrent>()), Times.AtLeastOnce); Times.AtLeastOnce);
Assert.False(File.Exists(filePath)); Assert.False(File.Exists(filePath));
Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.torrent"))); Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.torrent")));
@ -136,12 +144,19 @@ public class WatchFolderCheckerTests : IDisposable
var task = checker.StartAsync(cts.Token); var task = checker.StartAsync(cts.Token);
await Task.Delay(500); await Task.Delay(500);
await cts.CancelAsync(); await cts.CancelAsync();
try { await task; } catch (OperationCanceledException) { }
try
{
await task;
}
catch (OperationCanceledException)
{
}
// Assert // Assert
_torrentsServiceMock.Verify(x => x.AddMagnetToDebridQueue( _torrentsServiceMock.Verify(x => x.AddMagnetToDebridQueue(content,
content, It.IsAny<Torrent>()),
It.IsAny<Torrent>()), Times.AtLeastOnce); Times.AtLeastOnce);
Assert.False(File.Exists(filePath)); Assert.False(File.Exists(filePath));
Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.magnet"))); Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.magnet")));
@ -168,13 +183,20 @@ public class WatchFolderCheckerTests : IDisposable
var task = checker.StartAsync(cts.Token); var task = checker.StartAsync(cts.Token);
await Task.Delay(500); await Task.Delay(500);
await cts.CancelAsync(); await cts.CancelAsync();
try { await task; } catch (OperationCanceledException) { }
try
{
await task;
}
catch (OperationCanceledException)
{
}
// Assert // Assert
_torrentsServiceMock.Verify(x => x.AddNzbFileToDebridQueue( _torrentsServiceMock.Verify(x => x.AddNzbFileToDebridQueue(It.Is<Byte[]>(b => b.AsEnumerable().SequenceEqual(content)),
It.Is<Byte[]>(b => b.SequenceEqual(content)), "test.nzb",
"test.nzb", It.IsAny<Torrent>()),
It.IsAny<Torrent>()), Times.AtLeastOnce); Times.AtLeastOnce);
Assert.False(File.Exists(filePath)); Assert.False(File.Exists(filePath));
Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.nzb"))); Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.nzb")));
@ -196,7 +218,14 @@ public class WatchFolderCheckerTests : IDisposable
var task = checker.StartAsync(cts.Token); var task = checker.StartAsync(cts.Token);
await Task.Delay(500); await Task.Delay(500);
await cts.CancelAsync(); await cts.CancelAsync();
try { await task; } catch (OperationCanceledException) { }
try
{
await task;
}
catch (OperationCanceledException)
{
}
// Assert // Assert
_torrentsServiceMock.Verify(x => x.AddFileToDebridQueue(It.IsAny<Byte[]>(), It.IsAny<Torrent>()), Times.Never); _torrentsServiceMock.Verify(x => x.AddFileToDebridQueue(It.IsAny<Byte[]>(), It.IsAny<Torrent>()), Times.Never);
@ -208,6 +237,9 @@ public class WatchFolderCheckerTests : IDisposable
private class MockScopeFactory(IServiceScope scope) : IServiceScopeFactory private class MockScopeFactory(IServiceScope scope) : IServiceScopeFactory
{ {
public IServiceScope CreateScope() => scope; public IServiceScope CreateScope()
{
return scope;
}
} }
} }

View file

@ -182,6 +182,7 @@ public class DownloadHelperTest
}; };
String fileRelativePath; String fileRelativePath;
if (OSHelper.IsLinux) if (OSHelper.IsLinux)
{ {
fileRelativePath = "inside/lots/of/subdirectories/file.txt"; fileRelativePath = "inside/lots/of/subdirectories/file.txt";
@ -226,6 +227,7 @@ public class DownloadHelperTest
}; };
String fileRelativePath; String fileRelativePath;
if (OSHelper.IsLinux) if (OSHelper.IsLinux)
{ {
fileRelativePath = "inside/lots/of/subdirectories/file.txt"; fileRelativePath = "inside/lots/of/subdirectories/file.txt";

View file

@ -14,6 +14,7 @@ public class RateLimitHandlerTest
{ {
InnerHandler = new MockHttpMessageHandler(HttpStatusCode.TooManyRequests, 3600) InnerHandler = new MockHttpMessageHandler(HttpStatusCode.TooManyRequests, 3600)
}; };
var client = new HttpClient(handler); var client = new HttpClient(handler);
// Act & Assert // Act & Assert
@ -30,6 +31,7 @@ public class RateLimitHandlerTest
{ {
InnerHandler = new MockHttpMessageHandler(HttpStatusCode.TooManyRequests, null) InnerHandler = new MockHttpMessageHandler(HttpStatusCode.TooManyRequests, null)
}; };
var client = new HttpClient(handler); var client = new HttpClient(handler);
// Act & Assert // Act & Assert
@ -42,10 +44,12 @@ public class RateLimitHandlerTest
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{ {
var response = new HttpResponseMessage(statusCode); var response = new HttpResponseMessage(statusCode);
if (retryAfterSeconds.HasValue) if (retryAfterSeconds.HasValue)
{ {
response.Headers.RetryAfter = new System.Net.Http.Headers.RetryConditionHeaderValue(TimeSpan.FromSeconds(retryAfterSeconds.Value)); response.Headers.RetryAfter = new(TimeSpan.FromSeconds(retryAfterSeconds.Value));
} }
return Task.FromResult(response); return Task.FromResult(response);
} }
} }

View file

@ -9,16 +9,16 @@ namespace RdtClient.Service.Test.Services;
public class NzbTorrentsTest public class NzbTorrentsTest
{ {
private readonly Mocks _mocks;
private readonly MockFileSystem _fileSystem; private readonly MockFileSystem _fileSystem;
private readonly Mocks _mocks;
private readonly TorrentsService _torrents; private readonly TorrentsService _torrents;
public NzbTorrentsTest() public NzbTorrentsTest()
{ {
_mocks = new(); _mocks = new();
_fileSystem = new(); _fileSystem = new();
_torrents = new(
_mocks.TorrentsLoggerMock.Object, _torrents = new(_mocks.TorrentsLoggerMock.Object,
_mocks.TorrentDataMock.Object, _mocks.TorrentDataMock.Object,
_mocks.DownloadsMock.Object, _mocks.DownloadsMock.Object,
_mocks.ProcessFactoryMock.Object, _mocks.ProcessFactoryMock.Object,
@ -28,8 +28,7 @@ public class NzbTorrentsTest
null!, null!,
null!, null!,
null!, null!,
null! null!);
);
} }
[Fact] [Fact]
@ -37,21 +36,26 @@ public class NzbTorrentsTest
{ {
// Arrange // Arrange
var nzbLink = "http://example.com/test.nzb"; var nzbLink = "http://example.com/test.nzb";
var torrent = new Torrent var torrent = new Torrent
{ {
DownloadClient = DownloadClient.Bezzad DownloadClient = DownloadClient.Bezzad
}; };
_mocks.TorrentDataMock.Setup(t => t.GetByHash(It.IsAny<String>())).ReturnsAsync((Torrent)null!); _mocks.TorrentDataMock.Setup(t => t.GetByHash(It.IsAny<String>())).ReturnsAsync((Torrent)null!);
_mocks.TorrentDataMock.Setup(t => t.Add(
null, _mocks.TorrentDataMock.Setup(t => t.Add(null,
It.IsAny<String>(), It.IsAny<String>(),
nzbLink, nzbLink,
false, false,
DownloadType.Nzb, DownloadType.Nzb,
torrent.DownloadClient, torrent.DownloadClient,
It.IsAny<Torrent>() It.IsAny<Torrent>()))
)).ReturnsAsync(new Torrent { Hash = "mockHash", RdName = "test.nzb" }); .ReturnsAsync(new Torrent
{
Hash = "mockHash",
RdName = "test.nzb"
});
// Act // Act
var result = await _torrents.AddNzbLinkToDebridQueue(nzbLink, torrent); var result = await _torrents.AddNzbLinkToDebridQueue(nzbLink, torrent);
@ -60,15 +64,15 @@ public class NzbTorrentsTest
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("test.nzb", torrent.RdName); Assert.Equal("test.nzb", torrent.RdName);
Assert.Equal(TorrentStatus.Queued, torrent.RdStatus); Assert.Equal(TorrentStatus.Queued, torrent.RdStatus);
_mocks.TorrentDataMock.Verify(t => t.Add(
null, _mocks.TorrentDataMock.Verify(t => t.Add(null,
It.IsAny<String>(), It.IsAny<String>(),
nzbLink, nzbLink,
false, false,
DownloadType.Nzb, DownloadType.Nzb,
torrent.DownloadClient, torrent.DownloadClient,
torrent torrent),
), Times.Once); Times.Once);
} }
[Fact] [Fact]
@ -86,23 +90,30 @@ public class NzbTorrentsTest
public async Task AddNzbFileToDebridQueue_ValidFile_AddsToQueue() public async Task AddNzbFileToDebridQueue_ValidFile_AddsToQueue()
{ {
// Arrange // Arrange
var nzbContent = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\r\n <head>\r\n <meta type=\"title\">Test NZB Title</meta>\r\n </head>\r\n</nzb>"; var nzbContent =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\r\n <head>\r\n <meta type=\"title\">Test NZB Title</meta>\r\n </head>\r\n</nzb>";
var bytes = Encoding.UTF8.GetBytes(nzbContent); var bytes = Encoding.UTF8.GetBytes(nzbContent);
var torrent = new Torrent var torrent = new Torrent
{ {
DownloadClient = DownloadClient.Bezzad DownloadClient = DownloadClient.Bezzad
}; };
_mocks.TorrentDataMock.Setup(t => t.GetByHash(It.IsAny<String>())).ReturnsAsync((Torrent)null!); _mocks.TorrentDataMock.Setup(t => t.GetByHash(It.IsAny<String>())).ReturnsAsync((Torrent)null!);
_mocks.TorrentDataMock.Setup(t => t.Add(
null, _mocks.TorrentDataMock.Setup(t => t.Add(null,
It.IsAny<String>(), It.IsAny<String>(),
It.IsAny<String>(), It.IsAny<String>(),
true, true,
DownloadType.Nzb, DownloadType.Nzb,
torrent.DownloadClient, torrent.DownloadClient,
It.IsAny<Torrent>() It.IsAny<Torrent>()))
)).ReturnsAsync(new Torrent { Hash = "mockHash", RdName = "Test NZB Title" }); .ReturnsAsync(new Torrent
{
Hash = "mockHash",
RdName = "Test NZB Title"
});
// Act // Act
var result = await _torrents.AddNzbFileToDebridQueue(bytes, "filename.nzb", torrent); var result = await _torrents.AddNzbFileToDebridQueue(bytes, "filename.nzb", torrent);
@ -111,15 +122,15 @@ public class NzbTorrentsTest
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("Test NZB Title", torrent.RdName); Assert.Equal("Test NZB Title", torrent.RdName);
Assert.Equal(TorrentStatus.Queued, torrent.RdStatus); Assert.Equal(TorrentStatus.Queued, torrent.RdStatus);
_mocks.TorrentDataMock.Verify(t => t.Add(
null, _mocks.TorrentDataMock.Verify(t => t.Add(null,
It.IsAny<String>(), It.IsAny<String>(),
Convert.ToBase64String(bytes), Convert.ToBase64String(bytes),
true, true,
DownloadType.Nzb, DownloadType.Nzb,
torrent.DownloadClient, torrent.DownloadClient,
torrent torrent),
), Times.Once); Times.Once);
} }
[Fact] [Fact]

View file

@ -8,10 +8,10 @@ namespace RdtClient.Service.Test.Services;
public class QBittorrentTest public class QBittorrentTest
{ {
private readonly Mock<ILogger<QBittorrent>> _loggerMock;
private readonly Mock<Torrents> _torrentsMock;
private readonly Mock<Authentication> _authenticationMock; private readonly Mock<Authentication> _authenticationMock;
private readonly Mock<ILogger<QBittorrent>> _loggerMock;
private readonly QBittorrent _qBittorrent; private readonly QBittorrent _qBittorrent;
private readonly Mock<Torrents> _torrentsMock;
public QBittorrentTest() public QBittorrentTest()
{ {
@ -61,6 +61,7 @@ public class QBittorrentTest
// Arrange // Arrange
var downloadId = Guid.NewGuid(); var downloadId = Guid.NewGuid();
var torrentId = Guid.NewGuid(); var torrentId = Guid.NewGuid();
var allTorrents = new List<Torrent> var allTorrents = new List<Torrent>
{ {
new() new()
@ -72,12 +73,17 @@ public class QBittorrentTest
Type = DownloadType.Torrent, Type = DownloadType.Torrent,
Downloads = new List<Download> Downloads = new List<Download>
{ {
new() { DownloadId = downloadId, TorrentId = torrentId } new()
{
DownloadId = downloadId,
TorrentId = torrentId
}
} }
} }
}; };
_torrentsMock.Setup(m => m.Get()).ReturnsAsync(allTorrents); _torrentsMock.Setup(m => m.Get()).ReturnsAsync(allTorrents);
// Local download is also 100% // Local download is also 100%
_torrentsMock.Setup(m => m.GetDownloadStats(downloadId)).Returns((0, 1000, 1000)); _torrentsMock.Setup(m => m.GetDownloadStats(downloadId)).Returns((0, 1000, 1000));
@ -95,6 +101,7 @@ public class QBittorrentTest
// Arrange // Arrange
var downloadId = Guid.NewGuid(); var downloadId = Guid.NewGuid();
var torrentId = Guid.NewGuid(); var torrentId = Guid.NewGuid();
var allTorrents = new List<Torrent> var allTorrents = new List<Torrent>
{ {
new() new()
@ -106,12 +113,17 @@ public class QBittorrentTest
Type = DownloadType.Torrent, Type = DownloadType.Torrent,
Downloads = new List<Download> Downloads = new List<Download>
{ {
new() { DownloadId = downloadId, TorrentId = torrentId } new()
{
DownloadId = downloadId,
TorrentId = torrentId
}
} }
} }
}; };
_torrentsMock.Setup(m => m.Get()).ReturnsAsync(allTorrents); _torrentsMock.Setup(m => m.Get()).ReturnsAsync(allTorrents);
// Local download is 80% // Local download is 80%
_torrentsMock.Setup(m => m.GetDownloadStats(downloadId)).Returns((0, 1000, 800)); _torrentsMock.Setup(m => m.GetDownloadStats(downloadId)).Returns((0, 1000, 800));
@ -120,6 +132,7 @@ public class QBittorrentTest
// Assert // Assert
Assert.Single(result); Assert.Single(result);
// Current behavior is (1.0 + 0.8) / 2 = 0.9 // Current behavior is (1.0 + 0.8) / 2 = 0.9
Assert.Equal(0.9f, result[0].Progress); Assert.Equal(0.9f, result[0].Progress);
} }

View file

@ -1,5 +1,6 @@
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;
@ -9,9 +10,13 @@ namespace RdtClient.Service.Test.Services;
public class SabnzbdTest public class SabnzbdTest
{ {
private readonly AppSettings _appSettings = new()
{
Port = 6500
};
private readonly Mock<ILogger<Sabnzbd>> _loggerMock = new(); private readonly Mock<ILogger<Sabnzbd>> _loggerMock = new();
private readonly Mock<Torrents> _torrentsMock; private readonly Mock<Torrents> _torrentsMock;
private readonly AppSettings _appSettings = new() { Port = 6500 };
public SabnzbdTest() public SabnzbdTest()
{ {
@ -34,7 +39,10 @@ public class SabnzbdTest
Downloads = new List<Download> Downloads = new List<Download>
{ {
new() new()
{ BytesTotal = 1000, BytesDone = 500 } {
BytesTotal = 1000,
BytesDone = 500
}
} }
} }
}; };
@ -52,6 +60,7 @@ public class SabnzbdTest
Assert.Single(result.Slots); Assert.Single(result.Slots);
Assert.Equal("hash1", result.Slots[0].NzoId); Assert.Equal("hash1", result.Slots[0].NzoId);
Assert.Equal("Name 1", result.Slots[0].Filename); Assert.Equal("Name 1", result.Slots[0].Filename);
// (50% debrid + 50% download) / 2 = 50% // (50% debrid + 50% download) / 2 = 50%
Assert.Equal("50", result.Slots[0].Percentage); Assert.Equal("50", result.Slots[0].Percentage);
} }
@ -71,7 +80,10 @@ public class SabnzbdTest
Downloads = new List<Download> Downloads = new List<Download>
{ {
new() new()
{ BytesTotal = 1000, BytesDone = 0 } {
BytesTotal = 1000,
BytesDone = 0
}
} }
} }
}; };
@ -94,6 +106,7 @@ public class SabnzbdTest
// Arrange // Arrange
var now = DateTimeOffset.UtcNow; var now = DateTimeOffset.UtcNow;
var added = now.AddMinutes(-10); var added = now.AddMinutes(-10);
var torrentList = new List<Torrent> var torrentList = new List<Torrent>
{ {
new() new()
@ -113,6 +126,7 @@ public class SabnzbdTest
} }
} }
}; };
// Overall progress = (1.0 + 0.0) / 2 = 0.5 // Overall progress = (1.0 + 0.0) / 2 = 0.5
// Elapsed = 10 minutes // Elapsed = 10 minutes
// Total estimated = 10 / 0.5 = 20 minutes // Total estimated = 10 / 0.5 = 20 minutes
@ -142,6 +156,7 @@ public class SabnzbdTest
var now = DateTimeOffset.UtcNow; var now = DateTimeOffset.UtcNow;
var added = now.AddMinutes(-20); var added = now.AddMinutes(-20);
var retry = now.AddMinutes(-10); var retry = now.AddMinutes(-10);
var torrentList = new List<Torrent> var torrentList = new List<Torrent>
{ {
new() new()
@ -162,6 +177,7 @@ public class SabnzbdTest
} }
} }
}; };
// Later of Added and Retry is Retry (-10 mins) // Later of Added and Retry is Retry (-10 mins)
// Overall progress = (1.0 + 0.0) / 2 = 0.5 // Overall progress = (1.0 + 0.0) / 2 = 0.5
// Elapsed = 10 minutes // Elapsed = 10 minutes
@ -259,7 +275,7 @@ public class SabnzbdTest
{ {
// Arrange // Arrange
var savePath = @"C:\Downloads"; var savePath = @"C:\Downloads";
Data.Data.SettingData.Get.DownloadClient.MappedPath = savePath; SettingData.Get.DownloadClient.MappedPath = savePath;
var torrentList = new List<Torrent> var torrentList = new List<Torrent>
{ {
@ -292,7 +308,7 @@ public class SabnzbdTest
{ {
// Arrange // Arrange
var savePath = @"C:\Downloads"; var savePath = @"C:\Downloads";
Data.Data.SettingData.Get.DownloadClient.MappedPath = savePath; SettingData.Get.DownloadClient.MappedPath = savePath;
var torrentList = new List<Torrent> var torrentList = new List<Torrent>
{ {
@ -383,7 +399,7 @@ public class SabnzbdTest
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList); _torrentsMock.Setup(t => t.Get()).ReturnsAsync(torrentList);
Data.Data.SettingData.Get.General.Categories = "TV, Music, *"; SettingData.Get.General.Categories = "TV, Music, *";
var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings); var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings);

View file

@ -522,8 +522,8 @@ public class AllDebridDebridClientTest
{ {
public readonly Mock<IAllDebridNetClientFactory> AllDebridClientFactoryMock; public readonly Mock<IAllDebridNetClientFactory> AllDebridClientFactoryMock;
public readonly Mock<IAllDebridNETClient> AllDebridClientMock; public readonly Mock<IAllDebridNETClient> AllDebridClientMock;
public readonly Mock<ILogger<AllDebridDebridClient>> LoggerMock;
public readonly Mock<IDownloadableFileFilter> FileFilterMock; public readonly Mock<IDownloadableFileFilter> FileFilterMock;
public readonly Mock<ILogger<AllDebridDebridClient>> LoggerMock;
public Mocks() public Mocks()
{ {

View file

@ -9,14 +9,16 @@ using RdtClient.Data.Models.Internal;
using RdtClient.Service.Services; using RdtClient.Service.Services;
using RdtClient.Service.Services.DebridClients; using RdtClient.Service.Services.DebridClients;
using TorBoxNET; using TorBoxNET;
using DownloadClient = RdtClient.Data.Enums.DownloadClient;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace RdtClient.Service.Test.Services.TorrentClients; namespace RdtClient.Service.Test.Services.TorrentClients;
public class TorBoxDebridClientTest public class TorBoxDebridClientTest
{ {
private readonly Mock<ILogger<TorBoxDebridClient>> _loggerMock;
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
private readonly Mock<IDownloadableFileFilter> _fileFilterMock; private readonly Mock<IDownloadableFileFilter> _fileFilterMock;
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
private readonly Mock<ILogger<TorBoxDebridClient>> _loggerMock;
public TorBoxDebridClientTest() public TorBoxDebridClientTest()
{ {
@ -37,11 +39,26 @@ public class TorBoxDebridClientTest
// Arrange // Arrange
var torrents = new List<TorrentInfoResult> var torrents = new List<TorrentInfoResult>
{ {
new() { Hash = "hash1", Name = "torrent1", Size = 1000, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } new()
{
Hash = "hash1",
Name = "torrent1",
Size = 1000,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
}
}; };
var nzbs = new List<UsenetInfoResult> var nzbs = new List<UsenetInfoResult>
{ {
new() { Hash = "hash2", Name = "nzb1", Size = 2000, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow } new()
{
Hash = "hash2",
Name = "nzb1",
Size = 2000,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
}
}; };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object); var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object);
@ -70,6 +87,7 @@ public class TorBoxDebridClientTest
{ {
// Arrange // Arrange
var hash = "test-hash"; var hash = "test-hash";
var availability = new Response<List<AvailableTorrent?>> var availability = new Response<List<AvailableTorrent?>>
{ {
Data = new() Data = new()
@ -78,8 +96,16 @@ public class TorBoxDebridClientTest
{ {
Files = new() Files = new()
{ {
new() { Name = "file1.mkv", Size = 100 }, new()
new() { Name = "file2.txt", Size = 10 } {
Name = "file1.mkv",
Size = 100
},
new()
{
Name = "file2.txt",
Size = 10
}
} }
} }
} }
@ -104,7 +130,12 @@ public class TorBoxDebridClientTest
{ {
// Arrange // Arrange
var hash = "test-hash"; var hash = "test-hash";
var torrentAvailability = new Response<List<AvailableTorrent?>> { Data = new() };
var torrentAvailability = new Response<List<AvailableTorrent?>>
{
Data = new()
};
var usenetAvailability = new Response<List<AvailableUsenet?>> var usenetAvailability = new Response<List<AvailableUsenet?>>
{ {
Data = new() Data = new()
@ -113,7 +144,11 @@ public class TorBoxDebridClientTest
{ {
Files = new() Files = new()
{ {
new() { Name = "file1.nzb", Size = 200 } new()
{
Name = "file1.nzb",
Size = 200
}
} }
} }
} }
@ -137,8 +172,16 @@ public class TorBoxDebridClientTest
{ {
// Arrange // Arrange
var hash = "test-hash"; var hash = "test-hash";
var torrentAvailability = new Response<List<AvailableTorrent?>> { Data = new() };
var usenetAvailability = new Response<List<AvailableUsenet?>> { Data = new() }; var torrentAvailability = new Response<List<AvailableTorrent?>>
{
Data = new()
};
var usenetAvailability = new Response<List<AvailableUsenet?>>
{
Data = new()
};
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object); var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object);
clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(torrentAvailability); clientMock.Protected().Setup<Task<Response<List<AvailableTorrent?>>>>("GetTorrentAvailability", hash).ReturnsAsync(torrentAvailability);
@ -208,6 +251,7 @@ public class TorBoxDebridClientTest
RdId = "torrent-id", RdId = "torrent-id",
Type = DownloadType.Torrent Type = DownloadType.Torrent
}; };
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>();
@ -218,7 +262,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
torrentsApiMock.Setup(m => m.RequestDownloadAsync(123, 456, false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.RequestDownloadAsync(123, 456, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<String> { Data = "https://unrestricted-link" }); .ReturnsAsync(new Response<String>
{
Data = "https://unrestricted-link"
});
// Act // Act
var result = await clientMock.Object.Unrestrict(torrent, link); var result = await clientMock.Object.Unrestrict(torrent, link);
@ -237,6 +284,7 @@ public class TorBoxDebridClientTest
RdId = "nzb-id", RdId = "nzb-id",
Type = DownloadType.Nzb Type = DownloadType.Nzb
}; };
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>();
@ -247,7 +295,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
usenetApiMock.Setup(m => m.RequestDownloadAsync(123, 456, false, It.IsAny<CancellationToken>())) usenetApiMock.Setup(m => m.RequestDownloadAsync(123, 456, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<String> { Data = "https://unrestricted-link-nzb" }); .ReturnsAsync(new Response<String>
{
Data = "https://unrestricted-link-nzb"
});
// Act // Act
var result = await clientMock.Object.Unrestrict(torrent, link); var result = await clientMock.Object.Unrestrict(torrent, link);
@ -261,17 +312,32 @@ public class TorBoxDebridClientTest
public async Task AddNzbFile_CallsUsenetAddFileAsyncWithName() public async Task AddNzbFile_CallsUsenetAddFileAsyncWithName()
{ {
// Arrange // Arrange
var bytes = new Byte[] { 1, 2, 3 }; var bytes = new Byte[]
{
1, 2, 3
};
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
usenetApiMock.Setup(m => m.AddFileAsync(bytes, It.IsAny<Int32>(), name, It.IsAny<String?>(), It.IsAny<Boolean>(), It.IsAny<CancellationToken>())) usenetApiMock.Setup(m => m.AddFileAsync(bytes, It.IsAny<Int32>(), name, It.IsAny<String?>(), It.IsAny<Boolean>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<UsenetAddResult> { Data = new UsenetAddResult { Hash = "new-hash" } }); .ReturnsAsync(new Response<UsenetAddResult>
{
Data = new()
{
Hash = "new-hash"
}
});
// Act // Act
var result = await clientMock.Object.AddNzbFile(bytes, name); var result = await clientMock.Object.AddNzbFile(bytes, name);
@ -280,15 +346,27 @@ public class TorBoxDebridClientTest
Assert.Equal("new-hash", result); Assert.Equal("new-hash", result);
usenetApiMock.Verify(m => m.AddFileAsync(bytes, It.IsAny<Int32>(), name, It.IsAny<String?>(), It.IsAny<Boolean>(), It.IsAny<CancellationToken>()), Times.Once); usenetApiMock.Verify(m => m.AddFileAsync(bytes, It.IsAny<Int32>(), name, It.IsAny<String?>(), It.IsAny<Boolean>(), It.IsAny<CancellationToken>()), Times.Once);
} }
[Fact] [Fact]
public async Task GetDownloadInfos_GeneratesCorrectFakedlLinks_ForIndividualFiles() public async Task GetDownloadInfos_GeneratesCorrectFakedlLinks_ForIndividualFiles()
{ {
// Arrange // Arrange
var files = new List<DebridClientFile> var files = new List<DebridClientFile>
{ {
new() { Id = 1, Path = "file1.mkv", Bytes = 1000 }, new()
new() { Id = 2, Path = "file2.mkv", Bytes = 2000 } {
Id = 1,
Path = "file1.mkv",
Bytes = 1000
},
new()
{
Id = 2,
Path = "file2.mkv",
Bytes = 2000
}
}; };
var torrent = new Torrent var torrent = new Torrent
{ {
Hash = "test-hash", Hash = "test-hash",
@ -303,7 +381,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
torrentsApiMock.Setup(m => m.GetHashInfoAsync("test-hash", true, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.GetHashInfoAsync("test-hash", true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new TorrentInfoResult { Id = 12345 }); .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);
@ -325,14 +406,20 @@ public class TorBoxDebridClientTest
// Arrange // Arrange
var files = new List<DebridClientFile> var files = new List<DebridClientFile>
{ {
new() { Id = 1, Path = "file1.mkv", Bytes = 1000 } new()
{
Id = 1,
Path = "file1.mkv",
Bytes = 1000
}
}; };
var torrent = new Torrent var torrent = new Torrent
{ {
Hash = "test-hash", Hash = "test-hash",
RdName = "TestTorrent", RdName = "TestTorrent",
RdFiles = JsonConvert.SerializeObject(files), RdFiles = JsonConvert.SerializeObject(files),
DownloadClient = Data.Enums.DownloadClient.Aria2c DownloadClient = DownloadClient.Aria2c
}; };
Settings.Get.Provider.PreferZippedDownloads = true; Settings.Get.Provider.PreferZippedDownloads = true;
@ -345,7 +432,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
torrentsApiMock.Setup(m => m.GetHashInfoAsync("test-hash", true, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.GetHashInfoAsync("test-hash", true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new TorrentInfoResult { Id = 12345 }); .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);
@ -367,6 +457,7 @@ public class TorBoxDebridClientTest
{ {
Type = DownloadType.Torrent Type = DownloadType.Torrent
}; };
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>();
@ -377,7 +468,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
torrentsApiMock.Setup(m => m.RequestDownloadAsync(12345, 6789, false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.RequestDownloadAsync(12345, 6789, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<String> { Data = "https://real-download-link" }); .ReturnsAsync(new Response<String>
{
Data = "https://real-download-link"
});
// Act // Act
var result = await clientMock.Object.Unrestrict(torrent, link); var result = await clientMock.Object.Unrestrict(torrent, link);
@ -395,6 +489,7 @@ public class TorBoxDebridClientTest
{ {
Type = DownloadType.Torrent Type = DownloadType.Torrent
}; };
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>();
@ -405,7 +500,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
torrentsApiMock.Setup(m => m.RequestDownloadAsync(12345, 0, true, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.RequestDownloadAsync(12345, 0, true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<String> { Data = "https://real-zip-download-link" }); .ReturnsAsync(new Response<String>
{
Data = "https://real-zip-download-link"
});
// Act // Act
var result = await clientMock.Object.Unrestrict(torrent, link); var result = await clientMock.Object.Unrestrict(torrent, link);
@ -414,14 +512,21 @@ public class TorBoxDebridClientTest
Assert.Equal("https://real-zip-download-link", result); Assert.Equal("https://real-zip-download-link", result);
torrentsApiMock.Verify(m => m.RequestDownloadAsync(12345, 0, true, It.IsAny<CancellationToken>()), Times.Once); torrentsApiMock.Verify(m => m.RequestDownloadAsync(12345, 0, true, It.IsAny<CancellationToken>()), Times.Once);
} }
[Fact] [Fact]
public async Task GetDownloadInfos_GeneratesCorrectFakedlLinks_ForUsenet() public async Task GetDownloadInfos_GeneratesCorrectFakedlLinks_ForUsenet()
{ {
// Arrange // Arrange
var files = new List<DebridClientFile> var files = new List<DebridClientFile>
{ {
new() { Id = 1, Path = "file1.nzb", Bytes = 1000 } new()
{
Id = 1,
Path = "file1.nzb",
Bytes = 1000
}
}; };
var torrent = new Torrent var torrent = new Torrent
{ {
Type = DownloadType.Nzb, Type = DownloadType.Nzb,
@ -437,7 +542,14 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
usenetApiMock.Setup(m => m.GetCurrentAsync(true, It.IsAny<CancellationToken>())) usenetApiMock.Setup(m => m.GetCurrentAsync(true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<UsenetInfoResult> { new() { Hash = "nzb-hash", Id = 98765 } }); .ReturnsAsync(new List<UsenetInfoResult>
{
new()
{
Hash = "nzb-hash",
Id = 98765
}
});
_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);
@ -460,6 +572,7 @@ public class TorBoxDebridClientTest
{ {
Type = DownloadType.Nzb Type = DownloadType.Nzb
}; };
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>();
@ -470,7 +583,10 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
usenetApiMock.Setup(m => m.RequestDownloadAsync(98765, 4321, false, It.IsAny<CancellationToken>())) usenetApiMock.Setup(m => m.RequestDownloadAsync(98765, 4321, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<String> { Data = "https://real-usenet-link" }); .ReturnsAsync(new Response<String>
{
Data = "https://real-usenet-link"
});
// Act // Act
var result = await clientMock.Object.Unrestrict(torrent, link); var result = await clientMock.Object.Unrestrict(torrent, link);
@ -479,6 +595,7 @@ public class TorBoxDebridClientTest
Assert.Equal("https://real-usenet-link", result); Assert.Equal("https://real-usenet-link", result);
usenetApiMock.Verify(m => m.RequestDownloadAsync(98765, 4321, false, It.IsAny<CancellationToken>()), Times.Once); usenetApiMock.Verify(m => m.RequestDownloadAsync(98765, 4321, false, It.IsAny<CancellationToken>()), Times.Once);
} }
[Fact] [Fact]
public async Task AddTorrentMagnet_ThrowsRateLimitException_OnActiveLimit() public async Task AddTorrentMagnet_ThrowsRateLimitException_OnActiveLimit()
{ {
@ -486,7 +603,12 @@ public class TorBoxDebridClientTest
var magnetLink = "magnet:?xt=urn:btih:test"; var magnetLink = "magnet:?xt=urn:btih:test";
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -494,7 +616,16 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>())) userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<User> { Data = new User { Settings = new UserSettings { SeedTorrents = 5 } } }); .ReturnsAsync(new Response<User>
{
Data = new()
{
Settings = new()
{
SeedTorrents = 5
}
}
});
torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>()))
.ThrowsAsync(new TorBoxException("active_limit", "Active limit reached")); .ThrowsAsync(new TorBoxException("active_limit", "Active limit reached"));
@ -512,7 +643,12 @@ public class TorBoxDebridClientTest
var magnetLink = "magnet:?xt=urn:btih:test"; var magnetLink = "magnet:?xt=urn:btih:test";
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -520,10 +656,19 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>())) userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<User> { Data = new User { Settings = new UserSettings { SeedTorrents = 5 } } }); .ReturnsAsync(new Response<User>
{
Data = new()
{
Settings = new()
{
SeedTorrents = 5
}
}
});
torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception("slow_down")); .ThrowsAsync(new("slow_down"));
// Act & Assert // Act & Assert
await Assert.ThrowsAsync<RateLimitException>(() => clientMock.Object.AddTorrentMagnet(magnetLink)); await Assert.ThrowsAsync<RateLimitException>(() => clientMock.Object.AddTorrentMagnet(magnetLink));
@ -536,7 +681,12 @@ public class TorBoxDebridClientTest
var magnetLink = "magnet:?xt=urn:btih:test"; var magnetLink = "magnet:?xt=urn:btih:test";
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -544,7 +694,16 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>())) userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<User> { Data = new User { Settings = new UserSettings { SeedTorrents = 5 } } }); .ReturnsAsync(new Response<User>
{
Data = new()
{
Settings = new()
{
SeedTorrents = 5
}
}
});
torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>()))
.ThrowsAsync(new RateLimitException("TorBox rate limit exceeded", TimeSpan.FromMinutes(60))); .ThrowsAsync(new RateLimitException("TorBox rate limit exceeded", TimeSpan.FromMinutes(60)));
@ -561,7 +720,12 @@ public class TorBoxDebridClientTest
var magnetLink = "magnet:?xt=urn:btih:test"; var magnetLink = "magnet:?xt=urn:btih:test";
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -569,10 +733,19 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>())) userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<User> { Data = new User { Settings = new UserSettings { SeedTorrents = 5 } } }); .ReturnsAsync(new Response<User>
{
Data = new()
{
Settings = new()
{
SeedTorrents = 5
}
}
});
torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.AddMagnetAsync(magnetLink, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception("Wrapped", new RateLimitException("TorBox rate limit exceeded", TimeSpan.FromMinutes(60)))); .ThrowsAsync(new("Wrapped", new RateLimitException("TorBox rate limit exceeded", TimeSpan.FromMinutes(60))));
// Act & Assert // Act & Assert
var ex = await Assert.ThrowsAsync<RateLimitException>(() => clientMock.Object.AddTorrentMagnet(magnetLink)); var ex = await Assert.ThrowsAsync<RateLimitException>(() => clientMock.Object.AddTorrentMagnet(magnetLink));
@ -583,10 +756,19 @@ public class TorBoxDebridClientTest
public async Task AddTorrentFile_ThrowsRateLimitException_OnActiveLimit() public async Task AddTorrentFile_ThrowsRateLimitException_OnActiveLimit()
{ {
// Arrange // Arrange
var bytes = new Byte[] { 1, 2, 3 }; var bytes = new Byte[]
{
1, 2, 3
};
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -594,7 +776,16 @@ public class TorBoxDebridClientTest
clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object); clientMock.Protected().Setup<ITorBoxNetClient>("GetClient").Returns(torBoxClientMock.Object);
userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>())) userApiMock.Setup(m => m.GetAsync(true, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Response<User> { Data = new User { Settings = new UserSettings { SeedTorrents = 5 } } }); .ReturnsAsync(new Response<User>
{
Data = new()
{
Settings = new()
{
SeedTorrents = 5
}
}
});
torrentsApiMock.Setup(m => m.AddFileAsync(bytes, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>())) torrentsApiMock.Setup(m => m.AddFileAsync(bytes, 5, It.IsAny<Boolean>(), It.IsAny<String?>(), false, It.IsAny<CancellationToken>()))
.ThrowsAsync(new TorBoxException("active_limit", "Active limit reached")); .ThrowsAsync(new TorBoxException("active_limit", "Active limit reached"));
@ -611,7 +802,12 @@ public class TorBoxDebridClientTest
// Arrange // Arrange
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -630,10 +826,19 @@ public class TorBoxDebridClientTest
public async Task AddNzbFile_ThrowsRateLimitException_OnActiveLimit() public async Task AddNzbFile_ThrowsRateLimitException_OnActiveLimit()
{ {
// Arrange // Arrange
var bytes = new Byte[] { 1, 2, 3 }; var bytes = new Byte[]
{
1, 2, 3
};
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) { CallBase = true };
var clientMock = new Mock<TorBoxDebridClient>(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object)
{
CallBase = true
};
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);
@ -657,6 +862,7 @@ public class TorBoxDebridClientTest
RdId = "test-rd-id", RdId = "test-rd-id",
RdStatus = TorrentStatus.Downloading RdStatus = TorrentStatus.Downloading
}; };
var torrentClientTorrent = new DebridClientTorrent var torrentClientTorrent = new DebridClientTorrent
{ {
Status = "failed (Aborted, cannot be completed - https://sabnzbd.org/not-complete)", Status = "failed (Aborted, cannot be completed - https://sabnzbd.org/not-complete)",
@ -682,6 +888,7 @@ public class TorBoxDebridClientTest
RdStatus = TorrentStatus.Downloading, RdStatus = TorrentStatus.Downloading,
RdName = "test-torrent" RdName = "test-torrent"
}; };
var torrentClientTorrent = new DebridClientTorrent var torrentClientTorrent = new DebridClientTorrent
{ {
Status = "some-unknown-status", Status = "some-unknown-status",
@ -694,13 +901,11 @@ public class TorBoxDebridClientTest
await clientMock.Object.UpdateData(torrent, torrentClientTorrent); await clientMock.Object.UpdateData(torrent, torrentClientTorrent);
// Assert // Assert
_loggerMock.Verify( _loggerMock.Verify(x => x.Log(LogLevel.Information,
x => x.Log( It.IsAny<EventId>(),
Microsoft.Extensions.Logging.LogLevel.Information, It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("unmapped status") && v.ToString()!.Contains("some-unknown-status")),
It.IsAny<EventId>(), It.IsAny<Exception>(),
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("unmapped status") && v.ToString()!.Contains("some-unknown-status")), It.Is<Func<It.IsAnyType, Exception?, String>>((v, t) => true)),
It.IsAny<Exception>(), Times.Once);
It.Is<Func<It.IsAnyType, Exception?, String>>((v, t) => true)),
Times.Once);
} }
} }

View file

@ -11,11 +11,12 @@ using RdtClient.Data.Models.Internal;
using RdtClient.Service.Services; using RdtClient.Service.Services;
using RdtClient.Service.Test.Helpers; using RdtClient.Service.Test.Helpers;
using RdtClient.Service.Wrappers; using RdtClient.Service.Wrappers;
using DownloadClient = RdtClient.Data.Enums.DownloadClient;
using TorrentsService = RdtClient.Service.Services.Torrents; using TorrentsService = RdtClient.Service.Services.Torrents;
namespace RdtClient.Service.Test.Services; namespace RdtClient.Service.Test.Services;
class Mocks internal class Mocks
{ {
public readonly Mock<IDownloads> DownloadsMock; public readonly Mock<IDownloads> DownloadsMock;
public readonly Mock<IEnricher> EnricherMock; public readonly Mock<IEnricher> EnricherMock;
@ -92,6 +93,7 @@ public class TorrentsTest
String downloadPath; String downloadPath;
String torrentPath; String torrentPath;
String filePath; String filePath;
if (OSHelper.IsLinux) if (OSHelper.IsLinux)
{ {
downloadPath = $"{settings.DownloadClient.DownloadPath}/{torrent.Category}"; downloadPath = $"{settings.DownloadClient.DownloadPath}/{torrent.Category}";
@ -337,11 +339,15 @@ public class TorrentsTest
{ {
// Arrange // Arrange
var mocks = new Mocks(); var mocks = new Mocks();
var torrent = new Torrent var torrent = new Torrent
{ {
TorrentId = Guid.NewGuid() TorrentId = Guid.NewGuid()
}; };
var nzbContent = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\r\n <head>\r\n <meta type=\"title\">Test NZB Title</meta>\r\n </head>\r\n</nzb>";
var nzbContent =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\r\n <head>\r\n <meta type=\"title\">Test NZB Title</meta>\r\n </head>\r\n</nzb>";
var bytes = Encoding.UTF8.GetBytes(nzbContent); var bytes = Encoding.UTF8.GetBytes(nzbContent);
mocks.TorrentDataMock.Setup(t => t.Add(It.IsAny<String>(), mocks.TorrentDataMock.Setup(t => t.Add(It.IsAny<String>(),
@ -349,7 +355,7 @@ public class TorrentsTest
It.IsAny<String>(), It.IsAny<String>(),
true, true,
DownloadType.Nzb, DownloadType.Nzb,
It.IsAny<Data.Enums.DownloadClient>(), It.IsAny<DownloadClient>(),
It.IsAny<Torrent>())) It.IsAny<Torrent>()))
.ReturnsAsync(new Torrent()); .ReturnsAsync(new Torrent());
@ -374,8 +380,9 @@ public class TorrentsTest
It.IsAny<String>(), It.IsAny<String>(),
true, true,
DownloadType.Nzb, DownloadType.Nzb,
It.IsAny<Data.Enums.DownloadClient>(), It.IsAny<DownloadClient>(),
It.IsAny<Torrent>()), Times.Once); It.IsAny<Torrent>()),
Times.Once);
} }
[Fact] [Fact]
@ -383,10 +390,12 @@ public class TorrentsTest
{ {
// Arrange // Arrange
var mocks = new Mocks(); var mocks = new Mocks();
var torrent = new Torrent var torrent = new Torrent
{ {
TorrentId = Guid.NewGuid() TorrentId = Guid.NewGuid()
}; };
var link = "http://example.com/test.nzb"; var link = "http://example.com/test.nzb";
mocks.TorrentDataMock.Setup(t => t.Add(It.IsAny<String>(), mocks.TorrentDataMock.Setup(t => t.Add(It.IsAny<String>(),
@ -394,7 +403,7 @@ public class TorrentsTest
It.IsAny<String>(), It.IsAny<String>(),
false, false,
DownloadType.Nzb, DownloadType.Nzb,
It.IsAny<Data.Enums.DownloadClient>(), It.IsAny<DownloadClient>(),
It.IsAny<Torrent>())) It.IsAny<Torrent>()))
.ReturnsAsync(new Torrent()); .ReturnsAsync(new Torrent());
@ -419,7 +428,8 @@ public class TorrentsTest
link, link,
false, false,
DownloadType.Nzb, DownloadType.Nzb,
It.IsAny<Data.Enums.DownloadClient>(), It.IsAny<DownloadClient>(),
It.IsAny<Torrent>()), Times.Once); It.IsAny<Torrent>()),
Times.Once);
} }
} }

View file

@ -81,11 +81,11 @@ public class UpdateChecker(ILogger<UpdateChecker> logger, IHttpClientFactory htt
private async Task<T?> GitHubRequest<T>(String endpoint, CancellationToken cancellationToken) private async Task<T?> GitHubRequest<T>(String endpoint, CancellationToken cancellationToken)
{ {
var httpClient = httpClientFactory.CreateClient(); var httpClient = httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion)); httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion));
var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken); var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken);
return JsonConvert.DeserializeObject<T>(response); return JsonConvert.DeserializeObject<T>(response);
} }
} }

View file

@ -29,11 +29,13 @@ 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.Get.Watch.Interval, 10));
if (DateTime.Now < nextCheck) if (DateTime.Now < nextCheck)
{ {
var delay = nextCheck - DateTime.Now; var delay = nextCheck - DateTime.Now;
await Task.Delay(delay, stoppingToken); await Task.Delay(delay, stoppingToken);
} }
_prevCheck = DateTime.Now; _prevCheck = DateTime.Now;
if (String.IsNullOrWhiteSpace(Settings.Get.Watch.Path)) if (String.IsNullOrWhiteSpace(Settings.Get.Watch.Path))
@ -54,7 +56,6 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
errorStorePath = Settings.Get.Watch.ErrorPath; errorStorePath = Settings.Get.Watch.ErrorPath;
} }
var torrentFiles = Directory.GetFiles(Settings.Get.Watch.Path, "*.*", SearchOption.TopDirectoryOnly); var torrentFiles = Directory.GetFiles(Settings.Get.Watch.Path, "*.*", SearchOption.TopDirectoryOnly);
foreach (var torrentFile in torrentFiles) foreach (var torrentFile in torrentFiles)

View file

@ -65,6 +65,7 @@ public static class DiConfig
public static void RegisterHttpClients(this IServiceCollection services) public static void RegisterHttpClients(this IServiceCollection services)
{ {
services.AddHttpClient(); services.AddHttpClient();
services.ConfigureHttpClientDefaults(builder => services.ConfigureHttpClientDefaults(builder =>
{ {
builder.ConfigureHttpClient(httpClient => builder.ConfigureHttpClient(httpClient =>
@ -90,12 +91,14 @@ public static class DiConfig
{ {
builder.AddTimeout(new TimeoutStrategyOptions builder.AddTimeout(new TimeoutStrategyOptions
{ {
TimeoutGenerator = _ => new ValueTask<TimeSpan>(TimeSpan.FromSeconds(Settings.Get.Provider.Timeout)) TimeoutGenerator = _ => new(TimeSpan.FromSeconds(Settings.Get.Provider.Timeout))
}); });
builder.AddRateLimitHeaders(options => builder.AddRateLimitHeaders(options =>
{ {
options.EnableProactiveThrottling = true; options.EnableProactiveThrottling = true;
}); });
builder.AddRetry(new() builder.AddRetry(new()
{ {
ShouldHandle = args => args.Outcome switch ShouldHandle = args => args.Outcome switch
@ -126,10 +129,10 @@ public static class DiConfig
throw new RateLimitException("Provider rate limit exceeded", delay); throw new RateLimitException("Provider rate limit exceeded", delay);
} }
return new ValueTask<TimeSpan?>(delay); return new(delay);
} }
return new ValueTask<TimeSpan?>((TimeSpan?)null); return new((TimeSpan?)null);
} }
}); });
} }

View file

@ -1,6 +1,7 @@
using System.Reflection;
using RdtClient.Service.Services;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
using RdtClient.Service.Services;
namespace RdtClient.Service.Helpers; namespace RdtClient.Service.Helpers;
@ -11,18 +12,21 @@ public class CredentialRedactorEnricher : ILogEventEnricher
var sensitiveValues = new List<String>(); var sensitiveValues = new List<String>();
var apiKey = Settings.Get.Provider.ApiKey; var apiKey = Settings.Get.Provider.ApiKey;
if (!String.IsNullOrWhiteSpace(apiKey) && apiKey.Length > 5) if (!String.IsNullOrWhiteSpace(apiKey) && apiKey.Length > 5)
{ {
sensitiveValues.Add(apiKey); sensitiveValues.Add(apiKey);
} }
var aria2Secret = Settings.Get.DownloadClient.Aria2cSecret; var aria2Secret = Settings.Get.DownloadClient.Aria2cSecret;
if (!String.IsNullOrWhiteSpace(aria2Secret) && aria2Secret.Length > 5) if (!String.IsNullOrWhiteSpace(aria2Secret) && aria2Secret.Length > 5)
{ {
sensitiveValues.Add(aria2Secret); sensitiveValues.Add(aria2Secret);
} }
var dsPassword = Settings.Get.DownloadClient.DownloadStationPassword; var dsPassword = Settings.Get.DownloadClient.DownloadStationPassword;
if (!String.IsNullOrWhiteSpace(dsPassword) && dsPassword.Length > 5) if (!String.IsNullOrWhiteSpace(dsPassword) && dsPassword.Length > 5)
{ {
sensitiveValues.Add(dsPassword); sensitiveValues.Add(dsPassword);
@ -40,7 +44,7 @@ public class CredentialRedactorEnricher : ILogEventEnricher
{ {
var newText = logEvent.MessageTemplate.Text.Replace(sensitiveValue, "*****"); var newText = logEvent.MessageTemplate.Text.Replace(sensitiveValue, "*****");
var field = typeof(MessageTemplate).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) var field = typeof(MessageTemplate).GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.FirstOrDefault(f => f.FieldType == typeof(String)); .FirstOrDefault(f => f.FieldType == typeof(String));
field?.SetValue(logEvent.MessageTemplate, newText); field?.SetValue(logEvent.MessageTemplate, newText);
@ -48,6 +52,7 @@ public class CredentialRedactorEnricher : ILogEventEnricher
// Redact in properties // Redact in properties
var propertiesToUpdate = new List<LogEventProperty>(); var propertiesToUpdate = new List<LogEventProperty>();
foreach (var property in logEvent.Properties) foreach (var property in logEvent.Properties)
{ {
if (property.Value is ScalarValue scalarValue && scalarValue.Value is String stringValue && stringValue.Contains(sensitiveValue)) if (property.Value is ScalarValue scalarValue && scalarValue.Value is String stringValue && stringValue.Contains(sensitiveValue))

View file

@ -79,13 +79,14 @@ public static class FileHelper
public static String RemoveInvalidFileNameChars(String filename) public static String RemoveInvalidFileNameChars(String filename)
{ {
var invalidChars = Path.GetInvalidFileNameChars() var invalidChars = Path.GetInvalidFileNameChars()
.Concat(['/', '\\']) .Concat(['/', '\\'])
.Distinct() .Distinct()
.ToArray(); .ToArray();
var cleaned = String.Concat(filename.Split(invalidChars)); var cleaned = String.Concat(filename.Split(invalidChars));
cleaned = cleaned.Replace("..", ""); cleaned = cleaned.Replace("..", "");
cleaned = cleaned.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); cleaned = cleaned.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return cleaned; return cleaned;
} }

View file

@ -9,7 +9,11 @@ public static class FileSizeHelper
return "0 B"; return "0 B";
} }
String[] units = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; String[] units =
{
"B", "KB", "MB", "GB", "TB", "PB", "EB"
};
var unitIndex = 0; var unitIndex = 0;
Double size = bytes.Value; Double size = bytes.Value;

View file

@ -18,7 +18,7 @@ public static class Logger
} }
var stats = TorrentRunner.GetStats(download.DownloadId); var stats = TorrentRunner.GetStats(download.DownloadId);
var done = (Int32)((Double)stats.BytesDone / stats.BytesTotal * 100); var done = (Int32)(((Double)stats.BytesDone / stats.BytesTotal) * 100);
if (done < 0 || Double.IsNaN(done) || Double.IsInfinity(done)) if (done < 0 || Double.IsNaN(done) || Double.IsInfinity(done))
{ {

View file

@ -23,6 +23,7 @@ public class RateLimitHandler : DelegatingHandler
} }
response.Dispose(); response.Dispose();
throw new RateLimitException("TorBox rate limit exceeded", delay); throw new RateLimitException("TorBox rate limit exceeded", delay);
} }

View file

@ -18,12 +18,14 @@ public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor
if (context.User.Identity?.IsAuthenticated == true) if (context.User.Identity?.IsAuthenticated == true)
{ {
context.Succeed(requirement); context.Succeed(requirement);
return; return;
} }
if (Settings.Get.General.AuthenticationType == AuthenticationType.None) if (Settings.Get.General.AuthenticationType == AuthenticationType.None)
{ {
context.Succeed(requirement); context.Succeed(requirement);
return; return;
} }
@ -34,6 +36,7 @@ public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor
String? GetParam(String name) String? GetParam(String name)
{ {
var value = request.Query[name].ToString(); var value = request.Query[name].ToString();
if (String.IsNullOrWhiteSpace(value) && request.HasFormContentType) if (String.IsNullOrWhiteSpace(value) && request.HasFormContentType)
{ {
value = request.Form[name].ToString(); value = request.Form[name].ToString();
@ -48,20 +51,22 @@ public class SabnzbdHandler(Authentication authentication, IHttpContextAccessor
if (!String.IsNullOrWhiteSpace(maUsername) && !String.IsNullOrWhiteSpace(maPassword)) if (!String.IsNullOrWhiteSpace(maUsername) && !String.IsNullOrWhiteSpace(maPassword))
{ {
var loginResult = await authentication.Login(maUsername, maPassword); var loginResult = await authentication.Login(maUsername, maPassword);
if (loginResult.Succeeded) if (loginResult.Succeeded)
{ {
context.Succeed(requirement); context.Succeed(requirement);
return; return;
} }
// Invalid credentials provided // Invalid credentials provided
context.Fail(); context.Fail();
return; return;
} }
// Authentication required but missing credentials // Authentication required but missing credentials
context.Fail(); context.Fail();
return;
} }
} }
} }

View file

@ -56,32 +56,7 @@ public class AllDebridDebridClient(ILogger<AllDebridDebridClient> logger, IAllDe
{ {
private static readonly Int64 SessionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); private static readonly Int64 SessionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
private static List<DebridClientTorrent> _cache = []; private static List<DebridClientTorrent> _cache = [];
private static Int64 _sessionCounter = 0; private static Int64 _sessionCounter;
private static DebridClientTorrent Map(Magnet torrent)
{
var files = GetFiles(torrent.Files);
return new()
{
Id = torrent.Id.ToString(),
Filename = torrent.Filename ?? "",
OriginalFilename = torrent.Filename,
Hash = torrent.Hash ?? "",
Bytes = torrent.Size ?? 0,
OriginalBytes = torrent.Size ?? 0,
Host = null,
Split = 0,
Progress = (Int64)Math.Round((torrent.Downloaded ?? 0) * 100.0 / (torrent.Size ?? 1)),
Status = torrent.Status,
StatusCode = torrent.StatusCode ?? 0,
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0),
Files = files,
Links = [],
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0),
Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeders
};
}
public async Task<IList<DebridClientTorrent>> GetDownloads() public async Task<IList<DebridClientTorrent>> GetDownloads()
{ {
@ -141,7 +116,7 @@ public class AllDebridDebridClient(ILogger<AllDebridDebridClient> logger, IAllDe
return resultId; return resultId;
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -163,7 +138,7 @@ public class AllDebridDebridClient(ILogger<AllDebridDebridClient> logger, IAllDe
return resultId; return resultId;
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -306,12 +281,39 @@ public class AllDebridDebridClient(ILogger<AllDebridDebridClient> logger, IAllDe
return Task.FromResult(download.FileName); return Task.FromResult(download.FileName);
} }
private static DebridClientTorrent Map(Magnet torrent)
{
var files = GetFiles(torrent.Files);
return new()
{
Id = torrent.Id.ToString(),
Filename = torrent.Filename ?? "",
OriginalFilename = torrent.Filename,
Hash = torrent.Hash ?? "",
Bytes = torrent.Size ?? 0,
OriginalBytes = torrent.Size ?? 0,
Host = null,
Split = 0,
Progress = (Int64)Math.Round(((torrent.Downloaded ?? 0) * 100.0) / (torrent.Size ?? 1)),
Status = torrent.Status,
StatusCode = torrent.StatusCode ?? 0,
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0),
Files = files,
Links = [],
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0),
Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeders
};
}
private async Task<DebridClientTorrent> GetInfo(String torrentId) private async Task<DebridClientTorrent> GetInfo(String torrentId)
{ {
var result = await allDebridNetClientFactory.GetClient().Magnet.StatusAsync(torrentId) ?? throw new($"Unable to find magnet with ID {torrentId}"); var result = await allDebridNetClientFactory.GetClient().Magnet.StatusAsync(torrentId) ?? throw new($"Unable to find magnet with ID {torrentId}");
return Map(result); return Map(result);
} }
private static List<DebridClientFile> GetFiles(List<File>? files, String parentPath = "") private static List<DebridClientFile> GetFiles(List<File>? files, String parentPath = "")
{ {
if (files == null) if (files == null)
@ -325,7 +327,7 @@ public class AllDebridDebridClient(ILogger<AllDebridDebridClient> logger, IAllDe
? file.FolderOrFileName ? file.FolderOrFileName
: Path.Combine(parentPath, file.FolderOrFileName); : Path.Combine(parentPath, file.FolderOrFileName);
var result = new List<DebridClientFile>(); var result = new List<DebridClientFile>();
// If it's a file (has size) // If it's a file (has size)
if (file.Size.HasValue) if (file.Size.HasValue)

View file

@ -14,78 +14,6 @@ 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) : IDebridClient
{ {
private DebridLinkFrNETClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("DebridLink API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
return debridLinkClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
throw;
}
}
private DebridClientTorrent Map(Torrent torrent)
{
return new()
{
Id = torrent.Id ?? "",
Filename = torrent.Name ?? "",
OriginalFilename = torrent.Name ?? "",
Hash = torrent.HashString ?? "",
Bytes = torrent.TotalSize,
OriginalBytes = 0,
Host = torrent.ServerId ?? "",
Split = 0,
Progress = torrent.DownloadPercent,
Status = torrent.Status.ToString(),
Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created),
Files = (torrent.Files ?? []).Select((m, i) => new DebridClientFile
{
Path = m.Name ?? "",
Bytes = m.Size,
Id = i,
Selected = true,
DownloadLink = m.DownloadUrl
})
.ToList(),
Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(),
Ended = null,
Speed = torrent.UploadSpeed,
Seeders = torrent.PeersConnected,
};
}
public async Task<IList<DebridClientTorrent>> GetDownloads() public async Task<IList<DebridClientTorrent>> GetDownloads()
{ {
var page = 0; var page = 0;
@ -128,7 +56,7 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
return result.Id ?? ""; return result.Id ?? "";
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -143,7 +71,7 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
return result.Id ?? ""; return result.Id ?? "";
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -296,6 +224,78 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
return Task.FromResult(download.FileName); return Task.FromResult(download.FileName);
} }
private DebridLinkFrNETClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("DebridLink API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
return debridLinkClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
throw;
}
}
private DebridClientTorrent Map(Torrent torrent)
{
return new()
{
Id = torrent.Id ?? "",
Filename = torrent.Name ?? "",
OriginalFilename = torrent.Name ?? "",
Hash = torrent.HashString ?? "",
Bytes = torrent.TotalSize,
OriginalBytes = 0,
Host = torrent.ServerId ?? "",
Split = 0,
Progress = torrent.DownloadPercent,
Status = torrent.Status.ToString(),
Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created),
Files = (torrent.Files ?? []).Select((m, i) => new DebridClientFile
{
Path = m.Name ?? "",
Bytes = m.Size,
Id = i,
Selected = true,
DownloadLink = m.DownloadUrl
})
.ToList(),
Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(),
Ended = null,
Speed = torrent.UploadSpeed,
Seeders = torrent.PeersConnected
};
}
private async Task<DebridClientTorrent> GetInfo(String torrentId) private async Task<DebridClientTorrent> GetInfo(String torrentId)
{ {
var result = await GetClient().Seedbox.ListAsync(torrentId); var result = await GetClient().Seedbox.ListAsync(torrentId);

View file

@ -12,6 +12,7 @@ public interface IDebridClient
Task<String> AddNzbLink(String nzbLink); Task<String> AddNzbLink(String nzbLink);
Task<String> AddNzbFile(Byte[] bytes, String? name); Task<String> AddNzbFile(Byte[] bytes, String? name);
Task<IList<DebridClientAvailableFile>> GetAvailableFiles(String hash); Task<IList<DebridClientAvailableFile>> GetAvailableFiles(String hash);
/// <summary> /// <summary>
/// Tell the debrid provider which files to download. /// Tell the debrid provider which files to download.
/// </summary> /// </summary>
@ -21,6 +22,7 @@ public interface IDebridClient
/// <param name="torrent">The torrent to select files for</param> /// <param name="torrent">The torrent to select files for</param>
/// <returns>Number of files selected</returns> /// <returns>Number of files selected</returns>
Task<Int32?> SelectFiles(Torrent torrent); Task<Int32?> SelectFiles(Torrent torrent);
Task Delete(Torrent torrent); Task Delete(Torrent torrent);
Task<String> Unrestrict(Torrent torrent, String link); Task<String> Unrestrict(Torrent torrent, String link);
Task<Torrent> UpdateData(Torrent torrent, DebridClientTorrent? torrentClientTorrent); Task<Torrent> UpdateData(Torrent torrent, DebridClientTorrent? torrentClientTorrent);

View file

@ -13,66 +13,6 @@ 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) : IDebridClient
{ {
private PremiumizeNETClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("Premiumize API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);
var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient);
return premiumizeNetClient;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
throw;
}
}
private static DebridClientTorrent Map(Transfer transfer)
{
return new()
{
Id = transfer.Id,
Filename = transfer.Name,
OriginalFilename = transfer.Name,
Hash = transfer.Src,
Bytes = 0,
OriginalBytes = 0,
Host = null,
Split = 0,
Progress = (Int64) ((transfer.Progress ?? 1.0) * 100.0),
Status = transfer.Status,
Message = transfer.Message,
StatusCode = 0,
Added = null,
Files = [],
Links =
[
transfer.FolderId
],
Ended = null,
Speed = 0,
Seeders = 0
};
}
public async Task<IList<DebridClientTorrent>> GetDownloads() public async Task<IList<DebridClientTorrent>> GetDownloads()
{ {
var results = await GetClient().Transfers.ListAsync(); var results = await GetClient().Transfers.ListAsync();
@ -107,7 +47,7 @@ public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHtt
return resultId; return resultId;
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -129,7 +69,7 @@ public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHtt
return resultId; return resultId;
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -281,6 +221,66 @@ public class PremiumizeDebridClient(ILogger<PremiumizeDebridClient> logger, IHtt
return Task.FromResult(download.FileName); return Task.FromResult(download.FileName);
} }
private PremiumizeNETClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("Premiumize API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);
var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient);
return premiumizeNetClient;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
throw;
}
}
private static DebridClientTorrent Map(Transfer transfer)
{
return new()
{
Id = transfer.Id,
Filename = transfer.Name,
OriginalFilename = transfer.Name,
Hash = transfer.Src,
Bytes = 0,
OriginalBytes = 0,
Host = null,
Split = 0,
Progress = (Int64)((transfer.Progress ?? 1.0) * 100.0),
Status = transfer.Status,
Message = transfer.Message,
StatusCode = 0,
Added = null,
Files = [],
Links =
[
transfer.FolderId
],
Ended = null,
Speed = 0,
Seeders = 0
};
}
private async Task<DebridClientTorrent> GetInfo(String id) private async Task<DebridClientTorrent> GetInfo(String id)
{ {
var results = await GetClient().Transfers.ListAsync(); var results = await GetClient().Transfers.ListAsync();

View file

@ -16,84 +16,6 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
{ {
private TimeSpan? _offset; private TimeSpan? _offset;
private RdNetClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("Real-Debrid API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname);
rdtNetClient.UseApiAuthentication(apiKey);
// Get the server time to fix up the timezones on results
if (_offset == null)
{
var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result;
_offset = serverTime.Offset;
}
return rdtNetClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
throw;
}
}
private DebridClientTorrent Map(Torrent torrent)
{
return new()
{
Id = torrent.Id,
Filename = torrent.Filename,
OriginalFilename = torrent.OriginalFilename,
Hash = torrent.Hash,
Bytes = torrent.Bytes,
OriginalBytes = torrent.OriginalBytes,
Host = torrent.Host,
Split = torrent.Split,
Progress = torrent.Progress,
Status = torrent.Status,
Added = ChangeTimeZone(torrent.Added)!.Value,
Files = (torrent.Files ?? []).Select(m => new DebridClientFile
{
Path = m.Path,
Bytes = m.Bytes,
Id = m.Id,
Selected = m.Selected
}).ToList(),
Links = torrent.Links,
Ended = ChangeTimeZone(torrent.Ended),
Speed = torrent.Speed,
Seeders = torrent.Seeders,
};
}
public async Task<IList<DebridClientTorrent>> GetDownloads() public async Task<IList<DebridClientTorrent>> GetDownloads()
{ {
var offset = 0; var offset = 0;
@ -138,7 +60,7 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
return result.Id; return result.Id;
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -155,7 +77,7 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
return result.Id; return result.Id;
} }
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) || catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase) ||
ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase)) ex.Message.Contains("rate limit exceeded", StringComparison.OrdinalIgnoreCase))
{ {
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2)); throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
} }
@ -388,6 +310,84 @@ public class RealDebridDebridClient(ILogger<RealDebridDebridClient> logger, IHtt
return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last())); return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last()));
} }
private RdNetClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("Real-Debrid API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname);
rdtNetClient.UseApiAuthentication(apiKey);
// Get the server time to fix up the timezones on results
if (_offset == null)
{
var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result;
_offset = serverTime.Offset;
}
return rdtNetClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
throw;
}
}
private DebridClientTorrent Map(Torrent torrent)
{
return new()
{
Id = torrent.Id,
Filename = torrent.Filename,
OriginalFilename = torrent.OriginalFilename,
Hash = torrent.Hash,
Bytes = torrent.Bytes,
OriginalBytes = torrent.OriginalBytes,
Host = torrent.Host,
Split = torrent.Split,
Progress = torrent.Progress,
Status = torrent.Status,
Added = ChangeTimeZone(torrent.Added)!.Value,
Files = (torrent.Files ?? []).Select(m => new DebridClientFile
{
Path = m.Path,
Bytes = m.Bytes,
Id = m.Id,
Selected = m.Selected
})
.ToList(),
Links = torrent.Links,
Ended = ChangeTimeZone(torrent.Ended),
Speed = torrent.Speed,
Seeders = torrent.Seeders
};
}
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
{ {

View file

@ -2,8 +2,8 @@ using System.Diagnostics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using RdtClient.Data.Enums; using RdtClient.Data.Enums;
using RdtClient.Data.Models.DebridClient;
using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Data;
using RdtClient.Data.Models.DebridClient;
using RdtClient.Data.Models.Internal; using RdtClient.Data.Models.Internal;
using RdtClient.Service.Helpers; using RdtClient.Service.Helpers;
using TorBoxNET; using TorBoxNET;
@ -14,166 +14,34 @@ namespace RdtClient.Service.Services.DebridClients;
public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : IDebridClient public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : IDebridClient
{ {
private TimeSpan? _offset; private TimeSpan? _offset;
protected virtual ITorBoxNetClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("TorBox API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient(DiConfig.TORBOX_CLIENT);
var torBoxNetClient = new TorBoxNetClient(null, httpClient, 1);
torBoxNetClient.UseApiAuthentication(apiKey);
// Get the server time to fix up the timezones on results
if (_offset == null)
{
var serverTime = DateTimeOffset.UtcNow;
_offset = serverTime.Offset;
}
return torBoxNetClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to TorBox has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
throw;
}
}
protected virtual async Task<IEnumerable<TorrentInfoResult>?> GetCurrentTorrents()
{
return await GetClient().Torrents.GetCurrentAsync(true);
}
protected virtual async Task<IEnumerable<TorrentInfoResult>?> GetQueuedTorrents()
{
return await GetClient().Torrents.GetQueuedAsync(true);
}
protected virtual async Task<IEnumerable<UsenetInfoResult>?> GetCurrentUsenet()
{
return await GetClient().Usenet.GetCurrentAsync(true);
}
protected virtual async Task<IEnumerable<UsenetInfoResult>?> GetQueuedUsenet()
{
return await GetClient().Usenet.GetQueuedAsync(true);
}
protected virtual async Task<Response<List<AvailableTorrent?>>> GetTorrentAvailability(String hash)
{
return await GetClient().Torrents.GetAvailabilityAsync(hash, listFiles: true);
}
protected virtual async Task<Response<List<AvailableUsenet?>>> GetUsenetAvailability(String hash)
{
return await GetClient().Usenet.GetAvailabilityAsync(hash, listFiles: true);
}
private DebridClientTorrent Map(TorrentInfoResult torrent)
{
return new()
{
Id = torrent.Hash,
Filename = torrent.Name,
OriginalFilename = torrent.Name,
Hash = torrent.Hash,
Bytes = torrent.Size,
OriginalBytes = torrent.Size,
Host = torrent.DownloadPresent.ToString(),
Split = 0,
Progress = (Int64)(torrent.Progress * 100.0),
Status = torrent.DownloadState,
Type = DownloadType.Torrent,
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
Files = (torrent.Files ?? []).Select(m => new DebridClientFile
{
Path = String.Join("/", m.Name.Split('/').Skip(1)),
Bytes = m.Size,
Id = m.Id,
Selected = true
}).ToList(),
Links = [],
Ended = ChangeTimeZone(torrent.UpdatedAt),
Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeds,
};
}
private DebridClientTorrent Map(UsenetInfoResult usenet)
{
return new()
{
Id = usenet.Hash,
Filename = usenet.Name,
OriginalFilename = usenet.Name,
Hash = usenet.Hash,
Bytes = usenet.Size,
OriginalBytes = usenet.Size,
Host = usenet.DownloadPresent.ToString(),
Split = 0,
Progress = (Int64)(usenet.Progress * 100.0),
Status = usenet.DownloadState,
Type = DownloadType.Nzb,
Added = ChangeTimeZone(usenet.CreatedAt)!.Value,
Files = (usenet.Files ?? []).Select(m => new DebridClientFile
{
Path = String.Join("/", m.Name.Split('/').Skip(1)),
Bytes = m.Size,
Id = m.Id,
Selected = true
}).ToList(),
Links = [],
Ended = ChangeTimeZone(usenet.UpdatedAt),
Speed = usenet.DownloadSpeed,
Seeders = 0,
};
}
public async Task<IList<DebridClientTorrent>> GetDownloads() public async Task<IList<DebridClientTorrent>> GetDownloads()
{ {
var results = new List<DebridClientTorrent>(); var results = new List<DebridClientTorrent>();
var currentTorrents = await GetCurrentTorrents(); var currentTorrents = await GetCurrentTorrents();
if (currentTorrents != null) if (currentTorrents != null)
{ {
results.AddRange(currentTorrents.Select(Map)); results.AddRange(currentTorrents.Select(Map));
} }
var queuedTorrents = await GetQueuedTorrents(); var queuedTorrents = await GetQueuedTorrents();
if (queuedTorrents != null) if (queuedTorrents != null)
{ {
results.AddRange(queuedTorrents.Select(Map)); results.AddRange(queuedTorrents.Select(Map));
} }
var currentNzbs = await GetCurrentUsenet(); var currentNzbs = await GetCurrentUsenet();
if (currentNzbs != null) if (currentNzbs != null)
{ {
results.AddRange(currentNzbs.Select(Map)); results.AddRange(currentNzbs.Select(Map));
} }
var queuedNzbs = await GetQueuedUsenet(); var queuedNzbs = await GetQueuedUsenet();
if (queuedNzbs != null) if (queuedNzbs != null)
{ {
results.AddRange(queuedNzbs.Select(Map)); results.AddRange(queuedNzbs.Select(Map));
@ -193,60 +61,13 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
}; };
} }
private async Task<String> HandleAddTorrentErrors(Func<Boolean, Task<String>> action)
{
try
{
return await action(false);
}
catch (RateLimitException)
{
throw;
}
catch (Exception ex) when (ex.InnerException is RateLimitException rateLimitException)
{
throw rateLimitException;
}
catch (TorBoxException ex) when (ex.Error.Equals("active_limit", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
}
private async Task<String> HandleAddUsenetErrors(Func<Boolean, Task<String>> action)
{
try
{
return await action(false);
}
catch (RateLimitException)
{
throw;
}
catch (Exception ex) when (ex.InnerException is RateLimitException rateLimitException)
{
throw rateLimitException;
}
catch (TorBoxException ex) when (ex.Error.Equals("active_limit", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
}
public async Task<String> AddTorrentMagnet(String magnetLink) public async Task<String> AddTorrentMagnet(String magnetLink)
{ {
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().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued); var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued);
return result.Data!.Hash!; return result.Data!.Hash!;
}); });
} }
@ -257,6 +78,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
{ {
var user = await GetClient().User.GetAsync(true); var user = await GetClient().User.GetAsync(true);
var result = await GetClient().Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued); var result = await GetClient().Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued);
return result.Data!.Hash!; return result.Data!.Hash!;
}); });
} }
@ -266,6 +88,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
return await HandleAddUsenetErrors(async asQueued => return await HandleAddUsenetErrors(async asQueued =>
{ {
var result = await GetClient().Usenet.AddLinkAsync(nzbLink, as_queued: asQueued); var result = await GetClient().Usenet.AddLinkAsync(nzbLink, as_queued: asQueued);
return result.Data!.Hash!; return result.Data!.Hash!;
}); });
} }
@ -275,6 +98,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
return await HandleAddUsenetErrors(async asQueued => return await HandleAddUsenetErrors(async asQueued =>
{ {
var result = await GetClient().Usenet.AddFileAsync(bytes, name: name, as_queued: asQueued); var result = await GetClient().Usenet.AddFileAsync(bytes, name: name, as_queued: asQueued);
return result.Data!.Hash!; return result.Data!.Hash!;
}); });
} }
@ -286,10 +110,11 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
if (availability.Data != null && availability.Data.Count > 0) if (availability.Data != null && availability.Data.Count > 0)
{ {
return (availability.Data[0]?.Files ?? []).Select(file => new DebridClientAvailableFile return (availability.Data[0]?.Files ?? []).Select(file => new DebridClientAvailableFile
{ {
Filename = file.Name, Filename = file.Name,
Filesize = file.Size Filesize = file.Size
}).ToList(); })
.ToList();
} }
var usenetAvailability = await GetUsenetAvailability(hash); var usenetAvailability = await GetUsenetAvailability(hash);
@ -297,10 +122,11 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
if (usenetAvailability.Data != null && usenetAvailability.Data.Count > 0) if (usenetAvailability.Data != null && usenetAvailability.Data.Count > 0)
{ {
return (usenetAvailability.Data[0]?.Files ?? []).Select(file => new DebridClientAvailableFile return (usenetAvailability.Data[0]?.Files ?? []).Select(file => new DebridClientAvailableFile
{ {
Filename = file.Name, Filename = file.Name,
Filesize = file.Size Filesize = file.Size
}).ToList(); })
.ToList();
} }
return []; return [];
@ -337,6 +163,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
} }
var segments = link.Split('/'); var segments = link.Split('/');
if (segments is not [_, _, _, _, var torrentIdStr, var fileIdStrOrZip]) if (segments is not [_, _, _, _, var torrentIdStr, var fileIdStrOrZip])
{ {
throw new ArgumentException($"Invalid link format: {link}", nameof(link)); throw new ArgumentException($"Invalid link format: {link}", nameof(link));
@ -426,6 +253,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
else else
{ {
logger.LogTrace("Updating status for {TorrentName} from {OldStatus} to {NewStatus}", torrent.RdName, torrent.RdStatus, rdTorrent.Status); logger.LogTrace("Updating status for {TorrentName} from {OldStatus} to {NewStatus}", torrent.RdName, torrent.RdStatus, rdTorrent.Status);
torrent.RdStatus = rdTorrent.Status switch torrent.RdStatus = rdTorrent.Status switch
{ {
"allocating" => TorrentStatus.Processing, "allocating" => TorrentStatus.Processing,
@ -482,7 +310,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
} }
else else
{ {
var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, skipCache: true); var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, true);
id = torrentId?.Id; id = torrentId?.Id;
} }
@ -490,6 +318,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
{ {
return null; return null;
} }
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.Get.Provider.PreferZippedDownloads)
@ -525,6 +354,192 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
return Task.FromResult(download.FileName); return Task.FromResult(download.FileName);
} }
protected virtual ITorBoxNetClient GetClient()
{
try
{
var apiKey = Settings.Get.Provider.ApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new("TorBox API Key not set in the settings");
}
var httpClient = httpClientFactory.CreateClient(DiConfig.TORBOX_CLIENT);
var torBoxNetClient = new TorBoxNetClient(null, httpClient);
torBoxNetClient.UseApiAuthentication(apiKey);
// Get the server time to fix up the timezones on results
if (_offset == null)
{
var serverTime = DateTimeOffset.UtcNow;
_offset = serverTime.Offset;
}
return torBoxNetClient;
}
catch (AggregateException ae)
{
foreach (var inner in ae.InnerExceptions)
{
logger.LogError(inner, $"The connection to TorBox has failed: {inner.Message}");
}
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
throw;
}
catch (TaskCanceledException ex)
{
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
throw;
}
}
protected virtual async Task<IEnumerable<TorrentInfoResult>?> GetCurrentTorrents()
{
return await GetClient().Torrents.GetCurrentAsync(true);
}
protected virtual async Task<IEnumerable<TorrentInfoResult>?> GetQueuedTorrents()
{
return await GetClient().Torrents.GetQueuedAsync(true);
}
protected virtual async Task<IEnumerable<UsenetInfoResult>?> GetCurrentUsenet()
{
return await GetClient().Usenet.GetCurrentAsync(true);
}
protected virtual async Task<IEnumerable<UsenetInfoResult>?> GetQueuedUsenet()
{
return await GetClient().Usenet.GetQueuedAsync(true);
}
protected virtual async Task<Response<List<AvailableTorrent?>>> GetTorrentAvailability(String hash)
{
return await GetClient().Torrents.GetAvailabilityAsync(hash, true);
}
protected virtual async Task<Response<List<AvailableUsenet?>>> GetUsenetAvailability(String hash)
{
return await GetClient().Usenet.GetAvailabilityAsync(hash, true);
}
private DebridClientTorrent Map(TorrentInfoResult torrent)
{
return new()
{
Id = torrent.Hash,
Filename = torrent.Name,
OriginalFilename = torrent.Name,
Hash = torrent.Hash,
Bytes = torrent.Size,
OriginalBytes = torrent.Size,
Host = torrent.DownloadPresent.ToString(),
Split = 0,
Progress = (Int64)(torrent.Progress * 100.0),
Status = torrent.DownloadState,
Type = DownloadType.Torrent,
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
Files = (torrent.Files ?? []).Select(m => new DebridClientFile
{
Path = String.Join("/", m.Name.Split('/').Skip(1)),
Bytes = m.Size,
Id = m.Id,
Selected = true
})
.ToList(),
Links = [],
Ended = ChangeTimeZone(torrent.UpdatedAt),
Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeds
};
}
private DebridClientTorrent Map(UsenetInfoResult usenet)
{
return new()
{
Id = usenet.Hash,
Filename = usenet.Name,
OriginalFilename = usenet.Name,
Hash = usenet.Hash,
Bytes = usenet.Size,
OriginalBytes = usenet.Size,
Host = usenet.DownloadPresent.ToString(),
Split = 0,
Progress = (Int64)(usenet.Progress * 100.0),
Status = usenet.DownloadState,
Type = DownloadType.Nzb,
Added = ChangeTimeZone(usenet.CreatedAt)!.Value,
Files = (usenet.Files ?? []).Select(m => new DebridClientFile
{
Path = String.Join("/", m.Name.Split('/').Skip(1)),
Bytes = m.Size,
Id = m.Id,
Selected = true
})
.ToList(),
Links = [],
Ended = ChangeTimeZone(usenet.UpdatedAt),
Speed = usenet.DownloadSpeed,
Seeders = 0
};
}
private async Task<String> HandleAddTorrentErrors(Func<Boolean, Task<String>> action)
{
try
{
return await action(false);
}
catch (RateLimitException)
{
throw;
}
catch (Exception ex) when (ex.InnerException is RateLimitException rateLimitException)
{
throw rateLimitException;
}
catch (TorBoxException ex) when (ex.Error.Equals("active_limit", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
}
private async Task<String> HandleAddUsenetErrors(Func<Boolean, Task<String>> action)
{
try
{
return await action(false);
}
catch (RateLimitException)
{
throw;
}
catch (Exception ex) when (ex.InnerException is RateLimitException rateLimitException)
{
throw rateLimitException;
}
catch (TorBoxException ex) when (ex.Error.Equals("active_limit", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
catch (Exception ex) when (ex.Message.Contains("slow_down", StringComparison.OrdinalIgnoreCase))
{
throw new RateLimitException(ex.Message, TimeSpan.FromMinutes(2));
}
}
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset) private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
{ {
@ -540,7 +555,8 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
{ {
if (type == DownloadType.Nzb) if (type == DownloadType.Nzb)
{ {
var usenet = await GetClient().Usenet.GetHashInfoAsync(id, skipCache: true); var usenet = await GetClient().Usenet.GetHashInfoAsync(id, true);
if (usenet != null) if (usenet != null)
{ {
return Map(usenet); return Map(usenet);
@ -548,7 +564,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
} }
else else
{ {
var result = await GetClient().Torrents.GetHashInfoAsync(id, skipCache: true); var result = await GetClient().Torrents.GetHashInfoAsync(id, true);
if (result != null) if (result != null)
{ {
@ -568,6 +584,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
var innerFolder = Directory.GetDirectories(hashDir)[0]; var innerFolder = Directory.GetDirectories(hashDir)[0];
var moveDir = extractPath; var moveDir = extractPath;
if (!extractPath.EndsWith(torrent.RdName!)) if (!extractPath.EndsWith(torrent.RdName!))
{ {
moveDir = hashDir; moveDir = hashDir;

View file

@ -1,8 +1,8 @@
using RdtClient.Data.Enums; using RdtClient.Data.Enums;
using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Data;
using RdtClient.Service.Helpers; using RdtClient.Service.Helpers;
using RdtClient.Service.Services.Downloaders;
using RdtClient.Service.Services.DebridClients; using RdtClient.Service.Services.DebridClients;
using RdtClient.Service.Services.Downloaders;
namespace RdtClient.Service.Services; namespace RdtClient.Service.Services;

View file

@ -224,6 +224,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
var bytesDone = (Int64)(bytesTotal * rdProgress); var bytesDone = (Int64)(bytesTotal * rdProgress);
Double progress; Double progress;
if (torrent.Completed != null) if (torrent.Completed != null)
{ {
progress = 1.0; progress = 1.0;
@ -251,6 +252,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
} }
var remaining = TimeSpan.Zero; var remaining = TimeSpan.Zero;
if (progress > 0 && progress < 1.0) if (progress > 0 && progress < 1.0)
{ {
var startTime = torrent.Retry > torrent.Added ? torrent.Retry.Value : torrent.Added; var startTime = torrent.Retry > torrent.Added ? torrent.Retry.Value : torrent.Added;

View file

@ -10,70 +10,73 @@ public class RemoteService(IHubContext<RdtHub> hub, Torrents torrents)
var allTorrents = await torrents.Get(); var allTorrents = await torrents.Get();
var torrentDtos = allTorrents.Select(torrent => new TorrentDto var torrentDtos = allTorrents.Select(torrent => new TorrentDto
{ {
TorrentId = torrent.TorrentId, TorrentId = torrent.TorrentId,
Hash = torrent.Hash, Hash = torrent.Hash,
Category = torrent.Category, Category = torrent.Category,
DownloadAction = torrent.DownloadAction, DownloadAction = torrent.DownloadAction,
FinishedAction = torrent.FinishedAction, FinishedAction = torrent.FinishedAction,
FinishedActionDelay = torrent.FinishedActionDelay, FinishedActionDelay = torrent.FinishedActionDelay,
HostDownloadAction = torrent.HostDownloadAction, HostDownloadAction = torrent.HostDownloadAction,
DownloadMinSize = torrent.DownloadMinSize, DownloadMinSize = torrent.DownloadMinSize,
IncludeRegex = torrent.IncludeRegex, IncludeRegex = torrent.IncludeRegex,
ExcludeRegex = torrent.ExcludeRegex, ExcludeRegex = torrent.ExcludeRegex,
DownloadManualFiles = torrent.DownloadManualFiles, DownloadManualFiles = torrent.DownloadManualFiles,
DownloadClient = torrent.DownloadClient, DownloadClient = torrent.DownloadClient,
Added = torrent.Added, Added = torrent.Added,
FilesSelected = torrent.FilesSelected, FilesSelected = torrent.FilesSelected,
Completed = torrent.Completed, Completed = torrent.Completed,
Type = torrent.Type, Type = torrent.Type,
IsFile = torrent.IsFile, IsFile = torrent.IsFile,
Priority = torrent.Priority, Priority = torrent.Priority,
RetryCount = torrent.RetryCount, RetryCount = torrent.RetryCount,
DownloadRetryAttempts = torrent.DownloadRetryAttempts, DownloadRetryAttempts = torrent.DownloadRetryAttempts,
TorrentRetryAttempts = torrent.TorrentRetryAttempts, TorrentRetryAttempts = torrent.TorrentRetryAttempts,
DeleteOnError = torrent.DeleteOnError, DeleteOnError = torrent.DeleteOnError,
Lifetime = torrent.Lifetime, Lifetime = torrent.Lifetime,
Error = torrent.Error, Error = torrent.Error,
RdId = torrent.RdId, RdId = torrent.RdId,
RdName = torrent.RdName, RdName = torrent.RdName,
RdSize = torrent.RdSize, RdSize = torrent.RdSize,
RdHost = torrent.RdHost, RdHost = torrent.RdHost,
RdSplit = torrent.RdSplit, RdSplit = torrent.RdSplit,
RdProgress = torrent.RdProgress, RdProgress = torrent.RdProgress,
RdStatus = torrent.RdStatus, RdStatus = torrent.RdStatus,
RdStatusRaw = torrent.RdStatusRaw, RdStatusRaw = torrent.RdStatusRaw,
RdAdded = torrent.RdAdded, RdAdded = torrent.RdAdded,
RdEnded = torrent.RdEnded, RdEnded = torrent.RdEnded,
RdSpeed = torrent.RdSpeed, RdSpeed = torrent.RdSpeed,
RdSeeders = torrent.RdSeeders, RdSeeders = torrent.RdSeeders,
Files = torrent.Files, Files = torrent.Files,
Downloads = torrent.Downloads.Select(download => Downloads = torrent.Downloads.Select(download =>
{ {
var (speed, bytesTotal, bytesDone) = torrents.GetDownloadStats(download.DownloadId); var (speed, bytesTotal, bytesDone) = torrents.GetDownloadStats(download.DownloadId);
return new DownloadDto
{
DownloadId = download.DownloadId,
TorrentId = download.TorrentId,
Path = download.Path,
Link = download.Link,
Added = download.Added,
DownloadQueued = download.DownloadQueued,
DownloadStarted = download.DownloadStarted,
DownloadFinished = download.DownloadFinished,
UnpackingQueued = download.UnpackingQueued,
UnpackingStarted = download.UnpackingStarted,
UnpackingFinished = download.UnpackingFinished,
Completed = download.Completed,
RetryCount = download.RetryCount,
Error = download.Error,
BytesTotal = bytesTotal,
BytesDone = bytesDone,
Speed = speed
};
})
.ToList()
})
.ToList();
return new DownloadDto
{
DownloadId = download.DownloadId,
TorrentId = download.TorrentId,
Path = download.Path,
Link = download.Link,
Added = download.Added,
DownloadQueued = download.DownloadQueued,
DownloadStarted = download.DownloadStarted,
DownloadFinished = download.DownloadFinished,
UnpackingQueued = download.UnpackingQueued,
UnpackingStarted = download.UnpackingStarted,
UnpackingFinished = download.UnpackingFinished,
Completed = download.Completed,
RetryCount = download.RetryCount,
Error = download.Error,
BytesTotal = bytesTotal,
BytesDone = bytesDone,
Speed = speed
};
}).ToList()
}).ToList();
await hub.Clients.All.SendCoreAsync("update", await hub.Clients.All.SendCoreAsync("update",
[ [
torrentDtos torrentDtos

View file

@ -18,62 +18,65 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
{ {
NoOfSlots = activeTorrents.Count, NoOfSlots = activeTorrents.Count,
Slots = activeTorrents.Select((t, index) => Slots = activeTorrents.Select((t, index) =>
{ {
var rdProgress = Math.Clamp(t.RdProgress ?? 0.0, 0.0, 100.0) / 100.0; var rdProgress = Math.Clamp(t.RdProgress ?? 0.0, 0.0, 100.0) / 100.0;
Double progress; Double progress;
var dlStats = t.Downloads.Select(m => torrents.GetDownloadStats(m.DownloadId)).ToList(); var dlStats = t.Downloads.Select(m => torrents.GetDownloadStats(m.DownloadId)).ToList();
if (dlStats.Count > 0)
{
var bytesDone = dlStats.Sum(m => m.BytesDone);
var bytesTotal = dlStats.Sum(m => m.BytesTotal);
var downloadProgress = bytesTotal > 0 ? Math.Clamp((Double)bytesDone / bytesTotal, 0.0, 1.0) : 0;
progress = (rdProgress + downloadProgress) / 2.0;
}
else
{
progress = rdProgress;
}
var timeLeft = "0:00:00"; if (dlStats.Count > 0)
var startTime = t.Retry > t.Added ? t.Retry.Value : t.Added; {
var elapsed = DateTimeOffset.UtcNow - startTime; var bytesDone = dlStats.Sum(m => m.BytesDone);
var bytesTotal = dlStats.Sum(m => m.BytesTotal);
var downloadProgress = bytesTotal > 0 ? Math.Clamp((Double)bytesDone / bytesTotal, 0.0, 1.0) : 0;
progress = (rdProgress + downloadProgress) / 2.0;
}
else
{
progress = rdProgress;
}
if (progress is > 0 and < 1.0) var timeLeft = "0:00:00";
{ var startTime = t.Retry > t.Added ? t.Retry.Value : t.Added;
var totalEstimatedTime = TimeSpan.FromTicks((Int64)(elapsed.Ticks / progress)); var elapsed = DateTimeOffset.UtcNow - startTime;
var remaining = totalEstimatedTime - elapsed;
if (remaining.TotalSeconds > 0)
{
timeLeft = $"{(Int32)remaining.TotalHours}:{remaining.Minutes:D2}:{remaining.Seconds:D2}";
}
}
return new SabnzbdQueueSlot if (progress is > 0 and < 1.0)
{ {
Index = index, var totalEstimatedTime = TimeSpan.FromTicks((Int64)(elapsed.Ticks / progress));
NzoId = t.Hash, var remaining = totalEstimatedTime - elapsed;
Filename = t.RdName ?? t.Hash,
Size = FileSizeHelper.FormatSize(dlStats.Sum(d => d.BytesTotal)),
SizeLeft = FileSizeHelper.FormatSize(dlStats.Sum(d => d.BytesTotal - d.BytesDone)),
Percentage = (progress * 100.0).ToString("0"),
Status = t.RdStatus switch if (remaining.TotalSeconds > 0)
{ {
TorrentStatus.Queued => "Queued", timeLeft = $"{(Int32)remaining.TotalHours}:{remaining.Minutes:D2}:{remaining.Seconds:D2}";
TorrentStatus.Processing => "Downloading", }
TorrentStatus.WaitingForFileSelection => "Downloading", }
TorrentStatus.Downloading => "Downloading",
TorrentStatus.Uploading => "Downloading", return new SabnzbdQueueSlot
TorrentStatus.Finished => "Completed", {
TorrentStatus.Error => "Failed", Index = index,
_ => "Downloading" NzoId = t.Hash,
}, Filename = t.RdName ?? t.Hash,
Category = t.Category ?? "*", Size = FileSizeHelper.FormatSize(dlStats.Sum(d => d.BytesTotal)),
Priority = "Normal", SizeLeft = FileSizeHelper.FormatSize(dlStats.Sum(d => d.BytesTotal - d.BytesDone)),
TimeLeft = timeLeft Percentage = (progress * 100.0).ToString("0"),
};
}).ToList() Status = t.RdStatus switch
{
TorrentStatus.Queued => "Queued",
TorrentStatus.Processing => "Downloading",
TorrentStatus.WaitingForFileSelection => "Downloading",
TorrentStatus.Downloading => "Downloading",
TorrentStatus.Uploading => "Downloading",
TorrentStatus.Finished => "Completed",
TorrentStatus.Error => "Failed",
_ => "Downloading"
},
Category = t.Category ?? "*",
Priority = "Normal",
TimeLeft = timeLeft
};
})
.ToList()
}; };
return queue; return queue;
@ -91,29 +94,30 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
NoOfSlots = completedTorrents.Count, NoOfSlots = completedTorrents.Count,
TotalSlots = completedTorrents.Count, TotalSlots = completedTorrents.Count,
Slots = completedTorrents.Select(t => Slots = completedTorrents.Select(t =>
{ {
var path = savePath; var path = savePath;
if (!String.IsNullOrWhiteSpace(t.Category)) if (!String.IsNullOrWhiteSpace(t.Category))
{ {
path = Path.Combine(path, t.Category); path = Path.Combine(path, t.Category);
} }
if (!String.IsNullOrWhiteSpace(t.RdName)) if (!String.IsNullOrWhiteSpace(t.RdName))
{ {
path = Path.Combine(path, t.RdName); path = Path.Combine(path, t.RdName);
} }
return new SabnzbdHistorySlot return new SabnzbdHistorySlot
{ {
NzoId = t.Hash, NzoId = t.Hash,
Name = t.RdName ?? t.Hash, Name = t.RdName ?? t.Hash,
Size = FileSizeHelper.FormatSize(t.Downloads.Sum(d => d.BytesTotal)), Size = FileSizeHelper.FormatSize(t.Downloads.Sum(d => d.BytesTotal)),
Status = String.IsNullOrWhiteSpace(t.Error) ? "Completed" : "Failed", Status = String.IsNullOrWhiteSpace(t.Error) ? "Completed" : "Failed",
Category = t.Category ?? "Default", Category = t.Category ?? "Default",
Path = path Path = path
}; };
}).ToList() })
.ToList()
}; };
return history; return history;
@ -142,6 +146,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
}; };
var result = await torrents.AddNzbFileToDebridQueue(fileBytes, fileName, torrent); var result = await torrents.AddNzbFileToDebridQueue(fileBytes, fileName, torrent);
return result.Hash; return result.Hash;
} }
@ -168,6 +173,7 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
}; };
var result = await torrents.AddNzbLinkToDebridQueue(url, torrent); var result = await torrents.AddNzbLinkToDebridQueue(url, torrent);
return result.Hash; return result.Hash;
} }
@ -229,11 +235,12 @@ public class Sabnzbd(ILogger<Sabnzbd> logger, Torrents torrents, AppSettings app
var categoryList = GetCategories(); var categoryList = GetCategories();
var categories = categoryList.Select((c, i) => new SabnzbdCategory var categories = categoryList.Select((c, i) => new SabnzbdCategory
{ {
Name = c, Name = c,
Order = i, Order = i,
Dir = c == "*" ? "" : Path.Combine(savePath, c) Dir = c == "*" ? "" : Path.Combine(savePath, c)
}).ToList(); })
.ToList();
var config = new SabnzbdConfig var config = new SabnzbdConfig
{ {

View file

@ -16,6 +16,10 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new(); public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new(); public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
public static Boolean IsPausedForLowDiskSpace { get; set; }
public static DateTimeOffset NextDequeueTime { get; private set; } = DateTimeOffset.MinValue;
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)) if (ActiveDownloadClients.TryGetValue(downloadId, out var downloadClient))
@ -31,10 +35,6 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
return (0, 0, 0); return (0, 0, 0);
} }
public static Boolean IsPausedForLowDiskSpace { get; set; }
public static DateTimeOffset NextDequeueTime { get; private set; } = DateTimeOffset.MinValue;
public async Task Initialize() public async Task Initialize()
{ {
Log("Initializing TorrentRunner"); Log("Initializing TorrentRunner");
@ -359,7 +359,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
{ {
NextDequeueTime = DateTimeOffset.MinValue; NextDequeueTime = DateTimeOffset.MinValue;
await remoteService.UpdateRateLimitStatus(new RateLimitStatus await remoteService.UpdateRateLimitStatus(new()
{ {
NextDequeueTime = null, NextDequeueTime = null,
SecondsRemaining = 0 SecondsRemaining = 0
@ -568,6 +568,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
catch (Exception ex) catch (Exception ex)
{ {
LogError($"Unable to start download: {ex.Message}", download, torrent); LogError($"Unable to start download: {ex.Message}", download, torrent);
continue; continue;
} }
@ -755,7 +756,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
Log($"Rate-limit reached, pausing dequeuing for {retryAfter.TotalMinutes} minutes (until {NextDequeueTime}): {message}"); Log($"Rate-limit reached, pausing dequeuing for {retryAfter.TotalMinutes} minutes (until {NextDequeueTime}): {message}");
await remoteService.UpdateRateLimitStatus(new RateLimitStatus await remoteService.UpdateRateLimitStatus(new()
{ {
NextDequeueTime = NextDequeueTime, NextDequeueTime = NextDequeueTime,
SecondsRemaining = retryAfter.TotalSeconds SecondsRemaining = retryAfter.TotalSeconds

View file

@ -1,5 +1,6 @@
using System.Globalization; using System.Globalization;
using System.IO.Abstractions; using System.IO.Abstractions;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -10,8 +11,8 @@ using MonoTorrent;
using RdtClient.Data.Data; 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.DebridClient; using RdtClient.Data.Models.DebridClient;
using RdtClient.Data.Models.Internal;
using RdtClient.Service.BackgroundServices; using RdtClient.Service.BackgroundServices;
using RdtClient.Service.Helpers; using RdtClient.Service.Helpers;
using RdtClient.Service.Services.DebridClients; using RdtClient.Service.Services.DebridClients;
@ -40,6 +41,8 @@ public class Torrents(
ReferenceHandler = ReferenceHandler.IgnoreCycles ReferenceHandler = ReferenceHandler.IgnoreCycles
}; };
private static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
private IDebridClient DebridClient private IDebridClient DebridClient
{ {
get get
@ -56,8 +59,6 @@ public class Torrents(
} }
} }
private static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
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 TorrentRunner.GetStats(downloadId);
@ -99,16 +100,18 @@ public class Torrents(
public virtual async Task<Torrent> AddNzbLinkToDebridQueue(String nzbLink, Torrent torrent) public virtual async Task<Torrent> AddNzbLinkToDebridQueue(String nzbLink, Torrent torrent)
{ {
torrent.RdStatus = TorrentStatus.Queued; torrent.RdStatus = TorrentStatus.Queued;
try try
{ {
var uri = new Uri(nzbLink); var uri = new Uri(nzbLink);
var lastSegment = uri.Segments.LastOrDefault()?.TrimEnd('/'); var lastSegment = uri.Segments.LastOrDefault()?.TrimEnd('/');
torrent.RdName = !String.IsNullOrWhiteSpace(lastSegment) ? lastSegment : "Unknown NZB"; torrent.RdName = !String.IsNullOrWhiteSpace(lastSegment) ? lastSegment : "Unknown NZB";
} }
catch(Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "{ex.Message}, trying to parse {nzbLink}", ex.Message, nzbLink); logger.LogError(ex, "{ex.Message}, trying to parse {nzbLink}", ex.Message, nzbLink);
throw new ($"{ex.Message}, trying to parse {nzbLink}");
throw new($"{ex.Message}, trying to parse {nzbLink}");
} }
var nzbHash = ComputeMd5Hash(nzbLink); var nzbHash = ComputeMd5Hash(nzbLink);
@ -124,14 +127,17 @@ public class Torrents(
{ {
torrent.RdName = fileName ?? "Unknown NZB"; torrent.RdName = fileName ?? "Unknown NZB";
torrent.RdStatus = TorrentStatus.Queued; torrent.RdStatus = TorrentStatus.Queued;
try try
{ {
using var stream = new MemoryStream(bytes); using var stream = new MemoryStream(bytes);
var settings = new XmlReaderSettings var settings = new XmlReaderSettings
{ {
DtdProcessing = DtdProcessing.Ignore, DtdProcessing = DtdProcessing.Ignore,
XmlResolver = null XmlResolver = null
}; };
using var reader = XmlReader.Create(stream, settings); using var reader = XmlReader.Create(stream, settings);
var doc = XDocument.Load(reader); var doc = XDocument.Load(reader);
var nzbNamespace = doc.Root?.GetDefaultNamespace() ?? XNamespace.None; var nzbNamespace = doc.Root?.GetDefaultNamespace() ?? XNamespace.None;
@ -139,7 +145,8 @@ public class Torrents(
var title = doc.Root? var title = doc.Root?
.Elements(nzbNamespace + "head") .Elements(nzbNamespace + "head")
.Elements(nzbNamespace + "meta") .Elements(nzbNamespace + "meta")
.FirstOrDefault(x => x.Attribute("type")?.Value == "name")? .FirstOrDefault(x => x.Attribute("type")?.Value == "name")
?
.Value; .Value;
if (String.IsNullOrWhiteSpace(title)) if (String.IsNullOrWhiteSpace(title))
@ -147,7 +154,8 @@ public class Torrents(
title = doc.Root? title = doc.Root?
.Elements(nzbNamespace + "head") .Elements(nzbNamespace + "head")
.Elements(nzbNamespace + "meta") .Elements(nzbNamespace + "meta")
.FirstOrDefault(x => x.Attribute("type")?.Value == "title")? .FirstOrDefault(x => x.Attribute("type")?.Value == "title")
?
.Value; .Value;
} }
@ -156,9 +164,10 @@ public class Torrents(
torrent.RdName = title.Trim(); torrent.RdName = title.Trim();
} }
} }
catch(Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "{ex.Message}, trying to parse NZB file contents", ex.Message); logger.LogError(ex, "{ex.Message}, trying to parse NZB file contents", ex.Message);
throw new($"{ex.Message}, trying to parse NZB file contents"); throw new($"{ex.Message}, trying to parse NZB file contents");
} }
@ -649,7 +658,8 @@ public class Torrents(
{ {
Category = Settings.Get.Provider.Default.Category, Category = Settings.Get.Provider.Default.Category,
DownloadClient = Settings.Get.DownloadClient.Client, DownloadClient = Settings.Get.DownloadClient.Client,
DownloadAction = Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll, DownloadAction =
Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
HostDownloadAction = Settings.Get.Provider.Default.HostDownloadAction, HostDownloadAction = Settings.Get.Provider.Default.HostDownloadAction,
FinishedActionDelay = Settings.Get.Provider.Default.FinishedActionDelay, FinishedActionDelay = Settings.Get.Provider.Default.FinishedActionDelay,
FinishedAction = Settings.Get.Provider.Default.FinishedAction, FinishedAction = Settings.Get.Provider.Default.FinishedAction,
@ -1056,24 +1066,27 @@ public class Torrents(
private static String ComputeSha1Hash(String input) private static String ComputeSha1Hash(String input)
{ {
using var sha1 = System.Security.Cryptography.SHA1.Create(); using var sha1 = SHA1.Create();
var bytes = Encoding.UTF8.GetBytes(input); var bytes = Encoding.UTF8.GetBytes(input);
var hashBytes = sha1.ComputeHash(bytes); var hashBytes = sha1.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
} }
private static String ComputeMd5Hash(String input) private static String ComputeMd5Hash(String input)
{ {
using var md5 = System.Security.Cryptography.MD5.Create(); using var md5 = MD5.Create();
var bytes = Encoding.UTF8.GetBytes(input); var bytes = Encoding.UTF8.GetBytes(input);
var hashBytes = md5.ComputeHash(bytes); var hashBytes = md5.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
} }
private static String ComputeMd5HashFromBytes(Byte[] bytes) private static String ComputeMd5HashFromBytes(Byte[] bytes)
{ {
using var md5 = System.Security.Cryptography.MD5.Create(); using var md5 = MD5.Create();
var hashBytes = md5.ComputeHash(bytes); var hashBytes = md5.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
} }
} }

View file

@ -169,7 +169,7 @@ public class UnpackClient(Download download, String destinationPath)
{ {
Progess = (Int32)Math.Round(d); Progess = (Int32)Math.Round(d);
}, },
cancellationToken: cancellationToken); cancellationToken);
archive.Dispose(); archive.Dispose();

View file

@ -3,22 +3,25 @@ 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.Models.Sabnzbd; using RdtClient.Data.Models.Sabnzbd;
using RdtClient.Service.Services; using RdtClient.Service.Services;
using RdtClient.Web.Controllers; using RdtClient.Web.Controllers;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace RdtClient.Web.Test.Controllers; namespace RdtClient.Web.Test.Controllers;
public class SabnzbdControllerTest public class SabnzbdControllerTest
{ {
private readonly Mock<Sabnzbd> _sabnzbdMock;
private readonly Mock<Authentication> _authenticationMock; private readonly Mock<Authentication> _authenticationMock;
private readonly SabnzbdController _controller; private readonly SabnzbdController _controller;
private readonly Mock<Sabnzbd> _sabnzbdMock;
public SabnzbdControllerTest() public SabnzbdControllerTest()
{ {
Data.Data.SettingData.Get.General.AuthenticationType = Data.Enums.AuthenticationType.None; SettingData.Get.General.AuthenticationType = AuthenticationType.None;
Data.Data.SettingData.Get.Provider.ApiKey = "test-api-key"; SettingData.Get.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!);
var sabnzbdLoggerMock = new Mock<ILogger<Sabnzbd>>(); var sabnzbdLoggerMock = new Mock<ILogger<Sabnzbd>>();
@ -29,6 +32,7 @@ public class SabnzbdControllerTest
_controller = new(loggerMock.Object, _sabnzbdMock.Object); _controller = new(loggerMock.Object, _sabnzbdMock.Object);
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
_controller.ControllerContext = new() _controller.ControllerContext = new()
{ {
HttpContext = httpContext HttpContext = httpContext
@ -39,7 +43,11 @@ public class SabnzbdControllerTest
public async Task GetQueue_ReturnsOk() public async Task GetQueue_ReturnsOk()
{ {
// Arrange // Arrange
var queue = new SabnzbdQueue { NoOfSlots = 1 }; var queue = new SabnzbdQueue
{
NoOfSlots = 1
};
_sabnzbdMock.Setup(s => s.GetQueue()).ReturnsAsync(queue); _sabnzbdMock.Setup(s => s.GetQueue()).ReturnsAsync(queue);
// Act // Act
@ -56,8 +64,13 @@ public class SabnzbdControllerTest
public async Task GetQueue_Unauthorized_ReturnsOk_BecauseFilterIsSkippedInUnitTests() public async Task GetQueue_Unauthorized_ReturnsOk_BecauseFilterIsSkippedInUnitTests()
{ {
// Arrange // Arrange
Data.Data.SettingData.Get.General.AuthenticationType = Data.Enums.AuthenticationType.UserNamePassword; SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
var queue = new SabnzbdQueue { NoOfSlots = 1 };
var queue = new SabnzbdQueue
{
NoOfSlots = 1
};
_sabnzbdMock.Setup(s => s.GetQueue()).ReturnsAsync(queue); _sabnzbdMock.Setup(s => s.GetQueue()).ReturnsAsync(queue);
// Act // Act
@ -73,14 +86,18 @@ public class SabnzbdControllerTest
public async Task GetQueue_WithMaAuth_ReturnsOk() public async Task GetQueue_WithMaAuth_ReturnsOk()
{ {
// Arrange // Arrange
Data.Data.SettingData.Get.General.AuthenticationType = Data.Enums.AuthenticationType.UserNamePassword; SettingData.Get.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;
_authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(Microsoft.AspNetCore.Identity.SignInResult.Success); _authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(SignInResult.Success);
var queue = new SabnzbdQueue
{
NoOfSlots = 1
};
var queue = new SabnzbdQueue { NoOfSlots = 1 };
_sabnzbdMock.Setup(s => s.GetQueue()).ReturnsAsync(queue); _sabnzbdMock.Setup(s => s.GetQueue()).ReturnsAsync(queue);
// Act // Act
@ -94,7 +111,11 @@ public class SabnzbdControllerTest
public async Task GetHistory_ReturnsOk() public async Task GetHistory_ReturnsOk()
{ {
// Arrange // Arrange
var history = new SabnzbdHistory { NoOfSlots = 1 }; var history = new SabnzbdHistory
{
NoOfSlots = 1
};
_sabnzbdMock.Setup(s => s.GetHistory()).ReturnsAsync(history); _sabnzbdMock.Setup(s => s.GetHistory()).ReturnsAsync(history);
// Act // Act
@ -125,7 +146,7 @@ public class SabnzbdControllerTest
public void GetVersion_ReturnsOk() public void GetVersion_ReturnsOk()
{ {
// Arrange // Arrange
Data.Data.SettingData.Get.General.AuthenticationType = Data.Enums.AuthenticationType.UserNamePassword; SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
// Act // Act
var result = _controller.Version(); var result = _controller.Version();
@ -140,8 +161,14 @@ public class SabnzbdControllerTest
public void GetConfig_ReturnsOk() public void GetConfig_ReturnsOk()
{ {
// Arrange // Arrange
var config = new SabnzbdConfig { Misc = new() var config = new SabnzbdConfig
{ Port = "6500" } }; {
Misc = new()
{
Port = "6500"
}
};
_sabnzbdMock.Setup(s => s.GetConfig()).Returns(config); _sabnzbdMock.Setup(s => s.GetConfig()).Returns(config);
// Act // Act
@ -158,7 +185,12 @@ public class SabnzbdControllerTest
public void GetCategories_ReturnsOk() public void GetCategories_ReturnsOk()
{ {
// Arrange // Arrange
var categories = new List<String> { "*", "Default" }; var categories = new List<String>
{
"*",
"Default"
};
_sabnzbdMock.Setup(s => s.GetCategories()).Returns(categories); _sabnzbdMock.Setup(s => s.GetCategories()).Returns(categories);
// Act // Act
@ -200,10 +232,14 @@ public class SabnzbdControllerTest
// Arrange // Arrange
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Request.ContentType = "application/x-www-form-urlencoded"; httpContext.Request.ContentType = "application/x-www-form-urlencoded";
httpContext.Request.Form = new FormCollection(new() httpContext.Request.Form = new FormCollection(new()
{ {
{ "mode", "unknown_form" } {
"mode", "unknown_form"
}
}); });
_controller.ControllerContext.HttpContext = httpContext; _controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -233,12 +269,18 @@ public class SabnzbdControllerTest
fileMock.Setup(_ => _.OpenReadStream()).Returns(ms); fileMock.Setup(_ => _.OpenReadStream()).Returns(ms);
fileMock.Setup(_ => _.FileName).Returns(fileName); fileMock.Setup(_ => _.FileName).Returns(fileName);
fileMock.Setup(_ => _.Length).Returns(ms.Length); fileMock.Setup(_ => _.Length).Returns(ms.Length);
fileMock.Setup(_ => _.CopyToAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>())) fileMock.Setup(_ => _.CopyToAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<Stream, CancellationToken>((s, _) => ms.CopyTo(s)) .Callback<Stream, CancellationToken>((s, _) => ms.CopyTo(s))
.Returns(Task.CompletedTask); .Returns(Task.CompletedTask);
httpContext.Request.ContentType = "multipart/form-data; boundary=something"; httpContext.Request.ContentType = "multipart/form-data; boundary=something";
httpContext.Request.Form = new FormCollection(new(), new FormFileCollection { fileMock.Object });
httpContext.Request.Form = new FormCollection(new(),
new FormFileCollection
{
fileMock.Object
});
_controller.ControllerContext.HttpContext = httpContext; _controller.ControllerContext.HttpContext = httpContext;
_sabnzbdMock.Setup(s => s.AddFile(It.IsAny<Byte[]>(), fileName, "radarr", -100)).ReturnsAsync("nzo_id_123"); _sabnzbdMock.Setup(s => s.AddFile(It.IsAny<Byte[]>(), fileName, "radarr", -100)).ReturnsAsync("nzo_id_123");

View file

@ -1,6 +1,9 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
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;
@ -10,22 +13,22 @@ namespace RdtClient.Web.Test.Controllers;
public class SabnzbdHandlerTest public class SabnzbdHandlerTest
{ {
private readonly Mock<Authentication> _authenticationMock; private readonly Mock<Authentication> _authenticationMock;
private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock;
private readonly SabnzbdHandler _handler; private readonly SabnzbdHandler _handler;
private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock;
public SabnzbdHandlerTest() public SabnzbdHandlerTest()
{ {
_authenticationMock = new(null!, null!, null!); _authenticationMock = new(null!, null!, null!);
_httpContextAccessorMock = new(); _httpContextAccessorMock = new();
_handler = new(_authenticationMock.Object, _httpContextAccessorMock.Object); _handler = new(_authenticationMock.Object, _httpContextAccessorMock.Object);
Data.Data.SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword; SettingData.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
} }
[Fact] [Fact]
public async Task HandleAsync_AuthNone_Succeeds() public async Task HandleAsync_AuthNone_Succeeds()
{ {
// Arrange // Arrange
Data.Data.SettingData.Get.General.AuthenticationType = AuthenticationType.None; SettingData.Get.General.AuthenticationType = AuthenticationType.None;
var context = CreateContext(); var context = CreateContext();
// Act // Act
@ -45,7 +48,7 @@ public class SabnzbdHandlerTest
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext); _httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
var context = CreateContext(httpContext); var context = CreateContext(httpContext);
_authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(Microsoft.AspNetCore.Identity.SignInResult.Success); _authenticationMock.Setup(a => a.Login("user", "pass")).ReturnsAsync(SignInResult.Success);
// Act // Act
await _handler.HandleAsync(context); await _handler.HandleAsync(context);
@ -60,7 +63,7 @@ public class SabnzbdHandlerTest
// Arrange // Arrange
Settings.Get.General.AuthenticationType = AuthenticationType.UserNamePassword; Settings.Get.General.AuthenticationType = AuthenticationType.UserNamePassword;
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var claimsPrincipal = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity("TestAuth")); var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity("TestAuth"));
httpContext.User = claimsPrincipal; httpContext.User = claimsPrincipal;
var context = CreateContext(httpContext); var context = CreateContext(httpContext);
@ -83,7 +86,7 @@ public class SabnzbdHandlerTest
_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext); _httpContextAccessorMock.Setup(a => a.HttpContext).Returns(httpContext);
var context = CreateContext(httpContext); var context = CreateContext(httpContext);
_authenticationMock.Setup(a => a.Login("user", "wrong")).ReturnsAsync(Microsoft.AspNetCore.Identity.SignInResult.Failed); _authenticationMock.Setup(a => a.Login("user", "wrong")).ReturnsAsync(SignInResult.Failed);
// Act // Act
await _handler.HandleAsync(context); await _handler.HandleAsync(context);
@ -112,7 +115,8 @@ public class SabnzbdHandlerTest
private AuthorizationHandlerContext CreateContext(HttpContext? httpContext = null) private AuthorizationHandlerContext CreateContext(HttpContext? httpContext = null)
{ {
var requirement = new SabnzbdRequirement(); var requirement = new SabnzbdRequirement();
var user = httpContext?.User ?? new System.Security.Claims.ClaimsPrincipal(); var user = httpContext?.User ?? new ClaimsPrincipal();
return new([requirement], user, null); return new([requirement], user, null);
} }
} }

View file

@ -9,9 +9,9 @@ namespace RdtClient.Web.Test.Controllers;
public class TorrentsControllerNzbTest public class TorrentsControllerNzbTest
{ {
private readonly Mock<Torrents> _torrentsMock;
private readonly Mock<ILogger<TorrentsController>> _loggerMock;
private readonly TorrentsController _controller; private readonly TorrentsController _controller;
private readonly Mock<ILogger<TorrentsController>> _loggerMock;
private readonly Mock<Torrents> _torrentsMock;
public TorrentsControllerNzbTest() public TorrentsControllerNzbTest()
{ {

View file

@ -23,10 +23,14 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
if (String.IsNullOrWhiteSpace(mode)) if (String.IsNullOrWhiteSpace(mode))
{ {
return BadRequest(new SabnzbdResponse { Error = "No mode specified" }); return BadRequest(new SabnzbdResponse
{
Error = "No mode specified"
});
} }
logger.LogWarning($"Sabnzbd API called (not implemented) - Method: {Request.Method}, Query: {Request.QueryString}"); logger.LogWarning($"Sabnzbd API called (not implemented) - Method: {Request.Method}, Query: {Request.QueryString}");
return NotFound(new SabnzbdResponse()); return NotFound(new SabnzbdResponse());
} }
@ -36,7 +40,11 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
public ActionResult Version() public ActionResult Version()
{ {
logger.LogDebug("Sabnzbd mode: version"); logger.LogDebug("Sabnzbd mode: version");
return Ok(new SabnzbdResponse { Version = "4.4.0" });
return Ok(new SabnzbdResponse
{
Version = "4.4.0"
});
} }
[HttpGet] [HttpGet]
@ -57,11 +65,19 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
Error = "No value specified for delete operation" Error = "No value specified for delete operation"
}); });
} }
await sabnzbd.Delete(value ?? ""); await sabnzbd.Delete(value ?? "");
return Ok(new SabnzbdResponse { Status = true });
return Ok(new SabnzbdResponse
{
Status = true
});
} }
return Ok(new SabnzbdResponse { Queue = await sabnzbd.GetQueue() }); return Ok(new SabnzbdResponse
{
Queue = await sabnzbd.GetQueue()
});
} }
[HttpGet] [HttpGet]
@ -69,7 +85,11 @@ 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");
return Ok(new SabnzbdResponse { History = await sabnzbd.GetHistory() });
return Ok(new SabnzbdResponse
{
History = await sabnzbd.GetHistory()
});
} }
[HttpGet] [HttpGet]
@ -77,7 +97,11 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
public ActionResult GetConfig() public ActionResult GetConfig()
{ {
logger.LogDebug("Sabnzbd mode: get_config"); logger.LogDebug("Sabnzbd mode: get_config");
return Ok(new SabnzbdResponse { Config = sabnzbd.GetConfig() });
return Ok(new SabnzbdResponse
{
Config = sabnzbd.GetConfig()
});
} }
[HttpGet] [HttpGet]
@ -85,7 +109,11 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
public ActionResult GetCats() public ActionResult GetCats()
{ {
logger.LogDebug("Sabnzbd mode: get_cats"); logger.LogDebug("Sabnzbd mode: get_cats");
return Ok(new SabnzbdResponse { Categories = sabnzbd.GetCategories() });
return Ok(new SabnzbdResponse
{
Categories = sabnzbd.GetCategories()
});
} }
[HttpGet] [HttpGet]
@ -101,7 +129,12 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
Int32? priority = Int32.TryParse(priorityStr, out var p) ? p : null; Int32? priority = Int32.TryParse(priorityStr, out var p) ? p : null;
var result = await sabnzbd.AddUrl(url ?? "", category, priority); var result = await sabnzbd.AddUrl(url ?? "", category, priority);
return Ok(new SabnzbdResponse { Status = true, NzoIds = [result] });
return Ok(new SabnzbdResponse
{
Status = true,
NzoIds = [result]
});
} }
[HttpPost] [HttpPost]
@ -109,12 +142,14 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
public async Task<ActionResult> AddFile() public async Task<ActionResult> AddFile()
{ {
logger.LogDebug("Sabnzbd mode: addfile"); logger.LogDebug("Sabnzbd mode: addfile");
if (!Request.HasFormContentType) if (!Request.HasFormContentType)
{ {
return BadRequest("Expected multipart/form-data"); return BadRequest("Expected multipart/form-data");
} }
var file = Request.Form.Files.FirstOrDefault(); var file = Request.Form.Files.FirstOrDefault();
if (file == null) if (file == null)
{ {
return BadRequest("No file uploaded"); return BadRequest("No file uploaded");
@ -128,7 +163,11 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
await file.CopyToAsync(ms); await file.CopyToAsync(ms);
var result = await sabnzbd.AddFile(ms.ToArray(), file.FileName, category, priority); var result = await sabnzbd.AddFile(ms.ToArray(), file.FileName, category, priority);
return Ok(new SabnzbdResponse { Status = true, NzoIds = [result] }); return Ok(new SabnzbdResponse
{
Status = true,
NzoIds = [result]
});
} }
[HttpGet] [HttpGet]
@ -136,16 +175,23 @@ public class SabnzbdController(ILogger<SabnzbdController> logger, Sabnzbd sabnzb
public async Task<ActionResult> FullStatus() public async Task<ActionResult> FullStatus()
{ {
logger.LogDebug("Sabnzbd mode: fullstatus"); logger.LogDebug("Sabnzbd mode: fullstatus");
return Ok(new SabnzbdResponse { Version = "4.4.0", Queue = await sabnzbd.GetQueue() });
return Ok(new SabnzbdResponse
{
Version = "4.4.0",
Queue = await sabnzbd.GetQueue()
});
} }
private String? GetParam(String name) private String? GetParam(String name)
{ {
var value = Request.Query[name].ToString(); var value = Request.Query[name].ToString();
if (String.IsNullOrWhiteSpace(value) && Request.HasFormContentType) if (String.IsNullOrWhiteSpace(value) && Request.HasFormContentType)
{ {
value = Request.Form[name].ToString(); value = Request.Form[name].ToString();
} }
return value; return value;
} }
} }

View file

@ -22,70 +22,72 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
var results = await torrents.Get(); var results = await torrents.Get();
var torrentDtos = results.Select(torrent => new TorrentDto var torrentDtos = results.Select(torrent => new TorrentDto
{ {
TorrentId = torrent.TorrentId, TorrentId = torrent.TorrentId,
Hash = torrent.Hash, Hash = torrent.Hash,
Category = torrent.Category, Category = torrent.Category,
DownloadAction = torrent.DownloadAction, DownloadAction = torrent.DownloadAction,
FinishedAction = torrent.FinishedAction, FinishedAction = torrent.FinishedAction,
FinishedActionDelay = torrent.FinishedActionDelay, FinishedActionDelay = torrent.FinishedActionDelay,
HostDownloadAction = torrent.HostDownloadAction, HostDownloadAction = torrent.HostDownloadAction,
DownloadMinSize = torrent.DownloadMinSize, DownloadMinSize = torrent.DownloadMinSize,
IncludeRegex = torrent.IncludeRegex, IncludeRegex = torrent.IncludeRegex,
ExcludeRegex = torrent.ExcludeRegex, ExcludeRegex = torrent.ExcludeRegex,
DownloadManualFiles = torrent.DownloadManualFiles, DownloadManualFiles = torrent.DownloadManualFiles,
DownloadClient = torrent.DownloadClient, DownloadClient = torrent.DownloadClient,
Added = torrent.Added, Added = torrent.Added,
FilesSelected = torrent.FilesSelected, FilesSelected = torrent.FilesSelected,
Completed = torrent.Completed, Completed = torrent.Completed,
Type = torrent.Type, Type = torrent.Type,
IsFile = torrent.IsFile, IsFile = torrent.IsFile,
Priority = torrent.Priority, Priority = torrent.Priority,
RetryCount = torrent.RetryCount, RetryCount = torrent.RetryCount,
DownloadRetryAttempts = torrent.DownloadRetryAttempts, DownloadRetryAttempts = torrent.DownloadRetryAttempts,
TorrentRetryAttempts = torrent.TorrentRetryAttempts, TorrentRetryAttempts = torrent.TorrentRetryAttempts,
DeleteOnError = torrent.DeleteOnError, DeleteOnError = torrent.DeleteOnError,
Lifetime = torrent.Lifetime, Lifetime = torrent.Lifetime,
Error = torrent.Error, Error = torrent.Error,
RdId = torrent.RdId, RdId = torrent.RdId,
RdName = torrent.RdName, RdName = torrent.RdName,
RdSize = torrent.RdSize, RdSize = torrent.RdSize,
RdHost = torrent.RdHost, RdHost = torrent.RdHost,
RdSplit = torrent.RdSplit, RdSplit = torrent.RdSplit,
RdProgress = torrent.RdProgress, RdProgress = torrent.RdProgress,
RdStatus = torrent.RdStatus, RdStatus = torrent.RdStatus,
RdStatusRaw = torrent.RdStatusRaw, RdStatusRaw = torrent.RdStatusRaw,
RdAdded = torrent.RdAdded, RdAdded = torrent.RdAdded,
RdEnded = torrent.RdEnded, RdEnded = torrent.RdEnded,
RdSpeed = torrent.RdSpeed, RdSpeed = torrent.RdSpeed,
RdSeeders = torrent.RdSeeders, RdSeeders = torrent.RdSeeders,
Files = torrent.Files, Files = torrent.Files,
Downloads = torrent.Downloads.Select(download => Downloads = torrent.Downloads.Select(download =>
{ {
var (speed, bytesTotal, bytesDone) = torrents.GetDownloadStats(download.DownloadId); var (speed, bytesTotal, bytesDone) = torrents.GetDownloadStats(download.DownloadId);
return new DownloadDto return new DownloadDto
{ {
DownloadId = download.DownloadId, DownloadId = download.DownloadId,
TorrentId = download.TorrentId, TorrentId = download.TorrentId,
Path = download.Path, Path = download.Path,
Link = download.Link, Link = download.Link,
Added = download.Added, Added = download.Added,
DownloadQueued = download.DownloadQueued, DownloadQueued = download.DownloadQueued,
DownloadStarted = download.DownloadStarted, DownloadStarted = download.DownloadStarted,
DownloadFinished = download.DownloadFinished, DownloadFinished = download.DownloadFinished,
UnpackingQueued = download.UnpackingQueued, UnpackingQueued = download.UnpackingQueued,
UnpackingStarted = download.UnpackingStarted, UnpackingStarted = download.UnpackingStarted,
UnpackingFinished = download.UnpackingFinished, UnpackingFinished = download.UnpackingFinished,
Completed = download.Completed, Completed = download.Completed,
RetryCount = download.RetryCount, RetryCount = download.RetryCount,
Error = download.Error, Error = download.Error,
BytesTotal = bytesTotal, BytesTotal = bytesTotal,
BytesDone = bytesDone, BytesDone = bytesDone,
Speed = speed Speed = speed
}; };
}).ToList() })
}).ToList(); .ToList()
})
.ToList();
return Ok(torrentDtos); return Ok(torrentDtos);
} }
@ -146,30 +148,31 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
RdSeeders = torrent.RdSeeders, RdSeeders = torrent.RdSeeders,
Files = torrent.Files, Files = torrent.Files,
Downloads = torrent.Downloads.Select(download => Downloads = torrent.Downloads.Select(download =>
{ {
var (speed, bytesTotal, bytesDone) = torrents.GetDownloadStats(download.DownloadId); var (speed, bytesTotal, bytesDone) = torrents.GetDownloadStats(download.DownloadId);
return new DownloadDto return new DownloadDto
{ {
DownloadId = download.DownloadId, DownloadId = download.DownloadId,
TorrentId = download.TorrentId, TorrentId = download.TorrentId,
Path = download.Path, Path = download.Path,
Link = download.Link, Link = download.Link,
Added = download.Added, Added = download.Added,
DownloadQueued = download.DownloadQueued, DownloadQueued = download.DownloadQueued,
DownloadStarted = download.DownloadStarted, DownloadStarted = download.DownloadStarted,
DownloadFinished = download.DownloadFinished, DownloadFinished = download.DownloadFinished,
UnpackingQueued = download.UnpackingQueued, UnpackingQueued = download.UnpackingQueued,
UnpackingStarted = download.UnpackingStarted, UnpackingStarted = download.UnpackingStarted,
UnpackingFinished = download.UnpackingFinished, UnpackingFinished = download.UnpackingFinished,
Completed = download.Completed, Completed = download.Completed,
RetryCount = download.RetryCount, RetryCount = download.RetryCount,
Error = download.Error, Error = download.Error,
BytesTotal = bytesTotal, BytesTotal = bytesTotal,
BytesDone = bytesDone, BytesDone = bytesDone,
Speed = speed Speed = speed
}; };
}).ToList() })
.ToList()
}; };
return Ok(torrentDto); return Ok(torrentDto);
@ -180,6 +183,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
public ActionResult<DiskSpaceStatus?> GetDiskSpaceStatus() public ActionResult<DiskSpaceStatus?> GetDiskSpaceStatus()
{ {
var status = DiskSpaceMonitor.GetCurrentStatus(); var status = DiskSpaceMonitor.GetCurrentStatus();
return Ok(status); return Ok(status);
} }

View file

@ -5,9 +5,9 @@ using Microsoft.Extensions.Hosting.WindowsServices;
using RdtClient.Data.Data; using RdtClient.Data.Data;
using RdtClient.Data.Models.Internal; using RdtClient.Data.Models.Internal;
using RdtClient.Service; using RdtClient.Service;
using RdtClient.Service.Helpers;
using RdtClient.Service.Middleware; using RdtClient.Service.Middleware;
using RdtClient.Service.Services; using RdtClient.Service.Services;
using RdtClient.Service.Helpers;
using Serilog; using Serilog;
using Serilog.Debugging; using Serilog.Debugging;
using Serilog.Events; using Serilog.Events;
@ -74,8 +74,16 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
}); });
builder.Services.AddAuthorizationBuilder() builder.Services.AddAuthorizationBuilder()
.AddPolicy("AuthSetting", policyCorrectUser => { policyCorrectUser.Requirements.Add(new AuthSettingRequirement()); }) .AddPolicy("AuthSetting",
.AddPolicy("Sabnzbd", policyCorrectUser => { policyCorrectUser.Requirements.Add(new SabnzbdRequirement()); }); policyCorrectUser =>
{
policyCorrectUser.Requirements.Add(new AuthSettingRequirement());
})
.AddPolicy("Sabnzbd",
policyCorrectUser =>
{
policyCorrectUser.Requirements.Add(new SabnzbdRequirement());
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{ {