Compare commits

...

117 Commits

Author SHA1 Message Date
Weblate
5fee2c4cd9 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fonkio <maxime.fabre10@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: toeiazarothis <patrickdealmeida89000@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-04-20 08:18:02 +03:00
uwuceo
21d553cf1b Update description for MyAnonamouse's Freeleech setting (#2103) 2024-04-16 13:14:08 +03:00
Josh McKinney
782b2d3761 Add dev container workspace
Allows the linting and style settings for the frontend to be applied even when you load the main repo as a workspace

(cherry picked from commit d6278fced49b26be975c3a6039b38a94f700864b)
2024-04-16 12:59:13 +03:00
Servarr
e1da3eee80 Automated API Docs update 2024-04-16 10:34:38 +03:00
Bogdan
09af2da6b9 Fixed: Re-testing edited providers will forcibly test them 2024-04-16 09:17:14 +03:00
Bogdan
e3e9094d42 Bump version to 1.16.2 2024-04-14 08:37:49 +03:00
Bogdan
94634234ff Update categories for M-Team TP 2024-04-13 23:33:30 +03:00
Bogdan
a48d6029d9 Show releases with issues in the interactive search 2024-04-13 09:48:33 +03:00
Bogdan
9cc150b105 Fix AB tests 2024-04-13 09:14:43 +03:00
Bogdan
6a97d99876 Fixed: (AnimeBytes) Enable Use Filenames for Single Episodes by default 2024-04-13 07:52:33 +03:00
Josh McKinney
c957168040 Add DevContainer, VSCode config and extensions.json
(cherry picked from commit 5061dc4b5e5ea9925740496a5939a1762788b793)
2024-04-10 23:51:13 +03:00
Mark McDowall
61bc35b3fa New: Option to prefix app name on Telegram notification titles
(cherry picked from commit 37863a8deb339ef730b2dd5be61e1da1311fdd23)
2024-04-10 23:46:09 +03:00
Weblate
a84210c452 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2024-04-09 22:56:41 +03:00
Bogdan
8af6ea1d8f New: Retry on failed indexer requests 2024-04-09 22:05:11 +03:00
Bogdan
1a894ac583 Fixed: Matching at least 2 terms in the filter releases by query 2024-04-09 18:07:55 +03:00
bakerboy448
4f6e05414c Drop beta (Preview) from login meta description (#2097) 2024-04-09 17:30:26 +03:00
Bogdan
5096a088d4 Fixed: (IPTorrents) Improve category selector 2024-04-09 04:49:27 +03:00
Bogdan
6581bddba3 Detect shfs mounts 2024-04-08 22:27:42 +03:00
Bogdan
292af28d42 Bump version to 1.16.1 2024-04-07 07:59:28 +03:00
Bogdan
37a6d03d52 Fixed: (XSpeeds) Update categories
Co-authored-by: Garfield69 <garfield69@outlook.com>
2024-04-07 01:37:26 +03:00
Bogdan
fe35d450f0 Use info urls as guid and add remaster name to title for SecretCinema 2024-04-06 21:36:58 +03:00
Mark McDowall
6a9e27bc06 Fixed: Sending ntfy.sh notifications with unicode characters
(cherry picked from commit a169ebff2adda5c8585c6aae6249b1c1f7c12264)
2024-04-06 16:49:03 +03:00
Bogdan
a989bf82ea Fixed: (Gazelle) Ignore ineligible releases with Use Freeleech Token 2024-04-06 00:51:34 +03:00
Mark McDowall
ccc8d8002f Fixed: Testing SABnzbd when no categories are configured
(cherry picked from commit 0e31281828c737e3f6eecbb870960194888a970a)
2024-04-05 19:42:19 +03:00
Bogdan
eaaf8db486 Update timezone for ExoticaZ 2024-04-04 00:51:22 +03:00
Bogdan
c32fa7a84b Update timezone for FL & AvistaZ 2024-04-04 00:47:03 +03:00
Weblate
57e21a78ee Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translation: Servarr/Prowlarr
2024-04-02 17:34:07 +03:00
Bogdan
9cdf5d18d8 Fixed: Categories for Newznab/Torznab in add indexer 2024-04-02 16:01:41 +03:00
Bogdan
41b0a1211b Fixed: Migrate categories to capabilities in Newznab/Torznab settings 2024-04-02 13:07:11 +03:00
Bogdan
1b8f09f2ce Fixed: Improve capabilities fetching for applications sync 2024-04-01 17:55:26 +03:00
Bogdan
2f85de6b69 Add capabilities to Newznab & Torznab tests 2024-04-01 04:37:13 +03:00
Bogdan
b2ef9d5b0a Fixed: Filter invalid releases without categories or size 2024-04-01 03:54:02 +03:00
Bogdan
c80262d75b Translation fixes for health checks 2024-04-01 02:43:31 +03:00
Weblate
2a312d93ec Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2024-04-01 02:26:50 +03:00
Weblate
e09df2fff3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translation: Servarr/Prowlarr
2024-04-01 02:22:47 +03:00
Mark McDowall
f0c7d13b20 Translations for health checks
Use named tokens for backend translations

(cherry picked from commit 11f96c31048c2d1aafca0c91736d439f7f9a95a8)
2024-04-01 02:18:17 +03:00
Bogdan
4dac60bef9 Fixed: Displaying capabilities for Newznab and Torznab feeds (#2083) 2024-04-01 00:49:31 +03:00
Bogdan
5aefb46790 Fixed: Check VIP expiration only for enabled indexers
Check on ProviderBulkUpdatedEvent as well

Fixes #2082
2024-03-31 15:11:15 +03:00
Bogdan
41b043e551 Fixed: (Cardigann) Log invalid category values 2024-03-30 16:48:02 +02:00
Bogdan
5447fad1fc Fixed: (Cardigann) Deprecate noappend for category and categorydesc 2024-03-30 16:46:30 +02:00
Bogdan
6a1e01abbd Disable Shizaproject due to being unusable 2024-03-29 02:54:56 +02:00
Bogdan
2803ad5ba0 Update name for RuTracker.org
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2024-03-29 01:43:52 +02:00
Bogdan
8fa8a13036 Bump version to 1.16.0 2024-03-28 03:41:53 +02:00
Servarr
41ce79ccce Automated API Docs update 2024-03-28 02:24:29 +02:00
Bogdan
14ae062db2 Fixed: Http Client getting network interfaces on aarch64
Co-authored-by: Louis R <covert8@noreply.github.com>

Fixes #2076
2024-03-28 01:49:38 +02:00
Bogdan
d55a38da4a New: Allow HEAD requests to ping endpoint 2024-03-27 18:54:57 +02:00
Weblate
ab289cfc86 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Charles Follet <follet2004@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Stanislav <prekop3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-03-27 18:54:49 +02:00
Bogdan
12671e9905 New: Advanced settings toggle in indexer, notification and download client modals 2024-03-26 19:24:49 +02:00
Bogdan
a33023b8c6 New: Migrate M-Team - TP to API 2024-03-25 20:24:32 +02:00
Bogdan
a3e134ce0b Link indexer proxy settings from health page 2024-03-23 15:59:26 +02:00
Weblate
ee7c821cab Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2024-03-23 15:58:31 +02:00
Bogdan
ee4cf93aee Improve Indexer Proxy healthcheck messaging 2024-03-23 15:56:34 +02:00
Mark McDowall
2cacfba81f Fixed: Task progress messages in the UI
(cherry picked from commit c6417337812f3578a27f9dc1e44fdad80f557271)
2024-03-22 11:38:56 +02:00
Bogdan
02e420580e Fixed: (SubsPlease) Season search improvements and support for movie searches
Co-authored-by: Florent Delrieu <fdelrieu@klanik.com>
2024-03-21 18:40:44 +02:00
Bogdan
d99398d3f8 Fix tests for PHD 2024-03-21 14:02:19 +02:00
Bogdan
9ea8335aa0 Update timezone offset for PHD/CZ 2024-03-20 02:57:29 +02:00
Bogdan
52a91a50b2 Remove duplicated parameter 2024-03-19 21:35:43 +02:00
Bogdan
680bf46e25 Fixed: (SceneHD) Category filtering
Fixes #2028
2024-03-19 21:34:20 +02:00
Bogdan
d279c97f15 New: Pass general proxy credentials to FlareSolverr
Fixes #2073
2024-03-19 15:02:11 +02:00
Bogdan
7d5d338c8e Improve search page button colors
Also show all buttons on one line for info indexer modal

Fixes #2046
2024-03-19 14:40:20 +02:00
Bogdan
721ae1cac0 Fixed: (Cardigann) Avoid NullRef on forms with multipart/form-data 2024-03-19 00:39:42 +02:00
Bogdan
3881c9d753 Remove bulk edit for Reject Blocklisted Torrent Hashes While Grabbing 2024-03-17 19:57:34 +02:00
Servarr
131b344119 Automated API Docs update 2024-03-17 19:55:36 +02:00
Bogdan
d226e52881 Fixed: Move Reject Blocklisted Torrent Hashes While Grabbing to applications 2024-03-17 19:40:44 +02:00
Bogdan
583815b4f7 Bump version to 1.15.0 2024-03-17 15:11:07 +02:00
Bogdan
f0a8d22e84 Improve Search Types selection for BHD 2024-03-16 21:25:28 +02:00
Weblate
50c6f15e12 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Davide <daddobiker@gmail.com>
Co-authored-by: Dennis Langthjem <dennis@langthjem.dk>
Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ihor Mudryi <mudryy33@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2024-03-16 18:12:37 +02:00
Bogdan
2e3a95f389 Remove Status from IndexerDefinition 2024-03-16 17:41:25 +02:00
Bogdan
3d52096eb4 Downgrade YamlDotNet and improve logging for definitions update 2024-03-16 17:39:03 +02:00
Mark McDowall
e981cacbda Fixed: Disabled select option still selectable
(cherry picked from commit 063dba22a803295adee4fdcbe42718af3e85ca78)
2024-03-14 16:12:35 +02:00
Mark McDowall
218371a318 Convert Queued Tasks to TS
(cherry picked from commit 6d552f2a60f44052079b5e8944f5e1bbabac56e0)
2024-03-14 16:02:43 +02:00
Bogdan
30fd7c8c2a Fix stylelint command in package.json
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-03-14 15:52:20 +02:00
Bogdan
96d2d61fa0 Add download clients notice about sync to applications 2024-03-12 23:39:20 +02:00
Mark McDowall
17ff86aaea Fixes: Missing default path for Download Station
(cherry picked from commit 4bf3ab1511b4ea25642476bf9df13f91b6f73d76)

Closes #2062
2024-03-12 13:57:35 +02:00
Bogdan
7f8c1ace14 Replace special chars in search term with wildcard for RuTracker 2024-03-11 10:28:22 +02:00
Bogdan
dc0edb7bc1 Bump YamlDotNet, AngleSharp, BenchmarkDotNet 2024-03-10 13:36:25 +02:00
Bogdan
2ac996c9f9 Bump version to 1.14.3 2024-03-10 09:07:12 +02:00
Weblate
2ebabd69b5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Mark Martines <mark-martines@hotmail.com>
Co-authored-by: Maxence Winandy <maxence.winandy@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translation: Servarr/Prowlarr
2024-03-08 11:09:33 +02:00
Helvio Pedreschi
b3738f1602 Fixed: WebApp functionality on Apple devices
(cherry picked from commit c7dd7abf892eead7796fcc482aa2f2aabaf88712)
2024-03-08 11:05:27 +02:00
Bogdan
882152b911 Use proxied requests for indexers 2024-03-04 15:26:23 +02:00
Louis R
a25e79031f Fixed: Don't disable IPv6 in IPv6-only Environment
(cherry picked from commit 13af6f57796e54c3949cf340e03f020e6f8575c4)
2024-03-03 12:12:50 +02:00
Bogdan
cc85060b1b Bump version to 1.14.2 2024-03-03 12:11:15 +02:00
Weblate
00bd9c241a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-03-03 02:32:29 +02:00
Weblate
1283e06f95 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: modo24ro <marius.odobasa@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translation: Servarr/Prowlarr
2024-02-29 04:00:49 +02:00
Devotee2161
ab0108778a Fixed: (AnimeBytes) Artist and album search improvements 2024-02-29 04:00:26 +02:00
Bogdan
099b04f718 Update caniuse-lite 2024-02-29 02:37:14 +02:00
Chris
ecdc0a51a9 Fixed: Cleanse Discord Webhook URLs
(cherry picked from commit d1f2a8a9486471f4986da2fa16d5439ccf0426e1)
2024-02-24 23:42:45 +02:00
Bogdan
6c7c37affe Bump node to v20.x on builder 2024-02-23 20:13:02 +02:00
Servarr
45d378a2d9 Automated API Docs update 2024-02-23 19:55:10 +02:00
Bogdan
007601cb19 Fixed: Selection of last added custom filter
Plus some translations and typos
2024-02-23 19:45:48 +02:00
Bogdan
5f0d6e2fdd New: Sync Pack Seed Time to Whisparr applications
Fixes #2039
2024-02-21 17:34:22 +02:00
Bogdan
ede9879c99 Cleanup obsolete definitions for ANT/ABB/BB/MTV/PTN/TVV 2024-02-21 17:12:35 +02:00
Benjamin Harder
7287abc77c New: Sync Reject Blocklisted Torrent Hashes While Grabbing for torrent indexers to Apps 2024-02-21 05:30:42 +02:00
Bogdan
8c653b5c09 Fixed: (GGn) Don't die on invalid FreeTorrent values in the API 2024-02-19 01:56:51 +02:00
Ryan S
15c6b3c308 Fixed: (Shazbat) Added season and episode to search capabilities 2024-02-18 23:11:05 +02:00
Bogdan
9676447c74 Bump version to 1.14.1 2024-02-18 23:09:35 +02:00
Bogdan
5d35f1dcc7 Fixed: (AvistaZ) Avoid parsing invalid JSON on auth
Closes #2030
2024-02-16 17:17:06 +02:00
Weblate
858f16195e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: David13467 <davidnow00@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Steve Hansen <steve@hansenconsultancy.be>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2024-02-16 01:52:35 +02:00
Bogdan
a1a5dd574e Fixed: Using FlareSolverr in the same time with another HTTP/SOCKS proxy 2024-02-16 01:50:56 +02:00
Bogdan
a5ecc2dc9f Show download client ID as hint in select options 2024-02-14 18:36:57 +02:00
Bogdan
7d46660583 Fixed: Updated base url for TorrentsCSV 2024-02-14 01:41:14 +02:00
Bogdan
22cbf40e3c Bump label-actions action to v4 2024-02-13 15:49:26 +02:00
Weblate
25821c758f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2024-02-12 21:20:01 +02:00
Bogdan
6153737a78 Translations for download client settings
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
2024-02-12 21:17:54 +02:00
Bogdan
07adb45d63 Bump version to 1.14.0 2024-02-12 02:22:39 +02:00
Bogdan
02bc40b9b6 Translations for indexer settings 2024-02-10 21:29:01 +02:00
Weblate
83e7e30e4f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-02-10 19:49:52 +02:00
bakerboy448
ae870fd46a Improve HDBits Settings Helptext 2024-02-09 21:31:15 +02:00
Weblate
33b7ba8725 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hicabi Erdem <bilgi@hicabierdem.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: savin-msk <ns@a77.io>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2024-02-09 15:34:37 +02:00
Bogdan
dd2567a85f Fixed: Refresh tags state to clear removed tags by housekeeping 2024-02-07 23:24:25 +02:00
Bogdan
264ff8f885 Revert "Enable browser navigation buttons for PWA"
This reverts commit 1b36951879.
2024-02-07 18:29:27 +02:00
Mark McDowall
629c6a8891 Fixed: Redirecting after login with urlbase
(cherry picked from commit 745b92daf4bf4b9562ffe52dad84a12a5561add5)
2024-02-07 09:02:37 +02:00
Mark McDowall
0ce2f96789 New: Log database engine version on startup
(cherry picked from commit 6ab1d8e16b29e98b4d2ebb68e0356f6f2d3a2c10)
2024-02-07 09:02:19 +02:00
Mark McDowall
cd7d1571db Fixed: Don't use sub folder to check for free disk space for update
(cherry picked from commit f722d49b3a9efefa65bef1b24d90be9332ca62ea)
2024-02-07 09:01:59 +02:00
abcasada
4558f55282 Hints for week column and short dates in UI settings 2024-02-06 22:07:41 +02:00
Bogdan
21589fda57 Bump version to 1.13.3 2024-02-04 12:53:06 +02:00
Weblate
3496263cd2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander <a.burdun@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Ole Nørby <ole@olenoerby.dk>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Stas Panasiuk <temnyip@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2024-02-03 22:46:02 +02:00
230 changed files with 4685 additions and 3895 deletions

View File

@@ -0,0 +1,13 @@
// This file is used to open the backend and frontend in the same workspace, which is necessary as
// the frontend has vscode settings that are distinct from the backend
{
"folders": [
{
"path": ".."
},
{
"path": "../frontend"
}
],
"settings": {}
}

View File

@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "Prowlarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"nvmVersion": "latest"
}
},
"forwardPorts": [9696],
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

