1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-25 17:35:35 -04:00

Compare commits

...

62 Commits

Author SHA1 Message Date
Qstick
372d15ecf3 New DSN for 4.6 2023-07-03 11:16:06 -05:00
Bogdan
b95431500d Fixed: Sorting queue by year
Fixes #8765
2023-07-03 17:00:14 +03:00
Bogdan
3da72f54ef Add ContentSummary to HDBits requests
(cherry picked from commit b6f3bcb3093267c483a57e10ac425e871f1f7ba8)

Closes #8757
2023-07-03 11:22:43 +03:00
Bogdan
7cfff20cad Create overload for ToJson() with Formatting param
(cherry picked from commit aa2b0031671b6846eaa75e0914cd03ae7bbb0da7)
2023-07-03 11:20:02 +03:00
Qstick
d3895dec8f Fixed: Allow restore to process backups up to ~500MB
(cherry picked from commit 551edb9e655d2a541a2232f85e79a5e3f7b433aa)
2023-07-03 08:55:11 +01:00
Weblate
690bab3264 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: BeardedWatermelon <periklis.karantonis@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translation: Servarr/Radarr
2023-06-30 13:55:34 +03:00
Bogdan
986128e100 Removed suspicious URL in the default definitions
Co-authored-by: Rumplin <35449956+rumplin@users.noreply.github.com>
2023-06-29 18:06:04 +03:00
Weblate
dda0885f91 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2023-06-28 13:55:21 +03:00
Mark McDowall
7e218a886d Handle null for parent name with clean path is empty
(cherry picked from commit bd3816dac9b540d2f95960a254bb21ab3c91421e)
2023-06-28 08:27:19 +03:00
Mark McDowall
77cde138dc Fixed: Don't handle images in metadata folder as Roksbox images
(cherry picked from commit 95a8f59a32d55550d5367f08a964859a97a0df44)
2023-06-28 08:27:19 +03:00
Bogdan
239109e3dd New: Improve empty list messaging
(cherry picked from commit ee843259bca2d9764a3919b123524a51a3a16cce)

Closes #8738
2023-06-27 06:00:03 +03:00
Bogdan
4804eb0769 Fixed: Invalid image URL if Movie is missing background image
(cherry picked from commit dd096e0fda71b3afa9b09c2900abbf226d8a5204)

Closes #8742
2023-06-27 05:46:30 +03:00
Bogdan
60a55dfdac Allow array of string as value in EnhancedSelectInput
(cherry picked from commit 6816767fad9e4e839e77c5fe40aece97033cd052)
2023-06-26 23:49:23 +01:00
Servarr
cd82865303 Automated API Docs update 2023-06-26 02:25:31 +03:00
Servarr
439adb4ac6 Translated using Weblate (Hungarian) [skip ci] (#8734)
Currently translated at 97.6% (1156 of 1184 strings)


Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translation: Servarr/Radarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: sutoramon <sutoramon@gmail.com>
2023-06-26 02:25:08 +03:00
Bogdan
fd0ff78791 Add translation to movie collection filters 2023-06-26 02:23:26 +03:00
Ricardo Christmann
cbae355402 New: Added filter and sort options to Collections (#8731)
* New: Added filter and sort options to Collections

* Add AllMovieWithCollectionsTmdbIds method to MovieService and MovieRepository
2023-06-25 10:04:57 -04:00
Qstick
fed98a648f Bump version to 4.6.4 2023-06-25 01:10:05 -04:00
bakerboy448
e1c5656cff Add more trace logs related info to bug_report.yml [skip ci] [common] 2023-06-22 16:38:09 +03:00
Weblate
e3f88e1711 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1184 of 1184 strings)

Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]

Currently translated at 9.2% (109 of 1184 strings)

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

