diff --git a/.gitignore b/.gitignore index a2672ab5..95856435 100644 --- a/.gitignore +++ b/.gitignore @@ -167,4 +167,5 @@ src/.idea/ .idea/ **/logs/ -**/MediaCover/ \ No newline at end of file +**/MediaCover/ +**/archive/ \ No newline at end of file diff --git a/README.md b/README.md index e0819feb..3bd26c66 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,43 @@ # cleanuperr -# This tool is actively developed and not yet stable. Join the discord server if you want to get in touch with me as soon as possible (or if you just want to be informed of new releases), so we can squash those pesky bugs together: https://discord.gg/cJYPs9Bt +### This tool is actively developed and not yet stable. Join the discord server if you want to get in touch with me as soon as possible (or if you just want to be informed of new releases), so we can squash those pesky bugs together: https://discord.gg/cJYPs9Bt -## How it works +## Important note -1. Add excluded file names to prevent malicious files from being downloaded by qBittorrent. -2. cleanuperr goes through all items in Sonarr's queue at every 5th minute. -3. For each queue item, a call is made to qBittorrent to get the stats of the torrent. -4. If a torrent is found to be marked as completed, but with 0 downloaded bytes, cleanuperr calls Sonarr to add that torrent to the blocklist. -5. If any malicious torrents have been found, cleanuperr calls Sonarr to automatically search again. +Only the latest versions of qBittorrent, Deluge, Sonarr etc. are supported, or earlier versions that have the same API as the latest version. + +# Setup + +## Using qBittorrent's built-in feature (works only with qBittorrent) + +1. Go to qBittorrent -> Options -> Downloads -> make sure `Excluded file names` is checked -> Set the exclusion list found [here](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist) or create your own. +2. Start cleanuperr with `QUEUECLEANER__ENABLED` set to `true`. +3. cleanuperr will execute a queue cleaner cron job at every 5 minutes that will: + 1. go through all items from Sonarr/Radarr's queue. + 2. each a queue item is checked: + - if it has been marked as completed and 0 bytes have been downloaded (because qBittorrent blocked the files). + - if all its files are skipped. + 3. if the item IS NOT as described, it is skipped. + 4. if the item IS 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) + +1. Start cleanuperr with both `QUEUECLEANER_ENABLED` and `CONTENTBLOCKER_ENABLED` set to `true`. +2. Be sure to set and enable a blacklist or a whitelist as described in the [Environment variables](#Environment-variables) section. +3. cleanuperr with execute the following jobs: + - the same queue cleaner as described [here](#Using-qBittorrents-built-in-feature) + - a content blocker cron job at every 5 minutes that will mark files as unwanted/skipped if: + - they are in the blacklist. + - they are not in the whitelist. ## 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" \ @@ -34,20 +56,46 @@ docker run -d \ ``` ### Docker compose yaml + ``` version: "3.3" services: cleanuperr: environment: + - LOGGING__LOGLEVEL__DEFAULT=Information + - TRIGGERS__QUEUECLEANER=0 0/5 * * * ? + - TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ? + + - QUEUECLEANER__ENABLED=true + + - CONTENTBLOCKER__ENABLED=true + - CONTENTBLOCKER__BLACKLIST__ENABLED=true + - CONTENTBLOCKER__BLACKLIST__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist + # OR + # - CONTENTBLOCKER__WHITELIST__ENABLED=true + # - CONTENTBLOCKER__BLACKLIST__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist + + - QBITTORRENT__ENABLED=true - QBITTORRENT__URL=http://localhost:8080 - QBITTORRENT__USERNAME=user - QBITTORRENT__PASSWORD=pass + # OR + # - DELUGE__ENABLED=true + # - DELUGE__URL=http://localhost:8112 + # - DELUGE__PASSWORD=testing + # OR + # - TRANSMISSION__ENABLED=true + # - TRANSMISSION__URL=http://localhost:9091 + # - TRANSMISSION__USERNAME=test + # - TRANSMISSION__PASSWORD=testing + - SONARR__ENABLED=true - SONARR__INSTANCES__0__URL=http://localhost:8989 - SONARR__INSTANCES__0__APIKEY=secret1 - SONARR__INSTANCES__1__URL=http://localhost:8990 - SONARR__INSTANCES__1__APIKEY=secret2 + - RADARR__ENABLED=true - RADARR__INSTANCES__0__URL=http://localhost:7878 - RADARR__INSTANCES__0__APIKEY=secret3 @@ -61,10 +109,32 @@ services: | Variable | Required | Description | Default value | |---|---|---|---| -| TRIGGERS__QUEUECLEANER | No | [Quartz cron trigger](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) | 0 0/5 * * * ? | -| QBITTORRENT__URL | Yes | qBittorrent instance url | http://localhost:8080 | -| QBITTORRENT__USERNAME | Yes | qBittorrent user | empty | -| QBITTORRENT__PASSWORD | Yes | qBittorrent password | empty | +| LOGGING__LOGLEVEL__DEFAULT | No | Can be `Debug`, `Information`, `Warning` or `Error` | Information | +||||| +| 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 * * * ? | +||||| +| QUEUECLEANER__ENABLED | No | Enable or disable the queue cleaner | true | +||||| +| 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__PATH | Yes if blacklist is enabled | Path to the blacklist (local file or url) | empty | +| CONTENTBLOCKER__WHITELIST__ENABLED | Yes if content blocker is enabled and blacklist is not enabled | Enable or disable the whitelist | false | +| CONTENTBLOCKER__BLACKLIST__PATH | Yes if whitelist is enabled | Path to the whitelist (local file or url) | empty | +||||| +| QBITTORRENT__ENABLED | No | Enable or disable qBittorrent | true | +| QBITTORRENT__URL | Yes if qBittorrent is enabled | qBittorrent instance url | http://localhost:8112 | +| QBITTORRENT__USERNAME | Yes if qBittorrent is enabled | qBittorrent user | empty | +| QBITTORRENT__PASSWORD | Yes if qBittorrent is enabled | qBittorrent password | empty | +||||| +| DELUGE__ENABLED | No | Enable or disable Deluge | false | +| DELUGE__URL | Yes if Deluge is enabled | Deluge instance url | http://localhost:8080 | +| DELUGE__PASSWORD | Yes if Deluge is enabled | Deluge password | empty | +||||| +| TRANSMISSION__ENABLED | No | Enable or disable Transmission | true | +| TRANSMISSION__URL | Yes if Transmission is enabled | Transmission instance url | http://localhost:9091 | +| TRANSMISSION__USERNAME | Yes if Transmission is enabled | Transmission user | empty | +| TRANSMISSION__PASSWORD | Yes if Transmission is enabled | Transmission password | empty | ||||| | SONARR__ENABLED | No | Whether Sonarr cleanup is enabled or not | true | | SONARR__INSTANCES__0__URL | Yes | First Sonarr instance url | http://localhost:8989 | @@ -75,16 +145,24 @@ services: | RADARR__INSTANCES__0__APIKEY | Yes | First Radarr instance API key | empty | # +### To be noted -Multiple Sonarr/Radarr instances can be specified using this format: - +1. The blacklist and the whitelist can not be both enabled at the same time. +2. Only one download client can be enabled at a time. If you have more than one download client, you should deploy multiple instances of cleanuperr. +3. The blocklists (blacklist/whitelist) should have a single pattern on each line and supports the following: +``` +*example // file name ends with "example" +example* // file name starts with "example" +*example* // file name has "example" in the name +example // file name is exactly the word "example" + // regex +``` +4. Multiple Sonarr/Radarr instances can be specified using this format, where `` starts from 0: ``` SONARR__INSTANCES____URL SONARR__INSTANCES____APIKEY ``` -where `` starts from 0. - # ### Binaries (if you're not using Docker) @@ -95,529 +173,4 @@ where `` starts from 0. ### Run as a Windows Service -Check out this stackoverflow answer on how to do it: https://stackoverflow.com/a/15719678 - -## Extensions to block in qBittorrent -
- Extensions -
*(sample).*
-*.0xe
-*.73k
-*.73p
-*.7z
-*.89k
-*.89z
-*.8ck
-*.a7r
-*.ac
-*.acc
-*.ace
-*.acr
-*.actc
-*.action
-*.actm
-*.ade
-*.adp
-*.afmacro
-*.afmacros
-*.ahk
-*.ai
-*.aif
-*.air
-*.alz
-*.api
-*.apk
-*.app
-*.appimage
-*.applescript
-*.application
-*.appx
-*.arc
-*.arj
-*.arscript
-*.asb
-*.asp
-*.aspx
-*.aspx-exe
-*.atmx
-*.azw2
-*.ba_
-*.bak
-*.bas
-*.bash
-*.bat
-*.bdjo
-*.bdmv
-*.beam
-*.bin
-*.bmp
-*.bms
-*.bns
-*.bsa
-*.btm
-*.bz2
-*.c
-*.cab
-*.caction
-*.cci
-*.cda
-*.cdb
-*.cel
-*.celx
-*.cfs
-*.cgi
-*.cheat
-*.chm
-*.ckpt
-*.cla
-*.class
-*.clpi
-*.cmd
-*.cof
-*.coffee
-*.com
-*.command
-*.conf
-*.config
-*.cpl
-*.crt
-*.cs
-*.csh
-*.csharp
-*.csproj
-*.css
-*.csv
-*.cue
-*.cur
-*.cyw
-*.daemon
-*.dat
-*.data-00000-of-00001
-*.db
-*.deamon
-*.deb
-*.dek
-*.diz
-*.dld
-*.dll
-*.dmc
-*.dmg
-*.doc
-*.docb
-*.docm
-*.docx
-*.dot
-*.dotb
-*.dotm
-*.drv
-*.ds
-*.dw
-*.dword
-*.dxl
-*.e_e
-*.ear
-*.ebacmd
-*.ebm
-*.ebs
-*.ebs2
-*.ecf
-*.eham
-*.elf
-*.elf-so
-*.email
-*.emu
-*.epk
-*.es
-*.esh
-*.etc
-*.ex4
-*.ex5
-*.ex_
-*.exe
-*.exe-only
-*.exe-service
-*.exe-small
-*.exe1
-*.exopc
-*.exz
-*.ezs
-*.ezt
-*.fas
-*.fba
-*.fky
-*.flac
-*.flatpak
-*.flv
-*.fpi
-*.frs
-*.fxp
-*.gadget
-*.gat
-*.gif
-*.gifv
-*.gm9
-*.gpe
-*.gpu
-*.gs
-*.gz
-*.h5
-*.ham
-*.hex
-*.hlp
-*.hms
-*.hpf
-*.hta
-*.hta-psh
-*.htaccess
-*.htm
-*.html
-*.icd
-*.icns
-*.ico
-*.idx
-*.iim
-*.img
-*.index
-*.inf
-*.ini
-*.ink
-*.ins
-*.ipa
-*.ipf
-*.ipk
-*.ipsw
-*.iqylink
-*.iso
-*.isp
-*.isu
-*.ita
-*.izh
-*.izma ace
-*.jar
-*.java
-*.jpeg
-*.jpg
-*.js
-*.js_be
-*.js_le
-*.jse
-*.jsf
-*.json
-*.jsp
-*.jsx
-*.kix
-*.ksh
-*.kx
-*.lck
-*.ldb
-*.lib
-*.link
-*.lnk
-*.lo
-*.lock
-*.log
-*.loop-vbs
-*.ls
-*.m3u
-*.m4a
-*.mac
-*.macho
-*.mamc
-*.manifest
-*.mcr
-*.md
-*.mda
-*.mdb
-*.mde
-*.mdf
-*.mdn
-*.mdt
-*.mel
-*.mem
-*.meta
-*.mgm
-*.mhm
-*.mht
-*.mhtml
-*.mid
-*.mio
-*.mlappinstall
-*.mlx
-*.mm
-*.mobileconfig
-*.model
-*.moo
-*.mp3
-*.mpa
-*.mpk
-*.mpls
-*.mrc
-*.mrp
-*.ms
-*.msc
-*.msh
-*.msh1
-*.msh1xml
-*.msh2
-*.msh2xml
-*.mshxml
-*.msi
-*.msi-nouac
-*.msix
-*.msl
-*.msp
-*.mst
-*.msu
-*.mxe
-*.n
-*.ncl
-*.net
-*.nexe
-*.nfo
-*.nrg
-*.num
-*.nzb.bz2
-*.nzb.gz
-*.nzbs
-*.ocx
-*.odt
-*.ore
-*.ost
-*.osx
-*.osx-app
-*.otm
-*.out
-*.ova
-*.p
-*.paf
-*.pak
-*.pb
-*.pcd
-*.pdb
-*.pdf
-*.pea
-*.perl
-*.pex
-*.phar
-*.php
-*.php5
-*.pif
-*.pkg
-*.pl
-*.plsc
-*.plx
-*.png
-*.pol
-*.pot
-*.potm
-*.powershell
-*.ppam
-*.ppkg
-*.pps
-*.ppsm
-*.ppt
-*.pptm
-*.pptx
-*.prc
-*.prg
-*.ps
-*.ps1
-*.ps1xml
-*.ps2
-*.ps2xml
-*.psc1
-*.psc2
-*.psd
-*.psd1
-*.psh
-*.psh-cmd
-*.psh-net
-*.psh-reflection
-*.psm1
-*.pst
-*.pt
-*.pvd
-*.pwc
-*.pxo
-*.py
-*.pyc
-*.pyd
-*.pyo
-*.python
-*.pyz
-*.qit
-*.qpx
-*.ram
-*.rar
-*.raw
-*.rb
-*.rbf
-*.rbx
-*.readme
-*.reg
-*.resources
-*.resx
-*.rfs
-*.rfu
-*.rgs
-*.rm
-*.rox
-*.rpg
-*.rpj
-*.rpm
-*.ruby
-*.run
-*.rxe
-*.s2a
-*.sample
-*.sapk
-*.savedmodel
-*.sbs
-*.sca
-*.scar
-*.scb
-*.scf
-*.scpt
-*.scptd
-*.scr
-*.script
-*.sct
-*.seed
-*.server
-*.service
-*.sfv
-*.sh
-*.shb
-*.shell
-*.shortcut
-*.shs
-*.shtml
-*.sit
-*.sitx
-*.sk
-*.sldm
-*.sln
-*.smm
-*.snap
-*.snd
-*.spr
-*.sql
-*.sqx
-*.srec
-*.srt
-*.ssm
-*.sts
-*.sub
-*.svg
-*.swf
-*.sys
-*.tar
-*.tar.gz
-*.tbl
-*.tbz
-*.tcp
-*.text
-*.tf
-*.tgz
-*.thm
-*.thmx
-*.thumb
-*.tiapp
-*.tif
-*.tiff
-*.tipa
-*.tmp
-*.tms
-*.toast
-*.torrent
-*.tpk
-*.txt
-*.u3p
-*.udf
-*.upk
-*.upx
-*.url
-*.uvm
-*.uw8
-*.vb
-*.vba
-*.vba-exe
-*.vba-psh
-*.vbapplication
-*.vbe
-*.vbs
-*.vbscript
-*.vbscript 
-*.vcd
-*.vdo
-*.vexe
-*.vhd
-*.vhdx
-*.vlx
-*.vm
-*.vmdk
-*.vob
-*.vocab
-*.vpm
-*.vxp
-*.war
-*.wav
-*.wbk
-*.wcm
-*.webm
-*.widget
-*.wim
-*.wiz
-*.wma
-*.workflow
-*.wpk
-*.wpl
-*.wpm
-*.wps
-*.ws
-*.wsc
-*.wsf
-*.wsh
-*.x86
-*.x86_64
-*.xaml
-*.xap
-*.xbap
-*.xbe
-*.xex
-*.xig
-*.xla
-*.xlam
-*.xll
-*.xlm
-*.xls
-*.xlsb
-*.xlsm
-*.xlsx
-*.xlt
-*.xltb
-*.xltm
-*.xlw
-*.xml
-*.xqt
-*.xrt
-*.xys
-*.xz
-*.ygh
-*.z
-*.zip
-*.zipx
-*.zl9
-*.zoo
-*sample.avchd
-*sample.avi
-*sample.mkv
-*sample.mov
-*sample.mp4
-*sample.webm
-*sample.wmv
-Trailer.*
-VOSTFR
-api
-
-
+Check out this stackoverflow answer on how to do it: https://stackoverflow.com/a/15719678 \ No newline at end of file diff --git a/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs b/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs new file mode 100644 index 00000000..b20c4527 --- /dev/null +++ b/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs @@ -0,0 +1,40 @@ +namespace Common.Configuration.ContentBlocker; + +public sealed record ContentBlockerConfig : IConfig +{ + public const string SectionName = "ContentBlocker"; + + public required bool Enabled { get; init; } + + public PatternConfig? Blacklist { get; init; } + + public PatternConfig? Whitelist { get; init; } + + public void Validate() + { + if (!Enabled) + { + return; + } + + if (Blacklist is null && Whitelist is null) + { + throw new Exception("content blocker is enabled, but both blacklist and whitelist are missing"); + } + + if (Blacklist?.Enabled is true && Whitelist?.Enabled is true) + { + throw new Exception("only one exclusion (blacklist/whitelist) list is allowed"); + } + + if (Blacklist?.Enabled is true && string.IsNullOrEmpty(Blacklist.Path)) + { + throw new Exception("blacklist path is required"); + } + + if (Whitelist?.Enabled is true && string.IsNullOrEmpty(Whitelist.Path)) + { + throw new Exception("blacklist path is required"); + } + } +} \ No newline at end of file diff --git a/code/Common/Configuration/ContentBlocker/PatternConfig.cs b/code/Common/Configuration/ContentBlocker/PatternConfig.cs new file mode 100644 index 00000000..2dc63d90 --- /dev/null +++ b/code/Common/Configuration/ContentBlocker/PatternConfig.cs @@ -0,0 +1,8 @@ +namespace Common.Configuration.ContentBlocker; + +public sealed record PatternConfig +{ + public bool Enabled { get; init; } + + public string? Path { get; init; } +} \ No newline at end of file diff --git a/code/Common/Configuration/DelugeConfig.cs b/code/Common/Configuration/DelugeConfig.cs new file mode 100644 index 00000000..80d31dc8 --- /dev/null +++ b/code/Common/Configuration/DelugeConfig.cs @@ -0,0 +1,32 @@ +using System.Security; + +namespace Common.Configuration; + +public sealed record DelugeConfig : IConfig +{ + public const string SectionName = "Deluge"; + + public required bool Enabled { get; init; } + + public Uri? Url { get; init; } + + public string? Password { get; init; } + + public void Validate() + { + if (!Enabled) + { + return; + } + + if (Url is null) + { + throw new ArgumentNullException(nameof(Url)); + } + + if (string.IsNullOrEmpty(Password)) + { + throw new ArgumentNullException(nameof(Password)); + } + } +} \ No newline at end of file diff --git a/code/Common/Configuration/IConfig.cs b/code/Common/Configuration/IConfig.cs new file mode 100644 index 00000000..b653cb3d --- /dev/null +++ b/code/Common/Configuration/IConfig.cs @@ -0,0 +1,6 @@ +namespace Common.Configuration; + +public interface IConfig +{ + void Validate(); +} \ No newline at end of file diff --git a/code/Common/Configuration/QBitConfig.cs b/code/Common/Configuration/QBitConfig.cs index 3b7b3632..f0b727ee 100644 --- a/code/Common/Configuration/QBitConfig.cs +++ b/code/Common/Configuration/QBitConfig.cs @@ -1,12 +1,39 @@ -namespace Common.Configuration; +using System.ComponentModel.DataAnnotations; -public sealed class QBitConfig +namespace Common.Configuration; + +public sealed class QBitConfig : IConfig { public const string SectionName = "qBittorrent"; - public required Uri Url { get; set; } + public required bool Enabled { get; init; } - public required string Username { get; set; } + public Uri? Url { get; init; } - public required string Password { get; set; } + public string? Username { get; init; } + + public string? Password { get; init; } + + public void Validate() + { + if (!Enabled) + { + return; + } + + if (Url is null) + { + throw new ArgumentNullException(nameof(Url)); + } + + if (string.IsNullOrEmpty(Username)) + { + throw new ArgumentNullException(nameof(Username)); + } + + if (string.IsNullOrEmpty(Password)) + { + throw new ArgumentNullException(nameof(Password)); + } + } } \ No newline at end of file diff --git a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs new file mode 100644 index 00000000..7b3e9584 --- /dev/null +++ b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs @@ -0,0 +1,8 @@ +namespace Common.Configuration.QueueCleaner; + +public sealed record QueueCleanerConfig +{ + public const string SectionName = "QueueCleaner"; + + public required bool Enabled { get; init; } +} \ No newline at end of file diff --git a/code/Common/Configuration/TransmissionConfig.cs b/code/Common/Configuration/TransmissionConfig.cs new file mode 100644 index 00000000..7d3b5915 --- /dev/null +++ b/code/Common/Configuration/TransmissionConfig.cs @@ -0,0 +1,37 @@ +namespace Common.Configuration; + +public record TransmissionConfig +{ + public const string SectionName = "Transmission"; + + public required bool Enabled { get; init; } + + public Uri? Url { get; init; } + + public string? Username { get; init; } + + public string? Password { get; init; } + + public void Validate() + { + if (!Enabled) + { + return; + } + + if (Url is null) + { + throw new ArgumentNullException(nameof(Url)); + } + + if (string.IsNullOrEmpty(Username)) + { + throw new ArgumentNullException(nameof(Username)); + } + + if (string.IsNullOrEmpty(Password)) + { + throw new ArgumentNullException(nameof(Password)); + } + } +} \ No newline at end of file diff --git a/code/Common/Configuration/TriggersConfig.cs b/code/Common/Configuration/TriggersConfig.cs index 72444621..2a556f12 100644 --- a/code/Common/Configuration/TriggersConfig.cs +++ b/code/Common/Configuration/TriggersConfig.cs @@ -5,4 +5,6 @@ public sealed class TriggersConfig public const string SectionName = "Triggers"; public required string QueueCleaner { get; init; } + + public required string ContentBlocker { get; init; } } \ No newline at end of file diff --git a/code/Domain/Domain.csproj b/code/Domain/Domain.csproj index 3a635329..3990be5a 100644 --- a/code/Domain/Domain.csproj +++ b/code/Domain/Domain.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/code/Domain/Enums/BlocklistType.cs b/code/Domain/Enums/BlocklistType.cs new file mode 100644 index 00000000..99be1d59 --- /dev/null +++ b/code/Domain/Enums/BlocklistType.cs @@ -0,0 +1,7 @@ +namespace Domain.Enums; + +public enum BlocklistType +{ + Blacklist, + Whitelist +} \ No newline at end of file diff --git a/code/Domain/Arr/Enums/InstanceType.cs b/code/Domain/Enums/InstanceType.cs similarity index 70% rename from code/Domain/Arr/Enums/InstanceType.cs rename to code/Domain/Enums/InstanceType.cs index 5ef14d58..4ec96a4c 100644 --- a/code/Domain/Arr/Enums/InstanceType.cs +++ b/code/Domain/Enums/InstanceType.cs @@ -1,4 +1,4 @@ -namespace Domain.Arr.Enums; +namespace Domain.Enums; public enum InstanceType { diff --git a/code/Domain/Arr/Queue/QueueListResponse.cs b/code/Domain/Models/Arr/Queue/QueueListResponse.cs similarity index 100% rename from code/Domain/Arr/Queue/QueueListResponse.cs rename to code/Domain/Models/Arr/Queue/QueueListResponse.cs diff --git a/code/Domain/Arr/Queue/QueueRecord.cs b/code/Domain/Models/Arr/Queue/QueueRecord.cs similarity index 92% rename from code/Domain/Arr/Queue/QueueRecord.cs rename to code/Domain/Models/Arr/Queue/QueueRecord.cs index 0748c503..4af7404b 100644 --- a/code/Domain/Arr/Queue/QueueRecord.cs +++ b/code/Domain/Models/Arr/Queue/QueueRecord.cs @@ -3,6 +3,7 @@ public record QueueRecord { public int SeriesId { get; init; } + public int EpisodeId { get; init; } public int MovieId { get; init; } public required string Title { get; init; } public string Status { get; init; } diff --git a/code/Domain/Models/Deluge/Exceptions/DelugeClientException.cs b/code/Domain/Models/Deluge/Exceptions/DelugeClientException.cs new file mode 100644 index 00000000..07986fcb --- /dev/null +++ b/code/Domain/Models/Deluge/Exceptions/DelugeClientException.cs @@ -0,0 +1,8 @@ +namespace Domain.Models.Deluge.Exceptions; + +public class DelugeClientException : Exception +{ + public DelugeClientException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Exceptions/DelugeLoginException.cs b/code/Domain/Models/Deluge/Exceptions/DelugeLoginException.cs new file mode 100644 index 00000000..54212ce5 --- /dev/null +++ b/code/Domain/Models/Deluge/Exceptions/DelugeLoginException.cs @@ -0,0 +1,8 @@ +namespace Domain.Models.Deluge.Exceptions; + +public sealed class DelugeLoginException : DelugeClientException +{ + public DelugeLoginException() : base("login failed") + { + } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Exceptions/DelugeLogoutException.cs b/code/Domain/Models/Deluge/Exceptions/DelugeLogoutException.cs new file mode 100644 index 00000000..20a8d9a0 --- /dev/null +++ b/code/Domain/Models/Deluge/Exceptions/DelugeLogoutException.cs @@ -0,0 +1,8 @@ +namespace Domain.Models.Deluge.Exceptions; + +public sealed class DelugeLogoutException : DelugeClientException +{ + public DelugeLogoutException() : base("logout failed") + { + } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Request/DelugeRequest.cs b/code/Domain/Models/Deluge/Request/DelugeRequest.cs new file mode 100644 index 00000000..baa6e6ef --- /dev/null +++ b/code/Domain/Models/Deluge/Request/DelugeRequest.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; + +namespace Domain.Models.Deluge.Request; + +public class DelugeRequest +{ + [JsonProperty(PropertyName = "id")] + public int RequestId { get; set; } + + [JsonProperty(PropertyName = "method")] + public String Method { get; set; } + + [JsonProperty(PropertyName = "params")] + public List Params { get; set; } + + [JsonIgnore] + public NullValueHandling NullValueHandling { get; set; } + + public DelugeRequest(int requestId, String method, params object[] parameters) + { + RequestId = requestId; + Method = method; + Params = new List(); + + if (parameters != null) + { + Params.AddRange(parameters); + } + + NullValueHandling = NullValueHandling.Include; + } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeContents.cs b/code/Domain/Models/Deluge/Response/DelugeContents.cs new file mode 100644 index 00000000..8192559f --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeContents.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Domain.Models.Deluge.Response; + +public sealed record DelugeContents +{ + [JsonPropertyName("contents")] + public Dictionary Contents { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } // Always "dir" for the root +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeError.cs b/code/Domain/Models/Deluge/Response/DelugeError.cs new file mode 100644 index 00000000..2ec19ddd --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeError.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Domain.Models.Deluge.Response; + +public sealed record DelugeError +{ + [JsonProperty(PropertyName = "message")] + public String Message { get; set; } + + [JsonProperty(PropertyName = "code")] + public int Code { get; set; } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeFileOrDirectory.cs b/code/Domain/Models/Deluge/Response/DelugeFileOrDirectory.cs new file mode 100644 index 00000000..51941094 --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeFileOrDirectory.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace Domain.Models.Deluge.Response; + +public class DelugeFileOrDirectory +{ + [JsonPropertyName("type")] + public string Type { get; set; } // "file" or "dir" + + [JsonPropertyName("contents")] + public Dictionary? Contents { get; set; } // Recursive property for directories + + [JsonPropertyName("index")] + public required int Index { get; set; } + + [JsonPropertyName("path")] + public string Path { get; set; } + + [JsonPropertyName("size")] + public int? Size { get; set; } + + [JsonPropertyName("offset")] + public int? Offset { get; set; } + + [JsonPropertyName("progress")] + public double? Progress { get; set; } + + [JsonPropertyName("priority")] + public required int Priority { get; set; } + + [JsonPropertyName("progresses")] + public List Progresses { get; set; } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeMinimalStatus.cs b/code/Domain/Models/Deluge/Response/DelugeMinimalStatus.cs new file mode 100644 index 00000000..220a6fd4 --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeMinimalStatus.cs @@ -0,0 +1,6 @@ +namespace Domain.Models.Deluge.Response; + +public sealed record DelugeMinimalStatus +{ + public string? Hash { get; set; } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeResponse.cs b/code/Domain/Models/Deluge/Response/DelugeResponse.cs new file mode 100644 index 00000000..e82f6614 --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeResponse.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace Domain.Models.Deluge.Response; + +public sealed record DelugeResponse +{ + [JsonProperty(PropertyName = "id")] + public int ResponseId { get; set; } + + [JsonProperty(PropertyName = "result")] + public T? Result { get; set; } + + [JsonProperty(PropertyName = "error")] + public DelugeError? Error { get; set; } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeTorrent.cs b/code/Domain/Models/Deluge/Response/DelugeTorrent.cs new file mode 100644 index 00000000..38270786 --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeTorrent.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Domain.Models.Deluge.Response; + +public record DelugeTorrent +{ + [JsonProperty(PropertyName = "comment")] + public string Comment { get; set; } + + [JsonProperty(PropertyName = "is_seed")] + public bool IsSeed { get; set; } + + [JsonProperty(PropertyName = "hash")] + public string Hash { get; set; } + + [JsonProperty(PropertyName = "paused")] + public bool Paused { get; set; } + + [JsonProperty(PropertyName = "ratio")] + public double Ratio { get; set; } + + [JsonProperty(PropertyName = "message")] + public string Message { get; set; } + + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + + [JsonProperty(PropertyName = "label")] + public string Label { get; set; } +} \ No newline at end of file diff --git a/code/Domain/Models/Deluge/Response/DelugeTorrentExtended.cs b/code/Domain/Models/Deluge/Response/DelugeTorrentExtended.cs new file mode 100644 index 00000000..efdbf02c --- /dev/null +++ b/code/Domain/Models/Deluge/Response/DelugeTorrentExtended.cs @@ -0,0 +1,110 @@ +using Newtonsoft.Json; + +namespace Domain.Models.Deluge.Response; + +public sealed record DelugeTorrentExtended : DelugeTorrent +{ + [JsonProperty(PropertyName = "total_done")] + public long TotalDone { get; set; } + + [JsonProperty(PropertyName = "total_payload_download")] + public long TotalPayloadDownload { get; set; } + + [JsonProperty(PropertyName = "total_uploaded")] + public long TotalUploaded { get; set; } + + [JsonProperty(PropertyName = "next_announce")] + public int NextAnnounce { get; set; } + + [JsonProperty(PropertyName = "tracker_status")] + public string TrackerStatus { get; set; } + + [JsonProperty(PropertyName = "num_pieces")] + public int NumPieces { get; set; } + + [JsonProperty(PropertyName = "piece_length")] + public long PieceLength { get; set; } + + [JsonProperty(PropertyName = "is_auto_managed")] + public bool IsAutoManaged { get; set; } + + [JsonProperty(PropertyName = "active_time")] + public long ActiveTime { get; set; } + + [JsonProperty(PropertyName = "seeding_time")] + public long SeedingTime { get; set; } + + [JsonProperty(PropertyName = "time_since_transfer")] + public long TimeSinceTransfer { get; set; } + + [JsonProperty(PropertyName = "seed_rank")] + public int SeedRank { get; set; } + + [JsonProperty(PropertyName = "last_seen_complete")] + public long LastSeenComplete { get; set; } + + [JsonProperty(PropertyName = "completed_time")] + public long CompletedTime { get; set; } + + [JsonProperty(PropertyName = "owner")] public string Owner { get; set; } + + [JsonProperty(PropertyName = "public")] + public bool Public { get; set; } + + [JsonProperty(PropertyName = "shared")] + public bool Shared { get; set; } + + [JsonProperty(PropertyName = "queue")] public int Queue { get; set; } + + [JsonProperty(PropertyName = "total_wanted")] + public long TotalWanted { get; set; } + + [JsonProperty(PropertyName = "state")] public string State { get; set; } + + [JsonProperty(PropertyName = "progress")] + public float Progress { get; set; } + + [JsonProperty(PropertyName = "num_seeds")] + public int NumSeeds { get; set; } + + [JsonProperty(PropertyName = "total_seeds")] + public int TotalSeeds { get; set; } + + [JsonProperty(PropertyName = "num_peers")] + public int NumPeers { get; set; } + + [JsonProperty(PropertyName = "total_peers")] + public int TotalPeers { get; set; } + + [JsonProperty(PropertyName = "download_payload_rate")] + public long DownloadPayloadRate { get; set; } + + [JsonProperty(PropertyName = "upload_payload_rate")] + public long UploadPayloadRate { get; set; } + + [JsonProperty(PropertyName = "eta")] public long Eta { get; set; } + + [JsonProperty(PropertyName = "distributed_copies")] + public float DistributedCopies { get; set; } + + [JsonProperty(PropertyName = "time_added")] + public int TimeAdded { get; set; } + + [JsonProperty(PropertyName = "tracker_host")] + public string TrackerHost { get; set; } + + [JsonProperty(PropertyName = "download_location")] + public string DownloadLocation { get; set; } + + [JsonProperty(PropertyName = "total_remaining")] + public long TotalRemaining { get; set; } + + [JsonProperty(PropertyName = "max_download_speed")] + public long MaxDownloadSpeed { get; set; } + + [JsonProperty(PropertyName = "max_upload_speed")] + public long MaxUploadSpeed { get; set; } + + [JsonProperty(PropertyName = "seeds_peers_ratio")] + public float SeedsPeersRatio { get; set; } +} \ No newline at end of file diff --git a/code/Domain/Radarr/RadarrCommand.cs b/code/Domain/Models/Radarr/RadarrCommand.cs similarity index 80% rename from code/Domain/Radarr/RadarrCommand.cs rename to code/Domain/Models/Radarr/RadarrCommand.cs index 7ed29063..adf10471 100644 --- a/code/Domain/Radarr/RadarrCommand.cs +++ b/code/Domain/Models/Radarr/RadarrCommand.cs @@ -1,4 +1,4 @@ -namespace Domain.Radarr; +namespace Domain.Models.Radarr; public sealed record RadarrCommand { diff --git a/code/Domain/Sonarr/SonarrCommand.cs b/code/Domain/Models/Sonarr/SonarrCommand.cs similarity index 78% rename from code/Domain/Sonarr/SonarrCommand.cs rename to code/Domain/Models/Sonarr/SonarrCommand.cs index 03b60384..637b1131 100644 --- a/code/Domain/Sonarr/SonarrCommand.cs +++ b/code/Domain/Models/Sonarr/SonarrCommand.cs @@ -1,4 +1,4 @@ -namespace Domain.Sonarr; +namespace Domain.Models.Sonarr; public sealed record SonarrCommand { diff --git a/code/Executable/DependencyInjection.cs b/code/Executable/DependencyInjection.cs deleted file mode 100644 index e36f31f3..00000000 --- a/code/Executable/DependencyInjection.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Common.Configuration; -using Executable.Jobs; -using Infrastructure.Verticals.Arr; -using Infrastructure.Verticals.QueueCleaner; - -namespace Executable; -using Quartz; - -public static class DependencyInjection -{ - public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) => - services - .AddLogging(builder => builder.ClearProviders().AddConsole()) - .AddHttpClient() - .AddConfiguration(configuration) - .AddServices() - .AddQuartzServices(configuration); - - private static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) => - services - .Configure(configuration.GetSection(QBitConfig.SectionName)) - .Configure(configuration.GetSection(SonarrConfig.SectionName)) - .Configure(configuration.GetSection(RadarrConfig.SectionName)); - - private static IServiceCollection AddServices(this IServiceCollection services) => - services - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient(); - - private static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration) => - services - .AddQuartz(q => - { - TriggersConfig? config = configuration.GetRequiredSection(TriggersConfig.SectionName).Get(); - - if (config is null) - { - throw new NullReferenceException("Quartz configuration is null"); - } - - q.AddQueueCleanerJob(config.QueueCleaner); - }) - .AddQuartzHostedService(opt => - { - opt.WaitForJobsToComplete = true; - }); - - private static void AddQueueCleanerJob(this IServiceCollectionQuartzConfigurator q, string trigger) - { - q.AddJob(opts => - { - opts.WithIdentity(nameof(QueueCleanerJob)); - }); - - q.AddTrigger(opts => - { - opts.ForJob(nameof(QueueCleanerJob)) - .WithIdentity($"{nameof(QueueCleanerJob)}-trigger") - .WithCronSchedule(trigger); - }); - } -} \ No newline at end of file diff --git a/code/Executable/DependencyInjection/ConfigurationDI.cs b/code/Executable/DependencyInjection/ConfigurationDI.cs new file mode 100644 index 00000000..fee9adad --- /dev/null +++ b/code/Executable/DependencyInjection/ConfigurationDI.cs @@ -0,0 +1,16 @@ +using Common.Configuration; +using Common.Configuration.ContentBlocker; + +namespace Executable.DependencyInjection; + +public static class ConfigurationDI +{ + public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) => + services + .Configure(configuration.GetSection(ContentBlockerConfig.SectionName)) + .Configure(configuration.GetSection(QBitConfig.SectionName)) + .Configure(configuration.GetSection(DelugeConfig.SectionName)) + .Configure(configuration.GetSection(TransmissionConfig.SectionName)) + .Configure(configuration.GetSection(SonarrConfig.SectionName)) + .Configure(configuration.GetSection(RadarrConfig.SectionName)); +} \ No newline at end of file diff --git a/code/Executable/DependencyInjection/MainDI.cs b/code/Executable/DependencyInjection/MainDI.cs new file mode 100644 index 00000000..fe8bd46b --- /dev/null +++ b/code/Executable/DependencyInjection/MainDI.cs @@ -0,0 +1,50 @@ +using System.Net; +using Common.Configuration; +using Common.Configuration.ContentBlocker; +using Executable.Jobs; +using Infrastructure.Verticals.Arr; +using Infrastructure.Verticals.ContentBlocker; +using Infrastructure.Verticals.DownloadClient; +using Infrastructure.Verticals.DownloadClient.Deluge; +using Infrastructure.Verticals.DownloadClient.QBittorrent; +using Infrastructure.Verticals.DownloadClient.Transmission; +using Infrastructure.Verticals.QueueCleaner; + +namespace Executable.DependencyInjection; + +public static class MainDI +{ + public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) => + services + .AddLogging(builder => builder.ClearProviders().AddConsole()) + .AddHttpClients() + .AddConfiguration(configuration) + .AddServices() + .AddQuartzServices(configuration); + + private static IServiceCollection AddHttpClients(this IServiceCollection services) + { + // add default HttpClient + services.AddHttpClient(); + + // add Deluge HttpClient + services + .AddHttpClient(nameof(DelugeService), x => + { + x.Timeout = TimeSpan.FromSeconds(5); + }) + .ConfigurePrimaryHttpMessageHandler(_ => + { + return new HttpClientHandler + { + AllowAutoRedirect = true, + UseCookies = true, + CookieContainer = new CookieContainer(), + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + ServerCertificateCustomValidationCallback = (_, _, _, _) => true + }; + }); + + return services; + } +} \ No newline at end of file diff --git a/code/Executable/DependencyInjection/QuartzDI.cs b/code/Executable/DependencyInjection/QuartzDI.cs new file mode 100644 index 00000000..5ff4c9b7 --- /dev/null +++ b/code/Executable/DependencyInjection/QuartzDI.cs @@ -0,0 +1,99 @@ +using Common.Configuration; +using Common.Configuration.ContentBlocker; +using Common.Configuration.QueueCleaner; +using Executable.Jobs; +using Infrastructure.Verticals.ContentBlocker; +using Infrastructure.Verticals.QueueCleaner; +using Quartz; + +namespace Executable.DependencyInjection; + +public static class QuartzDI +{ + public static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration) => + services + .AddQuartz(q => + { + TriggersConfig? config = configuration + .GetRequiredSection(TriggersConfig.SectionName) + .Get(); + + if (config is null) + { + throw new NullReferenceException("triggers configuration is null"); + } + + q.AddQueueCleanerJob(configuration, config.QueueCleaner); + q.AddContentBlockerJob(configuration, config.ContentBlocker); + }) + .AddQuartzHostedService(opt => + { + opt.WaitForJobsToComplete = true; + }); + + private static void AddQueueCleanerJob( + this IServiceCollectionQuartzConfigurator q, + IConfiguration configuration, + string trigger + ) + { + QueueCleanerConfig? config = configuration + .GetRequiredSection(QueueCleanerConfig.SectionName) + .Get(); + + if (config is null) + { + throw new NullReferenceException($"{nameof(QueueCleaner)} configuration is null"); + } + + if (!config.Enabled) + { + return; + } + + q.AddJob(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( + this IServiceCollectionQuartzConfigurator q, + IConfiguration configuration, + string trigger + ) + { + ContentBlockerConfig? config = configuration + .GetRequiredSection(ContentBlockerConfig.SectionName) + .Get(); + + if (config is null) + { + throw new NullReferenceException($"{nameof(ContentBlocker)} configuration is null"); + } + + if (!config.Enabled) + { + return; + } + + q.AddJob(opts => + { + opts.WithIdentity(nameof(ContentBlockerJob)); + }); + + q.AddTrigger(opts => + { + opts.ForJob(nameof(ContentBlockerJob)) + .WithIdentity($"{nameof(ContentBlockerJob)}-trigger") + .WithCronSchedule(trigger, x =>x.WithMisfireHandlingInstructionDoNothing()); + }); + } +} \ No newline at end of file diff --git a/code/Executable/DependencyInjection/ServicesDI.cs b/code/Executable/DependencyInjection/ServicesDI.cs new file mode 100644 index 00000000..3c9f67d2 --- /dev/null +++ b/code/Executable/DependencyInjection/ServicesDI.cs @@ -0,0 +1,29 @@ +using Executable.Jobs; +using Infrastructure.Verticals.Arr; +using Infrastructure.Verticals.ContentBlocker; +using Infrastructure.Verticals.DownloadClient; +using Infrastructure.Verticals.DownloadClient.Deluge; +using Infrastructure.Verticals.DownloadClient.QBittorrent; +using Infrastructure.Verticals.DownloadClient.Transmission; +using Infrastructure.Verticals.QueueCleaner; + +namespace Executable.DependencyInjection; + +public static class ServicesDI +{ + public static IServiceCollection AddServices(this IServiceCollection services) => + services + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddSingleton(); +} \ No newline at end of file diff --git a/code/Executable/Jobs/ContentBlockerJob.cs b/code/Executable/Jobs/ContentBlockerJob.cs new file mode 100644 index 00000000..f02b1cee --- /dev/null +++ b/code/Executable/Jobs/ContentBlockerJob.cs @@ -0,0 +1,32 @@ +using Infrastructure.Verticals.ContentBlocker; +using Quartz; + +namespace Executable.Jobs; + +[DisallowConcurrentExecution] +public sealed class ContentBlockerJob : IJob +{ + private readonly ILogger _logger; + private readonly ContentBlocker _contentBlocker; + + public ContentBlockerJob( + ILogger 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"); + } + } +} \ No newline at end of file diff --git a/code/Executable/Jobs/QueueCleanerJob.cs b/code/Executable/Jobs/QueueCleanerJob.cs index d814bee4..64a82c5c 100644 --- a/code/Executable/Jobs/QueueCleanerJob.cs +++ b/code/Executable/Jobs/QueueCleanerJob.cs @@ -6,20 +6,23 @@ namespace Executable.Jobs; [DisallowConcurrentExecution] public sealed class QueueCleanerJob : IJob { - private ILogger _logger; - private QueueCleanerHandler _handler; + private readonly ILogger _logger; + private readonly QueueCleaner _queueCleaner; - public QueueCleanerJob(ILogger logger, QueueCleanerHandler handler) + public QueueCleanerJob( + ILogger logger, + QueueCleaner queueCleaner + ) { _logger = logger; - _handler = handler; + _queueCleaner = queueCleaner; } public async Task Execute(IJobExecutionContext context) { try { - await _handler.HandleAsync(); + await _queueCleaner.ExecuteAsync(); } catch (Exception ex) { diff --git a/code/Executable/Program.cs b/code/Executable/Program.cs index 1e253c08..a9876928 100644 --- a/code/Executable/Program.cs +++ b/code/Executable/Program.cs @@ -1,4 +1,4 @@ -using Executable; +using Executable.DependencyInjection; var builder = Host.CreateApplicationBuilder(args); diff --git a/code/Executable/appsettings.Development.json b/code/Executable/appsettings.Development.json index 85866fa0..1f8420f8 100644 --- a/code/Executable/appsettings.Development.json +++ b/code/Executable/appsettings.Development.json @@ -1,11 +1,63 @@ { "Logging": { "LogLevel": { - "Default": "Information", - "Microsoft.Hosting.Lifetime": "Information" + "Default": "Debug", + "Microsoft.Hosting.Lifetime": "Information", + "Quartz": "Warning", + "System.Net.Http.HttpClient": "Error" } }, "Triggers": { - "QueueCleaner": "0 0/1 * * * ?" + "QueueCleaner": "0/10 * * * * ?", + "ContentBlocker": "0/10 * * * * ?" + }, + "ContentBlocker": { + "Enabled": false, + "Blacklist": { + "Enabled": false, + "Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist" + }, + "Whitelist": { + "Enabled": false, + "Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist" + } + }, + "QueueCleaner": { + "Enabled": true + }, + "qBittorrent": { + "Enabled": true, + "Url": "http://localhost:8080", + "Username": "test", + "Password": "testing" + }, + "Deluge": { + "Enabled": false, + "Url": "http://localhost:8112", + "Password": "testing" + }, + "Transmission": { + "Enabled": false, + "Url": "http://localhost:9091", + "Username": "test", + "Password": "testing" + }, + "Sonarr": { + "Enabled": true, + "Instances": [ + { + "Url": "http://localhost:8989", + "ApiKey": "96736c3eb3144936b8f1d62d27be8cee" + } + ] + }, + "Radarr": { + "Enabled": true, + "Instances": [ + { + "Url": "http://localhost:7878", + "ApiKey": "705b553732ab4167ab23909305d60600" + } + ] } } diff --git a/code/Executable/appsettings.json b/code/Executable/appsettings.json index ad174238..725a028f 100644 --- a/code/Executable/appsettings.json +++ b/code/Executable/appsettings.json @@ -8,13 +8,40 @@ } }, "Triggers": { - "QueueCleaner": "0 0/5 * * * ?" + "QueueCleaner": "0 0/5 * * * ?", + "ContentBlocker": "0 0/5 * * * ?" + }, + "ContentBlocker": { + "Enabled": false, + "Blacklist": { + "Enabled": false, + "Path": "" + }, + "Whitelist": { + "Enabled": false, + "Path": "" + } + }, + "QueueCleaner": { + "Enabled": true }, "qBittorrent": { + "Enabled": true, "Url": "http://localhost:8080", "Username": "", "Password": "" }, + "Deluge": { + "Enabled": false, + "Url": "http://localhost:8112", + "Password": "testing" + }, + "Transmission": { + "Enabled": false, + "Url": "http://localhost:9091", + "Username": "test", + "Password": "testing" + }, "Sonarr": { "Enabled": true, "Instances": [ diff --git a/code/Infrastructure/Infrastructure.csproj b/code/Infrastructure/Infrastructure.csproj index 31441397..31d7c618 100644 --- a/code/Infrastructure/Infrastructure.csproj +++ b/code/Infrastructure/Infrastructure.csproj @@ -12,6 +12,7 @@ + diff --git a/code/Infrastructure/Verticals/Arr/ArrQueueIterator.cs b/code/Infrastructure/Verticals/Arr/ArrQueueIterator.cs new file mode 100644 index 00000000..9f70fe34 --- /dev/null +++ b/code/Infrastructure/Verticals/Arr/ArrQueueIterator.cs @@ -0,0 +1,53 @@ +using Common.Configuration; +using Domain.Arr.Queue; +using Microsoft.Extensions.Logging; + +namespace Infrastructure.Verticals.Arr; + +public sealed class ArrQueueIterator +{ + private readonly ILogger _logger; + + public ArrQueueIterator(ILogger logger) + { + _logger = logger; + } + + public async Task Iterate(ArrClient arrClient, ArrInstance arrInstance, Func, Task> action) + { + const ushort maxPage = 100; + ushort page = 1; + int totalRecords = 0; + int processedRecords = 0; + + do + { + QueueListResponse queueResponse = await arrClient.GetQueueItemsAsync(arrInstance, page); + + if (totalRecords is 0) + { + totalRecords = queueResponse.TotalRecords; + + _logger.LogInformation( + "{items} items found in queue | {url}", + queueResponse.TotalRecords, arrInstance.Url); + } + + if (queueResponse.Records.Count is 0) + { + break; + } + + await action(queueResponse.Records); + + processedRecords += queueResponse.Records.Count; + + if (processedRecords >= totalRecords) + { + break; + } + + page++; + } while (processedRecords < totalRecords && page < maxPage); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/Arr/RadarrClient.cs b/code/Infrastructure/Verticals/Arr/RadarrClient.cs index 95067b1b..16fe852e 100644 --- a/code/Infrastructure/Verticals/Arr/RadarrClient.cs +++ b/code/Infrastructure/Verticals/Arr/RadarrClient.cs @@ -1,6 +1,6 @@ using System.Text; using Common.Configuration; -using Domain.Radarr; +using Domain.Models.Radarr; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/code/Infrastructure/Verticals/Arr/SonarrClient.cs b/code/Infrastructure/Verticals/Arr/SonarrClient.cs index bcfaab47..fd3731ae 100644 --- a/code/Infrastructure/Verticals/Arr/SonarrClient.cs +++ b/code/Infrastructure/Verticals/Arr/SonarrClient.cs @@ -1,6 +1,6 @@ using System.Text; using Common.Configuration; -using Domain.Sonarr; +using Domain.Models.Sonarr; using Microsoft.Extensions.Logging; using Newtonsoft.Json; diff --git a/code/Infrastructure/Verticals/ContentBlocker/BlocklistProvider.cs b/code/Infrastructure/Verticals/ContentBlocker/BlocklistProvider.cs new file mode 100644 index 00000000..c823fb68 --- /dev/null +++ b/code/Infrastructure/Verticals/ContentBlocker/BlocklistProvider.cs @@ -0,0 +1,124 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; +using Common.Configuration.ContentBlocker; +using Domain.Enums; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Infrastructure.Verticals.ContentBlocker; + +public sealed class BlocklistProvider +{ + private readonly ILogger _logger; + private readonly ContentBlockerConfig _config; + private readonly HttpClient _httpClient; + + public BlocklistType BlocklistType { get; } + + public List Patterns { get; } = []; + + public List Regexes { get; } = []; + + public BlocklistProvider( + ILogger logger, + IOptions config, + IHttpClientFactory httpClientFactory) + { + _logger = logger; + _config = config.Value; + _httpClient = httpClientFactory.CreateClient(); + + _config.Validate(); + + if (_config.Blacklist?.Enabled is true) + { + BlocklistType = BlocklistType.Blacklist; + } + + if (_config.Whitelist?.Enabled is true) + { + BlocklistType = BlocklistType.Whitelist; + } + } + + public async Task LoadBlocklistAsync() + { + if (Patterns.Count > 0 || Regexes.Count > 0) + { + _logger.LogDebug("blocklist already loaded"); + return; + } + + try + { + await LoadPatternsAndRegexesAsync(); + } + catch + { + _logger.LogError("failed to load {type}", BlocklistType.ToString()); + throw; + } + } + + private async Task LoadPatternsAndRegexesAsync() + { + string[] patterns; + + if (BlocklistType is BlocklistType.Blacklist) + { + patterns = await ReadContentAsync(_config.Blacklist.Path); + } + else + { + patterns = await ReadContentAsync(_config.Whitelist.Path); + } + + long startTime = Stopwatch.GetTimestamp(); + ParallelOptions options = new() { MaxDegreeOfParallelism = 5 }; + + Parallel.ForEach(patterns, options, pattern => + { + try + { + Regex regex = new(pattern, RegexOptions.Compiled); + Regexes.Add(regex); + } + catch (ArgumentException) + { + Patterns.Add(pattern); + } + }); + + TimeSpan elapsed = Stopwatch.GetElapsedTime(startTime); + + _logger.LogDebug("loaded {count} patterns", Patterns.Count); + _logger.LogDebug("loaded {count} regexes", Regexes.Count); + _logger.LogDebug("blocklist loaded in {elapsed} ms", elapsed.TotalMilliseconds); + } + + private async Task ReadContentAsync(string path) + { + if (Uri.TryCreate(path, UriKind.Absolute, out var uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) + { + // http(s) url + return await ReadFromUrlAsync(path); + } + + if (File.Exists(path)) + { + // local file path + return await File.ReadAllLinesAsync(path); + } + + throw new ArgumentException($"blocklist not found | {path}"); + } + + private async Task ReadFromUrlAsync(string url) + { + using HttpResponseMessage response = await _httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + + return (await response.Content.ReadAsStringAsync()) + .Split(['\r','\n'], StringSplitOptions.RemoveEmptyEntries); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs new file mode 100644 index 00000000..ab457fdb --- /dev/null +++ b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs @@ -0,0 +1,98 @@ +using Common.Configuration; +using Domain.Arr.Queue; +using Domain.Enums; +using Infrastructure.Verticals.Arr; +using Infrastructure.Verticals.DownloadClient; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Infrastructure.Verticals.ContentBlocker; + +public sealed class ContentBlocker : IDisposable +{ + private readonly ILogger _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 IDownloadService _downloadService; + + public ContentBlocker( + ILogger logger, + IOptions sonarrConfig, + IOptions radarrConfig, + SonarrClient sonarrClient, + RadarrClient radarrClient, + ArrQueueIterator arrArrQueueIterator, + BlocklistProvider blocklistProvider, + DownloadServiceFactory downloadServiceFactory + ) + { + _logger = logger; + _sonarrConfig = sonarrConfig.Value; + _radarrConfig = radarrConfig.Value; + _sonarrClient = sonarrClient; + _radarrClient = radarrClient; + _arrArrQueueIterator = arrArrQueueIterator; + _blocklistProvider = blocklistProvider; + _downloadService = downloadServiceFactory.CreateDownloadClient(); + } + + public async Task ExecuteAsync() + { + await _blocklistProvider.LoadBlocklistAsync(); + await _downloadService.LoginAsync(); + + await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr); + await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr); + } + + 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 block content for {type} instance | {url}", instanceType, arrInstance.Url); + } + } + } + + private async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType) + { + ArrClient arrClient = GetClient(instanceType); + + await _arrArrQueueIterator.Iterate(arrClient, instance, async items => + { + foreach (QueueRecord record in items) + { + _logger.LogDebug("searching unwanted files for {title}", record.Title); + await _downloadService.BlockUnwantedFilesAsync(record.DownloadId); + } + }); + } + + private ArrClient GetClient(InstanceType type) => + type switch + { + InstanceType.Sonarr => _sonarrClient, + InstanceType.Radarr => _radarrClient, + _ => throw new NotImplementedException($"instance type {type} is not yet supported") + }; + + public void Dispose() + { + _downloadService.Dispose(); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/ContentBlocker/FilenameEvaluator.cs b/code/Infrastructure/Verticals/ContentBlocker/FilenameEvaluator.cs new file mode 100644 index 00000000..ef17eddc --- /dev/null +++ b/code/Infrastructure/Verticals/ContentBlocker/FilenameEvaluator.cs @@ -0,0 +1,81 @@ +using Domain.Enums; +using Microsoft.Extensions.Logging; + +namespace Infrastructure.Verticals.ContentBlocker; + +public sealed class FilenameEvaluator +{ + private readonly ILogger _logger; + private readonly BlocklistProvider _blocklistProvider; + + public FilenameEvaluator(ILogger logger, BlocklistProvider blocklistProvider) + { + _logger = logger; + _blocklistProvider = blocklistProvider; + } + + // TODO create unit tests + public bool IsValid(string filename) + { + return IsValidAgainstPatterns(filename) && IsValidAgainstRegexes(filename); + } + + private bool IsValidAgainstPatterns(string filename) + { + if (_blocklistProvider.Patterns.Count is 0) + { + return true; + } + + return _blocklistProvider.BlocklistType switch + { + BlocklistType.Blacklist => !_blocklistProvider.Patterns.Any(pattern => MatchesPattern(filename, pattern)), + BlocklistType.Whitelist => _blocklistProvider.Patterns.Any(pattern => MatchesPattern(filename, pattern)), + _ => true + }; + } + + private bool IsValidAgainstRegexes(string filename) + { + if (_blocklistProvider.Regexes.Count is 0) + { + return true; + } + + return _blocklistProvider.BlocklistType switch + { + BlocklistType.Blacklist => !_blocklistProvider.Regexes.Any(regex => regex.IsMatch(filename)), + BlocklistType.Whitelist => _blocklistProvider.Regexes.Any(regex => regex.IsMatch(filename)), + _ => true + }; + } + + private static bool MatchesPattern(string filename, string pattern) + { + bool hasStartWildcard = pattern.StartsWith('*'); + bool hasEndWildcard = pattern.EndsWith('*'); + + if (hasStartWildcard && hasEndWildcard) + { + return filename.Contains( + pattern.Substring(1, pattern.Length - 2), + StringComparison.InvariantCultureIgnoreCase + ); + } + + if (hasStartWildcard) + { + return filename.EndsWith(pattern.Substring(1), StringComparison.InvariantCultureIgnoreCase); + } + + if (hasEndWildcard) + { + return filename.StartsWith( + pattern.Substring(0, pattern.Length - 1), + StringComparison.InvariantCultureIgnoreCase + ); + } + + return filename == pattern; + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs new file mode 100644 index 00000000..94dc678f --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs @@ -0,0 +1,138 @@ +using System.Net.Http.Headers; +using System.Text.Json.Serialization; +using Common.Configuration; +using Domain.Models.Deluge.Exceptions; +using Domain.Models.Deluge.Request; +using Domain.Models.Deluge.Response; +using Infrastructure.Verticals.DownloadClient.Deluge.Extensions; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Infrastructure.Verticals.DownloadClient.Deluge; + +public sealed class DelugeClient +{ + private readonly DelugeConfig _config; + private readonly HttpClient _httpClient; + + public DelugeClient(IOptions config, IHttpClientFactory httpClientFactory) + { + _config = config.Value; + _httpClient = httpClientFactory.CreateClient(nameof(DelugeService)); + } + + public async Task LoginAsync() + { + return await SendRequest("auth.login", _config.Password); + } + + public async Task Logout() + { + return await SendRequest("auth.delete_session"); + } + + public async Task> ListTorrents(Dictionary? filters = null) + { + filters ??= new Dictionary(); + var keys = typeof(DelugeTorrent).GetAllJsonPropertyFromType(); + Dictionary result = + await SendRequest>("core.get_torrents_status", filters, keys); + return result.Values.ToList(); + } + + public async Task> ListTorrentsExtended(Dictionary? filters = null) + { + filters ??= new Dictionary(); + var keys = typeof(DelugeTorrentExtended).GetAllJsonPropertyFromType(); + Dictionary result = + await SendRequest>("core.get_torrents_status", filters, keys); + return result.Values.ToList(); + } + + public async Task GetTorrent(string hash) + { + List torrents = await ListTorrents(new Dictionary() { { "hash", hash } }); + return torrents.FirstOrDefault(); + } + + public async Task GetTorrentExtended(string hash) + { + List torrents = + await ListTorrentsExtended(new Dictionary { { "hash", hash } }); + return torrents.FirstOrDefault(); + } + + public async Task GetTorrentFiles(string hash) + { + return await SendRequest("web.get_torrent_files", hash); + } + + public async Task ChangeFilesPriority(string hash, List priorities) + { + Dictionary> filePriorities = new() + { + { "file_priorities", priorities } + }; + + await SendRequest>("core.set_torrent_options", hash, filePriorities); + } + + private async Task PostJson(String json) + { + StringContent content = new StringContent(json); + content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json"); + + var responseMessage = await _httpClient.PostAsync(new Uri(_config.Url, "/json"), content); + responseMessage.EnsureSuccessStatusCode(); + + var responseJson = await responseMessage.Content.ReadAsStringAsync(); + return responseJson; + } + + private DelugeRequest CreateRequest(string method, params object[] parameters) + { + if (String.IsNullOrWhiteSpace(method)) + { + throw new ArgumentException(nameof(method)); + } + + return new DelugeRequest(1, method, parameters); + } + + public async Task SendRequest(string method, params object[] parameters) + { + return await SendRequest(CreateRequest(method, parameters)); + } + + public async Task SendRequest(DelugeRequest webRequest) + { + var requestJson = JsonConvert.SerializeObject(webRequest, Formatting.None, new JsonSerializerSettings + { + NullValueHandling = webRequest.NullValueHandling + }); + + var responseJson = await PostJson(requestJson); + var settings = new JsonSerializerSettings + { + Error = (_, args) => + { + // Suppress the error and continue + args.ErrorContext.Handled = true; + } + }; + + DelugeResponse? webResponse = JsonConvert.DeserializeObject>(responseJson, settings); + + if (webResponse?.Error != null) + { + throw new DelugeClientException(webResponse.Error.Message); + } + + if (webResponse?.ResponseId != webRequest.RequestId) + { + throw new DelugeClientException("desync"); + } + + return webResponse.Result; + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs new file mode 100644 index 00000000..4d83c9bf --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs @@ -0,0 +1,164 @@ +using Common.Configuration; +using Domain.Models.Deluge.Response; +using Infrastructure.Verticals.ContentBlocker; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Infrastructure.Verticals.DownloadClient.Deluge; + +public sealed class DelugeService : IDownloadService +{ + private readonly ILogger _logger; + private readonly DelugeClient _client; + private readonly FilenameEvaluator _filenameEvaluator; + + public DelugeService( + ILogger logger, + IOptions config, + IHttpClientFactory httpClientFactory, + FilenameEvaluator filenameEvaluator + ) + { + _logger = logger; + _client = new (config, httpClientFactory); + _filenameEvaluator = filenameEvaluator; + } + + public async Task LoginAsync() + { + await _client.LoginAsync(); + } + + public async Task ShouldRemoveFromArrQueueAsync(string hash) + { + hash = hash.ToLowerInvariant(); + + DelugeContents? contents = null; + + if (!await HasMinimalStatus(hash)) + { + return false; + } + + try + { + contents = await _client.GetTorrentFiles(hash); + } + catch (Exception exception) + { + _logger.LogDebug(exception, "failed to find torrent {hash} in the download client", hash); + } + + if (contents is null) + { + return false; + } + + bool shouldRemove = true; + + ProcessFiles(contents.Contents, (_, file) => + { + if (file.Priority > 0) + { + shouldRemove = false; + } + }); + + return shouldRemove; + } + + public async Task BlockUnwantedFilesAsync(string hash) + { + hash = hash.ToLowerInvariant(); + + if (!await HasMinimalStatus(hash)) + { + return; + } + + DelugeContents? contents = null; + + try + { + contents = await _client.GetTorrentFiles(hash); + } + catch (Exception exception) + { + _logger.LogDebug(exception, "failed to find torrent {hash} in the download client", hash); + } + + if (contents is null) + { + return; + } + + Dictionary priorities = []; + bool hasPriorityUpdates = false; + + ProcessFiles(contents.Contents, (name, file) => + { + int priority = file.Priority; + + if (file.Priority is not 0 && !_filenameEvaluator.IsValid(name)) + { + priority = 0; + hasPriorityUpdates = true; + _logger.LogInformation("unwanted file found | {file}", file.Path); + } + + priorities.Add(file.Index, priority); + }); + + if (!hasPriorityUpdates) + { + return; + } + + _logger.LogDebug("changing priorities | torrent {hash}", hash); + + List sortedPriorities = priorities + .OrderBy(x => x.Key) + .Select(x => x.Value) + .ToList(); + + await _client.ChangeFilesPriority(hash, sortedPriorities); + } + + private async Task HasMinimalStatus(string hash) + { + DelugeMinimalStatus? status = await _client.SendRequest( + "web.get_torrent_status", + hash, + new[] { "hash" } + ); + + if (status?.Hash is null) + { + _logger.LogDebug("failed to find torrent {hash} in the download client", hash); + return false; + } + + return true; + } + + private static void ProcessFiles(Dictionary contents, Action processFile) + { + foreach (var (name, data) in contents) + { + switch (data.Type) + { + case "file": + processFile(name, data); + break; + case "dir" when data.Contents is not null: + // Recurse into subdirectories + ProcessFiles(data.Contents, processFile); + break; + } + } + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/Deluge/Extensions/DelugeExtensions.cs b/code/Infrastructure/Verticals/DownloadClient/Deluge/Extensions/DelugeExtensions.cs new file mode 100644 index 00000000..9375c59d --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/Deluge/Extensions/DelugeExtensions.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Infrastructure.Verticals.DownloadClient.Deluge.Extensions; + +internal static class DelugeExtensions +{ + public static List GetAllJsonPropertyFromType(this Type t) + { + var type = typeof(JsonPropertyAttribute); + var props = t.GetProperties() + .Where(prop => Attribute.IsDefined(prop, type)) + .ToList(); + + return props + .Select(x => x.GetCustomAttributes(type, true).Single()) + .Cast() + .Select(x => x.PropertyName) + .ToList(); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs new file mode 100644 index 00000000..89e5f9ae --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs @@ -0,0 +1,65 @@ +using Common.Configuration; +using Infrastructure.Verticals.DownloadClient.Deluge; +using Infrastructure.Verticals.DownloadClient.QBittorrent; +using Infrastructure.Verticals.DownloadClient.Transmission; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Infrastructure.Verticals.DownloadClient; + +public sealed class DownloadServiceFactory +{ + private readonly QBitConfig _qBitConfig; + private readonly DelugeConfig _delugeConfig; + private readonly TransmissionConfig _transmissionConfig; + private readonly IServiceProvider _serviceProvider; + + public DownloadServiceFactory( + IOptions qBitConfig, + IOptions delugeConfig, + IOptions transmissionConfig, + IServiceProvider serviceProvider) + { + _qBitConfig = qBitConfig.Value; + _delugeConfig = delugeConfig.Value; + _transmissionConfig = transmissionConfig.Value; + _serviceProvider = serviceProvider; + + _qBitConfig.Validate(); + _delugeConfig.Validate(); + _transmissionConfig.Validate(); + + int enabledCount = new[] { _qBitConfig.Enabled, _delugeConfig.Enabled, _transmissionConfig.Enabled } + .Count(enabled => enabled); + + if (enabledCount > 1) + { + throw new Exception("only one download client can be enabled"); + } + + if (enabledCount == 0) + { + throw new Exception("no download client is enabled"); + } + } + + public IDownloadService CreateDownloadClient() + { + if (_qBitConfig.Enabled) + { + return _serviceProvider.GetRequiredService(); + } + + if (_delugeConfig.Enabled) + { + return _serviceProvider.GetRequiredService(); + } + + if (_transmissionConfig.Enabled) + { + return _serviceProvider.GetRequiredService(); + } + + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs new file mode 100644 index 00000000..3fcdac95 --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs @@ -0,0 +1,10 @@ +namespace Infrastructure.Verticals.DownloadClient; + +public interface IDownloadService : IDisposable +{ + public Task LoginAsync(); + + public Task ShouldRemoveFromArrQueueAsync(string hash); + + public Task BlockUnwantedFilesAsync(string hash); +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs new file mode 100644 index 00000000..07a4e97d --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs @@ -0,0 +1,95 @@ +using Common.Configuration; +using Infrastructure.Verticals.ContentBlocker; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using QBittorrent.Client; + +namespace Infrastructure.Verticals.DownloadClient.QBittorrent; + +public sealed class QBitService : IDownloadService +{ + private readonly ILogger _logger; + private readonly QBitConfig _config; + private readonly QBittorrentClient _client; + private readonly FilenameEvaluator _filenameEvaluator; + + public QBitService( + ILogger logger, + IOptions config, + FilenameEvaluator filenameEvaluator + ) + { + _logger = logger; + _config = config.Value; + _client = new(_config.Url); + _filenameEvaluator = filenameEvaluator; + } + + public async Task LoginAsync() + { + await _client.LoginAsync(_config.Username, _config.Password); + } + + public async Task ShouldRemoveFromArrQueueAsync(string hash) + { + TorrentInfo? torrent = (await _client.GetTorrentListAsync(new TorrentListQuery { Hashes = [hash] })) + .FirstOrDefault(); + + if (torrent is null) + { + return false; + } + + // if all files were blocked by qBittorrent + if (torrent is { CompletionOn: not null, Downloaded: null or 0 }) + { + return true; + } + + IReadOnlyList? files = await _client.GetTorrentContentsAsync(hash); + + if (files is null) + { + return false; + } + + // if all files are marked as skip + if (files.All(x => x.Priority is TorrentContentPriority.Skip)) + { + return true; + } + + return false; + } + + public async Task BlockUnwantedFilesAsync(string hash) + { + IReadOnlyList? files = await _client.GetTorrentContentsAsync(hash); + + if (files is null) + { + return; + } + + foreach (TorrentContent file in files) + { + if (!file.Index.HasValue) + { + continue; + } + + if (file.Priority is TorrentContentPriority.Skip || _filenameEvaluator.IsValid(file.Name)) + { + continue; + } + + _logger.LogInformation("unwanted file found | {file}", file.Name); + await _client.SetFilePriorityAsync(hash, file.Index.Value, TorrentContentPriority.Skip); + } + } + + public void Dispose() + { + _client.Dispose(); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs new file mode 100644 index 00000000..c0cc9c36 --- /dev/null +++ b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs @@ -0,0 +1,141 @@ +using Common.Configuration; +using Infrastructure.Verticals.ContentBlocker; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Transmission.API.RPC; +using Transmission.API.RPC.Arguments; +using Transmission.API.RPC.Entity; + +namespace Infrastructure.Verticals.DownloadClient.Transmission; + +public sealed class TransmissionService : IDownloadService +{ + private readonly ILogger _logger; + private readonly TransmissionConfig _config; + private readonly Client _client; + private readonly FilenameEvaluator _filenameEvaluator; + private TorrentInfo[]? _torrentsCache; + + public TransmissionService( + ILogger logger, + IOptions config, + FilenameEvaluator filenameEvaluator + ) + { + _logger = logger; + _config = config.Value; + _client = new( + new Uri(_config.Url, "/transmission/rpc").ToString(), + login: _config.Username, + password: _config.Password + ); + _filenameEvaluator = filenameEvaluator; + } + + public async Task LoginAsync() + { + await _client.GetSessionInformationAsync(); + } + + public async Task ShouldRemoveFromArrQueueAsync(string hash) + { + TorrentInfo? torrent = await GetTorrentAsync(hash); + + if (torrent is null) + { + return false; + } + + foreach (TransmissionTorrentFileStats? stats in torrent.FileStats ?? []) + { + if (!stats.Wanted.HasValue) + { + // if any files stats are missing, do not remove + return false; + } + + if (stats.Wanted.HasValue && stats.Wanted.Value) + { + // if any files are wanted, do not remove + return false; + } + } + + // remove if all files are unwanted + return true; + } + + public async Task BlockUnwantedFilesAsync(string hash) + { + TorrentInfo? torrent = await GetTorrentAsync(hash); + + if (torrent?.FileStats is null || torrent.Files is null) + { + return; + } + + List unwantedFiles = []; + + for (int i = 0; i < torrent.Files.Length; i++) + { + if (torrent.FileStats?[i].Wanted == null) + { + continue; + } + + if (!torrent.FileStats[i].Wanted.Value || _filenameEvaluator.IsValid(torrent.Files[i].Name)) + { + continue; + } + + _logger.LogInformation("unwanted file found | {file}", torrent.Files[i].Name); + unwantedFiles.Add(i); + } + + if (unwantedFiles.Count is 0) + { + return; + } + + _logger.LogDebug("changing priorities | torrent {hash}", hash); + + await _client.TorrentSetAsync(new TorrentSettings + { + Ids = [ torrent.Id ], + FilesUnwanted = unwantedFiles.ToArray(), + }); + } + + public void Dispose() + { + } + + private async Task GetTorrentAsync(string hash) + { + TorrentInfo? torrent = _torrentsCache? + .FirstOrDefault(x => x.HashString.Equals(hash, StringComparison.InvariantCultureIgnoreCase)); + + if (_torrentsCache is null || torrent is null) + { + string[] fields = [TorrentFields.FILES, TorrentFields.FILE_STATS, TorrentFields.HASH_STRING, TorrentFields.ID]; + + // refresh cache + _torrentsCache = (await _client.TorrentGetAsync(fields)) + ?.Torrents; + } + + if (_torrentsCache?.Length is null or 0) + { + _logger.LogDebug("could not list torrents | {url}", _config.Url); + } + + torrent = _torrentsCache?.FirstOrDefault(x => x.HashString.Equals(hash, StringComparison.InvariantCultureIgnoreCase)); + + if (torrent is null) + { + _logger.LogDebug("could not find torrent | {hash} | {url}", hash, _config.Url); + } + + return torrent; + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs new file mode 100644 index 00000000..fdacb341 --- /dev/null +++ b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs @@ -0,0 +1,126 @@ +using Common.Configuration; +using Domain.Arr.Queue; +using Domain.Enums; +using Infrastructure.Verticals.Arr; +using Infrastructure.Verticals.DownloadClient; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Infrastructure.Verticals.QueueCleaner; + +public sealed class QueueCleaner : IDisposable +{ + private readonly ILogger _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( + ILogger logger, + IOptions sonarrConfig, + IOptions radarrConfig, + SonarrClient sonarrClient, + RadarrClient radarrClient, + ArrQueueIterator arrArrQueueIterator, + DownloadServiceFactory downloadServiceFactory + ) + { + _logger = logger; + _sonarrConfig = sonarrConfig.Value; + _radarrConfig = radarrConfig.Value; + _sonarrClient = sonarrClient; + _radarrClient = radarrClient; + _arrArrQueueIterator = arrArrQueueIterator; + _downloadService = downloadServiceFactory.CreateDownloadClient(); + } + + public async Task ExecuteAsync() + { + await _downloadService.LoginAsync(); + + 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 itemsToBeRefreshed = []; + ArrClient arrClient = GetClient(instanceType); + + await _arrArrQueueIterator.Iterate(arrClient, instance, async items => + { + foreach (QueueRecord record in items) + { + if (record.Protocol is not "torrent") + { + continue; + } + + if (string.IsNullOrEmpty(record.DownloadId)) + { + _logger.LogDebug("skip | download id is null for {title}", record.Title); + continue; + } + + if (!await _downloadService.ShouldRemoveFromArrQueueAsync(record.DownloadId)) + { + _logger.LogInformation("skip | {title}", record.Title); + continue; + } + + itemsToBeRefreshed.Add(GetRecordId(instanceType, record)); + + await arrClient.DeleteQueueItemAsync(instance, record); + } + }); + + await arrClient.RefreshItemsAsync(instance, 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(); + } +} \ No newline at end of file diff --git a/code/Infrastructure/Verticals/QueueCleaner/QueueCleanerHandler.cs b/code/Infrastructure/Verticals/QueueCleaner/QueueCleanerHandler.cs deleted file mode 100644 index 1b86197c..00000000 --- a/code/Infrastructure/Verticals/QueueCleaner/QueueCleanerHandler.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Common.Configuration; -using Domain.Arr.Enums; -using Domain.Arr.Queue; -using Infrastructure.Verticals.Arr; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using QBittorrent.Client; - -namespace Infrastructure.Verticals.QueueCleaner; - -public sealed class QueueCleanerHandler -{ - private readonly ILogger _logger; - private readonly QBitConfig _qBitConfig; - private readonly SonarrConfig _sonarrConfig; - private readonly RadarrConfig _radarrConfig; - private readonly SonarrClient _sonarrClient; - private readonly RadarrClient _radarrClient; - - public QueueCleanerHandler( - ILogger logger, - IOptions qBitConfig, - IOptions sonarrConfig, - IOptions radarrConfig, - SonarrClient sonarrClient, - RadarrClient radarrClient) - { - _logger = logger; - _qBitConfig = qBitConfig.Value; - _sonarrConfig = sonarrConfig.Value; - _radarrConfig = radarrConfig.Value; - _sonarrClient = sonarrClient; - _radarrClient = radarrClient; - } - - public async Task HandleAsync() - { - QBittorrentClient qBitClient = new(_qBitConfig.Url); - await qBitClient.LoginAsync(_qBitConfig.Username, _qBitConfig.Password); - - await ProcessArrConfigAsync(qBitClient, _sonarrConfig, InstanceType.Sonarr); - await ProcessArrConfigAsync(qBitClient, _radarrConfig, InstanceType.Radarr); - } - - private async Task ProcessArrConfigAsync(QBittorrentClient qBitClient, ArrConfig config, InstanceType instanceType) - { - if (!config.Enabled) - { - return; - } - - foreach (ArrInstance arrInstance in config.Instances) - { - try - { - await ProcessInstanceAsync(qBitClient, arrInstance, instanceType); - } - catch (Exception exception) - { - _logger.LogError(exception, "failed to clean {type} instance | {url}", instanceType, arrInstance.Url); - } - } - } - - private async Task ProcessInstanceAsync(QBittorrentClient qBitClient, ArrInstance instance, InstanceType instanceType) - { - ushort page = 1; - int totalRecords = 0; - int processedRecords = 0; - HashSet itemsToBeRefreshed = []; - ArrClient arrClient = GetClient(instanceType); - - do - { - QueueListResponse queueResponse = await arrClient.GetQueueItemsAsync(instance, page); - - if (totalRecords is 0) - { - totalRecords = queueResponse.TotalRecords; - - _logger.LogInformation( - "{items} items found in queue | {url}", - queueResponse.TotalRecords, instance.Url); - } - - foreach (QueueRecord record in queueResponse.Records) - { - if (record.Protocol is not "torrent") - { - continue; - } - - TorrentInfo? torrent = (await qBitClient.GetTorrentListAsync(new TorrentListQuery { Hashes = [record.DownloadId] })) - .FirstOrDefault(); - - if (torrent is not { CompletionOn: not null, Downloaded: null or 0 }) - { - _logger.LogInformation("skip | {torrent}", record.Title); - continue; - } - - itemsToBeRefreshed.Add(GetRecordId(instanceType, record)); - - await arrClient.DeleteQueueItemAsync(instance, record); - } - - if (queueResponse.Records.Count is 0) - { - break; - } - - processedRecords += queueResponse.Records.Count; - - if (processedRecords >= totalRecords) - { - break; - } - - page++; - } while (processedRecords < totalRecords); - - await arrClient.RefreshItemsAsync(instance, 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 - { - InstanceType.Sonarr => record.SeriesId, - InstanceType.Radarr => record.MovieId, - _ => throw new NotImplementedException($"instance type {type} is not yet supported") - }; -} \ No newline at end of file diff --git a/code/test/data/cleanuperr/config/blacklist b/code/test/data/cleanuperr/config/blacklist new file mode 100644 index 00000000..8e5f9f4f --- /dev/null +++ b/code/test/data/cleanuperr/config/blacklist @@ -0,0 +1,2 @@ +.*sample.* +*.zipx \ No newline at end of file diff --git a/code/test/data/cleanuperr/config/whitelist b/code/test/data/cleanuperr/config/whitelist new file mode 100644 index 00000000..71b0ffab --- /dev/null +++ b/code/test/data/cleanuperr/config/whitelist @@ -0,0 +1 @@ +*.mkv \ No newline at end of file diff --git a/code/test/data/deluge/config/archive/state-2024-11-14T13-53-18.tar.xz b/code/test/data/deluge/config/archive/state-2024-11-14T13-53-18.tar.xz new file mode 100644 index 00000000..e70043e7 Binary files /dev/null and b/code/test/data/deluge/config/archive/state-2024-11-14T13-53-18.tar.xz differ diff --git a/code/test/data/deluge/config/auth b/code/test/data/deluge/config/auth new file mode 100644 index 00000000..5e343ee3 --- /dev/null +++ b/code/test/data/deluge/config/auth @@ -0,0 +1 @@ +localclient:da4d4b43be734d48c1bb8b9ab0e39894520994e3:10 diff --git a/code/test/data/deluge/config/blocklist.conf b/code/test/data/deluge/config/blocklist.conf new file mode 100644 index 00000000..03e11cc4 --- /dev/null +++ b/code/test/data/deluge/config/blocklist.conf @@ -0,0 +1,15 @@ +{ + "file": 1, + "format": 1 +}{ + "check_after_days": 4, + "last_update": 0.0, + "list_compression": "", + "list_size": 0, + "list_type": "", + "load_on_start": false, + "timeout": 180, + "try_times": 3, + "url": "", + "whitelisted": [] +} \ No newline at end of file diff --git a/code/test/data/deluge/config/core.conf b/code/test/data/deluge/config/core.conf new file mode 100644 index 00000000..c6992c93 --- /dev/null +++ b/code/test/data/deluge/config/core.conf @@ -0,0 +1,97 @@ +{ + "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": false, + "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 +} \ No newline at end of file diff --git a/code/test/data/deluge/config/core.conf.bak b/code/test/data/deluge/config/core.conf.bak new file mode 100644 index 00000000..266ee89e --- /dev/null +++ b/code/test/data/deluge/config/core.conf.bak @@ -0,0 +1,97 @@ +{ + "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 +} \ No newline at end of file diff --git a/code/test/data/deluge/config/hostlist.conf b/code/test/data/deluge/config/hostlist.conf new file mode 100644 index 00000000..07bea729 --- /dev/null +++ b/code/test/data/deluge/config/hostlist.conf @@ -0,0 +1,14 @@ +{ + "file": 3, + "format": 1 +}{ + "hosts": [ + [ + "b5408e9794dd432789c55d8c46d15275", + "127.0.0.1", + 58846, + "localclient", + "da4d4b43be734d48c1bb8b9ab0e39894520994e3" + ] + ] +} \ No newline at end of file diff --git a/code/test/data/deluge/config/label.conf b/code/test/data/deluge/config/label.conf new file mode 100644 index 00000000..ca8d6e45 --- /dev/null +++ b/code/test/data/deluge/config/label.conf @@ -0,0 +1,51 @@ +{ + "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": { + "2b2ec156461d77bc48b8fe4d62cede50dcdff8e0": "radarr", + "59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c": "tv-sonarr", + "5a31d5f1689f5f45fd85c275a37acd2c7b82fde1": "tv-sonarr", + "6c890ff85b5317d5df291c3c23a782774e10e6fe": "radarr", + "a4a1d1dd1db25763caa8f5e4d25ad72ef304094b": "radarr", + "b72541215214be2a1d96ef6b29ca1305f5e5e1f6": "tv-sonarr" + } +} \ No newline at end of file diff --git a/code/test/data/deluge/config/label.conf.bak b/code/test/data/deluge/config/label.conf.bak new file mode 100644 index 00000000..d6e9193a --- /dev/null +++ b/code/test/data/deluge/config/label.conf.bak @@ -0,0 +1,50 @@ +{ + "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" + } +} \ No newline at end of file diff --git a/code/test/data/deluge/config/plugins/.python-eggs/Blocklist-1.4-py3.12.egg-tmp/deluge_blocklist/data/blocklist.js b/code/test/data/deluge/config/plugins/.python-eggs/Blocklist-1.4-py3.12.egg-tmp/deluge_blocklist/data/blocklist.js new file mode 100644 index 00000000..3c10b81b --- /dev/null +++ b/code/test/data/deluge/config/plugins/.python-eggs/Blocklist-1.4-py3.12.egg-tmp/deluge_blocklist/data/blocklist.js @@ -0,0 +1,429 @@ +/** + * blocklist.js + * + * Copyright (C) Omar Alvarez 2014 + * + * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with + * the additional special exception to link portions of this program with the OpenSSL library. + * See LICENSE for more details. + * + */ + +Ext.ns('Deluge.ux.preferences'); + +/** + * @class Deluge.ux.preferences.BlocklistPage + * @extends Ext.Panel + */ +Deluge.ux.preferences.BlocklistPage = Ext.extend(Ext.Panel, { + title: _('Blocklist'), + header: false, + layout: 'fit', + border: false, + autoScroll: true, + + initComponent: function () { + Deluge.ux.preferences.BlocklistPage.superclass.initComponent.call(this); + + this.URLFset = this.add({ + xtype: 'fieldset', + border: false, + title: _('General'), + autoHeight: true, + defaultType: 'textfield', + style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;', + autoWidth: true, + labelWidth: 40, + }); + + this.URL = this.URLFset.add({ + fieldLabel: _('URL:'), + labelSeparator: '', + name: 'url', + width: '80%', + }); + + this.SettingsFset = this.add({ + xtype: 'fieldset', + border: false, + title: _('Settings'), + autoHeight: true, + defaultType: 'spinnerfield', + style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;', + autoWidth: true, + labelWidth: 160, + }); + + this.checkListDays = this.SettingsFset.add({ + fieldLabel: _('Check for new list every (days):'), + labelSeparator: '', + name: 'check_list_days', + value: 4, + decimalPrecision: 0, + width: 80, + }); + + this.chkImportOnStart = this.SettingsFset.add({ + xtype: 'checkbox', + fieldLabel: _('Import blocklist on startup'), + name: 'check_import_startup', + }); + + this.OptionsFset = this.add({ + xtype: 'fieldset', + border: false, + title: _('Options'), + autoHeight: true, + defaultType: 'button', + style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;', + autoWidth: false, + width: '80%', + labelWidth: 0, + }); + + this.checkDownload = this.OptionsFset.add({ + fieldLabel: _(''), + name: 'check_download', + xtype: 'container', + layout: 'hbox', + margins: '4 0 0 5', + items: [ + { + xtype: 'button', + text: ' Check Download and Import ', + scale: 'medium', + }, + { + xtype: 'box', + autoEl: { + tag: 'img', + src: '../icons/ok.png', + }, + margins: '4 0 0 3', + }, + ], + }); + + this.forceDownload = this.OptionsFset.add({ + fieldLabel: _(''), + name: 'force_download', + text: ' Force Download and Import ', + margins: '2 0 0 0', + //icon: '../icons/blocklist_import24.png', + scale: 'medium', + }); + + this.ProgressFset = this.add({ + xtype: 'fieldset', + border: false, + title: _('Info'), + autoHeight: true, + defaultType: 'progress', + style: 'margin-top: 1px; margin-bottom: 0px; padding-bottom: 0px;', + autoWidth: true, + labelWidth: 0, + hidden: true, + }); + + this.downProgBar = this.ProgressFset.add({ + fieldLabel: _(''), + name: 'progress_bar', + width: '90%', + }); + + this.InfoFset = this.add({ + xtype: 'fieldset', + border: false, + title: _('Info'), + autoHeight: true, + defaultType: 'label', + style: 'margin-top: 0px; margin-bottom: 0px; padding-bottom: 0px;', + labelWidth: 60, + }); + + this.lblFileSize = this.InfoFset.add({ + fieldLabel: _('File Size:'), + labelSeparator: '', + name: 'file_size', + }); + + this.lblDate = this.InfoFset.add({ + fieldLabel: _('Date:'), + labelSeparator: '', + name: 'date', + }); + + this.lblType = this.InfoFset.add({ + fieldLabel: _('Type:'), + labelSeparator: '', + name: 'type', + }); + + this.lblURL = this.InfoFset.add({ + fieldLabel: _('URL:'), + labelSeparator: '', + name: 'lbl_URL', + }); + + this.WhitelistFset = this.add({ + xtype: 'fieldset', + border: false, + title: _('Whitelist'), + autoHeight: true, + defaultType: 'editorgrid', + style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;', + autoWidth: true, + labelWidth: 0, + items: [ + { + fieldLabel: _(''), + name: 'whitelist', + margins: '2 0 5 5', + height: 100, + width: 260, + autoExpandColumn: 'ip', + viewConfig: { + emptyText: _('Add an IP...'), + deferEmptyText: false, + }, + colModel: new Ext.grid.ColumnModel({ + columns: [ + { + id: 'ip', + header: _('IP'), + dataIndex: 'ip', + sortable: true, + hideable: false, + editable: true, + editor: { + xtype: 'textfield', + }, + }, + ], + }), + selModel: new Ext.grid.RowSelectionModel({ + singleSelect: false, + moveEditorOnEnter: false, + }), + store: new Ext.data.ArrayStore({ + autoDestroy: true, + fields: [{ name: 'ip' }], + }), + listeners: { + afteredit: function (e) { + e.record.commit(); + }, + }, + setEmptyText: function (text) { + if (this.viewReady) { + this.getView().emptyText = text; + this.getView().refresh(); + } else { + Ext.apply(this.viewConfig, { emptyText: text }); + } + }, + loadData: function (data) { + this.getStore().loadData(data); + if (this.viewReady) { + this.getView().updateHeaders(); + } + }, + }, + ], + }); + + this.ipButtonsContainer = this.WhitelistFset.add({ + xtype: 'container', + layout: 'hbox', + margins: '4 0 0 5', + items: [ + { + xtype: 'button', + text: ' Add IP ', + margins: '0 5 0 0', + }, + { + xtype: 'button', + text: ' Delete IP ', + }, + ], + }); + + this.updateTask = Ext.TaskMgr.start({ + interval: 2000, + run: this.onUpdate, + scope: this, + }); + + this.on('show', this.updateConfig, this); + + this.ipButtonsContainer.getComponent(0).setHandler(this.addIP, this); + this.ipButtonsContainer.getComponent(1).setHandler(this.deleteIP, this); + + this.checkDownload.getComponent(0).setHandler(this.checkDown, this); + this.forceDownload.setHandler(this.forceDown, this); + }, + + onApply: function () { + var config = {}; + + config['url'] = this.URL.getValue(); + config['check_after_days'] = this.checkListDays.getValue(); + config['load_on_start'] = this.chkImportOnStart.getValue(); + + var ipList = []; + var store = this.WhitelistFset.getComponent(0).getStore(); + + for (var i = 0; i < store.getCount(); i++) { + var record = store.getAt(i); + var ip = record.get('ip'); + ipList.push(ip); + } + + config['whitelisted'] = ipList; + + deluge.client.blocklist.set_config(config); + }, + + onOk: function () { + this.onApply(); + }, + + onUpdate: function () { + deluge.client.blocklist.get_status({ + success: function (status) { + if (status['state'] == 'Downloading') { + this.InfoFset.hide(); + this.checkDownload.getComponent(0).setDisabled(true); + this.checkDownload.getComponent(1).hide(); + this.forceDownload.setDisabled(true); + + this.ProgressFset.show(); + this.downProgBar.updateProgress( + status['file_progress'], + 'Downloading ' + .concat((status['file_progress'] * 100).toFixed(2)) + .concat('%'), + true + ); + } else if (status['state'] == 'Importing') { + this.InfoFset.hide(); + this.checkDownload.getComponent(0).setDisabled(true); + this.checkDownload.getComponent(1).hide(); + this.forceDownload.setDisabled(true); + + this.ProgressFset.show(); + this.downProgBar.updateText( + 'Importing '.concat(status['num_blocked']) + ); + } else if (status['state'] == 'Idle') { + this.ProgressFset.hide(); + this.checkDownload.getComponent(0).setDisabled(false); + this.forceDownload.setDisabled(false); + if (status['up_to_date']) { + this.checkDownload.getComponent(1).show(); + this.checkDownload.doLayout(); + } else { + this.checkDownload.getComponent(1).hide(); + } + this.InfoFset.show(); + this.lblFileSize.setText(fsize(status['file_size'])); + this.lblDate.setText(fdate(status['file_date'])); + this.lblType.setText(status['file_type']); + this.lblURL.setText( + status['file_url'].substr(0, 40).concat('...') + ); + } + }, + scope: this, + }); + }, + + checkDown: function () { + this.onApply(); + deluge.client.blocklist.check_import(); + }, + + forceDown: function () { + this.onApply(); + deluge.client.blocklist.check_import((force = true)); + }, + + updateConfig: function () { + deluge.client.blocklist.get_config({ + success: function (config) { + this.URL.setValue(config['url']); + this.checkListDays.setValue(config['check_after_days']); + this.chkImportOnStart.setValue(config['load_on_start']); + + var data = []; + var keys = Ext.keys(config['whitelisted']); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + data.push([config['whitelisted'][key]]); + } + + this.WhitelistFset.getComponent(0).loadData(data); + }, + scope: this, + }); + + deluge.client.blocklist.get_status({ + success: function (status) { + this.lblFileSize.setText(fsize(status['file_size'])); + this.lblDate.setText(fdate(status['file_date'])); + this.lblType.setText(status['file_type']); + this.lblURL.setText( + status['file_url'].substr(0, 40).concat('...') + ); + }, + scope: this, + }); + }, + + addIP: function () { + var store = this.WhitelistFset.getComponent(0).getStore(); + var IP = store.recordType; + var i = new IP({ + ip: '', + }); + this.WhitelistFset.getComponent(0).stopEditing(); + store.insert(0, i); + this.WhitelistFset.getComponent(0).startEditing(0, 0); + }, + + deleteIP: function () { + var selections = this.WhitelistFset.getComponent(0) + .getSelectionModel() + .getSelections(); + var store = this.WhitelistFset.getComponent(0).getStore(); + + this.WhitelistFset.getComponent(0).stopEditing(); + for (var i = 0; i < selections.length; i++) store.remove(selections[i]); + store.commitChanges(); + }, + + onDestroy: function () { + Ext.TaskMgr.stop(this.updateTask); + + deluge.preferences.un('show', this.updateConfig, this); + + Deluge.ux.preferences.BlocklistPage.superclass.onDestroy.call(this); + }, +}); + +Deluge.plugins.BlocklistPlugin = Ext.extend(Deluge.Plugin, { + name: 'Blocklist', + + onDisable: function () { + deluge.preferences.removePage(this.prefsPage); + }, + + onEnable: function () { + this.prefsPage = deluge.preferences.addPage( + new Deluge.ux.preferences.BlocklistPage() + ); + }, +}); + +Deluge.registerPlugin('Blocklist', Deluge.plugins.BlocklistPlugin); diff --git a/code/test/data/deluge/config/plugins/.python-eggs/Label-0.3-py3.12.egg-tmp/deluge_label/data/label.js b/code/test/data/deluge/config/plugins/.python-eggs/Label-0.3-py3.12.egg-tmp/deluge_label/data/label.js new file mode 100644 index 00000000..a0327e39 --- /dev/null +++ b/code/test/data/deluge/config/plugins/.python-eggs/Label-0.3-py3.12.egg-tmp/deluge_label/data/label.js @@ -0,0 +1,635 @@ +/** + * label.js + * + * Copyright (C) Damien Churchill 2010 + * + * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with + * the additional special exception to link portions of this program with the OpenSSL library. + * See LICENSE for more details. + * + */ + +Ext.ns('Deluge.ux.preferences'); + +/** + * @class Deluge.ux.preferences.LabelPage + * @extends Ext.Panel + */ +Deluge.ux.preferences.LabelPage = Ext.extend(Ext.Panel, { + title: _('Label'), + layout: 'fit', + border: false, + + initComponent: function () { + Deluge.ux.preferences.LabelPage.superclass.initComponent.call(this); + fieldset = this.add({ + xtype: 'fieldset', + border: false, + title: _('Label Preferences'), + autoHeight: true, + labelWidth: 1, + defaultType: 'panel', + }); + fieldset.add({ + border: false, + bodyCfg: { + html: _( + '

