mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-01-02 10:57:52 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c82b5e11b1 | ||
|
|
c36d9eb9cf | ||
|
|
2f21603e8e | ||
|
|
586f9964b5 | ||
|
|
124670bb98 | ||
|
|
baf6a8c2f4 | ||
|
|
cd345afc54 | ||
|
|
246ec4d6eb | ||
|
|
569eeae181 | ||
|
|
5a0ef56074 | ||
|
|
09bd4321fb | ||
|
|
4939e37210 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -4,9 +4,11 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: flmorg/universal-workflows/.github/workflows/dotnet.build.app.yml@main
|
||||
uses: flmorg/universal-workflows-testing/.github/workflows/dotnet.build.app.yml@main
|
||||
with:
|
||||
dockerRepository: flaminel/cleanuperr
|
||||
githubContext: ${{ toJSON(github) }}
|
||||
outputName: cleanuperr
|
||||
selfContained: false
|
||||
baseImage: 9.0-bookworm-slim
|
||||
secrets: inherit
|
||||
21
README.md
21
README.md
@@ -2,7 +2,7 @@ _Love this project? Give it a ⭐️ and let others know!_
|
||||
|
||||
# <img width="24px" src="./Logo/256.png" alt="cleanuperr"></img> Cleanuperr
|
||||
|
||||
[](https://discord.gg/sWggpnmGNY)
|
||||
[](https://discord.gg/SCtMCgtsc4)
|
||||
|
||||
Cleanuperr is a tool for automating the cleanup of unwanted or blocked files in Sonarr, Radarr, and supported download clients like qBittorrent. It removes incomplete or blocked downloads, updates queues, and enforces blacklists or whitelists to manage file selection. After removing blocked content, Cleanuperr can also trigger a search to replace the deleted shows/movies.
|
||||
|
||||
@@ -10,13 +10,16 @@ Cleanuperr was created primarily to address malicious files, such as `*.lnk` or
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Features:**
|
||||
> - Strike system to mark stalled or downloads stuck in metadata downloading.
|
||||
> - Strike system to mark bad downloads.
|
||||
> - Remove and block downloads that reached a maximum number of strikes.
|
||||
> - Remove and block downloads that have a low download speed or high estimated completion time.
|
||||
> - Remove downloads blocked by qBittorrent or by Cleanuperr's **content blocker**.
|
||||
> - Trigger a search for downloads removed from the *arrs.
|
||||
> - Clean up downloads that have been seeding for a certain amount of time.
|
||||
> - Notify on strike or download removal.
|
||||
> - Remove and block downloads that are **failing to be imported** by the arrs. [configuration](https://flmorg.github.io/cleanuperr/docs/configuration/queue-cleaner/import-failed)
|
||||
> - Remove and block downloads that are **stalled** or in **metadata downloading** state. [configuration](https://flmorg.github.io/cleanuperr/docs/configuration/queue-cleaner/stalled)
|
||||
> - Remove and block downloads that have a **low download speed** or **high estimated completion time**. [configuration](https://flmorg.github.io/cleanuperr/docs/configuration/queue-cleaner/slow)
|
||||
> - Remove and block downloads blocked by qBittorrent or by Cleanuperr's **Content Blocker**. [configuration](https://flmorg.github.io/cleanuperr/docs/configuration/content-blocker/general)
|
||||
> - Automatically trigger a search for downloads removed from the arrs.
|
||||
> - Clean up downloads that have been **seeding** for a certain amount of time. [configuration](https://flmorg.github.io/cleanuperr/docs/configuration/download-cleaner/seeding)
|
||||
> - Remove downloads that are **orphaned**/have no **hardlinks**/are not referenced by the arrs anymore (with [cross-seed](https://www.cross-seed.org/) support). [configuration](https://flmorg.github.io/cleanuperr/docs/configuration/download-cleaner/hardlinks)
|
||||
> - Notify on strike or download removal. [configuration](https://flmorg.github.io/cleanuperr/docs/category/notifications)
|
||||
> - Ignore certain torrent hashes, categories, tags or trackers from being processed by Cleanuperr.
|
||||
|
||||
Cleanuperr supports both qBittorrent's built-in exclusion features and its own blocklist-based system. Binaries for all platforms are provided, along with Docker images for easy deployment.
|
||||
@@ -38,13 +41,13 @@ Cleanuperr supports both qBittorrent's built-in exclusion features and its own b
|
||||
|
||||
Docs can be found [here](https://flmorg.github.io/cleanuperr/).
|
||||
|
||||
# <img width="24px" src="./Logo/256.png" alt="Cleanuperr"> Cleanuperr <svg width="14px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M376.6 84.5c11.3-13.6 9.5-33.8-4.1-45.1s-33.8-9.5-45.1 4.1L192 206 56.6 43.5C45.3 29.9 25.1 28.1 11.5 39.4S-3.9 70.9 7.4 84.5L150.3 256 7.4 427.5c-11.3 13.6-9.5 33.8 4.1 45.1s33.8 9.5 45.1-4.1L192 306 327.4 468.5c11.3 13.6 31.5 15.4 45.1 4.1s15.4-31.5 4.1-45.1L233.7 256 376.6 84.5z"/></svg> Huntarr <img width="24px" src="https://github.com/plexguide/Huntarr.io/blob/main/frontend/static/logo/256.png?raw=true" alt Huntarr></img>
|
||||
# <img style="vertical-align: middle;" width="24px" src="./Logo/256.png" alt="Cleanuperr"> <span style="vertical-align: middle;">Cleanuperr</span> <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/solid/x.svg" height="24px" width="30px" style="vertical-align: middle;"> <span style="vertical-align: middle;">Huntarr</span> <img style="vertical-align: middle;" width="24px" src="https://github.com/plexguide/Huntarr.io/blob/main/frontend/static/logo/512.png?raw=true" alt Huntarr></img>
|
||||
|
||||
Think of **Cleanuperr** as the janitor of your server; it keeps your download queue spotless, removes clutter, and blocks malicious files. Now imagine combining that with **Huntarr**, the compulsive librarian who finds missing and upgradable media to complete your collection
|
||||
|
||||
While **Huntarr** fills in the blanks and improves what you already have, **Cleanuperr** makes sure that only clean downloads get through. If you're aiming for a reliable and self-sufficient setup, **Cleanuperr** and **Huntarr** will take your automated media stack to another level.
|
||||
|
||||
<span style="font-size:24px"> ➡️ [**Huntarr**](https://github.com/plexguide/Huntarr.io) </span>
|
||||
<span style="font-size:24px"> ➡️ [**Huntarr**](https://github.com/plexguide/Huntarr.io) <span style="vertical-align: middle"></span></span>
|
||||
|
||||
# Credits
|
||||
Special thanks for inspiration go to:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Common.Configuration.ContentBlocker;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Common.Configuration.Arr;
|
||||
|
||||
@@ -7,6 +8,9 @@ public abstract record ArrConfig
|
||||
public required bool Enabled { get; init; }
|
||||
|
||||
public Block Block { get; init; } = new();
|
||||
|
||||
[ConfigurationKeyName("IMPORT_FAILED_MAX_STRIKES")]
|
||||
public short ImportFailedMaxStrikes { get; init; } = -1;
|
||||
|
||||
public required List<ArrInstance> Instances { get; init; }
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ public sealed record DownloadCleanerConfig : IJobConfig, IIgnoredDownloadsConfig
|
||||
[ConfigurationKeyName("UNLINKED_TARGET_CATEGORY")]
|
||||
public string UnlinkedTargetCategory { get; init; } = "cleanuperr-unlinked";
|
||||
|
||||
[ConfigurationKeyName("UNLINKED_USE_TAG")]
|
||||
public bool UnlinkedUseTag { get; init; }
|
||||
|
||||
[ConfigurationKeyName("UNLINKED_IGNORED_ROOT_DIR")]
|
||||
public string UnlinkedIgnoredRootDir { get; init; } = string.Empty;
|
||||
|
||||
@@ -33,11 +36,6 @@ public sealed record DownloadCleanerConfig : IJobConfig, IIgnoredDownloadsConfig
|
||||
return;
|
||||
}
|
||||
|
||||
if (Categories?.Count is null or 0)
|
||||
{
|
||||
throw new ValidationException("no categories configured");
|
||||
}
|
||||
|
||||
if (Categories?.GroupBy(x => x.Name).Any(x => x.Count() > 1) is true)
|
||||
{
|
||||
throw new ValidationException("duplicated clean categories found");
|
||||
|
||||
12
code/Common/Configuration/General/SearchConfig.cs
Normal file
12
code/Common/Configuration/General/SearchConfig.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Common.Configuration.General;
|
||||
|
||||
public sealed record SearchConfig
|
||||
{
|
||||
[ConfigurationKeyName("SEARCH_ENABLED")]
|
||||
public bool SearchEnabled { get; init; } = true;
|
||||
|
||||
[ConfigurationKeyName("SEARCH_DELAY")]
|
||||
public ushort SearchDelay { get; init; } = 30;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public static class ConfigurationDI
|
||||
public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) =>
|
||||
services
|
||||
.Configure<DryRunConfig>(configuration)
|
||||
.Configure<SearchConfig>(configuration)
|
||||
.Configure<QueueCleanerConfig>(configuration.GetSection(QueueCleanerConfig.SectionName))
|
||||
.Configure<ContentBlockerConfig>(configuration.GetSection(ContentBlockerConfig.SectionName))
|
||||
.Configure<DownloadCleanerConfig>(configuration.GetSection(DownloadCleanerConfig.SectionName))
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System.Net;
|
||||
using Common.Configuration.General;
|
||||
using Common.Helpers;
|
||||
using Domain.Models.Arr;
|
||||
using Infrastructure.Services;
|
||||
using Infrastructure.Verticals.DownloadClient.Deluge;
|
||||
using Infrastructure.Verticals.DownloadRemover.Consumers;
|
||||
using Infrastructure.Verticals.Notifications.Consumers;
|
||||
using Infrastructure.Verticals.Notifications.Models;
|
||||
using MassTransit;
|
||||
using MassTransit.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Polly;
|
||||
using Polly.Extensions.Http;
|
||||
@@ -27,6 +30,9 @@ public static class MainDI
|
||||
.AddNotifications(configuration)
|
||||
.AddMassTransit(config =>
|
||||
{
|
||||
config.AddConsumer<DownloadRemoverConsumer<SearchItem>>();
|
||||
config.AddConsumer<DownloadRemoverConsumer<SonarrSearchItem>>();
|
||||
|
||||
config.AddConsumer<NotificationConsumer<FailedImportStrikeNotification>>();
|
||||
config.AddConsumer<NotificationConsumer<StalledStrikeNotification>>();
|
||||
config.AddConsumer<NotificationConsumer<SlowStrikeNotification>>();
|
||||
@@ -36,6 +42,14 @@ public static class MainDI
|
||||
|
||||
config.UsingInMemory((context, cfg) =>
|
||||
{
|
||||
cfg.ReceiveEndpoint("download-remover-queue", e =>
|
||||
{
|
||||
e.ConfigureConsumer<DownloadRemoverConsumer<SearchItem>>(context);
|
||||
e.ConfigureConsumer<DownloadRemoverConsumer<SonarrSearchItem>>(context);
|
||||
e.ConcurrentMessageLimit = 1;
|
||||
e.PrefetchCount = 1;
|
||||
});
|
||||
|
||||
cfg.ReceiveEndpoint("notification-queue", e =>
|
||||
{
|
||||
e.ConfigureConsumer<NotificationConsumer<FailedImportStrikeNotification>>(context);
|
||||
|
||||
@@ -11,6 +11,8 @@ using Infrastructure.Verticals.DownloadClient;
|
||||
using Infrastructure.Verticals.DownloadClient.Deluge;
|
||||
using Infrastructure.Verticals.DownloadClient.QBittorrent;
|
||||
using Infrastructure.Verticals.DownloadClient.Transmission;
|
||||
using Infrastructure.Verticals.DownloadRemover;
|
||||
using Infrastructure.Verticals.DownloadRemover.Interfaces;
|
||||
using Infrastructure.Verticals.Files;
|
||||
using Infrastructure.Verticals.ItemStriker;
|
||||
using Infrastructure.Verticals.QueueCleaner;
|
||||
@@ -26,9 +28,11 @@ public static class ServicesDI
|
||||
.AddTransient<SonarrClient>()
|
||||
.AddTransient<RadarrClient>()
|
||||
.AddTransient<LidarrClient>()
|
||||
.AddTransient<ArrClientFactory>()
|
||||
.AddTransient<QueueCleaner>()
|
||||
.AddTransient<ContentBlocker>()
|
||||
.AddTransient<DownloadCleaner>()
|
||||
.AddTransient<IQueueItemRemover, QueueItemRemover>()
|
||||
.AddTransient<IFilenameEvaluator, FilenameEvaluator>()
|
||||
.AddTransient<IHardLinkFileService, HardLinkFileService>()
|
||||
.AddTransient<UnixHardLinkFileService>()
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"Path": ""
|
||||
}
|
||||
},
|
||||
"SEARCH_ENABLED": true,
|
||||
"SEARCH_DELAY": 5,
|
||||
"Triggers": {
|
||||
"QueueCleaner": "0/10 * * * * ?",
|
||||
"ContentBlocker": "0/10 * * * * ?",
|
||||
@@ -57,6 +59,7 @@
|
||||
}
|
||||
],
|
||||
"UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked",
|
||||
"UNLINKED_USE_TAG": false,
|
||||
"UNLINKED_IGNORED_ROOT_DIR": "",
|
||||
"UNLINKED_CATEGORIES": [
|
||||
"tv-sonarr",
|
||||
@@ -84,6 +87,7 @@
|
||||
},
|
||||
"Sonarr": {
|
||||
"Enabled": true,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"SearchType": "Episode",
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
@@ -98,6 +102,7 @@
|
||||
},
|
||||
"Radarr": {
|
||||
"Enabled": true,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
"Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist"
|
||||
@@ -111,6 +116,7 @@
|
||||
},
|
||||
"Lidarr": {
|
||||
"Enabled": true,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
"Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist"
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"Path": ""
|
||||
}
|
||||
},
|
||||
"SEARCH_ENABLED": true,
|
||||
"SEARCH_DELAY": 30,
|
||||
"Triggers": {
|
||||
"QueueCleaner": "0 0/5 * * * ?",
|
||||
"ContentBlocker": "0 0/5 * * * ?",
|
||||
@@ -47,6 +49,7 @@
|
||||
"DELETE_PRIVATE": false,
|
||||
"CATEGORIES": [],
|
||||
"UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked",
|
||||
"UNLINKED_USE_TAG": false,
|
||||
"UNLINKED_IGNORED_ROOT_DIR": "",
|
||||
"UNLINKED_CATEGORIES": [],
|
||||
"IGNORED_DOWNLOADS_PATH": ""
|
||||
@@ -71,6 +74,7 @@
|
||||
},
|
||||
"Sonarr": {
|
||||
"Enabled": false,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"SearchType": "Episode",
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
@@ -85,6 +89,7 @@
|
||||
},
|
||||
"Radarr": {
|
||||
"Enabled": false,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
"Path": ""
|
||||
@@ -98,6 +103,7 @@
|
||||
},
|
||||
"Lidarr": {
|
||||
"Enabled": false,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
"Path": ""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Domain.Models.Deluge.Response;
|
||||
using Infrastructure.Helpers;
|
||||
using Infrastructure.Services;
|
||||
|
||||
namespace Infrastructure.Extensions;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Infrastructure.Helpers;
|
||||
using Infrastructure.Services;
|
||||
using QBittorrent.Client;
|
||||
|
||||
namespace Infrastructure.Extensions;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Infrastructure.Helpers;
|
||||
using Infrastructure.Services;
|
||||
using Transmission.API.RPC.Entity;
|
||||
|
||||
namespace Infrastructure.Extensions;
|
||||
|
||||
@@ -13,4 +13,6 @@ public static class CacheKeys
|
||||
public static string StrikeItem(string hash, StrikeType strikeType) => $"item_{hash}_{strikeType.ToString()}";
|
||||
|
||||
public static string IgnoredDownloads(string name) => $"{name}_ignored";
|
||||
|
||||
public static string DownloadMarkedForRemoval(string hash, Uri url) => $"remove_{hash.ToLowerInvariant()}_{url}";
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
<PackageReference Include="MassTransit" Version="8.3.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.2" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Mono.Unix" Version="7.1.0-final.1.21458.1" />
|
||||
<PackageReference Include="Quartz" Version="3.13.1" />
|
||||
<PackageReference Include="Scrutor" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Infrastructure.Helpers;
|
||||
namespace Infrastructure.Services;
|
||||
|
||||
public static class UriService
|
||||
{
|
||||
@@ -73,7 +73,7 @@ public abstract class ArrClient : IArrClient
|
||||
return queueResponse;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ShouldRemoveFromQueue(InstanceType instanceType, QueueRecord record, bool isPrivateDownload)
|
||||
public virtual async Task<bool> ShouldRemoveFromQueue(InstanceType instanceType, QueueRecord record, bool isPrivateDownload, short arrMaxStrikes)
|
||||
{
|
||||
if (_queueCleanerConfig.ImportFailedIgnorePrivate && isPrivateDownload)
|
||||
{
|
||||
@@ -102,11 +102,19 @@ public abstract class ArrClient : IArrClient
|
||||
_logger.LogDebug("skip failed import check | contains ignored pattern | {name}", record.Title);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arrMaxStrikes is 0)
|
||||
{
|
||||
_logger.LogDebug("skip failed import check | arr max strikes is 0 | {name}", record.Title);
|
||||
return false;
|
||||
}
|
||||
|
||||
ushort maxStrikes = arrMaxStrikes > 0 ? (ushort)arrMaxStrikes : _queueCleanerConfig.ImportFailedMaxStrikes;
|
||||
|
||||
return await _striker.StrikeAndCheckLimit(
|
||||
record.DownloadId,
|
||||
record.Title,
|
||||
_queueCleanerConfig.ImportFailedMaxStrikes,
|
||||
maxStrikes,
|
||||
StrikeType.ImportFailed
|
||||
);
|
||||
}
|
||||
@@ -149,7 +157,7 @@ public abstract class ArrClient : IArrClient
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items);
|
||||
public abstract Task SearchItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items);
|
||||
|
||||
public virtual bool IsRecordValid(QueueRecord record)
|
||||
{
|
||||
|
||||
31
code/Infrastructure/Verticals/Arr/ArrClientFactory.cs
Normal file
31
code/Infrastructure/Verticals/Arr/ArrClientFactory.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Domain.Enums;
|
||||
using Infrastructure.Verticals.Arr.Interfaces;
|
||||
|
||||
namespace Infrastructure.Verticals.Arr;
|
||||
|
||||
public sealed class ArrClientFactory
|
||||
{
|
||||
private readonly ISonarrClient _sonarrClient;
|
||||
private readonly IRadarrClient _radarrClient;
|
||||
private readonly ILidarrClient _lidarrClient;
|
||||
|
||||
public ArrClientFactory(
|
||||
SonarrClient sonarrClient,
|
||||
RadarrClient radarrClient,
|
||||
LidarrClient lidarrClient
|
||||
)
|
||||
{
|
||||
_sonarrClient = sonarrClient;
|
||||
_radarrClient = radarrClient;
|
||||
_lidarrClient = lidarrClient;
|
||||
}
|
||||
|
||||
public IArrClient GetClient(InstanceType type) =>
|
||||
type switch
|
||||
{
|
||||
InstanceType.Sonarr => _sonarrClient,
|
||||
InstanceType.Radarr => _radarrClient,
|
||||
InstanceType.Lidarr => _lidarrClient,
|
||||
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||
};
|
||||
}
|
||||
@@ -9,11 +9,11 @@ public interface IArrClient
|
||||
{
|
||||
Task<QueueListResponse> GetQueueItemsAsync(ArrInstance arrInstance, int page);
|
||||
|
||||
Task<bool> ShouldRemoveFromQueue(InstanceType instanceType, QueueRecord record, bool isPrivateDownload);
|
||||
Task<bool> ShouldRemoveFromQueue(InstanceType instanceType, QueueRecord record, bool isPrivateDownload, short arrMaxStrikes);
|
||||
|
||||
Task DeleteQueueItemAsync(ArrInstance arrInstance, QueueRecord record, bool removeFromClient, DeleteReason deleteReason);
|
||||
|
||||
Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items);
|
||||
Task SearchItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items);
|
||||
|
||||
bool IsRecordValid(QueueRecord record);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class LidarrClient : ArrClient, ILidarrClient
|
||||
return query;
|
||||
}
|
||||
|
||||
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items)
|
||||
public override async Task SearchItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items)
|
||||
{
|
||||
if (items?.Count is null or 0)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ public class RadarrClient : ArrClient, IRadarrClient
|
||||
return query;
|
||||
}
|
||||
|
||||
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items)
|
||||
public override async Task SearchItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items)
|
||||
{
|
||||
if (items?.Count is null or 0)
|
||||
{
|
||||
|
||||
@@ -51,7 +51,7 @@ public class SonarrClient : ArrClient, ISonarrClient
|
||||
return query;
|
||||
}
|
||||
|
||||
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items)
|
||||
public override async Task SearchItemsAsync(ArrInstance arrInstance, HashSet<SearchItem>? items)
|
||||
{
|
||||
if (items?.Count is null or 0)
|
||||
{
|
||||
|
||||
@@ -6,16 +6,20 @@ using Common.Configuration.DownloadClient;
|
||||
using Domain.Enums;
|
||||
using Domain.Models.Arr;
|
||||
using Domain.Models.Arr.Queue;
|
||||
using Infrastructure.Helpers;
|
||||
using Infrastructure.Providers;
|
||||
using Infrastructure.Verticals.Arr;
|
||||
using Infrastructure.Verticals.Arr.Interfaces;
|
||||
using Infrastructure.Verticals.Context;
|
||||
using Infrastructure.Verticals.DownloadClient;
|
||||
using Infrastructure.Verticals.DownloadRemover.Models;
|
||||
using Infrastructure.Verticals.Jobs;
|
||||
using Infrastructure.Verticals.Notifications;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog.Context;
|
||||
using LogContext = Serilog.Context.LogContext;
|
||||
|
||||
namespace Infrastructure.Verticals.ContentBlocker;
|
||||
|
||||
@@ -32,9 +36,9 @@ public sealed class ContentBlocker : GenericHandler
|
||||
IOptions<SonarrConfig> sonarrConfig,
|
||||
IOptions<RadarrConfig> radarrConfig,
|
||||
IOptions<LidarrConfig> lidarrConfig,
|
||||
SonarrClient sonarrClient,
|
||||
RadarrClient radarrClient,
|
||||
LidarrClient lidarrClient,
|
||||
IMemoryCache cache,
|
||||
IBus messageBus,
|
||||
ArrClientFactory arrClientFactory,
|
||||
ArrQueueIterator arrArrQueueIterator,
|
||||
BlocklistProvider blocklistProvider,
|
||||
DownloadServiceFactory downloadServiceFactory,
|
||||
@@ -43,8 +47,7 @@ public sealed class ContentBlocker : GenericHandler
|
||||
) : base(
|
||||
logger, downloadClientConfig,
|
||||
sonarrConfig, radarrConfig, lidarrConfig,
|
||||
sonarrClient, radarrClient, lidarrClient,
|
||||
arrArrQueueIterator, downloadServiceFactory,
|
||||
cache, messageBus, arrClientFactory, arrArrQueueIterator, downloadServiceFactory,
|
||||
notifier
|
||||
)
|
||||
{
|
||||
@@ -75,21 +78,16 @@ public sealed class ContentBlocker : GenericHandler
|
||||
await base.ExecuteAsync();
|
||||
}
|
||||
|
||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config)
|
||||
{
|
||||
IReadOnlyList<string> ignoredDownloads = await _ignoredDownloadsProvider.GetIgnoredDownloads();
|
||||
|
||||
using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString());
|
||||
|
||||
HashSet<SearchItem> itemsToBeRefreshed = [];
|
||||
IArrClient arrClient = GetClient(instanceType);
|
||||
IArrClient arrClient = _arrClientFactory.GetClient(instanceType);
|
||||
BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType);
|
||||
ConcurrentBag<string> patterns = _blocklistProvider.GetPatterns(instanceType);
|
||||
ConcurrentBag<Regex> regexes = _blocklistProvider.GetRegexes(instanceType);
|
||||
|
||||
// push to context
|
||||
ContextProvider.Set(nameof(ArrInstance) + nameof(ArrInstance.Url), instance.Url);
|
||||
ContextProvider.Set(nameof(InstanceType), instanceType);
|
||||
|
||||
await _arrArrQueueIterator.Iterate(arrClient, instance, async items =>
|
||||
{
|
||||
@@ -117,9 +115,14 @@ public sealed class ContentBlocker : GenericHandler
|
||||
_logger.LogInformation("skip | {title} | ignored", record.Title);
|
||||
continue;
|
||||
}
|
||||
|
||||
string downloadRemovalKey = CacheKeys.DownloadMarkedForRemoval(record.DownloadId, instance.Url);
|
||||
|
||||
// push record to context
|
||||
ContextProvider.Set(nameof(QueueRecord), record);
|
||||
if (_cache.TryGetValue(downloadRemovalKey, out bool _))
|
||||
{
|
||||
_logger.LogDebug("skip | already marked for removal | {title}", record.Title);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug("searching unwanted files for {title}", record.Title);
|
||||
|
||||
@@ -133,8 +136,6 @@ public sealed class ContentBlocker : GenericHandler
|
||||
|
||||
_logger.LogDebug("all files are marked as unwanted | {hash}", record.Title);
|
||||
|
||||
itemsToBeRefreshed.Add(GetRecordSearchItem(instanceType, record, group.Count() > 1));
|
||||
|
||||
bool removeFromClient = true;
|
||||
|
||||
if (result.IsPrivate && !_config.DeletePrivate)
|
||||
@@ -142,11 +143,16 @@ public sealed class ContentBlocker : GenericHandler
|
||||
removeFromClient = false;
|
||||
}
|
||||
|
||||
await arrClient.DeleteQueueItemAsync(instance, record, removeFromClient, DeleteReason.AllFilesBlocked);
|
||||
await _notifier.NotifyQueueItemDeleted(removeFromClient, DeleteReason.AllFilesBlocked);
|
||||
await PublishQueueItemRemoveRequest(
|
||||
downloadRemovalKey,
|
||||
instanceType,
|
||||
instance,
|
||||
record,
|
||||
group.Count() > 1,
|
||||
removeFromClient,
|
||||
DeleteReason.AllFilesBlocked
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await arrClient.RefreshItemsAsync(instance, itemsToBeRefreshed);
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,11 @@ using Infrastructure.Verticals.Arr.Interfaces;
|
||||
using Infrastructure.Verticals.DownloadClient;
|
||||
using Infrastructure.Verticals.Jobs;
|
||||
using Infrastructure.Verticals.Notifications;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog.Context;
|
||||
using LogContext = Serilog.Context.LogContext;
|
||||
|
||||
namespace Infrastructure.Verticals.DownloadCleaner;
|
||||
|
||||
@@ -30,9 +32,9 @@ public sealed class DownloadCleaner : GenericHandler
|
||||
IOptions<SonarrConfig> sonarrConfig,
|
||||
IOptions<RadarrConfig> radarrConfig,
|
||||
IOptions<LidarrConfig> lidarrConfig,
|
||||
SonarrClient sonarrClient,
|
||||
RadarrClient radarrClient,
|
||||
LidarrClient lidarrClient,
|
||||
IMemoryCache cache,
|
||||
IBus messageBus,
|
||||
ArrClientFactory arrClientFactory,
|
||||
ArrQueueIterator arrArrQueueIterator,
|
||||
DownloadServiceFactory downloadServiceFactory,
|
||||
INotificationPublisher notifier,
|
||||
@@ -40,8 +42,7 @@ public sealed class DownloadCleaner : GenericHandler
|
||||
) : base(
|
||||
logger, downloadClientConfig,
|
||||
sonarrConfig, radarrConfig, lidarrConfig,
|
||||
sonarrClient, radarrClient, lidarrClient,
|
||||
arrArrQueueIterator, downloadServiceFactory,
|
||||
cache, messageBus, arrClientFactory, arrArrQueueIterator, downloadServiceFactory,
|
||||
notifier
|
||||
)
|
||||
{
|
||||
@@ -58,9 +59,12 @@ public sealed class DownloadCleaner : GenericHandler
|
||||
return;
|
||||
}
|
||||
|
||||
if (_config.Categories?.Count is null or 0)
|
||||
bool isUnlinkedEnabled = !string.IsNullOrEmpty(_config.UnlinkedTargetCategory) && _config.UnlinkedCategories?.Count > 0;
|
||||
bool isCleaningEnabled = _config.Categories?.Count > 0;
|
||||
|
||||
if (!isUnlinkedEnabled && !isCleaningEnabled)
|
||||
{
|
||||
_logger.LogWarning("no categories configured");
|
||||
_logger.LogWarning("{name} is not configured properly", nameof(DownloadCleaner));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,15 +72,27 @@ public sealed class DownloadCleaner : GenericHandler
|
||||
|
||||
await _downloadService.LoginAsync();
|
||||
List<object>? downloads = await _downloadService.GetSeedingDownloads();
|
||||
List<object>? downloadsToChangeCategory = null;
|
||||
|
||||
if (downloads?.Count is null or 0)
|
||||
{
|
||||
_logger.LogDebug("no seeding downloads found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.UnlinkedTargetCategory) && _config.UnlinkedCategories?.Count > 0)
|
||||
_logger.LogTrace("found {count} seeding downloads", downloads.Count);
|
||||
|
||||
List<object>? downloadsToChangeCategory = null;
|
||||
|
||||
if (isUnlinkedEnabled)
|
||||
{
|
||||
if (!_hardLinkCategoryCreated)
|
||||
{
|
||||
_logger.LogDebug("creating category {cat}", _config.UnlinkedTargetCategory);
|
||||
|
||||
await _downloadService.CreateCategoryAsync(_config.UnlinkedTargetCategory);
|
||||
if (_downloadClientConfig.DownloadClient is Common.Enums.DownloadClient.QBittorrent && !_config.UnlinkedUseTag)
|
||||
{
|
||||
_logger.LogDebug("creating category {cat}", _config.UnlinkedTargetCategory);
|
||||
await _downloadService.CreateCategoryAsync(_config.UnlinkedTargetCategory);
|
||||
}
|
||||
|
||||
_hardLinkCategoryCreated = true;
|
||||
}
|
||||
|
||||
@@ -89,24 +105,34 @@ public sealed class DownloadCleaner : GenericHandler
|
||||
await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr, true);
|
||||
await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr, true);
|
||||
await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr, true);
|
||||
|
||||
if (isUnlinkedEnabled)
|
||||
{
|
||||
_logger.LogTrace("found {count} potential downloads to change category", downloadsToChangeCategory?.Count);
|
||||
await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes, ignoredDownloads);
|
||||
_logger.LogTrace("finished changing category");
|
||||
}
|
||||
|
||||
_logger.LogTrace("looking for downloads to change category");
|
||||
await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes, ignoredDownloads);
|
||||
if (_config.Categories?.Count is null or 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<object>? downloadsToClean = _downloadService.FilterDownloadsToBeCleanedAsync(downloads, _config.Categories);
|
||||
|
||||
// release unused objects
|
||||
downloads = null;
|
||||
|
||||
_logger.LogTrace("looking for downloads to clean");
|
||||
|
||||
_logger.LogTrace("found {count} potential downloads to clean", downloadsToClean?.Count);
|
||||
await _downloadService.CleanDownloadsAsync(downloadsToClean, _config.Categories, _excludedHashes, ignoredDownloads);
|
||||
_logger.LogTrace("finished cleaning downloads");
|
||||
}
|
||||
|
||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config)
|
||||
{
|
||||
using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString());
|
||||
|
||||
IArrClient arrClient = GetClient(instanceType);
|
||||
IArrClient arrClient = _arrClientFactory.GetClient(instanceType);
|
||||
|
||||
await _arrArrQueueIterator.Iterate(arrClient, instance, async items =>
|
||||
{
|
||||
|
||||
@@ -251,6 +251,15 @@ public class QBitService : DownloadService, IQBitService
|
||||
?.Cast<TorrentInfo>()
|
||||
.Where(x => !string.IsNullOrEmpty(x.Hash))
|
||||
.Where(x => categories.Any(cat => cat.Equals(x.Category, StringComparison.InvariantCultureIgnoreCase)))
|
||||
.Where(x =>
|
||||
{
|
||||
if (_downloadCleanerConfig.UnlinkedUseTag)
|
||||
{
|
||||
return !x.Tags.Any(tag => tag.Equals(_downloadCleanerConfig.UnlinkedTargetCategory, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.Cast<object>()
|
||||
.ToList();
|
||||
|
||||
@@ -436,12 +445,18 @@ public class QBitService : DownloadService, IQBitService
|
||||
}
|
||||
|
||||
await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.UnlinkedTargetCategory);
|
||||
|
||||
if (_downloadCleanerConfig.UnlinkedUseTag)
|
||||
{
|
||||
_logger.LogInformation("tag added for {name}", download.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("category changed for {name}", download.Name);
|
||||
download.Category = _downloadCleanerConfig.UnlinkedTargetCategory;
|
||||
}
|
||||
|
||||
_logger.LogInformation("category changed for {name}", download.Name);
|
||||
|
||||
await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.UnlinkedTargetCategory);
|
||||
|
||||
download.Category = _downloadCleanerConfig.UnlinkedTargetCategory;
|
||||
await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.UnlinkedTargetCategory, _downloadCleanerConfig.UnlinkedUseTag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +482,12 @@ public class QBitService : DownloadService, IQBitService
|
||||
[DryRunSafeguard]
|
||||
protected virtual async Task ChangeCategory(string hash, string newCategory)
|
||||
{
|
||||
if (_downloadCleanerConfig.UnlinkedUseTag)
|
||||
{
|
||||
await _client.AddTorrentTagAsync([hash], newCategory);
|
||||
return;
|
||||
}
|
||||
|
||||
await _client.SetTorrentCategoryAsync([hash], newCategory);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Domain.Models.Arr;
|
||||
using Infrastructure.Verticals.DownloadRemover.Interfaces;
|
||||
using Infrastructure.Verticals.DownloadRemover.Models;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Infrastructure.Verticals.DownloadRemover.Consumers;
|
||||
|
||||
public class DownloadRemoverConsumer<T> : IConsumer<QueueItemRemoveRequest<T>>
|
||||
where T : SearchItem
|
||||
{
|
||||
private readonly ILogger<DownloadRemoverConsumer<T>> _logger;
|
||||
private readonly IQueueItemRemover _queueItemRemover;
|
||||
|
||||
public DownloadRemoverConsumer(
|
||||
ILogger<DownloadRemoverConsumer<T>> logger,
|
||||
IQueueItemRemover queueItemRemover
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_queueItemRemover = queueItemRemover;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<QueueItemRemoveRequest<T>> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _queueItemRemover.RemoveQueueItemAsync(context.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogError(exception,
|
||||
"failed to remove queue item| {title} | {url}",
|
||||
context.Message.Record.Title,
|
||||
context.Message.Instance.Url
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Domain.Models.Arr;
|
||||
using Infrastructure.Verticals.DownloadRemover.Models;
|
||||
|
||||
namespace Infrastructure.Verticals.DownloadRemover.Interfaces;
|
||||
|
||||
public interface IQueueItemRemover
|
||||
{
|
||||
Task RemoveQueueItemAsync<T>(QueueItemRemoveRequest<T> request) where T : SearchItem;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Common.Configuration.Arr;
|
||||
using Domain.Enums;
|
||||
using Domain.Models.Arr;
|
||||
using Domain.Models.Arr.Queue;
|
||||
|
||||
namespace Infrastructure.Verticals.DownloadRemover.Models;
|
||||
|
||||
public sealed record QueueItemRemoveRequest<T>
|
||||
where T : SearchItem
|
||||
{
|
||||
public required InstanceType InstanceType { get; init; }
|
||||
|
||||
public required ArrInstance Instance { get; init; }
|
||||
|
||||
public required T SearchItem { get; init; }
|
||||
|
||||
public required QueueRecord Record { get; init; }
|
||||
|
||||
public required bool RemoveFromClient { get; init; }
|
||||
|
||||
public required DeleteReason DeleteReason { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Common.Configuration.Arr;
|
||||
using Common.Configuration.General;
|
||||
using Domain.Enums;
|
||||
using Domain.Models.Arr;
|
||||
using Domain.Models.Arr.Queue;
|
||||
using Infrastructure.Helpers;
|
||||
using Infrastructure.Verticals.Arr;
|
||||
using Infrastructure.Verticals.Context;
|
||||
using Infrastructure.Verticals.DownloadRemover.Interfaces;
|
||||
using Infrastructure.Verticals.DownloadRemover.Models;
|
||||
using Infrastructure.Verticals.Notifications;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Infrastructure.Verticals.DownloadRemover;
|
||||
|
||||
public sealed class QueueItemRemover : IQueueItemRemover
|
||||
{
|
||||
private readonly SearchConfig _searchConfig;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ArrClientFactory _arrClientFactory;
|
||||
private readonly INotificationPublisher _notifier;
|
||||
|
||||
public QueueItemRemover(
|
||||
IOptions<SearchConfig> searchConfig,
|
||||
IMemoryCache cache,
|
||||
ArrClientFactory arrClientFactory,
|
||||
INotificationPublisher notifier
|
||||
)
|
||||
{
|
||||
_searchConfig = searchConfig.Value;
|
||||
_cache = cache;
|
||||
_arrClientFactory = arrClientFactory;
|
||||
_notifier = notifier;
|
||||
}
|
||||
|
||||
public async Task RemoveQueueItemAsync<T>(QueueItemRemoveRequest<T> request)
|
||||
where T : SearchItem
|
||||
{
|
||||
try
|
||||
{
|
||||
var arrClient = _arrClientFactory.GetClient(request.InstanceType);
|
||||
await arrClient.DeleteQueueItemAsync(request.Instance, request.Record, request.RemoveFromClient, request.DeleteReason);
|
||||
|
||||
// push to context
|
||||
ContextProvider.Set(nameof(QueueRecord), request.Record);
|
||||
ContextProvider.Set(nameof(ArrInstance) + nameof(ArrInstance.Url), request.Instance.Url);
|
||||
ContextProvider.Set(nameof(InstanceType), request.InstanceType);
|
||||
await _notifier.NotifyQueueItemDeleted(request.RemoveFromClient, request.DeleteReason);
|
||||
|
||||
if (!_searchConfig.SearchEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await arrClient.SearchItemsAsync(request.Instance, [request.SearchItem]);
|
||||
|
||||
// prevent tracker spamming
|
||||
await Task.Delay(TimeSpan.FromSeconds(_searchConfig.SearchDelay));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cache.Remove(CacheKeys.DownloadMarkedForRemoval(request.Record.DownloadId, request.Instance.Url));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@ using Domain.Enums;
|
||||
using Domain.Models.Arr;
|
||||
using Domain.Models.Arr.Queue;
|
||||
using Infrastructure.Verticals.Arr;
|
||||
using Infrastructure.Verticals.Arr.Interfaces;
|
||||
using Infrastructure.Verticals.DownloadClient;
|
||||
using Infrastructure.Verticals.DownloadRemover.Models;
|
||||
using Infrastructure.Verticals.Notifications;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -19,9 +21,9 @@ public abstract class GenericHandler : IHandler, IDisposable
|
||||
protected readonly SonarrConfig _sonarrConfig;
|
||||
protected readonly RadarrConfig _radarrConfig;
|
||||
protected readonly LidarrConfig _lidarrConfig;
|
||||
protected readonly ISonarrClient _sonarrClient;
|
||||
protected readonly IRadarrClient _radarrClient;
|
||||
protected readonly ILidarrClient _lidarrClient;
|
||||
protected readonly IMemoryCache _cache;
|
||||
protected readonly IBus _messageBus;
|
||||
protected readonly ArrClientFactory _arrClientFactory;
|
||||
protected readonly ArrQueueIterator _arrArrQueueIterator;
|
||||
protected readonly IDownloadService _downloadService;
|
||||
protected readonly INotificationPublisher _notifier;
|
||||
@@ -32,9 +34,9 @@ public abstract class GenericHandler : IHandler, IDisposable
|
||||
IOptions<SonarrConfig> sonarrConfig,
|
||||
IOptions<RadarrConfig> radarrConfig,
|
||||
IOptions<LidarrConfig> lidarrConfig,
|
||||
ISonarrClient sonarrClient,
|
||||
IRadarrClient radarrClient,
|
||||
ILidarrClient lidarrClient,
|
||||
IMemoryCache cache,
|
||||
IBus messageBus,
|
||||
ArrClientFactory arrClientFactory,
|
||||
ArrQueueIterator arrArrQueueIterator,
|
||||
DownloadServiceFactory downloadServiceFactory,
|
||||
INotificationPublisher notifier
|
||||
@@ -45,9 +47,9 @@ public abstract class GenericHandler : IHandler, IDisposable
|
||||
_sonarrConfig = sonarrConfig.Value;
|
||||
_radarrConfig = radarrConfig.Value;
|
||||
_lidarrConfig = lidarrConfig.Value;
|
||||
_sonarrClient = sonarrClient;
|
||||
_radarrClient = radarrClient;
|
||||
_lidarrClient = lidarrClient;
|
||||
_cache = cache;
|
||||
_messageBus = messageBus;
|
||||
_arrClientFactory = arrClientFactory;
|
||||
_arrArrQueueIterator = arrArrQueueIterator;
|
||||
_downloadService = downloadServiceFactory.CreateDownloadClient();
|
||||
_notifier = notifier;
|
||||
@@ -67,7 +69,7 @@ public abstract class GenericHandler : IHandler, IDisposable
|
||||
_downloadService.Dispose();
|
||||
}
|
||||
|
||||
protected abstract Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType);
|
||||
protected abstract Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config);
|
||||
|
||||
protected async Task ProcessArrConfigAsync(ArrConfig config, InstanceType instanceType, bool throwOnFailure = false)
|
||||
{
|
||||
@@ -80,7 +82,7 @@ public abstract class GenericHandler : IHandler, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessInstanceAsync(arrInstance, instanceType);
|
||||
await ProcessInstanceAsync(arrInstance, instanceType, config);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -93,16 +95,50 @@ public abstract class GenericHandler : IHandler, IDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected IArrClient GetClient(InstanceType type) =>
|
||||
type switch
|
||||
{
|
||||
InstanceType.Sonarr => _sonarrClient,
|
||||
InstanceType.Radarr => _radarrClient,
|
||||
InstanceType.Lidarr => _lidarrClient,
|
||||
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||
};
|
||||
|
||||
protected async Task PublishQueueItemRemoveRequest(
|
||||
string downloadRemovalKey,
|
||||
InstanceType instanceType,
|
||||
ArrInstance instance,
|
||||
QueueRecord record,
|
||||
bool isPack,
|
||||
bool removeFromClient,
|
||||
DeleteReason deleteReason
|
||||
)
|
||||
{
|
||||
if (instanceType is InstanceType.Sonarr)
|
||||
{
|
||||
QueueItemRemoveRequest<SonarrSearchItem> removeRequest = new()
|
||||
{
|
||||
InstanceType = instanceType,
|
||||
Instance = instance,
|
||||
Record = record,
|
||||
SearchItem = (SonarrSearchItem)GetRecordSearchItem(instanceType, record, isPack),
|
||||
RemoveFromClient = removeFromClient,
|
||||
DeleteReason = deleteReason
|
||||
};
|
||||
|
||||
await _messageBus.Publish(removeRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueItemRemoveRequest<SearchItem> removeRequest = new()
|
||||
{
|
||||
InstanceType = instanceType,
|
||||
Instance = instance,
|
||||
Record = record,
|
||||
SearchItem = GetRecordSearchItem(instanceType, record, isPack),
|
||||
RemoveFromClient = removeFromClient,
|
||||
DeleteReason = deleteReason
|
||||
};
|
||||
|
||||
await _messageBus.Publish(removeRequest);
|
||||
}
|
||||
|
||||
_cache.Set(downloadRemovalKey, true);
|
||||
_logger.LogInformation("item marked for removal | {title} | {url}", record.Title, instance.Url);
|
||||
}
|
||||
|
||||
protected SearchItem GetRecordSearchItem(InstanceType type, QueueRecord record, bool isPack = false)
|
||||
{
|
||||
return type switch
|
||||
|
||||
@@ -10,5 +10,5 @@ public interface INotificationPublisher
|
||||
|
||||
Task NotifyDownloadCleaned(double ratio, TimeSpan seedingTime, string categoryName, CleanReason reason);
|
||||
|
||||
Task NotifyCategoryChanged(string oldCategory, string newCategory);
|
||||
Task NotifyCategoryChanged(string oldCategory, string newCategory, bool isTag = false);
|
||||
}
|
||||
@@ -123,21 +123,29 @@ public class NotificationPublisher : INotificationPublisher
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task NotifyCategoryChanged(string oldCategory, string newCategory)
|
||||
public virtual async Task NotifyCategoryChanged(string oldCategory, string newCategory, bool isTag = false)
|
||||
{
|
||||
CategoryChangedNotification notification = new()
|
||||
{
|
||||
Title = "Category changed",
|
||||
Title = isTag? "Tag added" : "Category changed",
|
||||
Description = ContextProvider.Get<string>("downloadName"),
|
||||
Fields =
|
||||
[
|
||||
new() { Title = "Hash", Text = ContextProvider.Get<string>("hash").ToLowerInvariant() },
|
||||
new() { Title = "Old category", Text = oldCategory },
|
||||
new() { Title = "New category", Text = newCategory }
|
||||
new() { Title = "Hash", Text = ContextProvider.Get<string>("hash").ToLowerInvariant() }
|
||||
],
|
||||
Level = NotificationLevel.Important
|
||||
};
|
||||
|
||||
if (isTag)
|
||||
{
|
||||
notification.Fields.Add(new() { Title = "Tag", Text = newCategory });
|
||||
}
|
||||
else
|
||||
{
|
||||
notification.Fields.Add(new() { Title = "Old category", Text = oldCategory });
|
||||
notification.Fields.Add(new() { Title = "New category", Text = newCategory });
|
||||
}
|
||||
|
||||
await NotifyInternal(notification);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,22 +4,27 @@ using Common.Configuration.QueueCleaner;
|
||||
using Domain.Enums;
|
||||
using Domain.Models.Arr;
|
||||
using Domain.Models.Arr.Queue;
|
||||
using Infrastructure.Helpers;
|
||||
using Infrastructure.Providers;
|
||||
using Infrastructure.Verticals.Arr;
|
||||
using Infrastructure.Verticals.Arr.Interfaces;
|
||||
using Infrastructure.Verticals.Context;
|
||||
using Infrastructure.Verticals.DownloadClient;
|
||||
using Infrastructure.Verticals.DownloadRemover.Models;
|
||||
using Infrastructure.Verticals.Jobs;
|
||||
using Infrastructure.Verticals.Notifications;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog.Context;
|
||||
using LogContext = Serilog.Context.LogContext;
|
||||
|
||||
namespace Infrastructure.Verticals.QueueCleaner;
|
||||
|
||||
public sealed class QueueCleaner : GenericHandler
|
||||
{
|
||||
private readonly QueueCleanerConfig _config;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IgnoredDownloadsProvider<QueueCleanerConfig> _ignoredDownloadsProvider;
|
||||
|
||||
public QueueCleaner(
|
||||
@@ -29,9 +34,9 @@ public sealed class QueueCleaner : GenericHandler
|
||||
IOptions<SonarrConfig> sonarrConfig,
|
||||
IOptions<RadarrConfig> radarrConfig,
|
||||
IOptions<LidarrConfig> lidarrConfig,
|
||||
SonarrClient sonarrClient,
|
||||
RadarrClient radarrClient,
|
||||
LidarrClient lidarrClient,
|
||||
IMemoryCache cache,
|
||||
IBus messageBus,
|
||||
ArrClientFactory arrClientFactory,
|
||||
ArrQueueIterator arrArrQueueIterator,
|
||||
DownloadServiceFactory downloadServiceFactory,
|
||||
INotificationPublisher notifier,
|
||||
@@ -39,24 +44,23 @@ public sealed class QueueCleaner : GenericHandler
|
||||
) : base(
|
||||
logger, downloadClientConfig,
|
||||
sonarrConfig, radarrConfig, lidarrConfig,
|
||||
sonarrClient, radarrClient, lidarrClient,
|
||||
arrArrQueueIterator, downloadServiceFactory,
|
||||
cache, messageBus, arrClientFactory, arrArrQueueIterator, downloadServiceFactory,
|
||||
notifier
|
||||
)
|
||||
{
|
||||
_config = config.Value;
|
||||
_config.Validate();
|
||||
_cache = cache;
|
||||
_ignoredDownloadsProvider = ignoredDownloadsProvider;
|
||||
}
|
||||
|
||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config)
|
||||
{
|
||||
IReadOnlyList<string> ignoredDownloads = await _ignoredDownloadsProvider.GetIgnoredDownloads();
|
||||
|
||||
using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString());
|
||||
|
||||
HashSet<SearchItem> itemsToBeRefreshed = [];
|
||||
IArrClient arrClient = GetClient(instanceType);
|
||||
IArrClient arrClient = _arrClientFactory.GetClient(instanceType);
|
||||
|
||||
// push to context
|
||||
ContextProvider.Set(nameof(ArrInstance) + nameof(ArrInstance.Url), instance.Url);
|
||||
@@ -90,6 +94,14 @@ public sealed class QueueCleaner : GenericHandler
|
||||
continue;
|
||||
}
|
||||
|
||||
string downloadRemovalKey = CacheKeys.DownloadMarkedForRemoval(record.DownloadId, instance.Url);
|
||||
|
||||
if (_cache.TryGetValue(downloadRemovalKey, out bool _))
|
||||
{
|
||||
_logger.LogDebug("skip | already marked for removal | {title}", record.Title);
|
||||
continue;
|
||||
}
|
||||
|
||||
// push record to context
|
||||
ContextProvider.Set(nameof(QueueRecord), record);
|
||||
|
||||
@@ -108,7 +120,7 @@ public sealed class QueueCleaner : GenericHandler
|
||||
}
|
||||
|
||||
// failed import check
|
||||
bool shouldRemoveFromArr = await arrClient.ShouldRemoveFromQueue(instanceType, record, downloadCheckResult.IsPrivate);
|
||||
bool shouldRemoveFromArr = await arrClient.ShouldRemoveFromQueue(instanceType, record, downloadCheckResult.IsPrivate, config.ImportFailedMaxStrikes);
|
||||
DeleteReason deleteReason = downloadCheckResult.ShouldRemove ? downloadCheckResult.DeleteReason : DeleteReason.ImportFailed;
|
||||
|
||||
if (!shouldRemoveFromArr && !downloadCheckResult.ShouldRemove)
|
||||
@@ -116,8 +128,6 @@ public sealed class QueueCleaner : GenericHandler
|
||||
_logger.LogInformation("skip | {title}", record.Title);
|
||||
continue;
|
||||
}
|
||||
|
||||
itemsToBeRefreshed.Add(GetRecordSearchItem(instanceType, record, group.Count() > 1));
|
||||
|
||||
bool removeFromClient = true;
|
||||
|
||||
@@ -140,11 +150,16 @@ public sealed class QueueCleaner : GenericHandler
|
||||
}
|
||||
}
|
||||
|
||||
await arrClient.DeleteQueueItemAsync(instance, record, removeFromClient, deleteReason);
|
||||
await _notifier.NotifyQueueItemDeleted(removeFromClient, deleteReason);
|
||||
await PublishQueueItemRemoveRequest(
|
||||
downloadRemovalKey,
|
||||
instanceType,
|
||||
instance,
|
||||
record,
|
||||
group.Count() > 1,
|
||||
removeFromClient,
|
||||
deleteReason
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await arrClient.RefreshItemsAsync(instance, itemsToBeRefreshed);
|
||||
}
|
||||
}
|
||||
@@ -186,6 +186,9 @@ services:
|
||||
- HTTP_MAX_RETRIES=0
|
||||
- HTTP_TIMEOUT=20
|
||||
|
||||
- SEARCH_ENABLED=true
|
||||
- SEARCH_DELAY=5
|
||||
|
||||
- TRIGGERS__QUEUECLEANER=0/30 * * * * ?
|
||||
- TRIGGERS__CONTENTBLOCKER=0/30 * * * * ?
|
||||
- TRIGGERS__DOWNLOADCLEANER=0/30 * * * * ?
|
||||
@@ -221,15 +224,18 @@ services:
|
||||
- DOWNLOADCLEANER__ENABLED=true
|
||||
- DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH=/ignored
|
||||
- DOWNLOADCLEANER__DELETE_PRIVATE=false
|
||||
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__NAME=tv-sonarr
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO=-1
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME=0
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME=99999
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__NAME=nohardlink
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__NAME=cleanuperr-unlinked
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__MAX_RATIO=-1
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__MIN_SEED_TIME=0
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__MAX_SEED_TIME=99999
|
||||
|
||||
- DOWNLOADCLEANER__UNLINKED_TARGET_CATEGORY=cleanuperr-unlinked
|
||||
- DOWNLOADCLEANER__UNLINKED_USE_TAG=false
|
||||
- DOWNLOADCLEANER__UNLINKED_IGNORED_ROOT_DIR=/downloads
|
||||
- DOWNLOADCLEANER__UNLINKED_CATEGORIES__0=tv-sonarr
|
||||
- DOWNLOADCLEANER__UNLINKED_CATEGORIES__1=radarr
|
||||
@@ -249,6 +255,7 @@ services:
|
||||
# - TRANSMISSION__PASSWORD=testing
|
||||
|
||||
- SONARR__ENABLED=true
|
||||
- SONARR__IMPORT_FAILED_MAX_STRIKES=-1
|
||||
- SONARR__SEARCHTYPE=Episode
|
||||
- SONARR__BLOCK__TYPE=blacklist
|
||||
- SONARR__BLOCK__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist
|
||||
@@ -256,12 +263,14 @@ services:
|
||||
- SONARR__INSTANCES__0__APIKEY=425d1e713f0c405cbbf359ac0502c1f4
|
||||
|
||||
- RADARR__ENABLED=true
|
||||
- RADARR__IMPORT_FAILED_MAX_STRIKES=-1
|
||||
- RADARR__BLOCK__TYPE=blacklist
|
||||
- RADARR__BLOCK__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist
|
||||
- RADARR__INSTANCES__0__URL=http://radarr:7878
|
||||
- RADARR__INSTANCES__0__APIKEY=8b7454f668e54c5b8f44f56f93969761
|
||||
|
||||
- LIDARR__ENABLED=true
|
||||
- LIDARR__IMPORT_FAILED_MAX_STRIKES=-1
|
||||
- LIDARR__BLOCK__TYPE=blacklist
|
||||
- LIDARR__BLOCK__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist # TODO
|
||||
- LIDARR__INSTANCES__0__URL=http://lidarr:8686
|
||||
|
||||
@@ -13,7 +13,7 @@ Cleanuperr was created primarily to address malicious files, such as `*.lnk` or
|
||||
<Warning>
|
||||
Because this tool is actively developed and still a work in progress, using the `latest` Docker tag may result in breaking changes.
|
||||
|
||||
Join the Discord server if you want to reach out to me quickly (or just stay updated on new releases) so we can squash those pesky bugs together: https://discord.gg/sWggpnmGNY
|
||||
Join the Discord server if you want to reach out to me quickly (or just stay updated on new releases) so we can squash those pesky bugs together: https://discord.gg/SCtMCgtsc4
|
||||
</Warning>
|
||||
|
||||
|
||||
|
||||
21
docs/docs/2_features.mdx
Normal file
21
docs/docs/2_features.mdx
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Features
|
||||
|
||||
<div style={{ fontSize: '1.2rem' }}>
|
||||
- Strike system to mark bad downloads.
|
||||
- Remove and block downloads that reached a maximum number of strikes.
|
||||
- Remove and block downloads that are **failing to be imported** by the arrs. <Link to="/docs/configuration/queue-cleaner/import-failed">[configuration]</Link>
|
||||
- Remove and block downloads that are **stalled** or in **metadata downloading** state. <Link to="/docs/configuration/queue-cleaner/stalled">[configuration]</Link>
|
||||
- Remove and block downloads that have a **low download speed** or **high estimated completion time**. <Link to="/docs/configuration/queue-cleaner/slow">[configuration]</Link>
|
||||
- Remove and block downloads blocked by qBittorrent or by Cleanuperr's **Content Blocker**. <Link to="/docs/configuration/content-blocker/general">[configuration]</Link>
|
||||
- Automatically trigger a search for downloads removed from the arrs.
|
||||
- Clean up downloads that have been **seeding** for a certain amount of time. <Link to="/docs/configuration/download-cleaner/seeding">[configuration]</Link>
|
||||
- Remove downloads that are **orphaned**/have no **hardlinks**/are not referenced by the arrs anymore (with [cross-seed](https://www.cross-seed.org/) support). <Link to="/docs/configuration/download-cleaner/hardlinks">[configuration]</Link>
|
||||
- Notify on strike or download removal. <Link to="/docs/category/notifications">[configuration]</Link>
|
||||
- Ignore certain torrent hashes, categories, tags or trackers from being processed by Cleanuperr.
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
import GeneralSettings from '@site/src/components/configuration/GeneralSettings';
|
||||
|
||||
# General Settings
|
||||
# General settings
|
||||
|
||||
These are the general configuration settings that apply to the entire application.
|
||||
|
||||
|
||||
11
docs/docs/configuration/2_search.mdx
Normal file
11
docs/docs/configuration/2_search.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
import SearchSettings from '@site/src/components/configuration/SearchSettings';
|
||||
|
||||
# Search settings
|
||||
|
||||
These are the search configuration settings when searching for replacements.
|
||||
|
||||
<SearchSettings/>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"label": "Arrs settings",
|
||||
"position": 6,
|
||||
"position": 7,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Servarr settings."
|
||||
|
||||
@@ -3,6 +3,8 @@ sidebar_position: 1
|
||||
---
|
||||
|
||||
import ContentBlockerGeneralSettings from '@site/src/components/configuration/content-blocker/ContentBlockerGeneralSettings';
|
||||
import { Important } from '@site/src/components/Admonition';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# General Settings
|
||||
|
||||
@@ -16,4 +18,8 @@ These environment variables are needed to enable the Content Blocker functionali
|
||||
- [LIDARR__BLOCK__TYPE](/docs/configuration/arrs/lidarr?LIDARR__BLOCK__TYPE) (if Lidarr is enabled)
|
||||
- [LIDARR__BLOCK__PATH](/docs/configuration/arrs/lidarr?LIDARR__BLOCK__PATH) (if Lidarr is enabled)
|
||||
|
||||
<Important>
|
||||
These settings need a <Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">download client</Link> to be configured.
|
||||
</Important>
|
||||
|
||||
<ContentBlockerGeneralSettings/>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"label": "Content Blocker",
|
||||
"position": 2,
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Settings for the Content Blocker functionality."
|
||||
|
||||
@@ -3,9 +3,10 @@ sidebar_position: 2
|
||||
---
|
||||
|
||||
import DownloadCleanerCleanupSettings from '@site/src/components/configuration/download-cleaner/DownloadCleanerCleanupSettings';
|
||||
import { Note } from '@site/src/components/Admonition';
|
||||
import { Note, Important } from '@site/src/components/Admonition';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Cleanup Settings
|
||||
# Seeding settings
|
||||
|
||||
These settings control how the Download Cleaner handles different categories of downloads that need to be removed.
|
||||
|
||||
@@ -23,4 +24,8 @@ These settings control how the Download Cleaner handles different categories of
|
||||
```
|
||||
</Note>
|
||||
|
||||
<Important>
|
||||
These settings need a <Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">download client</Link> to be configured.
|
||||
</Important>
|
||||
|
||||
<DownloadCleanerCleanupSettings/>
|
||||
@@ -4,12 +4,17 @@ sidebar_position: 3
|
||||
|
||||
import DownloadCleanerHardlinksSettings from '@site/src/components/configuration/download-cleaner/DownloadCleanerHardlinksSettings';
|
||||
import { Important, Warning } from '@site/src/components/Admonition';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Hardlinks Settings
|
||||
|
||||
These settings control how the Download Cleaner handles downloads with no hardlinks remaining (they are not available in the arrs anymore).
|
||||
|
||||
The Download Cleaner will change the category of a download that has no hardlinks and the new category can be cleaned based on the rules configured [here](/docs/configuration/download-cleaner/categories).
|
||||
The Download Cleaner will change the category of a download that has no hardlinks and the new category can be cleaned based on the rules configured [here](/docs/configuration/download-cleaner/seeding).
|
||||
|
||||
<Important>
|
||||
These settings need a <Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">download client</Link> to be configured.
|
||||
</Important>
|
||||
|
||||
<Important>
|
||||
If you are using Docker, make sure to mount the downloads directory the same way it is mounted for the download client.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"label": "Download Cleaner",
|
||||
"position": 3,
|
||||
"position": 5,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Configure the Download Cleaner to automatically clean up downloads that have been seeding for a certain amount of time."
|
||||
|
||||
@@ -4,7 +4,7 @@ sidebar_position: 1
|
||||
|
||||
import DownloadClientSettings from '@site/src/components/configuration/download-client/DownloadClientSettings';
|
||||
|
||||
# Download Client Settings
|
||||
# General settings
|
||||
|
||||
These settings control how Cleanuperr interacts with your download client.
|
||||
|
||||
|
||||
7
docs/docs/configuration/download-client/2_qbit.mdx
Normal file
7
docs/docs/configuration/download-client/2_qbit.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
import QBittorrentSettings from '@site/src/components/configuration/download-client/QBittorrentSettings';
|
||||
|
||||
# qBittorrent settings
|
||||
|
||||
Settings used to access your qBittorrent instance.
|
||||
|
||||
<QBittorrentSettings/>
|
||||
7
docs/docs/configuration/download-client/3_deluge.mdx
Normal file
7
docs/docs/configuration/download-client/3_deluge.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
import DelugeSettings from '@site/src/components/configuration/download-client/DelugeSettings';
|
||||
|
||||
# Deluge settings
|
||||
|
||||
Settings used to access your Deluge instance.
|
||||
|
||||
<DelugeSettings/>
|
||||
@@ -0,0 +1,7 @@
|
||||
import TransmissionSettings from '@site/src/components/configuration/download-client/TransmissionSettings';
|
||||
|
||||
# Transmission settings
|
||||
|
||||
Settings used to access your Transmission instance.
|
||||
|
||||
<TransmissionSettings/>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"label": "Download Client",
|
||||
"position": 4,
|
||||
"position": 6,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Configure the download client settings for Cleanuperr."
|
||||
|
||||
@@ -1,157 +1,394 @@
|
||||
import { Note } from '@site/src/components/Admonition';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import CodeBlockContainer from '@theme/CodeBlock/Container';
|
||||
import Link from '@docusaurus/Link';
|
||||
import styles from './examples.module.css';
|
||||
|
||||
# Docker compose
|
||||
|
||||
<Note>
|
||||
**This example contains all settings and should be modified to fit your needs.**
|
||||
This example contains all settings and should be modified to fit your needs.
|
||||
Remove the variables that you do not need.
|
||||
</Note>
|
||||
|
||||
```
|
||||
services:
|
||||
cleanuperr:
|
||||
image: ghcr.io/flmorg/cleanuperr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./cleanuperr/logs:/var/logs
|
||||
- ./cleanuperr/ignored.txt:/ignored.txt
|
||||
environment:
|
||||
# general settings
|
||||
- TZ=America/New_York
|
||||
- DRY_RUN=false
|
||||
- HTTP_MAX_RETRIES=0
|
||||
- HTTP_TIMEOUT=100
|
||||
<Note>
|
||||
Click on an environment variable's name to go to its documentation.
|
||||
</Note>
|
||||
|
||||
# logging
|
||||
- LOGGING__LOGLEVEL=Information
|
||||
- LOGGING__FILE__ENABLED=false
|
||||
- LOGGING__FILE__PATH=/var/logs/
|
||||
- LOGGING__ENHANCED=true
|
||||
|
||||
# job triggers
|
||||
- TRIGGERS__QUEUECLEANER=0 0/5 * * * ?
|
||||
- TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ?
|
||||
- TRIGGERS__DOWNLOADCLEANER=0 0 * * * ?
|
||||
|
||||
# queue cleaner
|
||||
- QUEUECLEANER__ENABLED=true
|
||||
- QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
|
||||
- QUEUECLEANER__RUNSEQUENTIALLY=true
|
||||
|
||||
# failed imports
|
||||
- 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
|
||||
|
||||
# stalled downloads
|
||||
- QUEUECLEANER__STALLED_MAX_STRIKES=5
|
||||
- QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS=false
|
||||
- QUEUECLEANER__STALLED_IGNORE_PRIVATE=false
|
||||
- QUEUECLEANER__STALLED_DELETE_PRIVATE=false
|
||||
|
||||
# slow downloads
|
||||
- QUEUECLEANER__SLOW_MAX_STRIKES=5
|
||||
- QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS=true
|
||||
- QUEUECLEANER__SLOW_IGNORE_PRIVATE=false
|
||||
- QUEUECLEANER__SLOW_DELETE_PRIVATE=false
|
||||
- QUEUECLEANER__SLOW_MIN_SPEED=1MB
|
||||
- QUEUECLEANER__SLOW_MAX_TIME=20
|
||||
- QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE=60GB
|
||||
|
||||
# content blocker
|
||||
- CONTENTBLOCKER__ENABLED=true
|
||||
- CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH=/ignored.txt
|
||||
- CONTENTBLOCKER__IGNORE_PRIVATE=false
|
||||
- CONTENTBLOCKER__DELETE_PRIVATE=false
|
||||
|
||||
# download cleaner
|
||||
- DOWNLOADCLEANER__ENABLED=true
|
||||
- DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
|
||||
- DOWNLOADCLEANER__DELETE_PRIVATE=false
|
||||
|
||||
# categories to seed until max ratio or min seed time has been reached
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__NAME=tv-sonarr
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO=-1
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME=0
|
||||
- DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME=240
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__NAME=radarr
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__MAX_RATIO=-1
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__MIN_SEED_TIME=0
|
||||
- DOWNLOADCLEANER__CATEGORIES__1__MAX_SEED_TIME=240
|
||||
# remove downloads with no hardlinks
|
||||
- DOWNLOADCLEANER__CATEGORIES__2__NAME=cleanuperr-unlinked
|
||||
- DOWNLOADCLEANER__CATEGORIES__2__MAX_RATIO=-1
|
||||
- DOWNLOADCLEANER__CATEGORIES__2__MIN_SEED_TIME=0
|
||||
- DOWNLOADCLEANER__CATEGORIES__2__MAX_SEED_TIME=0
|
||||
|
||||
# change category for downloads with no hardlinks
|
||||
- DOWNLOADCLEANER__UNLINKED_TARGET_CATEGORY=cleanuperr-unlinked
|
||||
- DOWNLOADCLEANER__UNLINKED_IGNORED_ROOT_DIR=/downloads
|
||||
- DOWNLOADCLEANER__UNLINKED_CATEGORIES__0=tv-sonarr
|
||||
- DOWNLOADCLEANER__UNLINKED_CATEGORIES__1=radarr
|
||||
|
||||
- DOWNLOAD_CLIENT=none
|
||||
# OR
|
||||
# - DOWNLOAD_CLIENT=disabled
|
||||
# OR
|
||||
# - DOWNLOAD_CLIENT=qBittorrent
|
||||
# - QBITTORRENT__URL=http://localhost:8080
|
||||
# - QBITTORRENT__URL_BASE=myCustomPath
|
||||
# - QBITTORRENT__USERNAME=user
|
||||
# - QBITTORRENT__PASSWORD=pass
|
||||
# OR
|
||||
# - DOWNLOAD_CLIENT=deluge
|
||||
# - DELUGE__URL_BASE=myCustomPath
|
||||
# - DELUGE__URL=http://localhost:8112
|
||||
# - DELUGE__PASSWORD=testing
|
||||
# OR
|
||||
# - DOWNLOAD_CLIENT=transmission
|
||||
# - TRANSMISSION__URL=http://localhost:9091
|
||||
# - TRANSMISSION__URL_BASE=myCustomPath
|
||||
# - TRANSMISSION__USERNAME=test
|
||||
# - TRANSMISSION__PASSWORD=testing
|
||||
|
||||
- SONARR__ENABLED=true
|
||||
- SONARR__SEARCHTYPE=Episode
|
||||
- SONARR__BLOCK__TYPE=blacklist
|
||||
- SONARR__BLOCK__PATH=https://example.com/path/to/file.txt
|
||||
- SONARR__INSTANCES__0__URL=http://localhost:8989
|
||||
- SONARR__INSTANCES__0__APIKEY=secret1
|
||||
- SONARR__INSTANCES__1__URL=http://localhost:8990
|
||||
- SONARR__INSTANCES__1__APIKEY=secret2
|
||||
|
||||
- RADARR__ENABLED=true
|
||||
- RADARR__BLOCK__TYPE=blacklist
|
||||
- RADARR__BLOCK__PATH=https://example.com/path/to/file.txt
|
||||
- RADARR__INSTANCES__0__URL=http://localhost:7878
|
||||
- RADARR__INSTANCES__0__APIKEY=secret3
|
||||
- RADARR__INSTANCES__1__URL=http://localhost:7879
|
||||
- RADARR__INSTANCES__1__APIKEY=secret4
|
||||
|
||||
- LIDARR__ENABLED=true
|
||||
- LIDARR__BLOCK__TYPE=blacklist
|
||||
- LIDARR__BLOCK__PATH=https://example.com/path/to/file.txt
|
||||
- LIDARR__INSTANCES__0__URL=http://radarr:8686
|
||||
- LIDARR__INSTANCES__0__APIKEY=secret5
|
||||
- LIDARR__INSTANCES__1__URL=http://radarr:8687
|
||||
- LIDARR__INSTANCES__1__APIKEY=secret6
|
||||
|
||||
- NOTIFIARR__ON_IMPORT_FAILED_STRIKE=true
|
||||
- NOTIFIARR__ON_STALLED_STRIKE=true
|
||||
- NOTIFIARR__ON_SLOW_STRIKE=true
|
||||
- NOTIFIARR__ON_QUEUE_ITEM_DELETED=true
|
||||
- NOTIFIARR__ON_DOWNLOAD_CLEANED=true
|
||||
- NOTIFIARR__ON_CATEGORY_CHANGED=true
|
||||
- NOTIFIARR__API_KEY=notifiarr_secret
|
||||
- NOTIFIARR__CHANNEL_ID=discord_channel_id
|
||||
|
||||
- APPRISE__ON_IMPORT_FAILED_STRIKE=true
|
||||
- APPRISE__ON_STALLED_STRIKE=true
|
||||
- APPRISE__ON_SLOW_STRIKE=true
|
||||
- APPRISE__ON_QUEUE_ITEM_DELETED=true
|
||||
- APPRISE__ON_DOWNLOAD_CLEANED=true
|
||||
- NOTIFIARR__ON_CATEGORY_CHANGED=true
|
||||
- APPRISE__URL=http://apprise:8000
|
||||
- APPRISE__KEY=myConfigKey
|
||||
```
|
||||
<CodeBlock language="text" title="Environment Variables" wrap={true}>
|
||||
<div style={{ whiteSpace: 'pre' }}>
|
||||
{`services:
|
||||
cleanuperr:
|
||||
image: ghcr.io/flmorg/cleanuperr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# if you want persistent logs
|
||||
- ./cleanuperr/logs:/var/logs
|
||||
# if you want to ignore certain downloads from being processed
|
||||
- ./cleanuperr/ignored.txt:/ignored.txt
|
||||
# if you're using cross-seed and the hardlinks functionality
|
||||
- ./downloads:/downloads
|
||||
environment:
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?TZ">TZ</Link>
|
||||
{`=America/New_York
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?DRY_RUN">DRY_RUN</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?HTTP_MAX_RETRIES">HTTP_MAX_RETRIES</Link>
|
||||
{`=0
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?HTTP_TIMEOUT">HTTP_TIMEOUT</Link>
|
||||
{`=100
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?HTTP_VALIDATE_CERT">HTTP_VALIDATE_CERT</Link>
|
||||
{`=Enabled
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?LOGGING__LOGLEVEL">LOGGING__LOGLEVEL</Link>
|
||||
{`=Information
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?LOGGING__FILE__ENABLED">LOGGING__FILE__ENABLED</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?LOGGING__FILE__PATH">LOGGING__FILE__PATH</Link>
|
||||
{`=/var/logs/
|
||||
- `}
|
||||
<Link to="/docs/configuration/general?LOGGING__ENHANCED">LOGGING__ENHANCED</Link>
|
||||
{`=true
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/search?SEARCH_ENABLED">SEARCH_ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/search?SEARCH_DELAY">SEARCH_DELAY</Link>
|
||||
{`=30
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/general?TRIGGERS__QUEUECLEANER">TRIGGERS__QUEUECLEANER</Link>
|
||||
{`=0 0/5 * * * ?
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/general?QUEUECLEANER__ENABLED">QUEUECLEANER__ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/general?QUEUECLEANER__IGNORED_DOWNLOADS_PATH">QUEUECLEANER__IGNORED_DOWNLOADS_PATH</Link>
|
||||
{`=/ignored.txt
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/general?QUEUECLEANER__RUNSEQUENTIALLY">QUEUECLEANER__RUNSEQUENTIALLY</Link>
|
||||
{`=true
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES">QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES</Link>
|
||||
{`=5
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE">QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE">QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0">QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0</Link>
|
||||
{`=title mismatch
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1">QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1</Link>
|
||||
{`=manual import required
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/stalled?QUEUECLEANER__STALLED_MAX_STRIKES">QUEUECLEANER__STALLED_MAX_STRIKES</Link>
|
||||
{`=5
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/stalled?QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS">QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/stalled?QUEUECLEANER__STALLED_IGNORE_PRIVATE">QUEUECLEANER__STALLED_IGNORE_PRIVATE</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/stalled?QUEUECLEANER__STALLED_DELETE_PRIVATE">QUEUECLEANER__STALLED_DELETE_PRIVATE</Link>
|
||||
{`=false
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_MAX_STRIKES">QUEUECLEANER__SLOW_MAX_STRIKES</Link>
|
||||
{`=5
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS">QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_IGNORE_PRIVATE">QUEUECLEANER__SLOW_IGNORE_PRIVATE</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_DELETE_PRIVATE">QUEUECLEANER__SLOW_DELETE_PRIVATE</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_MIN_SPEED">QUEUECLEANER__SLOW_MIN_SPEED</Link>
|
||||
{`=1MB
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_MAX_TIME">QUEUECLEANER__SLOW_MAX_TIME</Link>
|
||||
{`=20
|
||||
- `}
|
||||
<Link to="/docs/configuration/queue-cleaner/slow?QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE">QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE</Link>
|
||||
{`=60GB
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/content-blocker/general?TRIGGERS__CONTENTBLOCKER">TRIGGERS__CONTENTBLOCKER</Link>
|
||||
{`=0 0/5 * * * ?
|
||||
- `}
|
||||
<Link to="/docs/configuration/content-blocker/general?CONTENTBLOCKER__ENABLED">CONTENTBLOCKER__ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/content-blocker/general?CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH">CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH</Link>
|
||||
{`=/ignored.txt
|
||||
- `}
|
||||
<Link to="/docs/configuration/content-blocker/general?CONTENTBLOCKER__IGNORE_PRIVATE">CONTENTBLOCKER__IGNORE_PRIVATE</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/content-blocker/general?CONTENTBLOCKER__DELETE_PRIVATE">CONTENTBLOCKER__DELETE_PRIVATE</Link>
|
||||
{`=false
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/general?TRIGGERS__DOWNLOADCLEANER">TRIGGERS__DOWNLOADCLEANER</Link>
|
||||
{`=0 0 * * * ?
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/general?DOWNLOADCLEANER__ENABLED">DOWNLOADCLEANER__ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/general?DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH">DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH</Link>
|
||||
{`=/ignored.txt
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/general?DOWNLOADCLEANER__DELETE_PRIVATE">DOWNLOADCLEANER__DELETE_PRIVATE</Link>
|
||||
{`=false
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__NAME">DOWNLOADCLEANER__CATEGORIES__0__NAME</Link>
|
||||
{`=tv-sonarr
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO">DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO</Link>
|
||||
{`=1
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME">DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME</Link>
|
||||
{`=0
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME">DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME</Link>
|
||||
{`=240
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__NAME">DOWNLOADCLEANER__CATEGORIES__1__NAME</Link>
|
||||
{`=radarr
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO">DOWNLOADCLEANER__CATEGORIES__1__MAX_RATIO</Link>
|
||||
{`=1
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME">DOWNLOADCLEANER__CATEGORIES__1__MIN_SEED_TIME</Link>
|
||||
{`=0
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME">DOWNLOADCLEANER__CATEGORIES__1__MAX_SEED_TIME</Link>
|
||||
{`=240
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__NAME">DOWNLOADCLEANER__CATEGORIES__2__NAME</Link>
|
||||
{`=cleanuperr-unlinked
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO">DOWNLOADCLEANER__CATEGORIES__2__MAX_RATIO</Link>
|
||||
{`=1
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME">DOWNLOADCLEANER__CATEGORIES__2__MIN_SEED_TIME</Link>
|
||||
{`=0
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/seeding?DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME">DOWNLOADCLEANER__CATEGORIES__2__MAX_SEED_TIME</Link>
|
||||
{`=240
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/hardlinks?DOWNLOADCLEANER__UNLINKED_TARGET_CATEGORY">DOWNLOADCLEANER__UNLINKED_TARGET_CATEGORY</Link>
|
||||
{`=cleanuperr-unlinked
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/hardlinks?DOWNLOADCLEANER__UNLINKED_USE_TAG">DOWNLOADCLEANER__UNLINKED_USE_TAG</Link>
|
||||
{`=false
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/hardlinks?DOWNLOADCLEANER__UNLINKED_IGNORED_ROOT_DIR">DOWNLOADCLEANER__UNLINKED_IGNORED_ROOT_DIR</Link>
|
||||
{`=/downloads
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/hardlinks?DOWNLOADCLEANER__UNLINKED_CATEGORIES__0">DOWNLOADCLEANER__UNLINKED_CATEGORIES__0</Link>
|
||||
{`=tv-sonarr
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-cleaner/hardlinks?DOWNLOADCLEANER__UNLINKED_CATEGORIES__0">DOWNLOADCLEANER__UNLINKED_CATEGORIES__1</Link>
|
||||
{`=radarr
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">DOWNLOAD_CLIENT</Link>
|
||||
{`=none
|
||||
# OR
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">DOWNLOAD_CLIENT</Link>
|
||||
{`=disabled
|
||||
# OR
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">DOWNLOAD_CLIENT</Link>
|
||||
{`=qBittorrent
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/qbit?QBITTORRENT__URL">QBITTORRENT__URL</Link>
|
||||
{`=http://localhost:8080
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/qbit?QBITTORRENT__URL_BASE">QBITTORRENT__URL_BASE</Link>
|
||||
{`=myCustomPath
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/qbit?QBITTORRENT__USERNAME">QBITTORRENT__USERNAME</Link>
|
||||
{`=user
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/qbit?QBITTORRENT__PASSWORD">QBITTORRENT__PASSWORD</Link>
|
||||
{`=pass
|
||||
# OR
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">DOWNLOAD_CLIENT</Link>
|
||||
{`=deluge
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/deluge?DELUGE__URL">DELUGE__URL</Link>
|
||||
{`=http://localhost:8112
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/deluge?DELUGE__URL_BASE">DELUGE__URL_BASE</Link>
|
||||
{`=myCustomPath
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/deluge?DELUGE__PASSWORD">DELUGE__PASSWORD</Link>
|
||||
{`=pass
|
||||
# OR
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">DOWNLOAD_CLIENT</Link>
|
||||
{`=transmission
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/transmission?TRANSMISSION__URL">TRANSMISSION__URL</Link>
|
||||
{`=http://localhost:9091
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/transmission?TRANSMISSION__URL_BASE">TRANSMISSION__URL_BASE</Link>
|
||||
{`=myCustomPath
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/transmission?TRANSMISSION__USERNAME">TRANSMISSION__USERNAME</Link>
|
||||
{`=user
|
||||
# - `}
|
||||
<Link to="/docs/configuration/download-client/transmission?TRANSMISSION__PASSWORD">TRANSMISSION__PASSWORD</Link>
|
||||
{`=pass
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__ENABLED">SONARR__ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__IMPORT_FAILED_MAX_STRIKES">SONARR__IMPORT_FAILED_MAX_STRIKES</Link>
|
||||
{`=-1
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__SEARCHTYPE">SONARR__SEARCHTYPE</Link>
|
||||
{`=Episode
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__BLOCK__TYPE">SONARR__BLOCK__TYPE</Link>
|
||||
{`=blacklist
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__BLOCK__PATH">SONARR__BLOCK__PATH</Link>
|
||||
{`=https://example.com/path/to/file.txt
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__INSTANCES__0__URL">SONARR__INSTANCES__0__URL</Link>
|
||||
{`=http://localhost:8989
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__INSTANCES__0__APIKEY">SONARR__INSTANCES__0__APIKEY</Link>
|
||||
{`=sonarrSecret1
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__INSTANCES__0__URL">SONARR__INSTANCES__1__URL</Link>
|
||||
{`=http://localhost:8990
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/sonarr?SONARR__INSTANCES__0__APIKEY">SONARR__INSTANCES__1__APIKEY</Link>
|
||||
{`=sonarrSecret2
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__ENABLED">RADARR__ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__IMPORT_FAILED_MAX_STRIKES">RADARR__IMPORT_FAILED_MAX_STRIKES</Link>
|
||||
{`=-1
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__BLOCK__TYPE">RADARR__BLOCK__TYPE</Link>
|
||||
{`=blacklist
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__BLOCK__PATH">RADARR__BLOCK__PATH</Link>
|
||||
{`=https://example.com/path/to/file.txt
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__INSTANCES__0__URL">RADARR__INSTANCES__0__URL</Link>
|
||||
{`=http://localhost:7878
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__INSTANCES__0__APIKEY">RADARR__INSTANCES__0__APIKEY</Link>
|
||||
{`=radarrSecret1
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__INSTANCES__0__URL">RADARR__INSTANCES__1__URL</Link>
|
||||
{`=http://localhost:7879
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/radarr?RADARR__INSTANCES__0__APIKEY">RADARR__INSTANCES__1__APIKEY</Link>
|
||||
{`=radarrSecret2
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__ENABLED">LIDARR__ENABLED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__IMPORT_FAILED_MAX_STRIKES">LIDARR__IMPORT_FAILED_MAX_STRIKES</Link>
|
||||
{`=-1
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__BLOCK__TYPE">LIDARR__BLOCK__TYPE</Link>
|
||||
{`=blacklist
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__BLOCK__PATH">LIDARR__BLOCK__PATH</Link>
|
||||
{`=https://example.com/path/to/file.txt
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__INSTANCES__0__URL">LIDARR__INSTANCES__0__URL</Link>
|
||||
{`=http://localhost:8686
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__INSTANCES__0__APIKEY">LIDARR__INSTANCES__0__APIKEY</Link>
|
||||
{`=lidarrSecret1
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__INSTANCES__0__URL">LIDARR__INSTANCES__1__URL</Link>
|
||||
{`=http://localhost:8687
|
||||
- `}
|
||||
<Link to="/docs/configuration/arrs/lidarr?LIDARR__INSTANCES__0__APIKEY">LIDARR__INSTANCES__1__APIKEY</Link>
|
||||
{`=lidarrSecret2
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__ON_IMPORT_FAILED_STRIKE">NOTIFIARR__ON_IMPORT_FAILED_STRIKE</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__ON_STALLED_STRIKE">NOTIFIARR__ON_STALLED_STRIKE</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__ON_SLOW_STRIKE">NOTIFIARR__ON_SLOW_STRIKE</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__ON_QUEUE_ITEM_DELETED">NOTIFIARR__ON_QUEUE_ITEM_DELETED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__ON_DOWNLOAD_CLEANED">NOTIFIARR__ON_DOWNLOAD_CLEANED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__ON_CATEGORY_CHANGED">NOTIFIARR__ON_CATEGORY_CHANGED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__API_KEY">NOTIFIARR__API_KEY</Link>
|
||||
{`=notifiarrSecret
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/notifiarr?NOTIFIARR__CHANNEL_ID">NOTIFIARR__CHANNEL_ID</Link>
|
||||
{`=discordChannelId
|
||||
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__ON_IMPORT_FAILED_STRIKE">APPRISE__ON_IMPORT_FAILED_STRIKE</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__ON_STALLED_STRIKE">APPRISE__ON_STALLED_STRIKE</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__ON_SLOW_STRIKE">APPRISE__ON_SLOW_STRIKE</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__ON_QUEUE_ITEM_DELETED">APPRISE__ON_QUEUE_ITEM_DELETED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__ON_DOWNLOAD_CLEANED">APPRISE__ON_DOWNLOAD_CLEANED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__ON_CATEGORY_CHANGED">APPRISE__ON_CATEGORY_CHANGED</Link>
|
||||
{`=true
|
||||
- `}
|
||||
<Link to="/docs/configuration/notifications/apprise?APPRISE__URL">APPRISE__URL</Link>
|
||||
{`=http://apprise:8000
|
||||
- `}<Link to="/docs/configuration/notifications/apprise?NOTIFIARR__CHANNEL_ID">APPRISE__KEY</Link>
|
||||
{`=myConfigKey`}
|
||||
</div>
|
||||
</CodeBlock>
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Note } from '@site/src/components/Admonition';
|
||||
|
||||
# Configuration file example (when not using Docker)
|
||||
# Configuration file (when not using Docker)
|
||||
|
||||
<Note>
|
||||
**This example contains all settings and should be modified to fit your needs.**
|
||||
This example contains all settings and should be modified to fit your needs.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
Click on an environment variable's name to go to its documentation.
|
||||
</Note>
|
||||
|
||||
```
|
||||
@@ -20,6 +24,8 @@ import { Note } from '@site/src/components/Admonition';
|
||||
"Path": "/var/logs"
|
||||
}
|
||||
},
|
||||
"SEARCH_ENABLED": true,
|
||||
"SEARCH_DELAY": 30,
|
||||
"Triggers": {
|
||||
"QueueCleaner": "0 0/5 * * * ?",
|
||||
"ContentBlocker": "0 0/5 * * * ?",
|
||||
@@ -78,6 +84,7 @@ import { Note } from '@site/src/components/Admonition';
|
||||
}
|
||||
],
|
||||
"UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked",
|
||||
"DOWNLOADCLEANER__UNLINKED_USE_TAG": false,
|
||||
"UNLINKED_IGNORED_ROOT_DIR": "/downloads",
|
||||
"UNLINKED_CATEGORIES": [
|
||||
"tv-sonarr",
|
||||
@@ -105,6 +112,7 @@ import { Note } from '@site/src/components/Admonition';
|
||||
},
|
||||
"Sonarr": {
|
||||
"Enabled": true,
|
||||
"IMPORT_FAILED_MAX_STRIKES=-1
|
||||
"SearchType": "Episode",
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
@@ -123,6 +131,7 @@ import { Note } from '@site/src/components/Admonition';
|
||||
},
|
||||
"Radarr": {
|
||||
"Enabled": true,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
"Path": "https://example.com/path/to/file.txt"
|
||||
@@ -140,6 +149,7 @@ import { Note } from '@site/src/components/Admonition';
|
||||
},
|
||||
"Lidarr": {
|
||||
"Enabled": true,
|
||||
"IMPORT_FAILED_MAX_STRIKES": -1,
|
||||
"Block": {
|
||||
"Type": "blacklist",
|
||||
"Path": "https://example.com/path/to/file.txt"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"label": "Configuration examples",
|
||||
"position": 8,
|
||||
"label": "Examples",
|
||||
"position": 1,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
|
||||
11
docs/docs/configuration/examples/examples.module.css
Normal file
11
docs/docs/configuration/examples/examples.module.css
Normal file
@@ -0,0 +1,11 @@
|
||||
[data-theme='light'] code a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
[data-theme='dark'] code a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"label": "Notifications",
|
||||
"position": 7,
|
||||
"position": 8,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Settings for receiving notifications."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,15 @@ sidebar_position: 3
|
||||
---
|
||||
|
||||
import QueueCleanerStalledSettings from '@site/src/components/configuration/queue-cleaner/QueueCleanerStalledSettings';
|
||||
import { Important } from '@site/src/components/Admonition';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Stalled Downloads Settings
|
||||
|
||||
These settings control how the Queue Cleaner handles stalled downloads.
|
||||
|
||||
<Important>
|
||||
These settings need a <Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">download client</Link> to be configured.
|
||||
</Important>
|
||||
|
||||
<QueueCleanerStalledSettings/>
|
||||
@@ -3,9 +3,15 @@ sidebar_position: 4
|
||||
---
|
||||
|
||||
import QueueCleanerSlowSettings from '@site/src/components/configuration/queue-cleaner/QueueCleanerSlowSettings';
|
||||
import { Important } from '@site/src/components/Admonition';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Slow Downloads Settings
|
||||
|
||||
These settings control how the Queue Cleaner handles slow downloads.
|
||||
|
||||
<Important>
|
||||
These settings need a <Link to="/docs/configuration/download-client/general?DOWNLOAD_CLIENT">download client</Link> to be configured.
|
||||
</Important>
|
||||
|
||||
<QueueCleanerSlowSettings/>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"label": "Queue Cleaner",
|
||||
"position": 1,
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Settings for the Queue Cleaner functionality."
|
||||
|
||||
@@ -61,7 +61,7 @@ const config: Config = {
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://discord.gg/sWggpnmGNY',
|
||||
href: 'https://discord.gg/SCtMCgtsc4',
|
||||
label: 'Discord',
|
||||
position: 'right',
|
||||
}
|
||||
@@ -76,6 +76,28 @@ const config: Config = {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
},
|
||||
algolia: {
|
||||
// The application ID provided by Algolia
|
||||
appId: 'Y4APRVTFUQ',
|
||||
|
||||
apiKey: 'bdaa942f24c8f4ed9893a5b5970405fa',
|
||||
|
||||
indexName: 'flmorgio',
|
||||
|
||||
// Optional: see doc section below
|
||||
contextualSearch: true,
|
||||
|
||||
// Optional: Algolia search parameters
|
||||
searchParameters: {},
|
||||
|
||||
// Optional: path for search page that enabled by default (`false` to disable it)
|
||||
searchPagePath: 'search',
|
||||
|
||||
// Optional: whether the insights feature is enabled or not on Docsearch (`false` by default)
|
||||
insights: true,
|
||||
|
||||
//... other Algolia params
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
};
|
||||
|
||||
|
||||
35
docs/src/components/configuration/SearchSettings.tsx
Normal file
35
docs/src/components/configuration/SearchSettings.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import EnvVars, { EnvVarProps } from "./EnvVars";
|
||||
|
||||
const settings: EnvVarProps[] = [
|
||||
{
|
||||
name: "SEARCH_ENABLED",
|
||||
description: [
|
||||
"Enabled searching for replacements after a download has been removed from an arr."
|
||||
],
|
||||
type: "boolean",
|
||||
defaultValue: "true",
|
||||
required: false,
|
||||
acceptedValues: ["true", "false"],
|
||||
notes: [
|
||||
"If you are using [Huntarr](https://github.com/plexguide/Huntarr.io), this setting should be set to false to let Huntarr do the searching.",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "SEARCH_DELAY",
|
||||
description: [
|
||||
"If searching for replacements is enabled, this setting will delay the searches by the specified number of seconds.",
|
||||
"This is useful to avoid overwhelming the indexer with too many requests at once.",
|
||||
],
|
||||
type: "positive integer number",
|
||||
defaultValue: "30",
|
||||
required: false,
|
||||
important: [
|
||||
"A lower value or `0` will result in faster searches, but may cause issues such as being rate limited or banned by the indexer.",
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
export default function SearchSettings() {
|
||||
return <EnvVars vars={settings} />;
|
||||
}
|
||||
@@ -12,6 +12,24 @@ const settings: EnvVarProps[] = [
|
||||
required: false,
|
||||
acceptedValues: ["true", "false"],
|
||||
},
|
||||
{
|
||||
name: "LIDARR__IMPORT_FAILED_MAX_STRIKES",
|
||||
description: [
|
||||
"Number of strikes before removing a failed import. Set to `0` to never remove failed imports.",
|
||||
"A strike is given when an item fails to be imported."
|
||||
],
|
||||
type: "integer number",
|
||||
defaultValue: "-1",
|
||||
required: false,
|
||||
notes: [
|
||||
"If the value is a positive number, it overwrites the values of [QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES](/cleanuperr/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES).",
|
||||
"`0` means to never remove failed imports.",
|
||||
"If not set to `0` or a negative number, the minimum value is `3`.",
|
||||
],
|
||||
warnings: [
|
||||
"The value is not restricted to be a certain positive number. Use a low value (e.g. `1`) at your own risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "LIDARR__BLOCK__TYPE",
|
||||
description: [
|
||||
|
||||
@@ -12,6 +12,24 @@ const settings: EnvVarProps[] = [
|
||||
required: false,
|
||||
acceptedValues: ["true", "false"],
|
||||
},
|
||||
{
|
||||
name: "RADARR__IMPORT_FAILED_MAX_STRIKES",
|
||||
description: [
|
||||
"Number of strikes before removing a failed import. Set to `0` to never remove failed imports.",
|
||||
"A strike is given when an item fails to be imported."
|
||||
],
|
||||
type: "integer number",
|
||||
defaultValue: "-1",
|
||||
required: false,
|
||||
notes: [
|
||||
"If the value is a positive number, it overwrites the values of [QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES](/cleanuperr/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES).",
|
||||
"`0` means to never remove failed imports.",
|
||||
"If not set to `0` or a negative number, the minimum value is `3`.",
|
||||
],
|
||||
warnings: [
|
||||
"The value is not restricted to be a certain positive number. Use a low value (e.g. `1`) at your own risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "RADARR__BLOCK__TYPE",
|
||||
description: [
|
||||
|
||||
@@ -12,6 +12,24 @@ const settings: EnvVarProps[] = [
|
||||
required: false,
|
||||
acceptedValues: ["true", "false"],
|
||||
},
|
||||
{
|
||||
name: "SONARR__IMPORT_FAILED_MAX_STRIKES",
|
||||
description: [
|
||||
"Number of strikes before removing a failed import. Set to `0` to never remove failed imports.",
|
||||
"A strike is given when an item fails to be imported."
|
||||
],
|
||||
type: "integer number",
|
||||
defaultValue: "-1",
|
||||
required: false,
|
||||
notes: [
|
||||
"If the value is a positive number, it overwrites the values of [QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES](/cleanuperr/docs/configuration/queue-cleaner/import-failed?QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES).",
|
||||
"`0` means to never remove failed imports.",
|
||||
"If not set to `0` or a negative number, the minimum value is `3`.",
|
||||
],
|
||||
warnings: [
|
||||
"The value is not restricted to be a certain positive number. Use a low value (e.g. `1`) at your own risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "SONARR__BLOCK__TYPE",
|
||||
description: [
|
||||
|
||||
@@ -11,6 +11,20 @@ const settings: EnvVarProps[] = [
|
||||
defaultValue: "cleanuperr-unlinked",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "DOWNLOADCLEANER__UNLINKED_USE_TAG",
|
||||
description: [
|
||||
"If set to true, a tag will be set instead of changing the category.",
|
||||
],
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
required: false,
|
||||
acceptedValues: ["true", "false"],
|
||||
notes: [
|
||||
"Works only for qBittorrent.",
|
||||
],
|
||||
|
||||
},
|
||||
{
|
||||
name: "DOWNLOADCLEANER__UNLINKED_IGNORED_ROOT_DIR",
|
||||
description: [
|
||||
@@ -30,11 +44,17 @@ const settings: EnvVarProps[] = [
|
||||
title: "Multiple patterns can be specified using incrementing numbers starting from 0.",
|
||||
content: `DOWNLOADCLEANER__UNLINKED_CATEGORIES__0=tv-sonarr
|
||||
DOWNLOADCLEANER__UNLINKED_CATEGORIES__1=radarr`
|
||||
}
|
||||
},
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
notes: [
|
||||
"The category name must match the category that was set in the *arr.",
|
||||
"For qBittorrent, the category name is the name of the download category.",
|
||||
"For Deluge, the category name is the name of the label.",
|
||||
"For Transmission, the category name is the last directory from the save location.",
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import EnvVars, { EnvVarProps } from "../EnvVars";
|
||||
|
||||
const settings: EnvVarProps[] = [
|
||||
{
|
||||
name: "DELUGE__URL",
|
||||
description: [
|
||||
"URL of the Deluge instance."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "http://localhost:8112",
|
||||
required: false,
|
||||
examples: ["http://localhost:8112", "http://192.168.1.100:8112", "https://mydomain.com:8112"],
|
||||
},
|
||||
{
|
||||
name: "DELUGE__URL_BASE",
|
||||
description: [
|
||||
"Adds a prefix to the deluge json url, such as `[DELUGE__URL]/[DELUGE__URL_BASE]/json`."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "DELUGE__PASSWORD",
|
||||
description: [
|
||||
"Password for Deluge authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
export default function DelugeSettings() {
|
||||
return <EnvVars vars={settings} />;
|
||||
}
|
||||
@@ -19,99 +19,6 @@ const settings: EnvVarProps[] = [
|
||||
"Setting `DOWNLOAD_CLIENT=disabled` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "QBITTORRENT__URL",
|
||||
description: [
|
||||
"URL of the qBittorrent instance."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "http://localhost:8080",
|
||||
required: false,
|
||||
examples: ["http://localhost:8080", "http://192.168.1.100:8080", "https://mydomain.com:8080"],
|
||||
},
|
||||
{
|
||||
name: "QBITTORRENT__URL_BASE",
|
||||
description: [
|
||||
"Adds a prefix to the qBittorrent url, such as `[QBITTORRENT__URL]/[QBITTORRENT__URL_BASE]/api`."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "QBITTORRENT__PASSWORD",
|
||||
description: [
|
||||
"Password for qBittorrent authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "DELUGE__URL",
|
||||
description: [
|
||||
"URL of the Deluge instance."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "http://localhost:8112",
|
||||
required: false,
|
||||
examples: ["http://localhost:8112", "http://192.168.1.100:8112", "https://mydomain.com:8112"],
|
||||
},
|
||||
{
|
||||
name: "DELUGE__URL_BASE",
|
||||
description: [
|
||||
"Adds a prefix to the deluge json url, such as `[DELUGE__URL]/[DELUGE__URL_BASE]/json`."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "DELUGE__PASSWORD",
|
||||
description: [
|
||||
"Password for Deluge authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__URL",
|
||||
description: [
|
||||
"URL of the Transmission instance."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "http://localhost:9091",
|
||||
required: false,
|
||||
examples: ["http://localhost:9091", "http://192.168.1.100:9091", "https://mydomain.com:9091"],
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__URL_BASE",
|
||||
description: [
|
||||
"Adds a prefix to the Transmission rpc url, such as `[TRANSMISSION__URL]/[TRANSMISSION__URL_BASE]/rpc`."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "transmission",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__USERNAME",
|
||||
description: [
|
||||
"Username for Transmission authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__PASSWORD",
|
||||
description: [
|
||||
"Password for Transmission authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
}
|
||||
];
|
||||
|
||||
export default function DownloadClientSettings() {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import EnvVars, { EnvVarProps } from "../EnvVars";
|
||||
|
||||
const settings: EnvVarProps[] = [
|
||||
{
|
||||
name: "QBITTORRENT__URL",
|
||||
description: [
|
||||
"URL of the qBittorrent instance."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "http://localhost:8080",
|
||||
required: false,
|
||||
examples: ["http://localhost:8080", "http://192.168.1.100:8080", "https://mydomain.com:8080"],
|
||||
},
|
||||
{
|
||||
name: "QBITTORRENT__URL_BASE",
|
||||
description: [
|
||||
"Adds a prefix to the qBittorrent url, such as `[QBITTORRENT__URL]/[QBITTORRENT__URL_BASE]/api`."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "QBITTORRENT__USERNAME",
|
||||
description: [
|
||||
"Username for qBittorrent authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "QBITTORRENT__PASSWORD",
|
||||
description: [
|
||||
"Password for qBittorrent authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
export default function QBittorrentSettings() {
|
||||
return <EnvVars vars={settings} />;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import EnvVars, { EnvVarProps } from "../EnvVars";
|
||||
|
||||
const settings: EnvVarProps[] = [
|
||||
{
|
||||
name: "TRANSMISSION__URL",
|
||||
description: [
|
||||
"URL of the Transmission instance."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "http://localhost:9091",
|
||||
required: false,
|
||||
examples: ["http://localhost:9091", "http://192.168.1.100:9091", "https://mydomain.com:9091"],
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__URL_BASE",
|
||||
description: [
|
||||
"Adds a prefix to the Transmission rpc url, such as `[TRANSMISSION__URL]/[TRANSMISSION__URL_BASE]/rpc`."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "transmission",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__USERNAME",
|
||||
description: [
|
||||
"Username for Transmission authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "TRANSMISSION__PASSWORD",
|
||||
description: [
|
||||
"Password for Transmission authentication."
|
||||
],
|
||||
type: "text",
|
||||
defaultValue: "Empty",
|
||||
required: false,
|
||||
}
|
||||
];
|
||||
|
||||
export default function TransmissionSettings() {
|
||||
return <EnvVars vars={settings} />;
|
||||
}
|
||||
Reference in New Issue
Block a user