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

Compare commits

...

96 Commits

Author SHA1 Message Date
Mark McDowall
9367a07db4 New: Return static response to requests while app is starting
(cherry picked from commit 303fc5d786ccf2ad14c8523fc239696c5d37ea53)
2023-02-08 01:35:52 +00:00
Qstick
9c86598b54 Fixup language specification tests 2023-02-06 19:38:44 -06:00
Qstick
0fe2262162 Fixed: Releases incorrectly rejected due to language 2023-02-06 19:06:03 -06:00
Qstick
47353aea75 Fixed: Avoid failure on null SceneName 2023-02-05 23:31:00 -06:00
Giulia Petenazzi
af43cb2aca New: Added release year to queue ( issue #6330) (#8019) 2023-02-05 19:09:20 -06:00
Fuochi
bc838b74c7 Fixed: Remove initial dot in filename (#4509) 2023-02-05 17:22:07 -06:00
Qstick
cbcf3d1058 New: Custom Format Updates (#8067) 2023-02-05 17:09:37 -06:00
Qstick
c72e64f081 Bump version to 4.4.2 2023-02-04 21:15:36 -06:00
Qstick
e09607edb0 Remove old, broken test
Fixes #7186
2023-02-04 21:12:22 -06:00
Winter
d91578aee3 Fixed: Releases from PTP showing skewed publish date
PTP returns UTC timestamps, without a timezone specifier. Previously, users
would see skewed publish dates, as the UTC timestamps were being parsed
as if they were in the system's timezone. To fix this, we just assume the
publish date is in UTC.
2023-02-04 17:46:23 -06:00
Mark McDowall
affedd7f9d Fixed: Ping endpoint no longer requires authentication
(cherry picked from commit ad42d4a14c814d5911dafb5e78e97ec09b4b13a5)
2023-02-04 17:44:37 -06:00
Qstick
c3665e9fea New: Spanish (Latino) languages
Closes #7914
Closes #3467
Closes #6415
2023-02-04 17:42:39 -06:00
Mark McDowall
364d8bd7c5 Fixed: Don't try to remove the same item from queue multiple times
Closes #7932

(cherry picked from commit 2491da067815e129df3a3a79c0cc7221a9d87094)
2023-02-04 17:32:18 -06:00
Mark McDowall
7142d1f224 Improve usage of Original Title renaming token
Closes #7168

Fixed: Don't recursively add the current file name to new file name when '{Original Title}' is used in addition to other naming tokens
(cherry picked from commit ebb48a19cc792c71bfbd57d5f106067190d95339)
2023-02-04 17:26:07 -06:00
Stevie Robinson
86777e021b Fixed: Mass Editor Footer on Smaller Screens
Closes #6968

(cherry picked from commit 9afcec8b1ffc11da93ae50b73f77f5ebe6e12391)
2023-02-04 17:26:07 -06:00
bakerboy448
9d2dacea97 New: Improve Manual Import logging when not parsing files
Closes #8059

(cherry picked from commit 83f63590630ae0728fd9f9f03567a294934eebcc)
2023-02-04 17:26:07 -06:00
Mark McDowall
d98c86c3d9 Fixed: Parse year in title from square brackets
(cherry picked from commit 99e60196a4e513d6340a090de4a5517f205e7a29)
2023-02-04 17:22:26 -06:00
Qstick
df681d82be Fixed: Update Images on MovieMetadata refresh 2023-02-03 21:37:30 -06:00
Weblate
daf81c5b26 Translated using Weblate (French) [skip ci]
Currently translated at 97.5% (1129 of 1157 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 89.9% (1041 of 1157 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 95.2% (1102 of 1157 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.5% (1106 of 1157 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.4% (1128 of 1157 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 21.4% (248 of 1157 strings)

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

Currently translated at 1.4% (17 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 94.4% (1093 of 1157 strings)

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

Currently translated at 99.9% (1156 of 1157 strings)

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

Currently translated at 99.9% (1156 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 90.8% (1051 of 1157 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Florian <sephrat.flo@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Uxose <nathan.renault@live.fr>
Co-authored-by: Vasilis Ieropoulos <kirav96@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: buzzke <buzzke@me.com>
Co-authored-by: hhjuhl <hans@kopula.dk>
Co-authored-by: oskhel <oskar.hellgren@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: ryabov-artem <art.rya@gmail.com>
Co-authored-by: zhuzhe1983 <zhuzhe1983@gmail.com>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2023-01-30 22:31:53 -06:00
Zak Saunders
78f929c60b Fixup File Name Tokens (#8036) 2023-01-30 22:30:37 -06:00
Qstick
87d59d12a4 Fixed: Avoid Sqlite Error when all profiles have lowest quality cutoff
(cherry picked from commit  f05e109b50cca496e7b42e2833eff161a43e12f4)
2023-01-26 16:36:25 +00:00
Mark McDowall
ce031124c7 Improve handling of releases without video files
New: Show warning in queue if download contains executable or archive file and no video file was detected

(cherry picked from commit b15b6a079846b21cac8476820fce9cde81732291)
2023-01-26 16:36:09 +00:00
Mark McDowall
d4ce08a044 Fixed: UTC time sent to UI for already imported message
(cherry picked from commit 3f598ffa6fbec90ecdbb266de4b0fe7558fbbc30)
2023-01-25 13:04:18 +00:00
Mark McDowall
871e78b314 Updated some JS dependencies 2023-01-25 13:03:33 +00:00
Mark McDowall
eeee682f6c New: Parse release group from VARYG releases with junk at the end
(cherry picked from commit 5ce8ea8985f880d4e68db852f04558a59461ae3d)
2023-01-25 13:02:25 +00:00
Qstick
9c594c3e53 Bump ImageSharp to 2.1.3
(cherry picked from commit c08b45156425d84e51072093d0ead42f1c105ad5)
2023-01-25 13:02:12 +00:00
voltron4lyfe
0b1b19a165 Fix Filter Button being squished #8024 2023-01-23 21:40:41 -06:00
Weblate
f1ff7b3b61 Translated using Weblate (Greek) [skip ci]
Currently translated at 95.2% (1102 of 1157 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.5% (1106 of 1157 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.4% (1128 of 1157 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 21.4% (248 of 1157 strings)

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

Currently translated at 1.4% (17 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 94.4% (1093 of 1157 strings)

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

Currently translated at 99.9% (1156 of 1157 strings)

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

Currently translated at 99.9% (1156 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 90.8% (1051 of 1157 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Florian <sephrat.flo@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Vasilis Ieropoulos <kirav96@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: buzzke <buzzke@me.com>
Co-authored-by: oskhel <oskar.hellgren@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: zhuzhe1983 <zhuzhe1983@gmail.com>
Co-authored-by: 無情天 <kofzhanganguo@126.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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
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/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2023-01-23 19:21:10 -06:00
Robert Dailey
165c588557 Updated .gitignore to ignore .idea/ completely
Unless the repository owners wish to have these files in their repo,
they should be ignored so that contributors are not stepping around
these files.

[skip-ci]
2023-01-24 00:13:47 +00:00
Mark McDowall
327e18bc7a New: Filter by Custom Format Score in Interactive Search
Closes #7825

(cherry picked from commit 998768bcf2c3308611a05a4518e7ef3fbcb473cc)
2023-01-22 18:34:30 -06:00
Qstick
f61f2c89dc Remove OMG indexer
Closes #7527

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-22 18:27:24 -06:00
Qstick
2327b72558 New: Send additional information with Webhook and Custom Scripts
Closes #7730

Co-Authored-By: Devin Buhl <onedr0p@users.noreply.github.com>
2023-01-22 17:52:54 -06:00
Qstick
66ddd08684 New: Improve logging when processing files for import
Closes #7754
2023-01-22 17:41:24 -06:00
Robert Dailey
4d2143e9b2 Fixed: Unable to load UI if Quality Profiles contain removed Custom Format items
Manually cherry picked from Sonarr commits:

- 2c004e1f9665763111fcd964b81338bdbe735865
- 4b4301a076488c595969921697d7002ca427c955
2023-01-22 16:24:24 -06:00
Qstick
7906ea2a0c Rejection string improvements
Closes #7611
2023-01-22 14:27:15 -06:00
Zak Saunders
9d1956794e Fixed: Progress bar text colour in Dark theme
(cherry picked from commit ca61efa57fc04a7f6753aedb4b8044d17e345429)
2023-01-22 14:05:04 -06:00
voltron4lyfe
4956ff7914 Adding indicator display option to PageMenuButton. 2023-01-22 14:04:15 -06:00
Mark McDowall
f22a589cb8 Fixed: Filter indicator in interactive search
(cherry picked from commit 80d36a06c8f42f239304d2f5a7edcb573b5072db)
2023-01-22 14:04:15 -06:00
Qstick
04185d6839 Filter useless PG Errors from coming to Sentry 2023-01-22 11:46:52 -06:00
Qstick
fb25e5d577 Fixed: Catch InvalidDataException during initial config to prevent boot loop
[Common]
2023-01-21 13:06:18 -06:00
Qstick
6845eaa9b2 Re-enable some update tests 2023-01-21 13:03:48 -06:00
Qstick
c1e65874bc Bump version to 4.4.1 2023-01-21 11:43:48 -06:00
Bakerboy448
226a5da0c9 Fixed: Parse HDCAMRip as CAM 2023-01-21 11:41:37 -06:00
Qstick
685a24e476 Fixed: RemotePathMappingCheck Improvements 2023-01-16 22:45:55 -06:00
Qstick
cae4faae61 Fixed: DownloadClientRootFolderCheck Improvements 2023-01-16 22:38:05 -06:00
Qstick
5dac6badf2 Fixed: Ignore movie add errors during collection sync
Fixes #7982
2023-01-11 23:34:21 -06:00
Weblate
5948f56482 Translated using Weblate (Ukrainian) [skip ci]
Currently translated at 100.0% (1156 of 1156 strings)

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

Currently translated at 100.0% (1156 of 1156 strings)

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

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.5% (1105 of 1156 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1156 of 1156 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.5% (1128 of 1156 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: aenron <1414004038@qq.com>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: jjTogo228 <juniorbiam@gmail.com>
Co-authored-by: verhese <sean.verheyen1@telenet.be>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-01-08 22:28:28 -06:00
Mark McDowall
98ddd0386b Fixed: Trakt connection auth tokens not being refreshed
Closes #7873

(cherry picked from commit d09e5d8eb4097cbba1ee0a668dbb27f941cc4f68)
2023-01-08 22:22:01 -06:00
Mark McDowall
2947b244e4 Fixed: Quality cutoff updating in UI when adding/removing qualities
Closes #7879

(cherry picked from commit fea66cb7bccc7e94523614db38b157fa38f55ea5)
2023-01-08 21:43:55 -06:00
Mark McDowall
72552b8084 New: Option to include movie image for Gotify notifications
Closes #7920

(cherry picked from commit e57e68c97a9d24f8344623ac8f731c2da220686b)
2023-01-08 21:41:08 -06:00
Qstick
09642444d7 Switch Trakt to STJson
Fixes #7913
2023-01-08 21:11:59 -06:00
Qstick
d1080b825c Fixed: Truncate custom format card tags
Fixes #7725
Fixes #7973

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-08 21:00:43 -06:00
Qstick
001421de10 New: Improve messaging for rejected quality upgrades
Fixes #7461

Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2023-01-08 20:52:47 -06:00
Qstick
bab9b8b36a Add Volta node config
Fixes #7600
Fixes #7747

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2023-01-08 20:52:47 -06:00
Qstick
0fb738aa2e Fixed: Kodi Metadata Subtitle Language
Fixes #7962

Co-Authored-By: Stevie Robinson <stevie.robinson@gmail.com>
2023-01-08 20:52:47 -06:00
Mark McDowall
4963920a46 New: Added health check warning if SABnzbd sorting is enabled
(cherry picked from commit 61fa1e5e3f00072f0d5f59cc883fac74fe12ee9d)
2023-01-08 20:38:26 -06:00
Qstick
f0d10fe1cd Fixed: Correct messaging when release is not upgrade
Fixes #7963
2023-01-08 20:24:13 -06:00
James Hu
386b33b624 New: Include movie title and year when logging report
* Include movie title and year when logging report

* Change verbage

Co-authored-by: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
2023-01-05 21:07:59 +00:00
Mark McDowall
98201508f2 New: Description for indexer RSS setting
(cherry picked from commit 396406b2174c4876057175e7537a4718eee2abca)
2023-01-04 10:19:57 +00:00
Qstick
9723c569a1 Bump version to 4.4.0 2023-01-03 18:48:07 -06:00
Qstick
0584d7676c Bump FFProbe and Newtonsoft 2023-01-02 22:02:50 -06:00
Weblate
09c42530ec Translated using Weblate (Dutch) [skip ci]
Currently translated at 95.4% (1102 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 94.3% (1090 of 1155 strings)

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

Currently translated at 99.9% (1154 of 1155 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 94.4% (1091 of 1155 strings)

Co-authored-by: Davide Palma <github@davidepalma.it>
Co-authored-by: Iagocds <cdsiago@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: lhquark <lhquark@gmail.com>
Co-authored-by: vyruz1986 <alex.goris@fastlikehell.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-01-02 21:47:27 -06:00
Mark McDowall
0697d694e0 New: Improved messaging when qBittorrent fails due to host header rejection
(cherry picked from commit 48b4cc5f3ffa0cb8eea6748db9091267216cef4f)
2023-01-02 21:46:10 -06:00
Mark McDowall
e085f6af8a Fixed: Multiple pushed releases will be processed sequentially
(cherry picked from commit 1f8e1cf582f59fe1e8dcc0fad15afeed6d9cd9d1)
2023-01-02 17:59:43 -06:00
Colin Gagnaire
7feda1c446 New: Add support for native Freebox Download Client
(cherry picked from commit fb76c237bfbb8aa43bcdd9ce34d90ea843011cee)
2022-12-27 21:20:33 +00:00
Winter
e1f83c205d Bump MonoTorrent to 2.0.7 2022-12-24 14:48:08 -06:00
Weblate
db00edd266 Translated using Weblate (Bengali) [skip ci]
Currently translated at 0.4% (5 of 1155 strings)

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

Currently translated at 99.3% (1147 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 95.4% (1102 of 1155 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 94.3% (1090 of 1155 strings)

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

Currently translated at 99.3% (1148 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 93.9% (1085 of 1155 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 93.9% (1085 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Frank van den Bosch <frank@fbtech.nl>
Co-authored-by: Freelf <freelf.me@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mipiaceanutella <remix-polity-0l@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: benniblot <ben2004engler@gmail.com>
Co-authored-by: deepserket <deepserket@gmail.com>
Co-authored-by: hidaba <nag@hidaba.com>
Co-authored-by: ningxia <xianing7105@163.com>
Co-authored-by: saambd <me@salimrahman.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bn/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
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
2022-12-22 20:34:42 -06:00
Mark McDowall
d699f61f5d Fixed: Prevent unexpected data breaking Series Import
(cherry picked from commit b8714d80a1ede761042ab469110edf552a74ac6b)
2022-12-22 20:33:30 -06:00
Mark McDowall
dc1b478f2c Fixed: Only log /proc/mounts exception once per process
(cherry picked from commit ce0388ca99b7f89bd9e8971777a7995c4361d268)
2022-12-22 20:32:43 -06:00
erri120
0ca665c903 New: Parse Open Matte as Edition
To make James Cameron happy.
2022-12-18 10:36:50 -06:00
Mark McDowall
111c6a743f New: Rename Emby to Emby / Jellyfin
(cherry picked from commit ee1ee8f267079e18015829065a76a628929cf4b2)
2022-12-17 18:18:52 +00:00
Qstick
d3517532a4 Update README for DigitalOcean attribution
[common]
2022-12-17 11:31:47 -06:00
Qstick
5790ebc558 Bump version to 4.3.2 2022-12-11 19:03:47 -06:00
Mark McDowall
c11f72c098 New: IPv6 support for connections/indexers/download clients
Closes #7850

(cherry picked from commit 1b90fbcf7df2c1086da4791c6491771924b1b7aa)
2022-12-10 12:05:55 -06:00
Mark McDowall
3617bef54b Fixed: Improve Bind Address validation and help text
Closes #7849

(cherry picked from commit 6bdeafcf8c78e145595f52e885356be1210abe91)
2022-12-10 12:04:04 -06:00
Zak Saunders
a5fb01f1e6 New: Auto theme option to match OS theme
Co-authored-by: Qstick <qstick@gmail.com>
(cherry picked from commit 4ca5a213fa0fc29ed93e7e31b080728d6fa7f1f3)
2022-12-09 22:11:03 -06:00
Qstick
fa6acb7497 Simplify X-Forwarded-For handling
This happens in asp.net middleware now

Co-Authored-By: ta264 <ta264@users.noreply.github.com>

(cherry picked from commit 16e2d130e6a2e7239bcfe92187a7f990f93eff00)
2022-12-09 22:05:50 -06:00
Qstick
904259df92 New: Improve IPAddress.IsLocal method
Co-Authored-By: ta264 <ta264@users.noreply.github.com>

(cherry picked from commit fd98a179ab6fed8037c99344b34593aac24a0ac0)
2022-12-09 22:05:32 -06:00
Qstick
65c316bd6d Fixed: Smb paths fail on Kodi update
Fixes #7854
2022-12-09 22:04:50 -06:00
Qstick
3b46a08606 Fix PendingRelease Tests 2022-12-05 22:12:34 -06:00
Servarr
6ad49373d4 Automated API Docs update 2022-12-05 21:52:13 -06:00
Qstick
2a1f57c085 Fixed: Sending Webhook on upgrade if media info is unavailable
Fixes #7838

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-05 21:34:00 -06:00
Qstick
9d9065fbcd API Updates
Fixes #7833
Fixes #6785
Fixes #6787

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-05 21:34:00 -06:00
Qstick
694940452c Fixed: Loading queue when there are pending items that were added before upgrading
Fixes #7823

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-05 21:34:00 -06:00
Qstick
f5d6a79998 Fixed: Grab/remove queue actions not showing spinner
Fixes #7821
2022-12-05 21:34:00 -06:00
Qstick
4cc98a10a0 Fixed: Use route Id for PUT requests if not passed in body
Fixes #7809
2022-12-05 21:34:00 -06:00
Qstick
1751bd1a58 Fixed: Correct Attribute compare for Id validation
(cherry picked from commit 7e48ea0231272ae56c30f5f43339f0dca7a27fb3)
2022-12-05 21:20:31 -06:00
Weblate
c0caf65b69 Translated using Weblate (Dutch) [skip ci]
Currently translated at 95.2% (1100 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Robin Flikkema <robin@robinflikkema.nl>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-12-02 21:13:27 -06:00
Davo1624
cd889872de Properly parse H.265 4k as 4k quality (#7812)
* Update en.json

* Properly parse H265 for 4k quality

`.WEB-DL.4K.H265.AAC` parses properly for 4k quality
`.WEB-DL.4K.H.265.AAC` parses improperly as 480p
2022-11-29 22:00:36 -06:00
Servarr
6366e335bc Automated API Docs update 2022-11-29 21:57:01 -06:00
Qstick
41f10d098e Don't block task queue for queued update task when there are longer running tasks
Fixes #7538

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-29 21:44:03 -06:00
Qstick
b2c1698097 Fixed: False Positive HC in some cases
Fixes #7785
2022-11-29 20:52:23 -06:00
Mark McDowall
ed20487f30 Fixed: Handle Flood reporting errors for completed and stopped downloads
(cherry picked from commit f2b2eb69a3e8b271535bd92dc2f5cbfd45664daf)
2022-11-28 21:25:34 -06:00
Bruno Garcia
d1235adfc4 Sentry SDK v3.23.1
Co-authored-by: Bruno Garcia <bruno@Brunos-MacBook-Pro.local>
(cherry picked from commit de3cb07c57d762084c983336aa01b761a8e4b74a)
2022-11-28 21:30:06 +00:00
Qstick
561993e30c Fixed: Parse multiple languages for two letter cases
Fixes #7783
2022-11-25 19:16:29 -06:00
Weblate
14f8f89634 Translated using Weblate (Czech) [skip ci]
Currently translated at 90.9% (1051 of 1155 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

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

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-11-24 18:40:38 -06:00
260 changed files with 5681 additions and 3350 deletions

23
.gitignore vendored
View File

@@ -166,27 +166,8 @@ packages.config.md5sum
# Common IntelliJ Platform excludes
# User specific
**/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
**/.idea/.idea.Radarr.Posix
**/.idea/.idea.Radarr.Windows
# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml
# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml
# Ignore Rider projects completely for now
.idea/
# ignore node_modules symlink
node_modules

View File

@@ -76,6 +76,15 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
## DigitalOcean
This project is also supported by DigitalOcean
<p>
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
</a>
</p>
### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)

View File

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

View File

@@ -56,6 +56,7 @@ class HistoryRow extends Component {
movie,
quality,
customFormats,
customFormatScore,
languages,
qualityCutoffNotMet,
eventType,
@@ -175,7 +176,7 @@ class HistoryRow extends Component {
key={name}
className={styles.customFormatScore}
>
{formatCustomFormatScore(data.customFormatScore)}
{formatCustomFormatScore(customFormatScore)}
</TableRowCell>
);
}
@@ -241,8 +242,9 @@ HistoryRow.propTypes = {
movie: PropTypes.object.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
date: PropTypes.string.isRequired,

View File

@@ -75,13 +75,23 @@ class Queue extends Component {
return;
}
const nextState = {};
if (prevProps.items !== items) {
nextState.items = items;
}
const selectedIds = this.getSelectedIds();
const isPendingSelected = _.some(this.props.items, (item) => {
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
});
if (isPendingSelected !== this.state.isPendingSelected) {
this.setState({ isPendingSelected });
nextState.isPendingSelected = isPendingSelected;
}
if (!_.isEmpty(nextState)) {
this.setState(nextState);
}
}
@@ -214,26 +224,29 @@ class Queue extends Component {
<PageContentBody>
{
isRefreshing && !isAllPopulated &&
<LoadingIndicator />
isRefreshing && !isAllPopulated ?
<LoadingIndicator /> :
null
}
{
!isRefreshing && hasError &&
!isRefreshing && hasError ?
<div>
{translate('FailedToLoadQueue')}
</div>
</div> :
null
}
{
isAllPopulated && !hasError && !items.length &&
isAllPopulated && !hasError && !items.length ?
<div>
{translate('QueueIsEmpty')}
</div>
</div> :
null
}
{
isAllPopulated && !hasError && !!items.length &&
isAllPopulated && !hasError && !!items.length ?
<div>
<Table
columns={columns}
@@ -268,7 +281,8 @@ class Queue extends Component {
isFetching={isRefreshing}
{...otherProps}
/>
</div>
</div> :
null
}
</PageContentBody>

View File

@@ -128,6 +128,7 @@ class QueueRow extends Component {
{
columns.map((column) => {
const {
name,
isVisible
@@ -234,6 +235,16 @@ class QueueRow extends Component {
);
}
if (name === 'year') {
return (
<TableRowCell key={name}>
{
movie ? movie.year : ''
}
</TableRowCell>
);
}
if (name === 'title') {
return (
<TableRowCell key={name}>
@@ -362,6 +373,7 @@ QueueRow.propTypes = {
estimatedCompletionTime: PropTypes.string,
timeleft: PropTypes.string,
size: PropTypes.number,
year: PropTypes.number,
sizeleft: PropTypes.number,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -225,13 +225,19 @@ class ImportMovieFooter extends Component {
body={
<ul>
{
importError.responseJSON.map((error, index) => {
return (
<li key={index}>
{error.errorMessage}
</li>
);
})
Array.isArray(importError.responseJSON) ?
importError.responseJSON.map((error, index) => {
return (
<li key={index}>
{error.errorMessage}
</li>
);
}) :
<li>
{
JSON.stringify(importError.responseJSON)
}
</li>
}
</ul>
}

View File

@@ -152,13 +152,19 @@ class ImportMovieSelectFolder extends Component {
<ul>
{
saveError.responseJSON.map((e, index) => {
return (
<li key={index}>
{e.errorMessage}
</li>
);
})
Array.isArray(saveError.responseJSON) ?
saveError.responseJSON.map((e, index) => {
return (
<li key={index}>
{e.errorMessage}
</li>
);
}) :
<li>
{
JSON.stringify(saveError.responseJSON)
}
</li>
}
</ul>
</Alert> :

View File

@@ -113,10 +113,12 @@ class EnhancedSelectInput extends Component {
this._scheduleUpdate();
}
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
if (!Array.isArray(this.props.value)) {
if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
}
}
}
@@ -332,6 +334,11 @@ class EnhancedSelectInput extends Component {
const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values);
let selectedValue = value;
if (!values.length) {
selectedValue = isMultiSelect ? [] : '';
}
return (
<div>
@@ -372,15 +379,17 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
{
isFetching &&
isFetching ?
<LoadingIndicator
className={styles.loading}
size={20}
/>
/> :
null
}
{
!isFetching &&
isFetching ?
null :
<Icon
name={icons.CARET_DOWN}
/>
@@ -400,7 +409,7 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
<SelectedValueComponent
value={value}
value={selectedValue}
values={values}
{...selectedValueOptions}
{...selectedOption}
@@ -418,15 +427,17 @@ class EnhancedSelectInput extends Component {
>
{
isFetching &&
isFetching ?
<LoadingIndicator
className={styles.loading}
size={20}
/>
/> :
null
}
{
!isFetching &&
isFetching ?
null :
<Icon
name={icons.CARET_DOWN}
/>
@@ -506,7 +517,7 @@ class EnhancedSelectInput extends Component {
</Manager>
{
isMobile &&
isMobile ?
<Modal
className={styles.optionsModal}
size={sizes.EXTRA_SMALL}
@@ -557,7 +568,8 @@ class EnhancedSelectInput extends Component {
}
</Scroller>
</ModalBody>
</Modal>
</Modal> :
null
}
</div>
);

View File

@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
>
<div className={styles.valueText}>
{
isMultiSelect &&
isMultiSelect ?
value.map((key, index) => {
const v = valuesMap[key];
return (
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key}
</Label>
);
})
}) :
null
}
{
!isMultiSelect && value
isMultiSelect ? null : value
}
</div>
{
hint != null && includeHint &&
hint != null && includeHint ?
<div className={styles.hintText}>
{hint}
</div>
</div> :
null
}
</EnhancedSelectInputSelectedValue>
);
}
HintedSelectInputSelectedValue.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,

View File

@@ -68,7 +68,7 @@ RootFolderSelectInputOption.propTypes = {
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
movieFolder: PropTypes.string,
isMissing: PropTypes.boolean,
isMissing: PropTypes.bool,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool
};

View File

@@ -60,7 +60,7 @@ class FilterMenu extends Component {
iconName={icons.FILTER}
text={translate('Filter')}
isDisabled={isDisabled}
indicator={selectedFilterKey !== 'all'}
showIndicator={selectedFilterKey !== 'all'}
/>
<FilterMenuContent

View File

@@ -1,11 +1,19 @@
.menuButton {
composes: menuButton from '~./MenuButton.css';
position: relative;
&:hover {
color: #666;
}
}
.indicatorContainer {
position: absolute;
top: 10px;
left: 10px;
}
.label {
margin-left: 5px;
}

View File

@@ -1,13 +1,15 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import MenuButton from 'Components/Menu/MenuButton';
import { icons } from 'Helpers/Props';
import styles from './PageMenuButton.css';
function PageMenuButton(props) {
const {
iconName,
indicator,
showIndicator,
text,
...otherProps
} = props;
@@ -22,6 +24,22 @@ function PageMenuButton(props) {
size={18}
/>
{
showIndicator ?
<span
className={classNames(
styles.indicatorContainer,
'fa-layers fa-fw'
)}
>
<Icon
name={icons.CIRCLE}
size={9}
/>
</span> :
null
}
<div className={styles.label}>
{text}
</div>
@@ -32,11 +50,11 @@ function PageMenuButton(props) {
PageMenuButton.propTypes = {
iconName: PropTypes.object.isRequired,
text: PropTypes.string,
indicator: PropTypes.bool.isRequired
showIndicator: PropTypes.bool.isRequired
};
PageMenuButton.defaultProps = {
indicator: false
showIndicator: false
};
export default PageMenuButton;

View File

@@ -9,7 +9,7 @@ import styles from './ToolbarMenuButton.css';
function ToolbarMenuButton(props) {
const {
iconName,
indicator,
showIndicator,
text,
...otherProps
} = props;
@@ -26,7 +26,7 @@ function ToolbarMenuButton(props) {
/>
{
indicator &&
showIndicator &&
<span
className={classNames(
styles.indicatorContainer,
@@ -53,11 +53,11 @@ function ToolbarMenuButton(props) {
ToolbarMenuButton.propTypes = {
iconName: PropTypes.object.isRequired,
text: PropTypes.string,
indicator: PropTypes.bool.isRequired
showIndicator: PropTypes.bool.isRequired
};
ToolbarMenuButton.defaultProps = {
indicator: false
showIndicator: false
};
export default ToolbarMenuButton;

View File

@@ -19,7 +19,7 @@
}
}
@media only screen and (max-width: $breakpointLarge) {
@media only screen and (max-width: $breakpointExtraLarge) {
.contentFooter {
flex-wrap: wrap;
}

View File

@@ -20,7 +20,11 @@
.frontTextContainer {
z-index: 1;
color: var(--white);
color: var(--progressBarFrontTextColor);
}
.backTextContainer {
color: var(--progressBarBackTextColor);
}
.backTextContainer,

View File

@@ -64,6 +64,15 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'customFormats',
label: React.createElement(Icon, {
name: icons.INTERACTIVE,
title: translate('CustomFormat')
}),
isSortable: true,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, {

View File

@@ -5,8 +5,10 @@
}
.quality,
.language {
.languages {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
text-align: center;
}
.label {
@@ -21,3 +23,7 @@
margin-top: 0;
text-align: start;
}
.customFormatTooltip {
max-width: 250px;
}

View File

@@ -12,6 +12,7 @@ import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import formatBytes from 'Utilities/Number/formatBytes';
@@ -150,6 +151,7 @@ class InteractiveImportRow extends Component {
languages,
releaseGroup,
size,
customFormats,
rejections,
isReprocessing,
isSelected,
@@ -226,7 +228,7 @@ class InteractiveImportRow extends Component {
</TableRowCellButton>
<TableRowCellButton
className={styles.language}
className={styles.languages}
title={translate('ClickToChangeLanguage')}
onPress={this.onSelectLanguagePress}
>
@@ -259,7 +261,26 @@ class InteractiveImportRow extends Component {
<TableRowCell>
{
!!rejections.length &&
customFormats?.length ?
<Popover
anchor={
<Icon name={icons.INTERACTIVE} />
}
title="Formats"
body={
<div className={styles.customFormatTooltip}>
<MovieFormats formats={customFormats} />
</div>
}
position={tooltipPositions.LEFT}
/> :
null
}
</TableRowCell>
<TableRowCell>
{
rejections.length ?
<Popover
anchor={
<Icon
@@ -282,7 +303,9 @@ class InteractiveImportRow extends Component {
</ul>
}
position={tooltipPositions.LEFT}
/>
canFlip={false}
/> :
null
}
</TableRowCell>
@@ -330,6 +353,7 @@ InteractiveImportRow.propTypes = {
languages: PropTypes.arrayOf(PropTypes.object),
releaseGroup: PropTypes.string,
size: PropTypes.number.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
isReprocessing: PropTypes.bool,
isSelected: PropTypes.bool,

View File

@@ -62,6 +62,7 @@ class MovieHistoryRow extends Component {
sourceTitle,
quality,
customFormats,
customFormatScore,
languages,
qualityCutoffNotMet,
date,
@@ -106,7 +107,7 @@ class MovieHistoryRow extends Component {
</TableRowCell>
<TableRowCell key={name}>
{formatCustomFormatScore(data.customFormatScore)}
{formatCustomFormatScore(customFormatScore)}
</TableRowCell>
<RelativeDateCellConnector
@@ -161,7 +162,8 @@ MovieHistoryRow.propTypes = {
sourceTitle: PropTypes.string.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,

View File

@@ -17,6 +17,10 @@
font-size: 24px;
}
.buttons {
flex: 0 0 auto;
}
.cloneButton {
composes: button from '~Components/Link/IconButton.css';
@@ -36,3 +40,10 @@
margin: 0;
border: none;
}
.label {
@add-mixin truncate;
composes: label from '~Components/Label.css';
max-width: 100%;
}

View File

@@ -90,7 +90,7 @@ class CustomFormat extends Component {
{name}
</div>
<div>
<div className={styles.buttons}>
<IconButton
className={styles.cloneButton}
title={translate('CloneCustomFormat')}
@@ -124,6 +124,7 @@ class CustomFormat extends Component {
return (
<Label
className={styles.label}
key={index}
kind={kind}
>

View File

@@ -89,6 +89,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="enableRss"
helpText={supportsRss.value ? translate('RSSHelpText') : undefined}
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
isDisabled={!supportsRss.value}
{...enableRss}

View File

@@ -30,6 +30,7 @@
code {
padding: 0 1px;
border: 1px solid var(--borderColor);
background-color: #f7f7f7;
background-color: var(--modalCloseButtonHoverColor);
color: var(--movieBackgroundColor);
}
}

View File

@@ -7,11 +7,11 @@
&:hover {
.token {
background-color: #ddd;
background-color: var(--popoverTitleBackgroundInverseColor);
}
.example {
background-color: #ccc;
background-color: var(--popoverTitleBorderInverseColor);
}
}
}
@@ -27,7 +27,7 @@
.token {
flex: 0 0 50%;
padding: 6px 16px;
background-color: #eee;
background-color: var(--popoverTitleBorderColor);
font-family: $monoSpaceFontFamily;
}
@@ -38,7 +38,7 @@
justify-content: space-between;
flex: 0 0 50%;
padding: 6px 16px;
background-color: #ddd;
background-color: var(--popoverTitleBackgroundColor);
.footNote {
padding: 2px;

View File

@@ -118,6 +118,12 @@ export const defaultState = {
isSortable: true,
isVisible: false
},
{
name: 'year',
label: translate('Year'),
isSortable: true,
isVisible: true
},
{
name: 'outputPath',
label: translate('OutputPath'),

View File

@@ -201,6 +201,11 @@ export const defaultState = {
return genreList.sort(sortByName);
}
},
{
name: 'customFormatScore',
label: translate('CustomFormatScore'),
type: filterBuilderTypes.NUMBER
},
{
name: 'rejectionCount',
label: translate('RejectionCount'),

View File

@@ -226,6 +226,8 @@ module.exports = {
//
// Misc
progressBarFrontTextColor: white,
progressBarBackTextColor: white,
progressBarBackgroundColor: '#727070',
logEventsBackgroundColor: '#2a2a2a'
};

View File

@@ -1,7 +1,11 @@
import * as dark from './dark';
import * as light from './light';
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const auto = defaultDark ? { ...dark } : { ...light };
export default {
auto,
light,
dark
};

View File

@@ -198,8 +198,8 @@ module.exports = {
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
popoverArrowBorderColor: '#fff',
popoverTitleBackgroundInverseColor: '#595959',
popoverTitleBorderInverseColor: '#707070',
popoverTitleBackgroundInverseColor: '#9b9b9b',
popoverTitleBorderInverseColor: '#bfbfbf',
popoverShadowInverseColor: 'rgba(0, 0, 0, 0.2)',
popoverArrowBorderInverseColor: 'rgba(58, 63, 81, 0.75)',
@@ -227,6 +227,8 @@ module.exports = {
//
// Misc
progressBarBackgroundColor: '#fff',
logEventsBackgroundColor: '#fff'
progressBarFrontTextColor: white,
progressBarBackTextColor: darkGray,
progressBarBackgroundColor: white,
logEventsBackgroundColor: white
};

View File

@@ -1,5 +1,4 @@
function formatCustomFormatScore(input) {
function formatCustomFormatScore(input, customFormatsLength = 0) {
const score = Number(input);
if (score > 0) {
@@ -10,7 +9,7 @@ function formatCustomFormatScore(input) {
return score;
}
return '';
return customFormatsLength > 0 ? '+0' : '';
}
export default formatCustomFormatScore;

View File

@@ -113,7 +113,7 @@
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "5.0.0",
"html-webpack-plugin": "5.3.1",
"loader-utils": "^2.0.0",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "1.5.0",
"postcss": "8.2.12",
"postcss-color-function": "4.1.0",
@@ -134,5 +134,9 @@
"webpack-cli": "4.9.1",
"webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8"
},
"volta": {
"node": "16.17.0",
"yarn": "1.22.19"
}
}

View File

@@ -0,0 +1,25 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
{
[TestFixture]
public class IsValidIPAddressFixture
{
[TestCase("192.168.0.1")]
[TestCase("::1")]
[TestCase("2001:db8:4006:812::200e")]
public void should_validate_ip_address(string input)
{
input.IsValidIpAddress().Should().BeTrue();
}
[TestCase("sonarr.tv")]
public void should_not_parse_non_ip_address(string input)
{
input.IsValidIpAddress().Should().BeFalse();
}
}
}

View File

@@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
@@ -10,6 +10,7 @@ namespace NzbDrone.Common.Test.Http
[TestCase("abc://my_host.com:8080/root/api/")]
[TestCase("abc://my_host.com:8080//root/api/")]
[TestCase("abc://my_host.com:8080/root//api/")]
[TestCase("abc://[::1]:8080/root//api/")]
public void should_parse(string uri)
{
var newUri = new HttpUri(uri);

View File

@@ -9,6 +9,7 @@ namespace NzbDrone.Common.EnvironmentInfo
bool IsAdmin { get; }
bool IsWindowsService { get; }
bool IsWindowsTray { get; }
bool IsStarting { get; set; }
bool IsExiting { get; set; }
bool IsTray { get; }
RuntimeMode Mode { get; }

View File

@@ -19,6 +19,7 @@ namespace NzbDrone.Common.EnvironmentInfo
_logger = logger;
IsWindowsService = hostLifetime is WindowsServiceLifetime;
IsStarting = true;
// net6.0 will return Radarr.dll for entry assembly, we need the actual
// executable name (Radarr on linux). On mono this will return the location of
@@ -82,6 +83,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public bool IsWindowsService { get; private set; }
public bool IsStarting { get; set; }
public bool IsExiting { get; set; }
public bool IsTray
{

View File

@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
{
public static bool IsLocalAddress(this IPAddress ipAddress)
{
if (ipAddress.IsIPv6LinkLocal)
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
if (ipAddress.IsIPv4MappedToIPv6)
{
return true;
ipAddress = ipAddress.MapToIPv4();
}
// Checks loopback ranges for both IPv4 and IPv6.
if (IPAddress.IsLoopback(ipAddress))
{
return true;
}
// IPv4
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
byte[] bytes = ipAddress.GetAddressBytes();
switch (bytes[0])
{
case 10:
case 127:
return true;
case 172:
return bytes[1] < 32 && bytes[1] >= 16;
case 192:
return bytes[1] == 168;
default:
return false;
}
return IsLocalIPv4(ipAddress.GetAddressBytes());
}
// IPv6
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
return ipAddress.IsIPv6LinkLocal ||
ipAddress.IsIPv6UniqueLocal ||
ipAddress.IsIPv6SiteLocal;
}
return false;
}
private static bool IsLocalIPv4(byte[] ipv4Bytes)
{
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
// Class A private range: 10.0.0.0 10.255.255.255 (10.0.0.0/8)
bool IsClassA() => ipv4Bytes[0] == 10;
// Class B private range: 172.16.0.0 172.31.255.255 (172.16.0.0/12)
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
// Class C private range: 192.168.0.0 192.168.255.255 (192.168.0.0/16)
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
}
}
}

View File

@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
@@ -192,5 +194,30 @@ namespace NzbDrone.Common.Extensions
.Replace("'", "%27")
.Replace("%7E", "~");
}
public static bool IsValidIpAddress(this string value)
{
if (!IPAddress.TryParse(value, out var parsedAddress))
{
return false;
}
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
{
return false;
}
if (parsedAddress.IsIPv6Multicast)
{
return false;
}
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
}
public static string ToUrlHost(this string input)
{
return input.Contains(":") ? $"[{input}]" : input;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri => _uri;
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
private void Parse()
{
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
var match = RegexUri.Match(_uri);
var scheme = match.Groups["scheme"];
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
var query = match.Groups["query"];
var fragment = match.Groups["fragment"];
if (!match.Success || (scheme.Success && !host.Success && path.Success))
if (!parseSuccess || (scheme.Success && !host.Success && path.Success))
{
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
}

View File

@@ -11,26 +11,41 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
try
{
sentryEvent.Message.Message = CleanseLogMessage.Cleanse(sentryEvent.Message.Message);
if (sentryEvent.Message is not null)
{
sentryEvent.Message.Formatted = CleanseLogMessage.Cleanse(sentryEvent.Message.Formatted);
sentryEvent.Message.Message = CleanseLogMessage.Cleanse(sentryEvent.Message.Message);
sentryEvent.Message.Params = sentryEvent.Message.Params?.Select(x => CleanseLogMessage.Cleanse(x switch
{
string str => str,
_ => x.ToString()
})).ToList();
}
if (sentryEvent.Fingerprint != null)
if (sentryEvent.Fingerprint.Any())
{
var fingerprint = sentryEvent.Fingerprint.Select(x => CleanseLogMessage.Cleanse(x)).ToList();
sentryEvent.SetFingerprint(fingerprint);
}
if (sentryEvent.Extra != null)
if (sentryEvent.Extra.Any())
{
var extras = sentryEvent.Extra.ToDictionary(x => x.Key, y => (object)CleanseLogMessage.Cleanse((string)y.Value));
var extras = sentryEvent.Extra.ToDictionary(x => x.Key, y => (object)CleanseLogMessage.Cleanse(y.Value as string));
sentryEvent.SetExtras(extras);
}
foreach (var exception in sentryEvent.SentryExceptions)
if (sentryEvent.SentryExceptions is not null)
{
exception.Value = CleanseLogMessage.Cleanse(exception.Value);
foreach (var frame in exception.Stacktrace.Frames)
foreach (var exception in sentryEvent.SentryExceptions)
{
frame.FileName = ShortenPath(frame.FileName);
exception.Value = CleanseLogMessage.Cleanse(exception.Value);
if (exception.Stacktrace is not null)
{
foreach (var frame in exception.Stacktrace.Frames)
{
frame.FileName = ShortenPath(frame.FileName);
}
}
}
}
}

View File

@@ -8,6 +8,7 @@ using System.Threading;
using NLog;
using NLog.Common;
using NLog.Targets;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
@@ -34,6 +35,14 @@ namespace NzbDrone.Common.Instrumentation.Sentry
SQLiteErrorCode.Auth
};
private static readonly HashSet<string> FilteredPostgresErrorCodes = new HashSet<string>
{
PostgresErrorCodes.OutOfMemory,
PostgresErrorCodes.TooManyConnections,
PostgresErrorCodes.DiskFull,
PostgresErrorCodes.ProgramLimitExceeded
};
// use string and not Type so we don't need a reference to the project
// where these are defined
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
@@ -99,9 +108,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Dsn = dsn;
o.AttachStacktrace = true;
o.MaxBreadcrumbs = 200;
o.SendDefaultPii = false;
o.Debug = false;
o.DiagnosticLevel = SentryLevel.Debug;
o.Release = BuildInfo.Release;
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
@@ -253,6 +259,19 @@ namespace NzbDrone.Common.Instrumentation.Sentry
isSentry = false;
}
var pgEx = logEvent.Exception as PostgresException;
if (pgEx != null && FilteredPostgresErrorCodes.Contains(pgEx.SqlState))
{
return false;
}
// We don't care about transient network and timeout errors
var npgEx = logEvent.Exception as NpgsqlException;
if (npgEx != null && npgEx.IsTransient)
{
return false;
}
if (FilteredExceptionTypeNames.Contains(ex.GetType().Name))
{
isSentry = false;

View File

@@ -7,10 +7,11 @@
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.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="Sentry" Version="3.20.1" />
<PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="Sentry" Version="3.23.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.5" />

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -9,7 +9,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats
{
[TestFixture]
public class CustomFormatsFixture : CoreTest
public class CustomFormatsTestHelpers : CoreTest
{
private static List<CustomFormat> _customFormats { get; set; }

View File

@@ -46,14 +46,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) },
};
CustomFormatsFixture.GivenCustomFormats(_format1, _format2);
CustomFormatsTestHelpers.GivenCustomFormats(_format1, _format2);
}
[Test]
public void should_allow_if_format_score_greater_than_min()
{
_remoteMovie.CustomFormats = new List<CustomFormat> { _format1 };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name);
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_deny_if_format_score_not_greater_than_min()
{
_remoteMovie.CustomFormats = new List<CustomFormat> { _format2 };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name);
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Console.WriteLine(_remoteMovie.CustomFormatScore);
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_deny_if_format_score_not_greater_than_min_2()
{
_remoteMovie.CustomFormats = new List<CustomFormat> { _format2, _format1 };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name);
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
@@ -86,7 +86,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_allow_if_all_format_is_defined_in_profile()
{
_remoteMovie.CustomFormats = new List<CustomFormat> { _format2, _format1 };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_deny_if_no_format_was_parsed_and_min_score_positive()
{
_remoteMovie.CustomFormats = new List<CustomFormat> { };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
@@ -106,7 +106,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_allow_if_no_format_was_parsed_min_score_is_zero()
{
_remoteMovie.CustomFormats = new List<CustomFormat> { };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_format1.Name, _format2.Name);
_remoteMovie.Movie.Profile.MinFormatScore = 0;
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);

View File

@@ -40,8 +40,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenProfile(Profile profile)
{
CustomFormatsFixture.GivenCustomFormats();
profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems();
CustomFormatsTestHelpers.GivenCustomFormats();
profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems();
profile.MinFormatScore = 0;
_remoteMovie.Movie.Profile = profile;
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
_customFormat = new CustomFormat("My Format", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 };
CustomFormatsFixture.GivenCustomFormats(_customFormat);
CustomFormatsTestHelpers.GivenCustomFormats(_customFormat);
}
[Test]
@@ -157,7 +157,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
MinFormatScore = 0,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("My Format"),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("My Format"),
UpgradeAllowed = true
});

View File

@@ -38,14 +38,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.Resolve<UpgradableSpecification>();
_upgradeHistory = Mocker.Resolve<HistorySpecification>();
CustomFormatsFixture.GivenCustomFormats();
CustomFormatsTestHelpers.GivenCustomFormats();
_fakeMovie = Builder<Movie>.CreateNew()
.With(c => c.Profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.Bluray1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("None"),
MinFormatScore = 0,
UpgradeAllowed = true
})
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Returns(true);
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>()))
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>(), It.IsAny<Movie>()))
.Returns(new List<CustomFormat>());
}
@@ -163,7 +163,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.Bluray1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0
};
@@ -171,7 +171,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>()))
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>(), It.IsAny<Movie>()))
.Returns(new List<CustomFormat>());
GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, MovieHistoryEventType.Grabbed);
@@ -186,7 +186,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.WEBDL1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0
};
@@ -221,7 +221,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.WEBDL1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0
};

View File

@@ -41,17 +41,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void WithEnglishRelease()
{
_remoteMovie.ParsedMovieInfo.Languages = new List<Language> { Language.English };
_remoteMovie.Languages = new List<Language> { Language.English };
}
private void WithGermanRelease()
{
_remoteMovie.ParsedMovieInfo.Languages = new List<Language> { Language.German };
_remoteMovie.Languages = new List<Language> { Language.German };
}
private void WithFrenchRelease()
{
_remoteMovie.ParsedMovieInfo.Languages = new List<Language> { Language.French };
_remoteMovie.Languages = new List<Language> { Language.French };
}
[Test]

View File

@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_customFormat1 = new CustomFormat("My Format 1", new LanguageSpecification { Value = (int)Language.English }) { Id = 1 };
_customFormat2 = new CustomFormat("My Format 2", new LanguageSpecification { Value = (int)Language.French }) { Id = 2 };
CustomFormatsFixture.GivenCustomFormats(_customFormat1, _customFormat2);
CustomFormatsTestHelpers.GivenCustomFormats(_customFormat1, _customFormat2);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(s => s.Get(It.IsAny<Quality>()))
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
remoteMovie.Movie = Builder<Movie>.CreateNew().With(m => m.Profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
MinFormatScore = 0
})
.With(m => m.Title = "A Movie")

View File

@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[SetUp]
public void Setup()
{
CustomFormatsFixture.GivenCustomFormats(_customFormat1, _customFormat2);
CustomFormatsTestHelpers.GivenCustomFormats(_customFormat1, _customFormat2);
}
private void GivenAutoDownloadPropers(ProperDownloadTypes type)
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
MinFormatScore = 0
};

View File

@@ -32,13 +32,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Mocker.Resolve<UpgradableSpecification>();
CustomFormatsFixture.GivenCustomFormats();
CustomFormatsTestHelpers.GivenCustomFormats();
_movie = Builder<Movie>.CreateNew()
.With(e => e.Profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0,
UpgradeAllowed = true
})
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Build();
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<ParsedMovieInfo>(), _movie))
.Setup(x => x.ParseCustomFormat(It.IsAny<RemoteMovie>(), It.IsAny<long>()))
.Returns(new List<CustomFormat>());
}

View File

@@ -0,0 +1,209 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class UpgradeAllowedSpecificationFixture : CoreTest<UpgradableSpecification>
{
private CustomFormat _customFormatOne;
private CustomFormat _customFormatTwo;
private Profile _qualityProfile;
[SetUp]
public void Setup()
{
_customFormatOne = new CustomFormat
{
Id = 1,
Name = "One"
};
_customFormatTwo = new CustomFormat
{
Id = 2,
Name = "Two"
};
_qualityProfile = new Profile
{
Cutoff = Quality.Bluray1080p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
UpgradeAllowed = false,
CutoffFormatScore = 100,
FormatItems = new List<ProfileFormatItem>
{
new ProfileFormatItem
{
Format = _customFormatOne,
Score = 50
},
new ProfileFormatItem
{
Format = _customFormatTwo,
Score = 100
}
}
};
}
[Test]
public void should_return_false_when_quality_is_better_custom_formats_are_the_same_and_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.Bluray1080p),
new List<CustomFormat>())
.Should().BeFalse();
}
[Test]
public void should_return_false_when_quality_is_same_and_custom_format_is_upgrade_and_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo })
.Should().BeFalse();
}
[Test]
public void should_return_true_for_custom_format_upgrade_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_same_custom_format_score_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_custom_format_score_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_language_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatTwo },
new QualityModel(Quality.DVD),
new List<CustomFormat> { _customFormatOne })
.Should().BeTrue();
}
[Test]
public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.Bluray1080p),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_same_quality_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.DVD),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_same_quality_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.DVD),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_quality_when_upgrading_is_allowed()
{
_qualityProfile.UpgradeAllowed = true;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.SDTV),
new List<CustomFormat>())
.Should().BeTrue();
}
[Test]
public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed()
{
_qualityProfile.UpgradeAllowed = false;
Subject.IsUpgradeAllowed(
_qualityProfile,
new QualityModel(Quality.DVD),
new List<CustomFormat>(),
new QualityModel(Quality.SDTV),
new List<CustomFormat>())
.Should().BeTrue();
}
}
}

View File

@@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.Resolve<UpgradableSpecification>();
_upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>();
CustomFormatsFixture.GivenCustomFormats();
CustomFormatsTestHelpers.GivenCustomFormats();
_firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now };
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(c => c.Profile = new Profile
{
Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0
})
.With(e => e.MovieFile = _firstFile)

View File

@@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Download.Aggregation.Aggregators;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Download.Aggregation.Aggregators
{
[TestFixture]
public class AggregateLanguagesFixture : CoreTest<AggregateLanguages>
{
private RemoteMovie _remoteMovie;
private Movie _movie;
private string _simpleReleaseTitle = "Series.Title.S01E01.xyz-RlsGroup";
[SetUp]
public void Setup()
{
_movie = Builder<Movie>.CreateNew()
.With(m => m.MovieMetadata = new MovieMetadata
{
Title = "Some Movie",
OriginalLanguage = Language.English
})
.Build();
_remoteMovie = Builder<RemoteMovie>.CreateNew()
.With(l => l.ParsedMovieInfo = null)
.With(l => l.Movie = _movie)
.Build();
}
private ParsedMovieInfo GetParsedMovieInfo(List<Language> languages, string releaseTitle, string releaseTokens = "")
{
return new ParsedMovieInfo
{
Languages = languages,
ReleaseTitle = releaseTitle,
SimpleReleaseTitle = releaseTokens
};
}
[Test]
public void should_return_existing_language_if_episode_title_does_not_have_language()
{
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.Original }, _simpleReleaseTitle);
Subject.Aggregate(_remoteMovie).Languages.Should().Contain(_movie.MovieMetadata.Value.OriginalLanguage);
}
[Test]
public void should_return_parsed_language()
{
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.French }, _simpleReleaseTitle);
Subject.Aggregate(_remoteMovie).Languages.Should().Equal(_remoteMovie.ParsedMovieInfo.Languages);
}
[Test]
public void should_exclude_language_that_is_part_of_episode_title_when_release_tokens_contains_episode_title()
{
var releaseTitle = "Series.Title.S01E01.Jimmy.The.Greek.xyz-RlsGroup";
var releaseTokens = ".Jimmy.The.Greek.xyz-RlsGroup";
_remoteMovie.Movie.Title = "Jimmy The Greek";
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.Greek }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteMovie).Languages.Should().Equal(_movie.MovieMetadata.Value.OriginalLanguage);
}
[Test]
public void should_remove_parsed_language_that_is_part_of_episode_title_when_release_tokens_contains_episode_title()
{
var releaseTitle = "Series.Title.S01E01.Jimmy.The.Greek.French.xyz-RlsGroup";
var releaseTokens = ".Jimmy.The.Greek.French.xyz-RlsGroup";
_remoteMovie.Movie.Title = "Jimmy The Greek";
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.Greek, Language.French }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteMovie).Languages.Should().Equal(Language.French);
}
[Test]
public void should_not_exclude_language_that_is_part_of_episode_title_when_release_tokens_does_not_contain_episode_title()
{
var releaseTitle = "Series.Title.S01E01.xyz-RlsGroup";
var releaseTokens = ".xyz-RlsGroup";
_remoteMovie.Movie.Title = "Jimmy The Greek";
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.Greek }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteMovie).Languages.Should().Equal(Language.Greek);
}
[Test]
public void should_use_reparse_language_after_determining_languages_that_are_in_episode_titles()
{
var releaseTitle = "Series.Title.S01E01.Jimmy.The.Greek.Greek.xyz-RlsGroup";
var releaseTokens = ".Jimmy.The.Greek.Greek.xyz-RlsGroup";
_remoteMovie.Movie.Title = "Jimmy The Greek";
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.Greek }, releaseTitle, releaseTokens);
Subject.Aggregate(_remoteMovie).Languages.Should().Equal(Language.Greek);
}
}
}

View File

@@ -0,0 +1,367 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.FreeboxDownload;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.FreeboxDownloadTests
{
[TestFixture]
public class TorrentFreeboxDownloadFixture : DownloadClientFixtureBase<TorrentFreeboxDownload>
{
protected FreeboxDownloadSettings _settings;
protected FreeboxDownloadConfiguration _downloadConfiguration;
protected FreeboxDownloadTask _task;
protected string _defaultDestination = @"/some/path";
protected string _encodedDefaultDestination = "L3NvbWUvcGF0aA==";
protected string _category = "somecat";
protected string _encodedDefaultDestinationAndCategory = "L3NvbWUvcGF0aC9zb21lY2F0";
protected string _destinationDirectory = @"/path/to/media";
protected string _encodedDestinationDirectory = "L3BhdGgvdG8vbWVkaWE=";
protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata");
protected string _downloadURL => "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download";
[SetUp]
public void Setup()
{
Subject.Definition = new DownloadClientDefinition();
_settings = new FreeboxDownloadSettings()
{
Host = "127.0.0.1",
Port = 443,
ApiUrl = "/api/v1/",
AppId = "someid",
AppToken = "S0mEv3RY1oN9T0k3n"
};
Subject.Definition.Settings = _settings;
_downloadConfiguration = new FreeboxDownloadConfiguration()
{
DownloadDirectory = _encodedDefaultDestination
};
_task = new FreeboxDownloadTask()
{
Id = "id0",
Name = "name",
DownloadDirectory = "L3NvbWUvcGF0aA==",
InfoHash = "HASH",
QueuePosition = 1,
Status = FreeboxDownloadTaskStatus.Unknown,
Eta = 0,
Error = "none",
Type = FreeboxDownloadTaskType.Bt.ToString(),
IoPriority = FreeboxDownloadTaskIoPriority.Normal.ToString(),
StopRatio = 150,
PieceLength = 125,
CreatedTimestamp = 1665261599,
Size = 1000,
ReceivedPrct = 0,
ReceivedBytes = 0,
ReceivedRate = 0,
TransmittedPrct = 0,
TransmittedBytes = 0,
TransmittedRate = 0,
};
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
}
protected void GivenCategory()
{
_settings.Category = _category;
}
protected void GivenDestinationDirectory()
{
_settings.DestinationDirectory = _destinationDirectory;
}
protected virtual void GivenDownloadConfiguration()
{
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.GetDownloadConfiguration(It.IsAny<FreeboxDownloadSettings>()))
.Returns(_downloadConfiguration);
}
protected virtual void GivenTasks(List<FreeboxDownloadTask> torrents)
{
if (torrents == null)
{
torrents = new List<FreeboxDownloadTask>();
}
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.GetTasks(It.IsAny<FreeboxDownloadSettings>()))
.Returns(torrents);
}
protected void PrepareClientToReturnQueuedItem()
{
_task.Status = FreeboxDownloadTaskStatus.Queued;
GivenTasks(new List<FreeboxDownloadTask>
{
_task
});
}
protected void GivenSuccessfulDownload()
{
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Setup(s => s.AddTaskFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected override RemoteMovie CreateRemoteMovie()
{
var movie = base.CreateRemoteMovie();
movie.Release.DownloadUrl = _downloadURL;
return movie;
}
[Test]
public void Download_with_DestinationDirectory_should_force_directory()
{
GivenDestinationDirectory();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDestinationDirectory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[Test]
public void Download_with_Category_should_force_directory()
{
GivenDownloadConfiguration();
GivenCategory();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestinationAndCategory, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[Test]
public void Download_without_DestinationDirectory_and_Category_should_use_default()
{
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), _encodedDefaultDestination, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[TestCase(false, false)]
[TestCase(true, true)]
public void Download_should_pause_torrent_as_expected(bool addPausedSetting, bool toBePausedFlag)
{
_settings.AddPaused = addPausedSetting;
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
Subject.Download(remoteMovie);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), toBePausedFlag, It.IsAny<bool>(), It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, true)]
[TestCase(0, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, false)]
[TestCase(0, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.First, true)]
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.First, false)]
[TestCase(22, (int)FreeboxDownloadPriority.First, (int)FreeboxDownloadPriority.Last, true)]
[TestCase(22, (int)FreeboxDownloadPriority.Last, (int)FreeboxDownloadPriority.Last, false)]
public void Download_should_queue_torrent_first_as_expected(int ageDay, int olderPriority, int recentPriority, bool toBeQueuedFirstFlag)
{
_settings.OlderPriority = olderPriority;
_settings.RecentPriority = recentPriority;
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
remoteMovie.Movie.MovieMetadata.Value.PhysicalRelease = DateTime.UtcNow.AddDays(-ageDay);
Subject.Download(remoteMovie);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), toBeQueuedFirstFlag, It.IsAny<double?>(), It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[TestCase(0, 0)]
[TestCase(1.5, 150)]
public void Download_should_define_seed_ratio_as_expected(double? providerSeedRatio, double? expectedSeedRatio)
{
GivenDownloadConfiguration();
GivenSuccessfulDownload();
var remoteMovie = CreateRemoteMovie();
remoteMovie.SeedConfiguration = new TorrentSeedConfiguration();
remoteMovie.SeedConfiguration.Ratio = providerSeedRatio;
Subject.Download(remoteMovie);
Mocker.GetMock<IFreeboxDownloadProxy>()
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), expectedSeedRatio, It.IsAny<FreeboxDownloadSettings>()), Times.Once());
}
[Test]
public void GetItems_should_return_empty_list_if_no_tasks_available()
{
GivenTasks(new List<FreeboxDownloadTask>());
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_should_return_ignore_tasks_of_unknown_type()
{
_task.Status = FreeboxDownloadTaskStatus.Done;
_task.Type = "toto";
GivenTasks(new List<FreeboxDownloadTask> { _task });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_when_destinationdirectory_is_set_should_ignore_downloads_in_wrong_folder()
{
_settings.DestinationDirectory = @"/some/path/that/will/not/match";
_task.Status = FreeboxDownloadTaskStatus.Done;
GivenTasks(new List<FreeboxDownloadTask> { _task });
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void GetItems_when_category_is_set_should_ignore_downloads_in_wrong_folder()
{
_settings.Category = "somecategory";
_task.Status = FreeboxDownloadTaskStatus.Done;
GivenTasks(new List<FreeboxDownloadTask> { _task });
Subject.GetItems().Should().BeEmpty();
}
[TestCase(FreeboxDownloadTaskStatus.Downloading, false, false)]
[TestCase(FreeboxDownloadTaskStatus.Done, true, true)]
[TestCase(FreeboxDownloadTaskStatus.Seeding, false, false)]
[TestCase(FreeboxDownloadTaskStatus.Stopped, false, false)]
public void GetItems_should_return_canBeMoved_and_canBeDeleted_as_expected(FreeboxDownloadTaskStatus apiStatus, bool canMoveFilesExpected, bool canBeRemovedExpected)
{
_task.Status = apiStatus;
GivenTasks(new List<FreeboxDownloadTask>() { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().CanBeRemoved.Should().Be(canBeRemovedExpected);
items.First().CanMoveFiles.Should().Be(canMoveFilesExpected);
}
[TestCase(FreeboxDownloadTaskStatus.Stopped, DownloadItemStatus.Paused)]
[TestCase(FreeboxDownloadTaskStatus.Stopping, DownloadItemStatus.Paused)]
[TestCase(FreeboxDownloadTaskStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(FreeboxDownloadTaskStatus.Starting, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Retry, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Checking, DownloadItemStatus.Downloading)]
[TestCase(FreeboxDownloadTaskStatus.Error, DownloadItemStatus.Warning)]
[TestCase(FreeboxDownloadTaskStatus.Seeding, DownloadItemStatus.Completed)]
[TestCase(FreeboxDownloadTaskStatus.Done, DownloadItemStatus.Completed)]
[TestCase(FreeboxDownloadTaskStatus.Unknown, DownloadItemStatus.Downloading)]
public void GetItems_should_return_item_as_downloadItemStatus(FreeboxDownloadTaskStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
_task.Status = apiStatus;
GivenTasks(new List<FreeboxDownloadTask>() { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Status.Should().Be(expectedItemStatus);
}
[Test]
public void GetItems_should_return_decoded_destination_directory()
{
var decodedDownloadDirectory = "/that/the/path";
_task.Status = FreeboxDownloadTaskStatus.Done;
_task.DownloadDirectory = "L3RoYXQvdGhlL3BhdGg=";
GivenTasks(new List<FreeboxDownloadTask> { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(decodedDownloadDirectory);
}
[Test]
public void GetItems_should_return_message_if_tasks_in_error()
{
_task.Status = FreeboxDownloadTaskStatus.Error;
_task.Error = "internal";
GivenTasks(new List<FreeboxDownloadTask> { _task });
var items = Subject.GetItems();
items.Should().HaveCount(1);
items.First().Message.Should().Be("Internal error.");
items.First().Status.Should().Be(DownloadItemStatus.Warning);
}
}
}

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download
_downloadClients = new List<IDownloadClient>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClients())
.Setup(v => v.GetDownloadClients(It.IsAny<bool>()))
.Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>()

View File

@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.With(h => h.Title = title)
.With(h => h.Release = release)
.With(h => h.Reason = reason)
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
.Build();
_heldReleases.AddRange(heldReleases);

View File

@@ -52,7 +52,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
_pending.Add(new PendingRelease
{
Id = id,
Title = "Movie.Title.2020.720p-Radarr",
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
Release = Builder<ReleaseInfo>.CreateNew().Build(),
MovieId = _movie.Id
});
}

View File

@@ -93,6 +93,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.With(h => h.MovieId = _movie.Id)
.With(h => h.Title = title)
.With(h => h.Release = release)
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
.Build();
Mocker.GetMock<IPendingReleaseRepository>()

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_warning_when_download_client_has_not_been_configured()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(Array.Empty<IDownloadClient>());
Subject.Check().ShouldBeWarning();
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Throws<Exception>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeError();
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(new List<DownloadClientItem>());
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeOk();

View File

@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IDiskProvider>()

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class DownloadClientFolderCheckFixture : CoreTest<DownloadClientSortingCheck>
{
private DownloadClientInfo _clientStatus;
private Mock<IDownloadClient> _downloadClient;
private static Exception[] DownloadClientExceptions =
{
new DownloadClientUnavailableException("error"),
new DownloadClientAuthenticationException("error"),
new DownloadClientException("error")
};
[SetUp]
public void Setup()
{
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
_clientStatus = new DownloadClientInfo
{
IsLocalhost = true,
SortingMode = null
};
_downloadClient = Mocker.GetMock<IDownloadClient>();
_downloadClient.Setup(s => s.Definition)
.Returns(new DownloadClientDefinition { Name = "Test" });
_downloadClient.Setup(s => s.GetStatus())
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
}
[Test]
public void should_return_ok_if_sorting_is_not_enabled()
{
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_if_sorting_is_enabled()
{
_clientStatus.SortingMode = "TV";
Subject.Check().ShouldBeWarning();
}
[Test]
[TestCaseSource("DownloadClientExceptions")]
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
{
_downloadClient.Setup(s => s.GetStatus())
.Throws(ex);
Subject.Check().ShouldBeOk();
ExceptionVerification.ExpectedErrors(0);
}
}
}

View File

@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients())
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IConfigService>()

View File

@@ -0,0 +1,133 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupQualityProfileFormatItemsFixture : DbTest<CleanupQualityProfileFormatItems, Profile>
{
[SetUp]
public void Setup()
{
Mocker.SetConstant<IQualityProfileFormatItemsCleanupRepository>(
new QualityProfileFormatItemsCleanupRepository(Mocker.Resolve<IMainDatabase>(), Mocker.Resolve<IEventAggregator>()));
Mocker.SetConstant<ICustomFormatRepository>(
new CustomFormatRepository(Mocker.Resolve<IMainDatabase>(), Mocker.Resolve<IEventAggregator>()));
}
[Test]
public void should_remove_orphaned_custom_formats()
{
var qualityProfile = Builder<Profile>.CreateNew()
.With(h => h.Items = Qualities.QualityFixture.GetDefaultQualities())
.With(h => h.MinFormatScore = 50)
.With(h => h.CutoffFormatScore = 100)
.With(h => h.FormatItems = new List<ProfileFormatItem>
{
Builder<ProfileFormatItem>.CreateNew()
.With(c => c.Format = new CustomFormat("My Custom Format") { Id = 0 })
.Build()
})
.BuildNew();
Db.Insert(qualityProfile);
Subject.Clean();
var result = AllStoredModels;
result.Should().HaveCount(1);
result.First().FormatItems.Should().BeEmpty();
result.First().MinFormatScore.Should().Be(0);
result.First().CutoffFormatScore.Should().Be(0);
}
[Test]
public void should_not_remove_unorphaned_custom_formats()
{
var minFormatScore = 50;
var cutoffFormatScore = 100;
var customFormat = Builder<CustomFormat>.CreateNew()
.With(h => h.Specifications = new List<ICustomFormatSpecification>())
.BuildNew();
Db.Insert(customFormat);
var qualityProfile = Builder<Profile>.CreateNew()
.With(h => h.Items = Qualities.QualityFixture.GetDefaultQualities())
.With(h => h.MinFormatScore = minFormatScore)
.With(h => h.CutoffFormatScore = cutoffFormatScore)
.With(h => h.FormatItems = new List<ProfileFormatItem>
{
Builder<ProfileFormatItem>.CreateNew()
.With(c => c.Format = customFormat)
.Build()
})
.BuildNew();
Db.Insert(qualityProfile);
Subject.Clean();
var result = AllStoredModels;
result.Should().HaveCount(1);
result.First().FormatItems.Should().HaveCount(1);
result.First().MinFormatScore.Should().Be(minFormatScore);
result.First().CutoffFormatScore.Should().Be(cutoffFormatScore);
}
[Test]
public void should_add_missing_custom_formats()
{
var minFormatScore = 50;
var cutoffFormatScore = 100;
var customFormat1 = Builder<CustomFormat>.CreateNew()
.With(h => h.Id = 1)
.With(h => h.Name = "Custom Format 1")
.With(h => h.Specifications = new List<ICustomFormatSpecification>())
.BuildNew();
var customFormat2 = Builder<CustomFormat>.CreateNew()
.With(h => h.Id = 2)
.With(h => h.Name = "Custom Format 2")
.With(h => h.Specifications = new List<ICustomFormatSpecification>())
.BuildNew();
Db.Insert(customFormat1);
Db.Insert(customFormat2);
var qualityProfile = Builder<Profile>.CreateNew()
.With(h => h.Items = Qualities.QualityFixture.GetDefaultQualities())
.With(h => h.MinFormatScore = minFormatScore)
.With(h => h.CutoffFormatScore = cutoffFormatScore)
.With(h => h.FormatItems = new List<ProfileFormatItem>
{
Builder<ProfileFormatItem>.CreateNew()
.With(c => c.Format = customFormat1)
.Build()
})
.BuildNew();
Db.Insert(qualityProfile);
Subject.Clean();
var result = AllStoredModels;
result.Should().HaveCount(1);
result.First().FormatItems.Should().HaveCount(2);
result.First().MinFormatScore.Should().Be(minFormatScore);
result.First().CutoffFormatScore.Should().Be(cutoffFormatScore);
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Indexers.Omgwtfnzbs;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework;
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests
_indexers = new List<IIndexer>();
_indexers.Add(Mocker.Resolve<Newznab>());
_indexers.Add(Mocker.Resolve<Omgwtfnzbs>());
_indexers.Add(Mocker.Resolve<FileList>());
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
}

View File

@@ -1,56 +0,0 @@
using System;
using System.Linq;
using System.Net.Http;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Omgwtfnzbs;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.OmgwtfnzbsTests
{
[TestFixture]
public class OmgwtfnzbsFixture : CoreTest<Omgwtfnzbs>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
{
Name = "Omgwtfnzbs",
Settings = new OmgwtfnzbsSettings()
{
ApiKey = "xxx",
Username = "me@my.domain"
}
};
}
[Test]
public void should_parse_recent_feed_from_omgwtfnzbs()
{
var recentFeed = ReadAllText(@"Files/Indexers/Omgwtfnzbs/Omgwtfnzbs.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(100);
var releaseInfo = releases.First();
releaseInfo.Title.Should().Be("Un.Petit.Boulot.2016.FRENCH.720p.BluRay.DTS.x264-LOST");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
releaseInfo.DownloadUrl.Should().Be("https://api.omgwtfnzbs.me/nzb/?id=8a2Bw&user=nzbdrone&api=nzbdrone");
releaseInfo.InfoUrl.Should().Be("https://omgwtfnzbs.me/details.php?id=8a2Bw");
releaseInfo.CommentUrl.Should().BeNullOrEmpty();
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2017/01/09 00:16:54"));
releaseInfo.Size.Should().Be(5354909355);
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Net.Http;
using FluentAssertions;
@@ -55,7 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
first.DownloadUrl.Should().Be("https://passthepopcorn.me/torrents.php?action=download&id=452135&authkey=00000000000000000000000000000000&torrent_pass=00000000000000000000000000000000");
first.InfoUrl.Should().Be("https://passthepopcorn.me/torrents.php?id=148131&torrentid=452135");
// first.PublishDate.Should().Be(DateTime.Parse("2017-04-17T12:13:42+0000").ToUniversalTime()); stupid timezones
first.PublishDate.Should().Be(DateTime.Parse("2016-10-18T23:40:59+0000").ToUniversalTime());
first.Size.Should().Be(2466170624L);
first.InfoHash.Should().BeNullOrEmpty();
first.MagnetUrl.Should().BeNullOrEmpty();

View File

@@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 34, Language.Bengali },
new object[] { 35, Language.Slovak },
new object[] { 36, Language.Latvian },
new object[] { 37, Language.SpanishLatino }
};
public static object[] ToIntCases =
@@ -92,6 +93,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Bengali, 34 },
new object[] { Language.Slovak, 35 },
new object[] { Language.Latvian, 36 },
new object[] { Language.SpanishLatino, 37 }
};
[Test]

View File

@@ -445,6 +445,58 @@ namespace NzbDrone.Core.Test.MediaFiles
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never());
}
[Test]
public void should_return_rejection_if_nothing_imported_and_contains_rar_file()
{
GivenValidMovie();
var path = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] American Psycho (2000) [720p]\[HorribleSubs] American Psycho (2000) [720p].mkv".AsOsAgnostic();
var imported = new List<ImportDecision>();
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), null, true, true))
.Returns(imported);
Mocker.GetMock<IImportApprovedMovie>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
.Returns(imported.Select(i => new ImportResult(i)).ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(new[] { _videoFiles.First().Replace(".ext", ".rar") });
var result = Subject.ProcessPath(path);
result.Count.Should().Be(1);
result.First().Result.Should().Be(ImportResultType.Rejected);
}
[Test]
public void should_return_rejection_if_nothing_imported_and_contains_executable_file()
{
GivenValidMovie();
var path = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] American Psycho (2000) [720p]\[HorribleSubs] American Psycho (2000) [720p].mkv".AsOsAgnostic();
var imported = new List<ImportDecision>();
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Movie>(), It.IsAny<DownloadClientItem>(), null, true, true))
.Returns(imported);
Mocker.GetMock<IImportApprovedMovie>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto))
.Returns(imported.Select(i => new ImportResult(i)).ToList());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(new[] { _videoFiles.First().Replace(".ext", ".exe") });
var result = Subject.ProcessPath(path);
result.Count.Should().Be(1);
result.First().Result.Should().Be(ImportResultType.Rejected);
}
private void VerifyNoImport()
{
Mocker.GetMock<IImportApprovedMovie>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),

