diff --git a/CHANGELOG.md b/CHANGELOG.md index a8b64aa..60fab41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.80] - 2024-07-13 +### Changed +- Add rate limiter to retry requests that are rate limited from Real-Debrid. +- Optimize calls to Real-Debrid API when torrents are finished and periodic updates. +- Update to .NET 8.0.7 + ## [2.0.79] - 2024-06-03 ### Changed - Fixed issue with qBittorrent progress sometimes throwing errors. diff --git a/package.json b/package.json index 7b1dfbb..204f20c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rdt-client", - "version": "2.0.79", + "version": "2.0.80", "description": "This is a web interface to manage your torrents on Real-Debrid.", "main": "index.js", "dependencies": { diff --git a/server/RdtClient.Data/RdtClient.Data.csproj b/server/RdtClient.Data/RdtClient.Data.csproj index 8a0f20f..ca714f0 100644 --- a/server/RdtClient.Data/RdtClient.Data.csproj +++ b/server/RdtClient.Data/RdtClient.Data.csproj @@ -8,15 +8,15 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs b/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs index 1b63026..846c000 100644 --- a/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs +++ b/server/RdtClient.Service/BackgroundServices/UpdateChecker.cs @@ -26,7 +26,7 @@ public class UpdateChecker(ILogger logger) : BackgroundService return; } - CurrentVersion = $"v{version[..version.LastIndexOf(".", StringComparison.Ordinal)]}"; + CurrentVersion = $"v{version[..version.LastIndexOf('.')]}"; logger.LogInformation("UpdateChecker started, currently on version {CurrentVersion}.", CurrentVersion); diff --git a/server/RdtClient.Service/DiConfig.cs b/server/RdtClient.Service/DiConfig.cs index c9a1449..e5f7aa2 100644 --- a/server/RdtClient.Service/DiConfig.cs +++ b/server/RdtClient.Service/DiConfig.cs @@ -1,8 +1,9 @@ -using Microsoft.AspNetCore.Authorization; +using System.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Polly; +using Polly.Extensions.Http; using RdtClient.Service.BackgroundServices; -using RdtClient.Service.Handlers; using RdtClient.Service.Middleware; using RdtClient.Service.Services; using RdtClient.Service.Services.TorrentClients; @@ -38,9 +39,13 @@ public static class DiConfig public static void RegisterHttpClients(this IServiceCollection services) { services.AddHttpClient(); - + + var retryPolicy = HttpPolicyExtensions + .HandleTransientHttpError() + .OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests) + .WaitAndRetryAsync(retryCount: 5, sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); + services.AddHttpClient(RD_CLIENT) - .AddHttpMessageHandler(sp => new RateLimitingHandler(sp.GetRequiredService>(), 60, TimeSpan.FromSeconds(60))) - .SetHandlerLifetime(Timeout.InfiniteTimeSpan); + .AddPolicyHandler(retryPolicy); } } \ No newline at end of file diff --git a/server/RdtClient.Service/Handlers/RateLimitingHandler.cs b/server/RdtClient.Service/Handlers/RateLimitingHandler.cs deleted file mode 100644 index 89cfd62..0000000 --- a/server/RdtClient.Service/Handlers/RateLimitingHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Concurrent; -using Microsoft.Extensions.Logging; - -namespace RdtClient.Service.Handlers; - -public class RateLimitingHandler : DelegatingHandler -{ - private readonly ConcurrentQueue _callQueue = new(); - private readonly TimeSpan _requestWindow; - private readonly ILogger _logger; - private readonly Int32 _requestsAllowedPerWindow; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The number of requests allowed per window. - /// The time to delay when the maximum number of requests has been reached (The window). - public RateLimitingHandler(ILogger logger, Int32 requestsAllowedPerWindow, TimeSpan requestWindow) - { - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(requestsAllowedPerWindow); - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(requestWindow, TimeSpan.Zero); - _logger = logger; - _requestsAllowedPerWindow = requestsAllowedPerWindow; - _requestWindow = requestWindow; - } - - /// - protected override async Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) - { - var now = DateTimeOffset.UtcNow; - _callQueue.Enqueue(now); - while (_callQueue.TryPeek(out var oldCall) && now - oldCall > _requestWindow) - { - _logger.LogDebug("Removing oldest request from queue"); - _callQueue.TryDequeue(out _); - } - if (_callQueue.Count > _requestsAllowedPerWindow) - { - _logger.LogDebug("Delaying request"); - var earliestAllowedRequestTime = now.Add(-_requestWindow); - var delayDuration = _callQueue.ElementAtOrDefault(_requestsAllowedPerWindow - 1) - - earliestAllowedRequestTime; - if (delayDuration > TimeSpan.Zero) - { - _logger.LogDebug("Delaying request for {DelayDuration}", delayDuration); - await Task.Delay(delayDuration, cancellationToken); - } - } - _logger.LogDebug("Sending request"); - return await base.SendAsync(request, cancellationToken); - } -} \ No newline at end of file diff --git a/server/RdtClient.Service/Helpers/DownloadHelper.cs b/server/RdtClient.Service/Helpers/DownloadHelper.cs index 1dc9830..0c9a1c4 100644 --- a/server/RdtClient.Service/Helpers/DownloadHelper.cs +++ b/server/RdtClient.Service/Helpers/DownloadHelper.cs @@ -27,7 +27,7 @@ public static class DownloadHelper var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList(); - if (matchingTorrentFiles.Any()) + if (matchingTorrentFiles.Count > 0) { var matchingTorrentFile = matchingTorrentFiles[0]; @@ -71,7 +71,7 @@ public static class DownloadHelper var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList(); - if (matchingTorrentFiles.Any()) + if (matchingTorrentFiles.Count > 0) { var matchingTorrentFile = matchingTorrentFiles[0]; diff --git a/server/RdtClient.Service/Helpers/JsonModelBinder.cs b/server/RdtClient.Service/Helpers/JsonModelBinder.cs index f7722b6..1a7c176 100644 --- a/server/RdtClient.Service/Helpers/JsonModelBinder.cs +++ b/server/RdtClient.Service/Helpers/JsonModelBinder.cs @@ -6,10 +6,7 @@ public class JsonModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { - if (bindingContext == null) - { - throw new ArgumentNullException(nameof(bindingContext)); - } + ArgumentNullException.ThrowIfNull(bindingContext); var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult != ValueProviderResult.None) diff --git a/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs b/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs index 747e0ce..cd64549 100644 --- a/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs +++ b/server/RdtClient.Service/Middleware/BaseHrefMiddleware.cs @@ -3,8 +3,17 @@ using Microsoft.AspNetCore.Http; namespace RdtClient.Service.Middleware; -public class BaseHrefMiddleware(RequestDelegate next, String basePath) +public partial class BaseHrefMiddleware(RequestDelegate next, String basePath) { + [GeneratedRegex(@")")] + private partial Regex ScriptRegex(); + + [GeneratedRegex("()")] + private partial Regex LinkRegex(); + private readonly String _basePath = $"/{basePath.TrimStart('/').TrimEnd('/')}/"; public async Task InvokeAsync(HttpContext context) @@ -27,10 +36,10 @@ public class BaseHrefMiddleware(RequestDelegate next, String basePath) { if (context.Response.ContentType?.Contains("text/html") == true) { - responseBody = Regex.Replace(responseBody, @")", $"$1{_basePath}$2$3"); - responseBody = Regex.Replace(responseBody, "()", $"$1{_basePath}$2$3"); + responseBody = ScriptRegex().Replace(responseBody, $"$1{_basePath}$2$3"); + responseBody = LinkRegex().Replace(responseBody, $"$1{_basePath}$2$3"); context.Response.Headers.Remove("Content-Length"); await context.Response.WriteAsync(responseBody); diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 374e759..c736d2e 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -11,14 +11,17 @@ - + + + + - - - + + + diff --git a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs index 184c63b..08d3e15 100644 --- a/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs +++ b/server/RdtClient.Service/Services/Downloaders/SymlinkDownloader.cs @@ -23,7 +23,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path) var filePath = new FileInfo(path); var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd(['\\', '/']); - var searchSubDirectories = rcloneMountPath.EndsWith("*"); + var searchSubDirectories = rcloneMountPath.EndsWith('*'); rcloneMountPath = rcloneMountPath.TrimEnd('*').TrimEnd(['\\', '/']); if (!Directory.Exists(rcloneMountPath)) diff --git a/server/RdtClient.Service/Services/RdtHub.cs b/server/RdtClient.Service/Services/RdtHub.cs index e8545af..f7f2367 100644 --- a/server/RdtClient.Service/Services/RdtHub.cs +++ b/server/RdtClient.Service/Services/RdtHub.cs @@ -7,7 +7,7 @@ public class RdtHub : Hub { private static readonly ConcurrentDictionary Users = new(); - public static Boolean HasConnections => Users.Any(); + public static Boolean HasConnections => !Users.IsEmpty; public override async Task OnConnectedAsync() { diff --git a/server/RdtClient.Service/Services/UnpackClient.cs b/server/RdtClient.Service/Services/UnpackClient.cs index b840966..2724f43 100644 --- a/server/RdtClient.Service/Services/UnpackClient.cs +++ b/server/RdtClient.Service/Services/UnpackClient.cs @@ -131,7 +131,7 @@ public class UnpackClient(Download download, String destinationPath) var entries = archive.Entries .Where(entry => !entry.IsDirectory) - .Select(m => m.Key) + .Select(m => m.Key!) .ToList(); archive.Dispose(); diff --git a/server/RdtClient.Web/Controllers/QBittorrentController.cs b/server/RdtClient.Web/Controllers/QBittorrentController.cs index b4dad0e..d0c57cb 100644 --- a/server/RdtClient.Web/Controllers/QBittorrentController.cs +++ b/server/RdtClient.Web/Controllers/QBittorrentController.cs @@ -282,7 +282,7 @@ public class QBittorrentController(ILogger logger, QBitto return BadRequest(); } - logger.LogDebug($"Delete {request.Hashes}"); + logger.LogDebug("Delete {Hashes}", request.Hashes); var hashes = request.Hashes.Split("|"); diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index ef6a09f..2ec30d0 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -29,18 +29,18 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - +