1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

105 Commits

Author SHA1 Message Date
nitsua 12ae4fba88 Move edit icon to the left on remote path mappings
Fixes: #6686
2021-12-26 22:19:43 -06:00
bakerboy448 194e0f3d7f Fixed: Various Translations 2021-12-26 21:49:44 -06:00
Qstick d1fa92bc6c Fixed: Manual Import language handling 2021-12-26 19:41:37 -06:00
Qstick 974e44ce48 New: Link indexer to specific download client 2021-12-26 19:07:22 -06:00
Taloth Saldono de05be62d7 Added BDLight to quality parser
(cherry picked from commit 5c8f2518baa1b2d4a8b0507f9fafe12b2ecff1e5)
2021-12-26 19:05:11 -06:00
Qstick cae5badee0 New: Support server notifications
(cherry picked from commit f5f0dd6fae5bc9f308506d56be42ac9a4be908e7)

Closes Radarr #5393

[common]
2021-12-26 15:48:28 -06:00
Qstick 45d8227654 Fixed: Handle MS variant MPEG4 files in video formatter 2021-12-25 18:24:30 -06:00
ta264 7bbd2246c4 Fix secondary ffprobe scan 2021-12-24 17:47:17 +00:00
ta264 59fed13442 MediaInfoModel initializer -> assignment for NRE tracking 2021-12-23 21:41:38 +00:00
Qstick 50b273acae Fixed: Ignore permissions issues on recycle bin files 2021-12-24 11:05:22 -06:00
Qstick 4278415fd7 Fixed: Default MinAvail to Released if not passed on add
Fixes #2117
2021-12-24 11:05:22 -06:00
Qstick 124b50288d Fix invalid PropType for sizeOnDisk in DeleteMovieModal 2021-12-24 11:05:22 -06:00
bakerboy448 3fcc395964 Fixed: Escape Characters as needed for *znab queries
Fixes #6799

