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

Compare commits

..

100 Commits

Author SHA1 Message Date
Matthew Barrington 9ea0957351 New: Add Ireland as a Certification Country (#8085)
Co-authored-by: Matthew Barrington <git@barrington.it>
2023-02-11 12:36:04 -06:00
Weblate 8befa436cc Translated using Weblate (French) [skip ci]
Currently translated at 99.6% (1153 of 1157 strings)

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

Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: KevoM <lilmarsu@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2023-02-11 12:35:19 -06:00
Qstick 53fdb6f07f Delete azuresync.yml 2023-02-11 10:36:40 -06:00
Servarr 0697dbff96 Automated API Docs update 2023-02-07 20:26:31 -06:00
Qstick 34924859aa Fixed: Settings fail to save for some auth setups
(cherry picked from commit a379d0c403449b2623f84aa6851c850971528ff8)
2023-02-07 20:24:48 -06: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 5713 additions and 3544 deletions
-45
View File
@@ -1,45 +0,0 @@
name: Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
concurrency: azuresync-${{ github.event.issue.number }}
permissions: {}
jobs:
alert:
permissions:
issues: write # to update issue body
runs-on: ubuntu-latest
steps:
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Radarr"
ado_wit: "Bug"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Radarr"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
+2 -21
View File
@@ -166,27 +166,8 @@ packages.config.md5sum
# Common IntelliJ Platform excludes # Common IntelliJ Platform excludes
# User specific # Ignore Rider projects completely for now
**/.idea/**/workspace.xml .idea/
**/.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 node_modules symlink # ignore node_modules symlink
node_modules node_modules
+9
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/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/) * [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
## 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 ### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) * [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
+1 -1
View File
@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.3.1' majorVersion: '4.4.2'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
+4 -2
View File
@@ -56,6 +56,7 @@ class HistoryRow extends Component {
movie, movie,
quality, quality,
customFormats, customFormats,
customFormatScore,
languages, languages,
qualityCutoffNotMet, qualityCutoffNotMet,
eventType, eventType,
@@ -175,7 +176,7 @@ class HistoryRow extends Component {
key={name} key={name}
className={styles.customFormatScore} className={styles.customFormatScore}
> >
{formatCustomFormatScore(data.customFormatScore)} {formatCustomFormatScore(customFormatScore)}
</TableRowCell> </TableRowCell>
); );
} }
@@ -241,8 +242,9 @@ HistoryRow.propTypes = {
movie: PropTypes.object.isRequired, movie: PropTypes.object.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
eventType: PropTypes.string.isRequired, eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
+23 -9
View File
@@ -75,13 +75,23 @@ class Queue extends Component {
return; return;
} }
const nextState = {};
if (prevProps.items !== items) {
nextState.items = items;
}
const selectedIds = this.getSelectedIds(); const selectedIds = this.getSelectedIds();
const isPendingSelected = _.some(this.props.items, (item) => { const isPendingSelected = _.some(this.props.items, (item) => {
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay'; return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
}); });
if (isPendingSelected !== this.state.isPendingSelected) { 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> <PageContentBody>
{ {
isRefreshing && !isAllPopulated && isRefreshing && !isAllPopulated ?
<LoadingIndicator /> <LoadingIndicator /> :
null
} }
{ {
!isRefreshing && hasError && !isRefreshing && hasError ?
<div> <div>
{translate('FailedToLoadQueue')} {translate('FailedToLoadQueue')}
</div> </div> :
null
} }
{ {
isAllPopulated && !hasError && !items.length && isAllPopulated && !hasError && !items.length ?
<div> <div>
{translate('QueueIsEmpty')} {translate('QueueIsEmpty')}
</div> </div> :
null
} }
{ {
isAllPopulated && !hasError && !!items.length && isAllPopulated && !hasError && !!items.length ?
<div> <div>
<Table <Table
columns={columns} columns={columns}
@@ -268,7 +281,8 @@ class Queue extends Component {
isFetching={isRefreshing} isFetching={isRefreshing}
{...otherProps} {...otherProps}
/> />
</div> </div> :
null
} }
</PageContentBody> </PageContentBody>
+12
View File
@@ -128,6 +128,7 @@ class QueueRow extends Component {
{ {
columns.map((column) => { columns.map((column) => {
const { const {
name, name,
isVisible isVisible
@@ -234,6 +235,16 @@ class QueueRow extends Component {
); );
} }
if (name === 'year') {
return (
<TableRowCell key={name}>
{
movie ? movie.year : ''
}
</TableRowCell>
);
}
if (name === 'title') { if (name === 'title') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
@@ -362,6 +373,7 @@ QueueRow.propTypes = {
estimatedCompletionTime: PropTypes.string, estimatedCompletionTime: PropTypes.string,
timeleft: PropTypes.string, timeleft: PropTypes.string,
size: PropTypes.number, size: PropTypes.number,
year: PropTypes.number,
sizeleft: PropTypes.number, sizeleft: PropTypes.number,
showRelativeDates: PropTypes.bool.isRequired, showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
@@ -225,13 +225,19 @@ class ImportMovieFooter extends Component {
body={ body={
<ul> <ul>
{ {
importError.responseJSON.map((error, index) => { Array.isArray(importError.responseJSON) ?
return ( importError.responseJSON.map((error, index) => {
<li key={index}> return (
{error.errorMessage} <li key={index}>
</li> {error.errorMessage}
); </li>
}) );
}) :
<li>
{
JSON.stringify(importError.responseJSON)
}
</li>
} }
</ul> </ul>
} }
@@ -152,13 +152,19 @@ class ImportMovieSelectFolder extends Component {
<ul> <ul>
{ {
saveError.responseJSON.map((e, index) => { Array.isArray(saveError.responseJSON) ?
return ( saveError.responseJSON.map((e, index) => {
<li key={index}> return (
{e.errorMessage} <li key={index}>
</li> {e.errorMessage}
); </li>
}) );
}) :
<li>
{
JSON.stringify(saveError.responseJSON)
}
</li>
} }
</ul> </ul>
</Alert> : </Alert> :
@@ -113,10 +113,12 @@ class EnhancedSelectInput extends Component {
this._scheduleUpdate(); this._scheduleUpdate();
} }
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) { if (!Array.isArray(this.props.value)) {
this.setState({ if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
selectedIndex: getSelectedIndex(this.props) this.setState({
}); selectedIndex: getSelectedIndex(this.props)
});
}
} }
} }
@@ -332,6 +334,11 @@ class EnhancedSelectInput extends Component {
const isMultiSelect = Array.isArray(value); const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values); const selectedOption = getSelectedOption(selectedIndex, values);
let selectedValue = value;
if (!values.length) {
selectedValue = isMultiSelect ? [] : '';
}
return ( return (
<div> <div>
@@ -372,15 +379,17 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress} onPress={this.onPress}
> >
{ {
isFetching && isFetching ?
<LoadingIndicator <LoadingIndicator
className={styles.loading} className={styles.loading}
size={20} size={20}
/> /> :
null
} }
{ {
!isFetching && isFetching ?
null :
<Icon <Icon
name={icons.CARET_DOWN} name={icons.CARET_DOWN}
/> />
@@ -400,7 +409,7 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress} onPress={this.onPress}
> >
<SelectedValueComponent <SelectedValueComponent
value={value} value={selectedValue}
values={values} values={values}
{...selectedValueOptions} {...selectedValueOptions}
{...selectedOption} {...selectedOption}
@@ -418,15 +427,17 @@ class EnhancedSelectInput extends Component {
> >
{ {
isFetching && isFetching ?
<LoadingIndicator <LoadingIndicator
className={styles.loading} className={styles.loading}
size={20} size={20}
/> /> :
null
} }
{ {
!isFetching && isFetching ?
null :
<Icon <Icon
name={icons.CARET_DOWN} name={icons.CARET_DOWN}
/> />
@@ -506,7 +517,7 @@ class EnhancedSelectInput extends Component {
</Manager> </Manager>
{ {
isMobile && isMobile ?
<Modal <Modal
className={styles.optionsModal} className={styles.optionsModal}
size={sizes.EXTRA_SMALL} size={sizes.EXTRA_SMALL}
@@ -557,7 +568,8 @@ class EnhancedSelectInput extends Component {
} }
</Scroller> </Scroller>
</ModalBody> </ModalBody>
</Modal> </Modal> :
null
} }
</div> </div>
); );
@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
> >
<div className={styles.valueText}> <div className={styles.valueText}>
{ {
isMultiSelect && isMultiSelect ?
value.map((key, index) => { value.map((key, index) => {
const v = valuesMap[key]; const v = valuesMap[key];
return ( return (
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key} {v ? v.value : key}
</Label> </Label>
); );
}) }) :
null
} }
{ {
!isMultiSelect && value isMultiSelect ? null : value
} }
</div> </div>
{ {
hint != null && includeHint && hint != null && includeHint ?
<div className={styles.hintText}> <div className={styles.hintText}>
{hint} {hint}
</div> </div> :
null
} }
</EnhancedSelectInputSelectedValue> </EnhancedSelectInputSelectedValue>
); );
} }
HintedSelectInputSelectedValue.propTypes = { 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, values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string, hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired, isMultiSelect: PropTypes.bool.isRequired,
@@ -68,7 +68,7 @@ RootFolderSelectInputOption.propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
freeSpace: PropTypes.number, freeSpace: PropTypes.number,
movieFolder: PropTypes.string, movieFolder: PropTypes.string,
isMissing: PropTypes.boolean, isMissing: PropTypes.bool,
isMobile: PropTypes.bool.isRequired, isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool isWindows: PropTypes.bool
}; };
+1 -1
View File
@@ -60,7 +60,7 @@ class FilterMenu extends Component {
iconName={icons.FILTER} iconName={icons.FILTER}
text={translate('Filter')} text={translate('Filter')}
isDisabled={isDisabled} isDisabled={isDisabled}
indicator={selectedFilterKey !== 'all'} showIndicator={selectedFilterKey !== 'all'}
/> />
<FilterMenuContent <FilterMenuContent
@@ -1,11 +1,19 @@
.menuButton { .menuButton {
composes: menuButton from '~./MenuButton.css'; composes: menuButton from '~./MenuButton.css';
position: relative;
&:hover { &:hover {
color: #666; color: #666;
} }
} }
.indicatorContainer {
position: absolute;
top: 10px;
left: 10px;
}
.label { .label {
margin-left: 5px; margin-left: 5px;
} }
+21 -3
View File
@@ -1,13 +1,15 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import MenuButton from 'Components/Menu/MenuButton'; import MenuButton from 'Components/Menu/MenuButton';
import { icons } from 'Helpers/Props';
import styles from './PageMenuButton.css'; import styles from './PageMenuButton.css';
function PageMenuButton(props) { function PageMenuButton(props) {
const { const {
iconName, iconName,
indicator, showIndicator,
text, text,
...otherProps ...otherProps
} = props; } = props;
@@ -22,6 +24,22 @@ function PageMenuButton(props) {
size={18} size={18}
/> />
{
showIndicator ?
<span
className={classNames(
styles.indicatorContainer,
'fa-layers fa-fw'
)}
>
<Icon
name={icons.CIRCLE}
size={9}
/>
</span> :
null
}
<div className={styles.label}> <div className={styles.label}>
{text} {text}
</div> </div>
@@ -32,11 +50,11 @@ function PageMenuButton(props) {
PageMenuButton.propTypes = { PageMenuButton.propTypes = {
iconName: PropTypes.object.isRequired, iconName: PropTypes.object.isRequired,
text: PropTypes.string, text: PropTypes.string,
indicator: PropTypes.bool.isRequired showIndicator: PropTypes.bool.isRequired
}; };
PageMenuButton.defaultProps = { PageMenuButton.defaultProps = {
indicator: false showIndicator: false
}; };
export default PageMenuButton; export default PageMenuButton;
@@ -9,7 +9,7 @@ import styles from './ToolbarMenuButton.css';
function ToolbarMenuButton(props) { function ToolbarMenuButton(props) {
const { const {
iconName, iconName,
indicator, showIndicator,
text, text,
...otherProps ...otherProps
} = props; } = props;
@@ -26,7 +26,7 @@ function ToolbarMenuButton(props) {
/> />
{ {
indicator && showIndicator &&
<span <span
className={classNames( className={classNames(
styles.indicatorContainer, styles.indicatorContainer,
@@ -53,11 +53,11 @@ function ToolbarMenuButton(props) {
ToolbarMenuButton.propTypes = { ToolbarMenuButton.propTypes = {
iconName: PropTypes.object.isRequired, iconName: PropTypes.object.isRequired,
text: PropTypes.string, text: PropTypes.string,
indicator: PropTypes.bool.isRequired showIndicator: PropTypes.bool.isRequired
}; };
ToolbarMenuButton.defaultProps = { ToolbarMenuButton.defaultProps = {
indicator: false showIndicator: false
}; };
export default ToolbarMenuButton; export default ToolbarMenuButton;
@@ -19,7 +19,7 @@
} }
} }
@media only screen and (max-width: $breakpointLarge) { @media only screen and (max-width: $breakpointExtraLarge) {
.contentFooter { .contentFooter {
flex-wrap: wrap; flex-wrap: wrap;
} }
+5 -1
View File
@@ -20,7 +20,11 @@
.frontTextContainer { .frontTextContainer {
z-index: 1; z-index: 1;
color: var(--white); color: var(--progressBarFrontTextColor);
}
.backTextContainer {
color: var(--progressBarBackTextColor);
} }
.backTextContainer, .backTextContainer,
@@ -64,6 +64,15 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'customFormats',
label: React.createElement(Icon, {
name: icons.INTERACTIVE,
title: translate('CustomFormat')
}),
isSortable: true,
isVisible: true
},
{ {
name: 'rejections', name: 'rejections',
label: React.createElement(Icon, { label: React.createElement(Icon, {
@@ -5,8 +5,10 @@
} }
.quality, .quality,
.language { .languages {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
text-align: center;
} }
.label { .label {
@@ -21,3 +23,7 @@
margin-top: 0; margin-top: 0;
text-align: start; text-align: start;
} }
.customFormatTooltip {
max-width: 250px;
}
@@ -12,6 +12,7 @@ import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal'; import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal'; import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
@@ -150,6 +151,7 @@ class InteractiveImportRow extends Component {
languages, languages,
releaseGroup, releaseGroup,
size, size,
customFormats,
rejections, rejections,
isReprocessing, isReprocessing,
isSelected, isSelected,
@@ -226,7 +228,7 @@ class InteractiveImportRow extends Component {
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton <TableRowCellButton
className={styles.language} className={styles.languages}
title={translate('ClickToChangeLanguage')} title={translate('ClickToChangeLanguage')}
onPress={this.onSelectLanguagePress} onPress={this.onSelectLanguagePress}
> >
@@ -259,7 +261,26 @@ class InteractiveImportRow extends Component {
<TableRowCell> <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 <Popover
anchor={ anchor={
<Icon <Icon
@@ -282,7 +303,9 @@ class InteractiveImportRow extends Component {
</ul> </ul>
} }
position={tooltipPositions.LEFT} position={tooltipPositions.LEFT}
/> canFlip={false}
/> :
null
} }
</TableRowCell> </TableRowCell>
@@ -330,6 +353,7 @@ InteractiveImportRow.propTypes = {
languages: PropTypes.arrayOf(PropTypes.object), languages: PropTypes.arrayOf(PropTypes.object),
releaseGroup: PropTypes.string, releaseGroup: PropTypes.string,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
rejections: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
isReprocessing: PropTypes.bool, isReprocessing: PropTypes.bool,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
@@ -62,6 +62,7 @@ class MovieHistoryRow extends Component {
sourceTitle, sourceTitle,
quality, quality,
customFormats, customFormats,
customFormatScore,
languages, languages,
qualityCutoffNotMet, qualityCutoffNotMet,
date, date,
@@ -106,7 +107,7 @@ class MovieHistoryRow extends Component {
</TableRowCell> </TableRowCell>
<TableRowCell key={name}> <TableRowCell key={name}>
{formatCustomFormatScore(data.customFormatScore)} {formatCustomFormatScore(customFormatScore)}
</TableRowCell> </TableRowCell>
<RelativeDateCellConnector <RelativeDateCellConnector
@@ -161,7 +162,8 @@ MovieHistoryRow.propTypes = {
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: 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, qualityCutoffNotMet: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
@@ -17,6 +17,10 @@
font-size: 24px; font-size: 24px;
} }
.buttons {
flex: 0 0 auto;
}
.cloneButton { .cloneButton {
composes: button from '~Components/Link/IconButton.css'; composes: button from '~Components/Link/IconButton.css';
@@ -36,3 +40,10 @@
margin: 0; margin: 0;
border: none; border: none;
} }
.label {
@add-mixin truncate;
composes: label from '~Components/Label.css';
max-width: 100%;
}
@@ -90,7 +90,7 @@ class CustomFormat extends Component {
{name} {name}
</div> </div>
<div> <div className={styles.buttons}>
<IconButton <IconButton
className={styles.cloneButton} className={styles.cloneButton}
title={translate('CloneCustomFormat')} title={translate('CloneCustomFormat')}
@@ -124,6 +124,7 @@ class CustomFormat extends Component {
return ( return (
<Label <Label
className={styles.label}
key={index} key={index}
kind={kind} kind={kind}
> >
@@ -89,6 +89,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="enableRss" name="enableRss"
helpText={supportsRss.value ? translate('RSSHelpText') : undefined}
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')} helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
isDisabled={!supportsRss.value} isDisabled={!supportsRss.value}
{...enableRss} {...enableRss}
@@ -30,6 +30,7 @@
code { code {
padding: 0 1px; padding: 0 1px;
border: 1px solid var(--borderColor); border: 1px solid var(--borderColor);
background-color: #f7f7f7; background-color: var(--modalCloseButtonHoverColor);
color: var(--movieBackgroundColor);
} }
} }
@@ -7,11 +7,11 @@
&:hover { &:hover {
.token { .token {
background-color: #ddd; background-color: var(--popoverTitleBackgroundInverseColor);
} }
.example { .example {
background-color: #ccc; background-color: var(--popoverTitleBorderInverseColor);
} }
} }
} }
@@ -27,7 +27,7 @@
.token { .token {
flex: 0 0 50%; flex: 0 0 50%;
padding: 6px 16px; padding: 6px 16px;
background-color: #eee; background-color: var(--popoverTitleBorderColor);
font-family: $monoSpaceFontFamily; font-family: $monoSpaceFontFamily;
} }
@@ -38,7 +38,7 @@
justify-content: space-between; justify-content: space-between;
flex: 0 0 50%; flex: 0 0 50%;
padding: 6px 16px; padding: 6px 16px;
background-color: #ddd; background-color: var(--popoverTitleBackgroundColor);
.footNote { .footNote {
padding: 2px; padding: 2px;
@@ -18,6 +18,7 @@ export const certificationCountryOptions = [
{ key: 'fr', value: 'France' }, { key: 'fr', value: 'France' },
{ key: 'de', value: 'Germany' }, { key: 'de', value: 'Germany' },
{ key: 'gb', value: 'Great Britain' }, { key: 'gb', value: 'Great Britain' },
{ key: 'ie', value: 'Ireland' },
{ key: 'it', value: 'Italy' }, { key: 'it', value: 'Italy' },
{ key: 'es', value: 'Spain' }, { key: 'es', value: 'Spain' },
{ key: 'us', value: 'United States' }, { key: 'us', value: 'United States' },
@@ -118,6 +118,12 @@ export const defaultState = {
isSortable: true, isSortable: true,
isVisible: false isVisible: false
}, },
{
name: 'year',
label: translate('Year'),
isSortable: true,
isVisible: true
},
{ {
name: 'outputPath', name: 'outputPath',
label: translate('OutputPath'), label: translate('OutputPath'),
@@ -201,6 +201,11 @@ export const defaultState = {
return genreList.sort(sortByName); return genreList.sort(sortByName);
} }
}, },
{
name: 'customFormatScore',
label: translate('CustomFormatScore'),
type: filterBuilderTypes.NUMBER
},
{ {
name: 'rejectionCount', name: 'rejectionCount',
label: translate('RejectionCount'), label: translate('RejectionCount'),
+2
View File
@@ -226,6 +226,8 @@ module.exports = {
// //
// Misc // Misc
progressBarFrontTextColor: white,
progressBarBackTextColor: white,
progressBarBackgroundColor: '#727070', progressBarBackgroundColor: '#727070',
logEventsBackgroundColor: '#2a2a2a' logEventsBackgroundColor: '#2a2a2a'
}; };
+4
View File
@@ -1,7 +1,11 @@
import * as dark from './dark'; import * as dark from './dark';
import * as light from './light'; import * as light from './light';
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const auto = defaultDark ? { ...dark } : { ...light };
export default { export default {
auto,
light, light,
dark dark
}; };
+6 -4
View File
@@ -198,8 +198,8 @@ module.exports = {
popoverShadowColor: 'rgba(0, 0, 0, 0.2)', popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
popoverArrowBorderColor: '#fff', popoverArrowBorderColor: '#fff',
popoverTitleBackgroundInverseColor: '#595959', popoverTitleBackgroundInverseColor: '#9b9b9b',
popoverTitleBorderInverseColor: '#707070', popoverTitleBorderInverseColor: '#bfbfbf',
popoverShadowInverseColor: 'rgba(0, 0, 0, 0.2)', popoverShadowInverseColor: 'rgba(0, 0, 0, 0.2)',
popoverArrowBorderInverseColor: 'rgba(58, 63, 81, 0.75)', popoverArrowBorderInverseColor: 'rgba(58, 63, 81, 0.75)',
@@ -227,6 +227,8 @@ module.exports = {
// //
// Misc // Misc
progressBarBackgroundColor: '#fff', progressBarFrontTextColor: white,
logEventsBackgroundColor: '#fff' progressBarBackTextColor: darkGray,
progressBarBackgroundColor: white,
logEventsBackgroundColor: white
}; };
@@ -1,5 +1,4 @@
function formatCustomFormatScore(input, customFormatsLength = 0) {
function formatCustomFormatScore(input) {
const score = Number(input); const score = Number(input);
if (score > 0) { if (score > 0) {
@@ -10,7 +9,7 @@ function formatCustomFormatScore(input) {
return score; return score;
} }
return ''; return customFormatsLength > 0 ? '+0' : '';
} }
export default formatCustomFormatScore; export default formatCustomFormatScore;
+5 -1
View File
@@ -113,7 +113,7 @@
"file-loader": "6.2.0", "file-loader": "6.2.0",
"filemanager-webpack-plugin": "5.0.0", "filemanager-webpack-plugin": "5.0.0",
"html-webpack-plugin": "5.3.1", "html-webpack-plugin": "5.3.1",
"loader-utils": "^2.0.0", "loader-utils": "^3.2.1",
"mini-css-extract-plugin": "1.5.0", "mini-css-extract-plugin": "1.5.0",
"postcss": "8.2.12", "postcss": "8.2.12",
"postcss-color-function": "4.1.0", "postcss-color-function": "4.1.0",
@@ -134,5 +134,9 @@
"webpack-cli": "4.9.1", "webpack-cli": "4.9.1",
"webpack-livereload-plugin": "3.0.2", "webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8" "worker-loader": "3.0.8"
},
"volta": {
"node": "16.17.0",
"yarn": "1.22.19"
} }
} }
@@ -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();
}
}
}
@@ -1,4 +1,4 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Test.Common; 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://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) public void should_parse(string uri)
{ {
var newUri = new HttpUri(uri); var newUri = new HttpUri(uri);
@@ -7,34 +7,50 @@ namespace NzbDrone.Common.Extensions
{ {
public static bool IsLocalAddress(this IPAddress ipAddress) 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)) if (IPAddress.IsLoopback(ipAddress))
{ {
return true; return true;
} }
// IPv4
if (ipAddress.AddressFamily == AddressFamily.InterNetwork) if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{ {
byte[] bytes = ipAddress.GetAddressBytes(); return IsLocalIPv4(ipAddress.GetAddressBytes());
switch (bytes[0]) }
{
case 10: // IPv6
case 127: if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
return true; {
case 172: return ipAddress.IsIPv6LinkLocal ||
return bytes[1] < 32 && bytes[1] >= 16; ipAddress.IsIPv6UniqueLocal ||
case 192: ipAddress.IsIPv6SiteLocal;
return bytes[1] == 168;
default:
return false;
}
} }
return false; 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();
}
} }
} }
@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -192,5 +194,30 @@ namespace NzbDrone.Common.Extensions
.Replace("'", "%27") .Replace("'", "%27")
.Replace("%7E", "~"); .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;
}
} }
} }
+4 -2
View File
@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
{ {
public class HttpUri : IEquatable<HttpUri> 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; private readonly string _uri;
public string FullUri => _uri; public string FullUri => _uri;
@@ -70,6 +70,8 @@ namespace NzbDrone.Common.Http
private void Parse() private void Parse()
{ {
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
var match = RegexUri.Match(_uri); var match = RegexUri.Match(_uri);
var scheme = match.Groups["scheme"]; var scheme = match.Groups["scheme"];
@@ -79,7 +81,7 @@ namespace NzbDrone.Common.Http
var query = match.Groups["query"]; var query = match.Groups["query"];
var fragment = match.Groups["fragment"]; 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); throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
} }
@@ -11,26 +11,41 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{ {
try 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(); var fingerprint = sentryEvent.Fingerprint.Select(x => CleanseLogMessage.Cleanse(x)).ToList();
sentryEvent.SetFingerprint(fingerprint); 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); sentryEvent.SetExtras(extras);
} }
foreach (var exception in sentryEvent.SentryExceptions) if (sentryEvent.SentryExceptions is not null)
{ {
exception.Value = CleanseLogMessage.Cleanse(exception.Value); foreach (var exception in sentryEvent.SentryExceptions)
foreach (var frame in exception.Stacktrace.Frames)
{ {
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);
}
}
} }
} }
} }
@@ -8,6 +8,7 @@ using System.Threading;
using NLog; using NLog;
using NLog.Common; using NLog.Common;
using NLog.Targets; using NLog.Targets;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using Sentry; using Sentry;
@@ -34,6 +35,14 @@ namespace NzbDrone.Common.Instrumentation.Sentry
SQLiteErrorCode.Auth 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 // use string and not Type so we don't need a reference to the project
// where these are defined // where these are defined
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string> private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
@@ -99,9 +108,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Dsn = dsn; o.Dsn = dsn;
o.AttachStacktrace = true; o.AttachStacktrace = true;
o.MaxBreadcrumbs = 200; o.MaxBreadcrumbs = 200;
o.SendDefaultPii = false;
o.Debug = false;
o.DiagnosticLevel = SentryLevel.Debug;
o.Release = BuildInfo.Release; o.Release = BuildInfo.Release;
o.BeforeSend = x => SentryCleanser.CleanseEvent(x); o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x); o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
@@ -253,6 +259,19 @@ namespace NzbDrone.Common.Instrumentation.Sentry
isSentry = false; 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)) if (FilteredExceptionTypeNames.Contains(ex.GetType().Name))
{ {
isSentry = false; isSentry = false;
+3 -2
View File
@@ -7,10 +7,11 @@
<PackageReference Include="DryIoc.dll" Version="5.3.0" /> <PackageReference Include="DryIoc.dll" Version="5.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" 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" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" /> <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="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.5" /> <PackageReference Include="System.Text.Json" Version="6.0.5" />
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@@ -9,7 +9,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats namespace NzbDrone.Core.Test.CustomFormats
{ {
[TestFixture] [TestFixture]
public class CustomFormatsFixture : CoreTest public class CustomFormatsTestHelpers : CoreTest
{ {
private static List<CustomFormat> _customFormats { get; set; } private static List<CustomFormat> _customFormats { get; set; }
@@ -46,14 +46,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) },
}; };
CustomFormatsFixture.GivenCustomFormats(_format1, _format2); CustomFormatsTestHelpers.GivenCustomFormats(_format1, _format2);
} }
[Test] [Test]
public void should_allow_if_format_score_greater_than_min() public void should_allow_if_format_score_greater_than_min()
{ {
_remoteMovie.CustomFormats = new List<CustomFormat> { _format1 }; _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); _remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); 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() public void should_deny_if_format_score_not_greater_than_min()
{ {
_remoteMovie.CustomFormats = new List<CustomFormat> { _format2 }; _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); _remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Console.WriteLine(_remoteMovie.CustomFormatScore); 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() public void should_deny_if_format_score_not_greater_than_min_2()
{ {
_remoteMovie.CustomFormats = new List<CustomFormat> { _format2, _format1 }; _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); _remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); 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() public void should_allow_if_all_format_is_defined_in_profile()
{ {
_remoteMovie.CustomFormats = new List<CustomFormat> { _format2, _format1 }; _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); _remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); 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() public void should_deny_if_no_format_was_parsed_and_min_score_positive()
{ {
_remoteMovie.CustomFormats = new List<CustomFormat> { }; _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); _remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); 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() public void should_allow_if_no_format_was_parsed_min_score_is_zero()
{ {
_remoteMovie.CustomFormats = new List<CustomFormat> { }; _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.Movie.Profile.MinFormatScore = 0;
_remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats); _remoteMovie.CustomFormatScore = _remoteMovie.Movie.Profile.CalculateCustomFormatScore(_remoteMovie.CustomFormats);
@@ -40,8 +40,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenProfile(Profile profile) private void GivenProfile(Profile profile)
{ {
CustomFormatsFixture.GivenCustomFormats(); CustomFormatsTestHelpers.GivenCustomFormats();
profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(); profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems();
profile.MinFormatScore = 0; profile.MinFormatScore = 0;
_remoteMovie.Movie.Profile = profile; _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 }; _customFormat = new CustomFormat("My Format", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 };
CustomFormatsFixture.GivenCustomFormats(_customFormat); CustomFormatsTestHelpers.GivenCustomFormats(_customFormat);
} }
[Test] [Test]
@@ -157,7 +157,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = Quality.HDTV720p.Id, Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
MinFormatScore = 0, MinFormatScore = 0,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("My Format"), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("My Format"),
UpgradeAllowed = true UpgradeAllowed = true
}); });
@@ -38,14 +38,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.Resolve<UpgradableSpecification>(); Mocker.Resolve<UpgradableSpecification>();
_upgradeHistory = Mocker.Resolve<HistorySpecification>(); _upgradeHistory = Mocker.Resolve<HistorySpecification>();
CustomFormatsFixture.GivenCustomFormats(); CustomFormatsTestHelpers.GivenCustomFormats();
_fakeMovie = Builder<Movie>.CreateNew() _fakeMovie = Builder<Movie>.CreateNew()
.With(c => c.Profile = new Profile .With(c => c.Profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.Bluray1080p.Id, Cutoff = Quality.Bluray1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("None"),
MinFormatScore = 0, MinFormatScore = 0,
UpgradeAllowed = true UpgradeAllowed = true
}) })
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Returns(true); .Returns(true);
Mocker.GetMock<ICustomFormatCalculationService>() Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>())) .Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>(), It.IsAny<Movie>()))
.Returns(new List<CustomFormat>()); .Returns(new List<CustomFormat>());
} }
@@ -163,7 +163,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.Bluray1080p.Id, Cutoff = Quality.Bluray1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems(), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0 MinFormatScore = 0
}; };
@@ -171,7 +171,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
Mocker.GetMock<ICustomFormatCalculationService>() Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>())) .Setup(x => x.ParseCustomFormat(It.IsAny<MovieHistory>(), It.IsAny<Movie>()))
.Returns(new List<CustomFormat>()); .Returns(new List<CustomFormat>());
GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, MovieHistoryEventType.Grabbed); GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, MovieHistoryEventType.Grabbed);
@@ -186,7 +186,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.WEBDL1080p.Id, Cutoff = Quality.WEBDL1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems(), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0 MinFormatScore = 0
}; };
@@ -221,7 +221,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.WEBDL1080p.Id, Cutoff = Quality.WEBDL1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems(), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0 MinFormatScore = 0
}; };
@@ -41,17 +41,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void WithEnglishRelease() private void WithEnglishRelease()
{ {
_remoteMovie.ParsedMovieInfo.Languages = new List<Language> { Language.English }; _remoteMovie.Languages = new List<Language> { Language.English };
} }
private void WithGermanRelease() private void WithGermanRelease()
{ {
_remoteMovie.ParsedMovieInfo.Languages = new List<Language> { Language.German }; _remoteMovie.Languages = new List<Language> { Language.German };
} }
private void WithFrenchRelease() private void WithFrenchRelease()
{ {
_remoteMovie.ParsedMovieInfo.Languages = new List<Language> { Language.French }; _remoteMovie.Languages = new List<Language> { Language.French };
} }
[Test] [Test]
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_customFormat1 = new CustomFormat("My Format 1", new LanguageSpecification { Value = (int)Language.English }) { Id = 1 }; _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 }; _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>() Mocker.GetMock<IQualityDefinitionService>()
.Setup(s => s.Get(It.IsAny<Quality>())) .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 remoteMovie.Movie = Builder<Movie>.CreateNew().With(m => m.Profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
MinFormatScore = 0 MinFormatScore = 0
}) })
.With(m => m.Title = "A Movie") .With(m => m.Title = "A Movie")
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
CustomFormatsFixture.GivenCustomFormats(_customFormat1, _customFormat2); CustomFormatsTestHelpers.GivenCustomFormats(_customFormat1, _customFormat2);
} }
private void GivenAutoDownloadPropers(ProperDownloadTypes type) private void GivenAutoDownloadPropers(ProperDownloadTypes type)
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var profile = new Profile var profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
MinFormatScore = 0 MinFormatScore = 0
}; };
@@ -32,13 +32,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Mocker.Resolve<UpgradableSpecification>(); Mocker.Resolve<UpgradableSpecification>();
CustomFormatsFixture.GivenCustomFormats(); CustomFormatsTestHelpers.GivenCustomFormats();
_movie = Builder<Movie>.CreateNew() _movie = Builder<Movie>.CreateNew()
.With(e => e.Profile = new Profile .With(e => e.Profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0, MinFormatScore = 0,
UpgradeAllowed = true UpgradeAllowed = true
}) })
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Build(); .Build();
Mocker.GetMock<ICustomFormatCalculationService>() 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>()); .Returns(new List<CustomFormat>());
} }
@@ -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();
}
}
}
@@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.Resolve<UpgradableSpecification>(); Mocker.Resolve<UpgradableSpecification>();
_upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>(); _upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>();
CustomFormatsFixture.GivenCustomFormats(); CustomFormatsTestHelpers.GivenCustomFormats();
_firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now }; _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 .With(c => c.Profile = new Profile
{ {
Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(), Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems(), FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(),
MinFormatScore = 0 MinFormatScore = 0
}) })
.With(e => e.MovieFile = _firstFile) .With(e => e.MovieFile = _firstFile)
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download
_downloadClients = new List<IDownloadClient>(); _downloadClients = new List<IDownloadClient>();
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClients()) .Setup(v => v.GetDownloadClients(It.IsAny<bool>()))
.Returns(_downloadClients); .Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.With(h => h.Title = title) .With(h => h.Title = title)
.With(h => h.Release = release) .With(h => h.Release = release)
.With(h => h.Reason = reason) .With(h => h.Reason = reason)
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
.Build(); .Build();
_heldReleases.AddRange(heldReleases); _heldReleases.AddRange(heldReleases);
@@ -52,7 +52,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
_pending.Add(new PendingRelease _pending.Add(new PendingRelease
{ {
Id = id, Id = id,
Title = "Movie.Title.2020.720p-Radarr",
ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year }, ParsedMovieInfo = new ParsedMovieInfo { MovieTitles = new List<string> { title }, Year = year },
Release = Builder<ReleaseInfo>.CreateNew().Build(),
MovieId = _movie.Id MovieId = _movie.Id
}); });
} }
@@ -93,6 +93,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
.With(h => h.MovieId = _movie.Id) .With(h => h.MovieId = _movie.Id)
.With(h => h.Title = title) .With(h => h.Title = title)
.With(h => h.Release = release) .With(h => h.Release = release)
.With(h => h.ParsedMovieInfo = _parsedMovieInfo)
.Build(); .Build();
Mocker.GetMock<IPendingReleaseRepository>() Mocker.GetMock<IPendingReleaseRepository>()
File diff suppressed because it is too large Load Diff
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_warning_when_download_client_has_not_been_configured() public void should_return_warning_when_download_client_has_not_been_configured()
{ {
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients()) .Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(Array.Empty<IDownloadClient>()); .Returns(Array.Empty<IDownloadClient>());
Subject.Check().ShouldBeWarning(); Subject.Check().ShouldBeWarning();
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Throws<Exception>(); .Throws<Exception>();
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients()) .Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object }); .Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeError(); Subject.Check().ShouldBeError();
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(new List<DownloadClientItem>()); .Returns(new List<DownloadClientItem>());
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients()) .Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { downloadClient.Object }); .Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeOk(); Subject.Check().ShouldBeOk();
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus); .Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients()) .Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object }); .Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
@@ -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);
}
}
}
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(_clientStatus); .Returns(_clientStatus);
Mocker.GetMock<IProvideDownloadClient>() Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClients()) .Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
.Returns(new IDownloadClient[] { _downloadClient.Object }); .Returns(new IDownloadClient[] { _downloadClient.Object });
Mocker.GetMock<IConfigService>() Mocker.GetMock<IConfigService>()
@@ -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);
}
}
}
@@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.FileList;
using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Indexers.Omgwtfnzbs;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests
_indexers = new List<IIndexer>(); _indexers = new List<IIndexer>();
_indexers.Add(Mocker.Resolve<Newznab>()); _indexers.Add(Mocker.Resolve<Newznab>());
_indexers.Add(Mocker.Resolve<Omgwtfnzbs>()); _indexers.Add(Mocker.Resolve<FileList>());
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers); Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
} }
@@ -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);
}
}
}
@@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using FluentAssertions; 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.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.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.Size.Should().Be(2466170624L);
first.InfoHash.Should().BeNullOrEmpty(); first.InfoHash.Should().BeNullOrEmpty();
first.MagnetUrl.Should().BeNullOrEmpty(); first.MagnetUrl.Should().BeNullOrEmpty();
@@ -49,6 +49,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 34, Language.Bengali }, new object[] { 34, Language.Bengali },
new object[] { 35, Language.Slovak }, new object[] { 35, Language.Slovak },
new object[] { 36, Language.Latvian }, new object[] { 36, Language.Latvian },
new object[] { 37, Language.SpanishLatino }
}; };
public static object[] ToIntCases = public static object[] ToIntCases =
@@ -92,6 +93,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Bengali, 34 }, new object[] { Language.Bengali, 34 },
new object[] { Language.Slovak, 35 }, new object[] { Language.Slovak, 35 },
new object[] { Language.Latvian, 36 }, new object[] { Language.Latvian, 36 },
new object[] { Language.SpanishLatino, 37 }
}; };
[Test] [Test]
@@ -445,6 +445,58 @@ namespace NzbDrone.Core.Test.MediaFiles
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Never()); .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() private void VerifyNoImport()
{ {
Mocker.GetMock<IImportApprovedMovie>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto), Mocker.GetMock<IImportApprovedMovie>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null, ImportMode.Auto),
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.MovieTests.MovieRepositoryTests
var profile = new Profile var profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
FormatItems = CustomFormatsFixture.GetDefaultFormatItems(), FormatItems = CustomFormatsTestHelpers.GetDefaultFormatItems(),
MinFormatScore = 0, MinFormatScore = 0,
Cutoff = Quality.Bluray1080p.Id, Cutoff = Quality.Bluray1080p.Id,
Name = "TestProfile" Name = "TestProfile"
@@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@@ -13,10 +14,10 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.NotificationTests namespace NzbDrone.Core.Test.NotificationTests
{ {
[TestFixture] [TestFixture]
public class TraktServiceFixture : CoreTest<TraktService> public class TraktServiceFixture : CoreTest<Trakt>
{ {
private DownloadMessage _downloadMessage; private DownloadMessage _downloadMessage;
private TraktSettings _traktSettings; private NotificationDefinition _traktDefinition;
[SetUp] [SetUp]
public void Setup() public void Setup()
@@ -34,11 +35,17 @@ namespace NzbDrone.Core.Test.NotificationTests
} }
}; };
_traktSettings = new TraktSettings _traktDefinition = new NotificationDefinition
{ {
AccessToken = "", Settings = new TraktSettings
RefreshToken = "" {
AccessToken = "",
RefreshToken = "",
Expires = DateTime.Now.AddDays(1)
}
}; };
Subject.Definition = _traktDefinition;
} }
private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType) private void GiventValidMediaInfo(Quality quality, string audioChannels, string audioFormat, string scanType)
@@ -56,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test] [Test]
public void should_add_collection_movie_if_null_mediainfo() public void should_add_collection_movie_if_null_mediainfo()
{ {
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile); Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>() Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.IsAny<TraktCollectMoviesResource>(), It.IsAny<string>()), Times.Once()); .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"); GiventValidMediaInfo(Quality.Bluray1080p, "5.1", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile); Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>() Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t => .Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
@@ -83,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{ {
GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive"); GiventValidMediaInfo(Quality.Bluray1080p, "2.0", "DTS", "Progressive");
Subject.AddMovieToCollection(_traktSettings, _downloadMessage.Movie, _downloadMessage.MovieFile); Subject.OnDownload(_downloadMessage);
Mocker.GetMock<ITraktProxy>() Mocker.GetMock<ITraktProxy>()
.Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t => .Verify(v => v.AddToCollection(It.Is<TraktCollectMoviesResource>(t =>
@@ -10,6 +10,8 @@ namespace NzbDrone.Core.Test.OrganizerTests
{ {
[TestCase("Mission: Impossible - no [HDTV-720p]", [TestCase("Mission: Impossible - no [HDTV-720p]",
"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) public void CleanFileName(string name, string expectedName)
{ {
FileNameBuilder.CleanFileName(name).Should().Be(expectedName); FileNameBuilder.CleanFileName(name).Should().Be(expectedName);
@@ -562,19 +562,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(string.Format("HDTV-720p{0}South{0}Park", separator)); .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("en-US")]
[TestCase("fr-FR")] [TestCase("fr-FR")]
[TestCase("az")] [TestCase("az")]
@@ -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");
}
}
}
@@ -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.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.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.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) public void should_parse_edition(string postTitle, string edition)
{ {
var parsed = Parser.Parser.ParseMovieTitle(postTitle); var parsed = Parser.Parser.ParseMovieTitle(postTitle);
@@ -1,3 +1,4 @@
using System.Linq;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
@@ -380,6 +381,18 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Latvian); 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.en.sub")]
[TestCase("Movie Title.eng.sub")] [TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")] [TestCase("Movie.Title.eng.forced.sub")]
@@ -215,6 +215,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Name FRENCH BluRay 720p 2016 kjhlj", 2016)] [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("Der.Movie.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", 1998)]
[TestCase("Movie Name (1897) [DVD].mp4", 1897)] [TestCase("Movie Name (1897) [DVD].mp4", 1897)]
[TestCase("World Movie Z Movie [2023]", 2023)]
public void should_parse_movie_year(string postTitle, int year) public void should_parse_movie_year(string postTitle, int year)
{ {
Parser.Parser.ParseMovieTitle(postTitle).Year.Should().Be(year); Parser.Parser.ParseMovieTitle(postTitle).Year.Should().Be(year);
@@ -254,6 +255,18 @@ namespace NzbDrone.Core.Test.ParserTests
parsed.Languages.Should().Contain(Language.German); 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 [tt1234567] 720p BluRay X264", "tt1234567")]
[TestCase("That Italian Movie 2008 [tt12345678] 720p BluRay X264", "tt12345678")] [TestCase("That Italian Movie 2008 [tt12345678] 720p BluRay X264", "tt12345678")]
public void should_parse_imdb_in_title(string postTitle, string imdb) public void should_parse_imdb_in_title(string postTitle, string imdb)
@@ -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)
};
}
}
}
@@ -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);
}
}
}
@@ -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();
}
}
}
@@ -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);
// }
// }
// }
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -194,20 +194,5 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie); Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
Subject.Map(_umlautAltInfo, "", _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);
}
} }
} }
@@ -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 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)] [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 (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) public void should_parse_cam(string title, bool proper)
{ {
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown); ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
@@ -451,17 +452,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name); 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.REPACK.720p.x264-aAF", true)] [TestCase("Movie.Title.2018.REPACK.720p.x264-aAF", true)]
[TestCase("Movie.Title.2018.PROPER.720p.x264-aAF", false)] [TestCase("Movie.Title.2018.PROPER.720p.x264-aAF", false)]
@@ -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("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("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-[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)] [TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", null)]
public void should_parse_expected_release_group(string title, string expected) 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("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.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.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) public void should_parse_exception_release_group(string title, string expected)
{ {
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.Profiles
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
MinFormatScore = 0, MinFormatScore = 0,
FormatItems = CustomFormatsFixture.GetDefaultFormatItems(), FormatItems = CustomFormatsTestHelpers.GetDefaultFormatItems(),
Cutoff = Quality.Bluray1080p.Id, Cutoff = Quality.Bluray1080p.Id,
Name = "TestProfile" Name = "TestProfile"
}; };
@@ -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 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 }; 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>() Mocker.GetMock<IProfileRepository>()
.Setup(s => s.Get(It.IsAny<int>())) .Setup(s => s.Get(It.IsAny<int>()))
@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.GetLatestUpdate("develop", new Version(10, 0)).Should().BeNull(); Subject.GetLatestUpdate("develop", new Version(10, 0)).Should().BeNull();
} }
[Ignore("Pending linux-x86 release")]
[Test] [Test]
public void finds_update_when_version_lower() 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(); Subject.GetLatestUpdate("invalid_branch", new Version(0, 2)).Should().NotBeNull();
} }
[Ignore("Pending linux-x86 release")]
[Test] [Test]
public void should_get_recent_updates() public void should_get_recent_updates()
{ {
@@ -198,7 +198,7 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant(); public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); 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 PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", 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); public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
@@ -14,28 +14,133 @@ namespace NzbDrone.Core.CustomFormats
{ {
public interface ICustomFormatCalculationService 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(MovieFile movieFile);
List<CustomFormat> ParseCustomFormat(Blocklist blocklist); List<CustomFormat> ParseCustomFormat(Blocklist blocklist, Movie movie);
List<CustomFormat> ParseCustomFormat(MovieHistory history); List<CustomFormat> ParseCustomFormat(MovieHistory history, Movie movie);
List<CustomFormat> ParseCustomFormat(LocalMovie localMovie);
} }
public class CustomFormatCalculationService : ICustomFormatCalculationService public class CustomFormatCalculationService : ICustomFormatCalculationService
{ {
private readonly ICustomFormatService _formatService; private readonly ICustomFormatService _formatService;
private readonly IParsingService _parsingService;
private readonly IMovieService _movieService;
public CustomFormatCalculationService(ICustomFormatService formatService, public CustomFormatCalculationService(ICustomFormatService formatService)
IParsingService parsingService,
IMovieService movieService)
{ {
_formatService = 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>(); var matches = new List<CustomFormat>();
@@ -45,7 +150,7 @@ namespace NzbDrone.Core.CustomFormats
.GroupBy(t => t.GetType()) .GroupBy(t => t.GetType())
.Select(g => new SpecificationMatchesGroup .Select(g => new SpecificationMatchesGroup
{ {
Matches = g.ToDictionary(t => t, t => t.IsSatisfiedBy(movieInfo)) Matches = g.ToDictionary(t => t, t => t.IsSatisfiedBy(input))
}) })
.ToList(); .ToList();
@@ -58,7 +163,7 @@ namespace NzbDrone.Core.CustomFormats
return matches; 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; var sceneName = string.Empty;
if (movieFile.SceneName.IsNotNullOrWhiteSpace()) if (movieFile.SceneName.IsNotNullOrWhiteSpace())
@@ -74,90 +179,29 @@ namespace NzbDrone.Core.CustomFormats
sceneName = Path.GetFileName(movieFile.RelativePath); 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(), SimpleReleaseTitle = sceneName.SimplifyReleaseTitle(),
Quality = movieFile.Quality, Quality = movieFile.Quality,
Languages = movieFile.Languages, Languages = movieFile.Languages,
ReleaseGroup = movieFile.ReleaseGroup, ReleaseGroup = movieFile.ReleaseGroup,
Edition = movieFile.Edition, Edition = movieFile.Edition,
Year = movieFile.Movie.MovieMetadata.Value.Year, Year = movieFile.Movie.MovieMetadata.Value.Year,
ImdbId = movieFile.Movie.MovieMetadata.Value.ImdbId, 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 }
}
}; };
return ParseCustomFormat(info, allCustomFormats); var input = new CustomFormatInput
}
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
{ {
MovieTitles = new List<string>() { movie.MovieMetadata.Value.Title }, MovieInfo = movieInfo,
SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? blocklist.SourceTitle.SimplifyReleaseTitle(), Movie = movie,
Quality = blocklist.Quality, Size = movieFile.Size,
Languages = blocklist.Languages, IndexerFlags = movieFile.IndexerFlags,
ReleaseGroup = parsed?.ReleaseGroup, Languages = movieFile.Languages,
Edition = parsed?.Edition, Filename = Path.GetFileName(movieFile.RelativePath)
Year = movie.MovieMetadata.Value.Year,
ImdbId = movie.MovieMetadata.Value.ImdbId,
ExtraInfo = new Dictionary<string, object>
{
{ "IndexerFlags", blocklist.IndexerFlags },
{ "Size", blocklist.Size }
}
}; };
return ParseCustomFormat(info, movie); return ParseCustomFormat(input, allCustomFormats);
}
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);
} }
} }
} }
@@ -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>();
}
}
}
@@ -21,9 +21,9 @@ namespace NzbDrone.Core.CustomFormats
public abstract NzbDroneValidationResult Validate(); public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo) public bool IsSatisfiedBy(CustomFormatInput input)
{ {
var match = IsSatisfiedByWithoutNegate(movieInfo); var match = IsSatisfiedByWithoutNegate(input);
if (Negate) if (Negate)
{ {
match = !match; match = !match;
@@ -32,6 +32,6 @@ namespace NzbDrone.Core.CustomFormats
return match; return match;
} }
protected abstract bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo); protected abstract bool IsSatisfiedByWithoutNegate(CustomFormatInput input);
} }
} }
@@ -1,5 +1,3 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
public class EditionSpecification : RegexSpecificationBase public class EditionSpecification : RegexSpecificationBase
@@ -8,9 +6,9 @@ namespace NzbDrone.Core.CustomFormats
public override string ImplementationName => "Edition"; public override string ImplementationName => "Edition";
public override string InfoLink => "https://wiki.servarr.com/radarr/settings#custom-formats-2"; 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);
} }
} }
} }
@@ -15,6 +15,6 @@ namespace NzbDrone.Core.CustomFormats
NzbDroneValidationResult Validate(); NzbDroneValidationResult Validate();
ICustomFormatSpecification Clone(); ICustomFormatSpecification Clone();
bool IsSatisfiedBy(ParsedMovieInfo movieInfo); bool IsSatisfiedBy(CustomFormatInput input);
} }
} }
@@ -32,10 +32,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Flag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))] [FieldDefinition(1, Label = "Flag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))]
public int Value { get; set; } 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 input.IndexerFlags.HasFlag((IndexerFlags)Value) == true;
return flags?.HasFlag((IndexerFlags)Value) == true;
} }
public override NzbDroneValidationResult Validate() public override NzbDroneValidationResult Validate()
@@ -32,12 +32,12 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))] [FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))]
public int Value { get; set; } 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") var comparedLanguage = input.MovieInfo != null && Value == Language.Original.Id && input.Movie.MovieMetadata.Value.OriginalLanguage != Language.Unknown
? (Language)movieInfo.ExtraInfo["OriginalLanguage"] ? input.Movie.MovieMetadata.Value.OriginalLanguage
: (Language)Value; : (Language)Value;
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false; return input?.Languages?.Contains(comparedLanguage) ?? false;
} }
public override NzbDroneValidationResult Validate() public override NzbDroneValidationResult Validate()
@@ -32,9 +32,9 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Quality Modifier", Type = FieldType.Select, SelectOptions = typeof(Modifier))] [FieldDefinition(1, Label = "Quality Modifier", Type = FieldType.Select, SelectOptions = typeof(Modifier))]
public int Value { get; set; } 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() public override NzbDroneValidationResult Validate()

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