View File

@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.MovieTests.MovieRepositoryTests
var profile = new Profile
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
FormatItems = CustomFormatsFixture.GetDefaultFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetDefaultFormatItems(),
MinFormatScore = 0,
Cutoff = Quality.Bluray1080p.Id,
Name = "TestProfile"

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -13,10 +14,10 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.NotificationTests
{
[TestFixture]
public class TraktServiceFixture : CoreTest<TraktService>
public class TraktServiceFixture : CoreTest<Trakt>
{
private DownloadMessage _downloadMessage;
private TraktSettings _traktSettings;
private NotificationDefinition _traktDefinition;
[SetUp]
public void Setup()
@@ -34,11 +35,17 @@ namespace NzbDrone.Core.Test.NotificationTests
}
};
_traktSettings = new TraktSettings
_traktDefinition = new NotificationDefinition
{
AccessToken = "",
RefreshToken = ""
Settings = new TraktSettings
{
AccessToken = "",
RefreshToken = "",
Expires = DateTime.Now.AddDays(1)
}
};
Subject.Definition = _traktDefinition;
}
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
@@ -56,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_add_collection_movie_if_null_mediainfo()
{
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.IsAny<TraktCollectMoviesResource>(), It.IsAny<string>()), Times.Once());
@@ -67,7 +74,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{
GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
@@ -83,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile);
Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>

