Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75492a5792 | |||
| 5ca717d7e0 | |||
| 7068ee5e5a | |||
| 9f770473e5 |
@@ -95,7 +95,7 @@ I've seen a few discussions on this type of naming and I've decided that I didn'
|
||||
- If it is, the item receives a **strike** and will continue to accumulate strikes every time it meets any of these conditions.
|
||||
- Check each queue item if it meets one of the following condition in the download client:
|
||||
- **Marked as completed, but 0 bytes have been downloaded** (due to files being blocked by qBittorrent or the **content blocker**).
|
||||
- All associated files of are marked as **unwanted/skipped**.
|
||||
- All associated files are marked as **unwanted/skipped/do not download**.
|
||||
- If the item **DOES NOT** match the above criteria, it will be skipped.
|
||||
- If the item **DOES** match the criteria or has received the **maximum number of strikes**:
|
||||
- It will be removed from the *arr's queue and blocked.
|
||||
@@ -183,16 +183,15 @@ services:
|
||||
- QUEUECLEANER__ENABLED=true
|
||||
- QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
|
||||
- QUEUECLEANER__RUNSEQUENTIALLY=true
|
||||
- QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=3
|
||||
- QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=5
|
||||
- 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=3
|
||||
- QUEUECLEANER__STALLED_MAX_STRIKES=5
|
||||
- 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
|
||||
@@ -272,7 +271,21 @@ services:
|
||||
|
||||
> [!TIP]
|
||||
> ### Run as a Windows Service
|
||||
> Check out this stackoverflow answer on how to do it: https://stackoverflow.com/a/15719678
|
||||
> 1. Download latest nssm build from `https://nssm.cc/builds`.
|
||||
> 2. Unzip `nssm.exe` in `C:\example\directory`.
|
||||
> 3. Open a terminal with Administrator rights and execute these commands:
|
||||
> ```
|
||||
> nssm.exe install Cleanuperr "C:\example\directory\cleanuperr.exe"
|
||||
> nssm.exe set Cleanuperr AppDirectory "C:\example\directory\"
|
||||
> nssm.exe set Cleanuperr AppStdout "C:\example\directory\cleanuperr.log"
|
||||
> nssm.exe set Cleanuperr AppStderr "C:\example\directory\cleanuperr.crash.log"
|
||||
> nssm.exe set Cleanuperr AppRotateFiles 1
|
||||
> nssm.exe set Cleanuperr AppRotateOnline 1
|
||||
> nssm.exe set Cleanuperr AppRotateBytes 10485760
|
||||
> nssm.exe set Cleanuperr AppRotateFiles 10
|
||||
> nssm.exe set Cleanuperr Start SERVICE_AUTO_START
|
||||
> nssm.exe start Cleanuperr
|
||||
> ```
|
||||
|
||||
### <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/linux.svg" height="20" style="vertical-align: middle;"> <span style="vertical-align: middle;">Linux</span>
|
||||
|
||||
|
||||
@@ -216,8 +216,6 @@
|
||||
*.log
|
||||
*.loop-vbs
|
||||
*.ls
|
||||
*.m3u
|
||||
*.m4a
|
||||
*.mac
|
||||
*.macho
|
||||
*.mamc
|
||||
@@ -271,7 +269,6 @@
|
||||
*.ncl
|
||||
*.net
|
||||
*.nexe
|
||||
*.nfo
|
||||
*.nrg
|
||||
*.num
|
||||
*.nzb.bz2
|
||||
@@ -402,7 +399,6 @@
|
||||
*.sql
|
||||
*.sqx
|
||||
*.srec
|
||||
*.srt
|
||||
*.ssm
|
||||
*.sts
|
||||
*.sub
|
||||
@@ -514,6 +510,4 @@
|
||||
*sample.mp4
|
||||
*sample.webm
|
||||
*sample.wmv
|
||||
Trailer.*
|
||||
VOSTFR
|
||||
api
|
||||
@@ -24,7 +24,7 @@ public sealed record QueueCleanerConfig : IJobConfig, IIgnoredDownloadsConfig
|
||||
public bool ImportFailedDeletePrivate { get; init; }
|
||||
|
||||
[ConfigurationKeyName("IMPORT_FAILED_IGNORE_PATTERNS")]
|
||||
public IReadOnlyList<string>? ImportFailedIgnorePatterns { get; init; }
|
||||
public List<string>? ImportFailedIgnorePatterns { get; init; }
|
||||
|
||||
[ConfigurationKeyName("STALLED_MAX_STRIKES")]
|
||||
public ushort StalledMaxStrikes { get; init; }
|
||||
@@ -38,24 +38,16 @@ 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 {SectionName.ToUpperInvariant()}__IMPORT_FAILED_MAX_STRIKES must be 3");
|
||||
throw new ValidationException("the minimum value for IMPORT_FAILED_MAX_STRIKES must be 3");
|
||||
}
|
||||
|
||||
if (StalledMaxStrikes is > 0 and < 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");
|
||||
throw new ValidationException("the minimum value for STALLED_MAX_STRIKES must be 3");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Domain.Enums;
|
||||
namespace Domain.Enums;
|
||||
|
||||
public enum DeleteReason
|
||||
{
|
||||
|
||||
@@ -25,17 +25,16 @@
|
||||
"Enabled": true,
|
||||
"RunSequentially": true,
|
||||
"IGNORED_DOWNLOADS_PATH": "../test/data/cleanuperr/ignored_downloads",
|
||||
"IMPORT_FAILED_MAX_STRIKES": 3,
|
||||
"IMPORT_FAILED_MAX_STRIKES": 5,
|
||||
"IMPORT_FAILED_IGNORE_PRIVATE": true,
|
||||
"IMPORT_FAILED_DELETE_PRIVATE": false,
|
||||
"IMPORT_FAILED_IGNORE_PATTERNS": [
|
||||
"file is a sample"
|
||||
],
|
||||
"STALLED_MAX_STRIKES": 3,
|
||||
"STALLED_MAX_STRIKES": 5,
|
||||
"STALLED_RESET_STRIKES_ON_PROGRESS": true,
|
||||
"STALLED_IGNORE_PRIVATE": true,
|
||||
"STALLED_DELETE_PRIVATE": false,
|
||||
"DOWNLOADING_METADATA_MAX_STRIKES": 3
|
||||
"STALLED_DELETE_PRIVATE": false
|
||||
},
|
||||
"DownloadCleaner": {
|
||||
"Enabled": false,
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
"STALLED_MAX_STRIKES": 0,
|
||||
"STALLED_RESET_STRIKES_ON_PROGRESS": false,
|
||||
"STALLED_IGNORE_PRIVATE": false,
|
||||
"STALLED_DELETE_PRIVATE": false,
|
||||
"DOWNLOADING_METADATA_MAX_STRIKES": 0
|
||||
"STALLED_DELETE_PRIVATE": false
|
||||
},
|
||||
"DownloadCleaner": {
|
||||
"Enabled": false,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FLM.QBittorrent" Version="1.0.0" />
|
||||
<PackageReference Include="FLM.Transmission" Version="1.0.2" />
|
||||
<PackageReference Include="FLM.Transmission" Version="1.0.3" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="MassTransit" Version="8.3.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
|
||||
|
||||
@@ -335,39 +335,33 @@ public class QBitService : DownloadService, IQBitService
|
||||
|
||||
private async Task<(bool, DeleteReason)> IsItemStuckAndShouldRemove(TorrentInfo torrent, bool isPrivate)
|
||||
{
|
||||
if (_queueCleanerConfig.StalledMaxStrikes is 0 && _queueCleanerConfig.DownloadingMetadataMaxStrikes is 0)
|
||||
if (_queueCleanerConfig.StalledMaxStrikes is 0)
|
||||
{
|
||||
return (false, DeleteReason.None);
|
||||
return (false, default);
|
||||
}
|
||||
|
||||
if (_queueCleanerConfig.StalledIgnorePrivate && isPrivate)
|
||||
{
|
||||
// ignore private trackers
|
||||
_logger.LogDebug("skip stalled check | download is private | {name}", torrent.Name);
|
||||
return (false, default);
|
||||
}
|
||||
|
||||
if (torrent.State is not TorrentState.StalledDownload and not TorrentState.FetchingMetadata
|
||||
and not TorrentState.ForcedFetchingMetadata)
|
||||
{
|
||||
// ignore other states
|
||||
return (false, DeleteReason.None);
|
||||
return (false, default);
|
||||
}
|
||||
|
||||
if (_queueCleanerConfig.StalledMaxStrikes > 0 && torrent.State is TorrentState.StalledDownload)
|
||||
ResetStrikesOnProgress(torrent.Hash, torrent.Downloaded ?? 0);
|
||||
|
||||
if (torrent.State is TorrentState.StalledDownload)
|
||||
{
|
||||
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.Stalled), DeleteReason.Stalled);
|
||||
}
|
||||
|
||||
if (_queueCleanerConfig.DownloadingMetadataMaxStrikes > 0)
|
||||
{
|
||||
return (await StrikeAndCheckLimit(torrent.Hash, torrent.Name, StrikeType.DownloadingMetadata), DeleteReason.DownloadingMetadata);
|
||||
}
|
||||
|
||||
return (false, DeleteReason.None);
|
||||
return (await StrikeAndCheckLimit(torrent.Hash, torrent.Name, StrikeType.DownloadingMetadata), DeleteReason.DownloadingMetadata);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<TorrentTracker>> GetTrackersAsync(string hash)
|
||||
|
||||
@@ -26,7 +26,6 @@ public class TransmissionService : DownloadService, ITransmissionService
|
||||
{
|
||||
private readonly TransmissionConfig _config;
|
||||
private readonly Client _client;
|
||||
private TorrentInfo[]? _torrentsCache;
|
||||
|
||||
private static readonly string[] Fields =
|
||||
[
|
||||
@@ -366,30 +365,8 @@ public class TransmissionService : DownloadService, ITransmissionService
|
||||
return (await StrikeAndCheckLimit(torrent.HashString!, torrent.Name!, StrikeType.Stalled), DeleteReason.Stalled);
|
||||
}
|
||||
|
||||
private async Task<TorrentInfo?> GetTorrentAsync(string hash)
|
||||
{
|
||||
TorrentInfo? torrent = _torrentsCache?
|
||||
.FirstOrDefault(x => x.HashString.Equals(hash, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (_torrentsCache is null || torrent is null)
|
||||
{
|
||||
// refresh cache
|
||||
_torrentsCache = (await _client.TorrentGetAsync(Fields))
|
||||
?.Torrents;
|
||||
}
|
||||
|
||||
if (_torrentsCache?.Length is null or 0)
|
||||
{
|
||||
_logger.LogDebug("could not list torrents | {url}", _config.Url);
|
||||
}
|
||||
|
||||
torrent = _torrentsCache?.FirstOrDefault(x => x.HashString.Equals(hash, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (torrent is null)
|
||||
{
|
||||
_logger.LogDebug("could not find torrent | {hash} | {url}", hash, _config.Url);
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
private async Task<TorrentInfo?> GetTorrentAsync(string hash) =>
|
||||
(await _client.TorrentGetAsync(Fields, hash))
|
||||
?.Torrents
|
||||
?.FirstOrDefault();
|
||||
}
|
||||
@@ -119,9 +119,9 @@ public sealed class QueueCleaner : GenericHandler
|
||||
|
||||
bool removeFromClient = true;
|
||||
|
||||
if (stalledCheckResult is { IsPrivate: true, DeleteReason: not DeleteReason.DownloadingMetadata })
|
||||
if (stalledCheckResult.IsPrivate)
|
||||
{
|
||||
if (stalledCheckResult is { ShouldRemove: true, DeleteReason: DeleteReason.Stalled } && !_config.StalledDeletePrivate)
|
||||
if (stalledCheckResult.ShouldRemove && !_config.StalledDeletePrivate)
|
||||
{
|
||||
removeFromClient = false;
|
||||
}
|
||||
|
||||
@@ -193,14 +193,13 @@ services:
|
||||
- QUEUECLEANER__ENABLED=true
|
||||
- QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored
|
||||
- QUEUECLEANER__RUNSEQUENTIALLY=true
|
||||
- QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=3
|
||||
- QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=5
|
||||
- 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=3
|
||||
- QUEUECLEANER__STALLED_MAX_STRIKES=5
|
||||
- QUEUECLEANER__STALLED_IGNORE_PRIVATE=true
|
||||
- QUEUECLEANER__STALLED_DELETE_PRIVATE=false
|
||||
- QUEUECLEANER__DOWNLOADING_METADATA_MAX_STRIKES=3
|
||||
|
||||
- CONTENTBLOCKER__ENABLED=true
|
||||
- CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH=/ignored
|
||||
|
||||
+2
-15
@@ -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).
|
||||
- A strike is given when an item is stalled (not downloading) or stuck while downloading metadata.
|
||||
- Type: Integer
|
||||
- Default: `0`
|
||||
- Required: No.
|
||||
@@ -184,19 +184,6 @@
|
||||
> [!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
|
||||
@@ -330,7 +317,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 (e.g. `myCategory` from `/downloads/path/myCategory`).
|
||||
> For Transmission, the category name is the last directory from the save location.
|
||||
|
||||
#### **`DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO`**
|
||||
- Maximum ratio to reach before removing a download.
|
||||
|
||||
Reference in New Issue
Block a user