The Label plugin is enabled.


' + + '

To add, remove or edit labels right-click on the Label filter ' + + 'entry in the sidebar.


' + + '

To apply a label right-click on torrent(s).

' + ), + }, + }); + }, +}); + +Ext.ns('Deluge.ux'); + +/** + * @class Deluge.ux.AddLabelWindow + * @extends Ext.Window + */ +Deluge.ux.AddLabelWindow = Ext.extend(Ext.Window, { + title: _('Add Label'), + width: 300, + height: 100, + closeAction: 'hide', + + initComponent: function () { + Deluge.ux.AddLabelWindow.superclass.initComponent.call(this); + this.addButton(_('Cancel'), this.onCancelClick, this); + this.addButton(_('Ok'), this.onOkClick, this); + + this.form = this.add({ + xtype: 'form', + height: 35, + baseCls: 'x-plain', + bodyStyle: 'padding:5px 5px 0', + defaultType: 'textfield', + labelWidth: 50, + items: [ + { + fieldLabel: _('Name'), + name: 'name', + allowBlank: false, + width: 220, + listeners: { + specialkey: { + fn: function (field, e) { + if (e.getKey() == 13) this.onOkClick(); + }, + scope: this, + }, + }, + }, + ], + }); + }, + + onCancelClick: function () { + this.hide(); + }, + + onOkClick: function () { + var label = this.form.getForm().getValues().name; + deluge.client.label.add(label, { + success: function () { + deluge.ui.update(); + this.fireEvent('labeladded', label); + }, + scope: this, + }); + this.hide(); + }, + + onHide: function (comp) { + Deluge.ux.AddLabelWindow.superclass.onHide.call(this, comp); + this.form.getForm().reset(); + }, + + onShow: function (comp) { + Deluge.ux.AddLabelWindow.superclass.onShow.call(this, comp); + this.form.getForm().findField('name').focus(false, 150); + }, +}); + +/** + * @class Deluge.ux.LabelOptionsWindow + * @extends Ext.Window + */ +Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, { + title: _('Label Options'), + width: 325, + height: 240, + closeAction: 'hide', + + initComponent: function () { + Deluge.ux.LabelOptionsWindow.superclass.initComponent.call(this); + this.addButton(_('Cancel'), this.onCancelClick, this); + this.addButton(_('Ok'), this.onOkClick, this); + + this.form = this.add({ + xtype: 'form', + }); + + this.tabs = this.form.add({ + xtype: 'tabpanel', + height: 175, + border: false, + items: [ + { + title: _('Maximum'), + items: [ + { + border: false, + items: [ + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + style: 'margin-bottom: 0px; padding-bottom: 0px;', + items: [ + { + xtype: 'checkbox', + name: 'apply_max', + fieldLabel: '', + boxLabel: _( + 'Apply per torrent max settings:' + ), + listeners: { + check: this.onFieldChecked, + }, + }, + ], + }, + { + xtype: 'fieldset', + border: false, + defaultType: 'spinnerfield', + style: 'margin-top: 0px; padding-top: 0px;', + items: [ + { + fieldLabel: _('Download Speed'), + name: 'max_download_speed', + width: 80, + disabled: true, + value: -1, + minValue: -1, + }, + { + fieldLabel: _('Upload Speed'), + name: 'max_upload_speed', + width: 80, + disabled: true, + value: -1, + minValue: -1, + }, + { + fieldLabel: _('Upload Slots'), + name: 'max_upload_slots', + width: 80, + disabled: true, + value: -1, + minValue: -1, + }, + { + fieldLabel: _('Connections'), + name: 'max_connections', + width: 80, + disabled: true, + value: -1, + minValue: -1, + }, + ], + }, + ], + }, + ], + }, + { + title: _('Queue'), + items: [ + { + border: false, + items: [ + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + style: 'margin-bottom: 0px; padding-bottom: 0px;', + items: [ + { + xtype: 'checkbox', + name: 'apply_queue', + fieldLabel: '', + boxLabel: _( + 'Apply queue settings:' + ), + listeners: { + check: this.onFieldChecked, + }, + }, + ], + }, + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + defaultType: 'checkbox', + style: 'margin-top: 0px; padding-top: 0px;', + defaults: { + style: 'margin-left: 20px', + }, + items: [ + { + boxLabel: _('Auto Managed'), + name: 'is_auto_managed', + disabled: true, + }, + { + boxLabel: _('Stop seed at ratio:'), + name: 'stop_at_ratio', + disabled: true, + }, + { + xtype: 'spinnerfield', + name: 'stop_ratio', + width: 60, + decimalPrecision: 2, + incrementValue: 0.1, + style: 'position: relative; left: 100px', + disabled: true, + }, + { + boxLabel: _('Remove at ratio'), + name: 'remove_at_ratio', + disabled: true, + }, + ], + }, + ], + }, + ], + }, + { + title: _('Folders'), + items: [ + { + border: false, + items: [ + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + style: 'margin-bottom: 0px; padding-bottom: 0px;', + items: [ + { + xtype: 'checkbox', + name: 'apply_move_completed', + fieldLabel: '', + boxLabel: _( + 'Apply folder settings:' + ), + listeners: { + check: this.onFieldChecked, + }, + }, + ], + }, + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + defaultType: 'checkbox', + labelWidth: 1, + style: 'margin-top: 0px; padding-top: 0px;', + defaults: { + style: 'margin-left: 20px', + }, + items: [ + { + boxLabel: _('Move completed to:'), + name: 'move_completed', + disabled: true, + }, + { + xtype: 'textfield', + name: 'move_completed_path', + width: 250, + disabled: true, + }, + ], + }, + ], + }, + ], + }, + { + title: _('Trackers'), + items: [ + { + border: false, + items: [ + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + style: 'margin-bottom: 0px; padding-bottom: 0px;', + items: [ + { + xtype: 'checkbox', + name: 'auto_add', + fieldLabel: '', + boxLabel: _( + 'Automatically apply label:' + ), + listeners: { + check: this.onFieldChecked, + }, + }, + ], + }, + { + xtype: 'fieldset', + border: false, + labelWidth: 1, + style: 'margin-top: 0px; padding-top: 0px;', + defaults: { + style: 'margin-left: 20px', + }, + defaultType: 'textarea', + items: [ + { + boxLabel: _('Move completed to:'), + name: 'auto_add_trackers', + width: 250, + height: 100, + disabled: true, + }, + ], + }, + ], + }, + ], + }, + ], + }); + }, + + getLabelOptions: function () { + deluge.client.label.get_options(this.label, { + success: this.gotOptions, + scope: this, + }); + }, + + gotOptions: function (options) { + this.form.getForm().setValues(options); + }, + + show: function (label) { + Deluge.ux.LabelOptionsWindow.superclass.show.call(this); + this.label = label; + this.setTitle(_('Label Options') + ': ' + this.label); + this.tabs.setActiveTab(0); + this.getLabelOptions(); + }, + + onCancelClick: function () { + this.hide(); + }, + + onOkClick: function () { + var values = this.form.getForm().getFieldValues(); + if (values['auto_add_trackers']) { + values['auto_add_trackers'] = + values['auto_add_trackers'].split('\n'); + } + deluge.client.label.set_options(this.label, values); + this.hide(); + }, + + onFieldChecked: function (field, checked) { + var fs = field.ownerCt.nextSibling(); + fs.items.each(function (field) { + field.setDisabled(!checked); + }); + }, +}); + +Ext.ns('Deluge.plugins'); + +/** + * @class Deluge.plugins.LabelPlugin + * @extends Deluge.Plugin + */ +Deluge.plugins.LabelPlugin = Ext.extend(Deluge.Plugin, { + name: 'Label', + + createMenu: function () { + this.labelMenu = new Ext.menu.Menu({ + items: [ + { + text: _('Add Label'), + iconCls: 'icon-add', + handler: this.onLabelAddClick, + scope: this, + }, + { + text: _('Remove Label'), + disabled: true, + iconCls: 'icon-remove', + handler: this.onLabelRemoveClick, + scope: this, + }, + { + text: _('Label Options'), + disabled: true, + handler: this.onLabelOptionsClick, + scope: this, + }, + ], + }); + }, + + setFilter: function (filter) { + filter.show_zero = true; + + filter.list.on('contextmenu', this.onLabelContextMenu, this); + filter.header.on('contextmenu', this.onLabelHeaderContextMenu, this); + this.filter = filter; + }, + + updateTorrentMenu: function (states) { + this.torrentMenu.removeAll(true); + this.torrentMenu.addMenuItem({ + text: _('No Label'), + label: '', + handler: this.onTorrentMenuClick, + scope: this, + }); + for (var state in states) { + if (!state || state == 'All') continue; + this.torrentMenu.addMenuItem({ + text: state, + label: state, + handler: this.onTorrentMenuClick, + scope: this, + }); + } + }, + + onDisable: function () { + deluge.sidebar.un('filtercreate', this.onFilterCreate); + deluge.sidebar.un('afterfiltercreate', this.onAfterFilterCreate); + delete Deluge.FilterPanel.templates.label; + this.deregisterTorrentStatus('label'); + deluge.menus.torrent.remove(this.tmSep); + deluge.menus.torrent.remove(this.tm); + deluge.preferences.removePage(this.prefsPage); + }, + + onEnable: function () { + this.prefsPage = deluge.preferences.addPage( + new Deluge.ux.preferences.LabelPage() + ); + this.torrentMenu = new Ext.menu.Menu(); + + this.tmSep = deluge.menus.torrent.add({ + xtype: 'menuseparator', + }); + + this.tm = deluge.menus.torrent.add({ + text: _('Label'), + menu: this.torrentMenu, + }); + + var lbltpl = + '