View File

@@ -10,6 +10,8 @@ namespace NzbDrone.Core.Test.OrganizerTests
{
[TestCase("Mission: Impossible - no [HDTV-720p]",
"Mission Impossible - no [HDTV-720p]")]
[TestCase(".45 (2006)", "45 (2006)")]
[TestCase(" The Movie Title ", "The Movie Title")]
public void CleanFileName(string name, string expectedName)
{
FileNameBuilder.CleanFileName(name).Should().Be(expectedName);

View File

@@ -562,19 +562,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(string.Format("HDTV-720p{0}South{0}Park", separator));
}
[Test]
public void should_be_able_to_use_original_filename()
{
_movie.Title = "30 Rock";
_namingConfig.StandardMovieFormat = "{Movie Title} - {Original Filename}";
_movieFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_movieFile.RelativePath = "30 Rock - S01E01 - Test";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("30 Rock - 30 Rock - S01E01 - Test");
}
[TestCase("en-US")]
[TestCase("fr-FR")]
[TestCase("az")]

View File

@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class OriginalTitleFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private MovieFile _movieFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.With(s => s.Title = "My Movie")
.Build();
_movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" };
_namingConfig = NamingConfig.Default;
_namingConfig.RenameMovies = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
Mocker.GetMock<ICustomFormatService>()
.Setup(v => v.All())
.Returns(new List<CustomFormat>());
}
[Test]
public void should_not_recursively_include_current_filename()
{
_movieFile.RelativePath = "My Movie";
_namingConfig.StandardMovieFormat = "{Movie Title} {[Original Title]}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("My Movie");
}
[Test]
public void should_include_original_title_if_not_current_file_name()
{
_movieFile.SceneName = "my.movie.2008";
_movieFile.RelativePath = "My Movie";
_namingConfig.StandardMovieFormat = "{Movie Title} {[Original Title]}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("My Movie [my.movie.2008]");
}
[Test]
public void should_include_current_filename_if_not_renaming_files()
{
_movieFile.SceneName = "my.movie.2008";
_namingConfig.RenameMovies = false;
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("my.movie.2008");
}
[Test]
public void should_include_current_filename_if_not_including_multiple_naming_tokens()
{
_movieFile.RelativePath = "My Movie";
_namingConfig.StandardMovieFormat = "{Original Title}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("My Movie");
}
}
}