[common]
2021-12-23 15:12:05 -06:00
Weblate 0ee9981cba Translated using Weblate (Arabic) [skip ci]
Currently translated at 95.7% (1069 of 1116 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 98.5% (1100 of 1116 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 99.7% (1113 of 1116 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.5% (1111 of 1116 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1116 of 1116 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 10.1% (113 of 1114 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 10.0% (112 of 1113 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.0% (1091 of 1113 strings)

Added translation using Weblate (Ukrainian) [skip ci]

Added translation using Weblate (Persian) [skip ci]

Added translation using Weblate (Bengali) [skip ci]

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: htrex <hantarex@gmail.com>
Co-authored-by: rakan <rakaz30@hotmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translation: Servarr/Radarr
2021-12-23 15:10:42 -06:00
Qstick 2848899206 Fixed: Calendar can show incorrect Release Types 2021-12-23 14:34:48 -06:00
Qstick f1a00764cd New: Additional logging for InvalidModel BadRequest API calls
[common]
2021-12-23 14:22:09 -06:00
Qstick 346236764c Drop all Commands rows before running migration 204 2021-12-23 11:50:13 -06:00
Qstick eecd4e4b7d New: Allow Import to update existing Custom Formats
Fixes #6178
2021-12-21 19:18:36 -06:00
Qstick 2838d8ca29 Add Ratings to Movie Index sort menu
Fixes #6741
2021-12-21 19:01:11 -06:00
bakerboy448 4ebcbc28aa fix applicationupdate filename typo
fix onapplicationupdate translate typo
2021-12-21 18:23:42 -06:00
Qstick 2c24f7ca04 Ensure Identity on Tables that have been modified 2021-12-21 18:09:05 -06:00
Qstick ec86de78d2 Maintain PrimaryKey and AutoIncrement on some schemas
[common]
2021-12-21 17:45:00 -06:00
Qstick 4f5f9ff77e Update NotificationResource.cs 2021-12-21 16:07:25 -06:00
erikp9 465bb403a9 fixed hardsub detection 2021-12-21 07:58:13 -06:00
Qstick 9e175e28ef New: OnApplicationUpdate Notifications (#6854)
Fixes #4681
[common]
2021-12-20 23:12:12 -06:00
Qstick 4d2a311e40 Remove PreDB from MovieStatusType
Fixes #5002
2021-12-20 21:25:07 -06:00
Qstick b2195148a2 Fixed: NullRef in SchemaBuilder when sending payload without optional Provider.Settings fields 2021-12-20 20:38:16 -06:00
Qstick 2ae7371d73 Update Deluge log statements 2021-12-20 20:35:38 -06:00
Mark McDowall 7b03a856c9 Fixed: Increase width and truncate long titles on Import List Exclusions
Closes #6779

(cherry picked from commit 2d0541c03b761a0ec5e10711d6bd577e07141517)
2021-12-20 20:16:22 -06:00
Qstick 48d1d47b67 Rename ImportExclusions to ImportListExclusions 2021-12-20 20:16:22 -06:00
Qstick 906b9bb92a Fixed: Use unmodified titles when searching Nyaa
Fixes #6642

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2021-12-20 20:16:22 -06:00
Mark McDowall 6fc14278e6 Fixed: Parsing of quality when release group contains Remux
Closes #6607
2021-12-20 19:58:47 -06:00
Mark McDowall 34b269086d Fixed: Get full path for download station instead of shared folder
Closes #6751
Fixes #6818

(cherry picked from commit b184e62fa7dd7ecd089619f176e6388c1c3be25d)
2021-12-20 19:55:09 -06:00
Qstick 6c40a27f2e Restore Parse API compatibility
Fixes #6852
2021-12-20 19:54:01 -06:00
Qstick be158a09b4 Ensure new languages are in All collection 2021-12-20 19:44:35 -06:00
Robin Dadswell eecd746f51 New: Health check for Discord notifications setup as Slack 2021-12-21 00:59:53 +00:00
Robin Dadswell 5ed034320e New: Migrate Discord from Slack to Discord notifications 2021-12-21 00:59:53 +00:00
bakerboy448 41dd678dfd Better wording of MissingFromDisk
closes #6834
2021-12-20 18:56:52 -06:00
Qstick 716eadc551 Add Multiple Languages
Closes #6385
Closes #6564
Closes #6694
Closes #6463

Co-Authored-By: siankatabg <siankatabg@users.noreply.github.com>
Co-Authored-By: tandy1000 <24867509+tandy-1000@users.noreply.github.com>
Co-Authored-By: Kristof Mattei <864376+kristof-mattei@users.noreply.github.com>
Co-Authored-By: Oleksandr Hulyi <4095184+pamidur@users.noreply.github.com>
2021-12-20 18:01:00 -06:00
bakerboy448 1cb31aa95c Fixed: Support movies with French in their title
Regression: Dropped Support for poorly named French Releases

Add language test case

Fixes #6821
2021-12-20 17:32:53 -06:00
Weblate 568dd2fbb2 Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (1113 of 1113 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 99.9% (1112 of 1113 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1113 of 1113 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2021-12-20 17:21:44 -06:00
Qstick 5d091e519e More Bluray UHD test cases
Fixes #6839
2021-12-19 22:57:06 -06:00
Qstick faab78c00a Fixed: Improve WEBDL detection of Netflix Rips
Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2021-12-19 22:53:51 -06:00
Qstick f1461056ce Fixed: Parsing of Ger.Dub releases as German
Fixes #6778

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2021-12-19 22:50:42 -06:00
ta264 2042ffce62 Fixed: Don't buffer update package to memory when downloading
[common]
2021-12-19 22:32:37 -06:00
ta264 c6ae6f7b1c Reinstate update tests on BSD
[common]
2021-12-19 22:32:37 -06:00
Weblate 8d7affae68 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1113 of 1113 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2021-12-19 22:29:56 -06:00
Qstick a418111245 remove removeHandler net6 serialization frontend hack 2021-12-19 20:39:45 -06:00
Qstick 6359ed5757 Bump RestSharp in test package to 106.15.0 2021-12-18 10:04:26 -06:00
Weblate 9a8c1d7d1b Translated using Weblate (Hungarian) [skip ci]
Currently translated at 100.0% (1113 of 1113 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 100.0% (1113 of 1113 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 97.2% (1082 of 1113 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Jessie <1355239678@qq.com>
Co-authored-by: Nuno Filipe de Vilhena Santos <nunovilhenasantos@msn.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2021-12-15 22:04:26 -06:00
bakerboy448 e89c2ee9f7 Fixed: Better wording of MissingFromDisk
[common]
2021-12-14 21:08:23 +00:00
Servarr 3d36f88939 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci] (#6810)
Currently translated at 100.0% (1113 of 1113 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 99.9% (1112 of 1113 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1113 of 1113 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.7% (1110 of 1113 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1113 of 1113 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: 西行寺鬼鬼子 <zhang.yaowei@live.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: 西行寺鬼鬼子 <zhang.yaowei@live.com>
2021-12-13 22:37:07 -05:00
Jacob Roeland 8e4320a93b Fix FAQ Link on Add New Movie page 2021-12-13 07:36:23 -06:00
Servarr b095676010 Translations using Weblate [skip ci] 2021-12-12 11:28:24 -06:00
bakerboy448 41d69d8484 fix erroneous logging for windows service on non-windows
[common]
2021-12-11 22:20:16 -06:00
ta264 073e59e3db Fixed: Windows installer and adding/removing services
(cherry picked from commit 27e3b5e630f04b0774bd6693ffb1c79e7cab95d6)
2021-12-08 21:58:50 +00:00
ta264 588a0843a4 Fixed: Support older glibc in libMonoPosixHelper
(cherry picked from commit 9e7af8369e165c19a2f181071e63bef6961cd64e)
2021-12-08 16:04:51 -06:00
ta264 26cedfd47d Fixed: ffprobe on macOS 10.13 2021-12-07 21:11:35 +00:00
bakerboy448 16789e5b6b New: Display Unknown Items in Activity Queue by Default 2021-12-07 17:34:04 -06:00
Weblate 159edcde94 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 99.9% (1112 of 1113 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 98.6% (1098 of 1113 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.7% (1110 of 1113 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 96.8% (1078 of 1113 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: hidaba <nag@hidaba.com>
Co-authored-by: x-nemesis <fsedgy@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2021-12-07 17:33:52 -06:00
hugepants 5855773842 Fixed: Grammar in tooltip of download button
When hovering over the tooltip of an unclicked download button (e.g. in interactive search), it hasn't yet been "added" yet so it should be "add".
And once clicked, the tooltip should show "download queue" rather than "downloaded queue" as "downloaded" implies it has already been downloaded.
2021-12-07 08:15:59 -06:00
Robin Dadswell 73f2da72f3 New: Placeholders in notification fields 2021-12-07 09:59:35 +00:00
Robin Dadswell 7dda481824 New: Frontend Placeholders from the Backend 2021-12-07 09:59:35 +00:00
ta264 4d6c3369c6 New: Add osx-arm64 and linux-musl-arm builds
(cherry picked from commit 4ce405728a3ae32c8d0282e8e2b758084e8e5864)
2021-12-06 17:13:41 -06:00
Taloth Saldono 5c7756b575 Fixed: Release Push api broken when no indexer id is specified
(cherry picked from commit 20306a38e1d0f6a7954978cf07cde8e185f79c78)
2021-12-05 23:25:17 -06:00
Weblate cf8aa09615 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1113 of 1113 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2021-12-02 17:49:35 -06:00
ta264 cdde7d4d8b Workaround .net error serializing new object()
See https://github.com/dotnet/runtime/issues/61995
2021-12-02 21:36:07 +00:00
Weblate e139708bb2 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 99.5% (1108 of 1113 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1113 of 1113 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.7% (1110 of 1113 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: mm519897405 <baiya@vip.qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2021-11-30 22:09:38 -06:00
Qstick 47206dd2bd Cleanup MovieImport Tests 2021-11-28 23:25:13 -06:00
Qstick 9442666493 Fixed: Duplicates on index before housekeeper runs when manual importing over existing file 2021-11-28 22:06:24 -06:00
Qstick 005ad00caf Speed up History by Movie endpoint by avoiding multiple db calls
Closes #6704
2021-11-28 16:38:45 -06:00
bakerboy448 2ae056e727 README updates [skip ci] 2021-11-28 15:55:40 -06:00
Qstick c538424229 New: Reanalyze media files if file size changes
Fixes #6757
Fixes #6765
Fixes #4482

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2021-11-28 15:42:44 -06:00
Qstick 507e8ec814 New: Parse 960p as 720p instead of 480p, parse 540p
Fixes #6304
Fixes #6767
2021-11-28 01:38:10 -06:00
Qstick 9d6614b14a New: Support AKA release titles
Co-Authored-By: aeonnoea <46950349+0aeonnoea0@users.noreply.github.com>
2021-11-28 01:37:21 -06:00
Csaba f9dab9d780 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 100.0% (1113 of 1113 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
2021-11-28 01:31:42 -06:00
Oskari Lavinto 18e0656d21 Translated using Weblate (Finnish) [skip ci]
Currently translated at 99.7% (1110 of 1113 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
2021-11-28 01:31:42 -06:00
Thomas Citharel 759d14cf99 Translated using Weblate (French) [skip ci]
Currently translated at 98.1% (1092 of 1113 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
2021-11-28 01:31:42 -06:00
reloxx 1238f60a5e Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (1113 of 1113 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
2021-11-28 01:31:42 -06:00
Weblate 2d28828e5e Translated using Weblate (Finnish) [skip ci]
Currently translated at 99.7% (1110 of 1113 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.7% (1110 of 1113 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 96.6% (1073 of 1110 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1110 of 1110 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1110 of 1110 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.8% (1108 of 1110 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bosci9 <rbos97@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2021-11-28 01:31:42 -06:00
Qstick 9a395b52ac New: Per download client setting to Remove Completed/Failed
Fixes #6322
Fixes #6328
Fixes #6331
Fixes #6337
2021-11-24 12:51:10 -06:00
Taloth Saldono e9dffb4819 Fixed SeedConfigProvider cache refresh after indexer settings change
(cherry picked from commit 8e46362ff9de34c3468290861f0606d8fecc1a44)
2021-11-24 12:51:10 -06:00
Qstick 8b93038937 Fixed: Properly handle provider delete in UI 2021-11-24 07:41:51 -06:00
Qstick c4cf38255e Fixed: Combine content headers with response headers 2021-11-23 19:21:59 -06:00
leaty 1c0621af0a New: Removing rtorrent downloads when seeding criteria have been met
(cherry picked from commit 411be4d0116f0739bb9c71235312d0c5a26dd3a2)

Fixes: #6320
Fixes: #5219
2021-11-23 15:06:44 -06:00
geogolem dd80a64560 Lidarr, Radarr, and Prowlarr (maybe Readarr too) all use
the same name for their Authentication cookies
.AspNetCore.Forms

Sonarr uses a different name "SonarrAuth"

this commit makes Radarr use "RadarrAuth"...

similar changes should likely be ported to Lidarr, Prowlarr (and possibly Readarr)
2021-11-23 14:10:52 -06:00
bakerboy448 beb22844c9 Update Github Templates [skip ci] [common] 2021-11-23 14:08:53 -06:00
ta264 399f242f87 Fixed: Restarting windows service from UI 2021-11-23 19:52:37 +00:00
ta264 6befbec381 Fixed: Tray app restart 2021-11-23 19:52:37 +00:00
ta264 8b8f79d6c3 New: Use native .NET socks proxy
(cherry picked from commit d93110336fea31565129b356c90043761f8c2c5b)
2021-11-23 19:52:37 +00:00
ta264 e54d4765dd Other package updates
(cherry picked from commit ffefdcaef9f77f76a969ac201abad30b45077ff7)
2021-11-23 19:52:37 +00:00
ta264 4068cfcabb New: Upgrade to .NET 6
(cherry picked from commit 242922f1c351312e0eaae68c4c0ff6c80c16b297)
2021-11-23 19:52:37 +00:00
ta264 c5b736e422 Use modern HttpClient 2021-11-23 19:52:37 +00:00
Roxedus 025634cd19 Complete reject reason for multi-part 2021-11-22 10:43:21 +00:00
Weblate 9c86c20c00 Translated using Weblate (Finnish) [skip ci]
Currently translated at 96.1% (1067 of 1110 strings)

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translation: Servarr/Radarr
2021-11-20 11:14:23 -06:00
Weblate 7bf44e2771 Merge 2021-11-20 16:46:20 +00:00
Mark McDowall b18daebc8a Rename QueryTitles to CleanSceneTitles in SearchCriteriaBase
(cherry picked from commit 747a4164e24e9861cacad39cf7d94db398747b38)
2021-11-19 17:46:01 -06:00
ta264 183d3d0872 Fixed: Error adding import list exclusions from discover
Fixes #6739
2021-11-18 21:55:25 +00:00
Weblate f1de24ccc8 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 99.8% (1108 of 1110 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 99.8% (1108 of 1110 strings)

Update translation files  [skip ci]

Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mm519897405 <baiya@vip.qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2021-11-18 01:59:33 -06:00
bakerboy448 498d9086b5 Fixed: Only blocklist pending releases when option is checked
(cherry picked from commit d11c691a7389d8984ddcbbd299d3192690719a1a)

Fixes #6730
Closes #6732
2021-11-17 16:32:48 +00:00
Robin Dadswell 2737937d37 Fixed: Blocklist error on blocklisting release 2021-11-17 12:15:05 +00:00
Weblate 3c9e818933 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 99.8% (1108 of 1110 strings)

Translated using Weblate (Norwegian Bokmål) [skip ci]

Currently translated at 13.2% (147 of 1110 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 96.3% (1070 of 1110 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 99.7% (1107 of 1110 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 93.8% (1042 of 1110 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 95.6% (1062 of 1110 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 95.9% (1065 of 1110 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 96.3% (1069 of 1110 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 97.3% (1081 of 1110 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 94.9% (1054 of 1110 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 96.0% (1066 of 1110 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 99.0% (1099 of 1110 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 22.7% (252 of 1110 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1110 of 1110 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1110 of 1110 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 95.9% (1065 of 1110 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 95.9% (1065 of 1110 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 95.9% (1065 of 1110 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 96.1% (1067 of 1110 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 96.5% (1072 of 1110 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 97.2% (1080 of 1110 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 97.2% (1080 of 1110 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.1% (1089 of 1110 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1110 of 1110 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1110 of 1110 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 99.8% (1108 of 1110 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 99.8% (1108 of 1110 strings)

Update translation files  [skip ci]

Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Panagiotis Christoforakis <panagiotischristoforakis@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dmunozv04 <dmunozv04@gmail.com>
Co-authored-by: mm519897405 <baiya@vip.qq.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: shenghuoshou <1015886870@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2021-11-17 08:45:43 +00:00
bakerboy448 a8b563de7b Fixed: Cleanse APIKey from Signalr logging
(cherry picked from commit 2983b600268176db56208d6fb014b97ad81bb576)
2021-11-15 09:34:06 -06:00
Mark McDowall 823fe2261e Rename NzbSearchService to ReleaseSearchService
(cherry picked from commit eb4a9f624e716fa932da7f7e5ff975e2d9f02655)
2021-11-14 17:55:28 +00:00
Qstick 329d141128 Translation sync 2021-11-14 11:06:58 -06:00
373 changed files with 4744 additions and 3859 deletions
+2 -4
View File
@@ -1,5 +1,4 @@
name: Bug Report name: Bug Report
title: "[BUG]: "
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first' description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
labels: ['Type: Bug', 'Status: Needs Triage'] labels: ['Type: Bug', 'Status: Needs Triage']
body: body:
@@ -64,12 +63,11 @@ body:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Anything else? label: Trace Logs?
description: | description: |
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files) Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
Links? References? Anything that will give us more context about the issue you are encountering!
***Generally speaking, all bug reports must have trace logs provided.*** ***Generally speaking, all bug reports must have trace logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations: validations:
required: true required: true
@@ -1,5 +1,4 @@
name: Feature Request name: Feature Request
title: "[FEAT]: "
description: 'Suggest an idea for Radarr' description: 'Suggest an idea for Radarr'
labels: ['Type: Feature Request', 'Status: Needs Triage'] labels: ['Type: Feature Request', 'Status: Needs Triage']
body: body:
+26 -17
View File
@@ -1,19 +1,21 @@
# Radarr # Radarr
[![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop) [![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/radarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget) [![Translated](https://translate.servarr.com/widgets/servarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker)
![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg) ![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers) [![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors) [![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors)
[![Mega Sponsors on Open Collective](https://opencollective.com/Radarr/megasponsors/badge.svg)](#mega-sponsors)
Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available. Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available.
Note that only one type of a given movie is supported. If you want both an 4k version and 1080p version of a given movie you will need multiple instances.
## Major Features Include: ## Major Features Include
* Adding new movies with lots of information, such as trailers, ratings, etc. * Adding new movies with lots of information, such as trailers, ratings, etc.
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc. * Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
* Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray* * Can watch for better quality of the movies you have and do an automatic upgrade. *e.g. from DVD to Blu-Ray*
* Automatic failed download handling will try another release if one fails * Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically * Manual search so you can pick any release or to see why a release was not downloaded automatically
* Full integration with SABnzbd and NZBGet * Full integration with SABnzbd and NZBGet
@@ -21,53 +23,60 @@ Radarr is a movie collection manager for Usenet and BitTorrent users. It can mon
* Automatically importing downloaded movies * Automatically importing downloaded movies
* Recognizing Special Editions, Director's Cut, etc. * Recognizing Special Editions, Director's Cut, etc.
* Identifying releases with hardcoded subs * Identifying releases with hardcoded subs
* QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported * Identifying releases with AKA movie names
* Full integration with Kodi, Plex (notification, library update) * SABnzbd, NZBGet, QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported and integrated
* A beautiful UI * Full integration with Kodi and Plex (notifications, library updates)
* Importing Metadata such as trailers or subtitles * Importing Metadata such as trailers or subtitles
* Adding metadata such as posters and information for Kodi and others to use * Adding metadata such as posters and information for Kodi and others to use
* Advanced customization for profiles, such that Radarr will always download the copy you want * Advanced customization for profiles, such that Radarr will always download the copy you want
* A beautiful UI
## Support ## Support
Note: GitHub Issues are for Bugs and Feature Requests Only
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord) [![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr) [![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
Note: GitHub Issues are for Bugs and Feature Requests Only
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues) [![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
## Contributors & Developers ## Contributors & Developers
[API Documentation](https://radarr.video/docs/api/) [API Documentation](https://radarr.video/docs/api/)
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md). This project exists thanks to all the people who contribute.
<a href="https://github.com/Radarr/Radarr/graphs/contributors"><img src="https://opencollective.com/Radarr/contributors.svg?width=890&button=false" /></a> - [Contribute (GitHub)](CONTRIBUTING.md)
- [Contribution (Wiki Article)](https://wiki.servarr.com/radarr/contributing)
[![Contributors List](https://opencollective.com/Radarr/contributors.svg?width=890&button=false)](https://github.com/Radarr/Radarr/graphs/contributors)
## Backers ## Backers
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer) Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer)
<img src="https://opencollective.com/Radarr/backers.svg?width=890"></a> [![Backers List](https://opencollective.com/Radarr/backers.svg?width=890)](https://opencollective.com/Radarr#backer)
## Sponsors ## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor) Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor)
<img src="https://opencollective.com/Radarr/sponsors.svg?width=890"></a> [![Sponsors List](https://opencollective.com/Radarr/sponsors.svg?width=890)](https://opencollective.com/Radarr#sponsor)
## Mega Sponsors ## Mega Sponsors
<img src="https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890"></a> [![Mega Sponsors List](https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890)](https://opencollective.com/Radarr#mega-sponsor)
## JetBrains ## JetBrains
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools. Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
* [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/) * [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/) * [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/) * [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/) * [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
### License ### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) * [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2010-2021 * Copyright 2010-2022
+47 -24
View File
@@ -13,7 +13,7 @@ variables:
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '5.0.401' dotnetVersion: '6.0.100'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger: trigger:
@@ -111,23 +111,23 @@ stages:
artifact: '$(osName)Backend' artifact: '$(osName)Backend'
displayName: Publish Backend displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/win-x64/publish' - publish: '$(testsFolder)/net6.0/win-x64/publish'
artifact: WindowsCoreTests artifact: WindowsCoreTests
displayName: Publish Windows Test Package displayName: Publish Windows Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/linux-x64/publish' - publish: '$(testsFolder)/net6.0/linux-x64/publish'
artifact: LinuxCoreTests artifact: LinuxCoreTests
displayName: Publish Linux Test Package displayName: Publish Linux Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/linux-musl-x64/publish' - publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
artifact: LinuxMuslCoreTests artifact: LinuxMuslCoreTests
displayName: Publish Linux Musl Test Package displayName: Publish Linux Musl Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/freebsd-x64/publish' - publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
artifact: FreebsdCoreTests artifact: FreebsdCoreTests
displayName: Publish FreeBSD Test Package displayName: Publish FreeBSD Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/osx-x64/publish' - publish: '$(testsFolder)/net6.0/osx-x64/publish'
artifact: MacCoreTests artifact: MacCoreTests
displayName: Publish MacOS Test Package displayName: Publish MacOS Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@@ -203,12 +203,12 @@ stages:
- bash: ./build.sh --packages - bash: ./build.sh --packages
displayName: Create Packages displayName: Create Packages
- bash: | - bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x86 setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x86
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create .NET Core Windows installer displayName: Create .NET Core Windows installer
- bash: | - bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x64 setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x64
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create .NET Core Windows installer displayName: Create .NET Core Windows installer
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller' artifact: 'WindowsInstaller'
@@ -251,29 +251,44 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0 rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create Windows x86 Core zip displayName: Create Windows x86 Core zip
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0 rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create MacOS Core app displayName: Create MacOS x64 Core app
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0 rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create MacOS Core tar displayName: Create MacOS x64 Core tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/macos/net5.0 rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
- task: ArchiveFiles@2
displayName: Create MacOS arm64 Core app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
- task: ArchiveFiles@2
displayName: Create MacOS arm64 Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create Linux Core tar displayName: Create Linux Core tar
inputs: inputs:
@@ -281,7 +296,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0 rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create Linux Musl Core tar displayName: Create Linux Musl Core tar
inputs: inputs:
@@ -289,7 +304,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create ARM32 Linux Core tar displayName: Create ARM32 Linux Core tar
inputs: inputs:
@@ -297,7 +312,15 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0 rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
- task: ArchiveFiles@2
displayName: Create ARM32 Linux Musl Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create ARM64 Linux Core tar displayName: Create ARM64 Linux Core tar
inputs: inputs:
@@ -305,7 +328,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0 rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create ARM64 Linux Musl Core tar displayName: Create ARM64 Linux Musl Core tar
inputs: inputs:
@@ -313,7 +336,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create FreeBSD Core Core tar displayName: Create FreeBSD Core Core tar
inputs: inputs:
@@ -321,7 +344,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0 rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages' artifact: 'Packages'
displayName: Publish Packages displayName: Publish Packages
@@ -866,8 +889,8 @@ stages:
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: | - bash: |
./build.sh --backend -f net5.0 -r win-x64 ./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net5.0/win-x64/publish/ ./test.sh Windows Unit Coverage TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@1
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
+29 -25
View File
@@ -129,7 +129,7 @@ PackageLinux()
echo "Adding Radarr.Mono to UpdatePackage" echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net5.0" ]; then if [ "$framework" = "net6.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi fi
@@ -140,12 +140,13 @@ PackageLinux()
PackageMacOS() PackageMacOS()
{ {
local framework="$1" local framework="$1"
local runtime="$2"
ProgressStart "Creating MacOS Package for $framework" ProgressStart "Creating MacOS Package for $framework $runtime"
local folder=$artifactsFolder/macos/$framework/Radarr local folder=$artifactsFolder/$runtime/$framework/Radarr
PackageFiles "$folder" "$framework" "osx-x64" PackageFiles "$folder" "$framework" "$runtime"
echo "Removing Service helpers" echo "Removing Service helpers"
rm -f $folder/ServiceUninstall.* rm -f $folder/ServiceUninstall.*
@@ -156,7 +157,7 @@ PackageMacOS()
echo "Adding Radarr.Mono to UpdatePackage" echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net5.0" ]; then if [ "$framework" = "net6.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi fi
@@ -167,10 +168,11 @@ PackageMacOS()
PackageMacOSApp() PackageMacOSApp()
{ {
local framework="$1" local framework="$1"
local runtime="$2"
ProgressStart "Creating macOS App Package for $framework" ProgressStart "Creating macOS App Package for $framework $runtime"
local folder=$artifactsFolder/macos-app/$framework local folder="$artifactsFolder/$runtime-app/$framework"
rm -rf $folder rm -rf $folder
mkdir -p $folder mkdir -p $folder
@@ -178,7 +180,7 @@ PackageMacOSApp()
mkdir -p $folder/Radarr.app/Contents/MacOS mkdir -p $folder/Radarr.app/Contents/MacOS
echo "Copying Binaries" echo "Copying Binaries"
cp -r $artifactsFolder/macos/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS cp -r $artifactsFolder/$runtime/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
echo "Removing Update Folder" echo "Removing Update Folder"
rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update
@@ -225,8 +227,8 @@ Package()
PackageWindows "$framework" "$runtime" PackageWindows "$framework" "$runtime"
;; ;;
osx) osx)
PackageMacOS "$framework" PackageMacOS "$framework" "$runtime"
PackageMacOSApp "$framework" PackageMacOSApp "$framework" "$runtime"
;; ;;
esac esac
} }
@@ -326,14 +328,14 @@ then
Build Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
PackageTests "net5.0" "win-x64" PackageTests "net6.0" "win-x64"
PackageTests "net5.0" "win-x86" PackageTests "net6.0" "win-x86"
PackageTests "net5.0" "linux-x64" PackageTests "net6.0" "linux-x64"
PackageTests "net5.0" "linux-musl-x64" PackageTests "net6.0" "linux-musl-x64"
PackageTests "net5.0" "osx-x64" PackageTests "net6.0" "osx-x64"
if [ "$ENABLE_BSD" = "YES" ]; if [ "$ENABLE_BSD" = "YES" ];
then then
PackageTests "net5.0" "freebsd-x64" PackageTests "net6.0" "freebsd-x64"
fi fi
else else
PackageTests "$FRAMEWORK" "$RID" PackageTests "$FRAMEWORK" "$RID"
@@ -362,17 +364,19 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
Package "net5.0" "win-x64" Package "net6.0" "win-x64"
Package "net5.0" "win-x86" Package "net6.0" "win-x86"
Package "net5.0" "linux-x64" Package "net6.0" "linux-x64"
Package "net5.0" "linux-musl-x64" Package "net6.0" "linux-musl-x64"
Package "net5.0" "linux-arm64" Package "net6.0" "linux-arm64"
Package "net5.0" "linux-musl-arm64" Package "net6.0" "linux-musl-arm64"
Package "net5.0" "linux-arm" Package "net6.0" "linux-arm"
Package "net5.0" "osx-x64" Package "net6.0" "linux-musl-arm"
Package "net6.0" "osx-x64"
Package "net6.0" "osx-arm64"
if [ "$ENABLE_BSD" = "YES" ]; if [ "$ENABLE_BSD" = "YES" ];
then then
Package "net5.0" "freebsd-x64" Package "net6.0" "freebsd-x64"
fi fi
else else
Package "$FRAMEWORK" "$RID" Package "$FRAMEWORK" "$RID"
-1
View File
@@ -4,7 +4,6 @@
], ],
"ignoreFiles": [ "ignoreFiles": [
"frontend/src/Styles/scaffolding.css", "frontend/src/Styles/scaffolding.css",
"**/Theme.Park/**/*.css",
"**/*.js" "**/*.js"
], ],
"rules": { "rules": {
-27
View File
@@ -124,20 +124,6 @@ module.exports = (env) => {
{ {
source: 'frontend/src/Content/robots.txt', source: 'frontend/src/Content/robots.txt',
destination: path.join(distFolder, 'Content/robots.txt') destination: path.join(distFolder, 'Content/robots.txt')
},
// Theme.Park
{
source: 'frontend/src/Content/Theme.Park/*',
destination: path.join(distFolder, 'Content/Theme.Park')
},
{
source: 'frontend/src/Content/Theme.Park/Themes/*',
destination: path.join(distFolder, 'Content/Theme.Park/Themes')
},
{
source: 'frontend/src/Content/Theme.Park/Resources/*',
destination: path.join(distFolder, 'Content/Theme.Park/Resources')
} }
] ]
} }
@@ -257,19 +243,6 @@ module.exports = (env) => {
} }
} }
] ]
},
{
test: /\.(png)?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Theme.Park/Resources/[name].[ext]'
}
}
]
} }
] ]
} }
+1 -1
View File
@@ -120,7 +120,7 @@ class Blocklist extends Component {
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label="Remove Selected" label={translate('RemoveSelected')}
iconName={icons.REMOVE} iconName={icons.REMOVE}
isDisabled={!selectedIds.length} isDisabled={!selectedIds.length}
isSpinning={isRemoving} isSpinning={isRemoving}
@@ -161,7 +161,7 @@ class AddNewMovie extends Component {
{translate('YouCanAlsoSearch')} {translate('YouCanAlsoSearch')}
</div> </div>
<div> <div>
<Link to="https://wiki.servarr.com/radarr/faq#why-cant-i-add-a-new-movie-to-radarr"> <Link to="https://wiki.servarr.com/radarr/faq#why-can-i-not-add-a-new-movie-to-radarr">
{translate('CantFindMovie')} {translate('CantFindMovie')}
</Link> </Link>
</div> </div>
-29
View File
@@ -1,29 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
const theme = window.Radarr.theme;
function ThemeSelector({ children }) {
return (
<>
{
theme !== 'default' &&
<>
<link rel="stylesheet" type="text/css"
href={'/Content/Theme.Park/radarr-base.css'}
/>
<link rel="stylesheet" type="text/css"
href={`/Content/Theme.Park/Themes/${theme}.css`}
/>
</>
}
{children}
</>
);
}
ThemeSelector.propTypes = {
children: PropTypes.object.isRequired
};
export default ThemeSelector;
@@ -43,15 +43,15 @@ class CalendarEvent extends Component {
const link = `/movie/${titleSlug}`; const link = `/movie/${titleSlug}`;
const eventType = []; const eventType = [];
if (moment(date).isSame(moment(inCinemas), 'day')) { if (inCinemas && moment(date).isSame(moment(inCinemas), 'day')) {
eventType.push('Cinemas'); eventType.push('Cinemas');
} }
if (moment(date).isSame(moment(physicalRelease), 'day')) { if (physicalRelease && moment(date).isSame(moment(physicalRelease), 'day')) {
eventType.push('Physical'); eventType.push('Physical');
} }
if (moment(date).isSame(moment(digitalRelease), 'day')) { if (digitalRelease && moment(date).isSame(moment(digitalRelease), 'day')) {
eventType.push('Digital'); eventType.push('Digital');
} }
+5
View File
@@ -16,4 +16,9 @@
color: #3a3f51; color: #3a3f51;
font-size: 21px; font-size: 21px;
line-height: inherit; line-height: inherit;
&.small {
color: #909293;
font-size: 18px;
}
} }
+9 -1
View File
@@ -1,5 +1,7 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { sizes } from 'Helpers/Props';
import styles from './FieldSet.css'; import styles from './FieldSet.css';
class FieldSet extends Component { class FieldSet extends Component {
@@ -9,13 +11,14 @@ class FieldSet extends Component {
render() { render() {
const { const {
size,
legend, legend,
children children
} = this.props; } = this.props;
return ( return (
<fieldset className={styles.fieldSet}> <fieldset className={styles.fieldSet}>
<legend className={styles.legend}> <legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
{legend} {legend}
</legend> </legend>
{children} {children}
@@ -26,8 +29,13 @@ class FieldSet extends Component {
} }
FieldSet.propTypes = { FieldSet.propTypes = {
size: PropTypes.oneOf(sizes.all).isRequired,
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
children: PropTypes.node children: PropTypes.node
}; };
FieldSet.defaultProps = {
size: sizes.MEDIUM
};
export default FieldSet; export default FieldSet;
@@ -166,7 +166,9 @@ class FilterBuilderModalContent extends Component {
</div> </div>
</div> </div>
<div className={styles.label}>Filters</div> <div className={styles.label}>
{translate('Filters')}
</div>
<div className={styles.rows}> <div className={styles.rows}>
{ {
@@ -6,8 +6,7 @@ import SelectInput from './SelectInput';
const availabilityOptions = [ const availabilityOptions = [
{ key: 'announced', value: translate('Announced') }, { key: 'announced', value: translate('Announced') },
{ key: 'inCinemas', value: translate('InCinemas') }, { key: 'inCinemas', value: translate('InCinemas') },
{ key: 'released', value: translate('Released') }, { key: 'released', value: translate('Released') }
{ key: 'preDB', value: translate('PreDB') }
]; ];
function AvailabilitySelectInput(props) { function AvailabilitySelectInput(props) {
@@ -0,0 +1,100 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.downloadClients,
(state, { includeAny }) => includeAny,
(state, { protocol }) => protocol,
(downloadClients, includeAny, protocolFilter) => {
const {
isFetching,
isPopulated,
error,
items
} = downloadClients;
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return {
key: downloadClient.id,
value: downloadClient.name
};
});
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
});
}
return {
isFetching,
isPopulated,
error,
values
};
}
);
}
const mapDispatchToProps = {
dispatchFetchDownloadClients: fetchDownloadClients
};
class DownloadClientSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchDownloadClients();
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
DownloadClientSelectInputConnector.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeAny: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchDownloadClients: PropTypes.func.isRequired
};
DownloadClientSelectInputConnector.defaultProps = {
includeAny: false,
protocol: 'torrent'
};
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);
@@ -8,6 +8,7 @@ import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector'; import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput'; import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector'; import DeviceInputConnector from './DeviceInputConnector';
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector'; import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText'; import FormInputHelpText from './FormInputHelpText';
@@ -73,6 +74,9 @@ function getComponent(type) {
case inputTypes.INDEXER_FLAGS_SELECT: case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector; return IndexerFlagsSelectInputConnector;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
case inputTypes.LANGUAGE_SELECT: case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInputConnector; return LanguageSelectInputConnector;
@@ -113,8 +117,6 @@ function FormInputGroup(props) {
helpTexts, helpTexts,
helpTextWarning, helpTextWarning,
helpLink, helpLink,
inlineLink,
tooltip,
pending, pending,
errors, errors,
warnings, warnings,
@@ -184,9 +186,6 @@ function FormInputGroup(props) {
!checkInput && helpText && !checkInput && helpText &&
<FormInputHelpText <FormInputHelpText
text={helpText} text={helpText}
link={inlineLink}
tooltip={tooltip}
isWarning={!!inlineLink}
/> />
} }
@@ -268,8 +267,6 @@ FormInputGroup.propTypes = {
helpTexts: PropTypes.arrayOf(PropTypes.string), helpTexts: PropTypes.arrayOf(PropTypes.string),
helpTextWarning: PropTypes.string, helpTextWarning: PropTypes.string,
helpLink: PropTypes.string, helpLink: PropTypes.string,
inlineLink: PropTypes.string,
tooltip: PropTypes.string,
pending: PropTypes.bool, pending: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.object), errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object) warnings: PropTypes.arrayOf(PropTypes.object)
@@ -64,6 +64,7 @@ function ProviderFieldFormGroup(props) {
label, label,
helpText, helpText,
helpLink, helpLink,
placeholder,
value, value,
type, type,
advanced, advanced,
@@ -96,6 +97,7 @@ function ProviderFieldFormGroup(props) {
label={label} label={label}
helpText={helpText} helpText={helpText}
helpLink={helpLink} helpLink={helpLink}
placeholder={placeholder}
value={value} value={value}
values={getSelectValues(selectOptions)} values={getSelectValues(selectOptions)}
errors={errors} errors={errors}
@@ -121,6 +123,7 @@ ProviderFieldFormGroup.propTypes = {
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
helpText: PropTypes.string, helpText: PropTypes.string,
helpLink: PropTypes.string, helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any, value: PropTypes.any,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired, advanced: PropTypes.bool.isRequired,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

@@ -1,30 +0,0 @@
:root {
--main-bg-color: radial-gradient(ellipse at center, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
--modal-bg-color: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
--modal-header-color: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
--modal-footer-color: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
--drop-down-menu-bg: radial-gradient(ellipse at top, #47918a 0%, #0b3161 100%) center center/cover no-repeat fixed;
--button-color: #009688;
--button-color-hover: #12afa0;
--button-text: #eee;
--button-text-hover: #FFF;
--accent-color: 18, 175, 160;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color: #0ed2bf;
--link-color-hover: #36e7d6;
--label-text-color: #fff;
--text:#ddd;
--text-hover: #fff;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #009688; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: rgb(21, 213, 194);
--petio-spinner: invert(39%) sepia(98%) saturate(527%) hue-rotate(129deg) brightness(94%) contrast(101%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 18, 175, 160;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: radial-gradient(circle, #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
--modal-bg-color: radial-gradient(circle , #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
--modal-header-color: radial-gradient(circle , #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
--modal-footer-color: radial-gradient(circle , #3a3a3a, #2d2d2d, #202020, #141414, #000000) center center/cover no-repeat fixed;
--drop-down-menu-bg: #2d2d2d;
--button-color: #7a7a7a;
--button-color-hover: #9b9b9b;
--button-text: #eee;
--button-text-hover: #FFF;
--accent-color: 170, 170, 170;
--accent-color-hover: rgba(255, 255, 255, 0.45);
--link-color: #7a7a7a;
--link-color-hover: #fff;
--label-text-color: black;
--text:#ddd;
--text-hover: #fff;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #6b5; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #e5a00d;
--petio-spinner: invert(35%) sepia(12%) saturate(4%) hue-rotate(2deg) brightness(104%) contrast(86%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 255, 255, 255;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: #282a36;
--modal-bg-color: #1e2029;
--modal-header-color: #1e2029;
--modal-footer-color: #1e2029;
--drop-down-menu-bg: #1e2029;
--button-color: #bd93f9;
--button-color-hover: #ff79c6;
--button-text: #eee;
--button-text-hover: #FFF;
--accent-color: 80, 250, 123;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color: #ff79c6;
--link-color-hover: #8be9fd;
--label-text-color: #282a36;
--text:#6272a4;
--text-hover: #95adfa;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #50fa7b; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #bd93f9;
--petio-spinner: invert(79%) sepia(27%) saturate(1033%) hue-rotate(74deg) brightness(104%) contrast(96%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 80, 250, 123;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
--modal-bg-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
--modal-header-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
--modal-footer-color: linear-gradient(0deg, rgba(247,101,184,1) 0%, rgb(21, 95, 165) 100%) center center/cover no-repeat fixed;
--drop-down-menu-bg: linear-gradient(90deg, rgba(247,101,184,1) 0%, rgba(21, 95, 165) 100%) center center/cover no-repeat fixed;
--button-color: #f98dc9;
--button-color-hover: #ff4cb1;
--button-text: #eee;
--button-text-hover: #fff;
--accent-color: 249, 141, 201;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color:rgb(255, 179, 222);
--link-color-hover: #d7fffe;
--label-text-color: #fff;
--text:#ddd;
--text-hover: #fff;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #f98dc9; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #f765b8;
--petio-spinner: invert(78%) sepia(17%) saturate(4447%) hue-rotate(290deg) brightness(109%) contrast(95%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 215,255,254;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: linear-gradient(45deg, #fb3f62 0%, #204c80 37%, #004249 97%) center center/cover no-repeat fixed;
--modal-bg-color: radial-gradient(circle, #204c80 0%, #000 100%) center center/cover no-repeat fixed;
--modal-header-color: radial-gradient(circle, #204c80 0%, #000 100%) center center/cover no-repeat fixed;
--modal-footer-color: radial-gradient(circle, #204c80 0%, #000 100%) center center/cover no-repeat fixed;
--drop-down-menu-bg: #204c80;
--button-color: #fb3f62;
--button-color-hover: #cd4164;
--button-text: #eee;
--button-text-hover: #FFF;
--accent-color: 251, 63, 98;
--accent-color-hover: rgba(var(--accent-color), .8);
--link-color: rgb(0, 255, 157);
--link-color-hover: rgba(0, 255, 157, 0.8);
--label-text-color: #282a36;
--text:#eee;
--text-hover: #fff;
--text-muted: #999;
--arr-queue-color: rgb(0, 255, 157);
--plex-poster-unwatched: #fb3f62;
--petio-spinner: invert(29%) sepia(87%) saturate(2199%) hue-rotate(331deg) brightness(115%) contrast(97%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 251, 63, 98;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: #2E3440;
--modal-bg-color: #3B4252;
--modal-header-color: #434C5E;
--modal-footer-color: #434C5E;
--drop-down-menu-bg: #3B4252;
--button-color: #79b8ca;
--button-color-hover: #6a9daf;
--button-text: #2E3440;
--button-text-hover: #D8DEE9;
--accent-color: 121, 184, 202;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color: #81A1C1;
--link-color-hover: #88C0D0;
--label-text-color: #222730;
--text:#D8DEE9;
--text-hover: #ECEFF4;
--text-muted: #81A1C1;
/*Specials*/
--arr-queue-color: #A3BE8C; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #D08770;
--petio-spinner: invert(83%) sepia(9%) saturate(1787%) hue-rotate(156deg) brightness(85%) contrast(83%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 121, 184, 202;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: #1f1f1f;
--modal-bg-color: #333;
--modal-header-color: #232323;
--modal-footer-color: #232323;
--drop-down-menu-bg: #1b1b1b;
--button-color: #2cabe3;
--button-color-hover: #298fbc;
--button-text: #eee;
--button-text-hover: #fff;
--accent-color: 44, 171, 227;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color: #2cabe3;
--link-color-hover: #3cc5ff;
--label-text-color: #fff;
--text:#96a2b4;
--text-hover: #fff;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #2cabe3; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #2cabe3;
--petio-spinner: invert(65%) sepia(83%) saturate(2026%) hue-rotate(167deg) brightness(90%) contrast(97%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 44, 171, 227;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: linear-gradient(360deg, hsl(221, 39%, 11%) 65%, hsl(215, 28%, 17%) 100%);
--modal-bg-color: #1f2937;
--modal-header-color: #1f2937;
--modal-footer-color: #1f2937;
--drop-down-menu-bg: #374151;
--button-color: #4f46e5;
--button-color-hover: #6366f1;
--button-text: #e5e7eb;
--button-text-hover: #fff;
--accent-color: 167, 139, 250;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color: #6366f1;
--link-color-hover: #a78bfa;
--label-text-color: #000;
--text: #d1d5db;
--text-hover: #fff;
--text-muted: #9ca3af;
/*Specials*/
--arr-queue-color: #6366f1; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #6366f1;
--petio-spinner: invert(24%) sepia(59%) saturate(3411%) hue-rotate(237deg) brightness(91%) contrast(96%); /* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 98, 116, 145;
}
@@ -1,28 +0,0 @@
:root {
--main-bg-color: url("Resources/blur-noise.png") repeat scroll 0% 0%, radial-gradient(circle at 0% 100%, rgba(54, 66, 84, 0.55) 0%, rgba(54, 66, 84, 0.043) 70%, rgba(54, 66, 84, 0) 80%), radial-gradient(circle at 100% 100%, rgba(113, 135, 153, 0.55) 0%, rgba(113, 135, 153, 0.043) 70%, rgba(113, 135, 153, 0) 80%), radial-gradient(circle at 100% 0%, rgba(54, 66, 84, 0.55) 0%, rgba(54, 66, 84, 0.043) 70%, rgba(54, 66, 84, 0) 80%), radial-gradient(circle at 0% 0%, rgba(91, 114, 135, 0.55) 0%, rgba(91, 114, 135, 0.043) 70%, rgba(91, 114, 135, 0) 80%), rgb(0, 0, 0) center center/cover no-repeat fixed;
--modal-bg-color: #1f2326;
--modal-header-color: #1f2326;
--modal-footer-color: #323232;
--drop-down-menu-bg: #191a1c;
--button-color: #cc7b19;
--button-color-hover: #e59029;
--button-text: #eee;
--button-text-hover: #fff;
--accent-color: 229, 160, 13;
--accent-color-hover: #ffc107;
--link-color: #e5a00d;
--link-color-hover: #fff;
--label-text-color: #fff;
--text:#ddd;
--text-hover: #fff;
--text-muted: #999;
--arr-queue-color: #27c24c;
--petio-spinner: invert(0%) sepia(0%) saturate(100%) hue-rotate(0deg) brightness(100%) contrast(100%);
--gitea-color-primary-dark-4: 255, 193, 7;
}
@@ -1,124 +0,0 @@
:root {
--main-bg-color: #454545;
--modal-bg-color: #595959;
--modal-header-color: #595959;
--modal-footer-color: #595959;
--drop-down-menu-bg: #606060;
--button-color: #5899eb;
--button-color-hover: #4b91ea;
--button-text: #eee;
--button-text-hover: #fff;
--accent-color: 255, 194, 48;
--accent-color-hover: rgb(255, 194, 48, .8);
--link-color: rgb(255, 194, 48);
--link-color-hover: rgb(255, 194, 48, .8);
--label-text-color: #eee;
--text: #ccc;
--text-hover: #fff;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #5d9cec;
--side-menu-active: #333333;
--scroller-hover: #606060;
--scroller: #707070;
--border-color: #606060;
--label-color: #707070;
--label-info: #5d9cec;
--header-color: #464b51;
--side-menu-color: #595959;
}
/* HEADER */
[class*="PageHeader-header-"] {
background-color: var(--header-color);
color: #fff;
}
[class*="PageToolbar-toolbar-"] {
background-color: #707070;
color: var(--text);
}
/* SIDE MENU */
[class*="PageSidebar-sidebar-"] {
background-color: var(--side-menu-color);
color: #fff;
}
[class*=PageSidebarItem-link-]:focus {
color: rgb(var(--accent-color)) !important;
}
[class*=PageSidebarItem-isActiveLink-] {
color: var(--link-color) !important;
}
[class*=PageSidebarItem-isActiveParentLink-] {
background-color: var(--side-menu-active);
}
/* SCROLLER */
[class*=ImportSeriesSelectSeries-results-]::-webkit-scrollbar-thumb:hover,
[class*=OverlayScroller-thumb-]:hover {
background-color: var(--scroller-hover) !important;
}
[class*="OverlayScroller-thumb-"],
[class*=Scroller-scroller-]::-webkit-scrollbar-thumb {
background-color: var(--scroller) !important;
}
/* MODALS */
[class*=ModalHeader-modalHeader-],
[class*=FieldSet-legend-] {
border-bottom: 1px solid var(--border-color);
}
[class*=ModalFooter-modalFooter-] {
border-top: 1px solid var(--border-color);
}
/* LABLES */
[class*="Label-default-"] {
border-color: var(--label-color);
background-color: var(--label-color);
color: white;
}
[class*="Label-info-"]:not([class*="PageSidebarItem-status-"] [class*="Label-info-"]) {
border-color: var(--label-info);
background-color: var(--label-info);
color: #fff;
}
/* SETTINGS */
[class*=Settings-link-] {
border-bottom: 1px solid var(--border-color);
}
/* SEARCH DROP DOWN */
[class*="MovieSearchInput-containerOpen-"] [class*="MovieSearchInput-movieContainer-"] {
border: 1px solid var(--drop-down-menu-bg);
background-color: var(--drop-down-menu-bg);
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
color: #e1e2e3;
}
/* SERIES PAGE */
[class*="MovieIndexPoster-controls-"] {
background-color: var(--label-color) !important;
color: #fff !important;
}
@@ -1,30 +0,0 @@
:root {
--main-bg-color: radial-gradient(ellipse at center, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
--modal-bg-color: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
--modal-header-color: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
--modal-footer-color: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
--drop-down-menu-bg: radial-gradient(ellipse at top, rgba(87, 108, 117, 1) 0%, rgba(37, 50, 55, 1) 100.2%) center center/cover no-repeat fixed;
--button-color: #607D8B;
--button-color-hover: #81a6b7;
--button-text: #eee;
--button-text-hover: #fff;
--accent-color: 129, 166, 183;
--accent-color-hover: rgb(var(--accent-color),.8);
--link-color: #81a6b7;
--link-color-hover: #9adfff;
--label-text-color: #fff;
--text:#bbb;
--text-hover: #fff;
--text-muted: #999;
/*Specials*/
--arr-queue-color: #81a6b7; /* Servarr apps + Bazarr*/
--plex-poster-unwatched: #70aeca;
--petio-spinner: invert(50%) sepia(31%) saturate(341%) hue-rotate(155deg) brightness(88%) contrast(85%);/* Made with https://codepen.io/jsm91/embed/ZEEawyZ */
--gitea-color-primary-dark-4: 129, 166, 183;
}
File diff suppressed because it is too large Load Diff
+2
View File
@@ -13,6 +13,7 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect'; export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect'; export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect'; export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const SELECT = 'select'; export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect'; export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag'; export const TAG = 'tag';
@@ -35,6 +36,7 @@ export const all = [
PASSWORD, PASSWORD,
PATH, PATH,
QUALITY_PROFILE_SELECT, QUALITY_PROFILE_SELECT,
DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT, ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT, INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT, LANGUAGE_SELECT,
@@ -19,6 +19,7 @@ import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal'; import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds'; import getSelectedIds from 'Utilities/Table/getSelectedIds';
@@ -40,6 +41,11 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{ {
name: 'quality', name: 'quality',
label: translate('Quality'), label: translate('Quality'),
@@ -83,6 +89,7 @@ const SELECT = 'select';
const MOVIE = 'movie'; const MOVIE = 'movie';
const LANGUAGE = 'language'; const LANGUAGE = 'language';
const QUALITY = 'quality'; const QUALITY = 'quality';
const RELEASE_GROUP = 'releaseGroup';
class InteractiveImportModalContent extends Component { class InteractiveImportModalContent extends Component {
@@ -202,10 +209,11 @@ class InteractiveImportModalContent extends Component {
const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems')); const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems'));
const bulkSelectOptions = [ const bulkSelectOptions = [
{ { key: SELECT, value: translate('SelectDotDot'), disabled: true },
key: SELECT, value: translate('SelectDotDot'), disabled: true },
{ key: LANGUAGE, value: translate('SelectLanguage') }, { key: LANGUAGE, value: translate('SelectLanguage') },
{ key: QUALITY, value: translate('SelectQuality') }]; { key: QUALITY, value: translate('SelectQuality') },
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
];
if (allowMovieChange) { if (allowMovieChange) {
bulkSelectOptions.splice(1, 0, { bulkSelectOptions.splice(1, 0, {
@@ -372,6 +380,13 @@ class InteractiveImportModalContent extends Component {
real={false} real={false}
onModalClose={this.onSelectModalClose} onModalClose={this.onSelectModalClose}
/> />
<SelectReleaseGroupModal
isOpen={selectModalOpen === RELEASE_GROUP}
ids={selectedIds}
releaseGroup=""
onModalClose={this.onSelectModalClose}
/>
</ModalContent> </ModalContent>
); );
} }
@@ -110,7 +110,8 @@ class InteractiveImportModalContentConnector extends Component {
const { const {
movie, movie,
quality, quality,
languages languages,
releaseGroup
} = item; } = item;
if (!movie) { if (!movie) {
@@ -132,6 +133,7 @@ class InteractiveImportModalContentConnector extends Component {
path: item.path, path: item.path,
folderName: item.folderName, folderName: item.folderName,
movieId: movie.id, movieId: movie.id,
releaseGroup,
quality, quality,
languages, languages,
downloadId: this.props.downloadId downloadId: this.props.downloadId
@@ -11,6 +11,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal'; import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
@@ -28,6 +29,7 @@ class InteractiveImportRow extends Component {
this.state = { this.state = {
isSelectMovieModalOpen: false, isSelectMovieModalOpen: false,
isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false, isSelectQualityModalOpen: false,
isSelectLanguageModalOpen: false isSelectLanguageModalOpen: false
}; };
@@ -103,6 +105,10 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectMovieModalOpen: true }); this.setState({ isSelectMovieModalOpen: true });
} }
onSelectReleaseGroupPress = () => {
this.setState({ isSelectReleaseGroupModalOpen: true });
}
onSelectQualityPress = () => { onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true }); this.setState({ isSelectQualityModalOpen: true });
} }
@@ -116,6 +122,11 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
} }
onSelectReleaseGroupModalClose = (changed) => {
this.setState({ isSelectReleaseGroupModalOpen: false });
this.selectRowAfterChange(changed);
}
onSelectQualityModalClose = (changed) => { onSelectQualityModalClose = (changed) => {
this.setState({ isSelectQualityModalOpen: false }); this.setState({ isSelectQualityModalOpen: false });
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
@@ -137,6 +148,7 @@ class InteractiveImportRow extends Component {
movie, movie,
quality, quality,
languages, languages,
releaseGroup,
size, size,
rejections, rejections,
isReprocessing, isReprocessing,
@@ -147,7 +159,8 @@ class InteractiveImportRow extends Component {
const { const {
isSelectMovieModalOpen, isSelectMovieModalOpen,
isSelectQualityModalOpen, isSelectQualityModalOpen,
isSelectLanguageModalOpen isSelectLanguageModalOpen,
isSelectReleaseGroupModalOpen
} = this.state; } = this.state;
const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : ''; const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : '';
@@ -155,6 +168,7 @@ class InteractiveImportRow extends Component {
const showMoviePlaceholder = isSelected && !movie; const showMoviePlaceholder = isSelected && !movie;
const showQualityPlaceholder = isSelected && !quality; const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !languages && !isReprocessing; const showLanguagePlaceholder = isSelected && !languages && !isReprocessing;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
return ( return (
<TableRow> <TableRow>
@@ -181,6 +195,17 @@ class InteractiveImportRow extends Component {
} }
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton
title={translate('ClickToChangeReleaseGroup')}
onPress={this.onSelectReleaseGroupPress}
>
{
showReleaseGroupPlaceholder ?
<InteractiveImportRowCellPlaceholder /> :
releaseGroup
}
</TableRowCellButton>
<TableRowCellButton <TableRowCellButton
className={styles.quality} className={styles.quality}
title={translate('ClickToChangeQuality')} title={translate('ClickToChangeQuality')}
@@ -268,6 +293,13 @@ class InteractiveImportRow extends Component {
onModalClose={this.onSelectMovieModalClose} onModalClose={this.onSelectMovieModalClose}
/> />
<SelectReleaseGroupModal
isOpen={isSelectReleaseGroupModalOpen}
ids={[id]}
releaseGroup={releaseGroup ?? ''}
onModalClose={this.onSelectReleaseGroupModalClose}
/>
<SelectQualityModal <SelectQualityModal
isOpen={isSelectQualityModalOpen} isOpen={isSelectQualityModalOpen}
ids={[id]} ids={[id]}
@@ -296,6 +328,7 @@ InteractiveImportRow.propTypes = {
movie: PropTypes.object, movie: PropTypes.object,
quality: PropTypes.object, quality: PropTypes.object,
languages: PropTypes.arrayOf(PropTypes.object), languages: PropTypes.arrayOf(PropTypes.object),
releaseGroup: PropTypes.string,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
isReprocessing: PropTypes.bool, isReprocessing: PropTypes.bool,
@@ -128,7 +128,7 @@ class SelectLanguageModalContent extends Component {
kind={kinds.SUCCESS} kind={kinds.SUCCESS}
onPress={this.onLanguageSelect} onPress={this.onLanguageSelect}
> >
{translate('SelectLanguges')} {translate('SelectLanguages')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import SelectReleaseGroupModalContentConnector from './SelectReleaseGroupModalContentConnector';
class SelectReleaseGroupModal extends Component {
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<SelectReleaseGroupModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
SelectReleaseGroupModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectReleaseGroupModal;
@@ -0,0 +1,99 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
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 translate from 'Utilities/String/translate';
class SelectReleaseGroupModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
const {
releaseGroup
} = props;
this.state = {
releaseGroup
};
}
//
// Listeners
onReleaseGroupChange = ({ value }) => {
this.setState({ releaseGroup: value });
}
onReleaseGroupSelect = () => {
this.props.onReleaseGroupSelect(this.state);
}
//
// Render
render() {
const {
onModalClose
} = this.props;
const {
releaseGroup
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('ManualImportSetReleaseGroup')}
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="releaseGroup"
value={releaseGroup}
onChange={this.onReleaseGroupChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.SUCCESS}
onPress={this.onReleaseGroupSelect}
>
{translate('SetReleaseGroup')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
SelectReleaseGroupModalContent.propTypes = {
releaseGroup: PropTypes.string.isRequired,
onReleaseGroupSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectReleaseGroupModalContent;
@@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import SelectReleaseGroupModalContent from './SelectReleaseGroupModalContent';
const mapDispatchToProps = {
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
};
class SelectReleaseGroupModalContentConnector extends Component {
//
// Listeners
onReleaseGroupSelect = ({ releaseGroup }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchReprocessInteractiveImportItems
} = this.props;
dispatchUpdateInteractiveImportItems({
ids,
releaseGroup
});
dispatchReprocessInteractiveImportItems({ ids });
this.props.onModalClose(true);
}
//
// Render
render() {
return (
<SelectReleaseGroupModalContent
{...this.props}
onReleaseGroupSelect={this.onReleaseGroupSelect}
/>
);
}
}
SelectReleaseGroupModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(null, mapDispatchToProps)(SelectReleaseGroupModalContentConnector);
@@ -155,7 +155,7 @@ DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
hasFile: PropTypes.bool.isRequired, hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.string.isRequired, sizeOnDisk: PropTypes.number.isRequired,
onDeletePress: PropTypes.func.isRequired, onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -101,6 +101,15 @@ function MovieIndexSortMenu(props) {
{translate('DigitalRelease')} {translate('DigitalRelease')}
</SortMenuItem> </SortMenuItem>
<SortMenuItem
name="ratings"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Ratings')}
</SortMenuItem>
<SortMenuItem <SortMenuItem
name="path" name="path"
sortKey={sortKey} sortKey={sortKey}
+1 -1
View File
@@ -6,7 +6,7 @@ export function getMovieStatusDetails(status) {
let statusDetails = { let statusDetails = {
icon: icons.ANNOUNCED, icon: icons.ANNOUNCED,
title: translate('Announced'), title: translate('Announced'),
message: translate('AnnoucedMsg') message: translate('AnnouncedMsg')
}; };
if (status === 'deleted') { if (status === 'deleted') {
@@ -4,6 +4,11 @@
margin-right: auto; margin-right: auto;
} }
.rightButtons {
justify-content: flex-end;
margin-right: auto;
}
.addSpecification { .addSpecification {
composes: customFormat from '~./CustomFormat.css'; composes: customFormat from '~./CustomFormat.css';
@@ -198,26 +198,25 @@ class EditCustomFormatModalContent extends Component {
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
{ <div className={styles.rightButtons}>
id && {
<Button id &&
className={styles.deleteButton} <Button
kind={kinds.DANGER} className={styles.deleteButton}
onPress={onDeleteCustomFormatPress} kind={kinds.DANGER}
> onPress={onDeleteCustomFormatPress}
{translate('Delete')} >
</Button> {translate('Delete')}
} </Button>
}
{ <Button
!id && className={styles.deleteButton}
<Button onPress={this.onImportPress}
className={styles.deleteButton} >
onPress={this.onImportPress} {translate('Import')}
> </Button>
{translate('Import')} </div>
</Button>
}
<Button <Button
onPress={onModalClose} onPress={onModalClose}
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form'; import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -13,7 +14,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent'; import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props'; import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css'; import styles from './EditDownloadClientModalContent.css';
@@ -45,7 +46,10 @@ class EditDownloadClientModalContent extends Component {
implementationName, implementationName,
name, name,
enable, enable,
protocol,
priority, priority,
removeCompletedDownloads,
removeFailedDownloads,
fields, fields,
message message
} = item; } = item;
@@ -136,6 +140,38 @@ class EditDownloadClientModalContent extends Component {
/> />
</FormGroup> </FormGroup>
<FieldSet
size={sizes.SMALL}
legend={translate('CompletedDownloadHandling')}
>
<FormGroup>
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeCompletedDownloads"
helpText={translate('RemoveCompletedDownloadsHelpText')}
{...removeCompletedDownloads}
onChange={onInputChange}
/>
</FormGroup>
{
protocol.value !== 'torrent' &&
<FormGroup>
<FormLabel>{translate('RemoveFailed')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeFailedDownloads"
helpText={translate('RemoveFailedDownloadsHelpText')}
{...removeFailedDownloads}
onChange={onInputChange}
/>
</FormGroup>
}
</FieldSet>
</Form> </Form>
} }
</ModalBody> </ModalBody>
@@ -1,12 +1,13 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form'; import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel'; import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, sizes } from 'Helpers/Props'; import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
function DownloadClientOptions(props) { function DownloadClientOptions(props) {
@@ -34,11 +35,15 @@ function DownloadClientOptions(props) {
} }
{ {
hasSettings && !isFetching && !error && hasSettings && !isFetching && !error && advancedSettings &&
<div> <div>
<FieldSet legend={translate('CompletedDownloadHandling')}> <FieldSet legend={translate('CompletedDownloadHandling')}>
<Form> <Form>
<FormGroup size={sizes.MEDIUM}> <FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Enable')}</FormLabel> <FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup <FormInputGroup
@@ -50,22 +55,6 @@ function DownloadClientOptions(props) {
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Remove')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeCompletedDownloads"
helpText={translate('RemoveCompletedDownloadsHelpText')}
onChange={onInputChange}
{...settings.removeCompletedDownloads}
/>
</FormGroup>
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
@@ -102,23 +91,10 @@ function DownloadClientOptions(props) {
{...settings.autoRedownloadFailed} {...settings.autoRedownloadFailed}
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Remove')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeFailedDownloads"
helpText={translate('RemoveFailedDownloadsHelpText')}
onChange={onInputChange}
{...settings.removeFailedDownloads}
/>
</FormGroup>
</Form> </Form>
<Alert kind={kinds.INFO}>
{translate('RemoveDownloadsAlert')}
</Alert>
</FieldSet> </FieldSet>
</div> </div>
} }
@@ -1,23 +1,20 @@
.remotePathMapping { .remotePathMapping {
display: flex; display: flex;
align-items: stretch; align-items: left;
margin-bottom: 10px; margin-bottom: 10px;
height: 30px; height: 30px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid $borderColor;
line-height: 30px; line-height: 30px;
} }
.actions {
flex: 0 0 25px;
}
.host { .host {
flex: 0 0 300px; flex: 1 0 300px;
} }
.path { .path {
flex: 0 0 400px; flex: 1 0 400px;
}
.actions {
display: flex;
justify-content: flex-end;
flex: 1 0 auto;
padding-right: 10px;
} }
@@ -66,9 +66,6 @@ class RemotePathMapping extends Component {
styles.remotePathMapping styles.remotePathMapping
)} )}
> >
<div className={styles.host}>{host}</div>
<div className={styles.path}>{remotePath}</div>
<div className={styles.path}>{localPath}</div>
<div className={styles.actions}> <div className={styles.actions}>
<Link <Link
@@ -78,6 +75,10 @@ class RemotePathMapping extends Component {
</Link> </Link>
</div> </div>
<div className={styles.host}>{host}</div>
<div className={styles.path}>{remotePath}</div>
<div className={styles.path}>{localPath}</div>
<EditRemotePathMappingModalConnector <EditRemotePathMappingModalConnector
id={id} id={id}
isOpen={this.state.isEditRemotePathMappingModalOpen} isOpen={this.state.isEditRemotePathMappingModalOpen}
@@ -4,17 +4,19 @@
font-weight: bold; font-weight: bold;
} }
.actions {
flex: 0 0 25px;
}
.host { .host {
flex: 0 0 300px; flex: 1 0 300px;
} }
.path { .path {
flex: 0 0 400px; flex: 1 0 400px;
} }
.addRemotePathMapping { .addRemotePathMapping {
display: flex;
justify-content: flex-end;
padding-right: 10px; padding-right: 10px;
} }
@@ -51,9 +51,15 @@ class RemotePathMappings extends Component {
{...otherProps} {...otherProps}
> >
<div className={styles.remotePathMappingsHeader}> <div className={styles.remotePathMappingsHeader}>
<div className={styles.host}>Host</div> <div className={styles.host}>
<div className={styles.path}>Remote Path</div> {translate('Host')}
<div className={styles.path}>Local Path</div> </div>
<div className={styles.path}>
{translate('RemotePath')}
</div>
<div className={styles.path}>
{translate('LocalPath')}
</div>
</div> </div>
<div> <div>
@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import EditImportExclusionModalContentConnector from './EditImportExclusionModalContentConnector'; import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector';
function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) { function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
return ( return (
<Modal <Modal
size={sizes.MEDIUM} size={sizes.MEDIUM}
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<EditImportExclusionModalContentConnector <EditImportListExclusionModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@@ -19,9 +19,9 @@ function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
); );
} }
EditImportExclusionModal.propTypes = { EditImportListExclusionModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default EditImportExclusionModal; export default EditImportListExclusionModal;
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions'; import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditImportExclusionModal from './EditImportExclusionModal'; import EditImportListExclusionModal from './EditImportListExclusionModal';
function mapStateToProps() { function mapStateToProps() {
return {}; return {};
@@ -12,7 +12,7 @@ const mapDispatchToProps = {
clearPendingChanges clearPendingChanges
}; };
class EditImportExclusionModalConnector extends Component { class EditImportListExclusionModalConnector extends Component {
// //
// Listeners // Listeners
@@ -27,7 +27,7 @@ class EditImportExclusionModalConnector extends Component {
render() { render() {
return ( return (
<EditImportExclusionModal <EditImportListExclusionModal
{...this.props} {...this.props}
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
/> />
@@ -35,9 +35,9 @@ class EditImportExclusionModalConnector extends Component {
} }
} }
EditImportExclusionModalConnector.propTypes = { EditImportListExclusionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired clearPendingChanges: PropTypes.func.isRequired
}; };
export default connect(mapStateToProps, mapDispatchToProps)(EditImportExclusionModalConnector); export default connect(mapStateToProps, mapDispatchToProps)(EditImportListExclusionModalConnector);
@@ -13,9 +13,9 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props'; import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './EditImportExclusionModalContent.css'; import styles from './EditImportListExclusionModalContent.css';
function EditImportExclusionModalContent(props) { function EditImportListExclusionModalContent(props) {
const { const {
id, id,
isFetching, isFetching,
@@ -130,7 +130,7 @@ function EditImportExclusionModalContent(props) {
); );
} }
EditImportExclusionModalContent.propTypes = { EditImportListExclusionModalContent.propTypes = {
id: PropTypes.number, id: PropTypes.number,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@@ -143,4 +143,4 @@ EditImportExclusionModalContent.propTypes = {
onDeleteImportExclusionPress: PropTypes.func onDeleteImportExclusionPress: PropTypes.func
}; };
export default EditImportExclusionModalContent; export default EditImportListExclusionModalContent;
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/settingsActions'; import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings'; import selectSettings from 'Store/Selectors/selectSettings';
import EditImportExclusionModalContent from './EditImportExclusionModalContent'; import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
const newImportExclusion = { const newImportExclusion = {
movieTitle: '', movieTitle: '',
@@ -97,7 +97,7 @@ class EditImportExclusionModalContentConnector extends Component {
render() { render() {
return ( return (
<EditImportExclusionModalContent <EditImportListExclusionModalContent
{...this.props} {...this.props}
onSavePress={this.onSavePress} onSavePress={this.onSavePress}
onInputChange={this.onInputChange} onInputChange={this.onInputChange}
@@ -8,12 +8,14 @@
} }
.movieTitle { .movieTitle {
flex: 0 0 400px; @add-mixin truncate;
flex: 0 0 600px;
} }
.tmdbId, .tmdbId,
.movieYear { .movieYear {
flex: 0 0 200px; flex: 0 0 70px;
} }
.actions { .actions {
@@ -6,10 +6,10 @@ import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal'; import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import EditImportExclusionModalConnector from './EditImportExclusionModalConnector'; import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportExclusion.css'; import styles from './ImportListExclusion.css';
class ImportExclusion extends Component { class ImportListExclusion extends Component {
// //
// Lifecycle // Lifecycle
@@ -78,7 +78,7 @@ class ImportExclusion extends Component {
</Link> </Link>
</div> </div>
<EditImportExclusionModalConnector <EditImportListExclusionModalConnector
id={id} id={id}
isOpen={this.state.isEditImportExclusionModalOpen} isOpen={this.state.isEditImportExclusionModalOpen}
onModalClose={this.onEditImportExclusionModalClose} onModalClose={this.onEditImportExclusionModalClose}
@@ -99,7 +99,7 @@ class ImportExclusion extends Component {
} }
} }
ImportExclusion.propTypes = { ImportListExclusion.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired, movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired, tmdbId: PropTypes.number.isRequired,
@@ -107,9 +107,9 @@ ImportExclusion.propTypes = {
onConfirmDeleteImportExclusion: PropTypes.func.isRequired onConfirmDeleteImportExclusion: PropTypes.func.isRequired
}; };
ImportExclusion.defaultProps = { ImportListExclusion.defaultProps = {
// The drag preview will not connect the drag handle. // The drag preview will not connect the drag handle.
connectDragSource: (node) => node connectDragSource: (node) => node
}; };
export default ImportExclusion; export default ImportListExclusion;
@@ -1,16 +1,16 @@
.importExclusionsHeader { .importListExclusionsHeader {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
font-weight: bold; font-weight: bold;
} }
.title { .title {
flex: 0 0 400px; flex: 0 0 600px;
} }
.tmdbId, .tmdbId,
.movieYear { .movieYear {
flex: 0 0 200px; flex: 0 0 70px;
} }
.addImportExclusion { .addImportExclusion {
@@ -6,11 +6,11 @@ import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent'; import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import EditImportExclusionModalConnector from './EditImportExclusionModalConnector'; import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportExclusion from './ImportExclusion'; import ImportListExclusion from './ImportListExclusion';
import styles from './ImportExclusions.css'; import styles from './ImportListExclusions.css';
class ImportExclusions extends Component { class ImportListExclusions extends Component {
// //
// Lifecycle // Lifecycle
@@ -51,16 +51,22 @@ class ImportExclusions extends Component {
{...otherProps} {...otherProps}
> >
<div className={styles.importExclusionsHeader}> <div className={styles.importExclusionsHeader}>
<div className={styles.tmdbId}>TMDB Id</div> <div className={styles.tmdbId}>
<div className={styles.title}>Title</div> TMDb Id
<div className={styles.movieYear}>Year</div> </div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
</div> </div>
<div> <div>
{ {
items.map((item, index) => { items.map((item, index) => {
return ( return (
<ImportExclusion <ImportListExclusion
key={item.id} key={item.id}
{...item} {...item}
{...otherProps} {...otherProps}
@@ -81,7 +87,7 @@ class ImportExclusions extends Component {
</Link> </Link>
</div> </div>
<EditImportExclusionModalConnector <EditImportListExclusionModalConnector
isOpen={this.state.isAddImportExclusionModalOpen} isOpen={this.state.isAddImportExclusionModalOpen}
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
/> />
@@ -92,11 +98,11 @@ class ImportExclusions extends Component {
} }
} }
ImportExclusions.propTypes = { ImportListExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired onConfirmDeleteImportExclusion: PropTypes.func.isRequired
}; };
export default ImportExclusions; export default ImportListExclusions;
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { deleteImportExclusion, fetchImportExclusions } from 'Store/Actions/settingsActions'; import { deleteImportExclusion, fetchImportExclusions } from 'Store/Actions/settingsActions';
import ImportExclusions from './ImportExclusions'; import ImportListExclusions from './ImportListExclusions';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@@ -42,7 +42,7 @@ class ImportExclusionsConnector extends Component {
render() { render() {
return ( return (
<ImportExclusions <ImportListExclusions
{...this.state} {...this.state}
{...this.props} {...this.props}
onConfirmDeleteImportExclusion={this.onConfirmDeleteImportExclusion} onConfirmDeleteImportExclusion={this.onConfirmDeleteImportExclusion}
@@ -7,7 +7,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import ImportExclusionsConnector from './ImportExclusions/ImportExclusionsConnector'; import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
import ImportListsConnector from './ImportLists/ImportListsConnector'; import ImportListsConnector from './ImportLists/ImportListsConnector';
import ImportListOptionsConnector from './Options/ImportListOptionsConnector'; import ImportListOptionsConnector from './Options/ImportListOptionsConnector';
@@ -86,7 +86,7 @@ class ImportListSettings extends Component {
onChildStateChange={this.onChildStateChange} onChildStateChange={this.onChildStateChange}
/> />
<ImportExclusionsConnector /> <ImportListExclusionsConnector />
</PageContentBody> </PageContentBody>
</PageContent> </PageContent>
@@ -45,7 +45,9 @@ function EditIndexerModalContent(props) {
supportsSearch, supportsSearch,
tags, tags,
fields, fields,
priority priority,
protocol,
downloadClientId
} = item; } = item;
return ( return (
@@ -154,8 +156,25 @@ function EditIndexerModalContent(props) {
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('DownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
name="downloadClientId"
helpText={translate('IndexerDownloadClientHelpText')}
{...downloadClientId}
includeAny={true}
protocol={protocol.value}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup> <FormGroup>
<FormLabel>Tags</FormLabel> <FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TAG} type={inputTypes.TAG}
@@ -63,6 +63,7 @@ class Notification extends Component {
onMovieFileDelete, onMovieFileDelete,
onMovieFileDeleteForUpgrade, onMovieFileDeleteForUpgrade,
onHealthIssue, onHealthIssue,
onApplicationUpdate,
supportsOnGrab, supportsOnGrab,
supportsOnDownload, supportsOnDownload,
supportsOnUpgrade, supportsOnUpgrade,
@@ -70,7 +71,8 @@ class Notification extends Component {
supportsOnMovieDelete, supportsOnMovieDelete,
supportsOnMovieFileDelete, supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade, supportsOnMovieFileDeleteForUpgrade,
supportsOnHealthIssue supportsOnHealthIssue,
supportsOnApplicationUpdate
} = this.props; } = this.props;
return ( return (
@@ -123,6 +125,14 @@ class Notification extends Component {
null null
} }
{
supportsOnApplicationUpdate && onApplicationUpdate ?
<Label kind={kinds.SUCCESS}>
{translate('OnApplicationUpdate')}
</Label> :
null
}
{ {
supportsOnMovieDelete && onMovieDelete ? supportsOnMovieDelete && onMovieDelete ?
<Label kind={kinds.SUCCESS}> <Label kind={kinds.SUCCESS}>
@@ -148,7 +158,7 @@ class Notification extends Component {
} }
{ {
!onGrab && !onDownload && !onRename && !onHealthIssue && !onMovieDelete && !onMovieFileDelete ? !onGrab && !onDownload && !onRename && !onHealthIssue && !onApplicationUpdate && !onMovieDelete && !onMovieFileDelete ?
<Label <Label
kind={kinds.DISABLED} kind={kinds.DISABLED}
outline={true} outline={true}
@@ -190,6 +200,7 @@ Notification.propTypes = {
onMovieFileDelete: PropTypes.bool.isRequired, onMovieFileDelete: PropTypes.bool.isRequired,
onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired, onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired, onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired, supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired, supportsOnDownload: PropTypes.bool.isRequired,
supportsOnMovieDelete: PropTypes.bool.isRequired, supportsOnMovieDelete: PropTypes.bool.isRequired,
@@ -198,6 +209,7 @@ Notification.propTypes = {
supportsOnUpgrade: PropTypes.bool.isRequired, supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired, supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired, supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired onConfirmDeleteNotification: PropTypes.func.isRequired
}; };
@@ -23,6 +23,7 @@ function NotificationEventItems(props) {
onMovieFileDelete, onMovieFileDelete,
onMovieFileDeleteForUpgrade, onMovieFileDeleteForUpgrade,
onHealthIssue, onHealthIssue,
onApplicationUpdate,
supportsOnGrab, supportsOnGrab,
supportsOnDownload, supportsOnDownload,
supportsOnUpgrade, supportsOnUpgrade,
@@ -30,6 +31,7 @@ function NotificationEventItems(props) {
supportsOnMovieDelete, supportsOnMovieDelete,
supportsOnMovieFileDelete, supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade, supportsOnMovieFileDeleteForUpgrade,
supportsOnApplicationUpdate,
supportsOnHealthIssue, supportsOnHealthIssue,
includeHealthWarnings includeHealthWarnings
} = item; } = item;
@@ -150,6 +152,17 @@ function NotificationEventItems(props) {
/> />
</div> </div>
} }
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onApplicationUpdate"
helpText={translate('OnApplicationUpdateHelpText')}
isDisabled={!supportsOnApplicationUpdate.value}
{...onApplicationUpdate}
onChange={onInputChange}
/>
</div>
</div> </div>
</div> </div>
</FormGroup> </FormGroup>
@@ -82,10 +82,18 @@ class DelayProfiles extends Component {
> >
<div> <div>
<div className={styles.delayProfilesHeader}> <div className={styles.delayProfilesHeader}>
<div className={styles.column}>Protocol</div> <div className={styles.column}>
<div className={styles.column}>Usenet Delay</div> {translate('Protocol')}
<div className={styles.column}>Torrent Delay</div> </div>
<div className={styles.tags}>Tags</div> <div className={styles.column}>
{translate('UsenetDelay')}
</div>
<div className={styles.column}>
{translate('TorrentDelay')}
</div>
<div className={styles.tags}>
{translate('Tags')}
</div>
</div> </div>
<div className={styles.delayProfiles}> <div className={styles.delayProfiles}>
@@ -25,9 +25,15 @@ class QualityDefinitions extends Component {
{...otherProps} {...otherProps}
> >
<div className={styles.header}> <div className={styles.header}>
<div className={styles.quality}>Quality</div> <div className={styles.quality}>
<div className={styles.title}>Title</div> {translate('Quality')}
<div className={styles.sizeLimit}>Size Limit</div> </div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.sizeLimit}>
{translate('SizeLimit')}
</div>
{ {
advancedSettings ? advancedSettings ?
@@ -152,7 +152,7 @@ function TagDetailsModalContent(props) {
{ {
indexers.length ? indexers.length ?
<FieldSet legend="Indexers"> <FieldSet legend={translate('Indexers')}>
{ {
indexers.map((item) => { indexers.map((item) => {
return ( return (
-30
View File
@@ -48,21 +48,6 @@ export const movieRuntimeFormatOptions = [
{ key: 'minutes', value: '75 mins' } { key: 'minutes', value: '75 mins' }
]; ];
export const themeOptions = [
{ key: 'default', value: 'Default' },
{ key: 'aquamarine', value: 'Aquamarine' },
{ key: 'dark', value: 'Dark' },
{ key: 'dracula', value: 'Dracula' },
{ key: 'hotline', value: 'Hotline' },
{ key: 'hotpink', value: 'Hotpink' },
{ key: 'nord', value: 'Nord' },
{ key: 'organizr', value: 'Organizr' },
{ key: 'overseerr', value: 'Overseerr' },
{ key: 'plex', value: 'Plex' },
{ key: 'radarr-darker', value: 'Radarr Darker' },
{ key: 'space-gray', value: 'Space Gray' }
];
class UISettings extends Component { class UISettings extends Component {
// //
@@ -199,21 +184,6 @@ class UISettings extends Component {
</FieldSet> </FieldSet>
<FieldSet legend={translate('Style')}> <FieldSet legend={translate('Style')}>
<FormGroup>
<FormLabel>{translate('Theme')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="theme"
helpText={translate('ThemeHelpText', ['Theme.Park'])}
inlineLink="https://github.com/GilbN/theme.park"
tooltip="Theme.Park Github"
helpTextWarning={translate('ThemeHelpTextWarning')}
values={themeOptions}
onChange={onInputChange}
{...settings.theme}
/>
</FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('SettingsEnableColorImpairedMode')}</FormLabel> <FormLabel>{translate('SettingsEnableColorImpairedMode')}</FormLabel>
<FormInputGroup <FormInputGroup
@@ -109,6 +109,7 @@ export default {
selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete; selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete;
selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete; selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete;
selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade; selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema; return selectedSchema;
}); });
@@ -600,6 +600,7 @@ export const actionHandlers = handleThunks({
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: '/exclusions/bulk', url: '/exclusions/bulk',
method: 'POST', method: 'POST',
contentType: 'application/json',
data: JSON.stringify(exclusions) data: JSON.stringify(exclusions)
}).request; }).request;
@@ -148,9 +148,10 @@ export const actionHandlers = handleThunks({
return { return {
id, id,
path: item.path, path: item.path,
movieId: item.movie.id, movieId: item.movie ? item.movie.id : undefined,
quality: item.quality, quality: item.quality,
languages: item.languages, languages: item.languages,
releaseGroup: item.releaseGroup,
downloadId: item.downloadId downloadId: item.downloadId
}; };
}); });
+1 -1
View File
@@ -27,7 +27,7 @@ const paged = `${section}.paged`;
export const defaultState = { export const defaultState = {
options: { options: {
includeUnknownMovieItems: false includeUnknownMovieItems: true
}, },
status: { status: {
+4 -7
View File
@@ -1,7 +1,6 @@
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import ThemeSelector from 'App/ThemeSelector';
import createAppStore from 'Store/createAppStore'; import createAppStore from 'Store/createAppStore';
import App from './App/App'; import App from './App/App';
@@ -14,11 +13,9 @@ const history = createBrowserHistory();
const store = createAppStore(history); const store = createAppStore(history);
render( render(
<ThemeSelector> <App
<App store={store}
store={store} history={history}
history={history} />,
/>
</ThemeSelector>,
document.getElementById('root') document.getElementById('root')
); );
+5 -5
View File
@@ -30,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "5.15.3", "@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3", "@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14", "@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "5.0.10", "@microsoft/signalr": "6.0.0",
"@sentry/browser": "6.13.2", "@sentry/browser": "6.13.2",
"@sentry/integrations": "6.13.2", "@sentry/integrations": "6.13.2",
"classnames": "2.3.1", "classnames": "2.3.1",
@@ -82,6 +82,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.13.16", "@babel/core": "7.13.16",
"@babel/eslint-parser": "7.13.14",
"@babel/plugin-proposal-class-properties": "7.13.0", "@babel/plugin-proposal-class-properties": "7.13.0",
"@babel/plugin-proposal-decorators": "7.13.15", "@babel/plugin-proposal-decorators": "7.13.15",
"@babel/plugin-proposal-export-default-from": "7.12.13", "@babel/plugin-proposal-export-default-from": "7.12.13",
@@ -94,7 +95,6 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.13.15", "@babel/preset-env": "7.13.15",
"@babel/preset-react": "7.13.13", "@babel/preset-react": "7.13.13",
"@babel/eslint-parser": "7.13.14",
"autoprefixer": "10.2.5", "autoprefixer": "10.2.5",
"babel-loader": "8.2.2", "babel-loader": "8.2.2",
"babel-plugin-inline-classnames": "2.0.1", "babel-plugin-inline-classnames": "2.0.1",
@@ -125,12 +125,12 @@
"run-sequence": "2.2.1", "run-sequence": "2.2.1",
"streamqueue": "1.1.2", "streamqueue": "1.1.2",
"style-loader": "2.0.0", "style-loader": "2.0.0",
"stylelint": "13.13.0",
"stylelint-order": "4.1.0",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"webpack": "5.35.1", "webpack": "5.35.1",
"webpack-cli": "4.6.0", "webpack-cli": "4.6.0",
"webpack-livereload-plugin": "3.0.1", "webpack-livereload-plugin": "3.0.1",
"worker-loader": "3.0.8", "worker-loader": "3.0.8"
"stylelint": "13.13.0",
"stylelint-order": "4.1.0"
} }
} }
+6 -5
View File
@@ -2,10 +2,11 @@
<!-- Common to all Radarr Projects --> <!-- Common to all Radarr Projects -->
<PropertyGroup> <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;linux-x64;linux-musl-x64;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;win-x86;osx-x64;osx-arm64;linux-x64;linux-musl-x64;linux-arm;linux-musl-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
<RadarrRootDir>$(MSBuildThisFileDirectory)..\</RadarrRootDir> <RadarrRootDir>$(MSBuildThisFileDirectory)..\</RadarrRootDir>
@@ -89,13 +90,13 @@
<!-- Standard testing packages --> <!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'"> <ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NunitXml.TestLogger" Version="2.1.62" /> <PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net5.0'"> <ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'">
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" /> <PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
</ItemGroup> </ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" /> <PackageReference Include="Selenium.Support" Version="3.141.0" />
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading; using System.Threading;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
@@ -15,8 +16,11 @@ using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Security;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories; using NzbDrone.Test.Common.Categories;
using HttpClient = NzbDrone.Common.Http.HttpClient;
namespace NzbDrone.Common.Test.Http namespace NzbDrone.Common.Test.Http
{ {
@@ -31,6 +35,8 @@ namespace NzbDrone.Common.Test.Http
private string _httpBinHost; private string _httpBinHost;
private string _httpBinHost2; private string _httpBinHost2;
private System.Net.Http.HttpClient _httpClient = new ();
[OneTimeSetUp] [OneTimeSetUp]
public void FixtureSetUp() public void FixtureSetUp()
{ {
@@ -38,7 +44,7 @@ namespace NzbDrone.Common.Test.Http
var mainHost = "httpbin.servarr.com"; var mainHost = "httpbin.servarr.com";
// Use mirrors for tests that use two hosts // Use mirrors for tests that use two hosts
var candidates = new[] { "eu.httpbin.org", /* "httpbin.org", */ "www.httpbin.org" }; var candidates = new[] { "httpbin1.servarr.com" };
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable. // httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
_httpBinHost = mainHost; _httpBinHost = mainHost;
@@ -46,29 +52,20 @@ namespace NzbDrone.Common.Test.Http
TestLogger.Info($"{candidates.Length} TestSites available."); TestLogger.Info($"{candidates.Length} TestSites available.");
_httpBinSleep = _httpBinHosts.Length < 2 ? 100 : 10; _httpBinSleep = 10;
} }
private bool IsTestSiteAvailable(string site) private bool IsTestSiteAvailable(string site)
{ {
try try
{ {
var req = WebRequest.Create($"https://{site}/get") as HttpWebRequest; var res = _httpClient.GetAsync($"https://{site}/get").GetAwaiter().GetResult();
var res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode != HttpStatusCode.OK) if (res.StatusCode != HttpStatusCode.OK)
{ {
return false; return false;
} }
try res = _httpClient.GetAsync($"https://{site}/status/429").GetAwaiter().GetResult();
{
req = WebRequest.Create($"https://{site}/status/429") as HttpWebRequest;
res = req.GetResponse() as HttpWebResponse;
}
catch (WebException ex)
{
res = ex.Response as HttpWebResponse;
}
if (res == null || res.StatusCode != (HttpStatusCode)429) if (res == null || res.StatusCode != (HttpStatusCode)429)
{ {
@@ -95,10 +92,13 @@ namespace NzbDrone.Common.Test.Http
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS"); Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0"); Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Enabled);
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>()); Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>()); Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>()); Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.GetMock<IConfigService>().Object, TestLogger));
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>()); Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(Array.Empty<IHttpRequestInterceptor>()); Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(Array.Empty<IHttpRequestInterceptor>());
Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>()); Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>());
@@ -138,6 +138,28 @@ namespace NzbDrone.Common.Test.Http
response.Content.Should().NotBeNullOrWhiteSpace(); response.Content.Should().NotBeNullOrWhiteSpace();
} }
[TestCase(CertificateValidationType.Enabled)]
[TestCase(CertificateValidationType.DisabledForLocalAddresses)]
public void bad_ssl_should_fail_when_remote_validation_enabled(CertificateValidationType validationType)
{
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
var request = new HttpRequest($"https://expired.badssl.com");
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void bad_ssl_should_pass_if_remote_validation_disabled()
{
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
var request = new HttpRequest($"https://expired.badssl.com");
Subject.Execute(request);
ExceptionVerification.ExpectedErrors(0);
}
[Test] [Test]
public void should_execute_typed_get() public void should_execute_typed_get()
{ {
@@ -162,15 +184,44 @@ namespace NzbDrone.Common.Test.Http
response.Resource.Data.Should().Be(message); response.Resource.Data.Should().Be(message);
} }
[TestCase("gzip")] [Test]
public void should_execute_get_using_gzip(string compression) public void should_execute_post_with_content_type()
{ {
var request = new HttpRequest($"https://{_httpBinHost}/{compression}"); var message = "{ my: 1 }";
var request = new HttpRequest($"https://{_httpBinHost}/post");
request.SetContent(message);
request.Headers.ContentType = "application/json";
var response = Subject.Post<HttpBinResource>(request);
response.Resource.Data.Should().Be(message);
}
[Test]
public void should_execute_get_using_gzip()
{
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers["Accept-Encoding"].ToString().Should().Be(compression); response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip");
response.Resource.Gzipped.Should().BeTrue(); response.Resource.Gzipped.Should().BeTrue();
response.Resource.Brotli.Should().BeFalse();
}
[Test]
public void should_execute_get_using_brotli()
{
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
response.Resource.Gzipped.Should().BeFalse();
response.Resource.Brotli.Should().BeTrue();
} }
[TestCase(HttpStatusCode.Unauthorized)] [TestCase(HttpStatusCode.Unauthorized)]
@@ -337,13 +388,38 @@ namespace NzbDrone.Common.Test.Http
{ {
var file = GetTempFilePath(); var file = GetTempFilePath();
Assert.Throws<WebException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file)); Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
File.Exists(file).Should().BeFalse(); File.Exists(file).Should().BeFalse();
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
} }
[Test]
public void should_not_write_redirect_content_to_stream()
{
var file = GetTempFilePath();
using (var fileStream = new FileStream(file, FileMode.Create))
{
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
request.AllowAutoRedirect = false;
request.ResponseStream = fileStream;
var response = Subject.Get(request);
response.StatusCode.Should().Be(HttpStatusCode.Moved);
}
ExceptionVerification.ExpectedErrors(1);
File.Exists(file).Should().BeTrue();
var fileInfo = new FileInfo(file);
fileInfo.Length.Should().Be(0);
}
[Test] [Test]
public void should_send_cookie() public void should_send_cookie()
{ {
@@ -743,7 +819,7 @@ namespace NzbDrone.Common.Test.Http
{ {
try try
{ {
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}"; string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
var requestSet = new HttpRequest(url); var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false; requestSet.AllowAutoRedirect = false;
@@ -773,6 +849,7 @@ namespace NzbDrone.Common.Test.Http
public string Url { get; set; } public string Url { get; set; }
public string Data { get; set; } public string Data { get; set; }
public bool Gzipped { get; set; } public bool Gzipped { get; set; }
public bool Brotli { get; set; }
} }
public class HttpCookieResource public class HttpCookieResource
@@ -62,6 +62,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")] [TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")] [TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")] [TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
// Announce URLs (passkeys) Magnet & Tracker // Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" />
@@ -3,6 +3,8 @@ using DryIoc;
using DryIoc.Microsoft.DependencyInjection; using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@@ -25,12 +27,15 @@ namespace NzbDrone.Common.Test
.AddNzbDroneLogger() .AddNzbDroneLogger()
.AutoAddServices(Bootstrap.ASSEMBLIES) .AutoAddServices(Bootstrap.ASSEMBLIES)
.AddDummyDatabase() .AddDummyDatabase()
.AddStartupContext(new StartupContext("first", "second")) .AddStartupContext(new StartupContext("first", "second"));
.GetServiceProvider();
container.GetRequiredService<IAppFolderFactory>().Register(); container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
Mocker.SetConstant<System.IServiceProvider>(container); var serviceProvider = container.GetServiceProvider();
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
Mocker.SetConstant<System.IServiceProvider>(serviceProvider);
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>() var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()
.Select(c => c.GetType().FullName); .Select(c => c.GetType().FullName);
+1 -1
View File
@@ -106,7 +106,7 @@ namespace NzbDrone.Common
Stream inStream = File.OpenRead(compressedFile); Stream inStream = File.OpenRead(compressedFile);
Stream gzipStream = new GZipInputStream(inStream); Stream gzipStream = new GZipInputStream(inStream);
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream); TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
tarArchive.ExtractContents(destination); tarArchive.ExtractContents(destination);
tarArchive.Close(); tarArchive.Close();
@@ -1,9 +1,9 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Security.Principal; using System.Security.Principal;
using System.ServiceProcess; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using NLog; using NLog;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
@@ -14,16 +14,13 @@ namespace NzbDrone.Common.EnvironmentInfo
private readonly Logger _logger; private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow; private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger) public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null)
{ {
_logger = logger; _logger = logger;
IsWindowsService = !IsUserInteractive && IsWindowsService = hostLifetime is WindowsServiceLifetime;
OsInfo.IsWindows &&
serviceProvider.ServiceExist(ServiceProvider.SERVICE_NAME) &&
serviceProvider.GetStatus(ServiceProvider.SERVICE_NAME) == ServiceControllerStatus.StartPending;
// net5.0 will return Radarr.dll for entry assembly, we need the actual // net6.0 will return Radarr.dll for entry assembly, we need the actual
// executable name (Radarr on linux). On mono this will return the location of // executable name (Radarr on linux). On mono this will return the location of
// the mono executable itself, which is not what we want. // the mono executable itself, which is not what we want.
var entry = Process.GetCurrentProcess().MainModule; var entry = Process.GetCurrentProcess().MainModule;
@@ -6,13 +6,6 @@ namespace NzbDrone.Common.Extensions
{ {
public static class EnumerableExtensions public static class EnumerableExtensions
{ {
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var knownKeys = new HashSet<TKey>();
return source.Where(element => knownKeys.Add(keySelector(element)));
}
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first,
Func<TFirst, TKey> firstKeySelector, Func<TFirst, TKey> firstKeySelector,
IEnumerable<TSecond> second, IEnumerable<TSecond> second,
@@ -0,0 +1,11 @@
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace NzbDrone.Common.Http.Dispatchers
{
public interface ICertificateValidationService
{
bool ShouldByPassValidationError(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors);
}
}
@@ -5,6 +5,5 @@ namespace NzbDrone.Common.Http.Dispatchers
public interface IHttpDispatcher public interface IHttpDispatcher
{ {
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies); HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
void DownloadFile(string url, string fileName);
} }
} }
@@ -1,12 +1,13 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Net; using System.Net;
using System.Reflection; using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using NLog; using NLog;
using NLog.Fluent; using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
@@ -14,200 +15,171 @@ namespace NzbDrone.Common.Http.Dispatchers
{ {
public class ManagedHttpDispatcher : IHttpDispatcher public class ManagedHttpDispatcher : IHttpDispatcher
{ {
private const string NO_PROXY_KEY = "no-proxy";
private const int connection_establish_timeout = 2000;
private static bool useIPv6 = Socket.OSSupportsIPv6;
private static bool hasResolvedIPv6Availability;
private readonly IHttpProxySettingsProvider _proxySettingsProvider; private readonly IHttpProxySettingsProvider _proxySettingsProvider;
private readonly ICreateManagedWebProxy _createManagedWebProxy; private readonly ICreateManagedWebProxy _createManagedWebProxy;
private readonly ICertificateValidationService _certificateValidationService;
private readonly IUserAgentBuilder _userAgentBuilder; private readonly IUserAgentBuilder _userAgentBuilder;
private readonly IPlatformInfo _platformInfo; private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
private readonly Logger _logger;
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder, IPlatformInfo platformInfo, Logger logger) public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
ICreateManagedWebProxy createManagedWebProxy,
ICertificateValidationService certificateValidationService,
IUserAgentBuilder userAgentBuilder,
ICacheManager cacheManager)
{ {
_proxySettingsProvider = proxySettingsProvider; _proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy; _createManagedWebProxy = createManagedWebProxy;
_certificateValidationService = certificateValidationService;
_userAgentBuilder = userAgentBuilder; _userAgentBuilder = userAgentBuilder;
_platformInfo = platformInfo;
_logger = logger; _httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
} }
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{ {
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive;
// Deflate is not a standard and could break depending on implementation. var cookieHeader = cookies.GetCookieHeader((Uri)request.Url);
// we should just stick with the more compatible Gzip if (cookieHeader.IsNotNullOrWhiteSpace())
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
webRequest.Method = request.Method.ToString();
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
webRequest.KeepAlive = request.ConnectionKeepAlive;
webRequest.AllowAutoRedirect = false;
webRequest.CookieContainer = cookies;
if (request.RequestTimeout != TimeSpan.Zero)
{ {
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds); requestMessage.Headers.Add("Cookie", cookieHeader);
} }
webRequest.Proxy = GetProxy(request.Url); using var cts = new CancellationTokenSource();
if (request.RequestTimeout != TimeSpan.Zero)
{
cts.CancelAfter(request.RequestTimeout);
}
else
{
// The default for System.Net.Http.HttpClient
cts.CancelAfter(TimeSpan.FromSeconds(100));
}
if (request.ContentData != null)
{
requestMessage.Content = new ByteArrayContent(request.ContentData);
}
if (request.Headers != null) if (request.Headers != null)
{ {
AddRequestHeaders(webRequest, request.Headers); AddRequestHeaders(requestMessage, request.Headers);
} }
HttpWebResponse httpWebResponse; var httpClient = GetClient(request.Url);
try using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
{ {
if (request.ContentData != null) byte[] data = null;
{
webRequest.ContentLength = request.ContentData.Length;
using (var writeStream = webRequest.GetRequestStream())
{
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
}
}
httpWebResponse = (HttpWebResponse)webRequest.GetResponse(); try
}
catch (WebException e)
{
httpWebResponse = (HttpWebResponse)e.Response;
if (httpWebResponse == null)
{ {
// The default messages for WebException on mono are pretty horrible. if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
if (e.Status == WebExceptionStatus.NameResolutionFailure)
{ {
throw new WebException($"DNS Name Resolution Failure: '{webRequest.RequestUri.Host}'", e.Status); responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
}
else if (e.ToString().Contains("TLS Support not"))
{
throw new TlsFailureException(webRequest, e);
}
else if (e.ToString().Contains("The authentication or decryption has failed."))
{
throw new TlsFailureException(webRequest, e);
}
else if (OsInfo.IsNotWindows)
{
throw new WebException($"{e.Message}: '{webRequest.RequestUri}'", e, e.Status, e.Response);
} }
else else
{ {
throw; data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
} }
} }
} catch (Exception ex)
byte[] data = null;
using (var responseStream = httpWebResponse.GetResponseStream())
{
if (responseStream != null && responseStream != Stream.Null)
{ {
try throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
{
data = responseStream.ToBytes();
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, httpWebResponse);
}
}
}
return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data, httpWebResponse.StatusCode);
}
public void DownloadFile(string url, string fileName)
{
try
{
var fileInfo = new FileInfo(fileName);
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
} }
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName); var headers = responseMessage.Headers.ToNameValueCollection();
var stopWatch = Stopwatch.StartNew(); headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
var uri = new HttpUri(url);
using (var webClient = new GZipWebClient()) return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
{
webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgentBuilder.GetUserAgent());
webClient.Proxy = GetProxy(uri);
webClient.DownloadFile(uri.FullUri, fileName);
stopWatch.Stop();
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
}
}
catch (WebException e)
{
_logger.Warn("Failed to get response from: {0} {1}", url, e.Message);
throw;
}
catch (Exception e)
{
_logger.Warn(e, "Failed to get response from: " + url);
throw;
} }
} }
protected virtual IWebProxy GetProxy(HttpUri uri) protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
{ {
IWebProxy proxy = null;
var proxySettings = _proxySettingsProvider.GetProxySettings(uri); var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
var key = proxySettings?.Key ?? NO_PROXY_KEY;
return _httpClientCache.Get(key, () => CreateHttpClient(proxySettings));
}
protected virtual System.Net.Http.HttpClient CreateHttpClient(HttpProxySettings proxySettings)
{
var handler = new SocketsHttpHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Brotli,
UseCookies = false, // sic - we don't want to use a shared cookie container
AllowAutoRedirect = false,
MaxConnectionsPerServer = 12,
ConnectCallback = onConnect,
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError
}
};
if (proxySettings != null) if (proxySettings != null)
{ {
proxy = _createManagedWebProxy.GetWebProxy(proxySettings); handler.Proxy = _createManagedWebProxy.GetWebProxy(proxySettings);
} }
return proxy; var client = new System.Net.Http.HttpClient(handler)
{
Timeout = Timeout.InfiniteTimeSpan
};
return client;
} }
protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers) protected virtual void AddRequestHeaders(HttpRequestMessage webRequest, HttpHeader headers)
{ {
foreach (var header in headers) foreach (var header in headers)
{ {
switch (header.Key) switch (header.Key)
{ {
case "Accept": case "Accept":
webRequest.Accept = header.Value; webRequest.Headers.Accept.ParseAdd(header.Value);
break; break;
case "Connection": case "Connection":
webRequest.Connection = header.Value; webRequest.Headers.Connection.Clear();
webRequest.Headers.Connection.Add(header.Value);
break; break;
case "Content-Length": case "Content-Length":
webRequest.ContentLength = Convert.ToInt64(header.Value); AddContentHeader(webRequest, "Content-Length", header.Value);
break; break;
case "Content-Type": case "Content-Type":
webRequest.ContentType = header.Value; AddContentHeader(webRequest, "Content-Type", header.Value);
break; break;
case "Date": case "Date":
webRequest.Date = HttpHeader.ParseDateTime(header.Value); webRequest.Headers.Remove("Date");
webRequest.Headers.Date = HttpHeader.ParseDateTime(header.Value);
break; break;
case "Expect": case "Expect":
webRequest.Expect = header.Value; webRequest.Headers.Expect.ParseAdd(header.Value);
break; break;
case "Host": case "Host":
webRequest.Host = header.Value; webRequest.Headers.Host = header.Value;
break; break;
case "If-Modified-Since": case "If-Modified-Since":
webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value); webRequest.Headers.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
break; break;
case "Range": case "Range":
throw new NotImplementedException(); throw new NotImplementedException();
case "Referer": case "Referer":
webRequest.Referer = header.Value; webRequest.Headers.Add("Referer", header.Value);
break; break;
case "Transfer-Encoding": case "Transfer-Encoding":
webRequest.TransferEncoding = header.Value; webRequest.Headers.TransferEncoding.ParseAdd(header.Value);
break; break;
case "User-Agent": case "User-Agent":
throw new NotSupportedException("User-Agent other than Radarr not allowed."); throw new NotSupportedException("User-Agent other than Radarr not allowed.");
@@ -219,5 +191,79 @@ namespace NzbDrone.Common.Http.Dispatchers
} }
} }
} }
private void AddContentHeader(HttpRequestMessage request, string header, string value)
{
var headers = request.Content?.Headers;
if (headers == null)
{
return;
}
headers.Remove(header);
headers.Add(header, value);
}
private static 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.
if (useIPv6)
{
try
{
var localToken = cancellationToken;
if (!hasResolvedIPv6Availability)
{
// to make things move fast, use a very low timeout for the initial ipv6 attempt.
var quickFailCts = new CancellationTokenSource(connection_establish_timeout);
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token);
localToken = linkedTokenSource.Token;
}
return await attemptConnection(AddressFamily.InterNetworkV6, context, localToken);
}
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;
}
finally
{
hasResolvedIPv6Availability = true;
}
}
// fallback to IPv4.
return await attemptConnection(AddressFamily.InterNetwork, context, cancellationToken);
}
private static async ValueTask<Stream> attemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
{
// Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
NoDelay = true
};
try
{
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
// The stream should take the ownership of the underlying socket,
// closing it when it's disposed.
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
} }
} }
-15
View File
@@ -1,15 +0,0 @@
using System;
using System.Net;
namespace NzbDrone.Common.Http
{
public class GZipWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.AutomaticDecompression = DecompressionMethods.GZip;
return request;
}
}
}
+71 -30
View File
@@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@@ -119,8 +121,6 @@ namespace NzbDrone.Common.Http
var stopWatch = Stopwatch.StartNew(); var stopWatch = Stopwatch.StartNew();
PrepareRequestCookies(request, cookieContainer);
var response = _httpDispatcher.GetResponse(request, cookieContainer); var response = _httpDispatcher.GetResponse(request, cookieContainer);
HandleResponseCookies(response, cookieContainer); HandleResponseCookies(response, cookieContainer);
@@ -187,57 +187,98 @@ namespace NzbDrone.Common.Http
} }
} }
private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieContainer) private void HandleResponseCookies(HttpResponse response, CookieContainer container)
{ {
// Don't collect persistnet cookies for intermediate/redirected urls. foreach (Cookie cookie in container.GetAllCookies())
/*lock (_cookieContainerCache)
{ {
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer()); cookie.Expired = true;
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url); }
var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
cookieContainer.Add(persistentCookies);
cookieContainer.Add(existingCookies);
}*/
}
private void HandleResponseCookies(HttpResponse response, CookieContainer cookieContainer)
{
var cookieHeaders = response.GetCookieHeaders(); var cookieHeaders = response.GetCookieHeaders();
if (cookieHeaders.Empty()) if (cookieHeaders.Empty())
{ {
return; return;
} }
AddCookiesToContainer(response.Request.Url, cookieHeaders, container);
if (response.Request.StoreResponseCookie) if (response.Request.StoreResponseCookie)
{ {
lock (_cookieContainerCache) lock (_cookieContainerCache)
{ {
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer()); var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
foreach (var cookieHeader in cookieHeaders) AddCookiesToContainer(response.Request.Url, cookieHeaders, persistentCookieContainer);
{ }
try }
{ }
persistentCookieContainer.SetCookies((Uri)response.Request.Url, cookieHeader);
} private void AddCookiesToContainer(HttpUri url, string[] cookieHeaders, CookieContainer container)
catch (Exception ex) {
{ foreach (var cookieHeader in cookieHeaders)
_logger.Debug(ex, "Invalid cookie in {0}", response.Request.Url); {
} try
} {
container.SetCookies((Uri)url, cookieHeader);
}
catch (Exception ex)
{
_logger.Debug(ex, "Invalid cookie in {0}", url);
} }
} }
} }
public void DownloadFile(string url, string fileName) public void DownloadFile(string url, string fileName)
{ {
_httpDispatcher.DownloadFile(url, fileName); var fileNamePart = fileName + ".part";
try
{
var fileInfo = new FileInfo(fileName);
if (fileInfo.Directory != null && !fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
}
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
var stopWatch = Stopwatch.StartNew();
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
{
var request = new HttpRequest(url);
request.AllowAutoRedirect = true;
request.ResponseStream = fileStream;
var response = Get(request);
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
{
throw new HttpException(request, response, "Site responded with html content.");
}
}
stopWatch.Stop();
if (File.Exists(fileName))
{
File.Delete(fileName);
}
File.Move(fileNamePart, fileName);
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
}
finally
{
if (File.Exists(fileNamePart))
{
File.Delete(fileNamePart);
}
}
} }
public HttpResponse Get(HttpRequest request) public HttpResponse Get(HttpRequest request)
{ {
request.Method = HttpMethod.GET; request.Method = HttpMethod.Get;
return Execute(request); return Execute(request);
} }
@@ -251,13 +292,13 @@ namespace NzbDrone.Common.Http
public HttpResponse Head(HttpRequest request) public HttpResponse Head(HttpRequest request)
{ {
request.Method = HttpMethod.HEAD; request.Method = HttpMethod.Head;
return Execute(request); return Execute(request);
} }
public HttpResponse Post(HttpRequest request) public HttpResponse Post(HttpRequest request)
{ {
request.Method = HttpMethod.POST; request.Method = HttpMethod.Post;
return Execute(request); return Execute(request);
} }
+22 -1
View File
@@ -1,14 +1,30 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Text; using System.Text;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http namespace NzbDrone.Common.Http
{ {
public static class WebHeaderCollectionExtensions
{
public static NameValueCollection ToNameValueCollection(this HttpHeaders headers)
{
var result = new NameValueCollection();
foreach (var header in headers)
{
result.Add(header.Key, header.Value.ConcatToString(";"));
}
return result;
}
}
public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable
{ {
public HttpHeader(NameValueCollection headers) public HttpHeader(NameValueCollection headers)
@@ -16,6 +32,11 @@ namespace NzbDrone.Common.Http
{ {
} }
public HttpHeader(HttpHeaders headers)
: base(headers.ToNameValueCollection())
{
}
public HttpHeader() public HttpHeader()
{ {
} }
-14
View File
@@ -1,14 +0,0 @@
namespace NzbDrone.Common.Http
{
public enum HttpMethod
{
GET,
POST,
PUT,
DELETE,
HEAD,
OPTIONS,
PATCH,
MERGE
}
}
+4
View File
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -11,6 +13,7 @@ namespace NzbDrone.Common.Http
{ {
public HttpRequest(string url, HttpAccept httpAccept = null) public HttpRequest(string url, HttpAccept httpAccept = null)
{ {
Method = HttpMethod.Get;
Url = new HttpUri(url); Url = new HttpUri(url);
Headers = new HttpHeader(); Headers = new HttpHeader();
AllowAutoRedirect = true; AllowAutoRedirect = true;
@@ -49,6 +52,7 @@ namespace NzbDrone.Common.Http
public TimeSpan RequestTimeout { get; set; } public TimeSpan RequestTimeout { get; set; }
public TimeSpan RateLimit { get; set; } public TimeSpan RateLimit { get; set; }
public string RateLimitKey { get; set; } public string RateLimitKey { get; set; }
public Stream ResponseStream { get; set; }
public override string ToString() public override string ToString()
{ {
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -35,7 +36,7 @@ namespace NzbDrone.Common.Http
{ {
BaseUrl = new HttpUri(baseUrl); BaseUrl = new HttpUri(baseUrl);
ResourceUrl = string.Empty; ResourceUrl = string.Empty;
Method = HttpMethod.GET; Method = HttpMethod.Get;
QueryParams = new List<KeyValuePair<string, string>>(); QueryParams = new List<KeyValuePair<string, string>>();
SuffixQueryParams = new List<KeyValuePair<string, string>>(); SuffixQueryParams = new List<KeyValuePair<string, string>>();
Segments = new Dictionary<string, string>(); Segments = new Dictionary<string, string>();
@@ -271,7 +272,7 @@ namespace NzbDrone.Common.Http
public virtual HttpRequestBuilder Post() public virtual HttpRequestBuilder Post()
{ {
Method = HttpMethod.POST; Method = HttpMethod.Post;
return this; return this;
} }
@@ -362,7 +363,7 @@ namespace NzbDrone.Common.Http
public virtual HttpRequestBuilder AddFormParameter(string key, object value) public virtual HttpRequestBuilder AddFormParameter(string key, object value)
{ {
if (Method != HttpMethod.POST) if (Method != HttpMethod.Post)
{ {
throw new NotSupportedException("HttpRequest Method must be POST to add FormParameter."); throw new NotSupportedException("HttpRequest Method must be POST to add FormParameter.");
} }
@@ -378,7 +379,7 @@ namespace NzbDrone.Common.Http
public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, byte[] data, string contentType = "application/octet-stream") public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, byte[] data, string contentType = "application/octet-stream")
{ {
if (Method != HttpMethod.POST) if (Method != HttpMethod.Post)
{ {
throw new NotSupportedException("HttpRequest Method must be POST to add FormUpload."); throw new NotSupportedException("HttpRequest Method must be POST to add FormUpload.");
} }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
@@ -17,14 +18,14 @@ namespace NzbDrone.Common.Http
public JsonRpcRequestBuilder(string baseUrl) public JsonRpcRequestBuilder(string baseUrl)
: base(baseUrl) : base(baseUrl)
{ {
Method = HttpMethod.POST; Method = HttpMethod.Post;
JsonParameters = new List<object>(); JsonParameters = new List<object>();
} }
public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable<object> parameters) public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable<object> parameters)
: base(baseUrl) : base(baseUrl)
{ {
Method = HttpMethod.POST; Method = HttpMethod.Post;
JsonMethod = method; JsonMethod = method;
JsonParameters = parameters.ToList(); JsonParameters = parameters.ToList();
} }
@@ -1,9 +1,5 @@
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets;
using com.LandonKey.SocksWebProxy;
using com.LandonKey.SocksWebProxy.Proxy;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -33,54 +29,37 @@ namespace NzbDrone.Common.Http.Proxy
} }
private IWebProxy CreateWebProxy(HttpProxySettings proxySettings) private IWebProxy CreateWebProxy(HttpProxySettings proxySettings)
{
var uri = GetProxyUri(proxySettings);
if (uri == null)
{
return null;
}
if (proxySettings.Username.IsNotNullOrWhiteSpace() && proxySettings.Password.IsNotNullOrWhiteSpace())
{
return new WebProxy(uri, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password));
}
else
{
return new WebProxy(uri, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray);
}
}
private Uri GetProxyUri(HttpProxySettings proxySettings)
{ {
switch (proxySettings.Type) switch (proxySettings.Type)
{ {
case ProxyType.Http: case ProxyType.Http:
if (proxySettings.Username.IsNotNullOrWhiteSpace() && proxySettings.Password.IsNotNullOrWhiteSpace()) return new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port);
{
return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray, new NetworkCredential(proxySettings.Username, proxySettings.Password));
}
else
{
return new WebProxy(proxySettings.Host + ":" + proxySettings.Port, proxySettings.BypassLocalAddress, proxySettings.BypassListAsArray);
}
case ProxyType.Socks4: case ProxyType.Socks4:
return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Four, proxySettings.Username, proxySettings.Password), false); return new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port);
case ProxyType.Socks5: case ProxyType.Socks5:
return new SocksWebProxy(new ProxyConfig(IPAddress.Loopback, GetNextFreePort(), GetProxyIpAddress(proxySettings.Host), proxySettings.Port, ProxyConfig.SocksVersion.Five, proxySettings.Username, proxySettings.Password), false); return new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port);
default:
return null;
} }
return null;
}
private static IPAddress GetProxyIpAddress(string host)
{
IPAddress ipAddress;
if (!IPAddress.TryParse(host, out ipAddress))
{
try
{
ipAddress = Dns.GetHostEntry(host).AddressList.OrderByDescending(a => a.AddressFamily == AddressFamily.InterNetwork).First();
}
catch (Exception e)
{
throw new InvalidOperationException(string.Format("Unable to resolve proxy hostname '{0}' to a valid IP address.", host), e);
}
}
return ipAddress;
}
private static int GetNextFreePort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
} }
} }
} }
@@ -11,7 +11,7 @@ namespace NzbDrone.Common.Instrumentation
private static readonly Regex[] CleansingRules = new[] private static readonly Regex[] CleansingRules = new[]
{ {
// Url // Url
new Regex(@"(?<=\?|&|: )(apikey|token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
+12 -12
View File
@@ -1,24 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants> <DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.7.4" /> <PackageReference Include="DryIoc.dll" Version="4.8.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.11" /> <PackageReference Include="NLog" Version="4.7.12" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" /> <PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="3.9.3" /> <PackageReference Include="Sentry" Version="3.11.1" />
<PackageReference Include="SharpZipLib" Version="1.2.0" /> <PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="5.0.2" /> <PackageReference Include="System.Text.Json" Version="6.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" /> <PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" /> <PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+1 -2
View File
@@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net5.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
<ApplicationIcon>..\NzbDrone.Host\Radarr.ico</ApplicationIcon> <ApplicationIcon>..\NzbDrone.Host\Radarr.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="!$(RuntimeIdentifier.StartsWith('win'))"> <PropertyGroup Condition="!$(RuntimeIdentifier.StartsWith('win'))">
<AssemblyName>Radarr</AssemblyName> <AssemblyName>Radarr</AssemblyName>
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.net>
<connectionManagement>
<add address="*" maxconnection="100" />
</connectionManagement>
</system.net>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
<runtime>
<loadFromRemoteSources enabled="true" />
</runtime>
</configuration>
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>

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