Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f35abdefe5 | |||
| 43a11f0e4c | |||
| a5a54e324d | |||
| 53adb6c1c1 | |||
| a0c8ff72fb | |||
| 599242aa2a | |||
| 3e0913b437 | |||
| 54cabd98b4 | |||
| cbc5c571b3 |
+4
-1
@@ -168,4 +168,7 @@ src/.idea/
|
|||||||
|
|
||||||
**/logs/
|
**/logs/
|
||||||
**/MediaCover/
|
**/MediaCover/
|
||||||
**/archive/
|
**/archive/
|
||||||
|
**/Backups/
|
||||||
|
*.fastresume
|
||||||
|
*.bak
|
||||||
@@ -10,75 +10,74 @@ Refer to the [Environment variables](#Environment-variables) section for detaile
|
|||||||
|
|
||||||
## Important note
|
## Important note
|
||||||
|
|
||||||
Only the <b>latest versions</b> of qBittorrent, Deluge, Sonarr etc. are supported, or earlier versions that have the same API as the latest version.
|
Only the **latest versions** of qBittorrent, Deluge, Sonarr etc. are supported, or earlier versions that have the same API as the latest version.
|
||||||
|
|
||||||
This tool is actively developed and still a work in progress. 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/cJYPs9Bt
|
This tool is actively developed and still a work in progress. 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/cJYPs9Bt
|
||||||
|
|
||||||
|
# How it works
|
||||||
|
|
||||||
|
1. **Content blocker** will:
|
||||||
|
- Run every 5 minutes (or configured cron).
|
||||||
|
- Process all items in the *arr queue.
|
||||||
|
- Find the corresponding item from the download client for each queue item.
|
||||||
|
- Mark the files that were found in the queue as **unwanted/skipped** if:
|
||||||
|
- They **are listed in the blacklist**, or
|
||||||
|
- They **are not included in the whitelist**.
|
||||||
|
2. **Queue cleaner** will:
|
||||||
|
- Run every 5 minutes (or configured cron).
|
||||||
|
- Process all items in the *arr queue.
|
||||||
|
- Check each queue item if it meets one of the following condition in the download client:
|
||||||
|
- **Marked as completed, but 0 bytes have been downloaded** (due to files being blocked by qBittorrent or the **content blocker**).
|
||||||
|
- All associated files of are marked as **unwanted/skipped**.
|
||||||
|
- If the item **DOES NOT** match the above criteria, it will be skipped.
|
||||||
|
- If the item **DOES** match the criteria:
|
||||||
|
- It will be removed from the *arr's queue.
|
||||||
|
- It will be deleted from the download client.
|
||||||
|
- A new search will be triggered for the *arr item.
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
## Using qBittorrent's built-in feature (works only with qBittorrent)
|
## Using qBittorrent's built-in feature (works only with qBittorrent)
|
||||||
|
|
||||||
1. Go to qBittorrent -> Options -> Downloads -> make sure `Excluded file names` is checked -> Set an exclusion list.
|
1. Go to qBittorrent -> Options -> Downloads -> make sure `Excluded file names` is checked -> Paste an exclusion list that you have copied.
|
||||||
- [blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist)
|
- [blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), or
|
||||||
- [permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive)
|
- [permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive), or
|
||||||
- create your own
|
- create your own
|
||||||
2. Start cleanuperr with `QUEUECLEANER__ENABLED` set to `true`.
|
2. qBittorrent will block files from being downloaded. In the case of malicious content, **nothing is downloaded and the torrent is marked as complete**.
|
||||||
3. cleanuperr will execute a queue cleaner cron job at every 5 minutes that will:
|
3. Start **cleanuperr** with `QUEUECLEANER__ENABLED` set to `true`.
|
||||||
1. go through all items from Sonarr/Radarr's queue.
|
4. The **queue cleaner** will perform a cleanup process as described in the [How it works](#how-it-works) section.
|
||||||
2. each a queue item is checked:
|
|
||||||
- if it has been <b>marked as completed and 0 bytes have been downloaded</b> (because qBittorrent blocked the files).
|
|
||||||
- if all its files are skipped.
|
|
||||||
3. if the item <b>IS NOT</b> as described, it is skipped.
|
|
||||||
4. if the item <b>IS</b> as described, it is removed from Sonarr/Radarr's queue, removed from qBittorrent and a search is triggered for the show/movie.
|
|
||||||
|
|
||||||
## Using cleanuperr's blocklist (works with all supported download clients)
|
## Using cleanuperr's blocklist (works with all supported download clients)
|
||||||
|
|
||||||
1. Start cleanuperr with both `QUEUECLEANER_ENABLED` and `CONTENTBLOCKER_ENABLED` set to `true`.
|
1. Set both `QUEUECLEANER_ENABLED` and `CONTENTBLOCKER_ENABLED` to `true` in your environment variables.
|
||||||
2. Be sure to set and enable a blacklist or a whitelist as described in the [Environment variables](#Environment-variables) section.
|
2. Configure and enable either a **blacklist** or a **whitelist** as described in the [Environment variables](#Environment-variables) section.
|
||||||
3. cleanuperr with execute the following jobs:
|
3. Once configured, cleanuperr will perform the following tasks:
|
||||||
- the same queue cleaner as described [here](#Using-qBittorrents-built-in-feature)
|
- Execute the **content blocker** job, as explained in the [How it works](#how-it-works) section.
|
||||||
- a content blocker cron job at every 5 minutes that will mark files as unwanted/skipped if:
|
- Execute the **queue cleaner** job, as explained in the [How it works](#how-it-works) section.
|
||||||
- they are in the blacklist.
|
|
||||||
- they are not in the whitelist.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run -d \
|
|
||||||
-e TRIGGERS__QUEUECLEANER="0 0/5 * * * ?" \
|
|
||||||
-e QBITTORRENT__ENABLED=true \
|
|
||||||
-e QBITTORRENT__URL="http://localhost:8080" \
|
|
||||||
-e QBITTORRENT__USERNAME="user" \
|
|
||||||
-e QBITTORRENT__PASSWORD="pass" \
|
|
||||||
-e SONARR__ENABLED=true \
|
|
||||||
-e SONARR__INSTANCES__0__URL="http://localhost:8989" \
|
|
||||||
-e SONARR__INSTANCES__0__APIKEY="secret1" \
|
|
||||||
-e SONARR__INSTANCES__1__URL="http://localhost:8990" \
|
|
||||||
-e SONARR__INSTANCES__1__APIKEY="secret2" \
|
|
||||||
-e RADARR__ENABLED=true \
|
|
||||||
-e RADARR__INSTANCES__0__URL="http://localhost:7878" \
|
|
||||||
-e RADARR__INSTANCES__0__APIKEY="secret3" \
|
|
||||||
-e RADARR__INSTANCES__1__URL="http://localhost:7879" \
|
|
||||||
-e RADARR__INSTANCES__1__APIKEY="secret4" \
|
|
||||||
...
|
|
||||||
flaminel/cleanuperr:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker compose yaml
|
### Docker compose yaml
|
||||||
|
|
||||||
```
|
```
|
||||||
version: "3.3"
|
version: "3.3"
|
||||||
services:
|
services:
|
||||||
cleanuperr:
|
cleanuperr:
|
||||||
|
volumes:
|
||||||
|
- ./cleanuperr/logs:/var/logs
|
||||||
environment:
|
environment:
|
||||||
- LOGGING__LOGLEVEL__DEFAULT=Information
|
- LOGGING__LOGLEVEL=Information
|
||||||
|
- LOGGING__FILE__ENABLED=false
|
||||||
|
- LOGGING__FILE__PATH=/var/logs/
|
||||||
|
- LOGGING__ENHANCED=true
|
||||||
|
|
||||||
- TRIGGERS__QUEUECLEANER=0 0/5 * * * ?
|
- TRIGGERS__QUEUECLEANER=0 0/5 * * * ?
|
||||||
- TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ?
|
- TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ?
|
||||||
|
|
||||||
- QUEUECLEANER__ENABLED=true
|
- QUEUECLEANER__ENABLED=true
|
||||||
|
- QUEUECLEANER__RUNSEQUENTIALLY=true
|
||||||
|
|
||||||
- CONTENTBLOCKER__ENABLED=true
|
- CONTENTBLOCKER__ENABLED=true
|
||||||
- CONTENTBLOCKER__BLACKLIST__ENABLED=true
|
- CONTENTBLOCKER__BLACKLIST__ENABLED=true
|
||||||
@@ -102,6 +101,7 @@ services:
|
|||||||
# - TRANSMISSION__PASSWORD=testing
|
# - TRANSMISSION__PASSWORD=testing
|
||||||
|
|
||||||
- SONARR__ENABLED=true
|
- SONARR__ENABLED=true
|
||||||
|
- SONARR__SEARCHTYPE=Episode
|
||||||
- SONARR__INSTANCES__0__URL=http://localhost:8989
|
- SONARR__INSTANCES__0__URL=http://localhost:8989
|
||||||
- SONARR__INSTANCES__0__APIKEY=secret1
|
- SONARR__INSTANCES__0__APIKEY=secret1
|
||||||
- SONARR__INSTANCES__1__URL=http://localhost:8990
|
- SONARR__INSTANCES__1__URL=http://localhost:8990
|
||||||
@@ -112,7 +112,7 @@ services:
|
|||||||
- RADARR__INSTANCES__0__APIKEY=secret3
|
- RADARR__INSTANCES__0__APIKEY=secret3
|
||||||
- RADARR__INSTANCES__1__URL=http://localhost:7879
|
- RADARR__INSTANCES__1__URL=http://localhost:7879
|
||||||
- RADARR__INSTANCES__1__APIKEY=secret4
|
- RADARR__INSTANCES__1__APIKEY=secret4
|
||||||
image: flaminel/cleanuperr:latest
|
image: ghcr.io/flmorg/cleanuperr:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -120,12 +120,16 @@ services:
|
|||||||
|
|
||||||
| Variable | Required | Description | Default value |
|
| Variable | Required | Description | Default value |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| LOGGING__LOGLEVEL__DEFAULT | No | Can be `Debug`, `Information`, `Warning` or `Error` | Information |
|
| LOGGING__LOGLEVEL | No | Can be `Verbose`, `Debug`, `Information`, `Warning`, `Error` or `Fatal` | `Information` |
|
||||||
|
| LOGGING__FILE__ENABLED | No | Enable or disable logging to file | false |
|
||||||
|
| LOGGING__FILE__PATH | No | Directory where to save the log files | empty |
|
||||||
|
| LOGGING__ENHANCED | No | Enhance logs whenever possible<br>A more detailed description is provided [here](variables.md#LOGGING__ENHANCED) | true |
|
||||||
|||||
|
|||||
|
||||||
| TRIGGERS__QUEUECLEANER | Yes if queue cleaner is enabled | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? |
|
| TRIGGERS__QUEUECLEANER | Yes if queue cleaner is enabled | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? |
|
||||||
| TRIGGERS__CONTENTBLOCKER | Yes if content blocker is enabled | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? |
|
| TRIGGERS__CONTENTBLOCKER | Yes if content blocker is enabled | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? |
|
||||||
|||||
|
|||||
|
||||||
| QUEUECLEANER__ENABLED | No | Enable or disable the queue cleaner | true |
|
| QUEUECLEANER__ENABLED | No | Enable or disable the queue cleaner | true |
|
||||||
|
| QUEUECLEANER__RUNSEQUENTIALLY | No | If set to true, the queue cleaner will run after the content blocker instead of running in parallel, streamlining the cleaning process | true |
|
||||||
|||||
|
|||||
|
||||||
| CONTENTBLOCKER__ENABLED | No | Enable or disable the content blocker | false |
|
| CONTENTBLOCKER__ENABLED | No | Enable or disable the content blocker | false |
|
||||||
| CONTENTBLOCKER__BLACKLIST__ENABLED | Yes if content blocker is enabled and whitelist is not enabled | Enable or disable the blacklist | false |
|
| CONTENTBLOCKER__BLACKLIST__ENABLED | Yes if content blocker is enabled and whitelist is not enabled | Enable or disable the blacklist | false |
|
||||||
@@ -134,24 +138,25 @@ services:
|
|||||||
| CONTENTBLOCKER__BLACKLIST__PATH | Yes if whitelist is enabled | Path to the whitelist (local file or url); Needs to be json compatible | empty |
|
| CONTENTBLOCKER__BLACKLIST__PATH | Yes if whitelist is enabled | Path to the whitelist (local file or url); Needs to be json compatible | empty |
|
||||||
|||||
|
|||||
|
||||||
| QBITTORRENT__ENABLED | No | Enable or disable qBittorrent | true |
|
| QBITTORRENT__ENABLED | No | Enable or disable qBittorrent | true |
|
||||||
| QBITTORRENT__URL | Yes if qBittorrent is enabled | qBittorrent instance url | http://localhost:8112 |
|
| QBITTORRENT__URL | No | qBittorrent instance url | http://localhost:8112 |
|
||||||
| QBITTORRENT__USERNAME | Yes if qBittorrent is enabled | qBittorrent user | empty |
|
| QBITTORRENT__USERNAME | No | qBittorrent user | empty |
|
||||||
| QBITTORRENT__PASSWORD | Yes if qBittorrent is enabled | qBittorrent password | empty |
|
| QBITTORRENT__PASSWORD | No | qBittorrent password | empty |
|
||||||
|||||
|
|||||
|
||||||
| DELUGE__ENABLED | No | Enable or disable Deluge | false |
|
| DELUGE__ENABLED | No | Enable or disable Deluge | false |
|
||||||
| DELUGE__URL | Yes if Deluge is enabled | Deluge instance url | http://localhost:8080 |
|
| DELUGE__URL | No | Deluge instance url | http://localhost:8080 |
|
||||||
| DELUGE__PASSWORD | Yes if Deluge is enabled | Deluge password | empty |
|
| DELUGE__PASSWORD | No | Deluge password | empty |
|
||||||
|||||
|
|||||
|
||||||
| TRANSMISSION__ENABLED | No | Enable or disable Transmission | true |
|
| TRANSMISSION__ENABLED | No | Enable or disable Transmission | true |
|
||||||
| TRANSMISSION__URL | Yes if Transmission is enabled | Transmission instance url | http://localhost:9091 |
|
| TRANSMISSION__URL | No | Transmission instance url | http://localhost:9091 |
|
||||||
| TRANSMISSION__USERNAME | No | Transmission user | empty |
|
| TRANSMISSION__USERNAME | No | Transmission user | empty |
|
||||||
| TRANSMISSION__PASSWORD | No | Transmission password | empty |
|
| TRANSMISSION__PASSWORD | No | Transmission password | empty |
|
||||||
|||||
|
|||||
|
||||||
| SONARR__ENABLED | No | Whether Sonarr cleanup is enabled or not | true |
|
| SONARR__ENABLED | No | Enable or disable Sonarr cleanup | true |
|
||||||
|
| SONARR__SEARCHTYPE | No | What to search for after removing a queue item<br>Can be `Episode`, `Season` or `Series` | `Episode` |
|
||||||
| SONARR__INSTANCES__0__URL | Yes | First Sonarr instance url | http://localhost:8989 |
|
| SONARR__INSTANCES__0__URL | Yes | First Sonarr instance url | http://localhost:8989 |
|
||||||
| SONARR__INSTANCES__0__APIKEY | Yes | First Sonarr instance API key | empty |
|
| SONARR__INSTANCES__0__APIKEY | Yes | First Sonarr instance API key | empty |
|
||||||
|||||
|
|||||
|
||||||
| RADARR__ENABLED | No | Whether Radarr cleanup is enabled or not | false |
|
| RADARR__ENABLED | No | Enable or disable Radarr cleanup | false |
|
||||||
| RADARR__INSTANCES__0__URL | Yes | First Radarr instance url | http://localhost:8989 |
|
| RADARR__INSTANCES__0__URL | Yes | First Radarr instance url | http://localhost:8989 |
|
||||||
| RADARR__INSTANCES__0__APIKEY | Yes | First Radarr instance API key | empty |
|
| RADARR__INSTANCES__0__APIKEY | Yes | First Radarr instance API key | empty |
|
||||||
|
|
||||||
@@ -167,7 +172,7 @@ services:
|
|||||||
example* // file name starts with "example"
|
example* // file name starts with "example"
|
||||||
*example* // file name has "example" in the name
|
*example* // file name has "example" in the name
|
||||||
example // file name is exactly the word "example"
|
example // file name is exactly the word "example"
|
||||||
<ANY_REGEX> // regex
|
regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with "regex:"
|
||||||
```
|
```
|
||||||
5. Multiple Sonarr/Radarr instances can be specified using this format, where `<NUMBER>` starts from 0:
|
5. Multiple Sonarr/Radarr instances can be specified using this format, where `<NUMBER>` starts from 0:
|
||||||
```
|
```
|
||||||
@@ -181,7 +186,7 @@ SONARR__INSTANCES__<NUMBER>__APIKEY
|
|||||||
|
|
||||||
1. Download the binaries from [releases](https://github.com/flmorg/cleanuperr/releases).
|
1. Download the binaries from [releases](https://github.com/flmorg/cleanuperr/releases).
|
||||||
2. Extract them from the zip file.
|
2. Extract them from the zip file.
|
||||||
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [above](/README.md#environment-variables).
|
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [above](#environment-variables).
|
||||||
|
|
||||||
### Run as a Windows Service
|
### Run as a Windows Service
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,8 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration.Arr;
|
||||||
|
|
||||||
public abstract record ArrConfig
|
public abstract record ArrConfig
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration.Arr;
|
||||||
|
|
||||||
public sealed class ArrInstance
|
public sealed class ArrInstance
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration.Arr;
|
||||||
|
|
||||||
public sealed record RadarrConfig : ArrConfig
|
public sealed record RadarrConfig : ArrConfig
|
||||||
{
|
{
|
||||||
+3
-1
@@ -1,6 +1,8 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration.Arr;
|
||||||
|
|
||||||
public sealed record SonarrConfig : ArrConfig
|
public sealed record SonarrConfig : ArrConfig
|
||||||
{
|
{
|
||||||
public const string SectionName = "Sonarr";
|
public const string SectionName = "Sonarr";
|
||||||
|
|
||||||
|
public SonarrSearchType SearchType { get; init; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Common.Configuration.Arr;
|
||||||
|
|
||||||
|
public enum SonarrSearchType
|
||||||
|
{
|
||||||
|
Episode,
|
||||||
|
Season,
|
||||||
|
Series
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Common.Configuration.ContentBlocker;
|
namespace Common.Configuration.ContentBlocker;
|
||||||
|
|
||||||
public sealed record ContentBlockerConfig : IConfig
|
public sealed record ContentBlockerConfig : IJobConfig
|
||||||
{
|
{
|
||||||
public const string SectionName = "ContentBlocker";
|
public const string SectionName = "ContentBlocker";
|
||||||
|
|
||||||
|
|||||||
+1
-8
@@ -1,6 +1,4 @@
|
|||||||
using System.Security;
|
namespace Common.Configuration.DownloadClient;
|
||||||
|
|
||||||
namespace Common.Configuration;
|
|
||||||
|
|
||||||
public sealed record DelugeConfig : IConfig
|
public sealed record DelugeConfig : IConfig
|
||||||
{
|
{
|
||||||
@@ -23,10 +21,5 @@ public sealed record DelugeConfig : IConfig
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(Url));
|
throw new ArgumentNullException(nameof(Url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Password))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(Password));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-13
@@ -1,6 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
namespace Common.Configuration.DownloadClient;
|
||||||
|
|
||||||
namespace Common.Configuration;
|
|
||||||
|
|
||||||
public sealed class QBitConfig : IConfig
|
public sealed class QBitConfig : IConfig
|
||||||
{
|
{
|
||||||
@@ -25,15 +23,5 @@ public sealed class QBitConfig : IConfig
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(Url));
|
throw new ArgumentNullException(nameof(Url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Username))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(Username));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Password))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(Password));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace Common.Configuration;
|
namespace Common.Configuration.DownloadClient;
|
||||||
|
|
||||||
public record TransmissionConfig
|
public record TransmissionConfig
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Common.Configuration;
|
||||||
|
|
||||||
|
public interface IJobConfig : IConfig
|
||||||
|
{
|
||||||
|
bool Enabled { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Common.Configuration.Logging;
|
||||||
|
|
||||||
|
public class FileLogConfig : IConfig
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
public string Path { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public void Validate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace Common.Configuration.Logging;
|
||||||
|
|
||||||
|
public class LoggingConfig : IConfig
|
||||||
|
{
|
||||||
|
public const string SectionName = "Logging";
|
||||||
|
|
||||||
|
public LogEventLevel LogLevel { get; set; }
|
||||||
|
|
||||||
|
public bool Enhanced { get; set; }
|
||||||
|
|
||||||
|
public FileLogConfig? File { get; set; }
|
||||||
|
|
||||||
|
public void Validate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
namespace Common.Configuration.QueueCleaner;
|
namespace Common.Configuration.QueueCleaner;
|
||||||
|
|
||||||
public sealed record QueueCleanerConfig
|
public sealed record QueueCleanerConfig : IJobConfig
|
||||||
{
|
{
|
||||||
public const string SectionName = "QueueCleaner";
|
public const string SectionName = "QueueCleaner";
|
||||||
|
|
||||||
public required bool Enabled { get; init; }
|
public required bool Enabled { get; init; }
|
||||||
|
|
||||||
|
public required bool RunSequentially { get; init; }
|
||||||
|
|
||||||
|
public void Validate()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ public record QueueRecord
|
|||||||
{
|
{
|
||||||
public int SeriesId { get; init; }
|
public int SeriesId { get; init; }
|
||||||
public int EpisodeId { get; init; }
|
public int EpisodeId { get; init; }
|
||||||
|
public int SeasonNumber { get; init; }
|
||||||
public int MovieId { get; init; }
|
public int MovieId { get; init; }
|
||||||
public required string Title { get; init; }
|
public required string Title { get; init; }
|
||||||
public string Status { get; init; }
|
public string Status { get; init; }
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace Domain.Models.Arr;
|
||||||
|
|
||||||
|
public class SearchItem
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is not SearchItem other)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Id == other.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Id.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace Domain.Models.Arr;
|
||||||
|
|
||||||
|
public sealed class SonarrSearchItem : SearchItem
|
||||||
|
{
|
||||||
|
public long SeriesId { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is not SonarrSearchItem other)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Id == other.Id && SeriesId == other.SeriesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Id, SeriesId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace Domain.Models.Deluge.Response;
|
|||||||
public sealed record DelugeContents
|
public sealed record DelugeContents
|
||||||
{
|
{
|
||||||
[JsonPropertyName("contents")]
|
[JsonPropertyName("contents")]
|
||||||
public Dictionary<string, DelugeFileOrDirectory> Contents { get; set; }
|
public Dictionary<string, DelugeFileOrDirectory>? Contents { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("type")]
|
[JsonPropertyName("type")]
|
||||||
public string Type { get; set; } // Always "dir" for the root
|
public string Type { get; set; } // Always "dir" for the root
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Domain.Models.Radarr;
|
||||||
|
|
||||||
|
public sealed record Movie
|
||||||
|
{
|
||||||
|
public required long Id { get; init; }
|
||||||
|
|
||||||
|
public required string Title { get; init; }
|
||||||
|
}
|
||||||
@@ -4,5 +4,5 @@ public sealed record RadarrCommand
|
|||||||
{
|
{
|
||||||
public required string Name { get; init; }
|
public required string Name { get; init; }
|
||||||
|
|
||||||
public required HashSet<int> MovieIds { get; init; }
|
public required List<long> MovieIds { get; init; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Domain.Models.Sonarr;
|
||||||
|
|
||||||
|
public sealed record Episode
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public int EpisodeNumber { get; set; }
|
||||||
|
|
||||||
|
public int SeasonNumber { get; set; }
|
||||||
|
|
||||||
|
public long SeriesId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Domain.Models.Sonarr;
|
||||||
|
|
||||||
|
public sealed record Series
|
||||||
|
{
|
||||||
|
public required long Id { get; init; }
|
||||||
|
|
||||||
|
public required string Title { get; init; }
|
||||||
|
}
|
||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
public sealed record SonarrCommand
|
public sealed record SonarrCommand
|
||||||
{
|
{
|
||||||
public required string Name { get; init; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public required int SeriesId { get; set; }
|
public long? SeriesId { get; set; }
|
||||||
|
|
||||||
|
public long? SeasonNumber { get; set; }
|
||||||
|
|
||||||
|
public List<long>? EpisodeIds { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.Arr;
|
||||||
using Common.Configuration.ContentBlocker;
|
using Common.Configuration.ContentBlocker;
|
||||||
|
using Common.Configuration.DownloadClient;
|
||||||
|
using Common.Configuration.Logging;
|
||||||
|
|
||||||
namespace Executable.DependencyInjection;
|
namespace Executable.DependencyInjection;
|
||||||
|
|
||||||
@@ -12,5 +15,6 @@ public static class ConfigurationDI
|
|||||||
.Configure<DelugeConfig>(configuration.GetSection(DelugeConfig.SectionName))
|
.Configure<DelugeConfig>(configuration.GetSection(DelugeConfig.SectionName))
|
||||||
.Configure<TransmissionConfig>(configuration.GetSection(TransmissionConfig.SectionName))
|
.Configure<TransmissionConfig>(configuration.GetSection(TransmissionConfig.SectionName))
|
||||||
.Configure<SonarrConfig>(configuration.GetSection(SonarrConfig.SectionName))
|
.Configure<SonarrConfig>(configuration.GetSection(SonarrConfig.SectionName))
|
||||||
.Configure<RadarrConfig>(configuration.GetSection(RadarrConfig.SectionName));
|
.Configure<RadarrConfig>(configuration.GetSection(RadarrConfig.SectionName))
|
||||||
|
.Configure<LoggingConfig>(configuration.GetSection(LoggingConfig.SectionName));
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using Common.Configuration.Logging;
|
||||||
|
using Infrastructure.Verticals.ContentBlocker;
|
||||||
|
using Infrastructure.Verticals.QueueCleaner;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog.Templates;
|
||||||
|
using Serilog.Templates.Themes;
|
||||||
|
|
||||||
|
namespace Executable.DependencyInjection;
|
||||||
|
|
||||||
|
public static class LoggingDI
|
||||||
|
{
|
||||||
|
public static ILoggingBuilder AddLogging(this ILoggingBuilder builder, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
LoggingConfig? config = configuration.GetSection(LoggingConfig.SectionName).Get<LoggingConfig>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(config?.File?.Path) && !Directory.Exists(config.File.Path))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(config.File.Path);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
throw new Exception($"log file path is not a valid directory | {config.File.Path}", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerConfiguration logConfig = new();
|
||||||
|
const string consoleOutputTemplate = "[{@t:yyyy-MM-dd HH:mm:ss.fff} {@l:u3}]{#if JobName is not null} {Concat('[',JobName,']'),PAD}{#end} {@m}\n{@x}";
|
||||||
|
const string fileOutputTemplate = "{@t:yyyy-MM-dd HH:mm:ss.fff zzz} [{@l:u3}]{#if JobName is not null} {Concat('[',JobName,']'),PAD}{#end} {@m:lj}\n{@x}";
|
||||||
|
LogEventLevel level = LogEventLevel.Information;
|
||||||
|
List<string> jobNames = [nameof(ContentBlocker), nameof(QueueCleaner)];
|
||||||
|
int padding = jobNames.Max(x => x.Length) + 2;
|
||||||
|
|
||||||
|
if (config is not null)
|
||||||
|
{
|
||||||
|
level = config.LogLevel;
|
||||||
|
|
||||||
|
if (config.File?.Enabled is true)
|
||||||
|
{
|
||||||
|
logConfig.WriteTo.File(
|
||||||
|
path: Path.Combine(config.File.Path, "cleanuperr-.txt"),
|
||||||
|
formatter: new ExpressionTemplate(fileOutputTemplate.Replace("PAD", padding.ToString())),
|
||||||
|
fileSizeLimitBytes: 10L * 1024 * 1024,
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
rollOnFileSizeLimit: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Logger = logConfig
|
||||||
|
.MinimumLevel.Is(level)
|
||||||
|
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
|
||||||
|
.MinimumLevel.Override("Quartz", LogEventLevel.Warning)
|
||||||
|
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error)
|
||||||
|
.WriteTo.Console(new ExpressionTemplate(consoleOutputTemplate.Replace("PAD", padding.ToString())))
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithProperty("ApplicationName", "cleanuperr")
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.ClearProviders()
|
||||||
|
.AddSerilog();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using Common.Configuration.ContentBlocker;
|
|||||||
using Common.Configuration.QueueCleaner;
|
using Common.Configuration.QueueCleaner;
|
||||||
using Executable.Jobs;
|
using Executable.Jobs;
|
||||||
using Infrastructure.Verticals.ContentBlocker;
|
using Infrastructure.Verticals.ContentBlocker;
|
||||||
|
using Infrastructure.Verticals.Jobs;
|
||||||
using Infrastructure.Verticals.QueueCleaner;
|
using Infrastructure.Verticals.QueueCleaner;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
@@ -23,60 +24,51 @@ public static class QuartzDI
|
|||||||
throw new NullReferenceException("triggers configuration is null");
|
throw new NullReferenceException("triggers configuration is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
q.AddQueueCleanerJob(configuration, config.QueueCleaner);
|
q.AddJobs(configuration, config);
|
||||||
q.AddContentBlockerJob(configuration, config.ContentBlocker);
|
|
||||||
})
|
})
|
||||||
.AddQuartzHostedService(opt =>
|
.AddQuartzHostedService(opt =>
|
||||||
{
|
{
|
||||||
opt.WaitForJobsToComplete = true;
|
opt.WaitForJobsToComplete = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
private static void AddQueueCleanerJob(
|
private static void AddJobs(
|
||||||
this IServiceCollectionQuartzConfigurator q,
|
this IServiceCollectionQuartzConfigurator q,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
string trigger
|
TriggersConfig triggersConfig
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
QueueCleanerConfig? config = configuration
|
ContentBlockerConfig? contentBlockerConfig = configuration
|
||||||
|
.GetRequiredSection(ContentBlockerConfig.SectionName)
|
||||||
|
.Get<ContentBlockerConfig>();
|
||||||
|
|
||||||
|
q.AddJob<ContentBlocker>(contentBlockerConfig, triggersConfig.ContentBlocker);
|
||||||
|
|
||||||
|
QueueCleanerConfig? queueCleanerConfig = configuration
|
||||||
.GetRequiredSection(QueueCleanerConfig.SectionName)
|
.GetRequiredSection(QueueCleanerConfig.SectionName)
|
||||||
.Get<QueueCleanerConfig>();
|
.Get<QueueCleanerConfig>();
|
||||||
|
|
||||||
if (config is null)
|
if (contentBlockerConfig?.Enabled is true && queueCleanerConfig is { Enabled: true, RunSequentially: true })
|
||||||
{
|
{
|
||||||
throw new NullReferenceException($"{nameof(QueueCleaner)} configuration is null");
|
q.AddJob<QueueCleaner>(queueCleanerConfig, string.Empty);
|
||||||
|
q.AddJobListener(new JobChainingListener(nameof(QueueCleaner)));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!config.Enabled)
|
|
||||||
{
|
{
|
||||||
return;
|
q.AddJob<QueueCleaner>(queueCleanerConfig, triggersConfig.QueueCleaner);
|
||||||
}
|
}
|
||||||
|
|
||||||
q.AddJob<QueueCleanerJob>(opts =>
|
|
||||||
{
|
|
||||||
opts.WithIdentity(nameof(QueueCleanerJob));
|
|
||||||
});
|
|
||||||
|
|
||||||
q.AddTrigger(opts =>
|
|
||||||
{
|
|
||||||
opts.ForJob(nameof(QueueCleanerJob))
|
|
||||||
.WithIdentity($"{nameof(QueueCleanerJob)}-trigger")
|
|
||||||
.WithCronSchedule(trigger, x =>x.WithMisfireHandlingInstructionDoNothing());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddContentBlockerJob(
|
private static void AddJob<T>(
|
||||||
this IServiceCollectionQuartzConfigurator q,
|
this IServiceCollectionQuartzConfigurator q,
|
||||||
IConfiguration configuration,
|
IJobConfig? config,
|
||||||
string trigger
|
string trigger
|
||||||
)
|
) where T: GenericHandler
|
||||||
{
|
{
|
||||||
ContentBlockerConfig? config = configuration
|
string typeName = typeof(T).Name;
|
||||||
.GetRequiredSection(ContentBlockerConfig.SectionName)
|
|
||||||
.Get<ContentBlockerConfig>();
|
|
||||||
|
|
||||||
if (config is null)
|
if (config is null)
|
||||||
{
|
{
|
||||||
throw new NullReferenceException($"{nameof(ContentBlocker)} configuration is null");
|
throw new NullReferenceException($"{typeName} configuration is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.Enabled)
|
if (!config.Enabled)
|
||||||
@@ -84,16 +76,39 @@ public static class QuartzDI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
q.AddJob<ContentBlockerJob>(opts =>
|
bool hasTrigger = trigger.Length > 0;
|
||||||
|
|
||||||
|
q.AddJob<GenericJob<T>>(opts =>
|
||||||
{
|
{
|
||||||
opts.WithIdentity(nameof(ContentBlockerJob));
|
opts.WithIdentity(typeName);
|
||||||
|
|
||||||
|
if (!hasTrigger)
|
||||||
|
{
|
||||||
|
// jobs with no triggers need to be stored durably
|
||||||
|
opts.StoreDurably();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// skip empty triggers
|
||||||
|
if (!hasTrigger)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
q.AddTrigger(opts =>
|
q.AddTrigger(opts =>
|
||||||
{
|
{
|
||||||
opts.ForJob(nameof(ContentBlockerJob))
|
opts.ForJob(typeName)
|
||||||
.WithIdentity($"{nameof(ContentBlockerJob)}-trigger")
|
.WithIdentity($"{typeName}-trigger")
|
||||||
.WithCronSchedule(trigger, x =>x.WithMisfireHandlingInstructionDoNothing());
|
.WithCronSchedule(trigger, x =>x.WithMisfireHandlingInstructionDoNothing())
|
||||||
|
.StartNow();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Startup trigger
|
||||||
|
q.AddTrigger(opts =>
|
||||||
|
{
|
||||||
|
opts.ForJob(typeName)
|
||||||
|
.WithIdentity($"{typeName}-startup-trigger")
|
||||||
|
.StartNow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,8 +15,6 @@ public static class ServicesDI
|
|||||||
services
|
services
|
||||||
.AddTransient<SonarrClient>()
|
.AddTransient<SonarrClient>()
|
||||||
.AddTransient<RadarrClient>()
|
.AddTransient<RadarrClient>()
|
||||||
.AddTransient<QueueCleanerJob>()
|
|
||||||
.AddTransient<ContentBlockerJob>()
|
|
||||||
.AddTransient<QueueCleaner>()
|
.AddTransient<QueueCleaner>()
|
||||||
.AddTransient<ContentBlocker>()
|
.AddTransient<ContentBlocker>()
|
||||||
.AddTransient<FilenameEvaluator>()
|
.AddTransient<FilenameEvaluator>()
|
||||||
|
|||||||
@@ -9,11 +9,17 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||||
<PackageReference Include="Quartz" Version="3.13.1" />
|
<PackageReference Include="Quartz" Version="3.13.1" />
|
||||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.13.1" />
|
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.13.1" />
|
||||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.13.1" />
|
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.13.1" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||||
|
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
using Infrastructure.Verticals.ContentBlocker;
|
|
||||||
using Quartz;
|
|
||||||
|
|
||||||
namespace Executable.Jobs;
|
|
||||||
|
|
||||||
[DisallowConcurrentExecution]
|
|
||||||
public sealed class ContentBlockerJob : IJob
|
|
||||||
{
|
|
||||||
private readonly ILogger<QueueCleanerJob> _logger;
|
|
||||||
private readonly ContentBlocker _contentBlocker;
|
|
||||||
|
|
||||||
public ContentBlockerJob(
|
|
||||||
ILogger<QueueCleanerJob> logger,
|
|
||||||
ContentBlocker contentBlocker
|
|
||||||
)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_contentBlocker = contentBlocker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _contentBlocker.ExecuteAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"{nameof(ContentBlockerJob)} failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Infrastructure.Verticals.Jobs;
|
||||||
|
using Quartz;
|
||||||
|
using Serilog.Context;
|
||||||
|
|
||||||
|
namespace Executable.Jobs;
|
||||||
|
|
||||||
|
[DisallowConcurrentExecution]
|
||||||
|
public sealed class GenericJob<T> : IJob
|
||||||
|
where T : GenericHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger<GenericJob<T>> _logger;
|
||||||
|
private readonly T _handler;
|
||||||
|
|
||||||
|
|
||||||
|
public GenericJob(ILogger<GenericJob<T>> logger, T handler)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
using var _ = LogContext.PushProperty("JobName", typeof(T).Name);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _handler.ExecuteAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "{name} failed", typeof(T).Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Infrastructure.Verticals.QueueCleaner;
|
|
||||||
using Quartz;
|
|
||||||
|
|
||||||
namespace Executable.Jobs;
|
|
||||||
|
|
||||||
[DisallowConcurrentExecution]
|
|
||||||
public sealed class QueueCleanerJob : IJob
|
|
||||||
{
|
|
||||||
private readonly ILogger<QueueCleanerJob> _logger;
|
|
||||||
private readonly QueueCleaner _queueCleaner;
|
|
||||||
|
|
||||||
public QueueCleanerJob(
|
|
||||||
ILogger<QueueCleanerJob> logger,
|
|
||||||
QueueCleaner queueCleaner
|
|
||||||
)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_queueCleaner = queueCleaner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _queueCleaner.ExecuteAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"{nameof(QueueCleanerJob)} failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ using Executable.DependencyInjection;
|
|||||||
var builder = Host.CreateApplicationBuilder(args);
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddInfrastructure(builder.Configuration);
|
builder.Services.AddInfrastructure(builder.Configuration);
|
||||||
|
builder.Logging.AddLogging(builder.Configuration);
|
||||||
|
|
||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": "Debug",
|
||||||
"Default": "Debug",
|
"Enhanced": true,
|
||||||
"Microsoft.Hosting.Lifetime": "Information",
|
"File": {
|
||||||
"Quartz": "Warning",
|
"Enabled": false,
|
||||||
"System.Net.Http.HttpClient": "Error"
|
"Path": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Triggers": {
|
"Triggers": {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"ContentBlocker": "0/10 * * * * ?"
|
"ContentBlocker": "0/10 * * * * ?"
|
||||||
},
|
},
|
||||||
"ContentBlocker": {
|
"ContentBlocker": {
|
||||||
"Enabled": false,
|
"Enabled": true,
|
||||||
"Blacklist": {
|
"Blacklist": {
|
||||||
"Enabled": false,
|
"Enabled": false,
|
||||||
"Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist"
|
"Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist"
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"QueueCleaner": {
|
"QueueCleaner": {
|
||||||
"Enabled": true
|
"Enabled": true,
|
||||||
|
"RunSequentially": true
|
||||||
},
|
},
|
||||||
"qBittorrent": {
|
"qBittorrent": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
},
|
},
|
||||||
"Sonarr": {
|
"Sonarr": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
|
"SearchType": "Episode",
|
||||||
"Instances": [
|
"Instances": [
|
||||||
{
|
{
|
||||||
"Url": "http://localhost:8989",
|
"Url": "http://localhost:8989",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": "Information",
|
||||||
"Default": "Information",
|
"Enhanced": true,
|
||||||
"Microsoft.Hosting.Lifetime": "Information",
|
"File": {
|
||||||
"Quartz": "Warning",
|
"Enabled": false,
|
||||||
"System.Net.Http.HttpClient": "Error"
|
"Path": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Triggers": {
|
"Triggers": {
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"QueueCleaner": {
|
"QueueCleaner": {
|
||||||
"Enabled": true
|
"Enabled": true,
|
||||||
|
"RunSequentially": true
|
||||||
},
|
},
|
||||||
"qBittorrent": {
|
"qBittorrent": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
},
|
},
|
||||||
"Sonarr": {
|
"Sonarr": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
|
"SearchType": "Episode",
|
||||||
"Instances": [
|
"Instances": [
|
||||||
{
|
{
|
||||||
"Url": "http://localhost:8989",
|
"Url": "http://localhost:8989",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FLM.Transmission" Version="1.0.0" />
|
<PackageReference Include="FLM.Transmission" Version="1.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.1" />
|
|
||||||
<PackageReference Include="QBittorrent.Client" Version="1.9.24285.1" />
|
<PackageReference Include="QBittorrent.Client" Version="1.9.24285.1" />
|
||||||
|
<PackageReference Include="Quartz" Version="3.13.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.Arr;
|
||||||
|
using Common.Configuration.Logging;
|
||||||
using Domain.Arr.Queue;
|
using Domain.Arr.Queue;
|
||||||
|
using Domain.Models.Arr;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Infrastructure.Verticals.Arr;
|
namespace Infrastructure.Verticals.Arr;
|
||||||
|
|
||||||
public abstract class ArrClient
|
public abstract class ArrClient
|
||||||
{
|
{
|
||||||
private protected ILogger<ArrClient> _logger;
|
protected readonly ILogger<ArrClient> _logger;
|
||||||
private protected HttpClient _httpClient;
|
protected readonly HttpClient _httpClient;
|
||||||
|
protected readonly LoggingConfig _loggingConfig;
|
||||||
|
|
||||||
protected ArrClient(ILogger<ArrClient> logger, IHttpClientFactory httpClientFactory)
|
protected ArrClient(ILogger<ArrClient> logger, IHttpClientFactory httpClientFactory, IOptions<LoggingConfig> loggingConfig)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
|
_loggingConfig = loggingConfig.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<QueueListResponse> GetQueueItemsAsync(ArrInstance arrInstance, int page)
|
public virtual async Task<QueueListResponse> GetQueueItemsAsync(ArrInstance arrInstance, int page)
|
||||||
@@ -68,7 +74,7 @@ public abstract class ArrClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<int> itemIds);
|
public abstract Task RefreshItemsAsync(ArrInstance arrInstance, ArrConfig config, HashSet<SearchItem>? items);
|
||||||
|
|
||||||
protected virtual void SetApiKey(HttpRequestMessage request, string apiKey)
|
protected virtual void SetApiKey(HttpRequestMessage request, string apiKey)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.Arr;
|
||||||
using Domain.Arr.Queue;
|
using Domain.Arr.Queue;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Common.Configuration;
|
using Common.Configuration.Arr;
|
||||||
|
using Common.Configuration.Logging;
|
||||||
|
using Domain.Models.Arr;
|
||||||
using Domain.Models.Radarr;
|
using Domain.Models.Radarr;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Infrastructure.Verticals.Arr;
|
namespace Infrastructure.Verticals.Arr;
|
||||||
|
|
||||||
public sealed class RadarrClient : ArrClient
|
public sealed class RadarrClient : ArrClient
|
||||||
{
|
{
|
||||||
public RadarrClient(ILogger<ArrClient> logger, IHttpClientFactory httpClientFactory)
|
public RadarrClient(
|
||||||
: base(logger, httpClientFactory)
|
ILogger<ArrClient> logger,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<LoggingConfig> loggingConfig
|
||||||
|
) : base(logger, httpClientFactory, loggingConfig)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<int> itemIds)
|
public override async Task RefreshItemsAsync(ArrInstance arrInstance, ArrConfig config, HashSet<SearchItem>? items)
|
||||||
{
|
{
|
||||||
if (itemIds.Count is 0)
|
if (items?.Count is null or 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<long> ids = items.Select(item => item.Id).ToList();
|
||||||
|
|
||||||
Uri uri = new(arrInstance.Url, "/api/v3/command");
|
Uri uri = new(arrInstance.Url, "/api/v3/command");
|
||||||
RadarrCommand command = new()
|
RadarrCommand command = new()
|
||||||
{
|
{
|
||||||
Name = "MoviesSearch",
|
Name = "MoviesSearch",
|
||||||
MovieIds = itemIds
|
MovieIds = ids,
|
||||||
};
|
};
|
||||||
|
|
||||||
using HttpRequestMessage request = new(HttpMethod.Post, uri);
|
using HttpRequestMessage request = new(HttpMethod.Post, uri);
|
||||||
@@ -36,17 +44,72 @@ public sealed class RadarrClient : ArrClient
|
|||||||
SetApiKey(request, arrInstance.ApiKey);
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
string? logContext = await ComputeCommandLogContextAsync(arrInstance, command);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
_logger.LogInformation("movie search triggered | {url} | movie ids: {ids}", arrInstance.Url, string.Join(",", itemIds));
|
_logger.LogInformation("{log}", GetSearchLog(arrInstance.Url, command, true, logContext));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_logger.LogError("movie search failed | {url} | movie ids: {ids}", arrInstance.Url, string.Join(",", itemIds));
|
_logger.LogError("{log}", GetSearchLog(arrInstance.Url, command, false, logContext));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetSearchLog(Uri instanceUrl, RadarrCommand command, bool success, string? logContext)
|
||||||
|
{
|
||||||
|
string status = success ? "triggered" : "failed";
|
||||||
|
string message = logContext ?? $"movie ids: {string.Join(',', command.MovieIds)}";
|
||||||
|
|
||||||
|
return $"movie search {status} | {instanceUrl} | {message}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string?> ComputeCommandLogContextAsync(ArrInstance arrInstance, RadarrCommand command)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_loggingConfig.Enhanced)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder log = new();
|
||||||
|
|
||||||
|
foreach (long movieId in command.MovieIds)
|
||||||
|
{
|
||||||
|
Movie? movie = await GetMovie(arrInstance, movieId);
|
||||||
|
|
||||||
|
if (movie is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Append($"[{movie.Title}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
return log.ToString();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(exception, "failed to compute log context");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Movie?> GetMovie(ArrInstance arrInstance, long movieId)
|
||||||
|
{
|
||||||
|
Uri uri = new(arrInstance.Url, $"api/v3/movie/{movieId}");
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Get, uri);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
string responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
return JsonConvert.DeserializeObject<Movie>(responseBody);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,244 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Common.Configuration;
|
using Common.Configuration.Arr;
|
||||||
|
using Common.Configuration.Logging;
|
||||||
|
using Domain.Models.Arr;
|
||||||
using Domain.Models.Sonarr;
|
using Domain.Models.Sonarr;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Infrastructure.Verticals.Arr;
|
namespace Infrastructure.Verticals.Arr;
|
||||||
|
|
||||||
public sealed class SonarrClient : ArrClient
|
public sealed class SonarrClient : ArrClient
|
||||||
{
|
{
|
||||||
public SonarrClient(ILogger<SonarrClient> logger, IHttpClientFactory httpClientFactory)
|
public SonarrClient(
|
||||||
: base(logger, httpClientFactory)
|
ILogger<SonarrClient> logger,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<LoggingConfig> loggingConfig
|
||||||
|
) : base(logger, httpClientFactory, loggingConfig)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task RefreshItemsAsync(ArrInstance arrInstance, HashSet<int> itemIds)
|
public override async Task RefreshItemsAsync(ArrInstance arrInstance, ArrConfig config, HashSet<SearchItem>? items)
|
||||||
{
|
{
|
||||||
foreach (int itemId in itemIds)
|
if (items?.Count is null or 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SonarrConfig sonarrConfig = (SonarrConfig)config;
|
||||||
|
|
||||||
|
Uri uri = new(arrInstance.Url, "/api/v3/command");
|
||||||
|
|
||||||
|
foreach (SonarrCommand command in GetSearchCommands(sonarrConfig.SearchType, items))
|
||||||
{
|
{
|
||||||
Uri uri = new(arrInstance.Url, "/api/v3/command");
|
|
||||||
SonarrCommand command = new()
|
|
||||||
{
|
|
||||||
Name = "SeriesSearch",
|
|
||||||
SeriesId = itemId
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpRequestMessage request = new(HttpMethod.Post, uri);
|
using HttpRequestMessage request = new(HttpMethod.Post, uri);
|
||||||
request.Content = new StringContent(
|
request.Content = new StringContent(
|
||||||
JsonConvert.SerializeObject(command),
|
JsonConvert.SerializeObject(command, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
|
||||||
Encoding.UTF8,
|
Encoding.UTF8,
|
||||||
"application/json"
|
"application/json"
|
||||||
);
|
);
|
||||||
SetApiKey(request, arrInstance.ApiKey);
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
string? logContext = await ComputeCommandLogContextAsync(arrInstance, command, sonarrConfig.SearchType);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
_logger.LogInformation("series search triggered | {url} | series id: {id}", arrInstance.Url, itemId);
|
_logger.LogInformation("{log}", GetSearchLog(sonarrConfig.SearchType, arrInstance.Url, command, true, logContext));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_logger.LogError("series search failed | {url} | series id: {id}", arrInstance.Url, itemId);
|
_logger.LogError("{log}", GetSearchLog(sonarrConfig.SearchType, arrInstance.Url, command, false, logContext));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetSearchLog(
|
||||||
|
SonarrSearchType searchType,
|
||||||
|
Uri instanceUrl,
|
||||||
|
SonarrCommand command,
|
||||||
|
bool success,
|
||||||
|
string? logContext
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string status = success ? "triggered" : "failed";
|
||||||
|
|
||||||
|
return searchType switch
|
||||||
|
{
|
||||||
|
SonarrSearchType.Episode =>
|
||||||
|
$"episodes search {status} | {instanceUrl} | {logContext ?? $"episode ids: {string.Join(',', command.EpisodeIds)}"}",
|
||||||
|
SonarrSearchType.Season =>
|
||||||
|
$"season search {status} | {instanceUrl} | {logContext ?? $"season: {command.SeasonNumber} series id: {command.SeriesId}"}",
|
||||||
|
SonarrSearchType.Series => $"series search {status} | {instanceUrl} | {logContext ?? $"series id: {command.SeriesId}"}",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(searchType), searchType, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string?> ComputeCommandLogContextAsync(ArrInstance arrInstance, SonarrCommand command, SonarrSearchType searchType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_loggingConfig.Enhanced)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder log = new();
|
||||||
|
|
||||||
|
if (searchType is SonarrSearchType.Episode)
|
||||||
|
{
|
||||||
|
var episodes = await GetEpisodesAsync(arrInstance, command.EpisodeIds);
|
||||||
|
|
||||||
|
if (episodes?.Count is null or 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var seriesIds = episodes
|
||||||
|
.Select(x => x.SeriesId)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
List<Series> series = [];
|
||||||
|
|
||||||
|
foreach (long id in seriesIds)
|
||||||
|
{
|
||||||
|
Series? show = await GetSeriesAsync(arrInstance, id);
|
||||||
|
|
||||||
|
if (show is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
series.Add(show);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in command.EpisodeIds.GroupBy(id => episodes.First(x => x.Id == id).SeriesId))
|
||||||
|
{
|
||||||
|
var show = series.First(x => x.Id == group.Key);
|
||||||
|
var episode = episodes
|
||||||
|
.Where(ep => group.Any(x => x == ep.Id))
|
||||||
|
.OrderBy(x => x.SeasonNumber)
|
||||||
|
.ThenBy(x => x.EpisodeNumber)
|
||||||
|
.Select(x => $"S{x.SeasonNumber.ToString().PadLeft(2, '0')}E{x.EpisodeNumber.ToString().PadLeft(2, '0')}")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
log.Append($"[{show.Title} {string.Join(',', episode)}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchType is SonarrSearchType.Season)
|
||||||
|
{
|
||||||
|
Series? show = await GetSeriesAsync(arrInstance, command.SeriesId.Value);
|
||||||
|
|
||||||
|
if (show is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Append($"[{show.Title} season {command.SeasonNumber}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchType is SonarrSearchType.Series)
|
||||||
|
{
|
||||||
|
Series? show = await GetSeriesAsync(arrInstance, command.SeriesId.Value);
|
||||||
|
|
||||||
|
if (show is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Append($"[{show.Title}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
return log.ToString();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(exception, "failed to compute log context");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<Episode>?> GetEpisodesAsync(ArrInstance arrInstance, List<long> episodeIds)
|
||||||
|
{
|
||||||
|
Uri uri = new(arrInstance.Url, $"api/v3/episode?{string.Join('&', episodeIds.Select(x => $"episodeIds={x}"))}");
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Get, uri);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
string responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
return JsonConvert.DeserializeObject<List<Episode>>(responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Series?> GetSeriesAsync(ArrInstance arrInstance, long seriesId)
|
||||||
|
{
|
||||||
|
Uri uri = new(arrInstance.Url, $"api/v3/series/{seriesId}");
|
||||||
|
using HttpRequestMessage request = new(HttpMethod.Get, uri);
|
||||||
|
SetApiKey(request, arrInstance.ApiKey);
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _httpClient.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
string responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
return JsonConvert.DeserializeObject<Series>(responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SonarrCommand> GetSearchCommands(SonarrSearchType searchType, HashSet<SearchItem> items)
|
||||||
|
{
|
||||||
|
const string episodeSearch = "EpisodeSearch";
|
||||||
|
const string seasonSearch = "SeasonSearch";
|
||||||
|
const string seriesSearch = "SeriesSearch";
|
||||||
|
|
||||||
|
List<SonarrCommand> commands = new();
|
||||||
|
|
||||||
|
foreach (SearchItem item in items)
|
||||||
|
{
|
||||||
|
SonarrCommand command = searchType is SonarrSearchType.Episode
|
||||||
|
? commands.FirstOrDefault() ?? new() { Name = episodeSearch, EpisodeIds = new() }
|
||||||
|
: new();
|
||||||
|
|
||||||
|
switch (searchType)
|
||||||
|
{
|
||||||
|
case SonarrSearchType.Episode when command.EpisodeIds is null:
|
||||||
|
command.EpisodeIds = [item.Id];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SonarrSearchType.Episode when command.EpisodeIds is not null:
|
||||||
|
command.EpisodeIds.Add(item.Id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SonarrSearchType.Season:
|
||||||
|
command.Name = seasonSearch;
|
||||||
|
command.SeasonNumber = item.Id;
|
||||||
|
command.SeriesId = ((SonarrSearchItem)item).SeriesId;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SonarrSearchType.Series:
|
||||||
|
command.Name = seriesSearch;
|
||||||
|
command.SeriesId = item.Id;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(searchType), searchType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchType is SonarrSearchType.Episode && commands.Count > 0)
|
||||||
|
{
|
||||||
|
// only one command will be generated for episodes search
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Common.Configuration.ContentBlocker;
|
using Common.Configuration.ContentBlocker;
|
||||||
using Domain.Enums;
|
using Domain.Enums;
|
||||||
@@ -15,9 +16,9 @@ public sealed class BlocklistProvider
|
|||||||
|
|
||||||
public BlocklistType BlocklistType { get; }
|
public BlocklistType BlocklistType { get; }
|
||||||
|
|
||||||
public List<string> Patterns { get; } = [];
|
public ConcurrentBag<string> Patterns { get; } = [];
|
||||||
|
|
||||||
public List<Regex> Regexes { get; } = [];
|
public ConcurrentBag<Regex> Regexes { get; } = [];
|
||||||
|
|
||||||
public BlocklistProvider(
|
public BlocklistProvider(
|
||||||
ILogger<BlocklistProvider> logger,
|
ILogger<BlocklistProvider> logger,
|
||||||
@@ -75,9 +76,18 @@ public sealed class BlocklistProvider
|
|||||||
|
|
||||||
long startTime = Stopwatch.GetTimestamp();
|
long startTime = Stopwatch.GetTimestamp();
|
||||||
ParallelOptions options = new() { MaxDegreeOfParallelism = 5 };
|
ParallelOptions options = new() { MaxDegreeOfParallelism = 5 };
|
||||||
|
const string regexId = "regex:";
|
||||||
|
|
||||||
Parallel.ForEach(patterns, options, pattern =>
|
Parallel.ForEach(patterns, options, pattern =>
|
||||||
{
|
{
|
||||||
|
if (!pattern.StartsWith(regexId))
|
||||||
|
{
|
||||||
|
Patterns.Add(pattern);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = pattern[regexId.Length..];
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Regex regex = new(pattern, RegexOptions.Compiled);
|
Regex regex = new(pattern, RegexOptions.Compiled);
|
||||||
@@ -85,7 +95,7 @@ public sealed class BlocklistProvider
|
|||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
Patterns.Add(pattern);
|
_logger.LogWarning("invalid regex | {pattern}", pattern);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.Arr;
|
||||||
using Domain.Arr.Queue;
|
using Domain.Arr.Queue;
|
||||||
using Domain.Enums;
|
using Domain.Enums;
|
||||||
using Infrastructure.Verticals.Arr;
|
using Infrastructure.Verticals.Arr;
|
||||||
using Infrastructure.Verticals.DownloadClient;
|
using Infrastructure.Verticals.DownloadClient;
|
||||||
|
using Infrastructure.Verticals.Jobs;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Infrastructure.Verticals.ContentBlocker;
|
namespace Infrastructure.Verticals.ContentBlocker;
|
||||||
|
|
||||||
public sealed class ContentBlocker : IDisposable
|
public sealed class ContentBlocker : GenericHandler
|
||||||
{
|
{
|
||||||
private readonly ILogger<ContentBlocker> _logger;
|
|
||||||
private readonly SonarrConfig _sonarrConfig;
|
|
||||||
private readonly RadarrConfig _radarrConfig;
|
|
||||||
private readonly SonarrClient _sonarrClient;
|
|
||||||
private readonly RadarrClient _radarrClient;
|
|
||||||
private readonly ArrQueueIterator _arrArrQueueIterator;
|
|
||||||
private readonly BlocklistProvider _blocklistProvider;
|
private readonly BlocklistProvider _blocklistProvider;
|
||||||
private readonly IDownloadService _downloadService;
|
|
||||||
|
|
||||||
public ContentBlocker(
|
public ContentBlocker(
|
||||||
ILogger<ContentBlocker> logger,
|
ILogger<ContentBlocker> logger,
|
||||||
@@ -28,48 +23,18 @@ public sealed class ContentBlocker : IDisposable
|
|||||||
ArrQueueIterator arrArrQueueIterator,
|
ArrQueueIterator arrArrQueueIterator,
|
||||||
BlocklistProvider blocklistProvider,
|
BlocklistProvider blocklistProvider,
|
||||||
DownloadServiceFactory downloadServiceFactory
|
DownloadServiceFactory downloadServiceFactory
|
||||||
)
|
) : base(logger, sonarrConfig.Value, radarrConfig.Value, sonarrClient, radarrClient, arrArrQueueIterator, downloadServiceFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_sonarrConfig = sonarrConfig.Value;
|
|
||||||
_radarrConfig = radarrConfig.Value;
|
|
||||||
_sonarrClient = sonarrClient;
|
|
||||||
_radarrClient = radarrClient;
|
|
||||||
_arrArrQueueIterator = arrArrQueueIterator;
|
|
||||||
_blocklistProvider = blocklistProvider;
|
_blocklistProvider = blocklistProvider;
|
||||||
_downloadService = downloadServiceFactory.CreateDownloadClient();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync()
|
public override async Task ExecuteAsync()
|
||||||
{
|
{
|
||||||
await _blocklistProvider.LoadBlocklistAsync();
|
await _blocklistProvider.LoadBlocklistAsync();
|
||||||
await _downloadService.LoginAsync();
|
await base.ExecuteAsync();
|
||||||
|
|
||||||
await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr);
|
|
||||||
await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessArrConfigAsync(ArrConfig config, InstanceType instanceType)
|
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
||||||
{
|
|
||||||
if (!config.Enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ArrInstance arrInstance in config.Instances)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ProcessInstanceAsync(arrInstance, instanceType);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.LogError(exception, "failed to block content for {type} instance | {url}", instanceType, arrInstance.Url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
|
||||||
{
|
{
|
||||||
ArrClient arrClient = GetClient(instanceType);
|
ArrClient arrClient = GetClient(instanceType);
|
||||||
|
|
||||||
@@ -77,6 +42,11 @@ public sealed class ContentBlocker : IDisposable
|
|||||||
{
|
{
|
||||||
foreach (QueueRecord record in items)
|
foreach (QueueRecord record in items)
|
||||||
{
|
{
|
||||||
|
if (record.Protocol is not "torrent")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(record.DownloadId))
|
if (string.IsNullOrEmpty(record.DownloadId))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("skip | download id is null for {title}", record.Title);
|
_logger.LogDebug("skip | download id is null for {title}", record.Title);
|
||||||
@@ -96,9 +66,4 @@ public sealed class ContentBlocker : IDisposable
|
|||||||
InstanceType.Radarr => _radarrClient,
|
InstanceType.Radarr => _radarrClient,
|
||||||
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||||
};
|
};
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_downloadService.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.DownloadClient;
|
||||||
using Domain.Models.Deluge.Exceptions;
|
using Domain.Models.Deluge.Exceptions;
|
||||||
using Domain.Models.Deluge.Request;
|
using Domain.Models.Deluge.Request;
|
||||||
using Domain.Models.Deluge.Response;
|
using Domain.Models.Deluge.Response;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.DownloadClient;
|
||||||
using Domain.Models.Deluge.Response;
|
using Domain.Models.Deluge.Response;
|
||||||
using Infrastructure.Verticals.ContentBlocker;
|
using Infrastructure.Verticals.ContentBlocker;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -49,7 +50,8 @@ public sealed class DelugeService : IDownloadService
|
|||||||
_logger.LogDebug(exception, "failed to find torrent {hash} in the download client", hash);
|
_logger.LogDebug(exception, "failed to find torrent {hash} in the download client", hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contents is null)
|
// if no files found, torrent might be stuck in Downloading metadata
|
||||||
|
if (contents?.Contents?.Count is null or 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.DownloadClient;
|
||||||
using Infrastructure.Verticals.DownloadClient.Deluge;
|
using Infrastructure.Verticals.DownloadClient.Deluge;
|
||||||
using Infrastructure.Verticals.DownloadClient.QBittorrent;
|
using Infrastructure.Verticals.DownloadClient.QBittorrent;
|
||||||
using Infrastructure.Verticals.DownloadClient.Transmission;
|
using Infrastructure.Verticals.DownloadClient.Transmission;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.DownloadClient;
|
||||||
using Infrastructure.Verticals.ContentBlocker;
|
using Infrastructure.Verticals.ContentBlocker;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@@ -27,6 +28,11 @@ public sealed class QBitService : IDownloadService
|
|||||||
|
|
||||||
public async Task LoginAsync()
|
public async Task LoginAsync()
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(_config.Username) && string.IsNullOrEmpty(_config.Password))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await _client.LoginAsync(_config.Username, _config.Password);
|
await _client.LoginAsync(_config.Username, _config.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,18 +54,14 @@ public sealed class QBitService : IDownloadService
|
|||||||
|
|
||||||
IReadOnlyList<TorrentContent>? files = await _client.GetTorrentContentsAsync(hash);
|
IReadOnlyList<TorrentContent>? files = await _client.GetTorrentContentsAsync(hash);
|
||||||
|
|
||||||
if (files is null)
|
// if no files found, torrent might be stuck in Downloading metadata
|
||||||
|
if (files?.Count is null or 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if all files are marked as skip
|
|
||||||
if (files.All(x => x.Priority is TorrentContentPriority.Skip))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// if all files are marked as skip
|
||||||
|
return files.All(x => x.Priority is TorrentContentPriority.Skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BlockUnwantedFilesAsync(string hash)
|
public async Task BlockUnwantedFilesAsync(string hash)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.DownloadClient;
|
||||||
using Infrastructure.Verticals.ContentBlocker;
|
using Infrastructure.Verticals.ContentBlocker;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@@ -41,7 +42,8 @@ public sealed class TransmissionService : IDownloadService
|
|||||||
{
|
{
|
||||||
TorrentInfo? torrent = await GetTorrentAsync(hash);
|
TorrentInfo? torrent = await GetTorrentAsync(hash);
|
||||||
|
|
||||||
if (torrent is null)
|
// if no files found, torrent might be stuck in Downloading metadata
|
||||||
|
if (torrent?.FileStats?.Length is null or 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using Common.Configuration.Arr;
|
||||||
|
using Domain.Arr.Queue;
|
||||||
|
using Domain.Enums;
|
||||||
|
using Domain.Models.Arr;
|
||||||
|
using Infrastructure.Verticals.Arr;
|
||||||
|
using Infrastructure.Verticals.DownloadClient;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Infrastructure.Verticals.Jobs;
|
||||||
|
|
||||||
|
public abstract class GenericHandler : IDisposable
|
||||||
|
{
|
||||||
|
protected readonly ILogger<GenericHandler> _logger;
|
||||||
|
protected readonly SonarrConfig _sonarrConfig;
|
||||||
|
protected readonly RadarrConfig _radarrConfig;
|
||||||
|
protected readonly SonarrClient _sonarrClient;
|
||||||
|
protected readonly RadarrClient _radarrClient;
|
||||||
|
protected readonly ArrQueueIterator _arrArrQueueIterator;
|
||||||
|
protected readonly IDownloadService _downloadService;
|
||||||
|
|
||||||
|
protected GenericHandler(
|
||||||
|
ILogger<GenericHandler> logger,
|
||||||
|
SonarrConfig sonarrConfig,
|
||||||
|
RadarrConfig radarrConfig,
|
||||||
|
SonarrClient sonarrClient,
|
||||||
|
RadarrClient radarrClient,
|
||||||
|
ArrQueueIterator arrArrQueueIterator,
|
||||||
|
DownloadServiceFactory downloadServiceFactory
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_sonarrConfig = sonarrConfig;
|
||||||
|
_radarrConfig = radarrConfig;
|
||||||
|
_sonarrClient = sonarrClient;
|
||||||
|
_radarrClient = radarrClient;
|
||||||
|
_arrArrQueueIterator = arrArrQueueIterator;
|
||||||
|
_downloadService = downloadServiceFactory.CreateDownloadClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
await _downloadService.LoginAsync();
|
||||||
|
|
||||||
|
await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr);
|
||||||
|
await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
_downloadService.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType);
|
||||||
|
|
||||||
|
private async Task ProcessArrConfigAsync(ArrConfig config, InstanceType instanceType)
|
||||||
|
{
|
||||||
|
if (!config.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ArrInstance arrInstance in config.Instances)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessInstanceAsync(arrInstance, instanceType);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, "failed to clean {type} instance | {url}", instanceType, arrInstance.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrClient GetClient(InstanceType type) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
InstanceType.Sonarr => _sonarrClient,
|
||||||
|
InstanceType.Radarr => _radarrClient,
|
||||||
|
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||||
|
};
|
||||||
|
|
||||||
|
protected ArrConfig GetConfig(InstanceType type) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
InstanceType.Sonarr => _sonarrConfig,
|
||||||
|
InstanceType.Radarr => _radarrConfig,
|
||||||
|
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||||
|
};
|
||||||
|
|
||||||
|
protected SearchItem GetRecordSearchItem(InstanceType type, QueueRecord record) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Episode => new SonarrSearchItem
|
||||||
|
{
|
||||||
|
Id = record.EpisodeId,
|
||||||
|
SeriesId = record.SeriesId
|
||||||
|
},
|
||||||
|
InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Season => new SonarrSearchItem
|
||||||
|
{
|
||||||
|
Id = record.SeasonNumber,
|
||||||
|
SeriesId = record.SeriesId
|
||||||
|
},
|
||||||
|
InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Series => new SonarrSearchItem
|
||||||
|
{
|
||||||
|
Id = record.SeriesId,
|
||||||
|
},
|
||||||
|
InstanceType.Radarr => new SearchItem
|
||||||
|
{
|
||||||
|
Id = record.MovieId,
|
||||||
|
},
|
||||||
|
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace Infrastructure.Verticals.Jobs;
|
||||||
|
|
||||||
|
public class JobChainingListener : IJobListener
|
||||||
|
{
|
||||||
|
private readonly string _nextJobName;
|
||||||
|
|
||||||
|
public JobChainingListener(string nextJobName)
|
||||||
|
{
|
||||||
|
_nextJobName = nextJobName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => nameof(JobChainingListener);
|
||||||
|
|
||||||
|
public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_nextJobName) || context.JobDetail.Key.Name == _nextJobName)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IScheduler scheduler = context.Scheduler;
|
||||||
|
JobKey nextJobKey = new(_nextJobName);
|
||||||
|
|
||||||
|
if (await scheduler.CheckExists(nextJobKey, cancellationToken))
|
||||||
|
{
|
||||||
|
await scheduler.TriggerJob(nextJobKey, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
using Common.Configuration;
|
using Common.Configuration;
|
||||||
|
using Common.Configuration.Arr;
|
||||||
using Domain.Arr.Queue;
|
using Domain.Arr.Queue;
|
||||||
using Domain.Enums;
|
using Domain.Enums;
|
||||||
|
using Domain.Models.Arr;
|
||||||
using Infrastructure.Verticals.Arr;
|
using Infrastructure.Verticals.Arr;
|
||||||
using Infrastructure.Verticals.DownloadClient;
|
using Infrastructure.Verticals.DownloadClient;
|
||||||
|
using Infrastructure.Verticals.Jobs;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Infrastructure.Verticals.QueueCleaner;
|
namespace Infrastructure.Verticals.QueueCleaner;
|
||||||
|
|
||||||
public sealed class QueueCleaner : IDisposable
|
public sealed class QueueCleaner : GenericHandler
|
||||||
{
|
{
|
||||||
private readonly ILogger<QueueCleaner> _logger;
|
|
||||||
private readonly SonarrConfig _sonarrConfig;
|
|
||||||
private readonly RadarrConfig _radarrConfig;
|
|
||||||
private readonly SonarrClient _sonarrClient;
|
|
||||||
private readonly RadarrClient _radarrClient;
|
|
||||||
private readonly ArrQueueIterator _arrArrQueueIterator;
|
|
||||||
private readonly IDownloadService _downloadService;
|
|
||||||
|
|
||||||
public QueueCleaner(
|
public QueueCleaner(
|
||||||
ILogger<QueueCleaner> logger,
|
ILogger<QueueCleaner> logger,
|
||||||
IOptions<SonarrConfig> sonarrConfig,
|
IOptions<SonarrConfig> sonarrConfig,
|
||||||
@@ -26,50 +21,13 @@ public sealed class QueueCleaner : IDisposable
|
|||||||
RadarrClient radarrClient,
|
RadarrClient radarrClient,
|
||||||
ArrQueueIterator arrArrQueueIterator,
|
ArrQueueIterator arrArrQueueIterator,
|
||||||
DownloadServiceFactory downloadServiceFactory
|
DownloadServiceFactory downloadServiceFactory
|
||||||
)
|
) : base(logger, sonarrConfig.Value, radarrConfig.Value, sonarrClient, radarrClient, arrArrQueueIterator, downloadServiceFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_sonarrConfig = sonarrConfig.Value;
|
|
||||||
_radarrConfig = radarrConfig.Value;
|
|
||||||
_sonarrClient = sonarrClient;
|
|
||||||
_radarrClient = radarrClient;
|
|
||||||
_arrArrQueueIterator = arrArrQueueIterator;
|
|
||||||
_downloadService = downloadServiceFactory.CreateDownloadClient();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync()
|
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
||||||
{
|
{
|
||||||
await _downloadService.LoginAsync();
|
HashSet<SearchItem> itemsToBeRefreshed = [];
|
||||||
|
|
||||||
await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr);
|
|
||||||
await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr);
|
|
||||||
|
|
||||||
// await _downloadClient.LogoutAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessArrConfigAsync(ArrConfig config, InstanceType instanceType)
|
|
||||||
{
|
|
||||||
if (!config.Enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ArrInstance arrInstance in config.Instances)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ProcessInstanceAsync(arrInstance, instanceType);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.LogError(exception, "failed to clean {type} instance | {url}", instanceType, arrInstance.Url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
|
||||||
{
|
|
||||||
HashSet<int> itemsToBeRefreshed = [];
|
|
||||||
ArrClient arrClient = GetClient(instanceType);
|
ArrClient arrClient = GetClient(instanceType);
|
||||||
|
|
||||||
await _arrArrQueueIterator.Iterate(arrClient, instance, async items =>
|
await _arrArrQueueIterator.Iterate(arrClient, instance, async items =>
|
||||||
@@ -93,34 +51,12 @@ public sealed class QueueCleaner : IDisposable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsToBeRefreshed.Add(GetRecordId(instanceType, record));
|
itemsToBeRefreshed.Add(GetRecordSearchItem(instanceType, record));
|
||||||
|
|
||||||
await arrClient.DeleteQueueItemAsync(instance, record);
|
await arrClient.DeleteQueueItemAsync(instance, record);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await arrClient.RefreshItemsAsync(instance, itemsToBeRefreshed);
|
await arrClient.RefreshItemsAsync(instance, GetConfig(instanceType), itemsToBeRefreshed);
|
||||||
}
|
|
||||||
|
|
||||||
private ArrClient GetClient(InstanceType type) =>
|
|
||||||
type switch
|
|
||||||
{
|
|
||||||
InstanceType.Sonarr => _sonarrClient,
|
|
||||||
InstanceType.Radarr => _radarrClient,
|
|
||||||
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
|
||||||
};
|
|
||||||
|
|
||||||
private int GetRecordId(InstanceType type, QueueRecord record) =>
|
|
||||||
type switch
|
|
||||||
{
|
|
||||||
// TODO add episode id
|
|
||||||
InstanceType.Sonarr => record.SeriesId,
|
|
||||||
InstanceType.Radarr => record.MovieId,
|
|
||||||
_ => throw new NotImplementedException($"instance type {type} is not yet supported")
|
|
||||||
};
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_downloadService.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
{
|
|
||||||
"file": 1,
|
|
||||||
"format": 1
|
|
||||||
}{
|
|
||||||
"add_paused": false,
|
|
||||||
"allow_remote": false,
|
|
||||||
"auto_manage_prefer_seeds": false,
|
|
||||||
"auto_managed": true,
|
|
||||||
"cache_expiry": 60,
|
|
||||||
"cache_size": 512,
|
|
||||||
"copy_torrent_file": false,
|
|
||||||
"daemon_port": 58846,
|
|
||||||
"del_copy_torrent_file": false,
|
|
||||||
"dht": true,
|
|
||||||
"dont_count_slow_torrents": false,
|
|
||||||
"download_location": "/downloads",
|
|
||||||
"download_location_paths_list": [],
|
|
||||||
"enabled_plugins": [
|
|
||||||
"Label"
|
|
||||||
],
|
|
||||||
"enc_in_policy": 1,
|
|
||||||
"enc_level": 2,
|
|
||||||
"enc_out_policy": 1,
|
|
||||||
"geoip_db_location": "/usr/share/GeoIP/GeoIP.dat",
|
|
||||||
"ignore_limits_on_local_network": true,
|
|
||||||
"info_sent": 0.0,
|
|
||||||
"listen_interface": "",
|
|
||||||
"listen_ports": [
|
|
||||||
6882,
|
|
||||||
6882
|
|
||||||
],
|
|
||||||
"listen_random_port": null,
|
|
||||||
"listen_reuse_port": true,
|
|
||||||
"listen_use_sys_port": false,
|
|
||||||
"lsd": true,
|
|
||||||
"max_active_downloading": 3,
|
|
||||||
"max_active_limit": 8,
|
|
||||||
"max_active_seeding": 5,
|
|
||||||
"max_connections_global": 200,
|
|
||||||
"max_connections_per_second": 20,
|
|
||||||
"max_connections_per_torrent": -1,
|
|
||||||
"max_download_speed": -1.0,
|
|
||||||
"max_download_speed_per_torrent": -1,
|
|
||||||
"max_half_open_connections": 50,
|
|
||||||
"max_upload_slots_global": 4,
|
|
||||||
"max_upload_slots_per_torrent": -1,
|
|
||||||
"max_upload_speed": -1.0,
|
|
||||||
"max_upload_speed_per_torrent": -1,
|
|
||||||
"move_completed": false,
|
|
||||||
"move_completed_path": "/downloads",
|
|
||||||
"move_completed_paths_list": [],
|
|
||||||
"natpmp": true,
|
|
||||||
"new_release_check": true,
|
|
||||||
"outgoing_interface": "",
|
|
||||||
"outgoing_ports": [
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"path_chooser_accelerator_string": "Tab",
|
|
||||||
"path_chooser_auto_complete_enabled": true,
|
|
||||||
"path_chooser_max_popup_rows": 20,
|
|
||||||
"path_chooser_show_chooser_button_on_localhost": true,
|
|
||||||
"path_chooser_show_hidden_files": false,
|
|
||||||
"peer_tos": "0x00",
|
|
||||||
"plugins_location": "/config/plugins",
|
|
||||||
"pre_allocate_storage": false,
|
|
||||||
"prioritize_first_last_pieces": false,
|
|
||||||
"proxy": {
|
|
||||||
"anonymous_mode": false,
|
|
||||||
"force_proxy": false,
|
|
||||||
"hostname": "",
|
|
||||||
"password": "",
|
|
||||||
"port": 8080,
|
|
||||||
"proxy_hostnames": true,
|
|
||||||
"proxy_peer_connections": true,
|
|
||||||
"proxy_tracker_connections": true,
|
|
||||||
"type": 0,
|
|
||||||
"username": ""
|
|
||||||
},
|
|
||||||
"queue_new_to_top": false,
|
|
||||||
"random_outgoing_ports": true,
|
|
||||||
"random_port": false,
|
|
||||||
"rate_limit_ip_overhead": true,
|
|
||||||
"remove_seed_at_ratio": false,
|
|
||||||
"seed_time_limit": 180,
|
|
||||||
"seed_time_ratio_limit": 7.0,
|
|
||||||
"send_info": false,
|
|
||||||
"sequential_download": false,
|
|
||||||
"share_ratio_limit": 2.0,
|
|
||||||
"shared": false,
|
|
||||||
"stop_seed_at_ratio": false,
|
|
||||||
"stop_seed_ratio": 2.0,
|
|
||||||
"super_seeding": false,
|
|
||||||
"torrentfiles_location": "/config/torrents",
|
|
||||||
"upnp": true,
|
|
||||||
"utpex": true
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"file": 1,
|
|
||||||
"format": 1
|
|
||||||
}{
|
|
||||||
"labels": {
|
|
||||||
"radarr": {
|
|
||||||
"apply_max": false,
|
|
||||||
"apply_move_completed": false,
|
|
||||||
"apply_queue": false,
|
|
||||||
"auto_add": false,
|
|
||||||
"auto_add_trackers": [],
|
|
||||||
"is_auto_managed": false,
|
|
||||||
"max_connections": -1,
|
|
||||||
"max_download_speed": -1,
|
|
||||||
"max_upload_slots": -1,
|
|
||||||
"max_upload_speed": -1,
|
|
||||||
"move_completed": false,
|
|
||||||
"move_completed_path": "",
|
|
||||||
"prioritize_first_last": false,
|
|
||||||
"remove_at_ratio": false,
|
|
||||||
"stop_at_ratio": false,
|
|
||||||
"stop_ratio": 2.0
|
|
||||||
},
|
|
||||||
"tv-sonarr": {
|
|
||||||
"apply_max": false,
|
|
||||||
"apply_move_completed": false,
|
|
||||||
"apply_queue": false,
|
|
||||||
"auto_add": false,
|
|
||||||
"auto_add_trackers": [],
|
|
||||||
"is_auto_managed": false,
|
|
||||||
"max_connections": -1,
|
|
||||||
"max_download_speed": -1,
|
|
||||||
"max_upload_slots": -1,
|
|
||||||
"max_upload_speed": -1,
|
|
||||||
"move_completed": false,
|
|
||||||
"move_completed_path": "",
|
|
||||||
"prioritize_first_last": false,
|
|
||||||
"remove_at_ratio": false,
|
|
||||||
"stop_at_ratio": false,
|
|
||||||
"stop_ratio": 2.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"torrent_labels": {
|
|
||||||
"59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c": "tv-sonarr",
|
|
||||||
"5a31d5f1689f5f45fd85c275a37acd2c7b82fde1": "tv-sonarr",
|
|
||||||
"6c890ff85b5317d5df291c3c23a782774e10e6fe": "radarr",
|
|
||||||
"a4a1d1dd1db25763caa8f5e4d25ad72ef304094b": "radarr",
|
|
||||||
"b72541215214be2a1d96ef6b29ca1305f5e5e1f6": "tv-sonarr"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -16,571 +16,6 @@
|
|||||||
"pwd_sha1": "3ac8756d294abe4f6c9dfa084b7fc2c84ce32f68",
|
"pwd_sha1": "3ac8756d294abe4f6c9dfa084b7fc2c84ce32f68",
|
||||||
"session_timeout": 3600,
|
"session_timeout": 3600,
|
||||||
"sessions": {
|
"sessions": {
|
||||||
"00390c773fafe30cb393f53a920b48ac353b58ca27ac9ed64a1cbc61d5026677": {
|
|
||||||
"expires": 1731936939.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0379df23e58eb57a0ec781168c5acb1527be9ce1dc48a6dec201905358dbedd8": {
|
|
||||||
"expires": 1731665164.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"03d38494090a758cbe3ecc1e8a004986528297c7200e58b36649e197276c95e3": {
|
|
||||||
"expires": 1731718770.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"03fe5879beed5c299cd18472b64d31c4c610cd413059d7582312b002bb0eef03": {
|
|
||||||
"expires": 1731689401.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"05c8e71ff1e411beb45e278e786fbde8c893854e2906f3111dd48f943082eba5": {
|
|
||||||
"expires": 1731593711.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0cce3c4e10dcdebd921e19905c9ce3c162cefafadbf35b3c64a1932860af0e7d": {
|
|
||||||
"expires": 1731721234.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0d5bce647f6368877290f7be8a0f63f070039dd76027158278143b2ea6078a42": {
|
|
||||||
"expires": 1731665495.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0f95357a4b9994584b429a5facaad735bc1e0adb0f994b7fad82318f589de991": {
|
|
||||||
"expires": 1731718993.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"15c8cf06252ea0039bc2569d4121378baa3287594f9148d4fb26e999966e5538": {
|
|
||||||
"expires": 1731714201.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"15d5edb14093bb821dbef9080853e00f969860add39bd21e301172ae911713b1": {
|
|
||||||
"expires": 1731665102.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"17f7a731a26bdde434e8f4edb6043c4699efa29b982ad1f5df26676747b400a7": {
|
|
||||||
"expires": 1731658344.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1800115899b60e88b29483c4656f9c56c58d38c008d96149bb70fb5e9d26a10c": {
|
|
||||||
"expires": 1731939230.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1970022246cbb41f07d1242920163980e93e4e96f11864ffcf047c8cb5cf9908": {
|
|
||||||
"expires": 1731706575.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"19daad7642cf7f056083ff2868c9565ff8b2f6750eae91d5b235989089239bdc": {
|
|
||||||
"expires": 1731689407.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1b525ddd164c645ec47f1b2b58044cdf32f90800bc1973afb7b56a5814b813da": {
|
|
||||||
"expires": 1731711800.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1da54bf8d0c73023d11e1cb91586b088898ef5a37d146c108bfe2a9633499b63": {
|
|
||||||
"expires": 1731693609.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2095c759cb9b9ce96ab3bf3f07301e8dd71de75aeed2d4db957d2227adbc56f5": {
|
|
||||||
"expires": 1731664924.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2185646cfe4fa4f9892ea3df734b02b31ab7dfeb0f0868a6c730f04328b1a87a": {
|
|
||||||
"expires": 1731716848.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2270be625fa3bf61e919e8c495bc6c7868e907709e6b06c533727d0469df61c9": {
|
|
||||||
"expires": 1731714357.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2438a71a5850697dfb99ba24afa21f82a99ca32bf59a05f5dbd8c0f8bf645e4f": {
|
|
||||||
"expires": 1731691862.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"24930581ae0aa9a0e0a520f3cccfb71f50308be43b15cb5b0fb3404a7d9a8a2f": {
|
|
||||||
"expires": 1731617366.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"28920404a5f4638ebcdb4eb4addad4db19ee0bdd8505457cce7b6f81ba06b363": {
|
|
||||||
"expires": 1731712508.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"29f26fc40c0be11ba12209d142e8a662ad3c5f58f4c9e2a4dcb9bf81a9eac0ef": {
|
|
||||||
"expires": 1731692292.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2b4e3dc65d727790957c28d570f474ef0ffacc98bf1372b11ef4c2eacfde585c": {
|
|
||||||
"expires": 1731711706.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2c1d2ff923df8718a46c89e575a2ebbcede10f9c585e5fe2ddb3a4a43ddabafa": {
|
|
||||||
"expires": 1731719810.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2c9235163d9dc1af694609caf5624465e872a9a9efaae9c5dd7de97190911970": {
|
|
||||||
"expires": 1731592559.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2ee0d1c4f504c080441ae4a8e61546077405ef9dce3ec291923d761b69f69586": {
|
|
||||||
"expires": 1731659695.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"317679509ed59022afd20bd8a891cc759fadd7ca9c85c88ac0b05cf9b9ea1791": {
|
|
||||||
"expires": 1731716722.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"31b14f8de2ba1de58011ecf8dfe7f8681ae4af543928f3903a9c080374a7fb08": {
|
|
||||||
"expires": 1731692998.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"337641d938548a3261f67b1b1e295e8d09b248d2c7358a84a2914c803e2c9827": {
|
|
||||||
"expires": 1731719800.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"33b864b8ab214816273040e0456297da90a7f2e5bf352368f906e64cc363ccdf": {
|
|
||||||
"expires": 1731659084.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"36d3798ad875c70807a02eac3b7fd4279550cb9dfa6ccb530b66c74f0d577a52": {
|
|
||||||
"expires": 1731939210.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3b14137d9b57080d81e0a4532ad703dc91e2542be56415f58feca56231851eb0": {
|
|
||||||
"expires": 1731714081.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3b6c35a4cde25fa4a846a3df5d41562e798089db25eb21b9363cccdba2a3e093": {
|
|
||||||
"expires": 1731939483.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3d7bf91cb3e15eb82297ab54bae4cc6e06a42b54679e5f963a580fdf0d4bcf57": {
|
|
||||||
"expires": 1731719791.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3e7234797204e1672caf4b5b5ef450898f931ee48dcffae0b3b89e138434c036": {
|
|
||||||
"expires": 1731723178.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3e79e3bd1e01d10a6017e61b152151150deeb5185781a6325cd0aa4b9bfec47d": {
|
|
||||||
"expires": 1731719395.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3e9020e47c34087dc02ff6a5b396b7f338cc3249c9812e320490c33d9e7ce245": {
|
|
||||||
"expires": 1731719545.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"44c3d9b212384742b7e0ac2a8c9a2bb48cf146a403ba01b6137051f54953a38a": {
|
|
||||||
"expires": 1731938866.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"470c34122403b8dec692f9079e892ca92d8bf13cdd6c2814b6d829996e5f8b67": {
|
|
||||||
"expires": 1731664624.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"4a47ca4a624fff02d9970d7e8a341ec08b8076cade949348795e45002c18556d": {
|
|
||||||
"expires": 1731718983.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"4e88bdb753b76965909d1a2eabcc6ef5c12dab11cb0f32185506a19192f9cae2": {
|
|
||||||
"expires": 1731935979.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"51ce01dfe69d8ae6afb019a05046154f4b51ef64569e3424e78a80957a11ba8c": {
|
|
||||||
"expires": 1731693589.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"538a9fd86a56727c571da77b70353ac0fa5568442ea17d69a817a579d37679ac": {
|
|
||||||
"expires": 1731939264.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"566fe0345702f6e2f30effef42ef664c46ac0e7f21aa3d2b414a1f290230fba6": {
|
|
||||||
"expires": 1731664385.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"57df9cb450fd2dd9717b9ab03bbce3492603188c11f5e83af8d1bb38ab36ed01": {
|
|
||||||
"expires": 1731938872.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"587362acb21285d814a9861a93b3f0e017ee9efb1bfe63343c13c09e7ea80f91": {
|
|
||||||
"expires": 1731716759.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"58f7b011411dbad96eadc573eb164e1cdb6f96d52febbb8d4adb2cdda6ed80ef": {
|
|
||||||
"expires": 1731936289.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5cb4123b4b425f1d8a6accf4c02386feea7f20bf6171291f715c2cd99bfb02c0": {
|
|
||||||
"expires": 1731665482.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5d9819c229e5f79b767a88db9c78b24998b424a66e8a7ba0039553b7c54051ec": {
|
|
||||||
"expires": 1731617370.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5e6cb57d0f9d97aa3ed75fefd40c8060a085c04727886511f0e7db126b203d43": {
|
|
||||||
"expires": 1731721196.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5e74fdaca0c6c7e7afec714793677646ff89d00ae35908a7125d6cf50ea0702c": {
|
|
||||||
"expires": 1731603184.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5fbe1b057086c9f17b5a4d7d9fd9f41eab0305ea89b5b1de2ca633b0b38aab50": {
|
|
||||||
"expires": 1731937913.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"644aae458bd0a092fe342d1f020d4b7eccff9cf6cfc0677fe0d9531754edaff0": {
|
|
||||||
"expires": 1731692249.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"653493a22274078fc44d52acb337b56bcd4084de2f0a1b6b79be186550a30cb3": {
|
|
||||||
"expires": 1731591365.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"66618b25addefa9f12946c5afb65e8d690c5a871fff3d03fb796751a9eee0d41": {
|
|
||||||
"expires": 1731591683.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"684613e83cfde35d79917108a4091de4c585e1ff627eee0d904920759dc3ea53": {
|
|
||||||
"expires": 1731712313.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"68ecec112a27957857622d4c0ba02824f5e03981db6f51fa01c0be8ac893c6f1": {
|
|
||||||
"expires": 1731591683.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"69d6aa5b5eae433a357dc92fe12bbfb7fd29629425f0edbe55e0ac4e8df112aa": {
|
|
||||||
"expires": 1731718574.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"6ade7fdf14f334646e2ad2f6b627146a69e0b896d0930a996b4f9df7bc4cf28e": {
|
|
||||||
"expires": 1731939300.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"6c19cb32acd64f6c1543bf07dba3aee5ca5d3ba71f754e1e95acc3da5dc6aa27": {
|
|
||||||
"expires": 1731718629.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"71c8d2cf33b22285f43c5855bdabd9ea25a18b177cfe28056c8bed41776d0ced": {
|
|
||||||
"expires": 1731664874.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"745592d9be94482df01bc76010f58844175050e2bb7c0974de4e1f852a589554": {
|
|
||||||
"expires": 1731689116.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"82ee9a0b4fde768d0580e85633012235fdf4683c0115ec121ba17282075483e7": {
|
|
||||||
"expires": 1731708818.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8346f9faa70bd614131a9115bcb33168ae9af221be0270e967c01e9c1c58129e": {
|
|
||||||
"expires": 1731693061.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"861dbf07e0df2c29fedc8fcd5a346b4dcb1a0ece0f741befc3c72d3fadc82268": {
|
|
||||||
"expires": 1731722565.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"870afa2ad6a0832fb19680b3bbf0bfd99de377c9cbaaff3cf6bf5a633fe541c3": {
|
|
||||||
"expires": 1731933969.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"88be8381b68afe4c56b949c2588dec6b57f2dbe5ae20faacb06c37b7cbdc8a8b": {
|
|
||||||
"expires": 1731718780.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8a010b80566da20d356b77df700e2444292eea50a928eb7613bc874462436f36": {
|
|
||||||
"expires": 1731602951.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8be44833b5b32f8ed1eb2e6dba5ec7aa49fc4307dbd18d03859c96d227a9058b": {
|
|
||||||
"expires": 1731658179.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8d5c74abebafb8bfc72a3c33c17195d8e6a52c4505b1ccff9eda1a81f9a74ef7": {
|
|
||||||
"expires": 1731939423.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8e2e998fabc8429ef4ba385a4d4ed401fbe2508192b65dc72280cdfc086948e0": {
|
|
||||||
"expires": 1731692291.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8e8ed10c9cb8ced98d7736a81b68990fff019ca2b32dd9093209b132906b68c3": {
|
|
||||||
"expires": 1731692267.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8fbfaad4b9fc517f848b6f052be51b3a2cd494247078bf053460f8b80e53065a": {
|
|
||||||
"expires": 1731595963.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8fddab944e5a0d45750d5da5b78230387228e12f27ad040316394a4d6b166b5d": {
|
|
||||||
"expires": 1731939300.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9364816c6b2e739b647c43ad27b2d52593c2d59a5181aefcda12d04ea31d9fc6": {
|
|
||||||
"expires": 1731718765.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9a29ef6e50f415e5ccd36140eeab85a4409ae271470c1d51a32e0791c75ea588": {
|
|
||||||
"expires": 1731658513.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9ac87d5bf293ab9dc0bb5dcefe1e65eb7e166c4b3f5a162529683d5d866d7f9f": {
|
|
||||||
"expires": 1731937503.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9b266dafeef7c956bb4d9e987791975b64e7333de860a0d9173e211524cc8540": {
|
|
||||||
"expires": 1731591553.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9c03a8e39a5ff42c889c73ad9d4d5d84e76747322dfdf714ccb74a0d37923682": {
|
|
||||||
"expires": 1731719394.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9d1df8609ca6ada923c85b2721c0b3e606b92372478b5ca943cb52a7b8886951": {
|
|
||||||
"expires": 1731939230.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9f9de499b714f5c7dcb873f1141ad463a34e7c34eaa62cc988167505e4f5ac54": {
|
|
||||||
"expires": 1731939220.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a484b99afebac4ce74b6f52cad90448496fb80f2d8756e503776072739748a57": {
|
|
||||||
"expires": 1731938872.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a48e673783b5c56edfd5b5bc73b1f9c527dc1ca6da3aef82f61692b40095b39c": {
|
|
||||||
"expires": 1731939445.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a7ba418572d761f89d13fd7a11d682a3c821406ccd29f3e6d683966d0fb3d3ab": {
|
|
||||||
"expires": 1731720041.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a7cc53afae41ab5e800893c7271acf272f34b604f7e1ace8c3f0232606d01e2b": {
|
|
||||||
"expires": 1731719810.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a8c19794aaac3afd733588d42d7ffddc5f8de336d4bd8aa5cf9c1ff36cc9d590": {
|
|
||||||
"expires": 1731933957.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"aa301c95f4890c38baa814503e940f28143304ad55dbfaea2db8aaec90169031": {
|
|
||||||
"expires": 1731599734.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ac98337f159cdc03b4865b80c78d53ab39502291fa19cc0b60233209e1d92bb7": {
|
|
||||||
"expires": 1731689363.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"adf9d353a020d93e54b32b14c28525a7e6f33fd735144d7b778a4e517192b7c5": {
|
|
||||||
"expires": 1731691941.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ae14e4adfe0504f8dfb3fcb575606e67f57e950cdf88f00ca76e0af215c2b413": {
|
|
||||||
"expires": 1731935980.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ae48fe0ecea0704704bc6327b6bf3258de377b08fa4d76b4f69b8470852960c6": {
|
|
||||||
"expires": 1731591544.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ae967211fa06ac26d96e3207e67c136efc6367acc92d608e52e1f204b5bd3da4": {
|
|
||||||
"expires": 1731664535.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"b0f55c6cad8bc2230754fd1bbeebfedd31ba244eac96e8d02be6d6a33b542b4f": {
|
|
||||||
"expires": 1731592529.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"b689520f2e3ccf2164da5dba4bac111d4cbc6d3bdcf44361127baa0068623cd0": {
|
|
||||||
"expires": 1731717015.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"bb92c09264296d8c12fac5d2abed2224b25a85d5a46d87f4a4351da76d00566e": {
|
|
||||||
"expires": 1731720040.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"bbba0f20f9c1639ebfcaedb54a87d4b968bc953f72b06105f6d995f5eee9bff7": {
|
|
||||||
"expires": 1731591455.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"c1a50b58a364f294755666d0e748ba2a06fbe55de758453180920774525214c8": {
|
|
||||||
"expires": 1731719336.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"c6af2f6a6627cbbf18696f1b8b904346726800d05b137d251ee28b699fbd858c": {
|
|
||||||
"expires": 1731665294.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"c86527a0aae330a71fc324a233ad876d420a54d387794374d126e7d6a0f19f92": {
|
|
||||||
"expires": 1731692356.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"cd65741faf869e193c4e2a51e9454cf88598f58987b2ce559ef3d6bfa98e7605": {
|
|
||||||
"expires": 1731591572.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"cdb4f2d14ab7de91b8be1261be40bf59f2bfa0e5e6327166ffa08cfa74eb357a": {
|
|
||||||
"expires": 1731692237.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d0f2c477a500bffcdcfbc56907df1b322200d9f7a801285bbe5440e5bca5e8c4": {
|
|
||||||
"expires": 1731711859.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d3a9186f1c2c81d8085d86c09ddd4fc5e205f61f25f3cabbc1ee379e52304c77": {
|
|
||||||
"expires": 1731718780.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d74ba4649e1a5a098d42c2f62472c94d4484f43b0979cbbd48b464ac5f20e49b": {
|
|
||||||
"expires": 1731716625.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d769dedd53059553cb54961641c0cbdf818533db06764fbcd5557a7200247c28": {
|
|
||||||
"expires": 1731718992.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d95d211a839a71c0bb00d2504f18deb81af2d0ab7183478543a5d28654a37197": {
|
|
||||||
"expires": 1731658629.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"da6f45956cf8b0b4a5552166dfe1372437b25179e0d24bfbeaba745d28febb53": {
|
|
||||||
"expires": 1731937514.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"df84bacd66f8dbf2d1f150d839029e11b3ce56c0536183fd33656685bd446c44": {
|
|
||||||
"expires": 1731719340.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"e8be12b35aa13a67bc7d3332d373b4117493319625c1ed87e3335ab8d09b9054": {
|
|
||||||
"expires": 1731686353.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"f04dce449eb7dc69f7973906564c4aa224ff20595a3f2a3bfedbcd23ffaa9117": {
|
|
||||||
"expires": 1731658418.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"f4472d087a796ef1f431c44d4f6e9d46ecfe88acce21368a7d85fc31e1efc1e9": {
|
|
||||||
"expires": 1731711611.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"f62a451ea2933e56ff9dae2299e374477adb54d8c8285ea59d2ab15b9a80bd13": {
|
|
||||||
"expires": 1731712500.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"show_session_speed": false,
|
"show_session_speed": false,
|
||||||
"show_sidebar": true,
|
"show_sidebar": true,
|
||||||
|
|||||||
@@ -1,480 +0,0 @@
|
|||||||
{
|
|
||||||
"file": 2,
|
|
||||||
"format": 1
|
|
||||||
}{
|
|
||||||
"base": "/",
|
|
||||||
"cert": "ssl/daemon.cert",
|
|
||||||
"default_daemon": "",
|
|
||||||
"enabled_plugins": [],
|
|
||||||
"first_login": false,
|
|
||||||
"https": false,
|
|
||||||
"interface": "0.0.0.0",
|
|
||||||
"language": "",
|
|
||||||
"pkey": "ssl/daemon.pkey",
|
|
||||||
"port": 8112,
|
|
||||||
"pwd_salt": "2bc0ed67acc6876dda1a1632594090478fdeab60",
|
|
||||||
"pwd_sha1": "3ac8756d294abe4f6c9dfa084b7fc2c84ce32f68",
|
|
||||||
"session_timeout": 3600,
|
|
||||||
"sessions": {
|
|
||||||
"0379df23e58eb57a0ec781168c5acb1527be9ce1dc48a6dec201905358dbedd8": {
|
|
||||||
"expires": 1731665164.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"03d38494090a758cbe3ecc1e8a004986528297c7200e58b36649e197276c95e3": {
|
|
||||||
"expires": 1731718770.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"03fe5879beed5c299cd18472b64d31c4c610cd413059d7582312b002bb0eef03": {
|
|
||||||
"expires": 1731689401.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"05c8e71ff1e411beb45e278e786fbde8c893854e2906f3111dd48f943082eba5": {
|
|
||||||
"expires": 1731593711.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0cce3c4e10dcdebd921e19905c9ce3c162cefafadbf35b3c64a1932860af0e7d": {
|
|
||||||
"expires": 1731721234.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0d5bce647f6368877290f7be8a0f63f070039dd76027158278143b2ea6078a42": {
|
|
||||||
"expires": 1731665495.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"0f95357a4b9994584b429a5facaad735bc1e0adb0f994b7fad82318f589de991": {
|
|
||||||
"expires": 1731718993.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"15c8cf06252ea0039bc2569d4121378baa3287594f9148d4fb26e999966e5538": {
|
|
||||||
"expires": 1731714201.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"15d5edb14093bb821dbef9080853e00f969860add39bd21e301172ae911713b1": {
|
|
||||||
"expires": 1731665102.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"17f7a731a26bdde434e8f4edb6043c4699efa29b982ad1f5df26676747b400a7": {
|
|
||||||
"expires": 1731658344.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1970022246cbb41f07d1242920163980e93e4e96f11864ffcf047c8cb5cf9908": {
|
|
||||||
"expires": 1731706575.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"19daad7642cf7f056083ff2868c9565ff8b2f6750eae91d5b235989089239bdc": {
|
|
||||||
"expires": 1731689407.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1b525ddd164c645ec47f1b2b58044cdf32f90800bc1973afb7b56a5814b813da": {
|
|
||||||
"expires": 1731711800.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"1da54bf8d0c73023d11e1cb91586b088898ef5a37d146c108bfe2a9633499b63": {
|
|
||||||
"expires": 1731693609.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2095c759cb9b9ce96ab3bf3f07301e8dd71de75aeed2d4db957d2227adbc56f5": {
|
|
||||||
"expires": 1731664924.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2185646cfe4fa4f9892ea3df734b02b31ab7dfeb0f0868a6c730f04328b1a87a": {
|
|
||||||
"expires": 1731716848.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2270be625fa3bf61e919e8c495bc6c7868e907709e6b06c533727d0469df61c9": {
|
|
||||||
"expires": 1731714357.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2438a71a5850697dfb99ba24afa21f82a99ca32bf59a05f5dbd8c0f8bf645e4f": {
|
|
||||||
"expires": 1731691862.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"24930581ae0aa9a0e0a520f3cccfb71f50308be43b15cb5b0fb3404a7d9a8a2f": {
|
|
||||||
"expires": 1731617366.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"28920404a5f4638ebcdb4eb4addad4db19ee0bdd8505457cce7b6f81ba06b363": {
|
|
||||||
"expires": 1731712508.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"29f26fc40c0be11ba12209d142e8a662ad3c5f58f4c9e2a4dcb9bf81a9eac0ef": {
|
|
||||||
"expires": 1731692292.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2b4e3dc65d727790957c28d570f474ef0ffacc98bf1372b11ef4c2eacfde585c": {
|
|
||||||
"expires": 1731711706.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2c1d2ff923df8718a46c89e575a2ebbcede10f9c585e5fe2ddb3a4a43ddabafa": {
|
|
||||||
"expires": 1731719810.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2c9235163d9dc1af694609caf5624465e872a9a9efaae9c5dd7de97190911970": {
|
|
||||||
"expires": 1731592559.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"2ee0d1c4f504c080441ae4a8e61546077405ef9dce3ec291923d761b69f69586": {
|
|
||||||
"expires": 1731659695.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"317679509ed59022afd20bd8a891cc759fadd7ca9c85c88ac0b05cf9b9ea1791": {
|
|
||||||
"expires": 1731716722.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"31b14f8de2ba1de58011ecf8dfe7f8681ae4af543928f3903a9c080374a7fb08": {
|
|
||||||
"expires": 1731692998.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"337641d938548a3261f67b1b1e295e8d09b248d2c7358a84a2914c803e2c9827": {
|
|
||||||
"expires": 1731719800.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"33b864b8ab214816273040e0456297da90a7f2e5bf352368f906e64cc363ccdf": {
|
|
||||||
"expires": 1731659084.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3b14137d9b57080d81e0a4532ad703dc91e2542be56415f58feca56231851eb0": {
|
|
||||||
"expires": 1731714081.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3d7bf91cb3e15eb82297ab54bae4cc6e06a42b54679e5f963a580fdf0d4bcf57": {
|
|
||||||
"expires": 1731719791.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3e7234797204e1672caf4b5b5ef450898f931ee48dcffae0b3b89e138434c036": {
|
|
||||||
"expires": 1731723178.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3e79e3bd1e01d10a6017e61b152151150deeb5185781a6325cd0aa4b9bfec47d": {
|
|
||||||
"expires": 1731719395.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"3e9020e47c34087dc02ff6a5b396b7f338cc3249c9812e320490c33d9e7ce245": {
|
|
||||||
"expires": 1731719545.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"470c34122403b8dec692f9079e892ca92d8bf13cdd6c2814b6d829996e5f8b67": {
|
|
||||||
"expires": 1731664624.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"4a47ca4a624fff02d9970d7e8a341ec08b8076cade949348795e45002c18556d": {
|
|
||||||
"expires": 1731718983.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"51ce01dfe69d8ae6afb019a05046154f4b51ef64569e3424e78a80957a11ba8c": {
|
|
||||||
"expires": 1731693589.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"566fe0345702f6e2f30effef42ef664c46ac0e7f21aa3d2b414a1f290230fba6": {
|
|
||||||
"expires": 1731664385.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"587362acb21285d814a9861a93b3f0e017ee9efb1bfe63343c13c09e7ea80f91": {
|
|
||||||
"expires": 1731716759.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5cb4123b4b425f1d8a6accf4c02386feea7f20bf6171291f715c2cd99bfb02c0": {
|
|
||||||
"expires": 1731665482.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5d9819c229e5f79b767a88db9c78b24998b424a66e8a7ba0039553b7c54051ec": {
|
|
||||||
"expires": 1731617370.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5e6cb57d0f9d97aa3ed75fefd40c8060a085c04727886511f0e7db126b203d43": {
|
|
||||||
"expires": 1731721196.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"5e74fdaca0c6c7e7afec714793677646ff89d00ae35908a7125d6cf50ea0702c": {
|
|
||||||
"expires": 1731603184.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"644aae458bd0a092fe342d1f020d4b7eccff9cf6cfc0677fe0d9531754edaff0": {
|
|
||||||
"expires": 1731692249.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"653493a22274078fc44d52acb337b56bcd4084de2f0a1b6b79be186550a30cb3": {
|
|
||||||
"expires": 1731591365.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"66618b25addefa9f12946c5afb65e8d690c5a871fff3d03fb796751a9eee0d41": {
|
|
||||||
"expires": 1731591683.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"684613e83cfde35d79917108a4091de4c585e1ff627eee0d904920759dc3ea53": {
|
|
||||||
"expires": 1731712313.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"68ecec112a27957857622d4c0ba02824f5e03981db6f51fa01c0be8ac893c6f1": {
|
|
||||||
"expires": 1731591683.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"69d6aa5b5eae433a357dc92fe12bbfb7fd29629425f0edbe55e0ac4e8df112aa": {
|
|
||||||
"expires": 1731718574.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"6c19cb32acd64f6c1543bf07dba3aee5ca5d3ba71f754e1e95acc3da5dc6aa27": {
|
|
||||||
"expires": 1731718629.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"71c8d2cf33b22285f43c5855bdabd9ea25a18b177cfe28056c8bed41776d0ced": {
|
|
||||||
"expires": 1731664874.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"745592d9be94482df01bc76010f58844175050e2bb7c0974de4e1f852a589554": {
|
|
||||||
"expires": 1731689116.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"82ee9a0b4fde768d0580e85633012235fdf4683c0115ec121ba17282075483e7": {
|
|
||||||
"expires": 1731708818.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8346f9faa70bd614131a9115bcb33168ae9af221be0270e967c01e9c1c58129e": {
|
|
||||||
"expires": 1731693061.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"861dbf07e0df2c29fedc8fcd5a346b4dcb1a0ece0f741befc3c72d3fadc82268": {
|
|
||||||
"expires": 1731722565.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"88be8381b68afe4c56b949c2588dec6b57f2dbe5ae20faacb06c37b7cbdc8a8b": {
|
|
||||||
"expires": 1731718780.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8a010b80566da20d356b77df700e2444292eea50a928eb7613bc874462436f36": {
|
|
||||||
"expires": 1731602951.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8be44833b5b32f8ed1eb2e6dba5ec7aa49fc4307dbd18d03859c96d227a9058b": {
|
|
||||||
"expires": 1731658179.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8e2e998fabc8429ef4ba385a4d4ed401fbe2508192b65dc72280cdfc086948e0": {
|
|
||||||
"expires": 1731692291.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8e8ed10c9cb8ced98d7736a81b68990fff019ca2b32dd9093209b132906b68c3": {
|
|
||||||
"expires": 1731692267.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"8fbfaad4b9fc517f848b6f052be51b3a2cd494247078bf053460f8b80e53065a": {
|
|
||||||
"expires": 1731595963.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9364816c6b2e739b647c43ad27b2d52593c2d59a5181aefcda12d04ea31d9fc6": {
|
|
||||||
"expires": 1731718765.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9a29ef6e50f415e5ccd36140eeab85a4409ae271470c1d51a32e0791c75ea588": {
|
|
||||||
"expires": 1731658513.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9b266dafeef7c956bb4d9e987791975b64e7333de860a0d9173e211524cc8540": {
|
|
||||||
"expires": 1731591553.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"9c03a8e39a5ff42c889c73ad9d4d5d84e76747322dfdf714ccb74a0d37923682": {
|
|
||||||
"expires": 1731719394.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a7ba418572d761f89d13fd7a11d682a3c821406ccd29f3e6d683966d0fb3d3ab": {
|
|
||||||
"expires": 1731720041.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"a7cc53afae41ab5e800893c7271acf272f34b604f7e1ace8c3f0232606d01e2b": {
|
|
||||||
"expires": 1731719810.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"aa301c95f4890c38baa814503e940f28143304ad55dbfaea2db8aaec90169031": {
|
|
||||||
"expires": 1731599734.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ac98337f159cdc03b4865b80c78d53ab39502291fa19cc0b60233209e1d92bb7": {
|
|
||||||
"expires": 1731689363.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"adf9d353a020d93e54b32b14c28525a7e6f33fd735144d7b778a4e517192b7c5": {
|
|
||||||
"expires": 1731691941.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ae48fe0ecea0704704bc6327b6bf3258de377b08fa4d76b4f69b8470852960c6": {
|
|
||||||
"expires": 1731591544.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"ae967211fa06ac26d96e3207e67c136efc6367acc92d608e52e1f204b5bd3da4": {
|
|
||||||
"expires": 1731664535.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"b0f55c6cad8bc2230754fd1bbeebfedd31ba244eac96e8d02be6d6a33b542b4f": {
|
|
||||||
"expires": 1731592529.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"b689520f2e3ccf2164da5dba4bac111d4cbc6d3bdcf44361127baa0068623cd0": {
|
|
||||||
"expires": 1731717015.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"bb92c09264296d8c12fac5d2abed2224b25a85d5a46d87f4a4351da76d00566e": {
|
|
||||||
"expires": 1731720040.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"bbba0f20f9c1639ebfcaedb54a87d4b968bc953f72b06105f6d995f5eee9bff7": {
|
|
||||||
"expires": 1731591455.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"c1a50b58a364f294755666d0e748ba2a06fbe55de758453180920774525214c8": {
|
|
||||||
"expires": 1731719336.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"c6af2f6a6627cbbf18696f1b8b904346726800d05b137d251ee28b699fbd858c": {
|
|
||||||
"expires": 1731665294.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"c86527a0aae330a71fc324a233ad876d420a54d387794374d126e7d6a0f19f92": {
|
|
||||||
"expires": 1731692356.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"cd65741faf869e193c4e2a51e9454cf88598f58987b2ce559ef3d6bfa98e7605": {
|
|
||||||
"expires": 1731591572.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"cdb4f2d14ab7de91b8be1261be40bf59f2bfa0e5e6327166ffa08cfa74eb357a": {
|
|
||||||
"expires": 1731692237.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d0f2c477a500bffcdcfbc56907df1b322200d9f7a801285bbe5440e5bca5e8c4": {
|
|
||||||
"expires": 1731711859.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d3a9186f1c2c81d8085d86c09ddd4fc5e205f61f25f3cabbc1ee379e52304c77": {
|
|
||||||
"expires": 1731718780.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d74ba4649e1a5a098d42c2f62472c94d4484f43b0979cbbd48b464ac5f20e49b": {
|
|
||||||
"expires": 1731716625.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d769dedd53059553cb54961641c0cbdf818533db06764fbcd5557a7200247c28": {
|
|
||||||
"expires": 1731718992.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"d95d211a839a71c0bb00d2504f18deb81af2d0ab7183478543a5d28654a37197": {
|
|
||||||
"expires": 1731658629.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"df84bacd66f8dbf2d1f150d839029e11b3ce56c0536183fd33656685bd446c44": {
|
|
||||||
"expires": 1731719340.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"e8be12b35aa13a67bc7d3332d373b4117493319625c1ed87e3335ab8d09b9054": {
|
|
||||||
"expires": 1731686353.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"f04dce449eb7dc69f7973906564c4aa224ff20595a3f2a3bfedbcd23ffaa9117": {
|
|
||||||
"expires": 1731658418.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"f4472d087a796ef1f431c44d4f6e9d46ecfe88acce21368a7d85fc31e1efc1e9": {
|
|
||||||
"expires": 1731711611.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
},
|
|
||||||
"f62a451ea2933e56ff9dae2299e374477adb54d8c8285ea59d2ab15b9a80bd13": {
|
|
||||||
"expires": 1731712500.0,
|
|
||||||
"level": 10,
|
|
||||||
"login": "admin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"show_session_speed": false,
|
|
||||||
"show_sidebar": true,
|
|
||||||
"sidebar_multiple_filters": true,
|
|
||||||
"sidebar_show_zero": false,
|
|
||||||
"theme": "gray"
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<rss version="2.0">
|
<rss version="2.0">
|
||||||
<channel>
|
<channel>
|
||||||
<title>Test feed</title>
|
<title>Test feed</title>
|
||||||
<link>http://nginx/custom/radarr_bad_single.xml</link>
|
<link>http://nginx/custom/radarr.xml</link>
|
||||||
<description>
|
<description>
|
||||||
Test
|
Test
|
||||||
</description>
|
</description>
|
||||||
@@ -11,6 +11,17 @@
|
|||||||
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
||||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||||
<ttl>30</ttl>
|
<ttl>30</ttl>
|
||||||
|
<item>
|
||||||
|
<title>Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/radarr_bad_nested.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8927f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<title>The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX</title>
|
<title>The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX</title>
|
||||||
<description>Test</description>
|
<description>Test</description>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<rss version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>Test feed</title>
|
|
||||||
<link>http://nginx/custom/radarr_bad_nested.xml</link>
|
|
||||||
<description>
|
|
||||||
Test
|
|
||||||
</description>
|
|
||||||
<language>en-CA</language>
|
|
||||||
<copyright> Test </copyright>
|
|
||||||
<pubDate>Tue, 5 Nov 2024 22:02:13 -0400</pubDate>
|
|
||||||
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
|
||||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
|
||||||
<ttl>30</ttl>
|
|
||||||
<item>
|
|
||||||
<title>Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB</title>
|
|
||||||
<description>Test</description>
|
|
||||||
<size>4138858110</size>
|
|
||||||
<link>http://nginx/custom/radarr_bad_nested.torrent</link>
|
|
||||||
<guid isPermaLink="false">
|
|
||||||
174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
|
||||||
</guid>
|
|
||||||
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</rss>
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>Test feed</title>
|
||||||
|
<link>http://nginx/custom/sonarr.xml</link>
|
||||||
|
<description>
|
||||||
|
Test
|
||||||
|
</description>
|
||||||
|
<language>en-CA</language>
|
||||||
|
<copyright> Test </copyright>
|
||||||
|
<pubDate>Tue, 5 Nov 2024 22:02:13 -0400</pubDate>
|
||||||
|
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
||||||
|
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||||
|
<ttl>30</ttl>
|
||||||
|
<item>
|
||||||
|
<title>Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/sonarr_bad_nested.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f6f9057a23f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<title>Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/sonarr_bad_single.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f689057ac3f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<title>Top.Gear.S23E01.720p.x265.HDTV.HEVC.-.YSTEAM</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>magnet:?xt=urn:btih:cf82cf859b110af0ad3d94b846e006828417b193&dn=TPG.2301.720p.x265.yourserie.com.mkv</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f6f5057ac3f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<title>Top.Gear.S23E01.720p.x265.HDTV.HEVC.-.YSTEAM</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/sonarr_bad_stuck_stalled.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<title>Top.Gear.S23E02.720p.x265.HDTV.HEVC.-.YSTEAM</title>
|
||||||
|
<description>Test</description>
|
||||||
|
<size>4138858110</size>
|
||||||
|
<link>http://nginx/custom/sonarr_bad_nested_top.torrent</link>
|
||||||
|
<guid isPermaLink="false">
|
||||||
|
174674a88c8947f6f9057ac3f82efde384ed216cade43564ec450f2cb4677554
|
||||||
|
</guid>
|
||||||
|
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<rss version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>Test feed</title>
|
|
||||||
<link>http://nginx/custom/sonarr_bad_nested.xml</link>
|
|
||||||
<description>
|
|
||||||
Test
|
|
||||||
</description>
|
|
||||||
<language>en-CA</language>
|
|
||||||
<copyright> Test </copyright>
|
|
||||||
<pubDate>Tue, 5 Nov 2024 22:02:13 -0400</pubDate>
|
|
||||||
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
|
||||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
|
||||||
<ttl>30</ttl>
|
|
||||||
<item>
|
|
||||||
<title>Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG</title>
|
|
||||||
<description>Test</description>
|
|
||||||
<size>4138858110</size>
|
|
||||||
<link>http://nginx/custom/sonarr_bad_nested.torrent</link>
|
|
||||||
<guid isPermaLink="false">
|
|
||||||
174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
|
||||||
</guid>
|
|
||||||
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</rss>
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1732896923e4:infod5:filesld6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl9:test.zipxeed6:lengthi2604e4:pathl49:Top.Gear.S23E02.720p.x265.HDTV.HEVC.-.YSTEAM.zipxeee4:name44:Top.Gear.S23E02.720p.x265.HDTV.HEVC.-.YSTEAM12:piece lengthi262144e6:pieces20:w¤ŸÌ³RÇþ'6Fíoð}ä°ee
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<rss version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>Test feed</title>
|
|
||||||
<link>http://nginx/custom/sonarr_bad_single.xml</link>
|
|
||||||
<description>
|
|
||||||
Test
|
|
||||||
</description>
|
|
||||||
<language>en-CA</language>
|
|
||||||
<copyright> Test </copyright>
|
|
||||||
<pubDate>Tue, 5 Nov 2024 22:02:13 -0400</pubDate>
|
|
||||||
<lastBuildDate>Tue, 5 Nov 2024 22:02:13 -0400</lastBuildDate>
|
|
||||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
|
||||||
<ttl>30</ttl>
|
|
||||||
<item>
|
|
||||||
<title>Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX</title>
|
|
||||||
<description>Test</description>
|
|
||||||
<size>4138858110</size>
|
|
||||||
<link>http://nginx/custom/sonarr_bad_single.torrent</link>
|
|
||||||
<guid isPermaLink="false">
|
|
||||||
174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554
|
|
||||||
</guid>
|
|
||||||
<pubDate>Sat, 24 Sep 2022 22:02:13 -0300</pubDate>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</rss>
|
|
||||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,4 +1,5 @@
|
|||||||
b72541215214be2a1d96ef6b29ca1305f5e5e1f6
|
|
||||||
a4a1d1dd1db25763caa8f5e4d25ad72ef304094b
|
|
||||||
2b2ec156461d77bc48b8fe4d62cede50dcdff8e0
|
2b2ec156461d77bc48b8fe4d62cede50dcdff8e0
|
||||||
|
a4a1d1dd1db25763caa8f5e4d25ad72ef304094b
|
||||||
|
b72541215214be2a1d96ef6b29ca1305f5e5e1f6
|
||||||
59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c
|
59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c
|
||||||
|
11cece7f8721c484126b66f609d52738ff1bbf1e
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[Stats]
|
[Stats]
|
||||||
AllStats=@Variant(\0\0\0\x1c\0\0\0\x2\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0\x44\0L\0\0\0\x4\0\0\0\0\0Z\xd2\x1b\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0\x90\xf9\xfc)
|
AllStats=@Variant(\0\0\0\x1c\0\0\0\x2\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0\x44\0L\0\0\0\x4\0\0\0\0\0\x61\xc0\xdf\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0\x9b\xf9\x8a)
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1732896923e4:infod5:filesld6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl9:test.zipxeed6:lengthi2604e4:pathl49:Top.Gear.S23E02.720p.x265.HDTV.HEVC.-.YSTEAM.zipxeee4:name44:Top.Gear.S23E02.720p.x265.HDTV.HEVC.-.YSTEAM12:piece lengthi262144e6:pieces20:w¤ŸÌ³RÇþ'6Fíoð}ä°ee
|
||||||
@@ -1 +1 @@
|
|||||||
de6996481f4e318e7baff03b4043929c585a7c4e
|
cf82cf859b110af0ad3d94b846e006828417b193
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[Stats]
|
[Stats]
|
||||||
AllStats=@Variant(\0\0\0\x1c\0\0\0\x2\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0\x44\0L\0\0\0\x4\0\0\0\0\0\x1e\xc7?\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0+1q)
|
AllStats=@Variant(\0\0\0\x1c\0\0\0\x2\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0\x44\0L\0\0\0\x4\0\0\0\0\0!\x9d\x8e\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0.\xe6I)
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"update":{"sid":"3e34254f4ee14bf4bec2d0359c3f8ee4","did":"92eba3c5-a8d0-44d5-836d-25bc4aa81a85","init":true,"started":"2024-11-18T17:39:31.1848496+00:00","timestamp":"2024-11-18T17:39:31.1852902+00:00","seq":0,"duration":0,"errors":0,"attrs":{"release":"Radarr@5.14.0.9383-master","environment":"master"}}}
|
{"update":{"sid":"743459ae24ef4f4c8a85171b21fd99a8","did":"92eba3c5-a8d0-44d5-836d-25bc4aa81a85","init":true,"started":"2024-11-29T15:46:38.3721409+00:00","timestamp":"2024-11-29T15:46:38.3728803+00:00","seq":0,"duration":0,"errors":0,"attrs":{"release":"Radarr@5.14.0.9383-master","environment":"master"}}}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
145
|
144
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"update":{"sid":"8d0da7a51b3942d9802c68e3c503f356","did":"1df9f2cc-17dc-4130-9753-9b694f82f1b5","init":true,"started":"2024-11-18T17:39:30.2546247+00:00","timestamp":"2024-11-18T17:39:30.255075+00:00","seq":0,"duration":0,"errors":0,"attrs":{"release":"4.0.10.2544-main","environment":"main"}}}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -168,15 +168,19 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
cleanuperr:
|
cleanuperr:
|
||||||
image: flaminel/cleanuperr:latest
|
image: ghcr.io/flmorg/cleanuperr:latest
|
||||||
container_name: cleanuperr
|
container_name: cleanuperr
|
||||||
environment:
|
environment:
|
||||||
- LOGGING__LOGLEVEL__DEFAULT=Debug
|
- LOGGING__LOGLEVEL=Debug
|
||||||
|
- LOGGING__FILE__ENABLED=false
|
||||||
|
- LOGGING__FILE__PATH=/var/logs
|
||||||
|
- LOGGING__ENHANCED=true
|
||||||
|
|
||||||
- TRIGGERS__QUEUECLEANER=0/30 * * * * ?
|
- TRIGGERS__QUEUECLEANER=0/30 * * * * ?
|
||||||
- TRIGGERS__CONTENTBLOCKER=0/30 * * * * ?
|
- TRIGGERS__CONTENTBLOCKER=0/30 * * * * ?
|
||||||
|
|
||||||
- QUEUECLEANER__ENABLED=true
|
- QUEUECLEANER__ENABLED=true
|
||||||
|
- QUEUECLEANER__RUNSEQUENTIALLY=true
|
||||||
|
|
||||||
- CONTENTBLOCKER__ENABLED=true
|
- CONTENTBLOCKER__ENABLED=true
|
||||||
- CONTENTBLOCKER__BLACKLIST__ENABLED=true
|
- CONTENTBLOCKER__BLACKLIST__ENABLED=true
|
||||||
@@ -200,12 +204,15 @@ services:
|
|||||||
# - TRANSMISSION__PASSWORD=testing
|
# - TRANSMISSION__PASSWORD=testing
|
||||||
|
|
||||||
- SONARR__ENABLED=true
|
- SONARR__ENABLED=true
|
||||||
|
- SONARR__SEARCHTYPE=Episode
|
||||||
- SONARR__INSTANCES__0__URL=http://sonarr:8989
|
- SONARR__INSTANCES__0__URL=http://sonarr:8989
|
||||||
- SONARR__INSTANCES__0__APIKEY=96736c3eb3144936b8f1d62d27be8cee
|
- SONARR__INSTANCES__0__APIKEY=96736c3eb3144936b8f1d62d27be8cee
|
||||||
|
|
||||||
- RADARR__ENABLED=true
|
- RADARR__ENABLED=true
|
||||||
- RADARR__INSTANCES__0__URL=http://radarr:7878
|
- RADARR__INSTANCES__0__URL=http://radarr:7878
|
||||||
- RADARR__INSTANCES__0__APIKEY=705b553732ab4167ab23909305d60600
|
- RADARR__INSTANCES__0__APIKEY=705b553732ab4167ab23909305d60600
|
||||||
|
volumes:
|
||||||
|
- ./data/cleanuperr/logs:/var/logs
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- qbittorrent
|
- qbittorrent
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## LOGGING__ENHANCED
|
||||||
|
|
||||||
|
Some logs may contain information that is hard to read. Enhancing these logs usually comes with the cost of additional calls to the APIs.
|
||||||
|
|
||||||
|
If enabled, logs like this
|
||||||
|
|
||||||
|
```movie search triggered | http://localhost:7878/ | movie ids: 1, 2```
|
||||||
|
|
||||||
|
will transform into
|
||||||
|
|
||||||
|
```movie search triggered | http://localhost:7878/ | [Speak No Evil][The Wild Robot]```
|
||||||
Reference in New Issue
Block a user