View File

@@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.1990.Ultimate.Rekall.Edition.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA5.1-TWA", "Ultimate Rekall Edition")]
[TestCase("Movie.Title.1971.Signature.Edition.1080p.BluRay.FLAC.2.0.x264-TDD", "Signature Edition")]
[TestCase("Movie.1979.The.Imperial.Edition.BluRay.720p.DTS.x264-CtrlHD", "Imperial Edition")]
[TestCase("Movie.1997.Open.Matte.1080p.BluRay.x264.DTS-FGT", "Open Matte")]
public void should_parse_edition(string postTitle, string edition)
{
var parsed = Parser.Parser.ParseMovieTitle(postTitle);

View File

@@ -1,3 +1,4 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Languages;
@@ -380,6 +381,18 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Latvian);
}
[TestCase("Movie.Title.2019.720p_Eng-Spa(Latino)_MovieClubMx")]
[TestCase("Movie.Title.1.WEB-DL.720p.Complete.Latino.YG")]
[TestCase("Movie.Title.1080p.WEB.H264.Latino.YG")]
[TestCase("Movie Title latino")]
[TestCase("Movie Title (Temporada 11 Completa) Audio Dual Ingles/Latino 1920x1080")]
[TestCase("Movie title 7x4 audio latino")]
public void should_parse_language_spanish_latino(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
result.First().Id.Should().Be(Language.SpanishLatino.Id);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -215,6 +215,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Name FRENCH BluRay 720p 2016 kjhlj", 2016)]
[TestCase("Der.Movie.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", 1998)]
[TestCase("Movie Name (1897) [DVD].mp4", 1897)]
[TestCase("World Movie Z Movie [2023]", 2023)]
public void should_parse_movie_year(string postTitle, int year)
{
Parser.Parser.ParseMovieTitle(postTitle).Year.Should().Be(year);
@@ -254,6 +255,18 @@ namespace NzbDrone.Core.Test.ParserTests
parsed.Languages.Should().Contain(Language.German);
}
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "KORSUB")]
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "KORSUBS")]
[TestCase("Movie Title 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
[TestCase("Movie.Title.2017.720p.SUBBED.HDRip.V2.XViD-26k.avi", "Generic Hardcoded Subs")]
[TestCase("Movie.Title.2000.1080p.BlueRay.x264.DTS.RoSubbed-playHD", null)]
[TestCase("Movie Title! 2018 [Web][MKV][h264][480p][AAC 2.0][Softsubs]", null)]
[TestCase("Movie Title! 2019 [HorribleSubs][Web][MKV][h264][848x480][AAC 2.0][Softsubs(HorribleSubs)]", null)]
public void should_parse_hardcoded_subs(string postTitle, string sub)
{
Parser.Parser.ParseMovieTitle(postTitle).HardcodedSubs.Should().Be(sub);
}
[TestCase("That Italian Movie 2008 [tt1234567] 720p BluRay X264", "tt1234567")]
[TestCase("That Italian Movie 2008 [tt12345678] 720p BluRay X264", "tt12345678")]
public void should_parse_imdb_in_title(string postTitle, string imdb)

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
using NUnit.Framework;
using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{
[TestFixture]
public abstract class AugmentMovieInfoFixture<TAugmenter> : CoreTest<TAugmenter>
where TAugmenter : class, IAugmentParsedMovieInfo
{
protected ParsedMovieInfo MovieInfo;
[SetUp]
public virtual void Setup()
{
MovieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string> { "A Movie" },
Year = 1998,
SimpleReleaseTitle = "A Movie Title 1998 Bluray 1080p",
Quality = new QualityModel(Quality.Bluray1080p)
};
}
}
}

View File

@@ -1,23 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{
[TestFixture]
public class AugmentWithFileSizeFixture : AugmentMovieInfoFixture<AugmentWithFileSize>
{
[Test]
public void should_add_file_size()
{
var localMovie = new LocalMovie
{
Size = 1500
};
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, localMovie);
movieInfo.ExtraInfo["Size"].Should().BeEquivalentTo(1500);
}
}
}