Currently translated at 100.0% (1184 of 1184 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 87.6% (1038 of 1184 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1184 of 1184 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: KHng0284 <giakhang021109@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: ted09080037 <ted09080037@gmail.com>
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/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2023-06-21 06:53:25 +03:00
Servarr
8fd267580a Automated API Docs update 2023-06-21 06:45:42 +03:00
Bogdan
8974aa823b Remove not implemented endpoints from API docs
Closes #8724
2023-06-21 06:34:07 +03:00
Bogdan
41492efd2e Convert to 'using' declaration in Housekeeping Tasks
Closes #8723
2023-06-21 06:25:44 +03:00
Bogdan
d008768fff Prevent NullRef when deleting missing backups
(cherry picked from commit 0ff0fe2e68f3abf7b8e4d6bf0c1e9dee4eb68227)

Closes #8721
2023-06-21 06:19:30 +03:00
Bogdan
cb21fe535d Fix translation for Unreleased 2023-06-20 19:39:16 +03:00
Bogdan
4cce2727e2 Update translations
(cherry picked from commit 26031389757f6b5270bbe5591101b08e58debb73)
2023-06-20 02:38:55 +03:00
Stevie Robinson
b1ff82da37 Fixed: Parsing Vyndros as release group
(cherry picked from commit f2ddd4757c897c522b553de8bafb5340746253c9)

Closes #8569
2023-06-19 07:09:04 +03:00
Mark McDowall
c5266152c5 Fixed: Strip additional domains from release names
(cherry picked from commit e273f16c3905e0c2451f43cf98b9b7ad1cbdc777)

Closes #8603
2023-06-19 07:00:22 +03:00
Bogdan
783878be1e Minor improvements in health checks
(cherry picked from commit a22f598b0c129110f2a3b663e9b40c84f3a1f02b)

Closes #8615
2023-06-19 06:38:51 +03:00
Bogdan
0cbfb4ca65 New: (UI) Search library by imdbId and tmdbId 2023-06-19 04:19:12 +03:00
bakerboy448
c22c9400c2 New: Indexer Messaging and Error Improvements
(cherry picked from commit 3b505d8734dcbe3fa53acba7f94f1361151e6a44)
2023-06-18 12:06:55 +03:00
Bogdan
0288c4b704 Bump version to 4.6.3 2023-06-18 12:05:11 +03:00
Márki-Zay Ferenc
e4429d2919 Fixed: Close database connection in housekeeping tasks 2023-06-17 03:52:12 +03:00
Bogdan
7052a7a5ec New: Improved page loading errors
Closes #8706
2023-06-16 23:45:26 +03:00
Weblate
b38912851b Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1184 of 1184 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2023-06-16 02:21:40 +03:00
Bogdan
1354c2c337 Fix sorting history items by movie titles 2023-06-15 23:21:16 +03:00
Servarr
e259235df6 Automated API Docs update 2023-06-14 09:17:16 +03:00
Bogdan
0cc1fe8308 Add HelpTextWarning support in FieldDefinition
(cherry picked from commit 0e07d54ee77d5f83716e17b6757e23f38ff73694)

Closes #8687
2023-06-14 07:55:44 +03:00
Bogdan
f4fe18a440 Require ApiKey for all actions in RadarrImport
(cherry picked from commit 19b8fbe13bf584b915a05fe9fc87622adbaee0b7)

Closes #8692
2023-06-14 07:50:53 +03:00
Bogdan
eeed935e3a Update cleansing rules for RSS TL feed and homedir for Mac
(cherry picked from commit e5ff4aafa3f0b855fec332788e9fc490a03dfce3)

Closes #8684
2023-06-14 07:48:26 +03:00
Bogdan
1b3701371a Fixed: Treat redirects as errors in Radarr Import List
(cherry picked from commit 059a156f4a34c6b9cbe139fa1973b814e8a534ae)
2023-06-14 07:40:06 +03:00
Qstick
d56f3ec2e7 Fixed: Correctly handle 302 and 303 redirects in HttpClient
(cherry picked from commit ed7c5a937f4b50fcdf819e8fe347c8c0bc6bd2e7)

(cherry picked from commit 11bd764a75d3b97117098738d3489c4b3329738f)
2023-06-14 07:37:37 +03:00
Weblate
e7e3aac971 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Update translation files  [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 97.5% (1155 of 1184 strings)

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

Currently translated at 22.1% (262 of 1184 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 97.8% (1158 of 1184 strings)

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

Currently translated at 100.0% (1184 of 1184 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.5% (1179 of 1184 strings)

Update translation files  [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Indonesian) [skip ci]

Currently translated at 7.5% (89 of 1180 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 21.8% (258 of 1180 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 97.9% (1156 of 1180 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 21.1% (249 of 1180 strings)

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

Currently translated at 22.2% (263 of 1180 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 98.2% (1159 of 1180 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 88.8% (1048 of 1180 strings)

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

Currently translated at 98.8% (1166 of 1180 strings)

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

Currently translated at 99.9% (1179 of 1180 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 86.2% (1018 of 1180 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 85.9% (1014 of 1180 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 88.1% (1040 of 1180 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 89.0% (1051 of 1180 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 98.2% (1159 of 1180 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 98.3% (1160 of 1180 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 97.1% (1146 of 1180 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 98.4% (1162 of 1180 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 21.3% (252 of 1180 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 88.3% (1042 of 1180 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 98.1% (1158 of 1180 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 91.2% (1077 of 1180 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 97.9% (1156 of 1180 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 99.6% (1176 of 1180 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 88.3% (1042 of 1180 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 89.1% (1052 of 1180 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 93.8% (1107 of 1180 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 98.2% (1159 of 1180 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 99.4% (1173 of 1180 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.9% (1179 of 1180 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
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/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-06-14 07:37:13 +03:00
Bogdan
d2cb36c88a Check only clients not in failure status in DownloadClientSortingCheck
(cherry picked from commit 0b872803358bac1297cc7618ea3c13a32a92b5a4)
2023-06-14 07:34:21 +03:00
bakerboy448
2fe28cb1dc Fixed: Handle checkingResumeData state form qBittorrent
(cherry picked from commit 8d8a16225ff7772ccb57784f272ca31e28bb8455)
2023-06-14 07:33:55 +03:00
Bogdan
5d65b4cae4 Fix sorting queue items by size 2023-06-14 04:46:19 +03:00
Bogdan
b0f56e2840 Update translations 2023-06-13 02:03:51 +03:00
Weblate
5593837482 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
2023-06-13 01:40:09 +03:00
Qstick
8231290c7b Update Remote Path Mapping delete modal title
(cherry picked from commit 18716a00516a971f7f2eb369b920266bea24fe08)

Closes #8675
2023-06-12 22:52:50 +03:00
Weblate
0c1b88c60a Translated using Weblate (Indonesian) [skip ci]
Currently translated at 7.3% (87 of 1179 strings)

Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translation: Servarr/Radarr
2023-06-12 11:49:17 +03:00
Qstick
0b8478e4a1 Bump version to 4.6.2 2023-06-11 09:39:17 -05:00
Weblate
69e09c8687 Translated using Weblate (Russian) [skip ci]
Currently translated at 98.3% (1159 of 1179 strings)

Co-authored-by: Андрей <andryfly7@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2023-06-10 22:43:53 +03:00
Bogdan
3f2ea49023 Add style for default kind in ProgressBar 2023-06-10 03:13:47 +03:00
Bogdan
32f09633e9 Use more specific styling for kinds in ProgressBar
(cherry picked from commit dd31c913d2a974d95f3be251714ce749cfd99a72)

Fixes #8669
2023-06-10 01:45:40 +03:00
Qstick
3542b263c7 Fixed: Don't die on movie refresh when collection has been deleted from TMDB
Fixes #8664
2023-06-07 20:12:59 -05:00
Weblate
d5cc84d8c8 Translated using Weblate (Greek) [skip ci]
Currently translated at 99.5% (1174 of 1179 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 99.4% (1172 of 1179 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 98.3% (1159 of 1179 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1179 of 1179 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1179 of 1179 strings)

Co-authored-by: MoowGlax <matthieu.derouet.pro@gmail.com>
Co-authored-by: Thodoris Kalatzis <teo.kal@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: emacsdias <emacs.dias@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: splifter <a.strahlke@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translation: Servarr/Radarr
2023-06-07 17:57:09 +03:00
Bogdan
c0790060fb Fixed: (UI) Sort last movies with no release dates
Towards #8662
2023-06-07 06:52:12 +03:00
Bogdan
5ec7e86488 Add language names as hints in TMDbSettings
Add Romanian in TMDb Language Codes
Move DateTime.TryParse to if condition in TMDbParser
2023-06-05 22:17:32 +03:00
Qstick
b8abafd72f Bump version to 4.6.1 2023-06-04 11:43:43 -05:00
Bogdan
a471f1b44f Bump NLog to 5.2.0 2023-06-02 07:50:34 +03:00
Weblate
7fe34be789 Update translation files [skip ci]
Updated by "Remove blank strings" hook in Weblate.

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

Currently translated at 98.8% (1166 of 1179 strings)

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

Currently translated at 100.0% (1179 of 1179 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 98.5% (1162 of 1179 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 99.3% (1171 of 1179 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.6% (1163 of 1179 strings)

Co-authored-by: Cc95459 <954591059@qq.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jens <jensmahnke@me.com>
Co-authored-by: Thijs Waalen <contact@thijswaalen.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: foXaCe <foxace66@gmail.com>
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/nl/
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
2023-06-01 02:00:11 +03:00
Zak Saunders
471a34eabf New: Remove Rarbg Indexer due to site shutdown 2023-05-31 16:20:11 +01:00
188 changed files with 1540 additions and 2218 deletions

View File

@@ -76,7 +76,7 @@ body:
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
description: Trace logs are generally required for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
required: true

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.6.0'
majorVersion: '4.6.4'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
@@ -156,16 +157,16 @@ class Blocklist extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadBlocklist')}
</div>
</Alert>
}
{
isPopulated && !error && !items.length &&
<div>
{translate('NoHistory')}
</div>
<Alert kind={kinds.INFO}>
{translate('NoHistoryBlocklist')}
</Alert>
}
{
@@ -209,7 +210,7 @@ class Blocklist extends Component {
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title={translate('RemoveSelected')}
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist')}
message={translate('RemoveSelectedItemBlocklistMessageText')}
confirmLabel={translate('RemoveSelected')}
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryRowConnector from './HistoryRowConnector';
@@ -83,9 +84,9 @@ class History extends Component {
{
!isFetchingAny && hasError &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadHistory')}
</div>
</Alert>
}
{
@@ -93,9 +94,9 @@ class History extends Component {
// wait for the episodes to populate because they are never coming.
isPopulated && !hasError && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoHistory')}
</div>
</Alert>
}
{

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -12,7 +13,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
@@ -231,17 +232,17 @@ class Queue extends Component {
{
!isRefreshing && hasError ?
<div>
<Alert kind={kinds.DANGER}>
{translate('FailedToLoadQueue')}
</div> :
</Alert> :
null
}
{
isAllPopulated && !hasError && !items.length ?
<div>
<Alert kind={kinds.INFO}>
{translate('QueueIsEmpty')}
</div> :
</Alert> :
null
}

View File

@@ -88,7 +88,7 @@ class RemoveQueueItemsModal extends Component {
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')}
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
</div>
{
@@ -133,7 +133,7 @@ class RemoveQueueItemsModal extends Component {
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
Remove
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -1,9 +1,11 @@
import { reduce } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
@@ -105,9 +107,9 @@ class ImportMovie extends Component {
{
!rootFoldersFetching && !!rootFoldersError ?
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadRootFolders')}
</div> :
</Alert> :
null
}
@@ -116,9 +118,9 @@ class ImportMovie extends Component {
!rootFoldersFetching &&
rootFoldersPopulated &&
!unmappedFolders.length ?
<div>
<Alert kind={kinds.INFO}>
{translate('AllMoviesInPathHaveBeenImported', [path])}
</div> :
</Alert> :
null
}

View File

@@ -92,9 +92,9 @@ class ImportMovieSelectFolder extends Component {
{
!isFetching && error ?
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadRootFolders')}
</div> :
</Alert> :
null
}

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AgendaConnector from './Agenda/AgendaConnector';
import * as calendarViews from './calendarViews';
@@ -31,9 +33,9 @@ class Calendar extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadTheCalendar')}
</div>
</Alert>
}
{

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -9,7 +10,7 @@ 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 { align, icons, sortDirections } from 'Helpers/Props';
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
import styles from 'Movie/Index/MovieIndex.css';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
@@ -313,9 +314,9 @@ class Collection extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadCollections')}
</div>
</Alert>
}
{

View File

@@ -2,17 +2,12 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
function createMapStateToProps() {
return createSelector(
createCollectionSelector(),
createAllMoviesSelector(),
(
collection,
allMovies
) => {
(collection) => {
// If a movie is deleted this selector may fire before the parent
// selecors, which will result in an undefined movie, if that happens
// we want to return early here and again in the render function to avoid
@@ -22,21 +17,11 @@ function createMapStateToProps() {
return {};
}
let allGenres = [];
let libraryMovies = 0;
collection.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
libraryMovies++;
}
});
const allGenres = collection.movies.flatMap((movie) => movie.genres);
return {
...collection,
genres: Array.from(new Set(allGenres)).slice(0, 3),
missingMovies: collection.movies.length - libraryMovies
genres: Array.from(new Set(allGenres)).slice(0, 3)
};
}
);

View File

@@ -28,6 +28,14 @@ function CollectionSortMenu(props) {
>
{translate('Title')}
</SortMenuItem>
<SortMenuItem
name="missingMovies"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Missing')}
</SortMenuItem>
</MenuContent>
</SortMenu>
);

View File

@@ -580,7 +580,7 @@ EnhancedSelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,

View File

@@ -63,6 +63,7 @@ function ProviderFieldFormGroup(props) {
name,
label,
helpText,
helpTextWarning,
helpLink,
placeholder,
value,
@@ -96,6 +97,7 @@ function ProviderFieldFormGroup(props) {
name={name}
label={label}
helpText={helpText}
helpTextWarning={helpTextWarning}
helpLink={helpLink}
placeholder={placeholder}
value={value}
@@ -122,6 +124,7 @@ ProviderFieldFormGroup.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
helpText: PropTypes.string,
helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,

View File

@@ -19,6 +19,8 @@ function createCleanMovieSelector() {
year,
images,
alternateTitles = [],
tmdbId,
imdbId,
tags = []
} = movie;
@@ -29,6 +31,8 @@ function createCleanMovieSelector() {
year,
images,
alternateTitles,
tmdbId,
imdbId,
firstCharacter: title.charAt(0).toLowerCase(),
tags: tags.reduce((acc, id) => {
const matchingTag = allTags.find((tag) => tag.id === id);

View File

@@ -12,6 +12,8 @@ function MovieSearchResult(props) {
year,
images,
alternateTitles,
tmdbId,
imdbId,
tags
} = props;
@@ -47,6 +49,22 @@ function MovieSearchResult(props) {
null
}
{
match.key === 'tmdbId' && tmdbId ?
<div className={styles.alternateTitle}>
TmdbId: {tmdbId}
</div> :
null
}
{
match.key === 'imdbId' && imdbId ?
<div className={styles.alternateTitle}>
ImdbId: {imdbId}
</div> :
null
}
{
tag ?
<div className={styles.tagContainer}>
@@ -69,6 +87,8 @@ MovieSearchResult.propTypes = {
year: PropTypes.number.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
tmdbId: PropTypes.number,
imdbId: PropTypes.string,
tags: PropTypes.arrayOf(PropTypes.object).isRequired,
match: PropTypes.object.isRequired
};

View File

@@ -9,6 +9,8 @@ const fuseOptions = {
keys: [
'title',
'alternateTitles.title',
'tmdbId',
'imdbId',
'tags.label'
]
};

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
function PageSectionContent(props) {
const {
@@ -17,7 +19,7 @@ function PageSectionContent(props) {
);
} else if (!isFetching && !!error) {
return (
<div>{errorMessage}</div>
<Alert kind={kinds.DANGER}>{errorMessage}</Alert>
);
} else if (isPopulated && !error) {
return (

View File

@@ -16,6 +16,46 @@
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
color: var(--white);
transition: width 0.6s ease;
&.default {
background-color: var(--darkGray);
}
&.primary {
background-color: var(--primaryColor);
}
&.danger {
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
}
}
&.success {
background-color: var(--successColor);
}
&.purple {
background-color: var(--purple);
}
&.warning {
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
}
}
&.info {
background-color: var(--infoColor);
}
&.queue {
background-color: var(--queueColor);
}
}
.frontTextContainer {
@@ -45,46 +85,6 @@
cursor: default;
}
.default {
background-color: var(--darkGray);
}
.primary {
background-color: var(--primaryColor);
}
.danger {
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
}
}
.success {
background-color: var(--successColor);
}
.purple {
background-color: var(--purple);
}
.warning {
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
}
}
.info {
background-color: var(--infoColor);
}
.queue {
background-color: var(--queueColor);
}
.small {
height: $progressBarSmallHeight;

View File

@@ -38,7 +38,7 @@ function ProgressBar(props) {
{
showText && width ?
<div
className={styles.backTextContainer}
className={classNames(styles.backTextContainer, styles[kind])}
style={{ width: actualWidth }}
>
<div className={styles.backText}>
@@ -67,7 +67,7 @@ function ProgressBar(props) {
{
showText ?
<div
className={styles.frontTextContainer}
className={classNames(styles.frontTextContainer, styles[kind])}
style={{ width: progressPercent }}
>
<div

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -10,7 +11,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { align, icons, sortDirections } from 'Helpers/Props';
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
import styles from 'Movie/Index/MovieIndex.css';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
@@ -369,9 +370,9 @@ class DiscoverMovie extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadMovies')}
</div>
</Alert>
}
{

View File

@@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { LanguageSettingsAppState } from 'App/State/SettingsAppState';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -86,7 +87,9 @@ function SelectLanguageModalContent(props: SelectLanguageModalContentProps) {
{isFetching ? <LoadingIndicator /> : null}
{!isFetching && error ? (
<div>{translate('UnableToLoadLanguages')}</div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadLanguages')}
</Alert>
) : null}
{isPopulated && !error ? (

View File

@@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { Error } from 'App/State/AppSectionState';
import AppState from 'App/State/AppState';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -130,7 +131,9 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
{isFetching && <LoadingIndicator />}
{!isFetching && error ? (
<div>{translate('UnableToLoadQualities')}</div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</Alert>
) : null}
{isPopulated && !error ? (

View File

@@ -303,6 +303,7 @@ class MovieDetails extends Component {
selectedTabIndex
} = this.state;
const fanartUrl = getFanartUrl(images);
const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150);
return (
@@ -361,9 +362,11 @@ class MovieDetails extends Component {
<div className={styles.header}>
<div
className={styles.backdrop}
style={{
backgroundImage: `url(${getFanartUrl(images)})`
}}
style={
fanartUrl ?
{ backgroundImage: `url(${fanartUrl})` } :
null
}
>
<div className={styles.backdropOverlay} />
</div>

View File

@@ -10,6 +10,7 @@ import { SelectProvider } from 'App/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import MoviesAppState, { MovieIndexAppState } from 'App/State/MoviesAppState';
import { RSS_SYNC } from 'Commands/commandNames';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -20,7 +21,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import withScrollPosition from 'Components/withScrollPosition';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import NoMovie from 'Movie/NoMovie';
@@ -337,7 +338,9 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
{!isFetching && !!error ? (
<div>{translate('UnableToLoadMovies')}</div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadMovies')}
</Alert>
) : null}
{isLoaded ? (

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -109,9 +110,9 @@ class FileEditModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -92,9 +93,9 @@ class SelectQualityModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</div>
</Alert>
}
{

View File

@@ -1,8 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import RootFolderRowConnector from './RootFolderRowConnector';
@@ -44,9 +46,9 @@ function RootFolders(props) {
if (!isFetching && !!error) {
return (
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadRootFolders')}
</div>
</Alert>
);
}

View File

@@ -152,13 +152,7 @@ class CustomFormat extends Component {
isOpen={this.state.isDeleteCustomFormatModalOpen}
kind={kinds.DANGER}
title={translate('DeleteCustomFormat')}
message={
<div>
<div>
{translate('AreYouSureYouWantToDeleteFormat', [name])}
</div>
</div>
}
message={translate('DeleteCustomFormatMessageText', [name])}
confirmLabel={translate('Delete')}
isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteCustomFormat}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import ClipboardButton from 'Components/Link/ClipboardButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -41,9 +42,9 @@ class ExportCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadCustomFormats')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -11,7 +12,7 @@ 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, sizes } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ImportCustomFormatModalContent.css';
@@ -95,9 +96,9 @@ class ImportCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadCustomFormats')}
</div>
</Alert>
}
{

View File

@@ -78,7 +78,7 @@ class Specification extends Component {
<IconButton
className={styles.cloneButton}
title={translate('CloneFormatTag')}
title={translate('CloneCondition')}
name={icons.CLONE}
onPress={this.onCloneSpecificationPress}
/>
@@ -114,14 +114,8 @@ class Specification extends Component {
<ConfirmModal
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteCustomFormat')}
message={
<div>
<div>
{translate('AreYouSureYouWantToDeleteFormat', [name])}
</div>
</div>
}
title={translate('DeleteCondition')}
message={translate('DeleteConditionMessageText', [name])}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}

View File

@@ -29,9 +29,9 @@ function DownloadClientOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadDownloadClientOptions')}
</div>
</Alert>
}
{

View File

@@ -88,8 +88,8 @@ class RemotePathMapping extends Component {
<ConfirmModal
isOpen={this.state.isDeleteRemotePathMappingModalOpen}
kind={kinds.DANGER}
title={translate('DeleteDelayProfile')}
message={translate('AreYouSureYouWantToDeleteThisRemotePathMapping')}
title={translate('DeleteRemotePathMapping')}
message={translate('DeleteRemotePathMappingMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteRemotePathMapping}
onCancel={this.onDeleteRemotePathMappingModalClose}

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
@@ -123,9 +124,9 @@ class GeneralSettings extends Component {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadGeneralSettings')}
</div>
</Alert>
}
{

View File

@@ -168,7 +168,7 @@ class SecuritySettings extends Component {
isOpen={this.state.isConfirmApiKeyResetModalOpen}
kind={kinds.DANGER}
title={translate('ResetAPIKey')}
message={translate('AreYouSureYouWantToResetYourAPIKey')}
message={translate('ResetAPIKeyMessageText')}
confirmLabel={translate('Reset')}
onConfirm={this.onConfirmResetApiKey}
onCancel={this.onCloseResetApiKeyModal}

View File

@@ -89,7 +89,7 @@ class ImportListExclusion extends Component {
isOpen={this.state.isDeleteImportExclusionModalOpen}
kind={kinds.DANGER}
title={translate('DeleteImportListExclusion')}
message={translate('AreYouSureYouWantToDeleteThisImportListExclusion')}
message={translate('DeleteImportListExclusionMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteImportExclusion}
onCancel={this.onDeleteImportExclusionModalClose}

View File

@@ -1,12 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function ImportListOptions(props) {
@@ -37,9 +38,9 @@ function ImportListOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadListOptions')}
</div>
</Alert>
}
{

View File

@@ -1,12 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function IndexerOptions(props) {
@@ -28,9 +29,9 @@ function IndexerOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadIndexerOptions')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { inputTypes, sizes } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
@@ -72,9 +73,9 @@ class MediaManagement extends Component {
{
!isFetching && error ?
<FieldSet legend={translate('NamingSettings')}>
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadMediaManagementSettings')}
</div>
</Alert>
</FieldSet> : null
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -7,7 +8,7 @@ import FormInputButton from 'Components/Form/FormInputButton';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, sizes } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import NamingModal from './NamingModal';
import styles from './Naming.css';
@@ -110,9 +111,9 @@ class Naming extends Component {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadNamingSettings')}
</div>
</Alert>
}
{

View File

@@ -6,8 +6,9 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import Alert from '../../../Components/Alert';
// Note: Do Not Translate Certification Countries
@@ -43,9 +44,9 @@ function MetadataOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadIndexerOptions')}
</div>
</Alert>
}
{

View File

@@ -141,7 +141,7 @@ class DelayProfile extends Component {
isOpen={this.state.isDeleteDelayProfileModalOpen}
kind={kinds.DANGER}
title={translate('DeleteDelayProfile')}
message={translate('AreYouSureYouWantToDeleteThisDelayProfile')}
message={translate('DeleteDelayProfileMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteDelayProfile}
onCancel={this.onDeleteDelayProfileModalClose}

View File

@@ -60,17 +60,19 @@ class ResetQualityDefinitionsModalContent extends Component {
<ModalBody>
<div className={styles.messageContainer}>
{translate('AreYouSureYouWantToResetQualityDefinitions')}
{translate('ResetQualityDefinitionsMessageText')}
</div>
<FormGroup>
<FormLabel>{translate('ResetTitles')}</FormLabel>
<FormLabel>
{translate('ResetTitles')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="resetDefinitionTitles"
value={resetDefinitionTitles}
helpText={translate('ResetTitlesHelpText')}
helpText={translate('ResetDefinitionTitlesHelpText')}
onChange={this.onResetDefinitionTitlesChange}
/>
</FormGroup>

View File

@@ -1,7 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import TagConnector from './TagConnector';
import styles from './Tags.css';
@@ -14,9 +16,9 @@ function Tags(props) {
if (!items.length) {
return (
<div>
<Alert kind={kinds.INFO}>
{translate('NoTagsHaveBeenAddedYet')}
</div>
</Alert>
);
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import themes from 'Styles/Themes';
import titleCase from 'Utilities/String/titleCase';
@@ -87,9 +88,9 @@ class UISettings extends Component {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadUISettings')}
</div>
</Alert>
}
{

View File

@@ -234,6 +234,18 @@ export const sortPredicates = {
}
return padNumber(result.toString(), 2) + qualityName;
},
inCinemas: function(item) {
return item.inCinemas || '';
},
physicalRelease: function(item) {
return item.physicalRelease || '';
},
digitalRelease: function(item) {
return item.digitalRelease || '';
}
};

View File

@@ -1,7 +1,7 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest';
@@ -60,8 +60,30 @@ export const defaultState = {
filters: [
{
key: 'all',
label: 'All',
label: translate('All'),
filters: []
},
{
key: 'missing',
label: translate('Missing'),
filters: [
{
key: 'missingMovies',
value: 0,
type: filterTypes.GREATER_THAN
}
]
},
{
key: 'complete',
label: translate('Complete'),
filters: [
{
key: 'missingMovies',
value: 0,
type: filterTypes.EQUAL
}
]
}
],

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -8,7 +9,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import BackupRow from './BackupRow';
import RestoreBackupModalConnector from './RestoreBackupModalConnector';
@@ -107,16 +108,16 @@ class Backups extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadBackups')}
</div>
</Alert>
}
{
noBackups &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoBackupsAreAvailable')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import LogsTableRow from './LogsTableRow';
@@ -82,9 +83,9 @@ function LogsTable(props) {
{
isPopulated && !error && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoEventsFound')}
</div>
</Alert>
}
{

View File

@@ -11,7 +11,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import LogsNavMenu from '../LogsNavMenu';
import LogFilesTableRow from './LogFilesTableRow';
@@ -118,9 +118,9 @@ class LogFiles extends Component {
{
!isFetching && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoLogFiles')}
</div>
</Alert>
}
</PageContentBody>
</PageContent>

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import SpinnerButton from 'Components/Link/SpinnerButton';
@@ -61,9 +62,9 @@ class Updates extends Component {
{
noUpdates &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoUpdatesAreAvailable')}
</div>
</Alert>
}
{

View File

@@ -104,7 +104,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
</ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'">

View File

@@ -27,19 +27,47 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
schema.Should().Contain(c => c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.HelpTextWarning == "Mandatory Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.HelpTextWarning == "Mandatory First Name" && (string)c.Value == "Bob");
}
[Test]
public void schema_should_have_nested_fields()
{
var model = new NestedTestModel
{
Name =
{
FirstName = "Bob",
LastName = "Poop"
}
};
var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 0 && c.Name == "name.firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.HelpTextWarning == "Mandatory First Name" && (string)c.Value == "Bob");
schema.Should().Contain(c => c.Order == 1 && c.Name == "name.lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.HelpTextWarning == "Mandatory Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 2 && c.Name == "quote" && c.Label == "Quote" && c.HelpText == "Your Favorite Quote");
}
}
public class TestModel
{
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name")]
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name", HelpTextWarning = "Mandatory First Name")]
public string FirstName { get; set; }
[FieldDefinition(1, Label = "Last Name", HelpText = "Your Last Name")]
[FieldDefinition(1, Label = "Last Name", HelpText = "Your Last Name", HelpTextWarning = "Mandatory Last Name")]
public string LastName { get; set; }
public string Other { get; set; }
}
public class NestedTestModel
{
[FieldDefinition(0)]
public TestModel Name { get; set; } = new TestModel();
[FieldDefinition(1, Label = "Quote", HelpText = "Your Favorite Quote")]
public string Quote { get; set; }
}
}

View File

@@ -10,7 +10,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Indexer Urls
[TestCase(@"https://iptorrents.com/torrents/rss?u=mySecret;tp=mySecret;l5;download")]
[TestCase(@"http://rss.torrentleech.org/mySecret")]
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/filename.torrent")]
[TestCase(@"https://rss24h.torrentleech.org/mySecret")]
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
[TestCase(@"https://www.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
@@ -44,6 +46,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Deluge
[TestCase(@",{""download_location"": ""C:\Users\\mySecret mySecret\\Downloads""}")]
[TestCase(@",{""download_location"": ""/home/mySecret/Downloads""}")]
[TestCase(@",{""download_location"": ""/Users/mySecret/Downloads""}")]
[TestCase(@"auth.login(""mySecret"")")]
// Download Station
@@ -59,8 +62,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Internal
[TestCase(@"OutputPath=/home/mySecret/Downloads")]
[TestCase(@"OutputPath=/Users/mySecret/Downloads")]
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlinking episode file: /Users/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("Hardlink '/Users/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]

View File

@@ -158,6 +158,25 @@ namespace NzbDrone.Common.Test
path.GetParentPath().Should().Be(parentPath);
}
[TestCase(@"C:\Test\mydir", "Test")]
[TestCase(@"C:\Test\", @"C:\")]
[TestCase(@"C:\", null)]
[TestCase(@"\\server\share", null)]
[TestCase(@"\\server\share\test", @"\\server\share")]
public void path_should_return_parent_name_windows(string path, string parentPath)
{
WindowsOnly();
path.GetParentName().Should().Be(parentPath);
}
[TestCase(@"/", null)]
[TestCase(@"/test", "/")]
public void path_should_return_parent_name_mono(string path, string parentPath)
{
PosixOnly();
path.GetParentName().Should().Be(parentPath);
}
[Test]
public void path_should_return_parent_for_oversized_path()
{

View File

@@ -87,9 +87,7 @@ namespace NzbDrone.Common.Extensions
public static string GetParentPath(this string childPath)
{
var cleanPath = OsInfo.IsWindows
? PARENT_PATH_END_SLASH_REGEX.Replace(childPath, "")
: childPath.TrimEnd(Path.DirectorySeparatorChar);
var cleanPath = childPath.GetCleanPath();
if (cleanPath.IsNullOrWhiteSpace())
{
@@ -99,6 +97,18 @@ namespace NzbDrone.Common.Extensions
return Directory.GetParent(cleanPath)?.FullName;
}
public static string GetParentName(this string childPath)
{
var cleanPath = childPath.GetCleanPath();
if (cleanPath.IsNullOrWhiteSpace())
{
return null;
}
return Directory.GetParent(cleanPath)?.Name;
}
public static string GetCleanPath(this string path)
{
var cleanPath = OsInfo.IsWindows

View File

@@ -60,8 +60,7 @@ namespace NzbDrone.Common.Http
if (request.AllowAutoRedirect && response.HasHttpRedirect)
{
var autoRedirectChain = new List<string>();
autoRedirectChain.Add(request.Url.ToString());
var autoRedirectChain = new List<string> { request.Url.ToString() };
do
{
@@ -75,6 +74,14 @@ namespace NzbDrone.Common.Http
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
}
// 302 or 303 should default to GET on redirect even if POST on original
if (RequestRequiresForceGet(response.StatusCode, response.Request.Method))
{
request.Method = HttpMethod.Get;
request.ContentData = null;
request.ContentSummary = null;
}
response = ExecuteRequest(request, cookieContainer);
}
while (response.HasHttpRedirect);
@@ -105,6 +112,16 @@ namespace NzbDrone.Common.Http
return response;
}
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
{
return statusCode switch
{
HttpStatusCode.Moved or HttpStatusCode.Found or HttpStatusCode.MultipleChoices => requestMethod == HttpMethod.Post,
HttpStatusCode.SeeOther => requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head,
_ => false,
};
}
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
{
foreach (var interceptor in _requestInterceptors)

View File

@@ -50,6 +50,8 @@ namespace NzbDrone.Common.Http
public bool HasHttpError => (int)StatusCode >= 400;
public bool HasHttpServerError => (int)StatusCode >= 500;
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
StatusCode == HttpStatusCode.MovedPermanently ||
StatusCode == HttpStatusCode.Found ||

View File

@@ -7,55 +7,55 @@ namespace NzbDrone.Common.Instrumentation
{
public class CleanseLogMessage
{
private static readonly Regex[] CleansingRules = new[]
{
// Url
new Regex(@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
private static readonly Regex[] CleansingRules =
{
// Url
new (@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Path
new Regex(@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Path
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"/(home|Users)/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new (@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Sabnzbd
new Regex(@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Sabnzbd
new (@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// uTorrent
new Regex(@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// uTorrent
new (@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Deluge
new Regex(@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Deluge
new (@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// BroadcastheNet
new Regex(@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// BroadcastheNet
new (@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Plex
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Plex
new (@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Notifiarr
new Regex(@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Notifiarr
new (@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
// Discord
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
public static string Cleanse(string message)
{
@@ -67,15 +67,15 @@ namespace NzbDrone.Common.Instrumentation
foreach (var regex in CleansingRules)
{
message = regex.Replace(message, m =>
{
var value = m.Value;
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
{
var value = m.Value;
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
{
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
}
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
}
return value;
});
return value;
});
}
message = CleanseRemoteIPRegex.Replace(message, CleanseRemoteIP);
@@ -86,7 +86,6 @@ namespace NzbDrone.Common.Instrumentation
private static string CleanseRemoteIP(Match match)
{
var group = match.Groups[1];
var valueAll = match.Value;
var valueIP = group.Value;
if (IPAddress.TryParse(valueIP, out var address) && !address.IsLocalAddress())

View File

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

View File

@@ -8,8 +8,8 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="NLog" Version="5.2.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.0" />
<PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="Sentry" Version="3.23.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />

View File

@@ -121,6 +121,11 @@ namespace NzbDrone.Common.Serializer
return JsonConvert.SerializeObject(obj, SerializerSettings);
}
public static string ToJson(this object obj, Formatting formatting)
{
return JsonConvert.SerializeObject(obj, formatting, SerializerSettings);
}
public static void Serialize<TModel>(TModel model, TextWriter outputStream)
{
var jsonTextWriter = new JsonTextWriter(outputStream);

View File

@@ -0,0 +1,64 @@
using System;
using System.Linq;
using Dapper;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class remove_invalid_roksbox_metadata_imagesFixture : MigrationTest<remove_invalid_roksbox_metadata_images>
{
[Test]
public void should_remove_incorrect_roksbox_metadata_images()
{
var db = WithDapperMigrationTestDb(c =>
{
c.Insert.IntoTable("MetadataFiles").Row(new
{
MovieId = 1,
Consumer = "RoksboxMetadata",
Type = 5,
RelativePath = @"metadata\Movie Title (2023).jpg",
LastUpdated = "2023-01-21 00:00:00.000",
Added = "2023-01-21 00:00:00.000",
MovieFileId = 1,
Extension = ".jpg"
});
c.Insert.IntoTable("MetadataFiles").Row(new
{
MovieId = 1,
Consumer = "RoksboxMetadata",
Type = 5,
RelativePath = @"Movie Title (2023).jpg",
LastUpdated = "2023-01-21 00:00:00.000",
MovieFileId = 1,
Added = "2023-01-21 00:00:00.000",
Extension = ".jpg"
});
});
var metadataFiles = db.Query<MetadataFile223>("SELECT * FROM \"MetadataFiles\"");
metadataFiles.Should().HaveCount(1);
metadataFiles.First().RelativePath.Should().NotContain("metadata");
}
}
public class MetadataFile223
{
public int Id { get; set; }
public int MovieId { get; set; }
public int? MovieFileId { get; set; }
public string RelativePath { get; set; }
public DateTime Added { get; set; }
public DateTime LastUpdated { get; set; }
public string Extension { get; set; }
public string Hash { get; set; }
public string Consumer { get; set; }
public int Type { get; set; }
}
}

View File

@@ -225,6 +225,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
[TestCase("checkingDL")]
[TestCase("checkingUP")]
[TestCase("metaDL")]
[TestCase("checkingResumeData")]
public void queued_item_should_have_required_properties(string state)
{
var torrent = new QBittorrentTorrent

View File

@@ -1,29 +0,0 @@
[
{
"f": "Thunderbirds.Are.Go.S01E09.Slingshot.1080p.WEB-DL.AAC2.0.H.264-Coo7[rartv]",
"c": "TV HD Episodes",
"d": "magnet:?xt=urn:btih:ff4737b5230307836ec8abce6ab73727f1358bf3&dn=Thunderbirds.Are.Go.S01E09.Slingshot.1080p.WEB-DL.AAC2.0.H.264-Coo7%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"s": "44",
"l": "19",
"t": "896238116",
"u": "2015-05-24 19:36:09"
},
{
"f": "Thunderbirds.Are.Go.S01E10.Tunnels.Of.Time.720p.HDTV.x264-RDVAS[rartv]",
"c": "TV HD Episodes",
"d": "magnet:?xt=urn:btih:47bf1d7bfb72a83300bbe68d0b6aa09591e7a0a1&dn=Thunderbirds.Are.Go.S01E10.Tunnels.Of.Time.720p.HDTV.x264-RDVAS%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"s": "179",
"l": "125",
"t": "556055350",
"u": "2015-05-24 19:07:59"
},
{
"f": "Tatau.S01E06.1080p.WEB-DL.AAC2.0.H.264-BS[rartv]",
"c": "TV HD Episodes",
"d": "magnet:?xt=urn:btih:8857e9b011c7a0483351371721fa9f3ba356dd73&dn=Tatau.S01E06.1080p.WEB-DL.AAC2.0.H.264-BS%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"s": "27",
"l": "22",
"t": "1652442143",
"u": "2015-05-24 18:54:49"
}
]

View File

@@ -1,84 +0,0 @@
{
"torrent_results": [
{
"title": "Sense8.S01E01.WEBRip.x264-FGT",
"category": "TV Episodes",
"download": "magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"seeders": 304,
"leechers": 200,
"size": 564198371,
"pubdate": "2015-06-05 16:58:11 +0000",
"episode_info": {
"imdb": "tt2431438",
"tvrage": "35197",
"tvdb": "268156",
"airdate": "2015-06-05",
"epnum": "01",
"seasonnum": "1",
"title": "Limbic Resonance"
},
"ranked": 1,
"info_page": "https:\/\/torrentapi.org\/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5"
},
{
"title": "Sense8.S01E02.WEBRip.x264-FGT",
"category": "TV Episodes",
"download": "magnet:?xt=urn:btih:e5ab5f398d929c791ac4f1d5bb2fba0997372a91&dn=Sense8.S01E02.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"seeders": 299,
"leechers": 209,
"size": 486918696,
"pubdate": "2015-06-05 16:58:23 +0000",
"episode_info": {
"imdb": "tt2431438",
"tvrage": "35197",
"tvdb": "268156",
"airdate": "2015-06-05",
"epnum": "02",
"seasonnum": "1",
"title": "I Am Also A We"
},
"ranked": 1,
"info_page": "https:\/\/torrentapi.org\/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_7__e5ab5f398d"
},
{
"title": "Comedy.Bang.Bang.S04E20.HDTV.x264-YesTV[rartv]",
"category": "TV Episodes",
"download": "magnet:?xt=urn:btih:0ed8bd14206e211eef9d3d36a48b038f280ef20c&dn=Comedy.Bang.Bang.S04E20.HDTV.x264-YesTV%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"seeders": 45,
"leechers": 15,
"size": 154208067,
"pubdate": "2015-06-05 17:33:37 +0000",
"episode_info": {
"imdb": "tt2176287",
"tvrage": "31483",
"tvdb": "258310",
"airdate": "2015-06-05",
"epnum": "20",
"seasonnum": "4",
"title": "Judd Apatow Wears a Polo and Blue Suede Shoes"
},
"ranked": 1,
"info_page": "https:\/\/torrentapi.org\/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_6_7__0ed8bd1420"
},
{
"title": "Comedy.Bang.Bang.S04E20.720p.HDTV.x264-YesTV[rartv]",
"category": "TV HD Episodes",
"download": "magnet:?xt=urn:btih:10257dee06327ba66cc2674e08d71b3bb2089b06&dn=Comedy.Bang.Bang.S04E20.720p.HDTV.x264-YesTV%5Brartv%5D&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce",
"seeders": 22,
"leechers": 6,
"size": 514574549,
"pubdate": "2015-06-05 17:33:49 +0000",
"episode_info": {
"imdb": "tt2176287",
"tvrage": "31483",
"tvdb": "258310",
"airdate": "2015-06-05",
"epnum": "20",
"seasonnum": "4",
"title": "Judd Apatow Wears a Polo and Blue Suede Shoes"
},
"ranked": 1,
"info_page": "https:\/\/torrentapi.org\/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_6_8__10257dee06"
}
]
}

View File

@@ -1,141 +0,0 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Rarbg;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
{
[TestFixture]
public class RarbgFixture : CoreTest<Rarbg>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Rarbg",
Settings = new RarbgSettings()
};
Mocker.GetMock<IRarbgTokenProvider>()
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
.Returns("validtoken");
}
[Test]
public void should_parse_recent_feed_from_Rarbg()
{
var recentFeed = ReadAllText(@"Files/Indexers/Rarbg/RecentFeed_v2.json");
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 releases = Subject.FetchRecent();
releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.Title.Should().Be("Sense8.S01E01.WEBRip.x264-FGT");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
torrentInfo.InfoUrl.Should().Be("https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id=Radarr");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-05 16:58:11 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be(564198371);
torrentInfo.InfoHash.Should().BeNull();
torrentInfo.MagnetUrl.Should().BeNull();
torrentInfo.Peers.Should().Be(304 + 200);
torrentInfo.Seeders.Should().Be(304);
}
[Test]
public void should_parse_error_20_as_empty_results()
{
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "{ error_code: 20, error: \"some message\" }"));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(0);
}
[Test]
public void should_warn_on_unknown_error()
{
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "{ error_code: 25, error: \"some message\" }"));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(0);
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_warn_and_record_failure_on_429_response()
{
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "", HttpStatusCode.TooManyRequests));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(0);
ExceptionVerification.ExpectedWarns(1);
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.Is<TimeSpan>(t => t == TimeSpan.FromMinutes(2))));
}
[Test]
public void should_warn_and_record_failure_on_520_response()
{
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "", (HttpStatusCode)520));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(0);
ExceptionVerification.ExpectedWarns(1);
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.Is<TimeSpan>(t => t == TimeSpan.FromMinutes(3))));
}
// Uncomment when RarbgParser is updated
// [Test]
// public void should_warn_and_record_failure_on_200_response_with_rate_limit()
// {
// Mocker.GetMock<IHttpClient>()
// .Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
// .Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), "{ rate_limit: 1 }"));
//
// var releases = Subject.FetchRecent();
//
// releases.Should().HaveCount(0);
//
// ExceptionVerification.ExpectedWarns(1);
//
// Mocker.GetMock<IIndexerStatusService>()
// .Verify(v => v.RecordFailure(It.IsAny<int>(), It.Is<TimeSpan>(t => t == TimeSpan.FromMinutes(5))));
// }
}
}

View File

@@ -109,6 +109,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
[TestCase("Movie.Title.2005.2160p.UHD.BluRay.TrueHD 7.1.Atmos.x265 - HQMUX", "HQMUX")]
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG (Blue Lock, Multi-Subs)", "VARYG")]
[TestCase("Movie Title (2023) (1080p BluRay x265 SDR AAC 2.0 English Vyndros)", "Vyndros")]
public void should_parse_exception_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);

View File

@@ -0,0 +1,43 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class UrlFixture : CoreTest
{
[TestCase("[www.test.com] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("test.net - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[www.test-hyphen.com] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("www.test123.org - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[test.co.uk] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("www.test-hyphen.net.au - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[www.test123.co.nz] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("test-hyphen123.org.au - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[www.test123.de] - Mad Movie Title 2023 [Bluray720p]", "Mad Movie Title")]
[TestCase("www.test-hyphen.de - Mad Movie Title 2023 [Bluray1080p]", "Mad Movie Title")]
[TestCase("www.test123.co.za - The Movie Title Bros. (2023)", "The Movie Title Bros.")]
[TestCase("[www.test-hyphen.ca] - Movie Title (2023)", "Movie Title")]
[TestCase("test123.ca - Movie Time 2023 720p HDTV x264 CRON", "Movie Time")]
[TestCase("[www.test-hyphen123.co.za] - Movie Title 2023", "Movie Title")]
public void should_not_parse_url_in_name(string postTitle, string title)
{
var result = Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.CleanMovieTitle();
result.Should().Be(title.CleanMovieTitle());
}
[TestCase("Movie.2023.English.HDTV.XviD-LOL[www.abb.com]", "LOL")]
[TestCase("Movie Title 2023 English HDTV XviD LOL[www.academy.org]", null)]
[TestCase("Movie Title Now 2023 DVDRip XviD RUNNER[www.aetna.net]", null)]
[TestCase("Movie.Title.2023.DVDRip.XviD-RUNNER[www.alfaromeo.io]", "RUNNER")]
[TestCase("Movie.Title.2023.English.HDTV.XviD-LOL[www.abbott.gov]", "LOL")]
[TestCase("Movie Title 2023 English HDTV XviD LOL[www.actor.org]", null)]
[TestCase("Movie Title Future 2023 DVDRip XviD RUNNER[www.allstate.net]", null)]
public void should_not_parse_url_in_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
}
}

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Annotations
public string Label { get; set; }
public string Unit { get; set; }
public string HelpText { get; set; }
public string HelpTextWarning { get; set; }
public string HelpLink { get; set; }
public FieldType Type { get; set; }
public bool Advanced { get; set; }

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(222)]
public class remove_rarbg : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Indexers").Row(new { Implementation = "Rarbg" });
}
}
}

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(223)]
public class remove_invalid_roksbox_metadata_images : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
IfDatabase("sqlite").Execute.Sql("DELETE FROM \"MetadataFiles\" WHERE \"Consumer\" = 'RoksboxMetadata' AND \"Type\" = 5 AND (\"RelativePath\" LIKE 'metadata/%' OR \"RelativePath\" LIKE 'metadata\\%')");
IfDatabase("postgresql").Execute.Sql("DELETE FROM \"MetadataFiles\" WHERE \"Consumer\" = 'RoksboxMetadata' AND \"Type\" = 5 AND (\"RelativePath\" LIKE 'metadata/%' OR \"RelativePath\" LIKE 'metadata\\\\%')");
}
}
}

View File

@@ -251,6 +251,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
case "queuedDL": // queuing is enabled and torrent is queued for download
case "checkingDL": // same as checkingUP, but torrent has NOT finished downloading
case "checkingUP": // torrent has finished downloading and is being checked. Set when `recheck torrent on completion` is enabled. In the event the check fails we shouldn't treat it as completed.
case "checkingResumeData": // torrent is checking resume data on load
item.Status = DownloadItemStatus.Queued;
break;

View File

@@ -84,7 +84,8 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
if (extension == ".jpg")
{
if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase))
if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase) &&
!path.GetParentName().Equals("metadata", StringComparison.InvariantCultureIgnoreCase))
{
metadata.Type = MetadataType.MovieImage;
return metadata;

View File

@@ -48,13 +48,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
try
{
var status = client.GetStatus();
var folders = status.OutputRootFolders;
var folders = status.OutputRootFolders.Where(folder => rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)));
foreach (var folder in folders)
{
if (rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot"), client.Definition.Name, folder.FullPath), "#downloads-in-root-folder");
}
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot"), client.Definition.Name, folder.FullPath), "#downloads-in-root-folder");
}
}
catch (DownloadClientException ex)

View File

@@ -32,7 +32,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var clients = _downloadClientProvider.GetDownloadClients();
var clients = _downloadClientProvider.GetDownloadClients(true);
foreach (var client in clients)
{

View File

@@ -23,12 +23,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var jackettAllProviders = _providerFactory.All().Where(
i => i.ConfigContract.Equals("TorznabSettings") &&
((i.Settings as TorznabSettings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)));
var jackettAllProviders = _providerFactory.All()
.Where(
i => i.ConfigContract.Equals("TorznabSettings") &&
(((TorznabSettings)i.Settings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)))
.ToArray();
if (jackettAllProviders.Empty())
{

View File

@@ -28,13 +28,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.Before(
DateTime.UtcNow.AddHours(-6)))
.ToList();
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.Before(DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{

View File

@@ -26,13 +26,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.After(
DateTime.UtcNow.AddHours(-6)))
.ToList();
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.After(DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{

View File

@@ -21,10 +21,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
// Not best for optimization but due to possible symlinks and junctions, we get mounts based on series path so internals can handle mount resolution.
var mounts = _movieService.AllMoviePaths()
.Select(p => _diskProvider.GetMount(p.Value))
.Where(m => m != null && m.MountOptions != null && m.MountOptions.IsReadOnly)
.DistinctBy(m => m.RootDirectory)
.ToList();
.Select(p => _diskProvider.GetMount(p.Value))
.Where(m => m is { MountOptions.IsReadOnly: true })
.DistinctBy(m => m.RootDirectory)
.ToList();
if (mounts.Any())
{

View File

@@ -18,10 +18,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var ptpIndexers = _indexerFactory.All().Where(i => i.Settings.GetType() == typeof(PassThePopcornSettings));
var ptpIndexers = _indexerFactory.All()
.Where(i => i.Settings.GetType() == typeof(PassThePopcornSettings));
var ptpIndexerOldSettings = ptpIndexers
.Where(i => (i.Settings as PassThePopcornSettings).APIUser.IsNullOrWhiteSpace()).Select(i => i.Name);
.Where(i => ((PassThePopcornSettings)i.Settings).APIUser.IsNullOrWhiteSpace()).Select(i => i.Name)
.ToList();
if (ptpIndexerOldSettings.Any())
{

View File

@@ -31,35 +31,38 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
if (_configService.ProxyEnabled)
if (!_configService.ProxyEnabled)
{
var addresses = Dns.GetHostAddresses(_configService.ProxyHostname);
if (!addresses.Any())
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckResolveIpMessage"), _configService.ProxyHostname), "#proxy-failed-resolve-ip");
}
return new HealthCheck(GetType());
}
var request = _cloudRequestBuilder.Create()
.Resource("/ping")
.Build();
var addresses = Dns.GetHostAddresses(_configService.ProxyHostname);
try
{
var response = _client.Execute(request);
if (!addresses.Any())
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckResolveIpMessage"), _configService.ProxyHostname), "#proxy-failed-resolve-ip");
}
// We only care about 400 responses, other error codes can be ignored
if (response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error("Proxy Health Check failed: {0}", response.StatusCode);
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckBadRequestMessage"), response.StatusCode), "#proxy-failed-test");
}
}
catch (Exception ex)
var request = _cloudRequestBuilder.Create()
.Resource("/ping")
.Build();
try
{
var response = _client.Execute(request);
// We only care about 400 responses, other error codes can be ignored
if (response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Proxy Health Check failed");
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckFailedToTestMessage"), request.Url), "#proxy-failed-test");
_logger.Error("Proxy Health Check failed: {0}", response.StatusCode);
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckBadRequestMessage"), response.StatusCode), "#proxy-failed-test");
}
}
catch (Exception ex)
{
_logger.Error(ex, "Proxy Health Check failed");
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("ProxyCheckFailedToTestMessage"), request.Url), "#proxy-failed-test");
}
return new HealthCheck(GetType());
}

View File

@@ -62,6 +62,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
var status = client.GetStatus();
var folders = status.OutputRootFolders;
foreach (var folder in folders)
{
if (!folder.IsValid)
@@ -70,14 +71,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-remote-path-mapping");
}
else if (_osInfo.IsDocker)
if (_osInfo.IsDocker)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckBadDockerPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#docker-bad-remote-path-mapping");
}
else
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-download-client-settings");
}
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalWrongOSPath"), client.Definition.Name, folder.FullPath, _osInfo.Name), "#bad-download-client-settings");
}
if (!_diskProvider.FolderExists(folder.FullPath))
@@ -86,14 +86,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckDockerFolderMissing"), client.Definition.Name, folder.FullPath), "#docker-bad-remote-path-mapping");
}
else if (!status.IsLocalhost)
if (!status.IsLocalhost)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckLocalFolderMissing"), client.Definition.Name, folder.FullPath), "#bad-remote-path-mapping");
}
else
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckGenericPermissions"), client.Definition.Name, folder.FullPath), "#permissions-error");
}
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckGenericPermissions"), client.Definition.Name, folder.FullPath), "#permissions-error");
}
}
}
@@ -122,24 +121,21 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType());
}
if (typeof(MovieImportFailedEvent).IsAssignableFrom(message.GetType()))
if (message is MovieImportFailedEvent failureMessage)
{
var failureMessage = (MovieImportFailedEvent)message;
// if we can see the file exists but the import failed then likely a permissions issue
if (failureMessage.MovieInfo != null)
{
var moviePath = failureMessage.MovieInfo.Path;
if (_diskProvider.FileExists(moviePath))
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckDownloadPermissions"), moviePath), "#permissions-error");
}
else
{
// If the file doesn't exist but MovieInfo is not null then the message is coming from
// ImportApprovedMovies and the file must have been removed part way through processing
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFileRemoved"), moviePath), "#remote-path-file-removed");
}
// If the file doesn't exist but MovieInfo is not null then the message is coming from
// ImportApprovedMovies and the file must have been removed part way through processing
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFileRemoved"), moviePath), "#remote-path-file-removed");
}
// If the previous case did not match then the failure occured in DownloadedMovieImportService,
@@ -170,14 +166,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-remote-path-mapping");
}
else if (_osInfo.IsDocker)
if (_osInfo.IsDocker)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesBadDockerPath"), client.Definition.Name, dlpath, _osInfo.Name), "#docker-bad-remote-path-mapping");
}
else
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesLocalWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-download-client-settings");
}
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesLocalWrongOSPath"), client.Definition.Name, dlpath, _osInfo.Name), "#bad-download-client-settings");
}
if (_diskProvider.FolderExists(dlpath))
@@ -190,15 +185,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFolderPermissions"), client.Definition.Name, dlpath), "#docker-bad-remote-path-mapping");
}
else if (!status.IsLocalhost)
if (!status.IsLocalhost)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckRemoteDownloadClient"), client.Definition.Name, dlpath), "#bad-remote-path-mapping");
}
else
{
// path mappings shouldn't be needed locally so probably a permissions issue
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesGenericPermissions"), client.Definition.Name, dlpath), "#permissions-error");
}
// path mappings shouldn't be needed locally so probably a permissions issue
return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RemotePathMappingCheckFilesGenericPermissions"), client.Definition.Name, dlpath), "#permissions-error");
}
catch (DownloadClientException ex)
{
@@ -215,10 +209,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType());
}
else
{
return Check();
}
return Check();
}
}
}

View File

@@ -29,11 +29,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var rootFolders = _movieService.AllMoviePaths()
.Select(s => _rootFolderService.GetBestRootFolderPath(s.Value))
.Distinct();
.Select(s => _rootFolderService.GetBestRootFolderPath(s.Value))
.Distinct();
var missingRootFolders = rootFolders.Where(s => !_diskProvider.FolderExists(s))
.ToList();
.ToList();
if (missingRootFolders.Any())
{

View File

@@ -22,7 +22,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var discordSlackNotifications = _notificationFactory.GetAvailableProviders().Where(n => n.ConfigContract.Equals("SlackSettings") && (n.Definition.Settings as SlackSettings).WebHookUrl.Contains("discord"));
var discordSlackNotifications = _notificationFactory.GetAvailableProviders()
.Where(n => n.ConfigContract.Equals("SlackSettings") && ((SlackSettings)n.Definition.Settings).WebHookUrl.Contains("discord"))
.ToList();
if (discordSlackNotifications.Empty())
{
@@ -31,8 +33,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("DiscordUrlInSlackNotification"),
string.Join(", ", discordSlackNotifications.Select(n => n.Name))),
string.Format(_localizationService.GetLocalizedString("DiscordUrlInSlackNotification"), string.Join(", ", discordSlackNotifications.Select(n => n.Name))),
"#discord-as-slack-notification");
}
}

View File

@@ -24,8 +24,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var request = _cloudRequestBuilder.Create()
.Resource("/time")
.Build();
.Resource("/time")
.Build();
var response = _client.Execute(request);
var result = Json.Deserialize<ServiceTimeResponse>(response.Content);

View File

@@ -76,7 +76,8 @@ namespace NzbDrone.Core.History
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
.Join<MovieHistory, Movie>((h, m) => h.MovieId == m.Id)
.Join<Movie, Profile>((m, p) => m.ProfileId == p.Id);
.Join<Movie, Profile>((m, p) => m.ProfileId == p.Id)
.LeftJoin<Movie, MovieMetadata>((m, mm) => m.MovieMetadataId == mm.Id);
protected override IEnumerable<MovieHistory> PagedQuery(SqlBuilder sql) =>
_database.QueryJoined<MovieHistory, Movie, Profile>(sql, (hist, movie, profile) =>

View File

@@ -14,34 +14,32 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
using var mapper = _database.OpenConnection();
if (_database.DatabaseType == DatabaseType.PostgreSQL)
{
if (_database.DatabaseType == DatabaseType.PostgreSQL)
{
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" = ANY (
SELECT ""Id"" FROM ""MetadataFiles""
WHERE ""RelativePath""
LIKE '_:\\%'
OR ""RelativePath""
LIKE '\\%'
OR ""RelativePath""
LIKE '/%'
)");
}
else
{
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" IN (
SELECT ""Id"" FROM ""MetadataFiles""
WHERE ""RelativePath""
LIKE '_:\%'
OR ""RelativePath""
LIKE '\%'
OR ""RelativePath""
LIKE '/%'
)");
}
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" = ANY (
SELECT ""Id"" FROM ""MetadataFiles""
WHERE ""RelativePath""
LIKE '_:\\%'
OR ""RelativePath""
LIKE '\\%'
OR ""RelativePath""
LIKE '/%'
)");
}
else
{
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" IN (
SELECT ""Id"" FROM ""MetadataFiles""
WHERE ""RelativePath""
LIKE '_:\%'
OR ""RelativePath""
LIKE '\%'
OR ""RelativePath""
LIKE '/%'
)");
}
}
}

View File

@@ -14,13 +14,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""NamingConfig""
WHERE ""Id"" NOT IN (
SELECT ""Id"" FROM ""NamingConfig""
LIMIT 1)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""NamingConfig""
WHERE ""Id"" NOT IN (
SELECT ""Id"" FROM ""NamingConfig""
LIMIT 1)");
}
}
}

View File

@@ -14,13 +14,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""Users""
WHERE ""Id"" NOT IN (
SELECT ""Id"" FROM ""Users""
LIMIT 1)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""Users""
WHERE ""Id"" NOT IN (
SELECT ""Id"" FROM ""Users""
LIMIT 1)");
}
}
}

View File

@@ -16,29 +16,28 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.OpenConnection();
using var mapper = _database.OpenConnection();
if (_database.DatabaseType == DatabaseType.PostgreSQL)
{
mapper.Execute(@"DELETE FROM ""PendingReleases""
WHERE ""Added"" < @TwoWeeksAgo
AND ""Reason"" = ANY (@Reasons)",
new
{
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback }
});
WHERE ""Added"" < @TwoWeeksAgo
AND ""Reason"" = ANY (@Reasons)",
new
{
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback }
});
}
else
{
mapper.Execute(@"DELETE FROM ""PendingReleases""
WHERE ""Added"" < @TwoWeeksAgo
AND ""REASON"" IN @Reasons",
new
{
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback }
});
mapper.Execute(@"DELETE FROM ""PendingReleases""
WHERE ""Added"" < @TwoWeeksAgo
AND ""REASON"" IN @Reasons",
new
{
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback }
});
}
}
}

View File

@@ -20,30 +20,26 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private void DeleteDuplicateMovieMetadata()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" IN (
SELECT MIN(""Id"") FROM ""MetadataFiles""
WHERE ""Type"" = 1
GROUP BY ""MovieId"", ""Consumer""
HAVING COUNT(""MovieId"") > 1
)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" IN (
SELECT MIN(""Id"") FROM ""MetadataFiles""
WHERE ""Type"" = 1
GROUP BY ""MovieId"", ""Consumer""
HAVING COUNT(""MovieId"") > 1
)");
}
private void DeleteDuplicateMovieFileMetadata()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" IN (
SELECT MIN(""Id"") FROM ""MetadataFiles""
WHERE ""Type"" = 1
GROUP BY ""MovieFileId"", ""Consumer""
HAVING COUNT(""MovieFileId"") > 1
)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""MetadataFiles""
WHERE ""Id"" IN (
SELECT MIN(""Id"") FROM ""MetadataFiles""
WHERE ""Type"" = 1
GROUP BY ""MovieFileId"", ""Consumer""
HAVING COUNT(""MovieFileId"") > 1
)");
}
}
}

View File

@@ -14,15 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""AlternativeTitles""
WHERE ""Id"" IN (
SELECT ""AlternativeTitles"".""Id"" FROM ""AlternativeTitles""
LEFT OUTER JOIN ""MovieMetadata""
ON ""AlternativeTitles"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
WHERE ""MovieMetadata"".""Id"" IS NULL)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""AlternativeTitles""
WHERE ""Id"" IN (
SELECT ""AlternativeTitles"".""Id"" FROM ""AlternativeTitles""
LEFT OUTER JOIN ""MovieMetadata""
ON ""AlternativeTitles"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
WHERE ""MovieMetadata"".""Id"" IS NULL)");
}
}
}

View File

@@ -14,15 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""Blocklist""
WHERE ""Id"" IN (
SELECT ""Blocklist"".""Id"" FROM ""Blocklist""
LEFT OUTER JOIN ""Movies""
ON ""Blocklist"".""MovieId"" = ""Movies"".""Id""
WHERE ""Movies"".""Id"" IS NULL)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""Blocklist""
WHERE ""Id"" IN (
SELECT ""Blocklist"".""Id"" FROM ""Blocklist""
LEFT OUTER JOIN ""Movies""
ON ""Blocklist"".""MovieId"" = ""Movies"".""Id""
WHERE ""Movies"".""Id"" IS NULL)");
}
}
}

View File

@@ -14,13 +14,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""Collections"" WHERE ""TmdbId"" IN (SELECT ""X"".""TmdbId"" FROM (SELECT ""Collections"".""TmdbId"", COUNT(""Movies"".""Id"") as ""MovieCount"" FROM ""Collections""
LEFT OUTER JOIN ""MovieMetadata"" ON ""Collections"".""TmdbId"" = ""MovieMetadata"".""CollectionTmdbId""
LEFT OUTER JOIN ""Movies"" ON ""Movies"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
GROUP BY ""Collections"".""Id"") AS ""X"" WHERE ""X"".""MovieCount"" = 0)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""Collections"" WHERE ""TmdbId"" IN (SELECT ""X"".""TmdbId"" FROM (SELECT ""Collections"".""TmdbId"", COUNT(""Movies"".""Id"") as ""MovieCount"" FROM ""Collections""
LEFT OUTER JOIN ""MovieMetadata"" ON ""Collections"".""TmdbId"" = ""MovieMetadata"".""CollectionTmdbId""
LEFT OUTER JOIN ""Movies"" ON ""Movies"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
GROUP BY ""Collections"".""Id"") AS ""X"" WHERE ""X"".""MovieCount"" = 0)");
}
}
}

View File

@@ -14,15 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""Credits""
WHERE ""Id"" IN (
SELECT ""Credits"".""Id"" FROM ""Credits""
LEFT OUTER JOIN ""MovieMetadata""
ON ""Credits"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
WHERE ""MovieMetadata"".""Id"" IS NULL)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""Credits""
WHERE ""Id"" IN (
SELECT ""Credits"".""Id"" FROM ""Credits""
LEFT OUTER JOIN ""MovieMetadata""
ON ""Credits"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
WHERE ""MovieMetadata"".""Id"" IS NULL)");
}
}
}

View File

@@ -14,14 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.OpenConnection();
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""DownloadClientStatus""
WHERE ""Id"" IN (
SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus""
LEFT OUTER JOIN ""DownloadClients""
ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id""
WHERE ""DownloadClients"".""Id"" IS NULL)");
WHERE ""Id"" IN (
SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus""
LEFT OUTER JOIN ""DownloadClients""
ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id""
WHERE ""DownloadClients"".""Id"" IS NULL)");
}
}
}

View File

@@ -20,29 +20,25 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private void DeleteOrphanedByMovie()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""ExtraFiles"".""Id"" FROM ""ExtraFiles""
LEFT OUTER JOIN ""Movies""
ON ""ExtraFiles"".""MovieId"" = ""Movies"".""Id""
WHERE ""Movies"".""Id"" IS NULL)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""ExtraFiles"".""Id"" FROM ""ExtraFiles""
LEFT OUTER JOIN ""Movies""
ON ""ExtraFiles"".""MovieId"" = ""Movies"".""Id""
WHERE ""Movies"".""Id"" IS NULL)");
}
private void DeleteOrphanedByMovieFile()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""ExtraFiles"".""Id"" FROM ""ExtraFiles""
LEFT OUTER JOIN ""MovieFiles""
ON ""ExtraFiles"".""MovieFileId"" = ""MovieFiles"".""Id""
WHERE ""ExtraFiles"".""MovieFileId"" > 0
AND ""MovieFiles"".""Id"" IS NULL)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""ExtraFiles"".""Id"" FROM ""ExtraFiles""
LEFT OUTER JOIN ""MovieFiles""
ON ""ExtraFiles"".""MovieFileId"" = ""MovieFiles"".""Id""
WHERE ""ExtraFiles"".""MovieFileId"" > 0
AND ""MovieFiles"".""Id"" IS NULL)");
}
}
}

View File

@@ -19,15 +19,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private void CleanupOrphanedByMovie()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""History""
WHERE ""Id"" IN (
SELECT ""History"".""Id"" FROM ""History""
LEFT OUTER JOIN ""Movies""
ON ""History"".""MovieId"" = ""Movies"".""Id""
WHERE ""Movies"".""Id"" IS NULL)");
}
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""History""
WHERE ""Id"" IN (
SELECT ""History"".""Id"" FROM ""History""
LEFT OUTER JOIN ""Movies""
ON ""History"".""MovieId"" = ""Movies"".""Id""
WHERE ""Movies"".""Id"" IS NULL)");
}
}
}

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