' + + '{filter}' + + 'No Label' + + ' ({count})' + + '
'; + + if (deluge.sidebar.hasFilter('label')) { + var filter = deluge.sidebar.getFilter('label'); + filter.list.columns[0].tpl = new Ext.XTemplate(lbltpl); + this.setFilter(filter); + this.updateTorrentMenu(filter.getStates()); + filter.list.refresh(); + } else { + deluge.sidebar.on('filtercreate', this.onFilterCreate, this); + deluge.sidebar.on( + 'afterfiltercreate', + this.onAfterFilterCreate, + this + ); + Deluge.FilterPanel.templates.label = lbltpl; + } + this.registerTorrentStatus('label', _('Label')); + }, + + onAfterFilterCreate: function (sidebar, filter) { + if (filter.filter != 'label') return; + this.updateTorrentMenu(filter.getStates()); + }, + + onFilterCreate: function (sidebar, filter) { + if (filter.filter != 'label') return; + this.setFilter(filter); + }, + + onLabelAddClick: function () { + if (!this.addWindow) { + this.addWindow = new Deluge.ux.AddLabelWindow(); + this.addWindow.on('labeladded', this.onLabelAdded, this); + } + this.addWindow.show(); + }, + + onLabelAdded: function (label) { + var filter = deluge.sidebar.getFilter('label'); + var states = filter.getStates(); + var statesArray = []; + + for (state in states) { + if (!state || state == 'All') continue; + statesArray.push(state); + } + + statesArray.push(label.toLowerCase()); + statesArray.sort(); + + //console.log(states); + //console.log(statesArray); + + states = {}; + + for (i = 0; i < statesArray.length; ++i) { + states[statesArray[i]] = 0; + } + + this.updateTorrentMenu(states); + }, + + onLabelContextMenu: function (dv, i, node, e) { + e.preventDefault(); + if (!this.labelMenu) this.createMenu(); + var r = dv.getRecord(node).get('filter'); + if (!r || r == 'All') { + this.labelMenu.items.get(1).setDisabled(true); + this.labelMenu.items.get(2).setDisabled(true); + } else { + this.labelMenu.items.get(1).setDisabled(false); + this.labelMenu.items.get(2).setDisabled(false); + } + dv.select(i); + this.labelMenu.showAt(e.getXY()); + }, + + onLabelHeaderContextMenu: function (e, t) { + e.preventDefault(); + if (!this.labelMenu) this.createMenu(); + this.labelMenu.items.get(1).setDisabled(true); + this.labelMenu.items.get(2).setDisabled(true); + this.labelMenu.showAt(e.getXY()); + }, + + onLabelOptionsClick: function () { + if (!this.labelOpts) + this.labelOpts = new Deluge.ux.LabelOptionsWindow(); + this.labelOpts.show(this.filter.getState()); + }, + + onLabelRemoveClick: function () { + var state = this.filter.getState(); + deluge.client.label.remove(state, { + success: function () { + deluge.ui.update(); + this.torrentMenu.items.each(function (item) { + if (item.text != state) return; + this.torrentMenu.remove(item); + var i = item; + }, this); + }, + scope: this, + }); + }, + + onTorrentMenuClick: function (item, e) { + var ids = deluge.torrents.getSelectedIds(); + Ext.each(ids, function (id, i) { + if (ids.length == i + 1) { + deluge.client.label.set_torrent(id, item.label, { + success: function () { + deluge.ui.update(); + }, + }); + } else { + deluge.client.label.set_torrent(id, item.label); + } + }); + }, +}); +Deluge.registerPlugin('Label', Deluge.plugins.LabelPlugin); diff --git a/code/test/data/deluge/config/session.state b/code/test/data/deluge/config/session.state new file mode 100644 index 00000000..e9f012c8 Binary files /dev/null and b/code/test/data/deluge/config/session.state differ diff --git a/code/test/data/deluge/config/session.state.bak b/code/test/data/deluge/config/session.state.bak new file mode 100644 index 00000000..7ef276c5 Binary files /dev/null and b/code/test/data/deluge/config/session.state.bak differ diff --git a/code/test/data/deluge/config/ssl/daemon.cert b/code/test/data/deluge/config/ssl/daemon.cert new file mode 100644 index 00000000..ae0e3a1b --- /dev/null +++ b/code/test/data/deluge/config/ssl/daemon.cert @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICpDCCAYwCAQAwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAwwNRGVsdWdlIERh +ZW1vbjAeFw0yNDExMTQxMjI0MjVaFw0yNzExMTQxMjI0MjVaMBgxFjAUBgNVBAMM +DURlbHVnZSBEYWVtb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDk +hV3jiOW40yERiQ6F0GgsM8doBRaCzdJ5du7JRKY0bxNzpkgsmjJp8HFljeROPbOl +plyWzJuol02sQ5WlcnUppPZCFlm3Hnw4wdsM2F7n4MC3i2M8/M73pIBbXw/7Ekro +ZijmS02DT3o6c4Urdh89w3GRs6MWESikBdzTDAVPV8REASAfoI1JVUFznxqEMysx +H9ANqdlkO0sMnBvOFvNxAyuVMOwCUEFsw7ynutJB/yrMUk1itoX21CigOH+pkNDe +JnfIKRa6BvU4aLCFGynAR3bk7TcwRiPoIiWPmwxktFVc+sr26fuGWd8KSPjOJZGV ++WZjYAqtiZRFX67VgAf1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAC3vWaRkzIue +9onnmcpv0uXPNDANefAL4B9sPVmWVdBrWGJTGm9umWZGOa2Z1VRGaC8+LpHJ074f +gmv/PE61GxL5usmfRRLJK4ZKIPzoIspqVKfuWJH6kbsAzg3x43RF0WvokJQKC+Y/ +tWY8a6ewJ1Uh1YDJEwxgR+WBguN64w4QdujPGoDnAWAEW7VJsc2PlYzYConpwKXy +RUOpcnZnAV3z98zRU6m0G/RyYZF8H00hXsEitDeuh5Kdu1tLCbhUYvVXxB6BY559 +bJ+DrxblqpM71wamFq9MDv+Z78XgGZFymoLLLRV3gBO1RsKVnl81Ywvo2LMkRp52 +XuvUJJPD6po= +-----END CERTIFICATE----- diff --git a/code/test/data/deluge/config/ssl/daemon.pkey b/code/test/data/deluge/config/ssl/daemon.pkey new file mode 100644 index 00000000..e6d48da0 --- /dev/null +++ b/code/test/data/deluge/config/ssl/daemon.pkey @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDkhV3jiOW40yER +iQ6F0GgsM8doBRaCzdJ5du7JRKY0bxNzpkgsmjJp8HFljeROPbOlplyWzJuol02s +Q5WlcnUppPZCFlm3Hnw4wdsM2F7n4MC3i2M8/M73pIBbXw/7EkroZijmS02DT3o6 +c4Urdh89w3GRs6MWESikBdzTDAVPV8REASAfoI1JVUFznxqEMysxH9ANqdlkO0sM +nBvOFvNxAyuVMOwCUEFsw7ynutJB/yrMUk1itoX21CigOH+pkNDeJnfIKRa6BvU4 +aLCFGynAR3bk7TcwRiPoIiWPmwxktFVc+sr26fuGWd8KSPjOJZGV+WZjYAqtiZRF +X67VgAf1AgMBAAECggEAA4T6ToghNu4pfYz60vIZaJ+I2//tZNOpVi2QEpF4GH6i +x2PcNgj56x/E31Ixc0hdUpkeppk9cc9CvFBzfDooYR0lSHHyV8ZPFiCw2vXKIGVv +EmSXLAKeErpPL8O7CfGHgyTE+dGsaUVOwJpe3AMptVh5O0vk9cYLNjDQ7H8sOxiQ +0uCTu7JyKRVOtmp9EFy/KqnHPaGVFuNmQH5byiDhuHWFC3lbC2QeYrlhMnPv38jw +NVuUI10E+ZlJJuhuSOwTdKTj0XxhtvMclsbkXOCGm4nL13EYqcyrTiHFbxDCV5c3 +V33xmtH+ABvdvF/68Ouk6ph1BRLTpAW/UmURcUvpUQKBgQD3O2clEmBac3yA487T +/uBhqId5JU5vAYltH2Cfs46aaDUjHe4QshuMPUiyyiZgFh2oc2JXbmDgbXkyrkyq +2K5sOcixKef/eoAIkT+o2Nd8PDMpApF0AcqyWAe0xNR0thWJvjdMon4aFHHZoCcW ++zyWYB8cxVdZCcPnnykpvJgSdwKBgQDsoBZxrq9Ta3S0Ho7NEYu4b6sh9rCfXCU7 +94+eeWPc6+oO+jQDtIS+RYWKwOKmqMzhzq1MdTl9+2yGTCa1st05gsyUpEpso2r5 +BlHUVBzrDEMhRN5FqOcKuRZ+G2HMAsTxJbVZnOS9Z0mf5nwCvJxs8jUEPbFDWQVr +AcKRApTH8wKBgQDw3T3TH0EiPks5Izh4z2L5ogBCZbcxbNTfrGctj/jJs+a5DMrI +F03BZl9yWIHksQc5+xf/SDk3zU/7sVZeSHY+WFmPSN2OyGD+d8wGiyP9FIVfWfIt +jCVXdW4kjnLSNidrqBcmIVUrwWld9aq/uAtCEemd1SERTPNAsI6g6+1YZwKBgQCL +P55Voi4NElRoVv9EUMn/bL+xygGgllJXGtWKtfb9oFtqGvWXJJlle3Yd9GqtFvMT +A1RahTWjHN19nry8+phTatTHuHMPwY+HIp/vKtylud6banK/XakxV0CUT7ramtqY +6s7xAHJfv7PFBJb/6UzIlDR83W0+q9mTYkLEoVc63wKBgCV2ok/C7+7e189gxUir +3ezxBlY9Cv+1/wIE5IjAwpmOPPzsZMKZ2SbDxJMoDBXACJtgOEleiUSYss9qkhVt +o0kUkEOWPM3vydRLCLcsdfpM826WAmGA/w6MsqWyZDoc/kw7UhEBqMhQVXzc/cGF +gRXRDfyZj4MpvAOBPTlTWbme +-----END PRIVATE KEY----- diff --git a/code/test/data/deluge/config/state/43536d439b7e81b161dd2cd2a0d64ea1978ba822.torrent b/code/test/data/deluge/config/state/43536d439b7e81b161dd2cd2a0d64ea1978ba822.torrent new file mode 100644 index 00000000..f16bbbd8 --- /dev/null +++ b/code/test/data/deluge/config/state/43536d439b7e81b161dd2cd2a0d64ea1978ba822.torrent @@ -0,0 +1,2 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731591387e4:infod6:lengthi6e4:name97:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.mkv.zipx12:piece lengthi262144e6:pieces20:s^^o +zee \ No newline at end of file diff --git a/code/test/data/deluge/config/state/torrents.fastresume b/code/test/data/deluge/config/state/torrents.fastresume new file mode 100644 index 00000000..3119ef0a Binary files /dev/null and b/code/test/data/deluge/config/state/torrents.fastresume differ diff --git a/code/test/data/deluge/config/state/torrents.fastresume.bak b/code/test/data/deluge/config/state/torrents.fastresume.bak new file mode 100644 index 00000000..c0a4dead Binary files /dev/null and b/code/test/data/deluge/config/state/torrents.fastresume.bak differ diff --git a/code/test/data/deluge/config/state/torrents.state b/code/test/data/deluge/config/state/torrents.state new file mode 100644 index 00000000..b57309ca Binary files /dev/null and b/code/test/data/deluge/config/state/torrents.state differ diff --git a/code/test/data/deluge/config/state/torrents.state.bak b/code/test/data/deluge/config/state/torrents.state.bak new file mode 100644 index 00000000..3c5bd24f Binary files /dev/null and b/code/test/data/deluge/config/state/torrents.state.bak differ diff --git a/code/test/data/deluge/config/web.conf b/code/test/data/deluge/config/web.conf new file mode 100644 index 00000000..9e9897b9 --- /dev/null +++ b/code/test/data/deluge/config/web.conf @@ -0,0 +1,590 @@ +{ + "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": { + "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_sidebar": true, + "sidebar_multiple_filters": true, + "sidebar_show_zero": false, + "theme": "gray" +} \ No newline at end of file diff --git a/code/test/data/deluge/config/web.conf.bak b/code/test/data/deluge/config/web.conf.bak new file mode 100644 index 00000000..f4067f27 --- /dev/null +++ b/code/test/data/deluge/config/web.conf.bak @@ -0,0 +1,480 @@ +{ + "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" +} \ No newline at end of file diff --git a/code/test/data/lidarr/config/lidarr.db b/code/test/data/lidarr/config/lidarr.db index 198d8e52..1d92f2c2 100644 Binary files a/code/test/data/lidarr/config/lidarr.db and b/code/test/data/lidarr/config/lidarr.db differ diff --git a/code/test/data/lidarr/config/lidarr.pid b/code/test/data/lidarr/config/lidarr.pid index bc768da7..aca544d0 100644 --- a/code/test/data/lidarr/config/lidarr.pid +++ b/code/test/data/lidarr/config/lidarr.pid @@ -1 +1 @@ -146 \ No newline at end of file +145 \ No newline at end of file diff --git a/code/test/data/lidarr/config/logs.db b/code/test/data/lidarr/config/logs.db index cf4a9119..e8250213 100644 Binary files a/code/test/data/lidarr/config/logs.db and b/code/test/data/lidarr/config/logs.db differ diff --git a/code/test/data/lidarr/config/logs.db-shm b/code/test/data/lidarr/config/logs.db-shm index 691b8153..f5f573d5 100644 Binary files a/code/test/data/lidarr/config/logs.db-shm and b/code/test/data/lidarr/config/logs.db-shm differ diff --git a/code/test/data/lidarr/config/logs.db-wal b/code/test/data/lidarr/config/logs.db-wal index f7a5c5a7..5f90fb80 100644 Binary files a/code/test/data/lidarr/config/logs.db-wal and b/code/test/data/lidarr/config/logs.db-wal differ diff --git a/code/test/data/nginx/radarr_bad.torrent b/code/test/data/nginx/radarr_bad.torrent deleted file mode 100644 index 5d27c09a..00000000 --- a/code/test/data/nginx/radarr_bad.torrent +++ /dev/null @@ -1 +0,0 @@ -d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731398141e4:infod6:lengthi4e4:name68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx12:piece lengthi262144e6:pieces20:J̱Lsӑ釘/ee \ No newline at end of file diff --git a/code/test/data/nginx/radarr_bad_nested.torrent b/code/test/data/nginx/radarr_bad_nested.torrent new file mode 100644 index 00000000..8f738b02 --- /dev/null +++ b/code/test/data/nginx/radarr_bad_nested.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931728e4:infod5:filesld6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipxeed6:lengthi2604e4:pathl9:test.zipxeee4:name59:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB12:piece lengthi262144e6:pieces20:w̳R'6Fo}ee \ No newline at end of file diff --git a/code/test/data/nginx/radarr_bad.xml b/code/test/data/nginx/radarr_bad_nested.xml similarity index 84% rename from code/test/data/nginx/radarr_bad.xml rename to code/test/data/nginx/radarr_bad_nested.xml index c484f904..e886aa4d 100644 --- a/code/test/data/nginx/radarr_bad.xml +++ b/code/test/data/nginx/radarr_bad_nested.xml @@ -1,7 +1,7 @@ Test feed - http://nginx/custom/radarr_bad.xml + http://nginx/custom/radarr_bad_nested.xml Test @@ -15,7 +15,7 @@ Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB Test 4138858110 - http://nginx/custom/radarr_bad.torrent + http://nginx/custom/radarr_bad_nested.torrent 174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554 diff --git a/code/test/data/nginx/radarr_bad_single.torrent b/code/test/data/nginx/radarr_bad_single.torrent new file mode 100644 index 00000000..b4af410f --- /dev/null +++ b/code/test/data/nginx/radarr_bad_single.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931618e4:infod6:lengthi2604e4:name70:The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX.mkv.zipx12:piece lengthi262144e6:pieces20:@;9fS E:I1ee \ No newline at end of file diff --git a/code/test/data/nginx/radarr_bad_single.xml b/code/test/data/nginx/radarr_bad_single.xml new file mode 100644 index 00000000..067cfe88 --- /dev/null +++ b/code/test/data/nginx/radarr_bad_single.xml @@ -0,0 +1,25 @@ + + + Test feed + http://nginx/custom/radarr_bad_single.xml + + Test + + en-CA + Test + Tue, 5 Nov 2024 22:02:13 -0400 + Tue, 5 Nov 2024 22:02:13 -0400 + https://validator.w3.org/feed/docs/rss2.html + 30 + + The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX + Test + 4138858110 + http://nginx/custom/radarr_bad_single.torrent + + 174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554 + + Sat, 24 Sep 2022 22:02:13 -0300 + + + \ No newline at end of file diff --git a/code/test/data/nginx/sonarr_bad.torrent b/code/test/data/nginx/sonarr_bad.torrent deleted file mode 100644 index a8f6a958..00000000 --- a/code/test/data/nginx/sonarr_bad.torrent +++ /dev/null @@ -1 +0,0 @@ -d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731357387e4:infod6:lengthi5e4:name93:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx12:piece lengthi262144e6:pieces20:NC"nvºee \ No newline at end of file diff --git a/code/test/data/nginx/sonarr_bad_nested.torrent b/code/test/data/nginx/sonarr_bad_nested.torrent new file mode 100644 index 00000000..654d40c2 --- /dev/null +++ b/code/test/data/nginx/sonarr_bad_nested.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931738e4:infod5:filesld6:lengthi2604e4:pathl89:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos. - Copy.zipxeed6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl9:test.zipxeee4:name88:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG12:piece lengthi262144e6:pieces20:w̳R'6Fo}ee \ No newline at end of file diff --git a/code/test/data/nginx/sonarr_bad.xml b/code/test/data/nginx/sonarr_bad_nested.xml similarity index 85% rename from code/test/data/nginx/sonarr_bad.xml rename to code/test/data/nginx/sonarr_bad_nested.xml index 74859276..4d8a2b95 100644 --- a/code/test/data/nginx/sonarr_bad.xml +++ b/code/test/data/nginx/sonarr_bad_nested.xml @@ -1,7 +1,7 @@ Test feed - http://nginx/custom/sonarr_bad.xml + http://nginx/custom/sonarr_bad_nested.xml Test @@ -15,7 +15,7 @@ Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG Test 4138858110 - http://nginx/custom/sonarr_bad.torrent + http://nginx/custom/sonarr_bad_nested.torrent 174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554 diff --git a/code/test/data/nginx/sonarr_bad_single.torrent b/code/test/data/nginx/sonarr_bad_single.torrent new file mode 100644 index 00000000..3924e586 --- /dev/null +++ b/code/test/data/nginx/sonarr_bad_single.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931604e4:infod6:lengthi2604e4:name126:Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv.zipx12:piece lengthi262144e6:pieces20:@;9fS E:I1ee \ No newline at end of file diff --git a/code/test/data/nginx/sonarr_bad_single.xml b/code/test/data/nginx/sonarr_bad_single.xml new file mode 100644 index 00000000..f2fc59ff --- /dev/null +++ b/code/test/data/nginx/sonarr_bad_single.xml @@ -0,0 +1,25 @@ + + + Test feed + http://nginx/custom/sonarr_bad_single.xml + + Test + + en-CA + Test + Tue, 5 Nov 2024 22:02:13 -0400 + Tue, 5 Nov 2024 22:02:13 -0400 + https://validator.w3.org/feed/docs/rss2.html + 30 + + Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX + Test + 4138858110 + http://nginx/custom/sonarr_bad_single.torrent + + 174674a88c8947f6f9057ac3f81efde384ed216cade43564ec450f2cb4677554 + + Sat, 24 Sep 2022 22:02:13 -0300 + + + \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/2b2ec156461d77bc48b8fe4d62cede50dcdff8e0.fastresume b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/2b2ec156461d77bc48b8fe4d62cede50dcdff8e0.fastresume new file mode 100644 index 00000000..8b6d6d9b Binary files /dev/null and b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/2b2ec156461d77bc48b8fe4d62cede50dcdff8e0.fastresume differ diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/2b2ec156461d77bc48b8fe4d62cede50dcdff8e0.torrent b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/2b2ec156461d77bc48b8fe4d62cede50dcdff8e0.torrent new file mode 100644 index 00000000..b5622bde --- /dev/null +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/2b2ec156461d77bc48b8fe4d62cede50dcdff8e0.torrent @@ -0,0 +1 @@ +d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931618e4:infod6:lengthi2604e4:name70:The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX.mkv.zipx12:piece lengthi262144e6:pieces20:@;9fS E:I1ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c.fastresume b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c.fastresume new file mode 100644 index 00000000..ceead199 Binary files /dev/null and b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c.fastresume differ diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c.torrent b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c.torrent new file mode 100644 index 00000000..44063c24 --- /dev/null +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c.torrent @@ -0,0 +1 @@ +d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931738e4:infod5:filesld6:lengthi2604e4:pathl89:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos. - Copy.zipxeed6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl9:test.zipxeee4:name88:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG12:piece lengthi262144e6:pieces20:w̳R'6Fo}ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/62a60318cd50e0597689d950627ecd971c33e6a8.fastresume b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/62a60318cd50e0597689d950627ecd971c33e6a8.fastresume deleted file mode 100644 index d043491c..00000000 Binary files a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/62a60318cd50e0597689d950627ecd971c33e6a8.fastresume and /dev/null differ diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/62a60318cd50e0597689d950627ecd971c33e6a8.torrent b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/62a60318cd50e0597689d950627ecd971c33e6a8.torrent deleted file mode 100644 index bcd00212..00000000 --- a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/62a60318cd50e0597689d950627ecd971c33e6a8.torrent +++ /dev/null @@ -1 +0,0 @@ -d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731357387e4:infod6:lengthi5e4:name93:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx12:piece lengthi262144e6:pieces20:NC"nvºee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/a4a1d1dd1db25763caa8f5e4d25ad72ef304094b.fastresume b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/a4a1d1dd1db25763caa8f5e4d25ad72ef304094b.fastresume new file mode 100644 index 00000000..a61331a3 Binary files /dev/null and b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/a4a1d1dd1db25763caa8f5e4d25ad72ef304094b.fastresume differ diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/a4a1d1dd1db25763caa8f5e4d25ad72ef304094b.torrent b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/a4a1d1dd1db25763caa8f5e4d25ad72ef304094b.torrent new file mode 100644 index 00000000..9ed7bf2a --- /dev/null +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/a4a1d1dd1db25763caa8f5e4d25ad72ef304094b.torrent @@ -0,0 +1 @@ +d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931728e4:infod5:filesld6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipxeed6:lengthi2604e4:pathl9:test.zipxeee4:name59:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB12:piece lengthi262144e6:pieces20:w̳R'6Fo}ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/b72541215214be2a1d96ef6b29ca1305f5e5e1f6.fastresume b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/b72541215214be2a1d96ef6b29ca1305f5e5e1f6.fastresume new file mode 100644 index 00000000..7be389f7 Binary files /dev/null and b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/b72541215214be2a1d96ef6b29ca1305f5e5e1f6.fastresume differ diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/b72541215214be2a1d96ef6b29ca1305f5e5e1f6.torrent b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/b72541215214be2a1d96ef6b29ca1305f5e5e1f6.torrent new file mode 100644 index 00000000..e4b14064 --- /dev/null +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/b72541215214be2a1d96ef6b29ca1305f5e5e1f6.torrent @@ -0,0 +1 @@ +d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931604e4:infod6:lengthi2604e4:name126:Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv.zipx12:piece lengthi262144e6:pieces20:@;9fS E:I1ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/c132c8bf59f25af11cf00c08ab61476fbc72b2c6.fastresume b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/c132c8bf59f25af11cf00c08ab61476fbc72b2c6.fastresume deleted file mode 100644 index 7b22a719..00000000 Binary files a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/c132c8bf59f25af11cf00c08ab61476fbc72b2c6.fastresume and /dev/null differ diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/c132c8bf59f25af11cf00c08ab61476fbc72b2c6.torrent b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/c132c8bf59f25af11cf00c08ab61476fbc72b2c6.torrent deleted file mode 100644 index d1873338..00000000 --- a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/c132c8bf59f25af11cf00c08ab61476fbc72b2c6.torrent +++ /dev/null @@ -1 +0,0 @@ -d10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731398141e4:infod6:lengthi4e4:name68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx12:piece lengthi262144e6:pieces20:J̱Lsӑ釘/ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/queue b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/queue index 58d02b1b..62a4727a 100644 --- a/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/queue +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/BT_backup/queue @@ -1 +1,4 @@ -c132c8bf59f25af11cf00c08ab61476fbc72b2c6 +b72541215214be2a1d96ef6b29ca1305f5e5e1f6 +a4a1d1dd1db25763caa8f5e4d25ad72ef304094b +2b2ec156461d77bc48b8fe4d62cede50dcdff8e0 +59ab2bc053430fe53e06a93e2eadb7acb6a6bf2c diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent-data.conf b/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent-data.conf index 5f2cb625..a301cee2 100644 --- a/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent-data.conf +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent-data.conf @@ -1,2 +1,2 @@ [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\x3\xae\x61\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0\x5?%) +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) diff --git a/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent.conf b/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent.conf index d2c44ba0..a565bdb0 100644 --- a/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent.conf +++ b/code/test/data/qbittorrent-bad/config/qBittorrent/qBittorrent.conf @@ -15,6 +15,9 @@ program= Session\AddTorrentStopped=false Session\DefaultSavePath=/downloads/ Session\ExcludedFileNames= +Session\MaxActiveDownloads=100 +Session\MaxActiveTorrents=100 +Session\MaxActiveUploads=100 Session\Port=6881 Session\QueueingSystemEnabled=true Session\SSL\Port=65325 @@ -32,7 +35,7 @@ MigrationVersion=6 [Network] Cookies=@Invalid() -PortForwardingEnabled=false +PortForwardingEnabled=true Proxy\HostnameLookupEnabled=false Proxy\Profiles\BitTorrent=true Proxy\Profiles\Misc=true diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx deleted file mode 100644 index 9daeafb9..00000000 --- a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx +++ /dev/null @@ -1 +0,0 @@ -test diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos. - Copy.zipx b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos. - Copy.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos. - Copy.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir1/Dir11/test11.zipx b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir1/Dir11/test11.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir1/Dir11/test11.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir1/sample.txt b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir1/sample.txt new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir1/sample.txt @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir2/test2.zipx b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir2/test2.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/Dir2/test2.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/test.zipx b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/test.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG/test.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv.zipx b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx deleted file mode 100644 index 30d74d25..00000000 --- a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir1/Dir11/test11.zipx b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir1/Dir11/test11.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir1/Dir11/test11.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir1/sample.txt b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir1/sample.txt new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir1/sample.txt @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir2/test2.zipx b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir2/test2.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Dir2/test2.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/test.zipx b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/test.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB/test.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX.mkv.zipx b/code/test/data/qbittorrent-bad/downloads/The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX.mkv.zipx new file mode 100644 index 00000000..69aeb573 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX.mkv.zipx @@ -0,0 +1 @@ +testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/radarr_bad.torrent b/code/test/data/qbittorrent-bad/downloads/radarr_bad.torrent deleted file mode 100644 index 5d27c09a..00000000 --- a/code/test/data/qbittorrent-bad/downloads/radarr_bad.torrent +++ /dev/null @@ -1 +0,0 @@ -d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731398141e4:infod6:lengthi4e4:name68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipx12:piece lengthi262144e6:pieces20:J̱Lsӑ釘/ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/radarr_bad_nested.torrent b/code/test/data/qbittorrent-bad/downloads/radarr_bad_nested.torrent new file mode 100644 index 00000000..8f738b02 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/radarr_bad_nested.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931728e4:infod5:filesld6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl68:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB.mkv.zipxeed6:lengthi2604e4:pathl9:test.zipxeee4:name59:Speak.No.Evil.2024.2160p.MA.WEB-DL.DDP5.1.Atmos.H.265-HHWEB12:piece lengthi262144e6:pieces20:w̳R'6Fo}ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/radarr_bad_single.torrent b/code/test/data/qbittorrent-bad/downloads/radarr_bad_single.torrent new file mode 100644 index 00000000..b4af410f --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/radarr_bad_single.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931618e4:infod6:lengthi2604e4:name70:The.Wild.Robot.2024.2160p.AMZN.WEB-DL.DDP5.1.Atmos.H.265-FLUX.mkv.zipx12:piece lengthi262144e6:pieces20:@;9fS E:I1ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/sonarr_bad.torrent b/code/test/data/qbittorrent-bad/downloads/sonarr_bad.torrent deleted file mode 100644 index a8f6a958..00000000 --- a/code/test/data/qbittorrent-bad/downloads/sonarr_bad.torrent +++ /dev/null @@ -1 +0,0 @@ -d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731357387e4:infod6:lengthi5e4:name93:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG.zipx12:piece lengthi262144e6:pieces20:NC"nvºee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/sonarr_bad_nested.torrent b/code/test/data/qbittorrent-bad/downloads/sonarr_bad_nested.torrent new file mode 100644 index 00000000..654d40c2 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/sonarr_bad_nested.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931738e4:infod5:filesld6:lengthi2604e4:pathl89:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos. - Copy.zipxeed6:lengthi2604e4:pathl4:Dir15:Dir1111:test11.zipxeed6:lengthi2604e4:pathl4:Dir110:sample.txteed6:lengthi2604e4:pathl4:Dir210:test2.zipxeed6:lengthi2604e4:pathl9:test.zipxeee4:name88:Agatha.All.Along.S01E01.Seekest.Thou.the.Road.2160p.APPS.WEB-DL.DDP5.1.Atmos.H.265-VARYG12:piece lengthi262144e6:pieces20:w̳R'6Fo}ee \ No newline at end of file diff --git a/code/test/data/qbittorrent-bad/downloads/sonarr_bad_single.torrent b/code/test/data/qbittorrent-bad/downloads/sonarr_bad_single.torrent new file mode 100644 index 00000000..3924e586 --- /dev/null +++ b/code/test/data/qbittorrent-bad/downloads/sonarr_bad_single.torrent @@ -0,0 +1 @@ +d8:announce28:http://tracker:6969/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1731931604e4:infod6:lengthi2604e4:name126:Agatha.All.Along.S01E02.Circle.Sewn.With.Fate.Unlock.Thy.Hidden.Gate.2160p.DSNP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv.zipx12:piece lengthi262144e6:pieces20:@;9fS E:I1ee \ No newline at end of file diff --git a/code/test/data/qbittorrent/config/qBittorrent/BT_backup/queue b/code/test/data/qbittorrent/config/qBittorrent/BT_backup/queue index e69de29b..f9c3d270 100644 --- a/code/test/data/qbittorrent/config/qBittorrent/BT_backup/queue +++ b/code/test/data/qbittorrent/config/qBittorrent/BT_backup/queue @@ -0,0 +1 @@ +de6996481f4e318e7baff03b4043929c585a7c4e diff --git a/code/test/data/qbittorrent/config/qBittorrent/qBittorrent-data.conf b/code/test/data/qbittorrent/config/qBittorrent/qBittorrent-data.conf index 9a2e9da8..07b0c969 100644 --- a/code/test/data/qbittorrent/config/qBittorrent/qBittorrent-data.conf +++ b/code/test/data/qbittorrent/config/qBittorrent/qBittorrent-data.conf @@ -1,2 +1,2 @@ [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\x6\xe4\xdd\0\0\0\x12\0\x41\0l\0l\0t\0i\0m\0\x65\0U\0L\0\0\0\x4\0\0\0\0\0\b\xc3\xde) +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) diff --git a/code/test/data/qbittorrent/config/qBittorrent/qBittorrent.conf b/code/test/data/qbittorrent/config/qBittorrent/qBittorrent.conf index 3393d25d..20147343 100644 --- a/code/test/data/qbittorrent/config/qBittorrent/qBittorrent.conf +++ b/code/test/data/qbittorrent/config/qBittorrent/qBittorrent.conf @@ -12,7 +12,7 @@ enabled=false program= [BitTorrent] -ExcludedFileNamesEnabled=true +ExcludedFileNamesEnabled=false Session\AddTorrentStopped=false Session\DefaultSavePath=/downloads/ Session\ExcludedFileNames=*.apk, *.bat, *.bin, *.bmp, *.cmd, *.com, *.db, *.diz, *.dll, *.dmg, *.etc, *.exe, *.gif, *.htm, *.html, *.ico, *.ini, *.iso, *.jar, *.jpg, *.js, *.link, *.lnk, *.msi, *.nfo, *.perl, *.php, *.pl, *.png, *.ps1, *.psc1, *.psd1, *.psm1, *.py, *.pyd, *.rb, *.readme, *.reg, *.run, *.scr, *.sh, *.sql, *.text, *.thumb, *.torrent, *.txt, *.url, *.vbs, *.wsf, *.xml, *.zipx, *.7z, *.bdjo, *.bdmv, *.bin, *.bmp, *.cci, *.clpi, *.crt, *.dll, *.exe, *.html, *.idx, *.inf, *.jar, *.jpeg, *.jpg, *.lnk, *.m4a, *.mpls, *.msi, *.nfo, *.pdf, *.png, *.rar, *(sample).*, *sample.mkv, *sample.mp4, *.sfv, *.srt, *.sub, *.tbl, Trailer.*, *.txt, *.url, *.xig, *.xml, *.xrt, *.zip, *.zipx, *.Lnk @@ -33,7 +33,7 @@ MigrationVersion=6 [Network] Cookies=@Invalid() -PortForwardingEnabled=false +PortForwardingEnabled=true Proxy\HostnameLookupEnabled=false Proxy\Profiles\BitTorrent=true Proxy\Profiles\Misc=true diff --git a/code/test/data/radarr/config/Sentry/E052B02F117E6BB423BE301CDA607148F4B3F8F6/.session b/code/test/data/radarr/config/Sentry/E052B02F117E6BB423BE301CDA607148F4B3F8F6/.session new file mode 100644 index 00000000..fd4423d4 --- /dev/null +++ b/code/test/data/radarr/config/Sentry/E052B02F117E6BB423BE301CDA607148F4B3F8F6/.session @@ -0,0 +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"}}} \ No newline at end of file diff --git a/code/test/data/radarr/config/logs.db b/code/test/data/radarr/config/logs.db index 4295d294..6041fca0 100644 Binary files a/code/test/data/radarr/config/logs.db and b/code/test/data/radarr/config/logs.db differ diff --git a/code/test/data/radarr/config/logs.db-shm b/code/test/data/radarr/config/logs.db-shm index 572e6c90..b6e8e9b8 100644 Binary files a/code/test/data/radarr/config/logs.db-shm and b/code/test/data/radarr/config/logs.db-shm differ diff --git a/code/test/data/radarr/config/logs.db-wal b/code/test/data/radarr/config/logs.db-wal index 3c247e02..f2d15712 100644 Binary files a/code/test/data/radarr/config/logs.db-wal and b/code/test/data/radarr/config/logs.db-wal differ diff --git a/code/test/data/radarr/config/radarr.db b/code/test/data/radarr/config/radarr.db index 9f943918..d736369a 100644 Binary files a/code/test/data/radarr/config/radarr.db and b/code/test/data/radarr/config/radarr.db differ diff --git a/code/test/data/readarr/config/cache.db b/code/test/data/readarr/config/cache.db index e055db8a..23727b5d 100644 Binary files a/code/test/data/readarr/config/cache.db and b/code/test/data/readarr/config/cache.db differ diff --git a/code/test/data/readarr/config/logs.db b/code/test/data/readarr/config/logs.db index ea25ff03..6e87350e 100644 Binary files a/code/test/data/readarr/config/logs.db and b/code/test/data/readarr/config/logs.db differ diff --git a/code/test/data/readarr/config/logs.db-shm b/code/test/data/readarr/config/logs.db-shm index 41cbb476..325e7c95 100644 Binary files a/code/test/data/readarr/config/logs.db-shm and b/code/test/data/readarr/config/logs.db-shm differ diff --git a/code/test/data/readarr/config/logs.db-wal b/code/test/data/readarr/config/logs.db-wal index 2360a6a6..c685061d 100644 Binary files a/code/test/data/readarr/config/logs.db-wal and b/code/test/data/readarr/config/logs.db-wal differ diff --git a/code/test/data/readarr/config/readarr.db b/code/test/data/readarr/config/readarr.db index 45c5b98b..9aaeb25f 100644 Binary files a/code/test/data/readarr/config/readarr.db and b/code/test/data/readarr/config/readarr.db differ diff --git a/code/test/data/readarr/config/readarr.pid b/code/test/data/readarr/config/readarr.pid index aca544d0..70e1a64c 100644 --- a/code/test/data/readarr/config/readarr.pid +++ b/code/test/data/readarr/config/readarr.pid @@ -1 +1 @@ -145 \ No newline at end of file +144 \ No newline at end of file diff --git a/code/test/data/sonarr/config/Sentry/07ADDC43B5669C4F6DB64F2EF2B23B3FEEDFE865/.session b/code/test/data/sonarr/config/Sentry/07ADDC43B5669C4F6DB64F2EF2B23B3FEEDFE865/.session index d7092f43..7c36c8d3 100644 --- a/code/test/data/sonarr/config/Sentry/07ADDC43B5669C4F6DB64F2EF2B23B3FEEDFE865/.session +++ b/code/test/data/sonarr/config/Sentry/07ADDC43B5669C4F6DB64F2EF2B23B3FEEDFE865/.session @@ -1 +1 @@ -{"update":{"sid":"e87df0117b134d64b99f5b878e652b49","did":"1df9f2cc-17dc-4130-9753-9b694f82f1b5","init":true,"started":"2024-11-12T08:27:39.2729256+00:00","timestamp":"2024-11-12T08:27:39.2735692+00:00","seq":0,"duration":0,"errors":0,"attrs":{"release":"4.0.10.2544-main","environment":"main"}}} \ No newline at end of file +{"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"}}} \ No newline at end of file diff --git a/code/test/data/sonarr/config/logs.db b/code/test/data/sonarr/config/logs.db index 76bc5344..045198ab 100644 Binary files a/code/test/data/sonarr/config/logs.db and b/code/test/data/sonarr/config/logs.db differ diff --git a/code/test/data/sonarr/config/logs.db-shm b/code/test/data/sonarr/config/logs.db-shm index 44a7d064..87ffeb54 100644 Binary files a/code/test/data/sonarr/config/logs.db-shm and b/code/test/data/sonarr/config/logs.db-shm differ diff --git a/code/test/data/sonarr/config/logs.db-wal b/code/test/data/sonarr/config/logs.db-wal index bcdb6267..0ac505c0 100644 Binary files a/code/test/data/sonarr/config/logs.db-wal and b/code/test/data/sonarr/config/logs.db-wal differ diff --git a/code/test/data/sonarr/config/sonarr.db b/code/test/data/sonarr/config/sonarr.db index 31e7a08c..05523cea 100644 Binary files a/code/test/data/sonarr/config/sonarr.db and b/code/test/data/sonarr/config/sonarr.db differ diff --git a/code/test/data/sonarr/config/sonarr.db-shm b/code/test/data/sonarr/config/sonarr.db-shm index 2a0d5e22..42b6592a 100644 Binary files a/code/test/data/sonarr/config/sonarr.db-shm and b/code/test/data/sonarr/config/sonarr.db-shm differ diff --git a/code/test/data/sonarr/config/sonarr.db-wal b/code/test/data/sonarr/config/sonarr.db-wal index 77455da5..33fd1d9d 100644 Binary files a/code/test/data/sonarr/config/sonarr.db-wal and b/code/test/data/sonarr/config/sonarr.db-wal differ diff --git a/code/test/data/sonarr/config/sonarr.pid b/code/test/data/sonarr/config/sonarr.pid index aca544d0..bc768da7 100644 --- a/code/test/data/sonarr/config/sonarr.pid +++ b/code/test/data/sonarr/config/sonarr.pid @@ -1 +1 @@ -145 \ No newline at end of file +146 \ No newline at end of file diff --git a/code/test/data/transmission/config/bandwidth-groups.json b/code/test/data/transmission/config/bandwidth-groups.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/code/test/data/transmission/config/bandwidth-groups.json @@ -0,0 +1,2 @@ +{ +} diff --git a/code/test/data/transmission/config/dht.dat b/code/test/data/transmission/config/dht.dat new file mode 100644 index 00000000..0f005239 Binary files /dev/null and b/code/test/data/transmission/config/dht.dat differ diff --git a/code/test/data/transmission/config/settings.json b/code/test/data/transmission/config/settings.json new file mode 100644 index 00000000..67c88321 --- /dev/null +++ b/code/test/data/transmission/config/settings.json @@ -0,0 +1,82 @@ +{ + "alt-speed-down": 50, + "alt-speed-enabled": false, + "alt-speed-time-begin": 540, + "alt-speed-time-day": 127, + "alt-speed-time-enabled": false, + "alt-speed-time-end": 1020, + "alt-speed-up": 50, + "announce-ip": "", + "announce-ip-enabled": false, + "anti-brute-force-enabled": false, + "anti-brute-force-threshold": 100, + "bind-address-ipv4": "0.0.0.0", + "bind-address-ipv6": "::", + "blocklist-enabled": false, + "blocklist-url": "http://www.example.com/blocklist", + "cache-size-mb": 4, + "default-trackers": "", + "dht-enabled": true, + "download-dir": "/downloads/complete", + "download-queue-enabled": true, + "download-queue-size": 5, + "encryption": 1, + "idle-seeding-limit": 30, + "idle-seeding-limit-enabled": false, + "incomplete-dir": "/downloads/incomplete", + "incomplete-dir-enabled": true, + "lpd-enabled": false, + "message-level": 2, + "peer-congestion-algorithm": "", + "peer-id-ttl-hours": 6, + "peer-limit-global": 200, + "peer-limit-per-torrent": 50, + "peer-port": 51413, + "peer-port-random-high": 65535, + "peer-port-random-low": 49152, + "peer-port-random-on-start": false, + "peer-socket-tos": "le", + "pex-enabled": true, + "port-forwarding-enabled": true, + "preallocation": 1, + "prefetch-enabled": true, + "queue-stalled-enabled": true, + "queue-stalled-minutes": 30, + "ratio-limit": 2, + "ratio-limit-enabled": false, + "rename-partial-files": true, + "rpc-authentication-required": false, + "rpc-bind-address": "0.0.0.0", + "rpc-enabled": true, + "rpc-host-whitelist": "", + "rpc-host-whitelist-enabled": false, + "rpc-password": "{cbb7a35b753789796ced190a807c3aa23d6296aeG5UaQOmv", + "rpc-port": 9091, + "rpc-socket-mode": "0750", + "rpc-url": "/transmission/", + "rpc-username": "", + "rpc-whitelist": "", + "rpc-whitelist-enabled": false, + "scrape-paused-torrents-enabled": true, + "script-torrent-added-enabled": false, + "script-torrent-added-filename": "", + "script-torrent-done-enabled": false, + "script-torrent-done-filename": "", + "script-torrent-done-seeding-enabled": false, + "script-torrent-done-seeding-filename": "", + "seed-queue-enabled": false, + "seed-queue-size": 10, + "speed-limit-down": 100, + "speed-limit-down-enabled": false, + "speed-limit-up": 100, + "speed-limit-up-enabled": false, + "start-added-torrents": true, + "tcp-enabled": true, + "torrent-added-verify-mode": "fast", + "trash-original-torrent-files": false, + "umask": "002", + "upload-slots-per-torrent": 14, + "utp-enabled": false, + "watch-dir": "/watch", + "watch-dir-enabled": true +} diff --git a/code/test/data/transmission/config/stats.json b/code/test/data/transmission/config/stats.json new file mode 100644 index 00000000..0223e479 --- /dev/null +++ b/code/test/data/transmission/config/stats.json @@ -0,0 +1,7 @@ +{ + "downloaded-bytes": 109368, + "files-added": 42, + "seconds-active": 55843, + "session-count": 2, + "uploaded-bytes": 0 +} diff --git a/code/test/docker-compose.yml b/code/test/docker-compose.yml index e4dfcc3c..3981ab20 100644 --- a/code/test/docker-compose.yml +++ b/code/test/docker-compose.yml @@ -48,6 +48,44 @@ services: - 6882:6881/udp restart: unless-stopped + deluge: + image: lscr.io/linuxserver/deluge:latest + container_name: deluge + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + volumes: + - ./data/deluge/config:/config + - ./data/deluge/downloads:/downloads + ports: + - 8112:8112 + - 6883:6881 + - 6883:6881/udp + - 58846:58846 + restart: unless-stopped + + transmission: + image: lscr.io/linuxserver/transmission:latest + container_name: transmission + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + - TRANSMISSION_RPC_AUTHENTICATION_REQUIRED=true + - TRANSMISSION_RPC_USERNAME=test + - TRANSMISSION_RPC_PASSWORD=testing + - TRANSMISSION_RPC_PORT=9091 + - TRANSMISSION_WEB_HOME=/usr/share/transmission/public_html + ports: + - 9091:9091 + - 51413:51413 + - 51413:51413/udp + volumes: + - ./data/transmission/config:/config + - ./data/transmission/downloads:/downloads + restart: unless-stopped + tracker: image: wiltonsr/opentracker:open container_name: opentracker @@ -76,6 +114,8 @@ services: - ./data/sonarr/config:/config - ./data/sonarr/tv:/tv - ./data/qbittorrent/downloads:/downloads + # - ./data/deluge/downloads:/downloads + # - ./data/transmission/downloads:/downloads ports: - 8989:8989 restart: unless-stopped @@ -91,6 +131,8 @@ services: - ./data/radarr/config:/config - ./data/radarr/movies:/movies - ./data/qbittorrent/downloads:/downloads + # - ./data/deluge/downloads:/downloads + # - ./data/transmission/downloads:/downloads ports: - 7878:7878 restart: unless-stopped @@ -126,22 +168,49 @@ services: restart: unless-stopped cleanuperr: - image: flaminel/cleanuperr:1.1.0 + image: flaminel/cleanuperr:latest container_name: cleanuperr environment: + - LOGGING__LOGLEVEL__DEFAULT=Debug + - TRIGGERS__QUEUECLEANER=0/30 * * * * ? + - TRIGGERS__CONTENTBLOCKER=0/30 * * * * ? + + - QUEUECLEANER__ENABLED=true + + - CONTENTBLOCKER__ENABLED=true + - CONTENTBLOCKER__BLACKLIST__ENABLED=true + - CONTENTBLOCKER__BLACKLIST__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist + # OR + # - CONTENTBLOCKER__WHITELIST__ENABLED=true + # - CONTENTBLOCKER__BLACKLIST__PATH=https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist + + - QBITTORRENT__ENABLED=true - QBITTORRENT__URL=http://qbittorrent:8080 - QBITTORRENT__USERNAME=test - QBITTORRENT__PASSWORD=testing + # OR + # - DELUGE__ENABLED=true + # - DELUGE__URL=http://localhost:8112 + # - DELUGE__PASSWORD=testing + # OR + # - TRANSMISSION__ENABLED=true + # - TRANSMISSION__URL=http://localhost:9091 + # - TRANSMISSION__USERNAME=test + # - TRANSMISSION__PASSWORD=testing + - SONARR__ENABLED=true - SONARR__INSTANCES__0__URL=http://sonarr:8989 - SONARR__INSTANCES__0__APIKEY=96736c3eb3144936b8f1d62d27be8cee + - RADARR__ENABLED=true - RADARR__INSTANCES__0__URL=http://radarr:7878 - RADARR__INSTANCES__0__APIKEY=705b553732ab4167ab23909305d60600 restart: unless-stopped depends_on: - qbittorrent + - deluge + - transmission - sonarr - radarr - lidarr