View File

@@ -1,109 +0,0 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Rarbg;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{
[TestFixture]
public class AugmentWithHistoryFixture : AugmentMovieInfoFixture<AugmentWithHistory>
{
private AugmentWithHistory _customSubject { get; set; }
[SetUp]
public override void Setup()
{
base.Setup();
// Add multi indexer
GivenIndexerSettings(new RarbgSettings
{
MultiLanguages = new List<int>
{
(int)Language.English,
(int)Language.French,
}
});
}
protected new AugmentWithHistory Subject
{
get
{
if (_customSubject == null)
{
_customSubject = new AugmentWithHistory(new List<Lazy<IAugmentParsedMovieInfo>> { new (Mocker.Resolve<AugmentWithReleaseInfo>()) });
}
return _customSubject;
}
}
private void GivenIndexerSettings(IIndexerSettings indexerSettings)
{
Mocker.GetMock<IIndexerFactory>().Setup(f => f.Get(It.IsAny<int>())).Returns(new IndexerDefinition
{
Settings = indexerSettings
});
}
private MovieHistory HistoryWithData(params string[] data)
{
var dict = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
for (var i = 0; i < data.Length; i += 2)
{
dict.Add(data[i], data[i + 1]);
}
return new MovieHistory
{
Data = dict,
EventType = MovieHistoryEventType.Grabbed
};
}
[Test]
public void should_add_indexer_flags()
{
var history = HistoryWithData("IndexerFlags", (IndexerFlags.PTP_Approved | IndexerFlags.PTP_Golden).ToString());
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, history);
movieInfo.ExtraInfo["IndexerFlags"].Should().BeEquivalentTo(IndexerFlags.PTP_Golden | IndexerFlags.PTP_Approved);
}
[Test]
public void should_add_size()
{
var history = HistoryWithData("Size", 9663676416.ToString());
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, history);
movieInfo.ExtraInfo["Size"].Should().BeEquivalentTo(9663676416);
}
[Test]
public void should_use_settings_languages_when_necessary()
{
var history = HistoryWithData("IndexerId", 1.ToString());
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, history);
movieInfo.Languages.Should().BeEquivalentTo();
MovieInfo.SimpleReleaseTitle = "A Movie 1998 Bluray 1080p MULTI";
var multiInfo = Subject.AugmentMovieInfo(MovieInfo, history);
multiInfo.Languages.Should().BeEquivalentTo(Language.English, Language.French);
}
[Test]
public void should_not_use_settings_languages()
{
var unknownIndexer = HistoryWithData();
var unknownIndexerInfo = Subject.AugmentMovieInfo(MovieInfo, unknownIndexer);
unknownIndexerInfo.Languages.Should().BeEquivalentTo();
}
}
}

