1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-09 15:01:39 -04:00

Compare commits

...

109 Commits

Author SHA1 Message Date
ta264
e7a8f6332c Fixed: Handle missing category when getting Qbittorrent download path
Fixes RADARR-7HC
Fixes RADARR-V49

(cherry picked from commit 6f97ca9a55471386454457ca52b93733e18e85e4)
Closes #6993
2022-01-25 20:10:52 -06:00
Qstick
b8c92d23f4 Fixed: Ignore case for DIVX VideoCodecID 2022-01-25 19:01:01 -06:00
bakerboy448
093e076db0 Fixed: Clarify Indexer Priority Helptext 2022-01-24 19:35:38 -06:00
Volodymyr Medvid
f6f949415c Fix Ukrainian language mappings 2022-01-24 19:23:12 -06:00
Qstick
ea2576a56c Bump to 4.0.4 2022-01-23 19:03:07 -06:00
Qstick
595acb696d Fixed: Bump FFMpegCore to avoid DivideByZero error 2022-01-23 18:24:28 -06:00
Qstick
38c9534eac Fixed: Webhook fails due to Frames/Analysis property serialization on MediaInfo 2022-01-23 18:24:28 -06:00
bakerboy448
9377ef7942 Fixed: Improved Indexer test failure message when no results are returned
(cherry picked from commit 05b1581b7dfa5bc8a2b38a60179e3472c8134f74)
2022-01-23 16:18:37 -06:00
bakerboy448
c2e5686bcf fixed sonarr ref in UTorrentSettings 2022-01-23 14:44:31 -06:00
Qstick
f08807daf6 Update donation links [common] 2022-01-23 12:12:16 -06:00
bakerboy448
72b3caa72d Fixed: Various Translations 2022-01-21 18:52:46 -06:00
Qstick
589368781b Fixed Incorrect placeholder width on search page
Co-Authored-By: nitsua <8321115+austinwbest@users.noreply.github.com>
2022-01-20 21:12:30 -06:00
Qstick
8fd6101121 New: Add AppName to system status response
Fixes #6952
2022-01-18 23:23:41 -06:00
Mark McDowall
ac9d6cbf0a Fixed: Jump bar on series page not showing when window is made wider
(cherry picked from commit 0cb8d93069d6310abd39ee2fe73219e17aa83fe6)
2022-01-17 11:30:47 -06:00
Qstick
6e0ed36e9f Fixed: Correct queue color for status bars 2022-01-17 08:58:07 -06:00
Qstick
fcb65055ef Bump to 4.0.3 2022-01-15 16:24:50 -06:00
bakerboy448
90456bbfed fixup 2022-01-15 14:51:11 -06:00
bakerboy448
2a74b7b2e1 Fixed: Parse HD.DVD as BluRay
Fixes #6925
2022-01-15 14:51:11 -06:00
Qstick
fc08c39fb8 Fixed: Revert manual import augmentation for unknown items 2022-01-15 14:24:51 -06:00
PearsonFlyer
76d65bf990 Fixed: Translation warning for search all 2022-01-15 07:27:58 -06:00
ta264
de243991dd Support for digest auth with HttpRequests
(cherry picked from commit 1e2d931f9a)
2022-01-13 19:23:30 -06:00
Qstick
4d1f251c1f Bump version 4.0.2 2022-01-10 19:59:16 -06:00
Qstick
ebb1e3131a Fixed: Use general settings cert validation for email 2022-01-10 09:17:54 -05:00
Qstick
6e502d63c2 Fixed: Handle wmapro and wmv3 codecs in formatter
Fixes RADARR-1G5R
Fixes RADARR-1G7V
2022-01-08 13:50:03 -06:00
Qstick
57e05b70da Remove unused variable from MovieHistoryRow 2022-01-08 01:09:02 -06:00
Qstick
59186adbfc Fixed: Mark as Failed from Movie Details page
Fixes #6483
2022-01-08 00:54:27 -06:00
Qstick
bc20e159ba Bump dotnet to 6.0.1
Security patch release
2022-01-07 21:33:48 -06:00
ta264
39b99341cd Fixed: Use our own HttpClient for Aria2 requests
[common]
2022-01-07 21:22:55 -06:00
ta264
b626c5bbf0 Fixed: Use our own HttpClient for rTorrent RPC requests
[common]
2022-01-07 21:22:55 -06:00
Robin Dadswell
a33b861cec Fixed: Download Client not sending on Import or Upgrade notifications (#6908)
* Fixed: Download client and ID for custom scripts

Based on Sonarr Commit eea3419849

* fixup! test

Co-authored-by: Qstick <qstick@gmail.com>
2022-01-06 22:04:10 -06:00
Qstick
3a48f07702 Fixed: Twitter connect not sending messages after http rework (#6901) [common] 2022-01-06 21:42:32 -06:00
Weblate
1aec0b7ee5 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 99.9% (1106 of 1107 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 99.0% (1096 of 1107 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.5% (1102 of 1107 strings)

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

Currently translated at 99.8% (1105 of 1107 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 99.9% (1106 of 1107 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.2% (1077 of 1107 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.4% (1097 of 1103 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1103 of 1103 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.6% (1088 of 1103 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Gian Klug <gian.klug@ict-scouts.ch>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Math <thimath62@live.fr>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: diemade <spamkill@posteo.ch>
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/fr/
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/ru/
Translation: Servarr/Radarr
2022-01-06 21:32:57 -06:00
nitsua
3e32161791 Fix broken headers on List Exclusion table 2022-01-05 20:03:50 -06:00
Qstick
fda1ad237b Fixed: Map DV Blu-ray to HDR10 compatibility 2022-01-04 23:46:36 -06:00
bakerboy448
52b6f39026 Fixed: Update indexer flag help link 2022-01-04 21:53:40 -06:00
Qstick
100fd95dd9 Fixed: Fix bad ratings objects in Migration 206 2022-01-03 20:20:16 -06:00
Mehul Vaghani
d571c7b75a Fixed: Updated link to Indexer Flags (#6893)
* Fixed: Updated link to Indexer Flags

* Fix: Updated link to Indexer Flags documentation

* Fix: Updated link to reflect right section
2022-01-03 18:15:32 -06:00
Qstick
8d7f48739b Bump version to 4.0.1 2022-01-03 14:20:47 -06:00
Mark McDowall
c061d7cec8 Fixed: Report certificate validation failures when configuring Plex Media Server connection
Closes #6797

(cherry picked from commit ec62884649f7af5f0a29346741754590e6de99ce)
2022-01-02 18:56:33 -06:00
Mark McDowall
91691205db Fixed: Download client name in history details
Closes #6838
2022-01-02 18:54:24 -06:00
Qstick
c1e07b30d7 Bump DSN for 4.0.0 2022-01-02 18:46:22 -06:00
Qstick
78a7770858 Fix CSS classes for Imdb and Tmdb ratings 2022-01-01 22:12:10 -06:00
Qstick
599f4907f4 New: Imdb Ratings 2022-01-01 20:18:18 -06:00
Weblate
ec9a7f5c8e Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 99.9% (1101 of 1102 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 99.9% (1101 of 1102 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.0% (1070 of 1102 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.8% (1089 of 1102 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.8% (1089 of 1102 strings)

Update translation files  [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: François-Xavier Payet <fx.payet@tfdn.cloud>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: diemade <spamkill@posteo.ch>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2022-01-01 16:50:43 -06:00
Robin Dadswell
54c914d48f New: End Jackett 'all' endpoint support
[common]
2022-01-01 19:26:13 +00:00
Qstick
75270d8151 Fixed: Avoid ArgumentRangeException on FFProbe check when no sidedata frames
Fixes RADARR-1G5Q
2022-01-01 10:48:24 -06:00
Qstick
7a859f340b Fixed: Avoid download path check false positives for flood 2021-12-29 18:25:11 -06:00
bakerboy448
13e44ce19a New: Add {MediaInfo VideoDynamicRangeType} token for renaming
New: Detect HDR Type
New: Display HDR Type in File Media Info Modal

Based on Sonarr 7b694ea71d7f78bad5c03393c4cf6f7a28ada1cb

Closes #6789
Fixes #4844

Co-authored-by: ta264 <ta264@users.noreply.github.com>
Co-authored-by: Qstick <qstick@gmail.com>
2021-12-29 10:10:11 -06:00
Weblate
9e4c94592d Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translation: Servarr/Radarr
2021-12-28 17:06:21 -06:00
Weblate
9d2a59b7fd Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

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

Currently translated at 100.0% (1116 of 1116 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 99.9% (1115 of 1116 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>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
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-27 17:44:28 -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
275 changed files with 3739 additions and 1919 deletions

View File

@@ -7,13 +7,13 @@ variables:
outputFolder: './_output'
artifactsFolder: './_artifacts'
testsFolder: './_tests'
majorVersion: '4.0.0'
majorVersion: '4.0.4'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.100'
dotnetVersion: '6.0.101'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger:

View File

@@ -120,7 +120,7 @@ class Blocklist extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Remove Selected"
label={translate('RemoveSelected')}
iconName={icons.REMOVE}
isDisabled={!selectedIds.length}
isSpinning={isRemoving}

View File

@@ -25,6 +25,7 @@ function HistoryDetails(props) {
releaseGroup,
nzbInfoUrl,
downloadClient,
downloadClientName,
downloadId,
age,
ageHours,
@@ -32,6 +33,8 @@ function HistoryDetails(props) {
publishedDate
} = data;
const downloadClientNameInfo = downloadClientName ?? downloadClient;
return (
<DescriptionList>
<DescriptionListItem
@@ -71,11 +74,12 @@ function HistoryDetails(props) {
}
{
!!downloadClient &&
downloadClientNameInfo ?
<DescriptionListItem
title={translate('DownloadClient')}
data={downloadClient}
/>
data={downloadClientNameInfo}
/> :
null
}
{

View File

@@ -161,7 +161,7 @@ class AddNewMovie extends Component {
{translate('YouCanAlsoSearch')}
</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')}
</Link>
</div>

View File

@@ -86,6 +86,13 @@ class AddNewMovieSearchResult extends Component {
} = this.state;
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
const posterWidth = 167;
const posterHeight = 250;
const elementStyle = {
width: `${posterWidth}px`,
height: `${posterHeight}px`
};
return (
<div className={styles.searchResult}>
@@ -102,6 +109,7 @@ class AddNewMovieSearchResult extends Component {
<div className={styles.posterContainer}>
<MoviePoster
className={styles.poster}
style={elementStyle}
images={images}
size={250}
overflow={true}
@@ -114,7 +122,7 @@ class AddNewMovieSearchResult extends Component {
monitored={monitored}
hasFile={hasFile}
status={status}
posterWidth={167}
posterWidth={posterWidth}
detailedProgressBar={true}
queueStatus={queueStatus}
queueState={queueState}
@@ -183,7 +191,7 @@ class AddNewMovieSearchResult extends Component {
<div>
<Label size={sizes.LARGE}>
<HeartRating
rating={ratings.value}
ratings={ratings}
iconSize={13}
/>
</Label>

View File

@@ -43,15 +43,15 @@ class CalendarEvent extends Component {
const link = `/movie/${titleSlug}`;
const eventType = [];
if (moment(date).isSame(moment(inCinemas), 'day')) {
if (inCinemas && moment(date).isSame(moment(inCinemas), 'day')) {
eventType.push('Cinemas');
}
if (moment(date).isSame(moment(physicalRelease), 'day')) {
if (physicalRelease && moment(date).isSame(moment(physicalRelease), 'day')) {
eventType.push('Physical');
}
if (moment(date).isSame(moment(digitalRelease), 'day')) {
if (digitalRelease && moment(date).isSame(moment(digitalRelease), 'day')) {
eventType.push('Digital');
}

View File

@@ -166,7 +166,9 @@ class FilterBuilderModalContent extends Component {
</div>
</div>
<div className={styles.label}>Filters</div>
<div className={styles.label}>
{translate('Filters')}
</div>
<div className={styles.rows}>
{

View File

@@ -6,8 +6,7 @@ import SelectInput from './SelectInput';
const availabilityOptions = [
{ key: 'announced', value: translate('Announced') },
{ key: 'inCinemas', value: translate('InCinemas') },
{ key: 'released', value: translate('Released') },
{ key: 'preDB', value: translate('PreDB') }
{ key: 'released', value: translate('Released') }
];
function AvailabilitySelectInput(props) {

View File

@@ -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);

View File

@@ -8,6 +8,7 @@ import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector';
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
@@ -73,6 +74,9 @@ function getComponent(type) {
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInputConnector;

View File

@@ -1,4 +1,5 @@
.heart {
.image {
align-content: center;
margin-right: 5px;
color: $themeRed;
vertical-align: -0.125em;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

File diff suppressed because one or more lines are too long

View File

@@ -81,7 +81,7 @@ class PageHeader extends Component {
<IconButton
className={styles.donate}
name={icons.HEART}
to="https://opencollective.com/radarr"
to="https://radarr.video/donate"
size={14}
/>
<IconButton

View File

@@ -100,7 +100,9 @@ class PageJumpBar extends Component {
// Listeners
onMeasure = ({ height }) => {
this.setState({ height });
if (height > 0) {
this.setState({ height });
}
}
//

View File

@@ -0,0 +1,5 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

View File

@@ -0,0 +1,58 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './RottenTomatoRating.css';
class RottenTomatoRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const rtRotten = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQ1LjE4NSA0NDQuNjg0Yy03OS4zNjkgNC4xNjctOTUuNTg3LTg2LjY1Mi0xMjYuNzI2LTg2LjAwNi0xMy4yNjguMjc5LTIzLjcyNiAxNC4xNTEtMTkuMTMzIDMwLjMyIDIuNTI1IDguODg4IDkuNTMgMjEuOTIzIDEzLjk0NCAzMC4wMTEgMTUuNTcgMjguNTQ0LTcuNDQ3IDYwLjg0NS0zNC4zODMgNjMuNTc3LTQ0Ljc2IDQuNTQtNjMuNDMzLTIxLjQyNi02Mi4yNzgtNDguMDA3IDEuMy0yOS44NCAyNi42LTYwLjMzMS42NS03My4zMDUtMjcuMTk0LTEzLjU5Ny00OS4zMDEgMzkuNTcyLTc1LjMyNSA1MS40MzktMjMuNTUzIDEwLjc0MS01Ni4yNDggMi40MTMtNjcuODcyLTIzLjc0MS04LjE2NC0xOC4zNzktNi42OC01My43NjggMjkuNjctNjcuMjcgMjIuNzA2LTguNDMzIDczLjMwNSAxMS4wMjkgNzUuOS0xMy42MjMgMi45OTItMjguNDE2LTUzLjE1NS0zMC44MTItNzAuMDYtMzcuNjI2LTI5LjkxMi0xMi4wNTUtNDcuNTY3LTM3Ljg1LTMzLjczNC02NS41MjIgMTAuMzc4LTIwLjc1NyA0MC45MTUtMjkuMjAzIDY0LjIyMy0yMC4xMSAyNy45MjIgMTAuODkyIDMyLjQwNCAzOS44NTMgNDYuNzEgNTEuODk3IDEyLjMyNCAxMC4zOCAyOS4xOSAxMS42OCA0MC4yMiA0LjU0MyA4LjEzNS01LjI2NSAxMC44NDMtMTYuODI4IDcuNzc0LTI3LjM5LTQuMDctMTQuMDIzLTE0Ljg3NS0yMi43NzMtMjUuNDE1LTMxLjM0Ni0xOC43NTgtMTUuMjQ5LTQ1LjI0LTI4LjM2LTI5LjIyMi02OS45ODMgMTMuMTMtMzQuMTEgNTEuNjQyLTM1LjM0IDUxLjY0Mi0zNS4zNCAxNS4zLTEuNzIgMjkuMDAyIDIuOSA0MC4xNjcgMTIuODc1IDE0LjkyNyAxMy4zMzUgMTcuODM0IDMxLjE2IDE1LjMzNiA1MC4xNzYtMi4yODMgMTcuMzU4LTguNDI2IDMyLjU2LTExLjYzIDQ5Ljc1OS0zLjcxNyAxOS45NjYgNi45NTQgNDAuMDg2IDI3LjI0OSA0MC44NjkgMjYuNjk0IDEuMDMxIDM0LjY5OC0xOS40ODYgMzcuOTY0LTMyLjQ5MiA0Ljc4Mi0xOS4wMjggMTEuMDU4LTM2LjY5NCAyOC43MTgtNDcuODIgMjUuMzQ2LTE1Ljk3IDYwLjU1Mi0xMi40NyA3Ni44ODYgMTguMjIyIDEyLjkyIDI0LjI4NCA4Ljc3MiA1Ny43MTUtMTEuMDQ3IDc1Ljk3LTguODkyIDguMTg4LTE5LjU4NCAxMS4wNzUtMzEuMTQ4IDExLjE1Ni0xNi41ODUuMTE3LTMzLjE2Mi0uMjktNDguNTU2IDcuNDcxLTEwLjQ4IDUuMjgxLTE1LjA0NyAxMy44ODgtMTUuMDQ1IDI1LjQyMyAwIDExLjI0MiA1Ljg1MyAxOC41ODUgMTUuMzM2IDIzLjM2MyAxNy44NiA5LjAwMyAzNy41NzcgMTAuODQzIDU2Ljg3MSAxNC4yMjIgMjcuOTggNC45IDUyLjU4MSAxNC43NTUgNjguMzc1IDQwLjcyLjE0Mi4yMjguMjguNDU4LjQxNS42OSAxOC4xMzkgMzAuNzQxLS44MzEgNzUuMDA1LTM2LjQ3NiA3Ni44NzgiIGZpbGw9IiMwQUM4NTUiLz48L3N2Zz4=';
const rtFresh = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNDc4LjI5IDI5Ni45OGMtMy45OS02My45NjYtMzYuNTItMTExLjgyLTg1LjQ2OC0xMzguNTggMC4yNzggMS41Ni0xLjEwOSAzLjUwOC0yLjY4OCAyLjgxOC0zMi4wMTYtMTQuMDA2LTg2LjMyOCAzMS4zMi0xMjQuMjggNy41ODQgMC4yODUgOC41MTktMS4zNzggNTAuMDcyLTU5LjkxNCA1Mi40ODMtMS4zODIgMC4wNTYtMi4xNDItMS4zNTUtMS4yNjgtMi4zNTQgNy44MjgtOC45MjkgMTUuNzMyLTMxLjUzNSA0LjM2Ny00My41ODYtMjQuMzM4IDIxLjgxLTM4LjQ3MiAzMC4wMTctODUuMTM4IDE5LjE4Ni0yOS44NzggMzEuMjQxLTQ2LjgwOSA3NC00My40ODUgMTI3LjI2IDYuNzggMTA4Ljc0IDEwOC42MyAxNzAuODkgMjExLjE5IDE2NC40OSAxMDIuNTYtNi4zOTUgMTkzLjQ3LTgwLjU3MiAxODYuNjgtMTg5LjMxIiBmaWxsPSIjRkEzMjBBIi8+PHBhdGggZD0iTTI5MS4zNzUgMTMyLjI5M2MyMS4wNzUtNS4wMjMgODEuNjkzLS40OSAxMDEuMTE0IDI1LjI3NCAxLjE2NiAxLjU0NS0uNDc1IDQuNDY4LTIuMzU1IDMuNjQ4LTMyLjAxNi0xNC4wMDYtODYuMzI4IDMxLjMyLTEyNC4yODIgNy41ODQuMjg1IDguNTE5LTEuMzc4IDUwLjA3Mi01OS45MTQgNTIuNDgzLTEuMzgyLjA1Ni0yLjE0Mi0xLjM1NS0xLjI2OC0yLjM1NCA3LjgyOC04LjkyOSAxNS43My0zMS41MzUgNC4zNjctNDMuNTg2LTI2LjUxMiAyMy43NTgtNDAuODg0IDMxLjM5Mi05OC40MjYgMTUuODM4LTEuODgzLS41MDgtMS4yNDEtMy41MzUuNzYyLTQuMjk4IDEwLjg3Ni00LjE1NyAzNS41MTUtMjIuMzYxIDU4LjgyNC0zMC4zODUgNC40MzgtMS41MjYgOC44NjItMi43MSAxMy4xOC0zLjQtMjUuNjY1LTIuMjkzLTM3LjIzNS01Ljg2Mi01My41NTktMy40LTEuNzg5LjI3LTMuMDA0LTEuODEzLTEuODk1LTMuMjQxIDIxLjk5NS0yOC4zMzIgNjIuNTEzLTM2Ljg4OCA4Ny41MTItMjEuODM3LTE1LjQxLTE5LjA5NC0yNy40OC0zNC4zMjEtMjcuNDgtMzQuMzIxbDI4LjYwMS0xNi4yNDZzMTEuODE3IDI2LjQgMjAuNDE0IDQ1LjYxNGMyMS4yNzUtMzEuNDM1IDYwLjg2LTM0LjMzNiA3Ny41ODUtMTIuMDMzLjk5MiAxLjMyNi0uMDQ1IDMuMjEtMS43MDIgMy4xNzEtMTMuNjEyLS4zMzEtMjEuMTA3IDEyLjA1LTIxLjY3NSAyMS40NjZsLjE5Ny4wMjMiIGZpbGw9IiMwMDkxMkQiLz48L3N2Zz4=';
const rating = ratings.rottenTomatoes;
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value}%`;
}
return (
<span>
{
!hideIcon &&
<img
className={styles.image}
src={rating.value > 50 ? rtFresh : rtRotten}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
RottenTomatoRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
RottenTomatoRating.defaultProps = {
iconSize: 14
};
export default RottenTomatoRating;

View File

@@ -0,0 +1,5 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

View File

@@ -0,0 +1,57 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './TmdbRating.css';
class TmdbRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const tmdbImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTAuMjQgODEuNTIiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeTE9IjQwLjc2IiB4Mj0iMTkwLjI0IiB5Mj0iNDAuNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNlYTEiLz48c3RvcCBvZmZzZXQ9Ii41NiIgc3RvcC1jb2xvcj0iIzNjYmVjOSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwYjNlNSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xMDUuNjcgMzYuMDZoNjYuOWExNy42NyAxNy42NyAwIDAwMTcuNjctMTcuNjZBMTcuNjcgMTcuNjcgMCAwMDE3Mi41Ny43M2gtNjYuOUExNy42NyAxNy42NyAwIDAwODggMTguNGExNy42NyAxNy42NyAwIDAwMTcuNjcgMTcuNjZ6bS04OCA0NWg3Ni45YTE3LjY3IDE3LjY3IDAgMDAxNy42Ny0xNy42NiAxNy42NyAxNy42NyAwIDAwLTE3LjY3LTE3LjY3aC03Ni45QTE3LjY3IDE3LjY3IDAgMDAwIDYzLjRhMTcuNjcgMTcuNjcgMCAwMDE3LjY3IDE3LjY2em0tNy4yNi00NS42NGg3LjhWNi45MmgxMC4xVjBoLTI4djYuOWgxMC4xem0yOC4xIDBoNy44VjguMjVoLjFsOSAyNy4xNWg2bDkuMy0yNy4xNWguMVYzNS40aDcuOFYwSDY2Ljc2bC04LjIgMjMuMWgtLjFMNTAuMzEgMGgtMTEuOHptMTEzLjkyIDIwLjI1YTE1LjA3IDE1LjA3IDAgMDAtNC41Mi01LjUyIDE4LjU3IDE4LjU3IDAgMDAtNi42OC0zLjA4IDMzLjU0IDMzLjU0IDAgMDAtOC4wNy0xaC0xMS43djM1LjRoMTIuNzVhMjQuNTggMjQuNTggMCAwMDcuNTUtMS4xNSAxOS4zNCAxOS4zNCAwIDAwNi4zNS0zLjMyIDE2LjI3IDE2LjI3IDAgMDA0LjM3LTUuNSAxNi45MSAxNi45MSAwIDAwMS42My03LjU4IDE4LjUgMTguNSAwIDAwLTEuNjgtOC4yNXpNMTQ1IDY4LjZhOC44IDguOCAwIDAxLTIuNjQgMy40IDEwLjcgMTAuNyAwIDAxLTQgMS44MiAyMS41NyAyMS41NyAwIDAxLTUgLjU1aC00LjA1di0yMWg0LjZhMTcgMTcgMCAwMTQuNjcuNjMgMTEuNjYgMTEuNjYgMCAwMTMuODggMS44N0E5LjE0IDkuMTQgMCAwMTE0NSA1OWE5Ljg3IDkuODcgMCAwMTEgNC41MiAxMS44OSAxMS44OSAwIDAxLTEgNS4wOHptNDQuNjMtLjEzYTggOCAwIDAwLTEuNTgtMi42MiA4LjM4IDguMzggMCAwMC0yLjQyLTEuODUgMTAuMzEgMTAuMzEgMCAwMC0zLjE3LTF2LS4xYTkuMjIgOS4yMiAwIDAwNC40Mi0yLjgyIDcuNDMgNy40MyAwIDAwMS42OC01IDguNDIgOC40MiAwIDAwLTEuMTUtNC42NSA4LjA5IDguMDkgMCAwMC0zLTIuNzIgMTIuNTYgMTIuNTYgMCAwMC00LjE4LTEuMyAzMi44NCAzMi44NCAwIDAwLTQuNjItLjMzaC0xMy4ydjM1LjRoMTQuNWEyMi40MSAyMi40MSAwIDAwNC43Mi0uNSAxMy41MyAxMy41MyAwIDAwNC4yOC0xLjY1IDkuNDIgOS40MiAwIDAwMy4xLTMgOC41MiA4LjUyIDAgMDAxLjItNC42OCA5LjM5IDkuMzkgMCAwMC0uNTUtMy4xOHptLTE5LjQyLTE1Ljc1aDUuM2ExMCAxMCAwIDAxMS44NS4xOCA2LjE4IDYuMTggMCAwMTEuNy41NyAzLjM5IDMuMzkgMCAwMTEuMjIgMS4xMyAzLjIyIDMuMjIgMCAwMS40OCAxLjgyIDMuNjMgMy42MyAwIDAxLS40MyAxLjggMy40IDMuNCAwIDAxLTEuMTIgMS4yIDQuOTIgNC45MiAwIDAxLTEuNTguNjUgNy41MSA3LjUxIDAgMDEtMS43Ny4yaC01LjY1em0xMS43MiAyMGEzLjkgMy45IDAgMDEtMS4yMiAxLjMgNC42NCA0LjY0IDAgMDEtMS42OC43IDguMTggOC4xOCAwIDAxLTEuODIuMmgtN3YtOGg1LjlhMTUuMzUgMTUuMzUgMCAwMTIgLjE1IDguNDcgOC40NyAwIDAxMi4wNS41NSA0IDQgMCAwMTEuNTcgMS4xOCAzLjExIDMuMTEgMCAwMS42MyAyIDMuNzEgMy43MSAwIDAxLS40MyAxLjkyeiIgZmlsbD0idXJsKCNhKSIvPjwvc3ZnPg==';
const rating = ratings.tmdb;
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value * 10}%`;
}
return (
<span title={`${rating.votes} votes`}>
{
!hideIcon &&
<img
className={styles.image}
src={tmdbImage}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
TmdbRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
TmdbRating.defaultProps = {
iconSize: 14
};
export default TmdbRating;

View File

@@ -73,7 +73,7 @@ function getInfoRowProps(row, props) {
return {
title: translate('Ratings'),
iconName: icons.HEART,
label: `${props.ratings.value * 10}%`
label: `${props.ratings.tmdb.value * 10}%`
};
}

View File

@@ -112,7 +112,7 @@ function DiscoverMoviePosterInfo(props) {
return (
<div className={styles.info}>
<HeartRating
rating={ratings.value}
ratings={ratings}
/>
</div>
);
@@ -129,7 +129,7 @@ DiscoverMoviePosterInfo.propTypes = {
digitalRelease: PropTypes.string,
physicalRelease: PropTypes.string,
runtime: PropTypes.number,
ratings: PropTypes.object,
ratings: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -246,7 +246,7 @@ class DiscoverMovieRow extends Component {
className={styles[name]}
>
<HeartRating
rating={ratings.value}
ratings={ratings}
/>
</VirtualTableRowCell>
);
@@ -373,7 +373,7 @@ DiscoverMovieRow.propTypes = {
digitalRelease: PropTypes.string,
runtime: PropTypes.number,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
ratings: PropTypes.object.isRequired,
ratings: PropTypes.arrayOf(PropTypes.object).isRequired,
certification: PropTypes.string,
collection: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -13,6 +13,7 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
@@ -35,6 +36,7 @@ export const all = [
PASSWORD,
PATH,
QUALITY_PROFILE_SELECT,
DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT,

View File

@@ -19,6 +19,7 @@ import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
@@ -40,6 +41,11 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{
name: 'quality',
label: translate('Quality'),
@@ -83,6 +89,7 @@ const SELECT = 'select';
const MOVIE = 'movie';
const LANGUAGE = 'language';
const QUALITY = 'quality';
const RELEASE_GROUP = 'releaseGroup';
class InteractiveImportModalContent extends Component {
@@ -202,10 +209,11 @@ class InteractiveImportModalContent extends Component {
const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems'));
const bulkSelectOptions = [
{
key: SELECT, value: translate('SelectDotDot'), disabled: true },
{ key: SELECT, value: translate('SelectDotDot'), disabled: true },
{ key: LANGUAGE, value: translate('SelectLanguage') },
{ key: QUALITY, value: translate('SelectQuality') }];
{ key: QUALITY, value: translate('SelectQuality') },
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
];
if (allowMovieChange) {
bulkSelectOptions.splice(1, 0, {
@@ -372,6 +380,13 @@ class InteractiveImportModalContent extends Component {
real={false}
onModalClose={this.onSelectModalClose}
/>
<SelectReleaseGroupModal
isOpen={selectModalOpen === RELEASE_GROUP}
ids={selectedIds}
releaseGroup=""
onModalClose={this.onSelectModalClose}
/>
</ModalContent>
);
}

View File

@@ -110,7 +110,8 @@ class InteractiveImportModalContentConnector extends Component {
const {
movie,
quality,
languages
languages,
releaseGroup
} = item;
if (!movie) {
@@ -132,6 +133,7 @@ class InteractiveImportModalContentConnector extends Component {
path: item.path,
folderName: item.folderName,
movieId: movie.id,
releaseGroup,
quality,
languages,
downloadId: this.props.downloadId

View File

@@ -11,6 +11,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import formatBytes from 'Utilities/Number/formatBytes';
@@ -28,6 +29,7 @@ class InteractiveImportRow extends Component {
this.state = {
isSelectMovieModalOpen: false,
isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false,
isSelectLanguageModalOpen: false
};
@@ -103,6 +105,10 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectMovieModalOpen: true });
}
onSelectReleaseGroupPress = () => {
this.setState({ isSelectReleaseGroupModalOpen: true });
}
onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true });
}
@@ -116,6 +122,11 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed);
}
onSelectReleaseGroupModalClose = (changed) => {
this.setState({ isSelectReleaseGroupModalOpen: false });
this.selectRowAfterChange(changed);
}
onSelectQualityModalClose = (changed) => {
this.setState({ isSelectQualityModalOpen: false });
this.selectRowAfterChange(changed);
@@ -137,6 +148,7 @@ class InteractiveImportRow extends Component {
movie,
quality,
languages,
releaseGroup,
size,
rejections,
isReprocessing,
@@ -147,7 +159,8 @@ class InteractiveImportRow extends Component {
const {
isSelectMovieModalOpen,
isSelectQualityModalOpen,
isSelectLanguageModalOpen
isSelectLanguageModalOpen,
isSelectReleaseGroupModalOpen
} = this.state;
const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : '';
@@ -155,6 +168,7 @@ class InteractiveImportRow extends Component {
const showMoviePlaceholder = isSelected && !movie;
const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !languages && !isReprocessing;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
return (
<TableRow>
@@ -181,6 +195,17 @@ class InteractiveImportRow extends Component {
}
</TableRowCellButton>
<TableRowCellButton
title={translate('ClickToChangeReleaseGroup')}
onPress={this.onSelectReleaseGroupPress}
>
{
showReleaseGroupPlaceholder ?
<InteractiveImportRowCellPlaceholder /> :
releaseGroup
}
</TableRowCellButton>
<TableRowCellButton
className={styles.quality}
title={translate('ClickToChangeQuality')}
@@ -268,6 +293,13 @@ class InteractiveImportRow extends Component {
onModalClose={this.onSelectMovieModalClose}
/>
<SelectReleaseGroupModal
isOpen={isSelectReleaseGroupModalOpen}
ids={[id]}
releaseGroup={releaseGroup ?? ''}
onModalClose={this.onSelectReleaseGroupModalClose}
/>
<SelectQualityModal
isOpen={isSelectQualityModalOpen}
ids={[id]}
@@ -296,6 +328,7 @@ InteractiveImportRow.propTypes = {
movie: PropTypes.object,
quality: PropTypes.object,
languages: PropTypes.arrayOf(PropTypes.object),
releaseGroup: PropTypes.string,
size: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
isReprocessing: PropTypes.bool,

View File

@@ -128,7 +128,7 @@ class SelectLanguageModalContent extends Component {
kind={kinds.SUCCESS}
onPress={this.onLanguageSelect}
>
{translate('SelectLanguges')}
{translate('SelectLanguages')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -155,7 +155,7 @@ DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.string.isRequired,
sizeOnDisk: PropTypes.number.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -5,7 +5,7 @@
.header {
position: relative;
width: 100%;
height: 350px;
height: 375px;
}
.errorMessage {
@@ -41,8 +41,8 @@
.poster {
flex-shrink: 0;
margin-right: 35px;
width: 200px;
height: 294px;
width: 217px;
height: 319px;
}
.info {

View File

@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import TextTruncate from 'react-text-truncate';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import InfoLabel from 'Components/InfoLabel';
import IconButton from 'Components/Link/IconButton';
import Marquee from 'Components/Marquee';
@@ -16,6 +16,8 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
@@ -449,17 +451,6 @@ class MovieDetails extends Component {
</span>
}
{
!!ratings &&
<span className={styles.rating}>
<HeartRating
rating={ratings.value}
iconSize={20}
hideHeart={isSmallScreen}
/>
</span>
}
{
<span className={styles.links}>
<Tooltip
@@ -501,6 +492,36 @@ class MovieDetails extends Component {
</div>
</div>
<div className={styles.details}>
{
!!ratings.tmdb &&
<span className={styles.rating}>
<TmdbRating
ratings={ratings}
iconSize={20}
/>
</span>
}
{
!!ratings.imdb &&
<span className={styles.rating}>
<ImdbRating
ratings={ratings}
iconSize={20}
/>
</span>
}
{
!!ratings.rottenTomatoes &&
<span className={styles.rating}>
<RottenTomatoRating
ratings={ratings}
iconSize={20}
/>
</span>
}
</div>
<div className={styles.detailsLabels}>
<InfoLabel
className={styles.detailsInfoLabel}

View File

@@ -67,8 +67,7 @@ class MovieHistoryRow extends Component {
data,
isMarkingAsFailed,
shortDateFormat,
timeFormat,
onMarkAsFailedPress
timeFormat
} = this.props;
const {
@@ -143,7 +142,7 @@ class MovieHistoryRow extends Component {
isMarkingAsFailed={isMarkingAsFailed}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
onMarkAsFailedPress={onMarkAsFailedPress}
onMarkAsFailedPress={this.onMarkAsFailedPress}
onModalClose={this.onDetailsModalClose}
/>
</TableRow>

View File

@@ -101,6 +101,15 @@ function MovieIndexSortMenu(props) {
{translate('DigitalRelease')}
</SortMenuItem>
<SortMenuItem
name="ratings"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Ratings')}
</SortMenuItem>
<SortMenuItem
name="path"
sortKey={sortKey}

View File

@@ -332,7 +332,7 @@ class MovieIndexRow extends Component {
className={styles[name]}
>
<HeartRating
rating={ratings.value}
ratings={ratings}
/>
</VirtualTableRowCell>
);

View File

@@ -6,7 +6,7 @@ export function getMovieStatusDetails(status) {
let statusDetails = {
icon: icons.ANNOUNCED,
title: translate('Announced'),
message: translate('AnnoucedMsg')
message: translate('AnnouncedMsg')
};
if (status === 'deleted') {

View File

@@ -4,6 +4,11 @@
margin-right: auto;
}
.rightButtons {
justify-content: flex-end;
margin-right: auto;
}
.addSpecification {
composes: customFormat from '~./CustomFormat.css';

View File

@@ -198,26 +198,25 @@ class EditCustomFormatModalContent extends Component {
</div>
</ModalBody>
<ModalFooter>
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteCustomFormatPress}
>
{translate('Delete')}
</Button>
}
<div className={styles.rightButtons}>
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteCustomFormatPress}
>
{translate('Delete')}
</Button>
}
{
!id &&
<Button
className={styles.deleteButton}
onPress={this.onImportPress}
>
{translate('Import')}
</Button>
}
<Button
className={styles.deleteButton}
onPress={this.onImportPress}
>
{translate('Import')}
</Button>
</div>
<Button
onPress={onModalClose}

View File

@@ -51,9 +51,15 @@ class RemotePathMappings extends Component {
{...otherProps}
>
<div className={styles.remotePathMappingsHeader}>
<div className={styles.host}>Host</div>
<div className={styles.path}>Remote Path</div>
<div className={styles.path}>Local Path</div>
<div className={styles.host}>
{translate('Host')}
</div>
<div className={styles.path}>
{translate('RemotePath')}
</div>
<div className={styles.path}>
{translate('LocalPath')}
</div>
</div>
<div>

View File

@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import EditImportExclusionModalContentConnector from './EditImportExclusionModalContentConnector';
import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector';
function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditImportExclusionModalContentConnector
<EditImportListExclusionModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@@ -19,9 +19,9 @@ function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
);
}
EditImportExclusionModal.propTypes = {
EditImportListExclusionModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditImportExclusionModal;
export default EditImportListExclusionModal;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditImportExclusionModal from './EditImportExclusionModal';
import EditImportListExclusionModal from './EditImportListExclusionModal';
function mapStateToProps() {
return {};
@@ -12,7 +12,7 @@ const mapDispatchToProps = {
clearPendingChanges
};
class EditImportExclusionModalConnector extends Component {
class EditImportListExclusionModalConnector extends Component {
//
// Listeners
@@ -27,7 +27,7 @@ class EditImportExclusionModalConnector extends Component {
render() {
return (
<EditImportExclusionModal
<EditImportListExclusionModal
{...this.props}
onModalClose={this.onModalClose}
/>
@@ -35,9 +35,9 @@ class EditImportExclusionModalConnector extends Component {
}
}
EditImportExclusionModalConnector.propTypes = {
EditImportListExclusionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(EditImportExclusionModalConnector);
export default connect(mapStateToProps, mapDispatchToProps)(EditImportListExclusionModalConnector);

View File

@@ -13,9 +13,9 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditImportExclusionModalContent.css';
import styles from './EditImportListExclusionModalContent.css';
function EditImportExclusionModalContent(props) {
function EditImportListExclusionModalContent(props) {
const {
id,
isFetching,
@@ -130,7 +130,7 @@ function EditImportExclusionModalContent(props) {
);
}
EditImportExclusionModalContent.propTypes = {
EditImportListExclusionModalContent.propTypes = {
id: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
@@ -143,4 +143,4 @@ EditImportExclusionModalContent.propTypes = {
onDeleteImportExclusionPress: PropTypes.func
};
export default EditImportExclusionModalContent;
export default EditImportListExclusionModalContent;

View File

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import EditImportExclusionModalContent from './EditImportExclusionModalContent';
import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
const newImportExclusion = {
movieTitle: '',
@@ -97,7 +97,7 @@ class EditImportExclusionModalContentConnector extends Component {
render() {
return (
<EditImportExclusionModalContent
<EditImportListExclusionModalContent
{...this.props}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}

View File

@@ -8,12 +8,14 @@
}
.movieTitle {
flex: 0 0 400px;
@add-mixin truncate;
flex: 0 0 600px;
}
.tmdbId,
.movieYear {
flex: 0 0 200px;
flex: 0 0 70px;
}
.actions {

View File

@@ -6,10 +6,10 @@ import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportExclusionModalConnector from './EditImportExclusionModalConnector';
import styles from './ImportExclusion.css';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportListExclusion.css';
class ImportExclusion extends Component {
class ImportListExclusion extends Component {
//
// Lifecycle
@@ -78,7 +78,7 @@ class ImportExclusion extends Component {
</Link>
</div>
<EditImportExclusionModalConnector
<EditImportListExclusionModalConnector
id={id}
isOpen={this.state.isEditImportExclusionModalOpen}
onModalClose={this.onEditImportExclusionModalClose}
@@ -99,7 +99,7 @@ class ImportExclusion extends Component {
}
}
ImportExclusion.propTypes = {
ImportListExclusion.propTypes = {
id: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired,
@@ -107,9 +107,9 @@ ImportExclusion.propTypes = {
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};
ImportExclusion.defaultProps = {
ImportListExclusion.defaultProps = {
// The drag preview will not connect the drag handle.
connectDragSource: (node) => node
};
export default ImportExclusion;
export default ImportListExclusion;

View File

@@ -1,16 +1,16 @@
.importExclusionsHeader {
.importListExclusionsHeader {
display: flex;
margin-bottom: 10px;
font-weight: bold;
}
.title {
flex: 0 0 400px;
flex: 0 0 600px;
}
.tmdbId,
.movieYear {
flex: 0 0 200px;
flex: 0 0 70px;
}
.addImportExclusion {

View File

@@ -6,11 +6,11 @@ import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportExclusionModalConnector from './EditImportExclusionModalConnector';
import ImportExclusion from './ImportExclusion';
import styles from './ImportExclusions.css';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion';
import styles from './ImportListExclusions.css';
class ImportExclusions extends Component {
class ImportListExclusions extends Component {
//
// Lifecycle
@@ -50,17 +50,23 @@ class ImportExclusions extends Component {
errorMessage={translate('UnableToLoadListExclusions')}
{...otherProps}
>
<div className={styles.importExclusionsHeader}>
<div className={styles.tmdbId}>TMDB Id</div>
<div className={styles.title}>Title</div>
<div className={styles.movieYear}>Year</div>
<div className={styles.importListExclusionsHeader}>
<div className={styles.tmdbId}>
TMDb Id
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
</div>
<div>
{
items.map((item, index) => {
return (
<ImportExclusion
<ImportListExclusion
key={item.id}
{...item}
{...otherProps}
@@ -81,7 +87,7 @@ class ImportExclusions extends Component {
</Link>
</div>
<EditImportExclusionModalConnector
<EditImportListExclusionModalConnector
isOpen={this.state.isAddImportExclusionModalOpen}
onModalClose={this.onModalClose}
/>
@@ -92,11 +98,11 @@ class ImportExclusions extends Component {
}
}
ImportExclusions.propTypes = {
ImportListExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};
export default ImportExclusions;
export default ImportListExclusions;

View File

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteImportExclusion, fetchImportExclusions } from 'Store/Actions/settingsActions';
import ImportExclusions from './ImportExclusions';
import ImportListExclusions from './ImportListExclusions';
function createMapStateToProps() {
return createSelector(
@@ -42,7 +42,7 @@ class ImportExclusionsConnector extends Component {
render() {
return (
<ImportExclusions
<ImportListExclusions
{...this.state}
{...this.props}
onConfirmDeleteImportExclusion={this.onConfirmDeleteImportExclusion}

View File

@@ -7,7 +7,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import ImportExclusionsConnector from './ImportExclusions/ImportExclusionsConnector';
import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
import ImportListsConnector from './ImportLists/ImportListsConnector';
import ImportListOptionsConnector from './Options/ImportListOptionsConnector';
@@ -86,7 +86,7 @@ class ImportListSettings extends Component {
onChildStateChange={this.onChildStateChange}
/>
<ImportExclusionsConnector />
<ImportListExclusionsConnector />
</PageContentBody>
</PageContent>

View File

@@ -45,7 +45,9 @@ function EditIndexerModalContent(props) {
supportsSearch,
tags,
fields,
priority
priority,
protocol,
downloadClientId
} = item;
return (
@@ -154,8 +156,25 @@ function EditIndexerModalContent(props) {
/>
</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>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}

View File

@@ -85,7 +85,7 @@ function IndexerOptions(props) {
type={inputTypes.CHECK}
name="preferIndexerFlags"
helpText={translate('PreferIndexerFlagsHelpText')}
helpLink="https://wiki.servarr.com/Definitions#Indexer_Flags"
helpLink="https://wiki.servarr.com/radarr/settings#indexer-flags"
onChange={onInputChange}
{...settings.preferIndexerFlags}
/>

View File

@@ -147,7 +147,8 @@ class NamingModal extends Component {
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' },
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' }
];
const releaseGroupTokens = [

View File

@@ -63,6 +63,7 @@ class Notification extends Component {
onMovieFileDelete,
onMovieFileDeleteForUpgrade,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
@@ -70,7 +71,8 @@ class Notification extends Component {
supportsOnMovieDelete,
supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade,
supportsOnHealthIssue
supportsOnHealthIssue,
supportsOnApplicationUpdate
} = this.props;
return (
@@ -123,6 +125,14 @@ class Notification extends Component {
null
}
{
supportsOnApplicationUpdate && onApplicationUpdate ?
<Label kind={kinds.SUCCESS}>
{translate('OnApplicationUpdate')}
</Label> :
null
}
{
supportsOnMovieDelete && onMovieDelete ?
<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
kind={kinds.DISABLED}
outline={true}
@@ -190,6 +200,7 @@ Notification.propTypes = {
onMovieFileDelete: PropTypes.bool.isRequired,
onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired,
supportsOnMovieDelete: PropTypes.bool.isRequired,
@@ -198,6 +209,7 @@ Notification.propTypes = {
supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -23,6 +23,7 @@ function NotificationEventItems(props) {
onMovieFileDelete,
onMovieFileDeleteForUpgrade,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
@@ -30,6 +31,7 @@ function NotificationEventItems(props) {
supportsOnMovieDelete,
supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade,
supportsOnApplicationUpdate,
supportsOnHealthIssue,
includeHealthWarnings
} = item;
@@ -150,6 +152,17 @@ function NotificationEventItems(props) {
/>
</div>
}
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onApplicationUpdate"
helpText={translate('OnApplicationUpdateHelpText')}
isDisabled={!supportsOnApplicationUpdate.value}
{...onApplicationUpdate}
onChange={onInputChange}
/>
</div>
</div>
</div>
</FormGroup>

View File

@@ -82,10 +82,18 @@ class DelayProfiles extends Component {
>
<div>
<div className={styles.delayProfilesHeader}>
<div className={styles.column}>Protocol</div>
<div className={styles.column}>Usenet Delay</div>
<div className={styles.column}>Torrent Delay</div>
<div className={styles.tags}>Tags</div>
<div className={styles.column}>
{translate('Protocol')}
</div>
<div className={styles.column}>
{translate('UsenetDelay')}
</div>
<div className={styles.column}>
{translate('TorrentDelay')}
</div>
<div className={styles.tags}>
{translate('Tags')}
</div>
</div>
<div className={styles.delayProfiles}>

View File

@@ -25,9 +25,15 @@ class QualityDefinitions extends Component {
{...otherProps}
>
<div className={styles.header}>
<div className={styles.quality}>Quality</div>
<div className={styles.title}>Title</div>
<div className={styles.sizeLimit}>Size Limit</div>
<div className={styles.quality}>
{translate('Quality')}
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.sizeLimit}>
{translate('SizeLimit')}
</div>
{
advancedSettings ?

View File

@@ -152,7 +152,7 @@ function TagDetailsModalContent(props) {
{
indexers.length ?
<FieldSet legend="Indexers">
<FieldSet legend={translate('Indexers')}>
{
indexers.map((item) => {
return (

View File

@@ -14,7 +14,6 @@ function createRemoveItemHandler(section, url) {
const ajaxOptions = {
url: `${url}/${id}?${$.param(queryParams, true)}`,
dataType: 'text',
method: 'DELETE'
};

View File

@@ -109,6 +109,7 @@ export default {
selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete;
selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete;
selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema;
});

View File

@@ -200,7 +200,7 @@ export const defaultState = {
ratings: function(item) {
const { ratings = {} } = item;
return ratings.value;
return ratings.tmdb? ratings.tmdb.value : 0;
}
},
@@ -330,8 +330,23 @@ export const defaultState = {
valueType: filterBuilderValueTypes.MINIMUM_AVAILABILITY
},
{
name: 'ratings',
label: 'Rating',
name: 'tmdbRating',
label: translate('TmdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'tmdbVotes',
label: translate('TmdbVotes'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),
type: filterBuilderTypes.NUMBER
},
{

View File

@@ -148,9 +148,10 @@ export const actionHandlers = handleThunks({
return {
id,
path: item.path,
movieId: item.movie.id,
movieId: item.movie ? item.movie.id : undefined,
quality: item.quality,
languages: item.languages,
releaseGroup: item.releaseGroup,
downloadId: item.downloadId
};
});

View File

@@ -131,10 +131,38 @@ export const filterPredicates = {
return dateFilterPredicate(item.digitalRelease, filterValue, type);
},
ratings: function(item, filterValue, type) {
tmdbRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
return predicate(item.ratings.value * 10, filterValue);
const rating = item.ratings.tmdb ? item.ratings.tmdb.value : 0;
return predicate(rating * 10, filterValue);
},
tmdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.tmdb ? item.ratings.tmdb.votes : 0;
return predicate(rating, filterValue);
},
imdbRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
console.log(item.ratings);
const rating = item.ratings.imdb ? item.ratings.imdb.value : 0;
return predicate(rating, filterValue);
},
imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.imdb ? item.ratings.imdb.votes : 0;
return predicate(rating, filterValue);
},
qualityCutoffNotMet: function(item) {

View File

@@ -209,7 +209,7 @@ export const defaultState = {
ratings: function(item) {
const { ratings = {} } = item;
return ratings.value;
return ratings.tmdb? ratings.tmdb.value : 0;
}
},
@@ -357,8 +357,23 @@ export const defaultState = {
}
},
{
name: 'ratings',
label: translate('Ratings'),
name: 'tmdbRating',
label: translate('TmdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'tmdbVotes',
label: translate('TmdbVotes'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),
type: filterBuilderTypes.NUMBER
},
{

View File

@@ -27,7 +27,7 @@ const paged = `${section}.paged`;
export const defaultState = {
options: {
includeUnknownMovieItems: false
includeUnknownMovieItems: true
},
status: {

View File

@@ -100,7 +100,7 @@ export default function createSentryMiddleware() {
return;
}
const dsn = isProduction ? 'https://b0fb75c38ef4487dbf742f79c4ba62d2@sentry.servarr.com/12' :
const dsn = isProduction ? 'https://7794f2858478485ea337fb5535624fbd@sentry.servarr.com/12' :
'https://da610619280249f891ec3ee306906793@sentry.servarr.com/13';
sentry.init({

View File

@@ -13,7 +13,7 @@ class Donations extends Component {
return (
<FieldSet legend={translate('Donations')}>
<div className={styles.logoContainer} title="Radarr">
<Link to="https://opencollective.com/radarr">
<Link to="https://radarr.video/donate">
<img
className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
@@ -21,7 +21,7 @@ class Donations extends Component {
</Link>
</div>
<div className={styles.logoContainer} title="Lidarr">
<Link to="https://opencollective.com/lidarr">
<Link to="https://lidarr.audio/donate">
<img
className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
@@ -29,7 +29,7 @@ class Donations extends Component {
</Link>
</div>
<div className={styles.logoContainer} title="Readarr">
<Link to="https://opencollective.com/readarr">
<Link to="https://readarr.com/donate">
<img
className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
@@ -37,7 +37,7 @@ class Donations extends Component {
</Link>
</div>
<div className={styles.logoContainer} title="Prowlarr">
<Link to="https://opencollective.com/prowlarr">
<Link to="https://prowlarr.com/donate">
<img
className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
@@ -45,7 +45,7 @@ class Donations extends Component {
</Link>
</div>
<div className={styles.logoContainer} title="Sonarr">
<Link to="https://opencollective.com/sonarr">
<Link to="https://sonarr.tv/donate">
<img
className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-sonarr.png`}

View File

@@ -19,6 +19,7 @@ function getInternalLink(source) {
case 'IndexerRssCheck':
case 'IndexerSearchCheck':
case 'IndexerStatusCheck':
case 'IndexerJackettAllCheck':
case 'IndexerLongTermStatusCheck':
return (
<IconButton

View File

@@ -200,7 +200,7 @@ class QueuedTaskRow extends Component {
{
clientUserAgent ?
<span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
{translate('from')}: {clientUserAgent}
{translate('From')}: {clientUserAgent}
</span> :
null
}

View File

@@ -2,7 +2,7 @@ import { kinds } from 'Helpers/Props';
function getStatusStyle(status, monitored, hasFile, isAvailable, returnType, queue = false) {
if (queue) {
return returnType === 'kinds' ? kinds.SUCCESS : 'queue';
return returnType === 'kinds' ? kinds.QUEUE : 'queue';
}
if (hasFile && monitored) {

View File

@@ -30,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "6.0.0",
"@microsoft/signalr": "6.0.1",
"@sentry/browser": "6.13.2",
"@sentry/integrations": "6.13.2",
"classnames": "2.3.1",

View File

@@ -146,7 +146,7 @@ namespace NzbDrone.Common.Test.Http
var request = new HttpRequest($"https://expired.badssl.com");
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
ExceptionVerification.ExpectedErrors(2);
ExceptionVerification.ExpectedErrors(1);
}
[Test]

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Common.EnvironmentInfo
private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(IHostLifetime hostLifetime, Logger logger)
public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null)
{
_logger = logger;

View File

@@ -171,5 +171,26 @@ namespace NzbDrone.Common.Extensions
{
return source.Contains(value, StringComparer.InvariantCultureIgnoreCase);
}
public static string EncodeRFC3986(this string value)
{
// From Twitterizer http://www.twitterizer.net/
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
var encoded = Uri.EscapeDataString(value);
return Regex
.Replace(encoded, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper())
.Replace("(", "%28")
.Replace(")", "%29")
.Replace("$", "%24")
.Replace("!", "%21")
.Replace("*", "%2A")
.Replace("'", "%27")
.Replace("%7E", "~");
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Net;
namespace NzbDrone.Common.Http
{
public class BasicNetworkCredential : NetworkCredential
{
public BasicNetworkCredential(string user, string pass)
: base(user, pass)
{
}
}
}

View File

@@ -4,9 +4,9 @@ using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -26,22 +26,21 @@ namespace NzbDrone.Common.Http.Dispatchers
private readonly ICertificateValidationService _certificateValidationService;
private readonly IUserAgentBuilder _userAgentBuilder;
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
private readonly Logger _logger;
private readonly ICached<CredentialCache> _credentialCache;
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
ICreateManagedWebProxy createManagedWebProxy,
ICertificateValidationService certificateValidationService,
IUserAgentBuilder userAgentBuilder,
ICacheManager cacheManager,
Logger logger)
ICacheManager cacheManager)
{
_proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy;
_certificateValidationService = certificateValidationService;
_userAgentBuilder = userAgentBuilder;
_logger = logger;
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
}
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
@@ -67,6 +66,26 @@ namespace NzbDrone.Common.Http.Dispatchers
cts.CancelAfter(TimeSpan.FromSeconds(100));
}
if (request.Credentials != null)
{
if (request.Credentials is BasicNetworkCredential bc)
{
// Manually set header to avoid initial challenge response
var authInfo = bc.UserName + ":" + bc.Password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
requestMessage.Headers.Add("Authorization", "Basic " + authInfo);
}
else if (request.Credentials is NetworkCredential nc)
{
var creds = GetCredentialCache();
foreach (var authtype in new[] { "Basic", "Digest" })
{
creds.Remove((Uri)request.Url, authtype);
creds.Add((Uri)request.Url, authtype, nc);
}
}
}
if (request.ContentData != null)
{
requestMessage.Content = new ByteArrayContent(request.ContentData);
@@ -79,49 +98,32 @@ namespace NzbDrone.Common.Http.Dispatchers
var httpClient = GetClient(request.Url);
HttpResponseMessage responseMessage;
try
using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
{
responseMessage = httpClient.Send(requestMessage, cts.Token);
}
catch (HttpRequestException e)
{
_logger.Error(e, "HttpClient error");
throw;
}
byte[] data = null;
byte[] data = null;
using (var responseStream = responseMessage.Content.ReadAsStream())
{
if (responseStream != null && responseStream != Stream.Null)
try
{
try
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
// A target ResponseStream was specified, write to that instead.
// But only on the OK status code, since we don't want to write failures and redirects.
responseStream.CopyTo(request.ResponseStream);
}
else
{
data = responseStream.ToBytes();
}
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
}
catch (Exception ex)
else
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
}
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
@@ -140,6 +142,8 @@ namespace NzbDrone.Common.Http.Dispatchers
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Brotli,
UseCookies = false, // sic - we don't want to use a shared cookie container
AllowAutoRedirect = false,
Credentials = GetCredentialCache(),
PreAuthenticate = true,
MaxConnectionsPerServer = 12,
ConnectCallback = onConnect,
SslOptions = new SslClientAuthenticationOptions
@@ -224,6 +228,11 @@ namespace NzbDrone.Common.Http.Dispatchers
headers.Add(header, value);
}
private CredentialCache GetCredentialCache()
{
return _credentialCache.Get("credentialCache", () => new CredentialCache());
}
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.

View File

@@ -38,6 +38,7 @@ namespace NzbDrone.Common.Http
public HttpHeader Headers { get; set; }
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public ICredentials Credentials { get; set; }
public bool SuppressHttpError { get; set; }
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
public bool UseSimplifiedUserAgent { get; set; }
@@ -89,12 +90,5 @@ namespace NzbDrone.Common.Http
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data);
}
public void AddBasicAuthentication(string username, string password)
{
var authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"));
Headers.Set("Authorization", "Basic " + authInfo);
}
}
}

View File

@@ -26,10 +26,9 @@ namespace NzbDrone.Common.Http
public bool ConnectionKeepAlive { get; set; }
public TimeSpan RateLimit { get; set; }
public bool LogResponseContent { get; set; }
public NetworkCredential NetworkCredential { get; set; }
public ICredentials NetworkCredential { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public List<HttpFormData> FormData { get; private set; }
public Action<HttpRequest> PostProcess { get; set; }
public HttpRequestBuilder(string baseUrl)
@@ -109,13 +108,7 @@ namespace NzbDrone.Common.Http
request.ConnectionKeepAlive = ConnectionKeepAlive;
request.RateLimit = RateLimit;
request.LogResponseContent = LogResponseContent;
if (NetworkCredential != null)
{
var authInfo = NetworkCredential.UserName + ":" + NetworkCredential.Password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers.Set("Authorization", "Basic " + authInfo);
}
request.Credentials = NetworkCredential;
foreach (var header in Headers)
{

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Common.Http
{
public class XmlRpcRequestBuilder : HttpRequestBuilder
{
public static string XmlRpcContentType = "text/xml";
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(XmlRpcRequestBuilder));
public string XmlMethod { get; private set; }
public List<object> XmlParameters { get; private set; }
public XmlRpcRequestBuilder(string baseUrl)
: base(baseUrl)
{
Method = HttpMethod.Post;
XmlParameters = new List<object>();
}
public XmlRpcRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
: this(BuildBaseUrl(useHttps, host, port, urlBase))
{
}
public override HttpRequestBuilder Clone()
{
var clone = base.Clone() as XmlRpcRequestBuilder;
clone.XmlParameters = new List<object>(XmlParameters);
return clone;
}
public XmlRpcRequestBuilder Call(string method, params object[] parameters)
{
var clone = Clone() as XmlRpcRequestBuilder;
clone.XmlMethod = method;
clone.XmlParameters = parameters.ToList();
return clone;
}
protected override void Apply(HttpRequest request)
{
base.Apply(request);
request.Headers.ContentType = XmlRpcContentType;
var methodCallElements = new List<XElement> { new XElement("methodName", XmlMethod) };
if (XmlParameters.Any())
{
var argElements = XmlParameters.Select(x => new XElement("param", ConvertParameter(x))).ToList();
var paramsElement = new XElement("params", argElements);
methodCallElements.Add(paramsElement);
}
var message = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("methodCall", methodCallElements));
var body = message.ToString();
Logger.Debug($"Executing remote method: {XmlMethod}");
Logger.Trace($"methodCall {XmlMethod} body:\n{body}");
request.SetContent(body);
}
private static XElement ConvertParameter(object value)
{
XElement data;
if (value is string s)
{
data = new XElement("string", s);
}
else if (value is List<string> l)
{
data = new XElement("array", new XElement("data", l.Select(x => new XElement("value", new XElement("string", x)))));
}
else if (value is int i)
{
data = new XElement("int", i);
}
else if (value is byte[] bytes)
{
data = new XElement("base64", Convert.ToBase64String(bytes));
}
else
{
throw new InvalidOperationException($"Unhandled argument type {value.GetType().Name}");
}
return new XElement("value", data);
}
}
}

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Common.Instrumentation
else
{
dsn = RuntimeInfo.IsProduction
? "https://39b572f7f3f04899b2c3254c7ac126d0@sentry.servarr.com/9"
? "https://26668106d708406b9ddf5a2bda34fcbb@sentry.servarr.com/9"
: "https://998b4673d4c849ccb5277b5966ed5bc2@sentry.servarr.com/10";
}

View File

@@ -12,7 +12,7 @@
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="3.11.1" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
<PackageReference Include="System.Text.Json" Version="6.0.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />

View File

@@ -0,0 +1,71 @@
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class migrate_discord_from_slackFixture : MigrationTest<migrate_discord_from_slack>
{
private readonly JsonSerializerOptions _serializerSettings;
public migrate_discord_from_slackFixture()
{
_serializerSettings = new JsonSerializerOptions
{
AllowTrailingCommas = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
PropertyNameCaseInsensitive = true,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
_serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true));
}
[Test]
public void should_replace_old_url()
{
var webhookUrl = "https://discord.com/api/webhooks/922499153416847361/f9CAcD5i_E_-0AoPfMVa8igVK8h271HpJDbd6euUrPh9KonWlMCziLOSMmD-2SQ4CHmX/slack";
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
Name = "SlackDiscord",
Implementation = "Slack",
Settings = new SlackNotificationSettings201
{
Icon = "TestURL",
Username = "TestUsername",
WebHookUrl = webhookUrl
}.ToJson(),
ConfigContract = "SlackSettings",
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnRename = true,
OnHealthIssue = true,
OnMovieDelete = true,
OnMovieFileDelete = true,
OnMovieFileDeleteForUpgrade = true,
IncludeHealthWarnings = true
});
});
var items = db.Query<NotificationEntity201>("SELECT Id,ConfigContract,Implementation,Name,Settings FROM Notifications");
items.Should().HaveCount(1);
items.First().ConfigContract.Should().Be("DiscordSettings");
var settings = JsonSerializer.Deserialize<DiscordNotificationSettings201>(items.First().Settings, _serializerSettings);
settings.Avatar.Should().Be("TestURL");
settings.Username.Should().Be("TestUsername");
settings.WebHookUrl.Should().Be(webhookUrl.Replace("/slack", ""));
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class remove_predbFixture : MigrationTest<remove_predb>
{
[Test]
public void should_change_min_avail_from_predb_on_list()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("ImportLists").Row(new
{
Enabled = 1,
EnableAuto = 1,
RootFolderPath = "D:\\Movies",
ProfileId = 1,
MinimumAvailability = 4,
ShouldMonitor = 1,
Name = "IMDB List",
Implementation = "RadarrLists",
Settings = new RadarrListSettings169
{
APIURL = "https://api.radarr.video/v2",
Path = "/imdb/list?listId=ls000199717",
}.ToJson(),
ConfigContract = "RadarrSettings"
});
});
var items = db.Query<ListDefinition201>("SELECT Id, MinimumAvailability FROM ImportLists");
items.Should().HaveCount(1);
items.First().MinimumAvailability.Should().Be(3);
}
[Test]
public void should_change_min_avail_from_predb_on_movie()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Movies").Row(new
{
Monitored = true,
Title = "Title",
CleanTitle = "CleanTitle",
Status = 3,
MinimumAvailability = 4,
Images = new[] { new { CoverType = "Poster" } }.ToJson(),
Recommendations = new[] { 1 }.ToJson(),
HasPreDBEntry = false,
Runtime = 90,
OriginalLanguage = 1,
ProfileId = 1,
MovieFileId = 0,
Path = string.Format("/Movies/{0}", "Title"),
TitleSlug = 123456,
TmdbId = 132456,
Added = DateTime.UtcNow,
LastInfoSync = DateTime.UtcNow,
});
});
var items = db.Query<Movie201>("SELECT Id, MinimumAvailability FROM Movies");
items.Should().HaveCount(1);
items.First().MinimumAvailability.Should().Be(3);
}
}
public class ListDefinition201
{
public int Id { get; set; }
public int MinimumAvailability { get; set; }
}
public class Movie201
{
public int Id { get; set; }
public int MinimumAvailability { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
using System.Data;
using System.Data;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
@@ -38,6 +38,7 @@ namespace NzbDrone.Core.Test.Datastore.SqliteSchemaDumperTests
result.Name.Should().Be(tableName);
result.Columns.Count.Should().Be(1);
result.Columns.First().Name.Should().Be(columnName);
result.Columns.First().IsIdentity.Should().BeTrue();
}
[TestCase(@"CREATE INDEX TestIndex ON TestTable (MyId)", "TestIndex", "TestTable", "MyId")]

View File

@@ -1,10 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
@@ -67,6 +68,17 @@ namespace NzbDrone.Core.Test.Download
return mock;
}
private void WithTorrentIndexer(int downloadClientId)
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Find(It.IsAny<int>()))
.Returns(Builder<IndexerDefinition>
.CreateNew()
.With(v => v.Id = _nextId++)
.With(v => v.DownloadClientId = downloadClientId)
.Build());
}
private void GivenBlockedClient(int id)
{
_blockedProviders.Add(new DownloadClientStatus
@@ -223,5 +235,39 @@ namespace NzbDrone.Core.Test.Download
client3.Definition.Id.Should().Be(2);
client4.Definition.Id.Should().Be(3);
}
[Test]
public void should_always_choose_indexer_client()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentIndexer(3);
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
client1.Definition.Id.Should().Be(3);
client2.Definition.Id.Should().Be(3);
client3.Definition.Id.Should().Be(3);
client4.Definition.Id.Should().Be(3);
client5.Definition.Id.Should().Be(3);
}
[Test]
public void should_fail_to_choose_client_when_indexer_reference_does_not_exist()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentIndexer(5);
Assert.Throws<DownloadClientUnavailableException>(() => Subject.GetDownloadClient(DownloadProtocol.Torrent, 1));
}
}
}

View File

@@ -31,8 +31,8 @@ namespace NzbDrone.Core.Test.Download
.Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClient(It.IsAny<DownloadProtocol>()))
.Returns<DownloadProtocol>(v => _downloadClients.FirstOrDefault(d => d.Protocol == v));
.Setup(v => v.GetDownloadClient(It.IsAny<DownloadProtocol>(), It.IsAny<int>()))
.Returns<DownloadProtocol, int>((v, i) => _downloadClients.FirstOrDefault(d => d.Protocol == v));
var releaseInfo = Builder<ReleaseInfo>.CreateNew()
.With(v => v.DownloadProtocol = DownloadProtocol.Usenet)

View File

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

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Torznab;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class IndexerJackettAllCheckFixture : CoreTest<IndexerJackettAllCheck>
{
private List<IndexerDefinition> _indexers = new List<IndexerDefinition>();
private IndexerDefinition _definition;
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.All())
.Returns(_indexers);
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
}
private void GivenIndexer(string baseUrl, string apiPath)
{
var torznabSettings = new TorznabSettings
{
BaseUrl = baseUrl,
ApiPath = apiPath
};
_definition = new IndexerDefinition
{
Name = "Indexer",
ConfigContract = "TorznabSettings",
Settings = torznabSettings
};
_indexers.Add(_definition);
}
[Test]
public void should_not_return_error_when_no_indexers()
{
Subject.Check().ShouldBeOk();
}
[TestCase("http://localhost:9117/", "api")]
public void should_not_return_error_when_no_jackett_all_indexers(string baseUrl, string apiPath)
{
GivenIndexer(baseUrl, apiPath);
Subject.Check().ShouldBeOk();
}
[TestCase("http://localhost:9117/torznab/all/api", "api")]
[TestCase("http://localhost:9117/api/v2.0/indexers/all/results/torznab", "api")]
[TestCase("http://localhost:9117/", "/torznab/all/api")]
[TestCase("http://localhost:9117/", "/api/v2.0/indexers/all/results/torznab")]
public void should_return_warning_if_any_jackett_all_indexer_exists(string baseUrl, string apiPath)
{
GivenIndexer(baseUrl, apiPath);
Subject.Check().ShouldBeWarning();
}
}
}

View File

@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_ok_on_movie_imported_event()
{
GivenFolderExists(_downloadRootPath);
var importEvent = new MovieImportedEvent(new LocalMovie(), new MovieFile(), true, new DownloadClientItem(), _downloadItem.DownloadId);
var importEvent = new MovieImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem());
Subject.Check(importEvent).ShouldBeOk();
}

View File

@@ -19,6 +19,10 @@ namespace NzbDrone.Core.Test.HealthCheck
Mocker.SetConstant<IEnumerable<IProvideHealthCheck>>(new[] { _healthCheck });
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.GetMock<IServerSideNotificationService>()
.Setup(v => v.GetServerChecks())
.Returns(new List<Core.HealthCheck.HealthCheck>());
}
[Test]

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.HistoryTests
DownloadId = "abcd"
};
Subject.Handle(new MovieImportedEvent(localMovie, movieFile, true, downloadClientItem, "abcd"));
Subject.Handle(new MovieImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem));
Mocker.GetMock<IHistoryRepository>()
.Verify(v => v.Insert(It.Is<MovieHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localMovie.Path))));

View File

@@ -204,5 +204,48 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
pageTier2.Url.Query.Should().NotContain("imdbid=0076759");
pageTier2.Url.Query.Should().Contain("q=");
}
[Test]
public void should_encode_raw_title()
{
_capabilities.SupportedMovieSearchParameters = new[] { "q" };
_capabilities.TextSearchEngine = "raw";
MovieSearchCriteria movieRawSearchCriteria = new MovieSearchCriteria
{
Movie = new Movies.Movie { Title = "Some Movie & Title: Words", Year = 2021, TmdbId = 123 },
SceneTitles = new List<string> { "Some Movie & Title: Words" }
};
var results = Subject.GetSearchRequests(movieRawSearchCriteria);
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("q=Some%20Movie%20%26%20Title%3A%20Words");
page.Url.Query.Should().NotContain(" & ");
page.Url.Query.Should().Contain("%26");
}
[Test]
public void should_use_clean_title_and_encode()
{
_capabilities.SupportedMovieSearchParameters = new[] { "q" };
_capabilities.TextSearchEngine = "sphinx";
MovieSearchCriteria movieRawSearchCriteria = new MovieSearchCriteria
{
Movie = new Movies.Movie { Title = "Some Movie & Title: Words", Year = 2021, TmdbId = 123 },
SceneTitles = new List<string> { "Some Movie & Title: Words" }
};
var results = Subject.GetSearchRequests(movieRawSearchCriteria);
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("q=Some%20Movie%20and%20Title%20Words%202021");
page.Url.Query.Should().Contain("and");
page.Url.Query.Should().NotContain(" & ");
page.Url.Query.Should().NotContain("%26");
}
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using FizzWare.NBuilder;
using FluentAssertions;
using FluentValidation.Results;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
@@ -10,6 +13,7 @@ using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Indexers.Torznab;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
{
@@ -31,7 +35,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
}
};
_caps = new NewznabCapabilities();
_caps = new NewznabCapabilities
{
Categories = Builder<NewznabCategory>.CreateListOfSize(1).All().With(t => t.Id = 1).Build().ToList()
};
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>()))
.Returns(_caps);
@@ -134,5 +142,50 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
Subject.PageSize.Should().Be(25);
}
[TestCase("http://localhost:9117/", "/api")]
public void url_and_api_not_jackett_all(string baseUrl, string apiPath)
{
var setting = new TorznabSettings()
{
BaseUrl = baseUrl,
ApiPath = apiPath
};
setting.Validate().IsValid.Should().BeTrue();
}
[TestCase("http://localhost:9117/torznab/all/api")]
[TestCase("http://localhost:9117/api/v2.0/indexers/all/results/torznab")]
public void jackett_all_url_should_not_validate(string baseUrl)
{
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
(Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl;
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
result.HasWarnings.Should().BeTrue();
}
[TestCase("/torznab/all/api")]
[TestCase("/api/v2.0/indexers/all/results/torznab")]
public void jackett_all_api_should_not_validate(string apiPath)
{
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
(Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath;
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
result.HasWarnings.Should().BeTrue();
}
}
}

View File

@@ -43,7 +43,10 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 28, Language.Thai },
new object[] { 29, Language.Bulgarian },
new object[] { 30, Language.PortugueseBR },
new object[] { 31, Language.Arabic }
new object[] { 31, Language.Arabic },
new object[] { 32, Language.Ukrainian },
new object[] { 33, Language.Persian },
new object[] { 34, Language.Bengali },
};
public static object[] ToIntCases =
@@ -81,7 +84,10 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Thai, 28 },
new object[] { Language.Bulgarian, 29 },
new object[] { Language.PortugueseBR, 30 },
new object[] { Language.Arabic, 31 }
new object[] { Language.Arabic, 31 },
new object[] { Language.Ukrainian, 32 },
new object[] { Language.Persian, 33 },
new object[] { Language.Bengali, 34 },
};
[Test]

View File

@@ -19,7 +19,12 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestCase("wmv1, WMV1", "Droned.wmv", "WMV")]
[TestCase("wmv2, WMV2", "Droned.wmv", "WMV")]
[TestCase("mpeg4, XVID", "", "XviD")]
[TestCase("mpeg4, DIVX", "", "DivX")]
[TestCase("mpeg4, divx", "", "DivX")]
[TestCase("mpeg4, DIV3", "spsm.dvdrip.divx.avi'.", "DivX")]
[TestCase("msmpeg4, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
[TestCase("msmpeg4v2, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
[TestCase("msmpeg4v3, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
[TestCase("vp6, 4", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
[TestCase("vp7, VP70", "Sweet Seymour.avi", "VP7")]
[TestCase("vp8, V_VP8", "Dick.mkv", "VP8")]

View File

@@ -0,0 +1,31 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
[TestFixture]
public class FormatVideoDynamicRangeTypeFixture : TestBase
{
[TestCase(HdrFormat.None, "")]
[TestCase(HdrFormat.Hlg10, "HLG")]
[TestCase(HdrFormat.Pq10, "PQ")]
[TestCase(HdrFormat.Hdr10, "HDR10")]
[TestCase(HdrFormat.Hdr10Plus, "HDR10Plus")]
[TestCase(HdrFormat.DolbyVision, "DV")]
[TestCase(HdrFormat.DolbyVisionHdr10, "DV HDR10")]
[TestCase(HdrFormat.DolbyVisionHlg, "DV HLG")]
[TestCase(HdrFormat.DolbyVisionSdr, "DV SDR")]
public void should_format_video_dynamic_range_type(HdrFormat format, string expectedVideoDynamicRangeType)
{
var mediaInfo = new MediaInfoModel
{
VideoHdrFormat = format,
SchemaRevision = 9
};
MediaInfoFormatter.FormatVideoDynamicRangeType(mediaInfo).Should().Be(expectedVideoDynamicRangeType);
}
}
}

View File

@@ -102,24 +102,38 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
info.VideoTransferCharacteristics.Should().Be("bt709");
}
[TestCase(8, "", "", "", HdrFormat.None)]
[TestCase(10, "", "", "", HdrFormat.None)]
[TestCase(10, "bt709", "bt709", "", HdrFormat.None)]
[TestCase(8, "bt2020", "smpte2084", "", HdrFormat.None)]
[TestCase(10, "bt2020", "bt2020-10", "", HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "arib-std-b67", "", HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "smpte2084", "", HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", HdrFormat.Hdr10Plus)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", HdrFormat.DolbyVision)]
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, HdrFormat expected)
[TestCase(8, "", "", "", null, HdrFormat.None)]
[TestCase(10, "", "", "", null, HdrFormat.None)]
[TestCase(10, "bt709", "bt709", "", null, HdrFormat.None)]
[TestCase(8, "bt2020", "smpte2084", "", null, HdrFormat.None)]
[TestCase(10, "bt2020", "bt2020-10", "", null, HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "arib-std-b67", "", null, HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "smpte2084", "", null, HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", null, HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", null, HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", null, HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", null, HdrFormat.Hdr10Plus)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", null, HdrFormat.DolbyVision)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 1, HdrFormat.DolbyVisionHdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 2, HdrFormat.DolbyVisionSdr)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 4, HdrFormat.DolbyVisionHlg)]
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, int? doviConfigId, HdrFormat expected)
{
var assembly = Assembly.GetAssembly(typeof(FFProbe));
var types = sideDataTypes.Split(",").Select(x => x.Trim()).ToList();
var sideData = types.Where(x => x.IsNotNullOrWhiteSpace()).Select(x => assembly.CreateInstance(x)).Cast<SideData>().ToList();
if (doviConfigId.HasValue)
{
sideData.ForEach(x =>
{
if (x.GetType().Name == "DoviConfigurationRecordSideData")
{
((DoviConfigurationRecordSideData)x).DvBlSignalCompatibilityId = doviConfigId.Value;
}
});
}
var result = VideoFileInfoReader.GetHdrFormat(bitDepth, colourPrimaries, transferFunction, sideData);
result.Should().Be(expected);

View File

@@ -78,6 +78,11 @@ namespace NzbDrone.Core.Test.NotificationTests
{
TestLogger.Info("OnHealthIssue was called");
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
TestLogger.Info("OnApplicationUpdate was called");
}
}
private class TestNotificationWithNoEvents : NotificationBase<TestSetting>
@@ -116,6 +121,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnMovieFileDelete.Should().BeTrue();
notification.SupportsOnMovieFileDeleteForUpgrade.Should().BeTrue();
notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnApplicationUpdate.Should().BeTrue();
}
[Test]
@@ -131,6 +137,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnMovieFileDelete.Should().BeFalse();
notification.SupportsOnMovieFileDeleteForUpgrade.Should().BeFalse();
notification.SupportsOnHealthIssue.Should().BeFalse();
notification.SupportsOnApplicationUpdate.Should().BeFalse();
}
}
}

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