View File

@@ -18,6 +18,6 @@ jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/label-actions@v3
- uses: dessant/label-actions@v4
with:
process-only: 'issues, prs'

1
.gitignore vendored
View File

@@ -127,6 +127,7 @@ coverage*.xml
coverage*.json
setup/Output/
*.~is
.mono
# VS outout folders
bin

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-dotnettools.csdevkit",
"ms-vscode-remote.remote-containers"
]
}

26
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": "Run Prowlarr",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Prowlarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

44
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,44 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build dotnet",
"command": "dotnet",
"type": "process",
"args": [
"msbuild",
"-restore",
"${workspaceFolder}/src/Prowlarr.sln",
"-p:GenerateFullPaths=true",
"-p:Configuration=Debug",
"-p:Platform=Posix",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Prowlarr.sln",
"-property:GenerateFullPaths=true",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/Prowlarr.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -9,14 +9,14 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.13.2'
majorVersion: '1.16.2'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417'
nodeVersion: '16.X'
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'

View File

@@ -12,7 +12,6 @@ export interface CommandBody {
lastStartTime: string;
trigger: string;
suppressMessages: boolean;
seriesId?: number;
}
interface Command extends ModelBase {

View File

@@ -1,3 +1,4 @@
import { maxBy } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -50,7 +51,7 @@ class FilterBuilderModalContent extends Component {
if (id) {
dispatchSetFilter({ selectedFilterKey: id });
} else {
const last = customFilters[customFilters.length -1];
const last = maxBy(customFilters, 'id');
dispatchSetFilter({ selectedFilterKey: last.id });
}
@@ -108,7 +109,7 @@ class FilterBuilderModalContent extends Component {
this.setState({
labelErrors: [
{
message: 'Label is required'
message: translate('LabelIsRequired')
}
]
});
@@ -146,13 +147,13 @@ class FilterBuilderModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Custom Filter
{translate('CustomFilter')}
</ModalHeader>
<ModalBody>
<div className={styles.labelContainer}>
<div className={styles.label}>
Label
{translate('Label')}
</div>
<div className={styles.labelInputContainer}>

View File

@@ -37,8 +37,8 @@ class CustomFilter extends Component {
dispatchSetFilter
} = this.props;
// Assume that delete and then unmounting means the delete was successful.
// Moving this check to a ancestor would be more accurate, but would have
// Assume that delete and then unmounting means the deletion was successful.
// Moving this check to an ancestor would be more accurate, but would have
// more boilerplate.
if (this.state.isDeleting && id === selectedFilterKey) {
dispatchSetFilter({ selectedFilterKey: 'all' });

View File

@@ -24,16 +24,20 @@ function createMapStateToProps() {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: true
get value() {
return translate('NoChange');
},
isDisabled: true
});
}
if (includeMixed) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
get value() {
return `(${translate('Mixed')})`;
},
isDisabled: true
});
}

View File

@@ -1,54 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import SelectInput from './SelectInput';
const availabilityOptions = [
{ key: 'announced', value: 'Announced' },
{ key: 'inCinemas', value: 'In Cinemas' },
{ key: 'released', value: 'Released' },
{ key: 'preDB', value: 'PreDB' }
];
function AvailabilitySelectInput(props) {
const values = [...availabilityOptions];
const {
includeNoChange,
includeMixed
} = props;
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
disabled: true
});
}
if (includeMixed) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
});
}
return (
<SelectInput
{...props}
values={values}
/>
);
}
AvailabilitySelectInput.propTypes = {
includeNoChange: PropTypes.bool.isRequired,
includeMixed: PropTypes.bool.isRequired
};
AvailabilitySelectInput.defaultProps = {
includeNoChange: false,
includeMixed: false
};
export default AvailabilitySelectInput;

View File