View File

@@ -1,90 +0,0 @@
// using FluentAssertions;
// using NUnit.Framework;
// using NzbDrone.Core.CustomFormats;
// using NzbDrone.Core.MediaFiles.MediaInfo;
// using NzbDrone.Core.Parser;
// using NzbDrone.Core.Parser.Augmenters;
// using NzbDrone.Core.Qualities;
// namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
// {
// [TestFixture]
// public class AugmentWithMediaInfoFixture : AugmentMovieInfoFixture<AugmentWithMediaInfo>
// {
// [TestCase(Resolution.R720p, Source.BLURAY, Resolution.R1080p)]
// [TestCase(Resolution.R1080p, Source.TV, Resolution.R720p)]
// public void should_correct_resolution(Resolution resolution, Source source, Resolution realResolution)
// {
// var quality = new QualityModel
// {
// Source = source,
// Resolution = resolution,
// };
// MovieInfo.Quality = quality;
// var realWidth = 480;
// switch (realResolution)
// {
// case Resolution.R720p:
// realWidth = 1280;
// break;
// case Resolution.R1080p:
// realWidth = 1920;
// break;
// case Resolution.R2160p:
// realWidth = 2160;
// break;
// }
// var mediaInfo = new MediaInfoModel
// {
// Width = realWidth
// };
// var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo);
// movieInfo.Quality.Resolution.Should().BeEquivalentTo(realResolution);
// movieInfo.Quality.QualityDetectionSource.Should().BeEquivalentTo(QualityDetectionSource.MediaInfo);
// }
// [TestCase(Resolution.R720P, Source.BLURAY, Resolution.R1080P, Modifier.BRDISK)]
// [TestCase(Resolution.R1080P, Source.BLURAY, Resolution.R720P, Modifier.REMUX)]
// [TestCase(Resolution.R480P, Source.BLURAY, Resolution.R720P)]
// [TestCase(Resolution.R720P, Source.DVD, Resolution.R480P)]
// public void should_not_correct_resolution(Resolution resolution, Source source, Resolution realResolution, Modifier modifier = Modifier.NONE)
// {
// var quality = new QualityModel
// {
// Source = source,
// Resolution = resolution,
// Modifier = modifier,
// };
// MovieInfo.Quality = quality;
// var realWidth = 480;
// switch (realResolution)
// {
// case Resolution.R720P:
// realWidth = 1280;
// break;
// case Resolution.R1080P:
// realWidth = 1920;
// break;
// case Resolution.R2160P:
// realWidth = 2160;
// break;
// }
// var mediaInfo = new MediaInfoModel
// {
// Width = realWidth
// };
// var movieInfo = Subject.AugmentMovieInfo(MovieInfo, mediaInfo);
// movieInfo.Quality.Resolution.Should().BeEquivalentTo(resolution);
// movieInfo.Quality.QualityDetectionSource.Should().BeEquivalentTo(QualityDetectionSource.Name);
// }
// }
// }

