From 82cc1ba560757761fcd3598cd72dac20121e1201 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Mon, 24 Mar 2025 15:10:45 +0200 Subject: [PATCH] added separate strikes for downloading metadata --- README.md | 5 +-- .../QueueCleaner/QueueCleanerConfig.cs | 14 ++++++-- code/Domain/Enums/DeleteReason.cs | 2 +- code/Executable/appsettings.Development.json | 7 ++-- code/Executable/appsettings.json | 3 +- .../DownloadClient/QBittorrent/QBitService.cs | 36 +++++++++++-------- .../Verticals/QueueCleaner/QueueCleaner.cs | 4 +-- code/test/docker-compose.yml | 5 +-- variables.md | 17 +++++++-- 9 files changed, 62 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index f512537..c069943 100644 --- a/README.md +++ b/README.md @@ -183,15 +183,16 @@ services: - QUEUECLEANER__ENABLED=true - QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt - QUEUECLEANER__RUNSEQUENTIALLY=true - - QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=5 + - QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=3 - QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE=false - QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE=false # - QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0=title mismatch # - QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1=manual import required - - QUEUECLEANER__STALLED_MAX_STRIKES=5 + - QUEUECLEANER__STALLED_MAX_STRIKES=3 - QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS=false - QUEUECLEANER__STALLED_IGNORE_PRIVATE=false - QUEUECLEANER__STALLED_DELETE_PRIVATE=false + - QUEUECLEANER__DOWNLOADING_METADATA_MAX_STRIKES=3 - CONTENTBLOCKER__ENABLED=true - CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH=/ignored.txt diff --git a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs index 9006253..417fb38 100644 --- a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs +++ b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs @@ -24,7 +24,7 @@ public sealed record QueueCleanerConfig : IJobConfig, IIgnoredDownloadsConfig public bool ImportFailedDeletePrivate { get; init; } [ConfigurationKeyName("IMPORT_FAILED_IGNORE_PATTERNS")] - public List? ImportFailedIgnorePatterns { get; init; } + public IReadOnlyList? ImportFailedIgnorePatterns { get; init; } [ConfigurationKeyName("STALLED_MAX_STRIKES")] public ushort StalledMaxStrikes { get; init; } @@ -38,16 +38,24 @@ public sealed record QueueCleanerConfig : IJobConfig, IIgnoredDownloadsConfig [ConfigurationKeyName("STALLED_DELETE_PRIVATE")] public bool StalledDeletePrivate { get; init; } + [ConfigurationKeyName("DOWNLOADING_METADATA_MAX_STRIKES")] + public ushort DownloadingMetadataMaxStrikes { get; init; } + public void Validate() { if (ImportFailedMaxStrikes is > 0 and < 3) { - throw new ValidationException("the minimum value for IMPORT_FAILED_MAX_STRIKES must be 3"); + throw new ValidationException($"the minimum value for {SectionName.ToUpperInvariant()}__IMPORT_FAILED_MAX_STRIKES must be 3"); } if (StalledMaxStrikes is > 0 and < 3) { - throw new ValidationException("the minimum value for STALLED_MAX_STRIKES must be 3"); + throw new ValidationException($"the minimum value for {SectionName.ToUpperInvariant()}__STALLED_MAX_STRIKES must be 3"); + } + + if (DownloadingMetadataMaxStrikes is > 0 and < 3) + { + throw new ValidationException($"the minimum value for {SectionName.ToUpperInvariant()}__DOWNLOADING_METADATA_MAX_STRIKES must be 3"); } } } \ No newline at end of file diff --git a/code/Domain/Enums/DeleteReason.cs b/code/Domain/Enums/DeleteReason.cs index baeddd3..9216fdc 100644 --- a/code/Domain/Enums/DeleteReason.cs +++ b/code/Domain/Enums/DeleteReason.cs @@ -1,4 +1,4 @@ -namespace Domain.Enums; +namespace Domain.Enums; public enum DeleteReason { diff --git a/code/Executable/appsettings.Development.json b/code/Executable/appsettings.Development.json index f5d8720..0cfe9bc 100644 --- a/code/Executable/appsettings.Development.json +++ b/code/Executable/appsettings.Development.json @@ -25,16 +25,17 @@ "Enabled": true, "RunSequentially": true, "IGNORED_DOWNLOADS_PATH": "../test/data/cleanuperr/ignored_downloads", - "IMPORT_FAILED_MAX_STRIKES": 5, + "IMPORT_FAILED_MAX_STRIKES": 3, "IMPORT_FAILED_IGNORE_PRIVATE": true, "IMPORT_FAILED_DELETE_PRIVATE": false, "IMPORT_FAILED_IGNORE_PATTERNS": [ "file is a sample" ], - "STALLED_MAX_STRIKES": 5, + "STALLED_MAX_STRIKES": 3, "STALLED_RESET_STRIKES_ON_PROGRESS": true, "STALLED_IGNORE_PRIVATE": true, - "STALLED_DELETE_PRIVATE": false + "STALLED_DELETE_PRIVATE": false, + "DOWNLOADING_METADATA_MAX_STRIKES": 3 }, "DownloadCleaner": { "Enabled": false, diff --git a/code/Executable/appsettings.json b/code/Executable/appsettings.json index 5fa1fc7..1e3c807 100644 --- a/code/Executable/appsettings.json +++ b/code/Executable/appsettings.json @@ -31,7 +31,8 @@ "STALLED_MAX_STRIKES": 0, "STALLED_RESET_STRIKES_ON_PROGRESS": false, "STALLED_IGNORE_PRIVATE": false, - "STALLED_DELETE_PRIVATE": false + "STALLED_DELETE_PRIVATE": false, + "DOWNLOADING_METADATA_MAX_STRIKES": 0 }, "DownloadCleaner": { "Enabled": false, diff --git a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs index 34c27d4..0d2bf1d 100644 --- a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs @@ -335,33 +335,39 @@ public class QBitService : DownloadService, IQBitService private async Task<(bool, DeleteReason)> IsItemStuckAndShouldRemove(TorrentInfo torrent, bool isPrivate) { - if (_queueCleanerConfig.StalledMaxStrikes is 0) + if (_queueCleanerConfig.StalledMaxStrikes is 0 && _queueCleanerConfig.DownloadingMetadataMaxStrikes is 0) { - return (false, default); - } - - if (_queueCleanerConfig.StalledIgnorePrivate && isPrivate) - { - // ignore private trackers - _logger.LogDebug("skip stalled check | download is private | {name}", torrent.Name); - return (false, default); + return (false, DeleteReason.None); } if (torrent.State is not TorrentState.StalledDownload and not TorrentState.FetchingMetadata and not TorrentState.ForcedFetchingMetadata) { // ignore other states - return (false, default); + return (false, DeleteReason.None); } - ResetStrikesOnProgress(torrent.Hash, torrent.Downloaded ?? 0); - - if (torrent.State is TorrentState.StalledDownload) + if (_queueCleanerConfig.StalledMaxStrikes > 0 && torrent.State is TorrentState.StalledDownload) { - return (await StrikeAndCheckLimit(torrent.Hash, torrent.Name, StrikeType.Stalled), DeleteReason.Stalled); + if (_queueCleanerConfig.StalledIgnorePrivate && isPrivate) + { + // ignore private trackers + _logger.LogDebug("skip stalled check | download is private | {name}", torrent.Name); + } + else + { + ResetStrikesOnProgress(torrent.Hash, torrent.Downloaded ?? 0); + + return (await StrikeAndCheckLimit(torrent.Hash, torrent.Name, StrikeType.Stalled), DeleteReason.Stalled); + } } - return (await StrikeAndCheckLimit(torrent.Hash, torrent.Name, StrikeType.DownloadingMetadata), DeleteReason.DownloadingMetadata); + if (_queueCleanerConfig.DownloadingMetadataMaxStrikes > 0) + { + return (await StrikeAndCheckLimit(torrent.Hash, torrent.Name, StrikeType.DownloadingMetadata), DeleteReason.DownloadingMetadata); + } + + return (false, DeleteReason.None); } private async Task> GetTrackersAsync(string hash) diff --git a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs index 32aadca..2446446 100644 --- a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs +++ b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs @@ -119,9 +119,9 @@ public sealed class QueueCleaner : GenericHandler bool removeFromClient = true; - if (stalledCheckResult.IsPrivate) + if (stalledCheckResult is { IsPrivate: true, DeleteReason: not DeleteReason.DownloadingMetadata }) { - if (stalledCheckResult.ShouldRemove && !_config.StalledDeletePrivate) + if (stalledCheckResult is { ShouldRemove: true, DeleteReason: DeleteReason.Stalled } && !_config.StalledDeletePrivate) { removeFromClient = false; } diff --git a/code/test/docker-compose.yml b/code/test/docker-compose.yml index fb8c186..553ffe7 100644 --- a/code/test/docker-compose.yml +++ b/code/test/docker-compose.yml @@ -193,13 +193,14 @@ services: - QUEUECLEANER__ENABLED=true - QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored - QUEUECLEANER__RUNSEQUENTIALLY=true - - QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=5 + - QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=3 - QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE=true - QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE=false - QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0=file is a sample - - QUEUECLEANER__STALLED_MAX_STRIKES=5 + - QUEUECLEANER__STALLED_MAX_STRIKES=3 - QUEUECLEANER__STALLED_IGNORE_PRIVATE=true - QUEUECLEANER__STALLED_DELETE_PRIVATE=false + - QUEUECLEANER__DOWNLOADING_METADATA_MAX_STRIKES=3 - CONTENTBLOCKER__ENABLED=true - CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH=/ignored diff --git a/variables.md b/variables.md index e556705..0ffc4b3 100644 --- a/variables.md +++ b/variables.md @@ -152,7 +152,7 @@ #### **`QUEUECLEANER__STALLED_MAX_STRIKES`** - Number of strikes before removing a stalled download. - Set to `0` to never remove stalled downloads. -- A strike is given when an item is stalled (not downloading) or stuck while downloading metadata. +- A strike is given when an item is stalled (not downloading). - Type: Integer - Default: `0` - Required: No. @@ -184,6 +184,19 @@ > [!WARNING] > Setting `QUEUECLEANER__STALLED_DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account. +#### **`QUEUECLEANER__DOWNLOADING_METADATA_MAX_STRIKES`** +- Number of strikes before removing a download stuck while downloading metadata. +- Set to `0` to never remove downloads stuck at `downloading metadata`. +- A strike is given when an item is stuck while downloading metadata. +- Type: Integer +- Default: `0` +- Required: No. +> [!NOTE] +> If not set to `0`, the minimum value is `3`. + +> [!IMPORTANT] +> `QUEUECLEANER__DOWNLOADING_METADATA_MAX_STRIKES` works only for qBitTorrent. + # ### Content Blocker settings @@ -317,7 +330,7 @@ > > For Deluge, the category name is the name of the label. > -> For Transmission, the category name is the last directory from the save location. +> For Transmission, the category name is the last directory from the save location (e.g. `myCategory` from `/downloads/path/myCategory`). #### **`DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO`** - Maximum ratio to reach before removing a download.