@@ -24,7 +24,8 @@ function createMapStateToProps() {
.sort(sortByName)
.map((downloadClient) => ({
key: downloadClient.id,
value: downloadClient.name
value: downloadClient.name,
hint: `(${downloadClient.id})`
}));
if (includeAny) {

View File

@@ -5,7 +5,6 @@ import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AppProfileSelectInputConnector from './AppProfileSelectInputConnector';
import AutoCompleteInput from './AutoCompleteInput';
import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector';
import CardigannCaptchaInputConnector from './CardigannCaptchaInputConnector';
import CheckInput from './CheckInput';
@@ -37,9 +36,6 @@ function getComponent(type) {
case inputTypes.AUTO_COMPLETE:
return AutoCompleteInput;
case inputTypes.AVAILABILITY_SELECT:
return AvailabilitySelectInput;
case inputTypes.CAPTCHA:
return CaptchaInputConnector;

View File

@@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "minimal-ui"
"display": "standalone"
}

View File

@@ -0,0 +1,17 @@
import { useCallback, useState } from 'react';
export default function useModalOpenState(
initialState: boolean
): [boolean, () => void, () => void] {
const [isOpen, setOpen] = useState(initialState);
const setModalOpen = useCallback(() => {
setOpen(true);
}, [setOpen]);
const setModalClosed = useCallback(() => {
setOpen(false);
}, [setOpen]);
return [isOpen, setModalOpen, setModalClosed];
}

View File

@@ -1,6 +1,5 @@
export const AUTO_COMPLETE = 'autoComplete';
export const APP_PROFILE_SELECT = 'appProfileSelect';
export const AVAILABILITY_SELECT = 'availabilitySelect';
export const CAPTCHA = 'captcha';
export const CARDIGANNCAPTCHA = 'cardigannCaptcha';
export const CHECK = 'check';
@@ -27,7 +26,6 @@ export const TAG_SELECT = 'tagSelect';
export const all = [
AUTO_COMPLETE,
APP_PROFILE_SELECT,
AVAILABILITY_SELECT,
CAPTCHA,
CARDIGANNCAPTCHA,
CHECK,

View File

@@ -97,7 +97,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="enable"
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
helpTextWarning={supportsRss.value ? undefined : translate('RssIsNotSupportedWithThisIndexer')}
{...enable}
onChange={onInputChange}
/>
@@ -144,6 +144,7 @@ function EditIndexerModalContent(props) {
}) :
null
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -35,7 +35,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'true',

View File

@@ -47,9 +47,3 @@
justify-content: space-between;
}
@media only screen and (max-width: $breakpointExtraSmall) {
.modalFooter {
flex-direction: column;
gap: 10px;
}
}

View File

@@ -212,7 +212,11 @@ class SearchFooter extends Component {
name="searchQuery"
value={searchQuery}
buttons={
<FormInputButton onPress={this.onQueryParameterModalOpenClick}>
<FormInputButton
kind={kinds.DEFAULT}
onPress={this.onQueryParameterModalOpenClick}
title={translate('ClickToChangeQueryOptions')}
>
<Icon
name={icon}
/>
@@ -275,6 +279,7 @@ class SearchFooter extends Component {
}
<SpinnerButton
kind={kinds.PRIMARY}
className={styles.searchButton}
isSpinning={isFetching}
isDisabled={isFetching || !hasIndexers}

View File

@@ -30,7 +30,7 @@ const syncLevelOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: ApplicationSyncLevel.Disabled,

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Card from 'Components/Card';
import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddDownloadClientModal from './AddDownloadClientModal';
import DownloadClient from './DownloadClient';
@@ -59,48 +60,59 @@ class DownloadClients extends Component {
} = this.state;
return (
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>
{
items.map((item) => {
return (
<DownloadClient
key={item.id}
{...item}
onConfirmDeleteDownloadClient={onConfirmDeleteDownloadClient}
/>
);
})
}
<Card
className={styles.addDownloadClient}
onPress={this.onAddDownloadClientPress}
>
<div className={styles.center}>
<Icon
name={icons.ADD}
size={45}
/>
</div>
</Card>
<div>
<Alert kind={kinds.INFO}>
<div>
{translate('ProwlarrDownloadClientsAlert')}
</div>
<div>
{translate('ProwlarrDownloadClientsInAppOnlyAlert')}
</div>
</Alert>
<AddDownloadClientModal
isOpen={isAddDownloadClientModalOpen}
onModalClose={this.onAddDownloadClientModalClose}
/>
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>
{
items.map((item) => {
return (
<DownloadClient
key={item.id}
{...item}
onConfirmDeleteDownloadClient={onConfirmDeleteDownloadClient}
/>
);
})
}
<EditDownloadClientModalConnector
isOpen={isEditDownloadClientModalOpen}
onModalClose={this.onEditDownloadClientModalClose}
/>
</PageSectionContent>
</FieldSet>
<Card
className={styles.addDownloadClient}
onPress={this.onAddDownloadClientPress}
>
<div className={styles.center}>
<Icon
name={icons.ADD}
size={45}
/>
</div>
</Card>
</div>
<AddDownloadClientModal
isOpen={isAddDownloadClientModalOpen}
onModalClose={this.onAddDownloadClientModalClose}
/>
<EditDownloadClientModalConnector
isOpen={isEditDownloadClientModalOpen}
onModalClose={this.onEditDownloadClientModalClose}
/>
</PageSectionContent>
</FieldSet>
</div>
);
}
}

View File

@@ -17,6 +17,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import AddCategoryModalConnector from './Categories/AddCategoryModalConnector';
import Category from './Categories/Category';
@@ -61,6 +62,7 @@ class EditDownloadClientModalContent extends Component {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteDownloadClientPress,
onConfirmDeleteCategory,
...otherProps
@@ -219,6 +221,12 @@ class EditDownloadClientModalContent extends Component {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -260,6 +268,7 @@ EditDownloadClientModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteDownloadClientPress: PropTypes.func,
onConfirmDeleteCategory: PropTypes.func.isRequired
};

View File

@@ -2,7 +2,15 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteDownloadClientCategory, fetchDownloadClientCategories, saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
import {
deleteDownloadClientCategory,
fetchDownloadClientCategories,
saveDownloadClient,
setDownloadClientFieldValue,
setDownloadClientValue,
testDownloadClient,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
@@ -27,7 +35,8 @@ const mapDispatchToProps = {
saveDownloadClient,
testDownloadClient,
fetchDownloadClientCategories,
deleteDownloadClientCategory
deleteDownloadClientCategory,
toggleAdvancedSettings
};
class EditDownloadClientModalContentConnector extends Component {
@@ -68,6 +77,10 @@ class EditDownloadClientModalContentConnector extends Component {
this.props.testDownloadClient({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
onConfirmDeleteCategory = (id) => {
this.props.deleteDownloadClientCategory({ id });
};
@@ -81,6 +94,7 @@ class EditDownloadClientModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
onConfirmDeleteCategory={this.onConfirmDeleteCategory}
@@ -102,6 +116,7 @@ EditDownloadClientModalContentConnector.propTypes = {
setDownloadClientFieldValue: PropTypes.func.isRequired,
saveDownloadClient: PropTypes.func.isRequired,
testDownloadClient: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -30,7 +30,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditIndexerProxyModalContent.css';
@@ -31,6 +32,7 @@ function EditIndexerProxyModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteIndexerProxyPress,
...otherProps
} = props;
@@ -130,6 +132,12 @@ function EditIndexerProxyModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -169,6 +177,7 @@ EditIndexerProxyModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteIndexerProxyPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveIndexerProxy, setIndexerProxyFieldValue, setIndexerProxyValue, testIndexerProxy } from 'Store/Actions/settingsActions';
import {
saveIndexerProxy,
setIndexerProxyFieldValue,
setIndexerProxyValue,
testIndexerProxy,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditIndexerProxyModalContent from './EditIndexerProxyModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setIndexerProxyValue,
setIndexerProxyFieldValue,
saveIndexerProxy,
testIndexerProxy
testIndexerProxy,
toggleAdvancedSettings
};
class EditIndexerProxyModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditIndexerProxyModalContentConnector extends Component {
this.props.testIndexerProxy({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditIndexerProxyModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditIndexerProxyModalContentConnector.propTypes = {
setIndexerProxyFieldValue: PropTypes.func.isRequired,
saveIndexerProxy: PropTypes.func.isRequired,
testIndexerProxy: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import NotificationEventItems from './NotificationEventItems';
import styles from './EditNotificationModalContent.css';
@@ -32,6 +33,7 @@ function EditNotificationModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteNotificationPress,
...otherProps
} = props;
@@ -136,6 +138,12 @@ function EditNotificationModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -175,6 +183,7 @@ EditNotificationModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteNotificationPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveNotification, setNotificationFieldValue, setNotificationValue, testNotification } from 'Store/Actions/settingsActions';
import {
saveNotification,
setNotificationFieldValue,
setNotificationValue,
testNotification,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditNotificationModalContent from './EditNotificationModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setNotificationValue,
setNotificationFieldValue,
saveNotification,
testNotification
testNotification,
toggleAdvancedSettings
};
class EditNotificationModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditNotificationModalContentConnector extends Component {
this.props.testNotification({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditNotificationModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditNotificationModalContentConnector.propTypes = {
setNotificationFieldValue: PropTypes.func.isRequired,
saveNotification: PropTypes.func.isRequired,
testNotification: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -96,7 +96,7 @@ class AppProfile extends Component {
kind={enableRss ? kinds.SUCCESS : kinds.DISABLED}
outline={!enableRss}
>
{translate('RSS')}
{translate('Rss')}
</Label>
}

View File

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchApplications, fetchIndexerProxies, fetchNotifications } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions';
import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions';
import Tags from './Tags';
function createMapStateToProps() {
@@ -25,6 +25,7 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchIndexerProxies: fetchIndexerProxies,
@@ -38,12 +39,14 @@ class MetadatasConnector extends Component {
componentDidMount() {
const {
dispatchFetchTags,
dispatchFetchTagDetails,
dispatchFetchNotifications,
dispatchFetchIndexerProxies,
dispatchFetchApplications
} = this.props;
dispatchFetchTags();
dispatchFetchTagDetails();
dispatchFetchNotifications();
dispatchFetchIndexerProxies();
@@ -63,6 +66,7 @@ class MetadatasConnector extends Component {
}
MetadatasConnector.propTypes = {
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchIndexerProxies: PropTypes.func.isRequired,

View File

@@ -21,19 +21,19 @@ export const firstDayOfWeekOptions = [
];
export const weekColumnOptions = [
{ key: 'ddd M/D', value: 'Tue 3/25' },
{ key: 'ddd MM/DD', value: 'Tue 03/25' },
{ key: 'ddd D/M', value: 'Tue 25/3' },
{ key: 'ddd DD/MM', value: 'Tue 25/03' }
{ key: 'ddd M/D', value: 'Tue 3/25', hint: 'ddd M/D' },
{ key: 'ddd MM/DD', value: 'Tue 03/25', hint: 'ddd MM/DD' },
{ key: 'ddd D/M', value: 'Tue 25/3', hint: 'ddd D/M' },
{ key: 'ddd DD/MM', value: 'Tue 25/03', hint: 'ddd DD/MM' }
];
const shortDateFormatOptions = [
{ key: 'MMM D YYYY', value: 'Mar 25 2014' },
{ key: 'DD MMM YYYY', value: '25 Mar 2014' },
{ key: 'MM/D/YYYY', value: '03/25/2014' },
{ key: 'MM/DD/YYYY', value: '03/25/2014' },
{ key: 'DD/MM/YYYY', value: '25/03/2014' },
{ key: 'YYYY-MM-DD', value: '2014-03-25' }
{ key: 'MMM D YYYY', value: 'Mar 25 2014', hint: 'MMM D YYYY' },
{ key: 'DD MMM YYYY', value: '25 Mar 2014', hint: 'DD MMM YYYY' },
{ key: 'MM/D/YYYY', value: '03/25/2014', hint: 'MM/D/YYYY' },
{ key: 'MM/DD/YYYY', value: '03/25/2014', hint: 'MM/DD/YYYY' },
{ key: 'DD/MM/YYYY', value: '25/03/2014', hint: 'DD/MM/YYYY' },
{ key: 'YYYY-MM-DD', value: '2014-03-25', hint: 'YYYY-MM-DD' }
];
const longDateFormatOptions = [

View File

@@ -1,8 +1,11 @@
import $ from 'jquery';
import _ from 'lodash';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions';
const abortCurrentRequests = {};
let lastTestData = null;
export function createCancelTestProviderHandler(section) {
return function(getState, payload, dispatch) {
@@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isTesting: true }));
const testData = getProviderState(payload, getState, section);
const {
queryParams = {},
...otherPayload
} = payload;
const testData = getProviderState({ ...otherPayload }, getState, section);
const params = { ...queryParams };
// If the user is re-testing the same provider without changes
// force it to be tested.
if (_.isEqual(testData, lastTestData)) {
params.forceTest = true;
}
lastTestData = testData;
const ajaxOptions = {
url: `${url}/test`,
url: `${url}/test?${$.param(params, true)}`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
@@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
lastTestData = null;
dispatch(set({
section,
isTesting: false,

View File

@@ -41,6 +41,14 @@ function getInternalLink(source) {
to="/settings/connect"
/>
);
case 'IndexerProxyStatusCheck':
return (
<IconButton
name={icons.SETTINGS}
title={translate('Settings')}
to="/settings/indexers"
/>
);
case 'IndexerRssCheck':
case 'IndexerSearchCheck':
case 'IndexerStatusCheck':

View File

@@ -10,15 +10,6 @@
width: 100%;
}
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}
.queued,
.started,
.ended {

View File

@@ -2,14 +2,12 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'commandName': string;
'duration': string;
'ended': string;
'queued': string;
'started': string;
'trigger': string;
'triggerContent': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,279 +0,0 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status, message) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`
};
default:
return {
name: icons.UNKNOWN,
title
};
}
}
function getFormattedDates(props) {
const {
queued,
started,
ended,
showRelativeDates,
shortDateFormat
} = props;
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-'
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-'
};
}
class QueuedTaskRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
...getFormattedDates(props),
isCancelConfirmModalOpen: false
};
this._updateTimeoutId = null;
}
componentDidMount() {
this.setUpdateTimer();
}
componentDidUpdate(prevProps) {
const {
queued,
started,
ended
} = this.props;
if (
queued !== prevProps.queued ||
started !== prevProps.started ||
ended !== prevProps.ended
) {
this.setState(getFormattedDates(this.props));
}
}
componentWillUnmount() {
if (this._updateTimeoutId) {
this._updateTimeoutId = clearTimeout(this._updateTimeoutId);
}
}
//
// Control
setUpdateTimer() {
this._updateTimeoutId = setTimeout(() => {
this.setState(getFormattedDates(this.props));
this.setUpdateTimer();
}, 30000);
}
//
// Listeners
onCancelPress = () => {
this.setState({
isCancelConfirmModalOpen: true
});
};
onAbortCancel = () => {
this.setState({
isCancelConfirmModalOpen: false
});
};
//
// Render
render() {
const {
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
clientUserAgent,
longDateFormat,
timeFormat,
onCancelPress
} = this.props;
const {
queuedAt,
startedAt,
endedAt,
isCancelConfirmModalOpen
} = this.state;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon
name={triggerIcon}
title={titleCase(trigger)}
/>
<Icon
{...getStatusIconProps(status, message)}
/>
</span>
</TableRowCell>
<TableRowCell>
<span className={styles.commandName}>
{commandName}
</span>
{
clientUserAgent ?
<span className={styles.userAgent} title={translate('UserAgentProvidedByTheAppThatCalledTheAPI')}>
from: {clientUserAgent}
</span> :
null
}
</TableRowCell>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell
className={styles.actions}
>
{
status === 'queued' &&
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={this.onCancelPress}
/>
}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={onCancelPress}
onCancel={this.onAbortCancel}
/>
</TableRow>
);
}
}
QueuedTaskRow.propTypes = {
trigger: PropTypes.string.isRequired,
commandName: PropTypes.string.isRequired,
queued: PropTypes.string.isRequired,
started: PropTypes.string,
ended: PropTypes.string,
status: PropTypes.string.isRequired,
duration: PropTypes.string,
message: PropTypes.string,
clientUserAgent: PropTypes.string,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onCancelPress: PropTypes.func.isRequired
};
export default QueuedTaskRow;

View File

@@ -0,0 +1,238 @@
import moment from 'moment';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
import { icons, kinds } from 'Helpers/Props';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import QueuedTaskRowNameCell from './QueuedTaskRowNameCell';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status: string, message: string | undefined) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title,
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title,
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`,
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`,
};
default:
return {
name: icons.UNKNOWN,
title,
};
}
}
function getFormattedDates(
queued: string,
started: string | undefined,
ended: string | undefined,
showRelativeDates: boolean,
shortDateFormat: string
) {
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-',
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-',
};
}
interface QueuedTimes {
queuedAt: string;
startedAt: string;
endedAt: string;
}
export interface QueuedTaskRowProps {
id: number;
trigger: string;
commandName: string;
queued: string;
started?: string;
ended?: string;
status: string;
duration?: string;
message?: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRow(props: QueuedTaskRowProps) {
const {
id,
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
body,
clientUserAgent,
} = props;
const dispatch = useDispatch();
const { longDateFormat, shortDateFormat, showRelativeDates, timeFormat } =
useSelector(createUISettingsSelector());
const updateTimeTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(
null
);
const [times, setTimes] = useState<QueuedTimes>(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
const [
isCancelConfirmModalOpen,
openCancelConfirmModal,
closeCancelConfirmModal,
] = useModalOpenState(false);
const handleCancelPress = useCallback(() => {
dispatch(cancelCommand({ id }));
}, [id, dispatch]);
useEffect(() => {
updateTimeTimeoutId.current = setTimeout(() => {
setTimes(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
}, 30000);
return () => {
if (updateTimeTimeoutId.current) {
clearTimeout(updateTimeTimeoutId.current);
}
};
}, [queued, started, ended, showRelativeDates, shortDateFormat, setTimes]);
const { queuedAt, startedAt, endedAt } = times;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon name={triggerIcon} title={titleCase(trigger)} />
<Icon {...getStatusIconProps(status, message)} />
</span>
</TableRowCell>
<QueuedTaskRowNameCell
commandName={commandName}
body={body}
clientUserAgent={clientUserAgent}
/>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell className={styles.actions}>
{status === 'queued' && (
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={openCancelConfirmModal}
/>
)}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={handleCancelPress}
onCancel={closeCancelConfirmModal}
/>
</TableRow>
);
}

View File

@@ -1,31 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import QueuedTaskRow from './QueuedTaskRow';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onCancelPress() {
dispatch(cancelCommand({
id: props.id
}));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(QueuedTaskRow);

View File

@@ -0,0 +1,8 @@
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}

View File

@@ -0,0 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'commandName': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { CommandBody } from 'Commands/Command';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRowNameCell.css';
export interface QueuedTaskRowNameCellProps {
commandName: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRowNameCell(
props: QueuedTaskRowNameCellProps
) {
const { commandName, clientUserAgent } = props;
return (
<TableRowCell>
<span className={styles.commandName}>{commandName}</span>
{clientUserAgent ? (
<span
className={styles.userAgent}
title={translate('TaskUserAgentTooltip')}
>
{translate('From')}: {clientUserAgent}
</span>
) : null}
</TableRowCell>
);
}

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import QueuedTaskRowConnector from './QueuedTaskRowConnector';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true
},
{
name: 'actions',
isVisible: true
}
];
function QueuedTasks(props) {
const {
isFetching,
isPopulated,
items
} = props;
return (
<FieldSet legend={translate('Queue')}>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
isPopulated &&
<Table
columns={columns}
>
<TableBody>
{
items.map((item) => {
return (
<QueuedTaskRowConnector
key={item.id}
{...item}
/>
);
})
}
</TableBody>
</Table>
}
</FieldSet>
);
}
QueuedTasks.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired
};
export default QueuedTasks;

View File

@@ -0,0 +1,74 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { fetchCommands } from 'Store/Actions/commandActions';
import translate from 'Utilities/String/translate';
import QueuedTaskRow from './QueuedTaskRow';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true,
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true,
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true,
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true,
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true,
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true,
},
{
name: 'actions',
isVisible: true,
},
];
export default function QueuedTasks() {
const dispatch = useDispatch();
const { isFetching, isPopulated, items } = useSelector(
(state: AppState) => state.commands
);
useEffect(() => {
dispatch(fetchCommands());
}, [dispatch]);
return (
<FieldSet legend={translate('Queue')}>
{isFetching && !isPopulated && <LoadingIndicator />}
{isPopulated && (
<Table columns={columns}>
<TableBody>
{items.map((item) => {
return <QueuedTaskRow key={item.id} {...item} />;
})}
</TableBody>
</Table>
)}
</FieldSet>
);
}

View File

@@ -1,46 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchCommands } from 'Store/Actions/commandActions';
import QueuedTasks from './QueuedTasks';
function createMapStateToProps() {
return createSelector(
(state) => state.commands,
(commands) => {
return commands;
}
);
}
const mapDispatchToProps = {
dispatchFetchCommands: fetchCommands
};
class QueuedTasksConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchCommands();
}
//
// Render
render() {
return (
<QueuedTasks
{...this.props}
/>
);
}
}
QueuedTasksConnector.propTypes = {
dispatchFetchCommands: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueuedTasksConnector);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import QueuedTasksConnector from './Queued/QueuedTasksConnector';
import QueuedTasks from './Queued/QueuedTasks';
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
function Tasks() {
@@ -10,7 +10,7 @@ function Tasks() {
<PageContent title={translate('Tasks')}>
<PageContentBody>
<ScheduledTasksConnector />
<QueuedTasksConnector />
<QueuedTasks />
</PageContentBody>
</PageContent>
);

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#e66001" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3a3f51" />
<meta name="msapplication-navbutton-color" content="#464b51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Prowlarr" />

View File

@@ -3,15 +3,18 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#e66001" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#464b51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Prowlarr (Preview)" />
<meta name="description" content="Prowlarr" />
<link
rel="apple-touch-icon"

View File

@@ -11,7 +11,7 @@
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
"lint-fix": "yarn lint --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
"stylelint-windows": "stylelint \"frontend/**/*.css\" --config frontend/.stylelintrc",
"check-modules": "are-you-es5 check . -r"
},
"repository": "https://github.com/Prowlarr/Prowlarr",

View File

@@ -144,16 +144,46 @@
</Otherwise>
</Choose>
<!--
Set architecture to RuntimeInformation.ProcessArchitecture if not specified -->
<Choose>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X64'">
<PropertyGroup>
<Architecture>x64</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X86'">
<PropertyGroup>
<Architecture>x86</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm64'">
<PropertyGroup>
<Architecture>arm64</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm'">
<PropertyGroup>
<Architecture>arm</Architecture>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<Architecture></Architecture>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition="'$(IsWindows)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<RuntimeIdentifier>win-$(Architecture)</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<RuntimeIdentifier>linux-$(Architecture)</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)' == 'true' and

View File

@@ -119,6 +119,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Discord
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
public void should_clean_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);

View File

@@ -1,13 +1,16 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -29,11 +32,14 @@ namespace NzbDrone.Common.Http.Dispatchers
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
private readonly ICached<CredentialCache> _credentialCache;
private readonly Logger _logger;
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
ICreateManagedWebProxy createManagedWebProxy,
ICertificateValidationService certificateValidationService,
IUserAgentBuilder userAgentBuilder,
ICacheManager cacheManager)
ICacheManager cacheManager,
Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy;
@@ -42,6 +48,8 @@ namespace NzbDrone.Common.Http.Dispatchers
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
_logger = logger;
}
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
@@ -275,7 +283,27 @@ namespace NzbDrone.Common.Http.Dispatchers
return _credentialCache.Get("credentialCache", () => new CredentialCache());
}
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
private bool HasRoutableIPv4Address()
{
// Get all IPv4 addresses from all interfaces and return true if there are any with non-loopback addresses
try
{
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
return networkInterfaces.Any(ni =>
ni.OperationalStatus == OperationalStatus.Up &&
ni.GetIPProperties().UnicastAddresses.Any(ip =>
ip.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip.Address)));
}
catch (Exception e)
{
_logger.Debug(e, "Caught exception while GetAllNetworkInterfaces assuming IPv4 connectivity: {0}", e.Message);
return true;
}
}
private async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.
// This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 and expected to be fixed in .NET 6.
@@ -298,10 +326,10 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch
{
// very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt.
// note that this may cause users to eventually get switched to ipv4 (on a random failure when they are switching networks, for instance)
// but in the interest of keeping this implementation simple, this is acceptable.
useIPv6 = false;
// Do not retry IPv6 if a routable IPv4 address is available, otherwise continue to attempt IPv6 connections.
var routableIPv4 = HasRoutableIPv4Address();
_logger.Info("IPv4 is available: {0}, IPv6 will be {1}", routableIPv4, routableIPv4 ? "disabled" : "left enabled");
useIPv6 = !routableIPv4;
}
finally
{

View File

@@ -61,6 +61,9 @@ namespace NzbDrone.Common.Instrumentation
// Applications
new (@"""name"":""apikey"",""value"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class newznab_category_to_capabilities_settingsFixture : MigrationTest<newznab_category_to_capabilities_settings>
{
[Test]
public void should_migrate_categories_when_capabilities_is_not_defined()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "Usenet Indexer",
Redirect = false,
AppProfileId = 0,
DownloadClientId = 0,
Priority = 25,
Added = DateTime.UtcNow,
Implementation = "Newznab",
Settings = new
{
Categories = new[]
{
new { Id = 2000, Name = "Movies" },
new { Id = 5000, Name = "TV" }
}
}.ToJson(),
ConfigContract = "NewznabSettings"
});
});
var items = db.Query<IndexerDefinition40>("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Newznab");
items.First().ConfigContract.Should().Be("NewznabSettings");
items.First().Settings.Should().ContainKey("capabilities");
items.First().Settings.Should().NotContainKey("categories");
var newznabSettings = items.First().Settings.ToObject<NewznabSettings40>();
newznabSettings.Capabilities.Should().NotBeNull();
newznabSettings.Capabilities.SupportsRawSearch.Should().Be(false);
newznabSettings.Capabilities.Categories.Should().HaveCount(2);
newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 2000 && c.Name == "Movies");
newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 5000 && c.Name == "TV");
}
[Test]
public void should_migrate_categories_when_capabilities_is_defined()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "Usenet Indexer",
Redirect = false,
AppProfileId = 0,
DownloadClientId = 0,
Priority = 25,
Added = DateTime.UtcNow,
Implementation = "Newznab",
Settings = new
{
Capabilities = new
{
SupportsRawSearch = true
},
Categories = new[]
{
new { Id = 2000, Name = "Movies" },
new { Id = 5000, Name = "TV" }
}
}.ToJson(),
ConfigContract = "NewznabSettings"
});
});
var items = db.Query<IndexerDefinition40>("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Newznab");
items.First().ConfigContract.Should().Be("NewznabSettings");
items.First().Settings.Should().ContainKey("capabilities");
items.First().Settings.Should().NotContainKey("categories");
var newznabSettings = items.First().Settings.ToObject<NewznabSettings40>();
newznabSettings.Capabilities.Should().NotBeNull();
newznabSettings.Capabilities.SupportsRawSearch.Should().Be(true);
newznabSettings.Capabilities.Categories.Should().HaveCount(2);
newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 2000 && c.Name == "Movies");
newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 5000 && c.Name == "TV");
}
[Test]
public void should_use_defaults_when_categories_are_empty()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "Usenet Indexer",
Redirect = false,
AppProfileId = 0,
DownloadClientId = 0,
Priority = 25,
Added = DateTime.UtcNow,
Implementation = "Newznab",
Settings = new
{
Categories = Array.Empty<object>()
}.ToJson(),
ConfigContract = "NewznabSettings"
});
});
var items = db.Query<IndexerDefinition40>("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Newznab");
items.First().ConfigContract.Should().Be("NewznabSettings");
items.First().Settings.Should().ContainKey("capabilities");
items.First().Settings.Should().NotContainKey("categories");
var newznabSettings = items.First().Settings.ToObject<NewznabSettings40>();
newznabSettings.Capabilities.Should().NotBeNull();
newznabSettings.Capabilities.SupportsRawSearch.Should().Be(false);
newznabSettings.Capabilities.Categories.Should().NotBeNull();
newznabSettings.Capabilities.Categories.Should().HaveCount(0);
}
[Test]
public void should_use_defaults_when_settings_are_empty()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "Usenet Indexer",
Redirect = false,
AppProfileId = 0,
DownloadClientId = 0,
Priority = 25,
Added = DateTime.UtcNow,
Implementation = "Newznab",
Settings = new { }.ToJson(),
ConfigContract = "NewznabSettings"
});
});
var items = db.Query<IndexerDefinition40>("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Newznab");
items.First().ConfigContract.Should().Be("NewznabSettings");
items.First().Settings.Should().NotContainKey("capabilities");
items.First().Settings.Should().NotContainKey("categories");
items.First().Settings.ToObject<NewznabSettings40>().Capabilities.Should().BeNull();
}
}
public class IndexerDefinition40
{
public int Id { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public JObject Settings { get; set; }
}
public class NewznabSettings39
{
public object Categories { get; set; }
}
public class NewznabSettings40
{
public NewznabCapabilitiesSettings40 Capabilities { get; set; }
}
public class NewznabCapabilitiesSettings40
{
public bool SupportsRawSearch { get; set; }
public List<IndexerCategory40> Categories { get; set; }
}
public class IndexerCategory40
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.Resolve<ConfigService>(), TestLogger));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>(), TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(Array.Empty<IHttpRequestInterceptor>(), Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), TestLogger));
Mocker.SetConstant<IProwlarrCloudRequestBuilder>(new ProwlarrCloudRequestBuilder());
}

View File

@@ -45,10 +45,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
var releases = (await Subject.Fetch(new BasicSearchCriteria { SearchTerm = "test", Categories = new[] { 2000, 5000 } })).Releases;
releases.Should().HaveCount(33);
releases.Should().HaveCount(39);
releases.First().Should().BeOfType<TorrentInfo>();
var firstTorrentInfo = releases.ElementAt(2) as TorrentInfo;
var firstTorrentInfo = releases.ElementAt(3) as TorrentInfo;
firstTorrentInfo.Title.Should().Be("[SubsPlease] One Piece: The Great Gold Pirate - 1059 [Web][MKV][h264][720p][AAC 2.0][Softsubs (SubsPlease)][Episode 1059]");
firstTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
firstTorrentInfo.Files.Should().Be(1);
firstTorrentInfo.MinimumSeedTime.Should().Be(259200);
var secondTorrentInfo = releases.ElementAt(16) as TorrentInfo;
var secondTorrentInfo = releases.ElementAt(20) as TorrentInfo;
secondTorrentInfo.Title.Should().Be("[GHOST] BLEACH S03 [Blu-ray][MKV][h265 10-bit][1080p][AC3 2.0][Dual Audio][Softsubs (GHOST)]");
secondTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
secondTorrentInfo.Files.Should().Be(22);
secondTorrentInfo.MinimumSeedTime.Should().Be(655200);
var thirdTorrentInfo = releases.ElementAt(18) as TorrentInfo;
var thirdTorrentInfo = releases.ElementAt(23) as TorrentInfo;
thirdTorrentInfo.Title.Should().Be("[Polarwindz] Cowboy Bebop: Tengoku no Tobira 2001 [Blu-ray][MKV][h265 10-bit][1080p][Opus 5.1][Softsubs (Polarwindz)]");
thirdTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
thirdTorrentInfo.Files.Should().Be(1);
thirdTorrentInfo.MinimumSeedTime.Should().Be(475200);
var fourthTorrentInfo = releases.ElementAt(3) as TorrentInfo;
var fourthTorrentInfo = releases.ElementAt(5) as TorrentInfo;
fourthTorrentInfo.Title.Should().Be("[SubsPlease] Dr. STONE: NEW WORLD S03E03 - 03 [Web][MKV][h264][720p][AAC 2.0][Softsubs (SubsPlease)][Episode 3]");
fourthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
fourthTorrentInfo.Files.Should().Be(1);
fourthTorrentInfo.MinimumSeedTime.Should().Be(259200);
var fifthTorrentInfo = releases.ElementAt(23) as TorrentInfo;
var fifthTorrentInfo = releases.ElementAt(28) as TorrentInfo;
fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
fifthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
@@ -138,7 +138,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
fifthTorrentInfo.Files.Should().Be(11);
fifthTorrentInfo.MinimumSeedTime.Should().Be(529200);
var sixthTorrentInfo = releases.ElementAt(31) as TorrentInfo;
var sixthTorrentInfo = releases.ElementAt(37) as TorrentInfo;
sixthTorrentInfo.Title.Should().Be("[HorribleSubs] Dr. STONE S01 [Web][MKV][h264][720p][AAC 2.0][Softsubs (HorribleSubs)]");
sixthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 21:26:21"));
torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 10:04:50"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 09:04:50"));
torrentInfo.Size.Should().Be(7085405541);
torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 05:24:49"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 04:24:49"));
torrentInfo.Size.Should().Be(69914591044);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 19:20:19"));
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -33,6 +33,10 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
};
_caps = new IndexerCapabilities();
_caps.Categories.AddCategoryMapping(2000, NewznabStandardCategory.Movies, "Movies");
_caps.Categories.AddCategoryMapping(5000, NewznabStandardCategory.TV, "TV");
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
.Returns(_caps);

View File

@@ -37,6 +37,9 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
_caps.Categories.AddCategoryMapping(2000, NewznabStandardCategory.Movies, "Movies");
_caps.Categories.AddCategoryMapping(2040, NewznabStandardCategory.MoviesHD, "Movies/HD");
_caps.Categories.AddCategoryMapping(5000, NewznabStandardCategory.TV, "TV");
_caps.Categories.AddCategoryMapping(5040, NewznabStandardCategory.TVHD, "TV/HD");
_caps.Categories.AddCategoryMapping(5070, NewznabStandardCategory.TVAnime, "TV/Anime");
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>(), It.IsAny<IndexerDefinition>()))
@@ -83,23 +86,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
var releases = (await Subject.Fetch(new MovieSearchCriteria())).Releases;
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var releaseInfo = releases.First() as TorrentInfo;
releaseInfo.Title.Should().Be("Series Title S05E02 HDTV x264-Xclusive [eztv]");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
releaseInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+%5Beztv%5D&tr=udp:%2F%2Fopen.demonii.com:1337&tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&tr=udp:%2F%2Fexodus.desync.com:6969");
releaseInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+%5Beztv%5D&tr=udp:%2F%2Fopen.demonii.com:1337&tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&tr=udp:%2F%2Fexodus.desync.com:6969");
releaseInfo.InfoUrl.Should().Be("https://thepiratebay.se/torrent/11811366/Series_Title_S05E02_HDTV_x264-Xclusive_%5Beztv%5D");
releaseInfo.CommentUrl.Should().Be("https://thepiratebay.se/torrent/11811366/Series_Title_S05E02_HDTV_x264-Xclusive_%5Beztv%5D");
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("Sat, 11 Apr 2015 21:34:00 -0600").ToUniversalTime());
releaseInfo.Size.Should().Be(388895872);
releaseInfo.InfoHash.Should().Be("9fb267cff5ae5603f07a347676ec3bf3e35f75e1");
releaseInfo.Seeders.Should().Be(34128);
releaseInfo.Peers.Should().Be(36724);
releases.Should().HaveCount(0);
}
[Test]

View File

@@ -13,6 +13,6 @@ namespace NzbDrone.Core.Applications
public override bool SendUpdatesToClient => true;
public override string CompletionMessage => null;
public override string CompletionMessage => "Completed";
}
}

View File

@@ -15,12 +15,14 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
private readonly ILazyLibrarianV1Proxy _lazyLibrarianV1Proxy;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public LazyLibrarian(ILazyLibrarianV1Proxy lazyLibrarianV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public LazyLibrarian(ILazyLibrarianV1Proxy lazyLibrarianV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_lazyLibrarianV1Proxy = lazyLibrarianV1Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -65,7 +67,9 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -74,7 +78,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var lazyLibrarianIndexer = BuildLazyLibrarianIndexer(indexer, indexer.Protocol);
var lazyLibrarianIndexer = BuildLazyLibrarianIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _lazyLibrarianV1Proxy.AddIndexer(lazyLibrarianIndexer, Settings);
@@ -107,11 +111,12 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var indexerProps = indexerMapping.RemoteIndexerName.Split(",");
var lazyLibrarianIndexer = BuildLazyLibrarianIndexer(indexer, indexer.Protocol, indexerProps[1]);
var lazyLibrarianIndexer = BuildLazyLibrarianIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerProps[1]);
//Use the old remote id to find the indexer on LazyLibrarian incase the update was from a name change in Prowlarr
var remoteIndexer = _lazyLibrarianV1Proxy.GetIndexer(indexerProps[1], lazyLibrarianIndexer.Type, Settings);
@@ -133,7 +138,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to LazyLibrarian", indexer.Name, indexer.Id);
var newRemoteIndexer = _lazyLibrarianV1Proxy.AddIndexer(lazyLibrarianIndexer, Settings);
@@ -146,7 +151,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
}
}
private LazyLibrarianIndexer BuildLazyLibrarianIndexer(IndexerDefinition indexer, DownloadProtocol protocol, string originalName = null)
private LazyLibrarianIndexer BuildLazyLibrarianIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, string originalName = null)
{
var schema = protocol == DownloadProtocol.Usenet ? LazyLibrarianProviderType.Newznab : LazyLibrarianProviderType.Torznab;
@@ -156,7 +161,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
Altername = $"{indexer.Name} (Prowlarr)",
Host = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/api",
Apikey = _configFileProvider.ApiKey,
Categories = string.Join(",", indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Categories = string.Join(",", indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Enabled = indexer.Enable,
Type = schema,
Priority = indexer.Priority

View File

@@ -21,13 +21,15 @@ namespace NzbDrone.Core.Applications.Lidarr
private readonly ILidarrV1Proxy _lidarrV1Proxy;
private readonly ICached<List<LidarrIndexer>> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public Lidarr(ICacheManager cacheManager, ILidarrV1Proxy lidarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public Lidarr(ICacheManager cacheManager, ILidarrV1Proxy lidarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_schemaCache = cacheManager.GetCache<List<LidarrIndexer>>(GetType());
_lidarrV1Proxy = lidarrV1Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -49,7 +51,7 @@ namespace NzbDrone.Core.Applications.Lidarr
try
{
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, testIndexer.Capabilities, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
@@ -118,7 +120,9 @@ namespace NzbDrone.Core.Applications.Lidarr
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -127,7 +131,7 @@ namespace NzbDrone.Core.Applications.Lidarr
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var lidarrIndexer = BuildLidarrIndexer(indexer, indexer.Protocol);
var lidarrIndexer = BuildLidarrIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _lidarrV1Proxy.AddIndexer(lidarrIndexer, Settings);
@@ -159,10 +163,11 @@ namespace NzbDrone.Core.Applications.Lidarr
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var lidarrIndexer = BuildLidarrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var lidarrIndexer = BuildLidarrIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var remoteIndexer = _lidarrV1Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
@@ -174,7 +179,7 @@ namespace NzbDrone.Core.Applications.Lidarr
{
_logger.Debug("Syncing remote indexer with current settings");
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
// Retain user fields not-affiliated with Prowlarr
lidarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => lidarrIndexer.Fields.All(s => s.Name != f.Name)));
@@ -200,7 +205,7 @@ namespace NzbDrone.Core.Applications.Lidarr
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to Lidarr", indexer.Name, indexer.Id);
lidarrIndexer.Id = 0;
@@ -214,11 +219,11 @@ namespace NzbDrone.Core.Applications.Lidarr
}
}
private LidarrIndexer BuildLidarrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
private LidarrIndexer BuildLidarrIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, int id = 0)
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
if (id == 0)
{
@@ -250,7 +255,7 @@ namespace NzbDrone.Core.Applications.Lidarr
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
if (indexer.Protocol == DownloadProtocol.Torrent)
{
@@ -258,10 +263,15 @@ namespace NzbDrone.Core.Applications.Lidarr
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime") != null)
if (lidarrIndexer.Fields.Any(x => x.Name == "seedCriteria.discographySeedTime"))
{
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}
if (lidarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = Settings.SyncRejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return lidarrIndexer;

View File

@@ -55,6 +55,10 @@ namespace NzbDrone.Core.Applications.Lidarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -62,7 +66,7 @@ namespace NzbDrone.Core.Applications.Lidarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -36,9 +36,12 @@ namespace NzbDrone.Core.Applications.Lidarr
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), HelpText = "Only Indexers that support these categories will be synced", Advanced = true)]
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "ApplicationSettingsSyncRejectBlocklistedTorrentHashes", HelpText = "ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool SyncRejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -15,12 +15,14 @@ namespace NzbDrone.Core.Applications.Mylar
private readonly IMylarV3Proxy _mylarV3Proxy;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public Mylar(IMylarV3Proxy mylarV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public Mylar(IMylarV3Proxy mylarV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_mylarV3Proxy = mylarV3Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -65,7 +67,9 @@ namespace NzbDrone.Core.Applications.Mylar
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -74,7 +78,7 @@ namespace NzbDrone.Core.Applications.Mylar
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var mylarIndexer = BuildMylarIndexer(indexer, indexer.Protocol);
var mylarIndexer = BuildMylarIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _mylarV3Proxy.AddIndexer(mylarIndexer, Settings);
@@ -107,11 +111,12 @@ namespace NzbDrone.Core.Applications.Mylar
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var indexerProps = indexerMapping.RemoteIndexerName.Split(",");
var mylarIndexer = BuildMylarIndexer(indexer, indexer.Protocol, indexerProps[1]);
var mylarIndexer = BuildMylarIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerProps[1]);
//Use the old remote id to find the indexer on Mylar incase the update was from a name change in Prowlarr
var remoteIndexer = _mylarV3Proxy.GetIndexer(indexerProps[1], mylarIndexer.Type, Settings);
@@ -133,7 +138,7 @@ namespace NzbDrone.Core.Applications.Mylar
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to Mylar", indexer.Name, indexer.Id);
var newRemoteIndexer = _mylarV3Proxy.AddIndexer(mylarIndexer, Settings);
@@ -146,7 +151,7 @@ namespace NzbDrone.Core.Applications.Mylar
}
}
private MylarIndexer BuildMylarIndexer(IndexerDefinition indexer, DownloadProtocol protocol, string originalName = null)
private MylarIndexer BuildMylarIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, string originalName = null)
{
var schema = protocol == DownloadProtocol.Usenet ? MylarProviderType.Newznab : MylarProviderType.Torznab;
@@ -156,7 +161,7 @@ namespace NzbDrone.Core.Applications.Mylar
Altername = $"{indexer.Name} (Prowlarr)",
Host = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/api",
Apikey = _configFileProvider.ApiKey,
Categories = string.Join(",", indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Categories = string.Join(",", indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())),
Enabled = indexer.Enable,
Type = schema,
};

View File

@@ -21,13 +21,15 @@ namespace NzbDrone.Core.Applications.Radarr
private readonly IRadarrV3Proxy _radarrV3Proxy;
private readonly ICached<List<RadarrIndexer>> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public Radarr(ICacheManager cacheManager, IRadarrV3Proxy radarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public Radarr(ICacheManager cacheManager, IRadarrV3Proxy radarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_schemaCache = cacheManager.GetCache<List<RadarrIndexer>>(GetType());
_radarrV3Proxy = radarrV3Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -49,7 +51,7 @@ namespace NzbDrone.Core.Applications.Radarr
try
{
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, testIndexer.Capabilities, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
@@ -118,7 +120,9 @@ namespace NzbDrone.Core.Applications.Radarr
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -127,7 +131,7 @@ namespace NzbDrone.Core.Applications.Radarr
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var radarrIndexer = BuildRadarrIndexer(indexer, indexer.Protocol);
var radarrIndexer = BuildRadarrIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _radarrV3Proxy.AddIndexer(radarrIndexer, Settings);
@@ -159,10 +163,11 @@ namespace NzbDrone.Core.Applications.Radarr
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var radarrIndexer = BuildRadarrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var radarrIndexer = BuildRadarrIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var remoteIndexer = _radarrV3Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
@@ -172,7 +177,7 @@ namespace NzbDrone.Core.Applications.Radarr
if (!radarrIndexer.Equals(remoteIndexer) || forceSync)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
// Retain user fields not-affiliated with Prowlarr
radarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => radarrIndexer.Fields.All(s => s.Name != f.Name)));
@@ -198,7 +203,7 @@ namespace NzbDrone.Core.Applications.Radarr
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to Radarr", indexer.Name, indexer.Id);
radarrIndexer.Id = 0;
@@ -212,11 +217,11 @@ namespace NzbDrone.Core.Applications.Radarr
}
}
private RadarrIndexer BuildRadarrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
private RadarrIndexer BuildRadarrIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, int id = 0)
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
if (id == 0)
{
@@ -248,13 +253,18 @@ namespace NzbDrone.Core.Applications.Radarr
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
if (indexer.Protocol == DownloadProtocol.Torrent)
{
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (radarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = Settings.SyncRejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return radarrIndexer;

View File

@@ -51,6 +51,10 @@ namespace NzbDrone.Core.Applications.Radarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -58,7 +62,7 @@ namespace NzbDrone.Core.Applications.Radarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -37,9 +37,12 @@ namespace NzbDrone.Core.Applications.Radarr
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), HelpText = "Only Indexers that support these categories will be synced", Advanced = true)]
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "ApplicationSettingsSyncRejectBlocklistedTorrentHashes", HelpText = "ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool SyncRejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -21,13 +21,15 @@ namespace NzbDrone.Core.Applications.Readarr
private readonly ICached<List<ReadarrIndexer>> _schemaCache;
private readonly IReadarrV1Proxy _readarrV1Proxy;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public Readarr(ICacheManager cacheManager, IReadarrV1Proxy readarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public Readarr(ICacheManager cacheManager, IReadarrV1Proxy readarrV1Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_schemaCache = cacheManager.GetCache<List<ReadarrIndexer>>(GetType());
_readarrV1Proxy = readarrV1Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -49,7 +51,7 @@ namespace NzbDrone.Core.Applications.Readarr
try
{
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, testIndexer.Capabilities, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
@@ -118,7 +120,9 @@ namespace NzbDrone.Core.Applications.Readarr
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -127,7 +131,7 @@ namespace NzbDrone.Core.Applications.Readarr
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var readarrIndexer = BuildReadarrIndexer(indexer, indexer.Protocol);
var readarrIndexer = BuildReadarrIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _readarrV1Proxy.AddIndexer(readarrIndexer, Settings);
@@ -159,10 +163,11 @@ namespace NzbDrone.Core.Applications.Readarr
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var readarrIndexer = BuildReadarrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var readarrIndexer = BuildReadarrIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var remoteIndexer = _readarrV1Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
@@ -174,7 +179,7 @@ namespace NzbDrone.Core.Applications.Readarr
{
_logger.Debug("Syncing remote indexer with current settings");
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
// Retain user fields not-affiliated with Prowlarr
readarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => readarrIndexer.Fields.All(s => s.Name != f.Name)));
@@ -200,7 +205,7 @@ namespace NzbDrone.Core.Applications.Readarr
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to Readarr", indexer.Name, indexer.Id);
readarrIndexer.Id = 0;
@@ -214,11 +219,11 @@ namespace NzbDrone.Core.Applications.Readarr
}
}
private ReadarrIndexer BuildReadarrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
private ReadarrIndexer BuildReadarrIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, int id = 0)
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _readarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");
@@ -244,7 +249,7 @@ namespace NzbDrone.Core.Applications.Readarr
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
if (indexer.Protocol == DownloadProtocol.Torrent)
{
@@ -252,10 +257,15 @@ namespace NzbDrone.Core.Applications.Readarr
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime") != null)
if (readarrIndexer.Fields.Any(x => x.Name == "seedCriteria.discographySeedTime"))
{
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}
if (readarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = Settings.SyncRejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return readarrIndexer;

View File

@@ -55,6 +55,10 @@ namespace NzbDrone.Core.Applications.Readarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -62,7 +66,7 @@ namespace NzbDrone.Core.Applications.Readarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -37,9 +37,12 @@ namespace NzbDrone.Core.Applications.Readarr
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), HelpText = "Only Indexers that support these categories will be synced", Advanced = true)]
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "ApplicationSettingsSyncRejectBlocklistedTorrentHashes", HelpText = "ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool SyncRejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -21,13 +21,15 @@ namespace NzbDrone.Core.Applications.Sonarr
private readonly ICached<List<SonarrIndexer>> _schemaCache;
private readonly ISonarrV3Proxy _sonarrV3Proxy;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public Sonarr(ICacheManager cacheManager, ISonarrV3Proxy sonarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public Sonarr(ICacheManager cacheManager, ISonarrV3Proxy sonarrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_schemaCache = cacheManager.GetCache<List<SonarrIndexer>>(GetType());
_sonarrV3Proxy = sonarrV3Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -49,7 +51,7 @@ namespace NzbDrone.Core.Applications.Sonarr
try
{
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, testIndexer.Capabilities, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
@@ -122,8 +124,10 @@ namespace NzbDrone.Core.Applications.Sonarr
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty() &&
indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty() &&
indexerCapabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -132,7 +136,7 @@ namespace NzbDrone.Core.Applications.Sonarr
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var sonarrIndexer = BuildSonarrIndexer(indexer, indexer.Protocol);
var sonarrIndexer = BuildSonarrIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _sonarrV3Proxy.AddIndexer(sonarrIndexer, Settings);
@@ -164,10 +168,11 @@ namespace NzbDrone.Core.Applications.Sonarr
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var sonarrIndexer = BuildSonarrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var sonarrIndexer = BuildSonarrIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var remoteIndexer = _sonarrV3Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
@@ -179,7 +184,7 @@ namespace NzbDrone.Core.Applications.Sonarr
{
_logger.Debug("Syncing remote indexer with current settings");
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any() || indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any() || indexerCapabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Any())
{
// Retain user fields not-affiliated with Prowlarr
sonarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => sonarrIndexer.Fields.All(s => s.Name != f.Name)));
@@ -206,7 +211,7 @@ namespace NzbDrone.Core.Applications.Sonarr
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any() || indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any() || indexerCapabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to Sonarr", indexer.Name, indexer.Id);
sonarrIndexer.Id = 0;
@@ -220,11 +225,11 @@ namespace NzbDrone.Core.Applications.Sonarr
}
}
private SonarrIndexer BuildSonarrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
private SonarrIndexer BuildSonarrIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, int id = 0)
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "animeStandardFormatSearch", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "animeStandardFormatSearch", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
if (id == 0)
{
@@ -256,8 +261,8 @@ namespace NzbDrone.Core.Applications.Sonarr
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()));
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value = JArray.FromObject(indexerCapabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()));
if (sonarrIndexer.Fields.Any(x => x.Name == "animeStandardFormatSearch"))
{
@@ -270,6 +275,11 @@ namespace NzbDrone.Core.Applications.Sonarr
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (sonarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = Settings.SyncRejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return sonarrIndexer;

View File

@@ -61,6 +61,10 @@ namespace NzbDrone.Core.Applications.Sonarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -68,7 +72,7 @@ namespace NzbDrone.Core.Applications.Sonarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && animeCats && animeStandardFormatSearchCompare && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && animeCats && animeStandardFormatSearchCompare && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -43,9 +43,12 @@ namespace NzbDrone.Core.Applications.Sonarr
[FieldDefinition(4, Label = "Anime Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
public IEnumerable<int> AnimeSyncCategories { get; set; }
[FieldDefinition(5, Label = "Sync Anime Standard Format Search", Type = FieldType.Checkbox, Advanced = true, HelpText = "Sync also searching for anime using the standard numbering")]
[FieldDefinition(5, Label = "Sync Anime Standard Format Search", Type = FieldType.Checkbox, HelpText = "Sync also searching for anime using the standard numbering", Advanced = true)]
public bool SyncAnimeStandardFormatSearch { get; set; }
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "ApplicationSettingsSyncRejectBlocklistedTorrentHashes", HelpText = "ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool SyncRejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -21,13 +21,15 @@ namespace NzbDrone.Core.Applications.Whisparr
private readonly IWhisparrV3Proxy _whisparrV3Proxy;
private readonly ICached<List<WhisparrIndexer>> _schemaCache;
private readonly IConfigFileProvider _configFileProvider;
private readonly IIndexerFactory _indexerFactory;
public Whisparr(ICacheManager cacheManager, IWhisparrV3Proxy whisparrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
public Whisparr(ICacheManager cacheManager, IWhisparrV3Proxy whisparrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, IIndexerFactory indexerFactory, Logger logger)
: base(appIndexerMapService, logger)
{
_schemaCache = cacheManager.GetCache<List<WhisparrIndexer>>(GetType());
_whisparrV3Proxy = whisparrV3Proxy;
_configFileProvider = configFileProvider;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@@ -49,7 +51,7 @@ namespace NzbDrone.Core.Applications.Whisparr
try
{
failures.AddIfNotNull(_whisparrV3Proxy.TestConnection(BuildWhisparrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
failures.AddIfNotNull(_whisparrV3Proxy.TestConnection(BuildWhisparrIndexer(testIndexer, testIndexer.Capabilities, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
@@ -118,7 +120,9 @@ namespace NzbDrone.Core.Applications.Whisparr
public override void AddIndexer(IndexerDefinition indexer)
{
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Empty())
{
_logger.Trace("Skipping add for indexer {0} [{1}] due to no app Sync Categories supported by the indexer", indexer.Name, indexer.Id);
@@ -127,7 +131,7 @@ namespace NzbDrone.Core.Applications.Whisparr
_logger.Trace("Adding indexer {0} [{1}]", indexer.Name, indexer.Id);
var whisparrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol);
var whisparrIndexer = BuildWhisparrIndexer(indexer, indexerCapabilities, indexer.Protocol);
var remoteIndexer = _whisparrV3Proxy.AddIndexer(whisparrIndexer, Settings);
@@ -159,10 +163,11 @@ namespace NzbDrone.Core.Applications.Whisparr
{
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
var indexerCapabilities = _indexerFactory.GetInstance(indexer).GetCapabilities();
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
var whisparrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var whisparrIndexer = BuildWhisparrIndexer(indexer, indexerCapabilities, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
var remoteIndexer = _whisparrV3Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
@@ -174,7 +179,7 @@ namespace NzbDrone.Core.Applications.Whisparr
{
_logger.Debug("Syncing remote indexer with current settings");
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
// Retain user fields not-affiliated with Prowlarr
whisparrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => whisparrIndexer.Fields.All(s => s.Name != f.Name)));
@@ -200,7 +205,7 @@ namespace NzbDrone.Core.Applications.Whisparr
{
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
if (indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
{
_logger.Debug("Remote indexer not found, re-adding {0} [{1}] to Whisparr", indexer.Name, indexer.Id);
whisparrIndexer.Id = 0;
@@ -214,11 +219,11 @@ namespace NzbDrone.Core.Applications.Whisparr
}
}
private WhisparrIndexer BuildWhisparrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
private WhisparrIndexer BuildWhisparrIndexer(IndexerDefinition indexer, IndexerCapabilities indexerCapabilities, DownloadProtocol protocol, int id = 0)
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _whisparrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");
@@ -244,13 +249,23 @@ namespace NzbDrone.Core.Applications.Whisparr
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexerCapabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
if (indexer.Protocol == DownloadProtocol.Torrent)
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (whisparrIndexer.Fields.Any(x => x.Name == "seedCriteria.seasonPackSeedTime"))
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}
if (whisparrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = Settings.SyncRejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return whisparrIndexer;

View File

@@ -47,10 +47,18 @@ namespace NzbDrone.Core.Applications.Whisparr
var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value);
var seedTimeCompare = seedTime == otherSeedTime;
var seasonSeedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
var otherSeasonSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
var seasonSeedTimeCompare = seasonSeedTime == otherSeasonSeedTime;
var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -58,7 +66,7 @@ namespace NzbDrone.Core.Applications.Whisparr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -37,9 +37,12 @@ namespace NzbDrone.Core.Applications.Whisparr
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), HelpText = "Only Indexers that support these categories will be synced", Advanced = true)]
public IEnumerable<int> SyncCategories { get; set; }
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "ApplicationSettingsSyncRejectBlocklistedTorrentHashes", HelpText = "ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool SyncRejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -0,0 +1,69 @@
using System.Data;
using System.Text.RegularExpressions;
using FluentMigrator;
using NLog;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Core.Datastore.Migration
{
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
public class DatabaseEngineVersionCheck : FluentMigrator.Migration
{
protected readonly Logger _logger;
public DatabaseEngineVersionCheck()
{
_logger = NzbDroneLogger.GetLogger(this);
}
public override void Up()
{
IfDatabase("sqlite").Execute.WithConnection(LogSqliteVersion);
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
}
public override void Down()
{
// No-op
}
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
{
using (var versionCmd = conn.CreateCommand())
{
versionCmd.Transaction = tran;
versionCmd.CommandText = "SELECT sqlite_version();";
using (var reader = versionCmd.ExecuteReader())
{
while (reader.Read())
{
var version = reader.GetString(0);
_logger.Info("SQLite {0}", version);
}
}
}
}
private void LogPostgresVersion(IDbConnection conn, IDbTransaction tran)
{
using (var versionCmd = conn.CreateCommand())
{
versionCmd.Transaction = tran;
versionCmd.CommandText = "SHOW server_version";
using (var reader = versionCmd.ExecuteReader())
{
while (reader.Read())
{
var version = reader.GetString(0);
var cleanVersion = Regex.Replace(version, @"\(.*?\)", "");
_logger.Info("Postgres {0}", cleanVersion);
}
}
}
}
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Data;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(40)]
public class newznab_category_to_capabilities_settings : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(MoveCategoriesToCapabilities);
}
private void MoveCategoriesToCapabilities(IDbConnection conn, IDbTransaction tran)
{
var updated = new List<object>();
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" IN ('Newznab', 'Torznab')";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = Json.Deserialize<JObject>(reader.GetString(1));
if ((settings.Value<JObject>("capabilities")?.ContainsKey("categories") ?? false) == false
&& settings.ContainsKey("categories")
&& settings.TryGetValue("categories", out var categories))
{
if (!settings.ContainsKey("capabilities"))
{
settings.Add("capabilities", new JObject());
}
settings.Value<JObject>("capabilities")?.Add(new JProperty("categories", JArray.FromObject(categories)));
if (settings.ContainsKey("categories"))
{
settings.Remove("categories");
}
}
updated.Add(new
{
Settings = settings.ToJson(),
Id = id
});
}
}
}
var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
}
}

View File

@@ -42,12 +42,13 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddNLog())
.AddFluentMigratorCore()
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
.ConfigureRunner(
builder => builder
.AddPostgres()
.AddNzbDroneSQLite()
.WithGlobalConnectionString(connectionString)
.WithMigrationsIn(Assembly.GetExecutingAssembly()))
.ScanIn(Assembly.GetExecutingAssembly()).For.All())
.Configure<TypeFilterOptions>(opt => opt.Namespace = "NzbDrone.Core.Datastore.Migration")
.Configure<ProcessorOptions>(opt =>
{

View File

@@ -32,13 +32,13 @@ namespace NzbDrone.Core.Download.Clients.Aria2
[FieldDefinition(1, Label = "Port", Type = FieldType.Number)]
public int Port { get; set; }
[FieldDefinition(2, Label = "XML RPC Path", Type = FieldType.Textbox)]
[FieldDefinition(2, Label = "XmlRpcPath", Type = FieldType.Textbox)]
public string RpcPath { get; set; }
[FieldDefinition(3, Label = "Use SSL", Type = FieldType.Checkbox)]
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; }
[FieldDefinition(4, Label = "Secret token", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(4, Label = "SecretToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string SecretToken { get; set; }
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientAriaSettingsDirectoryHelpText")]

View File

@@ -27,15 +27,16 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .torrent file")]
[FieldDefinition(0, Label = "TorrentBlackholeTorrentFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
[FieldToken(TokenField.HelpText, "TorrentBlackholeTorrentFolder", "extension", ".torrent")]
public string TorrentFolder { get; set; }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[FieldDefinition(1, Label = "Save Magnet Files", Type = FieldType.Checkbox, HelpText = "Save a .magnet file with the magnet link if no .torrent file is available (only useful if the download client supports .magnet files)")]
[FieldDefinition(1, Label = "TorrentBlackholeSaveMagnetFiles", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesHelpText")]
public bool SaveMagnetFiles { get; set; }
[FieldDefinition(2, Label = "Save Magnet Files", Type = FieldType.Textbox, HelpText = "Extension to use for magnet links, defaults to '.magnet'")]
[FieldDefinition(2, Label = "TorrentBlackholeSaveMagnetFilesExtension", Type = FieldType.Textbox, HelpText = "TorrentBlackholeSaveMagnetFilesExtensionHelpText")]
public string MagnetFileExtension { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -18,7 +18,8 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .nzb file")]
[FieldDefinition(0, Label = "UsenetBlackholeNzbFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
[FieldToken(TokenField.HelpText, "UsenetBlackholeNzbFolder", "extension", ".nzb")]
public string NzbFolder { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -34,22 +34,24 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Deluge")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Deluge")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the deluge json url, see http://[host]:[port]/[urlBase]/json")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/json")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback Category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(5, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(6, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(6, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(7, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(7, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -36,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Download Station")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Download Station")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -45,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(5, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientDownloadStationSettingsDirectoryHelpText")]
public string TvDirectory { get; set; }
public DownloadStationSettings()

View File

@@ -308,14 +308,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.Category.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
var destDir = GetDefaultDir();
if (destDir.IsNotNullOrWhiteSpace() && Settings.Category.IsNotNullOrWhiteSpace())
{
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
}
return null;
return destDir.TrimEnd('/');
}
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)

View File

@@ -274,14 +274,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.Category.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
var destDir = GetDefaultDir();
if (destDir.IsNotNullOrWhiteSpace() && Settings.Category.IsNotNullOrWhiteSpace())
{
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
}
return null;
return destDir.TrimEnd('/');
}
protected override string AddFromLink(ReleaseInfo release)

View File

@@ -40,10 +40,12 @@ namespace NzbDrone.Core.Download.Clients.Flood
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Flood")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Flood")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, HelpText = "Optionally adds a prefix to Flood API, such as [protocol]://[host]:[port]/[urlBase]api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, HelpText = "DownloadClientFloodSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "[protocol]://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -52,16 +54,16 @@ namespace NzbDrone.Core.Download.Clients.Flood
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "Manually specifies download destination")]
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDestinationHelpText")]
public string Destination { get; set; }
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "Initial tags of a download. To be recognized, a download must have all initial tags. This avoids conflicts with unrelated downloads.")]
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "DownloadClientFloodSettingsTagsHelpText")]
public IEnumerable<string> Tags { get; set; }
[FieldDefinition(8, Label = "Additional Tags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "Adds properties of media as tags. Hints are examples.", Advanced = true)]
[FieldDefinition(8, Label = "DownloadClientFloodSettingsAdditionalTags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "DownloadClientFloodSettingsAdditionalTagsHelpText", Advanced = true)]
public IEnumerable<int> AdditionalTags { get; set; }
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -46,34 +46,39 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
ApiUrl = "/api/v1/";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsHostHelpText")]
[FieldToken(TokenField.HelpText, "Host", "url", "mafreebox.freebox.fr")]
public string Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsPortHelpText")]
[FieldToken(TokenField.HelpText, "Port", "port", 443)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Freebox API")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
[FieldDefinition(3, Label = "DownloadClientFreeboxSettingsApiUrl", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientFreeboxSettingsApiUrlHelpText")]
[FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "url", "http://[host]:[port]/[api_base_url]/[api_version]/")]
[FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "defaultApiUrl", "/api/v1/")]
public string ApiUrl { get; set; }
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
[FieldDefinition(4, Label = "DownloadClientFreeboxSettingsAppId", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsAppIdHelpText")]
public string AppId { get; set; }
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
[FieldDefinition(5, Label = "DownloadClientFreeboxSettingsAppToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "DownloadClientFreeboxSettingsAppTokenHelpText")]
public string AppToken { get; set; }
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsDestinationHelpText")]
public string DestinationDirectory { get; set; }
[FieldDefinition(7, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated non-Prowlarr downloads (will create a [category] subdirectory in the output directory)")]
[FieldDefinition(7, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing")]
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -39,10 +39,13 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Hadouken")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Hadouken")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Hadouken url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Hadouken")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -51,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release.")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -41,16 +41,18 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBVortex url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(2, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "NZBVortex")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(4, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(5, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(5, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -41,10 +41,13 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to NZBGet")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "NZBGet")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBGet url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "NZBGet")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/jsonrpc")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -53,13 +56,13 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority for items added from Prowlarr")]
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(8, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NZBGet version 16.0")]
[FieldDefinition(8, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox, HelpText = "DownloadClientNzbgetSettingsAddPausedHelpText")]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -19,10 +19,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
{
private static readonly PneumaticSettingsValidator Validator = new PneumaticSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "This folder will need to be reachable from XBMC")]
[FieldDefinition(0, Label = "DownloadClientPneumaticSettingsNzbFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsNzbFolderHelpText")]
public string NzbFolder { get; set; }
[FieldDefinition(1, Label = "Strm Folder", Type = FieldType.Path, HelpText = ".strm files in this folder will be import by drone")]
[FieldDefinition(1, Label = "DownloadClientPneumaticSettingsStrmFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsStrmFolderHelpText")]
public string StrmFolder { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -35,10 +35,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsUseSslHelpText")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the qBittorrent url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "qBittorrent")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -47,22 +49,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(8, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "Initial state for torrents added to qBittorrent")]
[FieldDefinition(8, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "DownloadClientQbittorrentSettingsInitialStateHelpText")]
public int InitialState { get; set; }
[FieldDefinition(9, Label = "Sequential Order", Type = FieldType.Checkbox, HelpText = "Download in sequential order (qBittorrent 4.1.0+)")]
[FieldDefinition(9, Label = "DownloadClientQbittorrentSettingsSequentialOrder", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsSequentialOrderHelpText")]
public bool SequentialOrder { get; set; }
[FieldDefinition(10, Label = "First and Last First", Type = FieldType.Checkbox, HelpText = "Download first and last pieces first (qBittorrent 4.1.0+)")]
[FieldDefinition(10, Label = "DownloadClientQbittorrentSettingsFirstAndLastFirst", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText")]
public bool FirstAndLast { get; set; }
[FieldDefinition(13, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
[FieldDefinition(11, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
public int ContentLayout { get; set; }
public NzbDroneValidationResult Validate()

Some files were not shown because too many files have changed in this diff Show More