View File

@@ -1,29 +0,0 @@
using System;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{
[TestFixture]
public class AugmentWithOriginalLanguageFixture : AugmentMovieInfoFixture<AugmentWithOriginalLanguage>
{
[Test]
public void should_add_movie_original_language()
{
var releaseInfo = new ParsedMovieInfo();
var movie = new Movies.Movie
{
MovieMetadata = new Movies.MovieMetadata
{
OriginalLanguage = Language.English
}
};
var result = Subject.AugmentMovieInfo(releaseInfo, movie);
result.ExtraInfo.Should().ContainKey("OriginalLanguage");
result.ExtraInfo["OriginalLanguage"].Should().Be(Language.English);
}
}
}

View File

@@ -1,77 +0,0 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{
[TestFixture]
public class AugmentWithParsedMovieInfoFixture : AugmentMovieInfoFixture<AugmentWithParsedMovieInfo>
{
[Test]
public void should_add_edition_if_null()
{
var folderInfo = new ParsedMovieInfo
{
Edition = "Directors Cut"
};
var result = Subject.AugmentMovieInfo(MovieInfo, folderInfo);
result.Edition.Should().Be(folderInfo.Edition);
}
[Test]
public void should_preferr_longer_edition()
{
var folderInfo = new ParsedMovieInfo
{
Edition = "Super duper cut"
};
MovieInfo.Edition = "Rogue";
var result = Subject.AugmentMovieInfo(MovieInfo, folderInfo);
result.Edition.Should().Be(folderInfo.Edition);
MovieInfo.Edition = "Super duper awesome cut";
result = Subject.AugmentMovieInfo(MovieInfo, folderInfo);
result.Edition.Should().Be(MovieInfo.Edition);
}
[Test]
public void should_combine_languages()
{
var folderInfo = new ParsedMovieInfo
{
Languages = new List<Language> { Language.French }
};
MovieInfo.Languages = new List<Language> { Language.English };
var result = Subject.AugmentMovieInfo(MovieInfo, folderInfo);
result.Languages.Should().BeEquivalentTo(Language.English, Language.French);
}
[Test]
public void should_use_folder_release_group()
{
var folderInfo = new ParsedMovieInfo
{
ReleaseGroup = "AwesomeGroup"
};
MovieInfo.ReleaseGroup = "";
var result = Subject.AugmentMovieInfo(MovieInfo, folderInfo);
result.ReleaseGroup.Should().BeEquivalentTo(folderInfo.ReleaseGroup);
}
}
}

View File

@@ -1,82 +0,0 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Rarbg;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{
[TestFixture]
public class AugmentWithReleaseInfoFixture : AugmentMovieInfoFixture<AugmentWithReleaseInfo>
{
private IndexerDefinition _indexerDefinition;
private ReleaseInfo ReleaseInfoWithLanguages(params Language[] languages)
{
_indexerDefinition = new IndexerDefinition
{
Settings = new RarbgSettings { MultiLanguages = languages.ToList().Select(l => (int)l) }
};
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(1))
.Returns(_indexerDefinition);
return new ReleaseInfo
{
IndexerId = 1
};
}
[Test]
public void should_add_language_from_indexer()
{
var releaseInfo = ReleaseInfoWithLanguages(Language.English, Language.French);
MovieInfo.SimpleReleaseTitle = "A Movie Title 1998 Bluray 1080p MULTI";
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, releaseInfo);
movieInfo.Languages.Count.Should().Be(2);
movieInfo.Languages.Should().BeEquivalentTo(Language.English, Language.French);
}
[Test]
public void should_add_size_info()
{
var releaseInfo = new ReleaseInfo
{
Size = 1500
};
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, releaseInfo);
movieInfo.ExtraInfo["Size"].Should().BeEquivalentTo(1500);
}
[Test]
public void should_not_add_size_when_already_present()
{
var releaseInfo = new ReleaseInfo
{
Size = 1500
};
MovieInfo.ExtraInfo["Size"] = 1600;
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, releaseInfo);
movieInfo.ExtraInfo["Size"].Should().BeEquivalentTo(1600);
}
[Test]
public void should_add_indexer_flags()
{
var releaseInfo = new ReleaseInfo
{
IndexerFlags = IndexerFlags.PTP_Approved | IndexerFlags.PTP_Golden
};
var movieInfo = Subject.AugmentMovieInfo(MovieInfo, releaseInfo);
movieInfo.ExtraInfo["IndexerFlags"].Should().BeEquivalentTo(IndexerFlags.PTP_Approved | IndexerFlags.PTP_Golden);
}
}
}

View File

@@ -194,20 +194,5 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
Subject.Map(_umlautAltInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
}
[Test]
public void should_convert_original()
{
Subject.Map(_multiLanguageInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.English);
Subject.Map(_multiLanguageInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.French);
}
[Test]
public void should_remove_original_as_already_exists()
{
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.English);
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.French);
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().NotContain(Language.Original);
}
}
}

View File

@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Name 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)]
[TestCase("Movie Name (2018) 720p Hindi HQ CAMrip x264 AAC 1.4GB", false)]
[TestCase("Movie Name (2022) New HDCAMRip 1080p [Love Rulz]", false)]
public void should_parse_cam(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
@@ -451,17 +452,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
}
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "KORSUB")]
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "KORSUBS")]
[TestCase("Movie Title 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
[TestCase("Movie.Title.2017.720p.SUBBED.HDRip.V2.XViD-26k.avi", "Generic Hardcoded Subs")]
[TestCase("Movie Title! 2018 [Web][MKV][h264][480p][AAC 2.0][Softsubs]", null)]
[TestCase("Movie Title! 2019 [HorribleSubs][Web][MKV][h264][848x480][AAC 2.0][Softsubs(HorribleSubs)]", null)]
public void should_parse_hardcoded_subs(string postTitle, string sub)
{
QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub);
}
[TestCase("Movie Title 2018 REPACK 720p x264 aAF", true)]
[TestCase("Movie.Title.2018.REPACK.720p.x264-aAF", true)]
[TestCase("Movie.Title.2018.PROPER.720p.x264-aAF", false)]

View File

@@ -51,6 +51,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Some.Really.Bad.Movie.Title.[2021].1080p.WEB-HDRip.Dual.Audio.[Hindi.[Clean]. .English].x264.AAC.DD.2.0.By.Full4Movies.mkv-xpost", null)]
[TestCase("The.Movie.Title.2013.1080p.10bit.AMZN.WEB-DL.DDP5.1.HEVC-Vyndros", "Vyndros")]
[TestCase("Movie.Name.2022.1080p.BluRay.x264-[YTS.AG]", "YTS.AG")]
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG", "VARYG")]
[TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", null)]
public void should_parse_expected_release_group(string title, string expected)
{
@@ -107,6 +108,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Why.Cant.You.Use.Normal.Characters.2021.2160p.UHD.HDR10+.BluRay.TrueHD.Atmos.7.1.x265-ZØNEHD", "ZØNEHD")]
[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")]
public void should_parse_exception_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.Profiles
{
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
MinFormatScore = 0,
FormatItems = CustomFormatsFixture.GetDefaultFormatItems(),
FormatItems = CustomFormatsTestHelpers.GetDefaultFormatItems(),
Cutoff = Quality.Bluray1080p.Id,
Name = "TestProfile"
};

View File

@@ -170,9 +170,9 @@ namespace NzbDrone.Core.Test.Profiles
var customFormat1 = new CustomFormat("My Format 1", new LanguageSpecification { Value = (int)Language.English }) { Id = 1 };
var customFormat2 = new CustomFormat("My Format 2", new LanguageSpecification { Value = (int)Language.French }) { Id = 2 };
CustomFormatsFixture.GivenCustomFormats(customFormat1, customFormat2);
CustomFormatsTestHelpers.GivenCustomFormats(customFormat1, customFormat2);
profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(customFormat2.Name);
profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(customFormat2.Name);
Mocker.GetMock<IProfileRepository>()
.Setup(s => s.Get(It.IsAny<int>()))

View File

@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.GetLatestUpdate("develop", new Version(10, 0)).Should().BeNull();
}
[Ignore("Pending linux-x86 release")]
[Test]
public void finds_update_when_version_lower()
{
@@ -40,7 +39,6 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.GetLatestUpdate("invalid_branch", new Version(0, 2)).Should().NotBeNull();
}
[Ignore("Pending linux-x86 release")]
[Test]
public void should_get_recent_updates()
{

View File

@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string Theme => GetValue("Theme", "light", persist: false);
public string Theme => GetValue("Theme", "auto", persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);

View File

@@ -14,28 +14,133 @@ namespace NzbDrone.Core.CustomFormats
{
public interface ICustomFormatCalculationService
{
List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo, Movie movie);
List<CustomFormat> ParseCustomFormat(RemoteMovie remoteMovie, long size);
List<CustomFormat> ParseCustomFormat(MovieFile movieFile, Movie movie);
List<CustomFormat> ParseCustomFormat(MovieFile movieFile);
List<CustomFormat> ParseCustomFormat(Blocklist blocklist);
List<CustomFormat> ParseCustomFormat(MovieHistory history);
List<CustomFormat> ParseCustomFormat(Blocklist blocklist, Movie movie);
List<CustomFormat> ParseCustomFormat(MovieHistory history, Movie movie);
List<CustomFormat> ParseCustomFormat(LocalMovie localMovie);
}
public class CustomFormatCalculationService : ICustomFormatCalculationService
{
private readonly ICustomFormatService _formatService;
private readonly IParsingService _parsingService;
private readonly IMovieService _movieService;
public CustomFormatCalculationService(ICustomFormatService formatService,
IParsingService parsingService,
IMovieService movieService)
public CustomFormatCalculationService(ICustomFormatService formatService)
{
_formatService = formatService;
_parsingService = parsingService;
_movieService = movieService;
}
public static List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo, List<CustomFormat> allCustomFormats)
public List<CustomFormat> ParseCustomFormat(RemoteMovie remoteMovie, long size)
{
var input = new CustomFormatInput
{
MovieInfo = remoteMovie.ParsedMovieInfo,
Movie = remoteMovie.Movie,
Size = size,
Languages = remoteMovie.Languages
};
return ParseCustomFormat(input);
}
public List<CustomFormat> ParseCustomFormat(MovieFile movieFile, Movie movie)
{
return ParseCustomFormat(movieFile, movie, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(MovieFile movieFile)
{
return ParseCustomFormat(movieFile, movieFile.Movie, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(Blocklist blocklist, Movie movie)
{
var parsed = Parser.Parser.ParseMovieTitle(blocklist.SourceTitle);
var movieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string>() { movie.Title },
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? blocklist.SourceTitle.SimplifyReleaseTitle(),
ReleaseTitle = parsed?.ReleaseTitle ?? blocklist.SourceTitle,
Edition = parsed?.Edition,
Quality = blocklist.Quality,
Languages = blocklist.Languages,
ReleaseGroup = parsed?.ReleaseGroup
};
var input = new CustomFormatInput
{
MovieInfo = movieInfo,
Movie = movie,
Size = blocklist.Size ?? 0,
IndexerFlags = blocklist.IndexerFlags,
Languages = blocklist.Languages
};
return ParseCustomFormat(input);
}
public List<CustomFormat> ParseCustomFormat(MovieHistory history, Movie movie)
{
var parsed = Parser.Parser.ParseMovieTitle(history.SourceTitle);
long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags);
var movieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string>() { movie.Title },
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? history.SourceTitle.SimplifyReleaseTitle(),
ReleaseTitle = parsed?.ReleaseTitle ?? history.SourceTitle,
Edition = parsed?.Edition,
Quality = history.Quality,
Languages = history.Languages,
ReleaseGroup = parsed?.ReleaseGroup,
};
var input = new CustomFormatInput
{
MovieInfo = movieInfo,
Movie = movie,
Size = size,
IndexerFlags = flags,
Languages = history.Languages
};
return ParseCustomFormat(input);
}
public List<CustomFormat> ParseCustomFormat(LocalMovie localMovie)
{
var episodeInfo = new ParsedMovieInfo
{
MovieTitles = new List<string>() { localMovie.Movie.Title },
SimpleReleaseTitle = localMovie.SceneName?.SimplifyReleaseTitle(),
ReleaseTitle = localMovie.SceneName,
Quality = localMovie.Quality,
Edition = localMovie.Edition,
Languages = localMovie.Languages,
ReleaseGroup = localMovie.ReleaseGroup
};
var input = new CustomFormatInput
{
MovieInfo = episodeInfo,
Movie = localMovie.Movie,
Size = localMovie.Size,
Languages = localMovie.Languages
};
return ParseCustomFormat(input);
}
private List<CustomFormat> ParseCustomFormat(CustomFormatInput input)
{
return ParseCustomFormat(input, _formatService.All());
}
private static List<CustomFormat> ParseCustomFormat(CustomFormatInput input, List<CustomFormat> allCustomFormats)
{
var matches = new List<CustomFormat>();
@@ -45,7 +150,7 @@ namespace NzbDrone.Core.CustomFormats
.GroupBy(t => t.GetType())
.Select(g => new SpecificationMatchesGroup
{
Matches = g.ToDictionary(t => t, t => t.IsSatisfiedBy(movieInfo))
Matches = g.ToDictionary(t => t, t => t.IsSatisfiedBy(input))
})
.ToList();
@@ -58,7 +163,7 @@ namespace NzbDrone.Core.CustomFormats
return matches;
}
public static List<CustomFormat> ParseCustomFormat(MovieFile movieFile, List<CustomFormat> allCustomFormats)
private static List<CustomFormat> ParseCustomFormat(MovieFile movieFile, Movie movie, List<CustomFormat> allCustomFormats)
{
var sceneName = string.Empty;
if (movieFile.SceneName.IsNotNullOrWhiteSpace())
@@ -74,90 +179,29 @@ namespace NzbDrone.Core.CustomFormats
sceneName = Path.GetFileName(movieFile.RelativePath);
}
var info = new ParsedMovieInfo
var movieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string>() { movieFile.Movie.MovieMetadata.Value.Title },
MovieTitles = new List<string>() { movie.Title },
SimpleReleaseTitle = sceneName.SimplifyReleaseTitle(),
Quality = movieFile.Quality,
Languages = movieFile.Languages,
ReleaseGroup = movieFile.ReleaseGroup,
Edition = movieFile.Edition,
Year = movieFile.Movie.MovieMetadata.Value.Year,
ImdbId = movieFile.Movie.MovieMetadata.Value.ImdbId,
ExtraInfo = new Dictionary<string, object>
{
{ "IndexerFlags", movieFile.IndexerFlags },
{ "Size", movieFile.Size },
{ "Filename", Path.GetFileName(movieFile.RelativePath) },
{ "OriginalLanguage", movieFile.Movie.MovieMetadata.Value.OriginalLanguage }
}
ImdbId = movieFile.Movie.MovieMetadata.Value.ImdbId
};
return ParseCustomFormat(info, allCustomFormats);
}
public List<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo, Movie movie)
{
movieInfo = _parsingService.EnhanceMovieInfo(movieInfo, new List<object> { movie }) ?? movieInfo;
return ParseCustomFormat(movieInfo, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(MovieFile movieFile)
{
return ParseCustomFormat(movieFile, _formatService.All());
}
public List<CustomFormat> ParseCustomFormat(Blocklist blocklist)
{
var movie = _movieService.GetMovie(blocklist.MovieId);
var parsed = _parsingService.ParseMovieInfo(blocklist.SourceTitle, null);
var info = new ParsedMovieInfo
var input = new CustomFormatInput
{
MovieTitles = new List<string>() { movie.MovieMetadata.Value.Title },
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? blocklist.SourceTitle.SimplifyReleaseTitle(),
Quality = blocklist.Quality,
Languages = blocklist.Languages,
ReleaseGroup = parsed?.ReleaseGroup,
Edition = parsed?.Edition,
Year = movie.MovieMetadata.Value.Year,
ImdbId = movie.MovieMetadata.Value.ImdbId,
ExtraInfo = new Dictionary<string, object>
{
{ "IndexerFlags", blocklist.IndexerFlags },
{ "Size", blocklist.Size }
}
MovieInfo = movieInfo,
Movie = movie,
Size = movieFile.Size,
IndexerFlags = movieFile.IndexerFlags,
Languages = movieFile.Languages,
Filename = Path.GetFileName(movieFile.RelativePath)
};
return ParseCustomFormat(info, movie);
}
public List<CustomFormat> ParseCustomFormat(MovieHistory history)
{
var movie = _movieService.GetMovie(history.MovieId);
var parsed = _parsingService.ParseMovieInfo(history.SourceTitle, null);
Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags);
long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
var info = new ParsedMovieInfo
{
MovieTitles = new List<string>() { movie.MovieMetadata.Value.Title },
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? history.SourceTitle.SimplifyReleaseTitle(),
Quality = history.Quality,
Languages = history.Languages,
ReleaseGroup = parsed?.ReleaseGroup,
Edition = parsed?.Edition,
Year = movie.MovieMetadata.Value.Year,
ImdbId = movie.MovieMetadata.Value.ImdbId,
ExtraInfo = new Dictionary<string, object>
{
{ "IndexerFlags", flags },
{ "Size", size }
}
};
return ParseCustomFormat(info, movie);
return ParseCustomFormat(input, allCustomFormats);
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.CustomFormats
{
public class CustomFormatInput
{
public ParsedMovieInfo MovieInfo { get; set; }
public Movie Movie { get; set; }
public long Size { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public List<Language> Languages { get; set; }
public string Filename { get; set; }
public CustomFormatInput()
{
Languages = new List<Language>();
}
}
}

View File

@@ -21,9 +21,9 @@ namespace NzbDrone.Core.CustomFormats
public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo)
public bool IsSatisfiedBy(CustomFormatInput input)
{
var match = IsSatisfiedByWithoutNegate(movieInfo);
var match = IsSatisfiedByWithoutNegate(input);
if (Negate)
{
match = !match;
@@ -32,6 +32,6 @@ namespace NzbDrone.Core.CustomFormats
return match;
}
protected abstract bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo);
protected abstract bool IsSatisfiedByWithoutNegate(CustomFormatInput input);
}
}

View File

@@ -1,5 +1,3 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.CustomFormats
{
public class EditionSpecification : RegexSpecificationBase
@@ -8,9 +6,9 @@ namespace NzbDrone.Core.CustomFormats
public override string ImplementationName => "Edition";
public override string InfoLink => "https://wiki.servarr.com/radarr/settings#custom-formats-2";
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
return MatchString(movieInfo.Edition);
return MatchString(input.MovieInfo.Edition);
}
}
}

View File

@@ -15,6 +15,6 @@ namespace NzbDrone.Core.CustomFormats
NzbDroneValidationResult Validate();
ICustomFormatSpecification Clone();
bool IsSatisfiedBy(ParsedMovieInfo movieInfo);
bool IsSatisfiedBy(CustomFormatInput input);
}
}

View File

@@ -32,10 +32,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Flag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?;
return flags?.HasFlag((IndexerFlags)Value) == true;
return input.IndexerFlags.HasFlag((IndexerFlags)Value) == true;
}
public override NzbDroneValidationResult Validate()

View File

@@ -32,12 +32,12 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var comparedLanguage = movieInfo != null && Value == Language.Original.Id && movieInfo.ExtraInfo.ContainsKey("OriginalLanguage")
? (Language)movieInfo.ExtraInfo["OriginalLanguage"]
var comparedLanguage = input.MovieInfo != null && Value == Language.Original.Id && input.Movie.MovieMetadata.Value.OriginalLanguage != Language.Unknown
? input.Movie.MovieMetadata.Value.OriginalLanguage
: (Language)Value;
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false;
return input?.Languages?.Contains(comparedLanguage) ?? false;
}
public override NzbDroneValidationResult Validate()

View File

@@ -32,9 +32,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Quality Modifier", Type = FieldType.Select, SelectOptions = typeof(Modifier))]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
return (input.MovieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
}
public override NzbDroneValidationResult Validate()

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