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

Compare commits

...

190 Commits

Author SHA1 Message Date
Qstick
676dcbae0e Fixed: Truncate custom format card tags
(cherry picked from commit fe476a319e1768a929e326e87a54b10bc5e4f402)
2022-11-04 16:33:58 +00:00
ta264
5a7a9db7ed Bump FFprobe 2022-11-04 10:33:45 -05:00
Qstick
182cda47b0 Fixed: Detect 3D in some video files 2022-11-03 23:41:35 -05:00
Qstick
294d95fae4 Create CODE_OF_CONDUCT.md 2022-11-03 15:56:41 -05:00
Davo1624
0e3f871e0e Clarify quality profile wording (#7714)
[common]
2022-11-03 13:23:35 -05:00
ta264
b0f5f02edc Use wildcard for FreeBSD itemPattern 2022-11-02 21:20:58 +00:00
Qstick
2afe6af5a6 Ignore brotli test on osx
[common]
2022-10-30 19:02:27 -05:00
Weblate
e2eaf91aa7 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 100.0% (1148 of 1148 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Update translation files  [skip ci]

Updated by "Remove blank strings" hook in Weblate.

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

Currently translated at 99.9% (1146 of 1147 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Sincejunly <qq943384135@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
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/zh_CN/
Translation: Servarr/Radarr
2022-10-23 10:17:56 -05:00
Qstick
0e1c2c3c50 Bump moment to 2.29.4 2022-10-19 21:54:56 -05:00
Qstick
69cf2e89a6 Fixed: WEB-Rip parsed as WebDL
Fixes #7424
Fixes #7463

Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-10-19 21:46:24 -05:00
Mark McDowall
9830230589 New: Auto focus input when editing release group during manual import
Closes #7075

(cherry picked from commit 715711e6d718a79744dd4ec2eb570f8d0732de3b)
2022-10-19 21:16:36 -05:00
Kevin Richter
b1f0b2c216 Fixed: Improve moving file to location where another one exists
Fixes #7460

(cherry picked from commit 8ab040f612ee04dac4813a08cdeaddd446a64dc9)
2022-10-19 21:12:33 -05:00
Mark McDowall
7c6858ecfb New: Rename Protocol to Preferred Protocol in Delay Profiles
Closes #7278

(cherry picked from commit 704cf7aebef60b5b5bdf1ea55d68d4a3394396e0)
2022-10-19 21:03:23 -05:00
Qstick
ee32d42c94 New: Parse Spanish language using Español
Fixes #7252
2022-10-19 20:58:12 -05:00
Qstick
3390df4085 New: Parse anime version with a space before 'v'
Closes #7633
2022-10-19 20:50:02 -05:00
Qstick
01bc5f6fc8 New: Include MediaInfo / CF for Webhooks
Fixes #7680

(cherry picked from commit 47116ea6637c4bcb3365f6882bfd02ea74bf687e)
2022-10-19 20:33:40 -05:00
Dtaggart
2d867a6cb6 New: Add indexer name to the download report log
Fixes #7686

(cherry picked from commit 00d467314b0e206742d4aff5aa20563e79b2ec55)
2022-10-19 20:33:40 -05:00
Qstick
411f8866ec Fixed: Handle rename when video has no audio tracks
Fixes #7648
2022-10-19 20:07:45 -05:00
Mark McDowall
5316382113 New: Natural Sorting Manual Import Relative Paths
(cherry picked from commit bdd5865876796bc203c8117418a5389afc8b5f11)
2022-10-18 21:31:07 -05:00
Mark McDowall
8fe81b428a Fixed: Ping plex.tv to keep auth token active
(cherry picked from commit 93bd8543b158c952b50e56d4339e4a3c699770b0)
2022-10-18 21:31:07 -05:00
Mark McDowall
43a2a2d335 Fixed: Plex Library Updates
(cherry picked from commit bd70fa54107c225ea08da53183e2be944e730475)
2022-10-18 21:31:07 -05:00
Qstick
5c8b58c30d New: Parse more BDRemux release names
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-10-18 20:13:29 -05:00
Qstick
131a223bb9 New: Parse more WEB release names
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-10-18 20:13:29 -05:00
Bakerboy448
dfaab639bf New: Include MediaInfo for CustomScripts OnDownload
(cherry picked from commit 77a7f3ef4f4762c45813fa94212ab9976e84f6f8)
2022-10-18 20:13:29 -05:00
Stevie Robinson
c7be63d48f Fixed: updated rTorrent download client note
(cherry picked from commit 743d28b93a55553ee25381570d0daa04ed2117af)
2022-10-18 20:13:29 -05:00
Qstick
2958faf4a8 Add import date to upgraded episodes in CustomScript and Webhook connections
Fixes #7547

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-10-18 20:13:29 -05:00
Qstick
4280df8b61 Fixed: Better error messaging if you try to import an invalid Custom Format
Co-Authored-By: Robert Dailey <1768054+rcdailey@users.noreply.github.com>
2022-10-18 20:13:29 -05:00
Qstick
1f91be6407 Fixed: Fall back to sorting by release title if series is not matched
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-10-18 20:13:29 -05:00
Chromo-residuum-opec
eb43a3c2d0 Update help text for rTorrent download client options
(cherry picked from commit d2a23f7bcdf71800f019644d7b6b5d712e311d7f)
2022-10-18 20:13:29 -05:00
Qstick
20c7e84676 New: Use filename without extension if SceneName is unavailable
Fixe #6639
2022-10-18 20:13:29 -05:00
Mark McDowall
691a8955fe Fixed: Parsing similar movie titles with common words at end 2022-10-18 20:13:29 -05:00
Qstick
53a9c849cb New: CustomFormat Score for history grabs
Fixes #5892
Fixes #5893
2022-10-18 20:13:29 -05:00
Qstick
856a55a9c9 New: Added advanced subtitle/audio language filter to {MediaInfo ..}
Fixes #4710

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
2022-10-18 20:13:29 -05:00
Qstick
43cd536746 Throw on search error
Fixes #4690
2022-10-18 20:13:29 -05:00
Qstick
4a205d8041 Add thread to discord notification [common] 2022-10-16 23:23:13 -05:00
Qstick
2525ac2d1a Bump version to 4.3.1 2022-10-16 21:14:07 -05:00
psylenced
e5ceb20a83 Fixed: Discord webhook logs now get cleansed to remove webhook id and token. 2022-10-16 09:55:22 -05:00
Qstick
0f6b11f55d Cleanup Dual Target and Mono References 2022-10-15 21:01:37 -05:00
Qstick
500bc3a571 New: Include CustomFormats for CustomScript on Grab
Fixes #7656

Co-Authored-By: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-10-15 14:34:10 -05:00
nuxen
e6567d0365 Fixed: Add HQMUX to the exception Release Groups
Fixed: Add HQMUX to the exception Release Groups
2022-10-15 12:09:18 -05:00
Qstick
dbca393772 New: Retry Postgres connection 3 times (with 5 second sleep) on Startup
(cherry picked from commit 3e700b63c26b247fcac83428ba79e53c88f797ff)

[common]
2022-10-15 12:08:47 -05:00
Qstick
9662495fa2 Fixed: Remove buggy CF import check 2022-10-15 12:01:09 -05:00
Qstick
76f0c54b3c Update FUNDING.yml 2022-10-14 22:12:40 -05:00
Weblate
d7ff92115c Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 99.9% (1146 of 1147 strings)

Co-authored-by: fyu0h <biiigback@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-10-10 22:31:42 -05:00
owine
54a49d6878 Add trailing spaces to issue template
Only OS had one
2022-10-10 21:13:43 -05:00
Qstick
a8362511f9 Fixed: Only replace the Title instance of a release
For movies such as X (2022) we don't want to replace every instance of `X` in the release string, but only the instance we identify as the title.
2022-10-09 00:46:38 -05:00
Alex
b9f2b3e06f build: harden support.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-10-09 00:04:52 -05:00
Alex
d995bc5a7e build: harden azuresync.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-10-09 00:04:52 -05:00
Alex
8886162bba build: harden lock.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-10-09 00:04:52 -05:00
Qstick
eb9eb4ec64 Fixed: Cleanup MovieMetadata that was removed from a collection 2022-10-09 00:02:34 -05:00
Servarr
f910a8fde7 Automated API Docs update 2022-10-07 21:34:01 -05:00
Qstick
f6904608a7 Remove unused calendar parameter 2022-10-07 21:26:25 -05:00
Weblate
0c79548fc4 Translated using Weblate (Dutch) [skip ci]
Currently translated at 94.8% (1088 of 1147 strings)

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

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: contramundi <robert.ars@outlook.com>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: libsu <libsu@qq.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-10-07 20:45:04 -05:00
bakerboy448
362e664ce6 update feature request template
[skip ci] [common]
2022-10-06 17:58:06 +01:00
Qstick
c2cbfb274a Bump version to 4.3.0 2022-09-25 11:55:51 -05:00
Qstick
9b3770a018 Ignore SQLiteException tests on Azure 2022-09-24 20:16:19 -05:00
Qstick
9db6289693 Correct SQLiteException Sentry filtering 2022-09-24 19:45:10 -05:00
Qstick
8a63f6ae37 Fix TagDetails sql for PG, add test 2022-09-24 19:01:47 -05:00
dglava
069b18e5e3 Fixed: Add YTS.AG to the exception Release Groups (#7627)
* Fixed: Add YTS.AG to the exception Release Groups

* Fix: Remove unneeded test case for YTS.AG

* Fix: Simplify ExceptionReleaseGroupRegex
2022-09-24 13:27:51 -05:00
Bakerboy448
f05333db51 Fixed: Improve RarBG Error Handling 2022-09-23 21:48:07 -05:00
Qstick
f50e8f631e fix typo in MovieRepository 2022-09-23 21:46:39 -05:00
bakerboy448
b9886cd11c Fixed: Repack Preference Ignored
(cherry picked from commit 04447d9d4db8cc3d54baf0a721f4430cf77908c4)

Fixes #6595
Closes #7621
2022-09-23 21:44:27 -05:00
Qstick
9f3eecb2a9 Fixed: Ignore Movies with null tags when pulling AllMovieTags 2022-09-23 21:34:10 -05:00
Stevie Robinson
52c24a4333 New: Torrent Seed Ratio no longer advance settings
(cherry picked from commit c98fac65ed8e669c5e97ed3255c424b61fe0e8b3)
2022-09-22 12:29:06 -05:00
Weblate
9bc31b46fa Translated using Weblate (Dutch) [skip ci]
Currently translated at 94.8% (1088 of 1147 strings)

Co-authored-by: Bob <bob@mijnthuisadres.nl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translation: Servarr/Radarr
2022-09-21 21:10:10 +01:00
Qstick
f4d8e113c1 Remove unused package 'react-slick' 2022-09-17 22:32:46 -05:00
Qstick
1e1a4240d1 Fixed: Collection Carousel Improvements 2022-09-17 22:17:41 -05:00
Weblate
8fb53df4af Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 14.6% (168 of 1147 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

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

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

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

Currently translated at 1.2% (14 of 1147 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

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

Currently translated at 1.0% (12 of 1147 strings)

Added translation using Weblate (Latvian) [skip ci]

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Dainel Amendoeira <daniel@amendoeira.eu>
Co-authored-by: Gylesie <github-anon.dasheens@aleeas.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: HiNesslio <chi.lio@shms-mail.ch>
Co-authored-by: Marcin <ml.cichy@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
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/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2022-09-17 22:00:56 -05:00
bakerboy448
f6dd600d2b Clarify Folder as Root Folder (#7598) 2022-09-17 21:59:29 -05:00
Mijail Todorovich
40a15d59e0 Fixed: Toolbar Button labels overlap
Fixes #7553
2022-09-17 21:57:50 -05:00
Mark McDowall
c7baa66de2 Fixed: Series list jump bar click issues
(cherry picked from commit 9c7378625112088d022239fdbdb90c0dc941d61d)
2022-09-17 21:53:44 -05:00
Qstick
2be70f5001 Fixed: Use translated title for sorttitle in Kodi nfo
Fixes #7482
2022-09-17 21:52:44 -05:00
Mark McDowall
da857701f6 Handle redirects for 308 redirects
(cherry picked from commit 6eed7c8fed096fa1762448bc57876440f542be98)
2022-09-17 21:07:17 -05:00
Qstick
828d7eb1f3 Fixed: Improve Radarr List help text 2022-09-12 20:18:18 -05:00
bakerboy448
b3a056edf9 Fixed: Improve Quality Profile in-use helptext 2022-09-12 20:16:27 -05:00
Qstick
98437c3cac Bump version to 4.2.4 2022-09-08 22:35:00 -05:00
Mark McDowall
c5616c5ba1 FileNameBuilderFixture tests should run on Windows
(cherry picked from commit 90d3dc2f984f0048b70b8796345993dfae1c66a2)
2022-09-08 20:43:31 -05:00
Krisjanis Lejejs
61979bff7a New: Add Latvian language 2022-09-08 20:35:51 -05:00
Qstick
90d0d8bec8 Fixed: Defaults for Trakt Popular List
Fixes #7576
2022-09-08 20:22:45 -05:00
Qstick
2d814ecd20 Fixed: Strip additional domains out of release prefix
Fixes #7589
2022-09-08 20:15:08 -05:00
Qstick
6542119402 Fixed: Collections not sorting properly on Index
Fixes #7577
2022-09-08 20:02:18 -05:00
Robin Dadswell
b9185574f3 Update Bug Report Template
[skip ci] [common]
2022-09-08 15:31:53 +01:00
bakerboy448
99e0d42b71 Update Bug Report Template [skip ci] [common] 2022-09-07 20:54:21 -05:00
Weblate
d01fa5e6a4 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1147 of 1147 strings)

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

Currently translated at 99.9% (1146 of 1147 strings)

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

Currently translated at 99.3% (1140 of 1147 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.5% (1130 of 1147 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 99.9% (1144 of 1145 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Gian Klug <gian.klug@ict-scouts.ch>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mijail Todorovich <mijailtodorovich+git@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Thomas Schwery <thomas@schwery.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: cikyw <cikyw@vomoto.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-09-07 20:54:04 -05:00
psylenced
2ce9d099e1 Fix: Trace logging postgres cleanse for large json files. 2022-09-06 22:33:59 -05:00
bakerboy448
12829580e5 Update src/NzbDrone.Core/CustomFormats/Specifications/RegexSpecificationBase.cs 2022-09-04 12:17:27 -05:00
bakerboy448
dadd796737 New: (UI) Indicate Custom Formats are Case Insensitive 2022-09-04 12:17:27 -05:00
Servarr
a3f508b8d4 Automated API Docs update 2022-08-27 22:33:39 +01:00
Devin Buhl
1ab3df03a3 New: Add application URL to host configuration settings
(cherry picked from commit 762042ba97c2ae689cee32d8e66a458f6d7a8adc)
2022-08-27 22:16:00 +01:00
Robin Dadswell
5558e10711 New: Setting to add Collection to NFO files 2022-08-27 07:26:05 +01:00
Qstick
573405bae7 Really fix UI Error on Collection Filter
#7563
2022-08-21 14:55:53 -05:00
Dominik Krivohlavek
43d77308f9 New: Preserve language tags when importing subtitle files 2022-08-20 18:17:44 -05:00
Mark McDowall
b3c3f7ddae Fixed: Skip extras in 'Extras' subfolder 2022-08-20 18:17:44 -05:00
Stéphane Dupont
dd5bc41eda New: Import subtitles from sub folders 2022-08-20 18:17:44 -05:00
Qstick
c8ab4f8c68 Bump version to 4.2.3 2022-08-20 18:13:19 -05:00
Weblate
a4ddae0ccc Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 88.7% (1016 of 1145 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 99.9% (1144 of 1145 strings)

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

Currently translated at 100.0% (1145 of 1145 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Jessie <1355239678@qq.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: emanuelsipos <emanuelsipos1@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-08-19 21:33:34 -05:00
Servarr
d730161800 Automated API Docs update 2022-08-18 22:45:47 -05:00
Qstick
66c1af0555 New: (API) Get Collection by TmdbId 2022-08-18 22:37:15 -05:00
Chris
dca00db317 Added: Ntfy provider for notifications. (#7455)
* Added: Ntfy provider for notifications.

* Changed: Clean up of validation and fields for ntfy

* Fixed: Unused title setting, and spelling issue.
2022-08-18 20:12:06 -05:00
Chris
812e5ac5a3 Fixed: Postgres secret regex now less greedy
[common]
2022-08-18 21:36:22 +01:00
Chris
d01bae92bf Fixed: Regex in log cleanser taking 10+ minutes on messages longer than 100k. (#7481) 2022-08-18 13:00:58 +01:00
Qstick
1a6bf51741 New: Add support for Plex Edition tags in naming 2022-08-16 23:04:46 -05:00
Qstick
f3e7843150 New: Make Plex imdb tags conditional 2022-08-16 23:04:46 -05:00
Qstick
886b9b1c05 Fixed: Correctly map movie by original title on import
Fixes #7348
2022-08-15 23:16:22 -05:00
Qstick
d8891ee4ea Fixed: UI Error on Collection Filter
Fixes #7474
2022-08-14 16:33:40 -05:00
Qstick
192dd9c137 Fixed: Allow 0 Min on Size CustomFormat Condition
Fixes #7476
2022-08-14 16:19:28 -05:00
Gylesie
b549fddf95 New: Add Slovak Language 2022-08-13 16:25:32 -05:00
Qstick
c1f538ed97 Bump version to 4.2.2 2022-08-13 15:48:22 -05:00
Weblate
e72f8097fb Translated using Weblate (Spanish) [skip ci]
Currently translated at 97.8% (1120 of 1145 strings)

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

Currently translated at 22.4% (257 of 1145 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

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

Currently translated at 0.8% (10 of 1145 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 0.1% (1 of 1145 strings)

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

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 99.9% (1144 of 1145 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

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

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.3% (1125 of 1144 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: M0C <pigers@gmail.com>
Co-authored-by: Michael Maldonado <michael@maldonado.tech>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Sytha <tharaud.sylvain@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: beefnoodle <acer.wang@protonmail.com>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
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
2022-08-13 13:49:13 -05:00
Qstick
3eec088306 Regenerate yarn.lock 2022-08-13 13:31:46 -05:00
Qstick
ad097dd1a2 Bump Sentry to 3.20.1 2022-08-13 12:55:49 -05:00
Qstick
b4b38a5318 Bump dotnet to 6.0.8 2022-08-13 12:40:52 -05:00
psylenced
b0717a0803 Changed: Removed Tigole from ExceptionRelease match as is checked in ExceptionReleaseExact. 2022-08-13 12:21:35 -05:00
psylenced
4d1d08d345 Fixed: Tigole release group not being parsed and matched correctly, requiring manual import. 2022-08-13 12:21:35 -05:00
psylenced
e689817508 Fixed: Configured recycle bin is excluded from import. 2022-08-13 12:03:03 -05:00
Qstick
3b191caf16 Really fix Original Language in Language CF Specification
Co-Authored-By: François-Xavier Payet <fx.payet@tfdn.org>
2022-08-13 12:02:10 -05:00
Qstick
cc6ca0b067 Better Sentry Filtering for AggregateException children 2022-08-12 23:33:27 -05:00
Qstick
57cb63fb18 Run Postgres tests on 20.04 2022-08-12 23:32:58 -05:00
Qstick
20f709d22a Fixed: Blank Collection on MovieDetails when no Collection for Movie 2022-08-12 22:55:42 -05:00
Qstick
5d8775ac96 Remove non-functional filters for Trakt Lists
Fixes #7464
2022-08-12 22:42:38 -05:00
Qstick
4890972e16 Fixed: Original CF shouldn't need to be named "Original"
Fixes #7473
2022-08-12 22:31:41 -05:00
Qstick
40dc808f61 Fixed NullRef in Skyhook Proxy during List Sync 2022-08-12 22:19:24 -05:00
bakerboy448
97077e09d2 Fixed: Remove Notifiarr Environment Option 2022-08-09 20:23:26 -05:00
Chris
9ba7027d00 Fixed: Trakt list request now uses correct rules for generating slug (#7449)
* Changed: Parser.ToUrlSlug now has optional parameters to define how it works in edge cases based on provider.

* Fixed: Trakt list request now uses correct rules for generating slug on site.

* Added: Unit tests for slug parser.

* Fixed: Null and blank parameters to ToSlugUrl parser. Added tests.
2022-08-07 22:18:06 -05:00
Qstick
9903e70925 Fixed: Allow blank ReleaseGroup and Edition from MovieFile edit
Fixes #7453
2022-08-07 22:05:27 -05:00
Qstick
3a6f3666f5 Fixed: Don't process files that don't have a supported media file extension
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-08-07 21:54:39 -05:00
Qstick
915c66be50 Fixed: Avoid failure if list contains same movie but without tmdbid
Fixes #7454
2022-08-07 21:47:46 -05:00
javaisbetterthanpython
70b22e483a Fixed: Log correct path when moving movies (#7439)
Fixes #7440
2022-07-25 18:17:52 -05:00
Deathspike
cad1191da5 Fixed: Watch state not preserved on metadata rewrite (#7436) 2022-07-23 12:17:26 -05:00
Qstick
43910af127 Fixed: NullRefException in TorrentRssParser
From 6c494e9a92
2022-07-21 20:11:45 -05:00
Qstick
f01c477b81 Bump Version to 4.2.1 2022-07-21 15:31:32 -04:00
bakerboy448
0054318658 Fixed: Parse Group ZØNEHD 2022-07-18 22:23:41 -05:00
bakerboy448
03a3f4522a New: Parse Group HONE 2022-07-18 22:23:41 -05:00
bakerboy448
3d3562dcda New: (Discord) Include Custom Formats & Score On Grab
Fixes #6733
2022-07-18 21:24:37 -05:00
Weblate
7a079c5e0c Translated using Weblate (Catalan) [skip ci]
Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 0.1% (1 of 1145 strings)

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

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 99.9% (1144 of 1145 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

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

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.3% (1125 of 1144 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: M0C <pigers@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Sytha <tharaud.sylvain@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
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/pl/
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-07-18 20:17:12 -05:00
Terebi42
4d70798f2f Fixed: User Triggered Auto Searches now ignores monitored status (#7422) 2022-07-18 18:25:46 -05:00
Robin Dadswell
d55864f869 Fixed: Postgres timezone issues (#7183)
[common]

Co-authored-by: ta264 <ta264@users.noreply.github.com>
2022-07-18 14:57:15 +01:00
Qstick
3c41c84fb0 Speed up and reduce meta calls for Imdb Lists when mapping 2022-07-17 12:57:00 -05:00
Qstick
eae9a6d6e0 Fixed: ImportListMovies not saved if from a list without TMDBIds 2022-07-17 12:55:13 -05:00
Mark Mckessock
867f8f5835 Match 'HQCAM' as CAM source (#7412)
* Add HQCAM source regex

* Add cam testcases
2022-07-15 23:09:12 -05:00
Qstick
0c81387cfb Fix RefreshMovieServiceFixture folder service mock 2022-07-15 22:36:35 -05:00
Qstick
c5fb5200de Fixed: Collections not deleted on Movie Delete 2022-07-15 22:08:25 -05:00
Qstick
cc306fcd36 Fixed: Bulk Collection RootFolder change failure 2022-07-15 21:57:32 -05:00
Qstick
2bb7984961 New: Collection Folder, Genre, QualityProfile Filters 2022-07-15 21:57:32 -05:00
Qstick
21e605452a Fixed: Trim RootFolderPath on Migration 2022-07-15 21:57:31 -05:00
Qstick
476f5b5bfd Avoid multiple metadata DB calls on list mapping 2022-07-15 21:57:31 -05:00
Qstick
b6920cfe82 Fixed: Prevent excluded movies from being added by collections 2022-07-15 21:57:31 -05:00
Qstick
e89b98d0f6 Fixed: Avoid NullRef in MapMovieToTmdbMovie 2022-07-14 22:08:11 -05:00
bakerboy448
1db690ad39 Fixed: Notifiarr - Better HTTP Error Handling
also quiet sentry
2022-07-14 19:08:16 -05:00
Qstick
d5c524719b Fix Nullref on Collection delete 2022-07-12 19:20:46 -05:00
bakerboy448
ced6586860 New: (Notifiarr) Custom Formats in OnGrab 2022-07-12 08:44:59 -05:00
Servarr
8b3019821a Automated API Docs update 2022-07-10 13:03:18 -05:00
Qstick
16ed68d5de New: Custom Format Spec Validation
Fixes #7405
2022-07-10 12:25:42 -05:00
Qstick
098a893083 Fixed: Don't fail on single failure for Discover bulk add
Fixes #7409
2022-07-09 19:11:16 -05:00
Qstick
548e3400b5 Remove general yarn restore key to avoid cross OS conflict 2022-07-09 18:59:15 -05:00
Weblate
5c31e3f1a2 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

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

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.6% (1117 of 1144 strings)

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

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Maxent <rouaultmaxent@gmail.com>
Co-authored-by: Moritz Ellerbrock <github@elmoritz.eu>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-07-09 00:16:35 -05:00
Qstick
7404793dcf Fixed: Don't call for server notifications on event driven check
[common]
2022-07-03 12:36:53 -05:00
Qstick
d8af17ce3d Rename MovieImportedEvent to MovieFileImportedEvent 2022-07-03 12:35:15 -05:00
bakerboy448
44c912f02d Fixed: Improved parsing WebDL Releases 2022-07-03 11:55:06 -05:00
Alien21
b104368e23 New: adding a link to tmdb in the import combobox movie search results (#7352)
Co-authored-by: Alien21 <alien21@alien21.com>
2022-07-02 18:32:55 -05:00
Qstick
aa0104b6bc Fixed: Housekeeper doesn't remove collections that have MovieMeta from lists 2022-07-02 16:29:12 -05:00
Qstick
69fcd8ec94 Fixed: Notify on Bulk Adds (Lists, Collections, Imports)
Closes #7351
2022-07-02 15:53:56 -05:00
Robin Dadswell
a59928c66a Updated NLog Version (#7365)
[common]
2022-07-02 16:26:59 +01:00
Weblate
1cb7ae11a2 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-07-02 09:33:13 -05:00
Qstick
ca519047dd Fixed: Migration 208 fails when collection doesn't have name 2022-07-01 19:45:05 -05:00
Qstick
f15a6abde0 Fixed: Don't call AddMovies if no movies to add from Collection 2022-06-26 20:25:50 -05:00
Qstick
2aacebc938 New: Default to IMDb Ratings in Kodi Metadata
Fixes #7071
2022-06-26 20:25:50 -05:00
Weblate
120e9b673e Translated using Weblate (Slovak) [skip ci]
Currently translated at 9.5% (109 of 1143 strings)

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

Currently translated at 22.3% (256 of 1143 strings)

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

Currently translated at 0.1% (2 of 1143 strings)

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

Currently translated at 100.0% (1143 of 1143 strings)

Added translation using Weblate (Lithuanian) [skip ci]

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: loksum213108 <lok3222003@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
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/zh_TW/
Translation: Servarr/Radarr
2022-06-26 18:43:59 -05:00
Qstick
0a77a13fa8 New: Separate Ratings Columns
Fixes #7281
2022-06-26 17:23:07 -05:00
Qstick
383f9647c3 Fixed: Add support for more Anime release formats
Ref #6954
2022-06-26 15:30:05 -05:00
Weblate
7f7c672b93 Translated using Weblate (Portuguese) [skip ci]
Currently translated at 98.2% (1123 of 1143 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1143 of 1143 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 97.9% (1119 of 1143 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: RckCell <pabloant86@gmail.com>
Co-authored-by: Vitor Brito <main@vitorbrito.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translation: Servarr/Radarr
2022-06-25 18:16:54 -05:00
Servarr
2690ad8fe1 Automated API Docs update 2022-06-25 18:16:32 -05:00
Qstick
801204b6de New: Bulk Edit Collections Profile, Root, Availability
Fixes #7350
2022-06-25 16:01:01 -05:00
Servarr
cb9514abaf Automated API Docs update 2022-06-25 15:30:31 -05:00
Qstick
fd22cb44f6 Fixed: Collections Improvements
Fixes #7383
2022-06-25 15:23:39 -05:00
Qstick
2d68716376 Add back Movie Credits and Alt Titles Indexes 2022-06-23 19:48:41 -05:00
Qstick
b97e76c8b8 Fixed: Validate if equals or child for startup folder
(cherry picked from commit 0991cfe27efd6ddb533227b25754661e18d7e9ad)
2022-06-23 00:34:03 -04:00
bakerboy448
bfad4a8cd1 New: Notifiarr include Media Info in Download Notifications 2022-06-22 18:12:33 -05:00
bakerboy448
61f05710f5 New: Notifiarr moved from webhook to API 2022-06-22 18:12:33 -05:00
Weblate
a8ecefd91f Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (1143 of 1143 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 97.8% (1118 of 1143 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 99.9% (1142 of 1143 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 99.9% (1142 of 1143 strings)

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

Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Marcin <ml.cichy@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-06-22 09:51:52 -05:00
ta264
e3468daba0 Use DryIoc for Automoqer, drop Unity dependency
[common]
2022-06-20 21:29:22 +01:00
Qstick
f2a7d0d520 Additional logging for partial Plex path scan 2022-06-18 16:33:49 -05:00
Weblate
43257f0726 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 99.3% (1136 of 1143 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: carreyli <laddie1987@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-06-11 09:19:20 -05:00
James Hughes
6c2bf860fe Fixed: Improved empty root folder failsafe logging (#7341)
Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-06-08 16:10:23 -05:00
Qstick
3a1d848e59 Fixed: Register PostgresOptions when running in utility mode 2022-06-08 06:04:19 -05:00
PearsonFlyer
f6590e71d2 Fixed: Clarified genre filtering helptext on Trakt lists 2022-06-08 05:55:44 -05:00
Qstick
586dd737fd Fixed: Lithuanian media info parsing
Fixes #7336
2022-06-05 14:38:55 -05:00
Weblate
fa84dda38c Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1143 of 1143 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 99.8% (1139 of 1141 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-06-04 13:43:25 -05:00
Servarr
4a233ce915 Automated API Docs update 2022-06-04 13:40:14 -05:00
Qstick
ffdd9a1708 Fixed: MovieAdded trigger not available in UI 2022-06-04 13:21:44 -05:00
272 changed files with 7210 additions and 2533 deletions

View File

@@ -1,7 +1,6 @@
{
"paths": [
"frontend/src/**/*.js",
"src/NzbDrone.Core/Localization/Core/*.json"
"frontend/src/**/*.js"
],
"ignored": [
"**/node_modules/**/*"

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: radarr
patreon: # Replace with a single Patreon username
open_collective: radarr
ko_fi: # Replace with a single Ko-fi username

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:
@@ -42,12 +42,14 @@ body:
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
- **Database**: Sqlite 3.36.0
value: |
- OS:
- Radarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
- Radarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
- Database:
render: markdown
validations:
required: true

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing issues
- label: I have searched the existing open and closed issues
required: true
- type: textarea
attributes:

View File

@@ -7,8 +7,12 @@ on:
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

View File

@@ -5,8 +5,13 @@ on:
schedule:
- cron: '0 0 * * *'
permissions: {}
jobs:
lock:
permissions:
issues: write # to lock issues (dessant/lock-threads)
pull-requests: write # to lock PRs (dessant/lock-threads)
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2

View File

@@ -4,8 +4,12 @@ on:
issues:
types: [labeled, unlabeled, reopened]
permissions: {}
jobs:
support:
permissions:
issues: write # to modify issues
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2

132
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<development@radarr.video>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.2.0'
majorVersion: '4.3.1'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.300'
dotnetVersion: '6.0.400'
nodeVersion: '16.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
@@ -173,7 +173,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --frontend
@@ -550,7 +549,7 @@ stages:
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: 'ubuntu-18.04'
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
@@ -577,6 +576,7 @@ stages:
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
@@ -687,7 +687,7 @@ stages:
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: 'ubuntu-18.04'
vmImage: ${{ variables.linuxImage }}
steps:
- task: UseDotNet@2
@@ -722,6 +722,7 @@ stages:
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
@@ -760,7 +761,7 @@ stages:
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '/$(pattern)'
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- bash: |
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
@@ -976,7 +977,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --lint
@@ -1143,4 +1143,5 @@ stages:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId)

View File

@@ -223,7 +223,6 @@ module.exports = (env) => {
{
loader: 'url-loader',
options: {
limit: 24096,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
@@ -233,12 +232,11 @@ module.exports = (env) => {
},
{
test: /\.(ttf|eot|eot?#iefix|gif|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
limit: 24096,
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}

View File

@@ -10,6 +10,12 @@
width: 80px;
}
.customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}
.releaseGroup {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@@ -9,6 +9,7 @@ import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
import HistoryEventTypeCell from './HistoryEventTypeCell';
import styles from './HistoryRow.css';
@@ -168,6 +169,17 @@ class HistoryRow extends Component {
);
}
if (name === 'customFormatScore') {
return (
<TableRowCell
key={name}
className={styles.customFormatScore}
>
{formatCustomFormatScore(data.customFormatScore)}
</TableRowCell>
);
}
if (name === 'releaseGroup') {
return (
<TableRowCell

View File

@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
@@ -190,7 +190,7 @@ class AddNewMovieSearchResult extends Component {
<div>
<Label size={sizes.LARGE}>
<HeartRating
<TmdbRating
ratings={ratings}
iconSize={13}
/>

View File

@@ -1,4 +1,5 @@
.movie {
.container {
display: flex;
padding: 10px 20px;
width: 100%;
@@ -6,3 +7,19 @@
background-color: $menuItemHoverBackgroundColor;
}
}
.movie {
flex: 1 0 0;
overflow: hidden;
}
.tmdbLink {
composes: link from '~Components/Link/Link.css';
margin-left: auto;
color: $textColor;
}
.tmdbLinkIcon {
margin-left: 10px;
}

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import ImportMovieTitle from './ImportMovieTitle';
import styles from './ImportMovieSearchResult.css';
@@ -18,6 +20,7 @@ class ImportMovieSearchResult extends Component {
render() {
const {
tmdbId,
title,
year,
studio,
@@ -25,17 +28,30 @@ class ImportMovieSearchResult extends Component {
} = this.props;
return (
<Link
className={styles.movie}
onPress={this.onPress}
>
<ImportMovieTitle
title={title}
year={year}
network={studio}
isExistingMovie={isExistingMovie}
/>
</Link>
<div className={styles.container}>
<Link
className={styles.movie}
onPress={this.onPress}
>
<ImportMovieTitle
title={title}
year={year}
network={studio}
isExistingMovie={isExistingMovie}
/>
</Link>
<Link
className={styles.tmdbLink}
to={`https://www.themoviedb.org/movie/${tmdbId}`}
>
<Icon
className={styles.tmdbLinkIcon}
name={icons.EXTERNAL_LINK}
size={16}
/>
</Link>
</div>
);
}
}

View File

@@ -1,6 +1,9 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput';
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
@@ -22,6 +25,9 @@ class CollectionFooter extends Component {
this.state = {
monitor: NO_CHANGE,
monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE,
destinationRootFolder: null
};
}
@@ -36,7 +42,10 @@ class CollectionFooter extends Component {
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitor: NO_CHANGE
monitor: NO_CHANGE,
qualityProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
minimumAvailability: NO_CHANGE
});
}
@@ -55,7 +64,10 @@ class CollectionFooter extends Component {
onUpdateSelectedPress = () => {
const {
monitor,
monitored
monitored,
qualityProfileId,
minimumAvailability,
rootFolderPath
} = this.state;
const changes = {};
@@ -68,6 +80,18 @@ class CollectionFooter extends Component {
changes.monitor = monitor;
}
if (qualityProfileId !== NO_CHANGE) {
changes.qualityProfileId = qualityProfileId;
}
if (minimumAvailability !== NO_CHANGE) {
changes.minimumAvailability = minimumAvailability;
}
if (rootFolderPath !== NO_CHANGE) {
changes.rootFolderPath = rootFolderPath;
}
this.props.onUpdateSelectedPress(changes);
};
@@ -82,7 +106,10 @@ class CollectionFooter extends Component {
const {
monitored,
monitor
monitor,
qualityProfileId,
minimumAvailability,
rootFolderPath
} = this.state;
const monitoredOptions = [
@@ -125,6 +152,52 @@ class CollectionFooter extends Component {
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
<QualityProfileSelectInputConnector
name="qualityProfileId"
value={qualityProfileId}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MinimumAvailability')}
isSaving={isSaving && minimumAvailability !== NO_CHANGE}
/>
<AvailabilitySelectInput
name="minimumAvailability"
value={minimumAvailability}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
<RootFolderSelectInputConnector
name="rootFolderPath"
value={rootFolderPath}
includeNoChange={true}
isDisabled={!selectedCount}
selectedValueOptions={{ includeFreeSpace: false }}
onChange={this.onInputChange}
/>
</div>
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<CollectionFooterLabel

View File

@@ -115,7 +115,7 @@ class EditCollectionModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>{translate('Folder')}</FormLabel>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}

View File

@@ -1,7 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Slider from 'react-slick';
import TextTruncate from 'react-text-truncate';
import { Navigation } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/react';
import EditCollectionModalConnector from 'Collection/Edit/EditCollectionModalConnector';
import CheckInput from 'Components/Form/CheckInput';
import Icon from 'Components/Icon';
@@ -17,8 +18,9 @@ import CollectionMovieConnector from './CollectionMovieConnector';
import CollectionMovieLabelConnector from './CollectionMovieLabelConnector';
import styles from './CollectionOverview.css';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
@@ -52,8 +54,12 @@ class CollectionOverview extends Component {
//
// Control
setSliderRef = (ref) => {
this.setState({ slider: ref });
setSliderPrevRef = (ref) => {
this._swiperPrevRef = ref;
};
setSliderNextRef = (ref) => {
this._swiperNextRef = ref;
};
//
@@ -120,15 +126,6 @@ class CollectionOverview extends Component {
const contentHeight = getContentHeight(rowHeight, isSmallScreen);
const overviewHeight = contentHeight - titleRowHeight - posterHeight;
const sliderSettings = {
arrows: false,
dots: false,
infinite: false,
slidesToShow: 1,
slidesToScroll: 1,
variableWidth: true
};
return (
<div className={styles.container}>
<div className={styles.content}>
@@ -166,19 +163,21 @@ class CollectionOverview extends Component {
{
showPosters &&
<div className={styles.navigationButtons}>
<IconButton
name={icons.ARROW_LEFT}
title={translate('ScrollMovies')}
onPress={this.state.slider?.slickPrev}
size={20}
/>
<span ref={this.setSliderPrevRef}>
<IconButton
name={icons.ARROW_LEFT}
title={translate('ScrollMovies')}
size={20}
/>
</span>
<IconButton
name={icons.ARROW_RIGHT}
title={translate('ScrollMovies')}
onPress={this.state.slider?.slickNext}
size={20}
/>
<span ref={this.setSliderNextRef}>
<IconButton
name={icons.ARROW_RIGHT}
title={translate('ScrollMovies')}
size={20}
/>
</span>
</div>
}
@@ -270,9 +269,23 @@ class CollectionOverview extends Component {
{
showPosters ?
<div className={styles.sliderContainer}>
<Slider ref={this.setSliderRef} {...sliderSettings}>
<Swiper
slidesPerView='auto'
spaceBetween={10}
slidesPerGroup={3}
loop={false}
loopFillGroupWithBlank={true}
className="mySwiper"
modules={[Navigation]}
onInit={(swiper) => {
swiper.params.navigation.prevEl = this._swiperPrevRef;
swiper.params.navigation.nextEl = this._swiperNextRef;
swiper.navigation.init();
swiper.navigation.update();
}}
>
{movies.map((movie) => (
<div className={styles.movie} key={movie.tmdbId}>
<SwiperSlide key={movie.tmdbId} style={{ width: posterWidth }}>
<CollectionMovieConnector
key={movie.tmdbId}
posterWidth={posterWidth}
@@ -281,9 +294,9 @@ class CollectionOverview extends Component {
collectionId={id}
{...movie}
/>
</div>
</SwiperSlide>
))}
</Slider>
</Swiper>
</div> :
<div className={styles.labelsContainer}>
{movies.map((movie) => (

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -22,11 +22,11 @@ class ImdbRating extends PureComponent {
let ratingString = '0.0';
if (rating) {
ratingString = `${rating.value}`;
ratingString = `${rating.value.toFixed(1)}`;
}
return (
<span title={`${rating.votes} votes`}>
<span title={`${rating ? rating.votes : 0} votes`}>
{
!hideIcon &&
<img

View File

@@ -1,4 +1,5 @@
.jumpBar {
z-index: $pageJumpBarZIndex;
display: flex;
align-content: stretch;
align-items: stretch;

View File

@@ -2,7 +2,8 @@
composes: link from '~Components/Link/Link.css';
padding-top: 4px;
width: $toolbarButtonWidth;
min-width: $toolbarButtonWidth;
width: min-content;
text-align: center;
&:hover {

View File

@@ -21,9 +21,11 @@ class RottenTomatoRating extends PureComponent {
const rating = ratings.rottenTomatoes;
let ratingString = '0%';
let ratingImage = rtFresh;
if (rating) {
ratingString = `${rating.value}%`;
ratingImage = rating.value > 50 ? rtFresh : rtRotten;
}
return (
@@ -32,7 +34,7 @@ class RottenTomatoRating extends PureComponent {
!hideIcon &&
<img
className={styles.image}
src={rating.value > 50 ? rtFresh : rtRotten}
src={ratingImage}
style={{
height: `${iconSize}px`
}}

View File

@@ -192,7 +192,7 @@ class TableOptionsModal extends Component {
<TableOptionsColumnDragSource
key={name}
name={name}
label={label || columnLabel}
label={columnLabel || label}
isVisible={isVisible}
isModifiable={true}
index={index}
@@ -210,7 +210,7 @@ class TableOptionsModal extends Component {
<TableOptionsColumn
key={name}
name={name}
label={label || columnLabel}
label={columnLabel || label}
isVisible={isVisible}
index={index}
isModifiable={false}

View File

@@ -22,7 +22,7 @@ class TmdbRating extends PureComponent {
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value * 10}%`;
ratingString = `${(rating.value * 10).toFixed()}%`;
}
return (

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import HeartRating from 'Components/HeartRating';
import TmdbRating from 'Components/TmdbRating';
import { getMovieStatusDetails } from 'Movie/MovieStatus';
import formatRuntime from 'Utilities/Date/formatRuntime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
@@ -111,7 +111,7 @@ function DiscoverMoviePosterInfo(props) {
if (sortKey === 'ratings' && ratings) {
return (
<div className={styles.info}>
<HeartRating
<TmdbRating
ratings={ratings}
/>
</div>

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import ImportListListConnector from 'Components/ImportListListConnector';
import IconButton from 'Components/Link/IconButton';
@@ -8,6 +7,7 @@ import Link from 'Components/Link/Link';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover';
import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal';
import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal';
@@ -245,7 +245,7 @@ class DiscoverMovieRow extends Component {
key={name}
className={styles[name]}
>
<HeartRating
<TmdbRating
ratings={ratings}
/>
</VirtualTableRowCell>

View File

@@ -22,6 +22,7 @@ import {
import {
faArrowCircleLeft as fasArrowCircleLeft,
faArrowCircleRight as fasArrowCircleRight,
faAsterisk as fasAsterisk,
faBackward as fasBackward,
faBan as fasBan,
faBars as fasBars,
@@ -154,6 +155,7 @@ export const FILE = farFile;
export const FILM = fasFilm;
export const FILTER = fasFilter;
export const FLAG = fasFlag;
export const FOOTNOTE = fasAsterisk;
export const FOLDER = farFolder;
export const FOLDER_OPEN = fasFolderOpen;
export const GENRE = fasTheaterMasks;

View File

@@ -0,0 +1,7 @@
.modalBody {
composes: modalBody from '~Components/Modal/ModalBody.css';
display: flex;
flex: 1 1 auto;
flex-direction: column;
}

View File

@@ -9,8 +9,9 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectReleaseGroupModalContent.css';
class SelectReleaseGroupModalContent extends Component {
@@ -58,7 +59,10 @@ class SelectReleaseGroupModalContent extends Component {
{translate('ManualImportSetReleaseGroup')}
</ModalHeader>
<ModalBody>
<ModalBody
className={styles.modalBody}
scrollDirection={scrollDirections.NONE}
>
<Form>
<FormGroup>
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
@@ -67,6 +71,7 @@ class SelectReleaseGroupModalContent extends Component {
type={inputTypes.TEXT}
name="releaseGroup"
value={releaseGroup}
autoFocus={true}
onChange={this.onReleaseGroupChange}
/>
</FormGroup>

View File

@@ -11,6 +11,7 @@ import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import styles from './MovieHistoryRow.css';
@@ -104,6 +105,10 @@ class MovieHistoryRow extends Component {
/>
</TableRowCell>
<TableRowCell key={name}>
{formatCustomFormatScore(data.customFormatScore)}
</TableRowCell>
<RelativeDateCellConnector
date={date}
/>

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
@@ -35,6 +36,15 @@ const columns = [
isSortable: false,
isVisible: true
},
{
name: 'customFormatScore',
label: React.createElement(Icon, {
name: icons.SCORE,
title: 'Custom format score'
}),
isSortable: true,
isVisible: true
},
{
name: 'date',
label: translate('Date'),

View File

@@ -102,12 +102,21 @@ function MovieIndexSortMenu(props) {
</SortMenuItem>
<SortMenuItem
name="ratings"
name="imdbRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Ratings')}
{translate('ImdbRating')}
</SortMenuItem>
<SortMenuItem
name="tmdbRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('TmdbRating')}
</SortMenuItem>
<SortMenuItem

View File

@@ -77,7 +77,9 @@
flex: 0 0 120px;
}
.ratings {
.imdbRating,
.tmdbRating,
.rottenTomatoesRating {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 80px;

View File

@@ -84,7 +84,9 @@
flex: 0 0 120px;
}
.ratings {
.imdbRating,
.tmdbRating,
.rottenTomatoesRating {
composes: cell;
flex: 0 0 80px;

View File

@@ -1,13 +1,15 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TagListConnector from 'Components/TagListConnector';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds } from 'Helpers/Props';
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
@@ -349,13 +351,39 @@ class MovieIndexRow extends Component {
);
}
if (name === 'ratings') {
if (name === 'tmdbRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<HeartRating
<TmdbRating
ratings={ratings}
/>
</VirtualTableRowCell>
);
}
if (name === 'rottenTomatoesRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<RottenTomatoRating
ratings={ratings}
/>
</VirtualTableRowCell>
);
}
if (name === 'imdbRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<ImdbRating
ratings={ratings}
/>
</VirtualTableRowCell>

View File

@@ -36,3 +36,9 @@
margin: 0;
border: none;
}
.label {
composes: label from '~Components/Label.css';
max-width: 100%;
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MiddleTruncate from 'react-middle-truncate';
import Card from 'Components/Card';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
@@ -124,10 +125,15 @@ class CustomFormat extends Component {
return (
<Label
className={styles.label}
key={index}
kind={kind}
>
{item.name}
<MiddleTruncate
text={item.name}
start={10}
end={14}
/>
</Label>
);
})

View File

@@ -21,6 +21,7 @@ function HostSettings(props) {
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
@@ -90,6 +91,21 @@ function HostSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -166,7 +166,7 @@ function EditImportListModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>{translate('Folder')}</FormLabel>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}

View File

@@ -16,3 +16,20 @@
margin-left: 10px;
width: 200px;
}
.footNote {
display: flex;
color: $helpTextColor;
.icon {
margin-top: 3px;
margin-right: 5px;
padding: 2px;
}
code {
padding: 0 1px;
border: 1px solid $borderColor;
background-color: #f7f7f7;
}
}

View File

@@ -3,17 +3,94 @@ import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import SelectInput from 'Components/Form/SelectInput';
import TextInput from 'Components/Form/TextInput';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { sizes } from 'Helpers/Props';
import { icons, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import NamingOption from './NamingOption';
import styles from './NamingModal.css';
const separatorOptions = [
{ key: ' ', value: 'Space ( )' },
{ key: '.', value: 'Period (.)' },
{ key: '_', value: 'Underscore (_)' },
{ key: '-', value: 'Dash (-)' }
];
const caseOptions = [
{ key: 'title', value: translate('DefaultCase') },
{ key: 'lower', value: translate('LowerCase') },
{ key: 'upper', value: translate('UpperCase') }
];
const fileNameTokens = [
{
token: '{Movie Title} - {Quality Full}',
example: 'Movie Title (2010) - HDTV-720p Proper'
}
];
const movieTokens = [
{ token: '{Movie Title}', example: 'Movie\'s Title' },
{ token: '{Movie Title:DE}', example: 'Titel des Films' },
{ token: '{Movie CleanTitle}', example: 'Movies Title' },
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The' },
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
{ token: '{Movie CleanOriginalTitle}', example: 'Τίτλος ταινίας' },
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
{ token: '{Movie Collection}', example: 'The Movie Collection' },
{ token: '{Movie Certification}', example: 'R' },
{ token: '{Release Year}', example: '2009' }
];
const movieIdTokens = [
{ token: '{ImdbId}', example: 'tt12345' },
{ token: '{TmdbId}', example: '123456' }
];
const qualityTokens = [
{ token: '{Quality Full}', example: 'HDTV-720p Proper' },
{ token: '{Quality Title}', example: 'HDTV-720p' }
];
const mediaInfoTokens = [
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]', footNote: 1 },
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]', footNote: 1 },
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]', footNote: 1 },
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' },
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' },
{ token: '{MediaInfo 3D}', example: '3D' }
];
const releaseGroupTokens = [
{ token: '{Release Group}', example: 'Rls Grp' }
];
const editionTokens = [
{ token: '{Edition Tags}', example: 'IMAX' }
];
const customFormatTokens = [
{ token: '{Custom Formats}', example: 'Surround Sound x264' }
];
const originalTokens = [
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
{ token: '{Original Filename}', example: 'movie title hdtv.x264-Evolve' }
];
class NamingModal extends Component {
//
@@ -94,81 +171,6 @@ class NamingModal extends Component {
case: tokenCase
} = this.state;
const separatorOptions = [
{ key: ' ', value: 'Space ( )' },
{ key: '.', value: 'Period (.)' },
{ key: '_', value: 'Underscore (_)' },
{ key: '-', value: 'Dash (-)' }
];
const caseOptions = [
{ key: 'title', value: translate('DefaultCase') },
{ key: 'lower', value: translate('LowerCase') },
{ key: 'upper', value: translate('UpperCase') }
];
const fileNameTokens = [
{
token: '{Movie Title} - {Quality Full}',
example: 'Movie Title (2010) - HDTV-720p Proper'
}
];
const movieTokens = [
{ token: '{Movie Title}', example: 'Movie\'s Title' },
{ token: '{Movie Title:DE}', example: 'Titel des Films' },
{ token: '{Movie CleanTitle}', example: 'Movies Title' },
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The' },
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
{ token: '{Movie CleanOriginalTitle}', example: 'Τίτλος ταινίας' },
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
{ token: '{Movie Collection}', example: 'The Movie Collection' },
{ token: '{Movie Certification}', example: 'R' },
{ token: '{Release Year}', example: '2009' }
];
const movieIdTokens = [
{ token: '{ImdbId}', example: 'tt12345' },
{ token: '{TmdbId}', example: '123456' }
];
const qualityTokens = [
{ token: '{Quality Full}', example: 'HDTV-720p Proper' },
{ token: '{Quality Title}', example: 'HDTV-720p' }
];
const mediaInfoTokens = [
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]' },
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]' },
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' },
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' }
];
const releaseGroupTokens = [
{ token: '{Release Group}', example: 'Rls Grp' }
];
const editionTokens = [
{ token: '{Edition Tags}', example: 'IMAX' }
];
const customFormatTokens = [
{ token: '{Custom Formats}', example: 'Surround Sound x264' }
];
const originalTokens = [
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
{ token: '{Original Filename}', example: 'movie title hdtv.x264-Evolve' }
];
return (
<Modal
isOpen={isOpen}
@@ -297,7 +299,7 @@ class NamingModal extends Component {
<FieldSet legend={translate('MediaInfo')}>
<div className={styles.groups}>
{
mediaInfoTokens.map(({ token, example }) => {
mediaInfoTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
@@ -305,6 +307,7 @@ class NamingModal extends Component {
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
@@ -314,6 +317,14 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<div>
MediaInfo Full/AudioLanguages/SubtitleLanguages support a <code>:EN+DE</code> suffix allowing you to filter the languages included in the filename. Use <code>-DE</code> to exclude specific languages.
Appending <code>+</code> (eg <code>:EN+</code>) will output <code>[EN]</code>/<code>[EN+--]</code>/<code>[--]</code> depending on excluded languages. For example <code>{'{'}MediaInfo Full:EN+DE{'}'}</code>.
</div>
</div>
</FieldSet>
<FieldSet legend={translate('ReleaseGroup')}>

View File

@@ -35,9 +35,15 @@
display: flex;
align-items: center;
align-self: stretch;
justify-content: space-between;
flex: 0 0 50%;
padding: 6px 16px;
background-color: #ddd;
.footNote {
padding: 2px;
color: #aaa;
}
}
.lower {

View File

@@ -1,8 +1,9 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { sizes } from 'Helpers/Props';
import { icons, sizes } from 'Helpers/Props';
import styles from './NamingOption.css';
class NamingOption extends Component {
@@ -39,6 +40,7 @@ class NamingOption extends Component {
token,
tokenSeparator,
example,
footNote,
tokenCase,
isFullFilename,
size
@@ -60,6 +62,11 @@ class NamingOption extends Component {
<div className={styles.example}>
{example.replace(/ /g, tokenSeparator)}
{
footNote !== 0 &&
<Icon className={styles.footNote} name={icons.FOOTNOTE} />
}
</div>
</Link>
);
@@ -69,6 +76,7 @@ class NamingOption extends Component {
NamingOption.propTypes = {
token: PropTypes.string.isRequired,
example: PropTypes.string.isRequired,
footNote: PropTypes.number.isRequired,
tokenSeparator: PropTypes.string.isRequired,
tokenCase: PropTypes.string.isRequired,
isFullFilename: PropTypes.bool.isRequired,
@@ -77,6 +85,7 @@ NamingOption.propTypes = {
};
NamingOption.defaultProps = {
footNote: 0,
size: sizes.SMALL,
isFullFilename: false
};

View File

@@ -85,7 +85,7 @@ class DelayProfile extends Component {
connectDragSource
} = this.props;
let preferred = titleCase(preferredProtocol);
let preferred = `Prefer ${titleCase(preferredProtocol)}`;
if (!enableUsenet) {
preferred = translate('OnlyTorrent');

View File

@@ -83,7 +83,7 @@ class DelayProfiles extends Component {
<div>
<div className={styles.delayProfilesHeader}>
<div className={styles.column}>
{translate('Protocol')}
{translate('PreferredProtocol')}
</div>
<div className={styles.column}>
{translate('UsenetDelay')}

View File

@@ -17,6 +17,13 @@ import { boolSettingShape, numberSettingShape, tagSettingShape } from 'Helpers/P
import translate from 'Utilities/String/translate';
import styles from './EditDelayProfileModalContent.css';
const protocolOptions = [
{ key: 'preferUsenet', value: translate('PreferUsenet') },
{ key: 'preferTorrent', value: translate('PreferTorrent') },
{ key: 'onlyUsenet', value: translate('OnlyUsenet') },
{ key: 'onlyTorrent', value: translate('OnlyTorrent') }
];
function EditDelayProfileModalContent(props) {
const {
id,
@@ -26,7 +33,6 @@ function EditDelayProfileModalContent(props) {
saveError,
item,
protocol,
protocolOptions,
onInputChange,
onProtocolChange,
onSavePress,
@@ -52,22 +58,24 @@ function EditDelayProfileModalContent(props) {
<ModalBody>
{
isFetching &&
<LoadingIndicator />
isFetching ?
<LoadingIndicator /> :
null
}
{
!isFetching && !!error &&
!isFetching && !!error ?
<div>
{translate('UnableToAddANewQualityProfilePleaseTryAgain')}
</div>
</div> :
null
}
{
!isFetching && !error &&
!isFetching && !error ?
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('Protocol')}</FormLabel>
<FormLabel>{translate('PreferredProtocol')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -143,19 +151,21 @@ function EditDelayProfileModalContent(props) {
/>
</FormGroup>
}
</Form>
</Form> :
null
}
</ModalBody>
<ModalFooter>
{
id && id > 1 &&
id && id > 1 ?
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteDelayProfilePress}
>
{translate('Delete')}
</Button>
</Button> :
null
}
<Button
@@ -193,7 +203,6 @@ EditDelayProfileModalContent.propTypes = {
saveError: PropTypes.object,
item: PropTypes.shape(delayProfileShape).isRequired,
protocol: PropTypes.string.isRequired,
protocolOptions: PropTypes.arrayOf(PropTypes.object).isRequired,
onInputChange: PropTypes.func.isRequired,
onProtocolChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,

View File

@@ -5,7 +5,6 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveDelayProfile, setDelayProfileValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import translate from 'Utilities/String/translate';
import EditDelayProfileModalContent from './EditDelayProfileModalContent';
const newDelayProfile = {
@@ -17,13 +16,6 @@ const newDelayProfile = {
tags: []
};
const protocolOptions = [
{ key: 'preferUsenet', value: translate('PreferUsenet') },
{ key: 'preferTorrent', value: translate('PreferTorrent') },
{ key: 'onlyUsenet', value: translate('OnlyUsenet') },
{ key: 'onlyTorrent', value: translate('OnlyTorrent') }
];
function createDelayProfileSelector() {
return createSelector(
(state, { id }) => id,
@@ -79,7 +71,6 @@ function createMapStateToProps() {
return {
protocol,
protocolOptions,
...delayProfile
};
}

View File

@@ -262,10 +262,10 @@ export const defaultState = {
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const collectionList = items.reduce((acc, movie) => {
if (movie.collection) {
if (movie.collection && movie.collection.title) {
acc.push({
id: movie.collection.name,
name: movie.collection.name
id: movie.collection.title,
name: movie.collection.title
});
}
@@ -561,7 +561,7 @@ export const actionHandlers = handleThunks({
}, []);
const promise = createAjaxRequest({
url: '/movie/import',
url: '/importlist/movie',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(allNewMovies)

View File

@@ -1,5 +1,7 @@
import React from 'react';
import { createAction } from 'redux-actions';
import { filterTypes, sortDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
import { filterTypes, icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
@@ -84,6 +86,15 @@ export const defaultState = {
label: translate('SourceTitle'),
isVisible: false
},
{
name: 'customFormatScore',
columnLabel: translate('CustomFormatScore'),
label: React.createElement(Icon, {
name: icons.SCORE,
title: 'Custom format score'
}),
isVisible: false
},
{
name: 'details',
columnLabel: translate('Details'),

View File

@@ -4,6 +4,7 @@ import { batchActions } from 'redux-batched-actions';
import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import naturalExpansion from 'Utilities/String/naturalExpansion';
import { set, update, updateItem } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
@@ -35,7 +36,7 @@ export const defaultState = {
relativePath: function(item, direction) {
const relativePath = item.relativePath;
return relativePath.toLowerCase();
return naturalExpansion(relativePath.toLowerCase());
},
movie: function(item, direction) {

View File

@@ -116,7 +116,7 @@ export const filterPredicates = {
const predicate = filterTypePredicates[type];
const { collection } = item;
return predicate(collection ? collection.name : '', filterValue);
return predicate(collection && collection.title ? collection.title : '', filterValue);
},
originalLanguage: function(item, filterValue, type) {
@@ -162,6 +162,14 @@ export const filterPredicates = {
return predicate(rating, filterValue);
},
rottenTomatoesRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.rottenTomatoes ? item.ratings.rottenTomatoes.value : 0;
return predicate(rating, filterValue);
},
imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];

View File

@@ -1,10 +1,12 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewMovie from 'Utilities/Movie/getNewMovie';
import translate from 'Utilities/String/translate';
import { set, update, updateItem } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
@@ -63,19 +65,81 @@ export const defaultState = {
}
],
filterPredicates: {},
filterPredicates: {
genres: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
let allGenres = [];
item.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
});
const genres = Array.from(new Set(allGenres)).slice(0, 3);
return predicate(genres, filterValue);
},
totalMovies: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { movies } = item;
const totalMovies = movies.length;
return predicate(totalMovies, filterValue);
}
},
filterBuilderProps: [
{
name: 'title',
label: 'Title',
label: translate('Title'),
type: filterBuilderTypes.STRING
},
{
name: 'monitored',
label: 'Monitored',
label: translate('Monitored'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.BOOL
},
{
name: 'qualityProfileId',
label: translate('QualityProfile'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.QUALITY_PROFILE
},
{
name: 'rootFolderPath',
label: translate('RootFolder'),
type: filterBuilderTypes.STRING
},
{
name: 'genres',
label: translate('Genres'),
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const genreList = items.reduce((acc, collection) => {
let collectionGenres = [];
collection.movies.forEach((movie) => {
collectionGenres = collectionGenres.concat(movie.genres);
});
const genres = Array.from(new Set(collectionGenres)).slice(0, 3);
genres.forEach((genre) => {
acc.push({
id: genre,
name: genre
});
});
return acc;
}, []);
return genreList.sort(sortByName);
}
},
{
name: 'totalMovies',
label: translate('TotalMovies'),
type: filterBuilderTypes.NUMBER
}
]
};
@@ -254,27 +318,32 @@ export const actionHandlers = handleThunks({
const {
collectionIds,
monitored,
monitor
monitor,
qualityProfileId,
rootFolderPath,
minimumAvailability
} = payload;
const response = {};
const collections = [];
collectionIds.forEach((id) => {
const collectionToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
collectionToUpdate.monitored = monitored;
}
collections.push(collectionToUpdate);
});
if (payload.hasOwnProperty('monitored')) {
response.monitored = monitored;
}
if (payload.hasOwnProperty('monitor')) {
response.monitorMovies = monitor === 'monitored';
}
response.collections = collections;
if (payload.hasOwnProperty('qualityProfileId')) {
response.qualityProfileId = qualityProfileId;
}
if (payload.hasOwnProperty('minimumAvailability')) {
response.minimumAvailability = minimumAvailability;
}
response.rootFolderPath = rootFolderPath;
response.collectionIds = collectionIds;
dispatch(set({
section,

View File

@@ -165,11 +165,11 @@ export const actionHandlers = handleThunks({
requestData.quality = quality;
}
if (releaseGroup) {
if (releaseGroup !== undefined) {
requestData.releaseGroup = releaseGroup;
}
if (edition) {
if (edition !== undefined) {
requestData.edition = edition;
}
@@ -201,11 +201,11 @@ export const actionHandlers = handleThunks({
props.quality = quality;
}
if (edition) {
if (edition !== undefined) {
props.edition = edition;
}
if (releaseGroup) {
if (releaseGroup !== undefined) {
props.releaseGroup = releaseGroup;
}

View File

@@ -178,8 +178,20 @@ export const defaultState = {
isVisible: true
},
{
name: 'ratings',
label: translate('Ratings'),
name: 'tmdbRating',
label: translate('TmdbRating'),
isSortable: true,
isVisible: false
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
isSortable: true,
isVisible: false
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
isSortable: true,
isVisible: false
},
@@ -215,7 +227,7 @@ export const defaultState = {
collection: function(item) {
const { collection ={} } = item;
return collection.name;
return collection.title;
},
originalLanguage: function(item) {
@@ -224,10 +236,22 @@ export const defaultState = {
return originalLanguage.name;
},
ratings: function(item) {
imdbRating: function(item) {
const { ratings = {} } = item;
return ratings.tmdb? ratings.tmdb.value : 0;
return ratings.imdb ? ratings.imdb.value : 0;
},
tmdbRating: function(item) {
const { ratings = {} } = item;
return ratings.tmdb ? ratings.tmdb.value : 0;
},
rottenTomatoesRating: function(item) {
const { ratings = {} } = item;
return ratings.rottenTomatoes ? ratings.rottenTomatoes.value : 0;
}
},
@@ -315,10 +339,10 @@ export const defaultState = {
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const collectionList = items.reduce((acc, movie) => {
if (movie.collection) {
if (movie.collection && movie.collection.title) {
acc.push({
id: movie.collection.name,
name: movie.collection.name
id: movie.collection.title,
name: movie.collection.title
});
}
@@ -413,6 +437,11 @@ export const defaultState = {
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),

View File

@@ -1,4 +1,5 @@
module.exports = {
pageJumpBarZIndex: 10,
modalZIndex: 1000,
popperZIndex: 2000
};

View File

@@ -0,0 +1,16 @@
function formatCustomFormatScore(input) {
const score = Number(input);
if (score > 0) {
return `+${score}`;
}
if (score < 0) {
return score;
}
return '';
}
export default formatCustomFormatScore;

View File

@@ -0,0 +1,11 @@
const regex = /\d+/g;
function naturalExpansion(input) {
if (!input) {
return '';
}
return input.replace(regex, (n) => n.padStart(8, '0'));
}
export default naturalExpansion;

View File

@@ -1,9 +1,11 @@
const regex = /\b\w+/g;
function titleCase(input) {
if (!input) {
return '';
}
return input.replace(/\b\w+/g, (match) => {
return input.replace(regex, (match) => {
return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase();
});
}

View File

@@ -30,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "6.1.0",
"@fortawesome/free-solid-svg-icons": "6.1.0",
"@fortawesome/react-fontawesome": "0.1.18",
"@microsoft/signalr": "6.0.5",
"@microsoft/signalr": "6.0.8",
"@sentry/browser": "6.18.2",
"@sentry/integrations": "6.18.2",
"classnames": "2.3.1",
@@ -45,7 +45,7 @@
"jquery": "3.6.0",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.2",
"moment": "2.29.4",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.7.2",
@@ -62,8 +62,7 @@
"react-document-title": "2.0.3",
"react-dom": "17.0.2",
"react-focus-lock": "2.5.0",
"react-slick": "0.28.1",
"slick-carousel": "1.8.1",
"swiper": "8.3.2",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",
"react-measure": "1.4.7",

View File

@@ -90,7 +90,7 @@
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />

View File

@@ -8,5 +8,6 @@
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -212,6 +212,7 @@ namespace NzbDrone.Common.Test.Http
}
[Test]
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
public void should_execute_get_using_brotli()
{
var request = new HttpRequest($"https://{_httpBinHost}/brotli");

View File

@@ -64,6 +64,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
@@ -79,14 +80,33 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Notifiarr
[TestCase(@"https://xxx.yyy/api/v1/notification/radarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
// Discord
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
public void should_clean_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
}
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
public void should_keep_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
cleansedMessage.Should().Contain("shouldkeep1");
cleansedMessage.Should().Contain("shouldkeep2");
cleansedMessage.Should().Contain("shouldkeep3");
}
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
[TestCase(@"Auth-Success ip 32.2.3.5")]

View File

@@ -18,9 +18,26 @@ namespace NzbDrone.Common.Test.InstrumentationTests
private static LogLevel[] SentryLevels = LogLevel.AllLevels.Where(x => x >= LogLevel.Error).ToArray();
private static LogLevel[] OtherLevels = AllLevels.Except(SentryLevels).ToArray();
// TODO: SQLiteException filtering tests don't work on linux-86 and alpine customer Azure agents due to sqlite library not being loaded up, pass local
private static Exception[] FilteredExceptions = new Exception[]
{
new UnauthorizedAccessException()
// new SQLiteException(SQLiteErrorCode.Locked, "database is locked"),
new UnauthorizedAccessException(),
new AggregateException(new Exception[]
{
new UnauthorizedAccessException(),
new UnauthorizedAccessException()
})
};
private static Exception[] NonFilteredExceptions = new Exception[]
{
// new SQLiteException(SQLiteErrorCode.Error, "it's borked"),
new AggregateException(new Exception[]
{
new UnauthorizedAccessException(),
new NotImplementedException()
})
};
[SetUp]
@@ -63,6 +80,14 @@ namespace NzbDrone.Common.Test.InstrumentationTests
_subject.IsSentryMessage(log).Should().BeFalse();
}
[Test]
[TestCaseSource("NonFilteredExceptions")]
public void should_not_filter_event_for_filtered_exception_types(Exception ex)
{
var log = GivenLogEvent(LogLevel.Error, ex, "test");
_subject.IsSentryMessage(log).Should().BeTrue();
}
[Test]
[TestCaseSource("FilteredExceptions")]
public void should_not_filter_event_for_filtered_exception_types_if_filtering_disabled(Exception ex)

View File

@@ -278,7 +278,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetUpdateClientExePath()
{
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\radarr_update\Radarr.Update.exe".AsOsAgnostic());
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\radarr_update\Radarr.Update".AsOsAgnostic().ProcessNameToExe());
}
[Test]

View File

@@ -264,6 +264,11 @@ namespace NzbDrone.Common.Disk
protected virtual void MoveFileInternal(string source, string destination)
{
if (File.Exists(destination))
{
throw new FileAlreadyExistsException("File already exists", destination);
}
File.Move(source, destination);
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Common.Disk
{
public class FileAlreadyExistsException : Exception
{
public string Filename { get; set; }
public FileAlreadyExistsException(string message, string filename)
: base(message)
{
Filename = filename;
}
}
}

View File

@@ -2,13 +2,6 @@ using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public enum PlatformType
{
DotNet = 0,
Mono = 1,
NetCore = 2
}
public interface IPlatformInfo
{
Version Version { get; }
@@ -16,31 +9,18 @@ namespace NzbDrone.Common.EnvironmentInfo
public class PlatformInfo : IPlatformInfo
{
private static PlatformType _platform;
private static Version _version;
static PlatformInfo()
{
_platform = PlatformType.NetCore;
_version = Environment.Version;
}
public static PlatformType Platform => _platform;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static bool IsNetCore => Platform == PlatformType.NetCore;
public static string PlatformName
{
get
{
if (IsDotNet)
{
return ".NET";
}
else
{
return ".NET Core";
}
return ".NET";
}
}

View File

@@ -267,9 +267,9 @@ namespace NzbDrone.Common.Extensions
return substring.Substring(0, lastSeparatorIndex);
}
public static string ProcessNameToExe(this string processName, PlatformType runtime)
public static string ProcessNameToExe(this string processName)
{
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
if (OsInfo.IsWindows)
{
processName += ".exe";
}
@@ -277,11 +277,6 @@ namespace NzbDrone.Common.Extensions
return processName;
}
public static string ProcessNameToExe(this string processName)
{
return processName.ProcessNameToExe(PlatformInfo.Platform);
}
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
{
return appFolderInfo.AppDataFolder;
@@ -352,9 +347,9 @@ namespace NzbDrone.Common.Extensions
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
}
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
}
public static string GetDatabase(this IAppFolderInfo appFolderInfo)

View File

@@ -55,7 +55,8 @@ namespace NzbDrone.Common.Http
StatusCode == HttpStatusCode.Found ||
StatusCode == HttpStatusCode.TemporaryRedirect ||
StatusCode == HttpStatusCode.RedirectMethod ||
StatusCode == HttpStatusCode.SeeOther;
StatusCode == HttpStatusCode.SeeOther ||
StatusCode == HttpStatusCode.PermanentRedirect;
public string[] GetCookieHeaders()
{

View File

@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
@@ -50,7 +50,10 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Notifiarr
new Regex(@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
new Regex(@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Fluent;
@@ -8,47 +10,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
{
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return logBuilder.Property("Sentry", fingerprint);
}
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
}
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
}
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
}
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
}
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
{
SentryLogger.Log(level)
.CopyLogEvent(logBuilder.LogEventInfo)
SentryLogger.ForLogEvent(level)
.CopyLogEvent(logBuilder.LogEvent)
.SentryFingerprint(fingerprint)
.Write();
.Log();
return logBuilder.Property("Sentry", null);
return logBuilder.Property<string>("Sentry", null);
}
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
{
return logBuilder.LoggerName(logEvent.LoggerName)
.TimeStamp(logEvent.TimeStamp)
return logBuilder.TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters)
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
.Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
.Exception(logEvent.Exception);
}
}

View File

@@ -1,13 +1,16 @@
using NLog;
using System;
using System.Text;
using NLog;
using NLog.Targets;
namespace NzbDrone.Common.Instrumentation
{
public class NzbDroneFileTarget : FileTarget
{
protected override string GetFormattedMessage(LogEventInfo logEvent)
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
return CleanseLogMessage.Cleanse(Layout.Render(logEvent));
var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
target.Append(result);
}
}
}

View File

@@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached)
{
RegisterDebugger();
@@ -97,10 +99,21 @@ namespace NzbDrone.Common.Instrumentation
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
var loggingRule = new LoggingRule("*", LogLevel.Trace, target);
LogManager.Configuration.AddTarget("debugger", target);
LogManager.Configuration.LoggingRules.Add(loggingRule);
}
private static void RegisterGlobalFilters()
{
LogManager.Setup().LoadConfiguration(c =>
{
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
});
}
private static void RegisterConsole()
{
var level = LogLevel.Trace;

View File

@@ -229,21 +229,48 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
if (FilterEvents)
{
var sqlEx = logEvent.Exception as SQLiteException;
if (sqlEx != null && FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
var exceptions = new List<Exception>();
var aggEx = logEvent.Exception as AggregateException;
if (aggEx != null && aggEx.InnerExceptions.Count > 0)
{
return false;
exceptions.AddRange(aggEx.InnerExceptions);
}
else
{
exceptions.Add(logEvent.Exception);
}
if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
// If any are sentry then send to sentry
foreach (var ex in exceptions)
{
return false;
var isSentry = true;
var sqlEx = ex as SQLiteException;
if (sqlEx != null && FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
{
isSentry = false;
}
if (FilteredExceptionTypeNames.Contains(ex.GetType().Name))
{
isSentry = false;
}
if (FilteredExceptionMessages.Any(x => ex.Message.Contains(x)))
{
isSentry = false;
}
if (isSentry)
{
return true;
}
}
if (FilteredExceptionMessages.Any(x => logEvent.Exception.Message.Contains(x)))
{
return false;
}
// The exception or aggregate exception children were not sentry exceptions
return false;
}
return true;

View File

@@ -8,12 +8,12 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="3.15.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.20.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.4" />
<PackageReference Include="System.Text.Json" Version="6.0.5" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />

View File

@@ -27,6 +27,20 @@ namespace NzbDrone.Core.Test.Datastore
Mocker.Resolve<IDatabase>().Vacuum();
}
[Test]
public void postgres_should_not_contain_timestamp_without_timezone_columns()
{
if (Db.DatabaseType != DatabaseType.PostgreSQL)
{
return;
}
Mocker.Resolve<IDatabase>()
.OpenConnection().Query("SELECT table_name, column_name, data_type FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = 'public' AND data_type = 'timestamp without time zone'")
.Should()
.BeNullOrEmpty();
}
[Test]
public void get_version()
{

View File

@@ -56,6 +56,51 @@ namespace NzbDrone.Core.Test.Datastore.Migration
movies.First().CollectionTmdbId.Should().Be(collections.First().TmdbId);
}
[Test]
public void should_skip_collection_from_movie_without_name()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Movies").Row(new
{
Monitored = true,
MinimumAvailability = 4,
ProfileId = 1,
MovieFileId = 0,
MovieMetadataId = 1,
Path = string.Format("/Movies/{0}", "Title"),
});
c.Insert.IntoTable("MovieMetadata").Row(new
{
Title = "Title",
CleanTitle = "CleanTitle",
Status = 3,
Images = new[] { new { CoverType = "Poster" } }.ToJson(),
Recommendations = new[] { 1 }.ToJson(),
Runtime = 90,
OriginalTitle = "Title",
CleanOriginalTitle = "CleanTitle",
OriginalLanguage = 1,
TmdbId = 132456,
Collection = new { TmdbId = 11 }.ToJson(),
LastInfoSync = DateTime.UtcNow,
});
});
var collections = db.Query<Collection208>("SELECT \"Id\", \"Title\", \"TmdbId\", \"Monitored\" FROM \"Collections\"");
collections.Should().HaveCount(1);
collections.First().TmdbId.Should().Be(11);
collections.First().Title.Should().Be("Collection 11");
collections.First().Monitored.Should().BeFalse();
var movies = db.Query<Movie208>("SELECT \"Id\", \"CollectionTmdbId\" FROM \"MovieMetadata\"");
movies.Should().HaveCount(1);
movies.First().CollectionTmdbId.Should().Be(collections.First().TmdbId);
}
[Test]
public void should_not_duplicate_collection()
{

View File

@@ -1,6 +1,8 @@
using FizzWare.NBuilder;
using System;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
@@ -168,5 +170,80 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Should()
.BeFalse();
}
[Test]
public void should_return_false_when_repack_but_auto_download_repack_is_false()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotUpgrade);
_parsedMovieInfo.Quality.Revision.IsRepack = true;
_movie.MovieFileId = 1;
_movie.MovieFile = Builder<MovieFile>.CreateNew()
.With(e => e.Quality = new QualityModel(Quality.SDTV))
.With(e => e.ReleaseGroup = "Radarr")
.Build();
var remoteMovie = Builder<RemoteMovie>.CreateNew()
.With(e => e.ParsedMovieInfo = _parsedMovieInfo)
.With(e => e.Movie = _movie)
.Build();
Subject.IsSatisfiedBy(remoteMovie, null)
.Accepted
.Should()
.BeFalse();
}
[Test]
public void should_return_true_when_repack_but_auto_download_repack_is_true()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
_parsedMovieInfo.Quality.Revision.IsRepack = true;
_movie.MovieFileId = 1;
_movie.MovieFile = Builder<MovieFile>.CreateNew()
.With(e => e.Quality = new QualityModel(Quality.SDTV))
.With(e => e.ReleaseGroup = "Radarr")
.Build();
var remoteMovie = Builder<RemoteMovie>.CreateNew()
.With(e => e.ParsedMovieInfo = _parsedMovieInfo)
.With(e => e.Movie = _movie)
.Build();
Subject.IsSatisfiedBy(remoteMovie, null)
.Accepted
.Should()
.BeTrue();
}
[Test]
public void should_return_true_when_repacks_are_not_preferred()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
_parsedMovieInfo.Quality.Revision.IsRepack = true;
_movie.MovieFileId = 1;
_movie.MovieFile = Builder<MovieFile>.CreateNew()
.With(e => e.Quality = new QualityModel(Quality.SDTV))
.With(e => e.ReleaseGroup = "Radarr")
.Build();
var remoteMovie = Builder<RemoteMovie>.CreateNew()
.With(e => e.ParsedMovieInfo = _parsedMovieInfo)
.With(e => e.Movie = _movie)
.Build();
Subject.IsSatisfiedBy(remoteMovie, null)
.Accepted
.Should()
.BeTrue();
}
}
}

View File

@@ -0,0 +1,245 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras
{
[TestFixture]
public class ExtraServiceFixture : CoreTest<ExtraService>
{
private Movie _movie;
private MovieFile _movieFile;
private LocalMovie _localMovie;
private string _movieFolder;
private string _releaseFolder;
private Mock<IManageExtraFiles> _subtitleService;
private Mock<IManageExtraFiles> _otherExtraService;
[SetUp]
public void Setup()
{
_movieFolder = @"C:\Test\Movies\Movie Title".AsOsAgnostic();
_releaseFolder = @"C:\Test\Unsorted TV\Movie.Title.2022".AsOsAgnostic();
_movie = Builder<Movie>.CreateNew()
.With(s => s.Path = _movieFolder)
.Build();
_movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = Path.Combine(_movie.Path, "Movie Title - 2022.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Movie Title - 2022.mkv".AsOsAgnostic())
.Build();
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _movie)
.With(l => l.Path = Path.Combine(_releaseFolder, "Movie.Title.2022.mkv").AsOsAgnostic())
.Build();
_subtitleService = new Mock<IManageExtraFiles>();
_subtitleService.SetupGet(s => s.Order).Returns(0);
_subtitleService.Setup(s => s.CanImportFile(It.IsAny<LocalMovie>(), It.IsAny<MovieFile>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Returns(false);
_subtitleService.Setup(s => s.CanImportFile(It.IsAny<LocalMovie>(), It.IsAny<MovieFile>(), It.IsAny<string>(), ".srt", It.IsAny<bool>()))
.Returns(true);
_otherExtraService = new Mock<IManageExtraFiles>();
_otherExtraService.SetupGet(s => s.Order).Returns(1);
_otherExtraService.Setup(s => s.CanImportFile(It.IsAny<LocalMovie>(), It.IsAny<MovieFile>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Returns(true);
Mocker.SetConstant<IEnumerable<IManageExtraFiles>>(new[]
{
_subtitleService.Object,
_otherExtraService.Object
});
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderExists(It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
WithExistingFolder(_movie.Path);
WithExistingFile(_movieFile.Path);
WithExistingFile(_localMovie.Path);
Mocker.GetMock<IConfigService>().Setup(v => v.ImportExtraFiles).Returns(true);
Mocker.GetMock<IConfigService>().Setup(v => v.ExtraFileExtensions).Returns("nfo,srt");
}
private void WithExistingFolder(string path, bool exists = true)
{
var dir = Path.GetDirectoryName(path);
if (exists && dir.IsNotNullOrWhiteSpace())
{
WithExistingFolder(dir);
}
Mocker.GetMock<IDiskProvider>().Setup(v => v.FolderExists(path)).Returns(exists);
}
private void WithExistingFile(string path, bool exists = true, int size = 1000)
{
var dir = Path.GetDirectoryName(path);
if (exists && dir.IsNotNullOrWhiteSpace())
{
WithExistingFolder(dir);
}
Mocker.GetMock<IDiskProvider>().Setup(v => v.FileExists(path)).Returns(exists);
Mocker.GetMock<IDiskProvider>().Setup(v => v.GetFileSize(path)).Returns(size);
}
private void WithExistingFiles(List<string> files)
{
foreach (string file in files)
{
WithExistingFile(file);
}
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetFiles(_releaseFolder, It.IsAny<SearchOption>()))
.Returns(files.ToArray());
}
[Test]
public void should_not_pass_file_if_import_disabled()
{
Mocker.GetMock<IConfigService>().Setup(v => v.ImportExtraFiles).Returns(false);
var nfofile = Path.Combine(_releaseFolder, "Movie.Title.2022.nfo").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
nfofile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
_otherExtraService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
}
[Test]
[TestCase("Movie Title - 2022.sub")]
[TestCase("Movie Title - 2022.ass")]
public void should_not_pass_unwanted_file(string filePath)
{
Mocker.GetMock<IConfigService>().Setup(v => v.ImportExtraFiles).Returns(false);
var nfofile = Path.Combine(_releaseFolder, filePath).AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
nfofile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
_otherExtraService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
}
[Test]
public void should_pass_subtitle_file_to_subtitle_service()
{
var subtitleFile = Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
subtitleFile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { subtitleFile }, true), Times.Once());
_otherExtraService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { subtitleFile }, true), Times.Never());
}
[Test]
public void should_pass_nfo_file_to_other_service()
{
var nfofile = Path.Combine(_releaseFolder, "Movie.Title.2022.nfo").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
nfofile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { nfofile }, true), Times.Never());
_otherExtraService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { nfofile }, true), Times.Once());
}
[Test]
public void should_search_subtitles_when_importing_from_job_folder()
{
_localMovie.FolderMovieInfo = new ParsedMovieInfo();
var subtitleFile = Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
subtitleFile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.AllDirectories), Times.Once);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.TopDirectoryOnly), Times.Never);
}
[Test]
public void should_not_search_subtitles_when_not_importing_from_job_folder()
{
_localMovie.FolderMovieInfo = null;
var subtitleFile = Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
subtitleFile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.AllDirectories), Times.Never);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.TopDirectoryOnly), Times.Once);
}
}
}

View File

@@ -0,0 +1,84 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras.Others
{
[TestFixture]
public class OtherExtraServiceFixture : CoreTest<OtherExtraService>
{
private Movie _movie;
private MovieFile _movieFile;
private LocalMovie _localMovie;
private string _movieFolder;
private string _releaseFolder;
[SetUp]
public void Setup()
{
_movieFolder = @"C:\Test\Movies\Movie Title".AsOsAgnostic();
_releaseFolder = @"C:\Test\Unsorted Movies\Movie.Title.2022".AsOsAgnostic();
_movie = Builder<Movie>.CreateNew()
.With(s => s.Path = _movieFolder)
.Build();
_movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = Path.Combine(_movie.Path, "Movie Title - 2022.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Movie Title - 2022.mkv")
.Build();
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _movie)
.With(l => l.Path = Path.Combine(_releaseFolder, "Movie.Title.2022.mkv").AsOsAgnostic())
.With(l => l.FileMovieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string> { "Movie Title" },
Year = 2022
})
.Build();
}
[Test]
[TestCase("Movie Title - 2022.nfo", "Movie Title - 2022.nfo")]
[TestCase("Movie.Title.2022.nfo", "Movie Title - 2022.nfo")]
[TestCase("Movie Title 2022.nfo", "Movie Title - 2022.nfo")]
[TestCase("Movie_Title_2022.nfo", "Movie Title - 2022.nfo")]
[TestCase(@"Movie.Title.2022\thumb.jpg", "Movie Title - 2022.jpg")]
public void should_import_matching_file(string filePath, string expectedOutputPath)
{
var files = new List<string> { Path.Combine(_releaseFolder, filePath).AsOsAgnostic() };
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(1);
results[0].RelativePath.AsOsAgnostic().PathEquals(expectedOutputPath.AsOsAgnostic()).Should().Be(true);
}
[Test]
public void should_not_import_multiple_nfo_files()
{
var files = new List<string>
{
Path.Combine(_releaseFolder, "Movie.Title.2022.nfo").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie_Title_2022.nfo").AsOsAgnostic(),
};
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(1);
}
}
}

View File

@@ -0,0 +1,179 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MovieImport;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras.Subtitles
{
[TestFixture]
public class SubtitleServiceFixture : CoreTest<SubtitleService>
{
private Movie _movie;
private MovieFile _movieFile;
private LocalMovie _localMovie;
private string _MovieFolder;
private string _releaseFolder;
[SetUp]
public void Setup()
{
_MovieFolder = @"C:\Test\Movies\Movie Title".AsOsAgnostic();
_releaseFolder = @"C:\Test\Unsorted Movies\Movie.Title.2022".AsOsAgnostic();
_movie = Builder<Movie>.CreateNew()
.With(s => s.Path = _MovieFolder)
.Build();
_movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = Path.Combine(_movie.Path, "Movie Title - 2022.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Movie Title - 2022.mkv".AsOsAgnostic())
.Build();
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _movie)
.With(l => l.Path = Path.Combine(_releaseFolder, "Movie.Title.2022.mkv").AsOsAgnostic())
.With(l => l.FileMovieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string> { "Movie Title" },
Year = 2022
})
.Build();
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
Mocker.GetMock<IDetectSample>().Setup(s => s.IsSample(It.IsAny<MovieMetadata>(), It.IsAny<string>()))
.Returns(DetectSampleResult.NotSample);
}
[Test]
[TestCase("Movie.Title.2022.en.nfo")]
public void should_not_import_non_subtitle_file(string filePath)
{
var files = new List<string> { Path.Combine(_releaseFolder, filePath).AsOsAgnostic() };
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(0);
}
[Test]
[TestCase("Movie Title - 2022.srt", "Movie Title - 2022.srt")]
[TestCase("Movie.Title.2022.en.srt", "Movie Title - 2022.en.srt")]
[TestCase("Movie.Title.2022.english.srt", "Movie Title - 2022.en.srt")]
[TestCase("Movie Title 2022_en_sdh_forced.srt", "Movie Title - 2022.en.sdh.forced.srt")]
[TestCase("Movie_Title_2022 en.srt", "Movie Title - 2022.en.srt")]
[TestCase(@"Subs\Movie.Title.2022\2_en.srt", "Movie Title - 2022.en.srt")]
[TestCase("sub.srt", "Movie Title - 2022.srt")]
public void should_import_matching_subtitle_file(string filePath, string expectedOutputPath)
{
var files = new List<string> { Path.Combine(_releaseFolder, filePath).AsOsAgnostic() };
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(1);
results[0].RelativePath.AsOsAgnostic().PathEquals(expectedOutputPath.AsOsAgnostic()).Should().Be(true);
}
[Test]
public void should_import_multiple_subtitle_files_per_language()
{
var files = new List<string>
{
Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.eng.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Subs", "Movie_Title_2022_en_forced.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Subs", "Movie.Title.2022", "2_fr.srt").AsOsAgnostic()
};
var expectedOutputs = new string[]
{
"Movie Title - 2022.1.en.srt",
"Movie Title - 2022.2.en.srt",
"Movie Title - 2022.en.forced.srt",
"Movie Title - 2022.fr.srt",
};
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(expectedOutputs.Length);
for (int i = 0; i < expectedOutputs.Length; i++)
{
results[i].RelativePath.AsOsAgnostic().PathEquals(expectedOutputs[i].AsOsAgnostic()).Should().Be(true);
}
}
[Test]
public void should_import_multiple_subtitle_files_per_language_with_tags()
{
var files = new List<string>
{
Path.Combine(_releaseFolder, "Movie.Title.2022.en.forced.cc.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.other.en.forced.cc.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.en.forced.sdh.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.en.forced.default.srt").AsOsAgnostic(),
};
var expectedOutputs = new[]
{
"Movie Title - 2022.1.en.forced.cc.srt",
"Movie Title - 2022.2.en.forced.cc.srt",
"Movie Title - 2022.en.forced.sdh.srt",
"Movie Title - 2022.en.forced.default.srt"
};
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(expectedOutputs.Length);
for (int i = 0; i < expectedOutputs.Length; i++)
{
results[i].RelativePath.AsOsAgnostic().PathEquals(expectedOutputs[i].AsOsAgnostic()).Should().Be(true);
}
}
[Test]
[TestCase(@"Subs\2_en.srt", "Movie Title - 2022.en.srt")]
public void should_import_unmatching_subtitle_file_if_only_episode(string filePath, string expectedOutputPath)
{
var subtitleFile = Path.Combine(_releaseFolder, filePath).AsOsAgnostic();
var sampleFile = Path.Combine(_movie.Path, "Movie Title - 2022.sample.mkv").AsOsAgnostic();
var videoFiles = new string[]
{
_localMovie.Path,
sampleFile
};
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(videoFiles);
Mocker.GetMock<IDetectSample>().Setup(s => s.IsSample(It.IsAny<MovieMetadata>(), sampleFile))
.Returns(DetectSampleResult.Sample);
var results = Subject.ImportFiles(_localMovie, _movieFile, new List<string> { subtitleFile }, true).ToList();
results.Count().Should().Be(1);
results[0].RelativePath.AsOsAgnostic().PathEquals(expectedOutputPath.AsOsAgnostic()).Should().Be(true);
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
}
[Test]
public void should_not_delete_unorphaned_collection_items()
public void should_delete_orphaned_collection_with_meta_but_no_movie_items()
{
var collection = Builder<MovieCollection>.CreateNew()
.With(h => h.Id = 3)
@@ -40,6 +40,27 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
Db.Insert(movie);
Subject.Clean();
AllStoredModels.Should().HaveCount(0);
}
[Test]
public void should_not_delete_unorphaned_collection()
{
var collection = Builder<MovieCollection>.CreateNew()
.With(h => h.Id = 3)
.With(h => h.TmdbId = 123456)
.With(h => h.Title = "Some Credit")
.BuildNew();
Db.Insert(collection);
var movieMeta = Builder<MovieMetadata>.CreateNew().With(m => m.CollectionTmdbId = collection.TmdbId).BuildNew();
Db.Insert(movieMeta);
var movie = Builder<Movie>.CreateNew().With(m => m.MovieMetadataId = movieMeta.Id).BuildNew();
Db.Insert(movie);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}

View File

@@ -47,6 +47,8 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 32, Language.Ukrainian },
new object[] { 33, Language.Persian },
new object[] { 34, Language.Bengali },
new object[] { 35, Language.Slovak },
new object[] { 36, Language.Latvian },
};
public static object[] ToIntCases =
@@ -88,6 +90,8 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Ukrainian, 32 },
new object[] { Language.Persian, 33 },
new object[] { Language.Bengali, 34 },
new object[] { Language.Slovak, 35 },
new object[] { Language.Latvian, 36 },
};
[Test]

View File

@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
[Test]
public void should_return_default_if_no_info_is_known()
{
var result = Subject.Aggregate(_localMovie, null, false);
var result = Subject.Aggregate(_localMovie, null);
result.Languages.Should().Contain(_movie.MovieMetadata.Value.OriginalLanguage);
}
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
null,
null);
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.French });
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.French });
}
[Test]
@@ -94,7 +94,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
null,
null);
var aggregation = Subject.Aggregate(_localMovie, null, false);
var aggregation = Subject.Aggregate(_localMovie, null);
aggregation.Languages.Should().Equal(new List<Language> { Language.German });
}
@@ -107,7 +107,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
new List<Language> { Language.Spanish },
null);
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.Spanish });
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.Spanish });
}
[Test]
@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
new List<Language> { Language.Unknown },
null);
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
}
[Test]
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
new List<Language> { Language.Unknown },
new List<Language> { Language.Japanese, Language.English });
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.Japanese, Language.English });
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.Japanese, Language.English });
}
[Test]
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
new List<Language> { Language.Unknown },
new List<Language> { Language.Unknown });
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
}
}
}

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
GivenAugmenters(_fileExtensionAugmenter, nullMock);
var result = Subject.Aggregate(new LocalMovie(), null, false);
var result = Subject.Aggregate(new LocalMovie(), null);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Extension);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Extension);
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
GivenAugmenters(_fileExtensionAugmenter, _nameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), null, false);
var result = Subject.Aggregate(new LocalMovie(), null);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
GivenAugmenters(_fileExtensionAugmenter, _mediaInfoAugmenter);
var result = Subject.Aggregate(new LocalMovie(), null, false);
var result = Subject.Aggregate(new LocalMovie(), null);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Extension);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
GivenAugmenters(_nameAugmenter, _mediaInfoAugmenter);
var result = Subject.Aggregate(new LocalMovie(), null, false);
var result = Subject.Aggregate(new LocalMovie(), null);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
{
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
result.Quality.Revision.Version.Should().Be(1);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Unknown);
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
result.Quality.Revision.Version.Should().Be(2);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
result.Quality.Revision.Version.Should().Be(0);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
result.Quality.Revision.Version.Should().Be(2);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);

View File

@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
Movie = _movie
};
Subject.Aggregate(localMovie, null, false);
Subject.Aggregate(localMovie, null);
localMovie.ReleaseGroup.Should().Be("Viva");
}
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
Movie = _movie
};
Subject.Aggregate(localMovie, null, false);
Subject.Aggregate(localMovie, null);
localMovie.ReleaseGroup.Should().Be("Drone");
}
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
Movie = _movie
};
Subject.Aggregate(localMovie, null, false);
Subject.Aggregate(localMovie, null);
localMovie.ReleaseGroup.Should().Be("Wizzy");
}
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
Movie = _movie
};
Subject.Aggregate(localMovie, null, false);
Subject.Aggregate(localMovie, null);
localMovie.ReleaseGroup.Should().Be("FraMeSToR");
}
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
Movie = _movie
};
Subject.Aggregate(localMovie, null, false);
Subject.Aggregate(localMovie, null);
localMovie.ReleaseGroup.Should().Be("FraMeSToR");
}

View File

@@ -0,0 +1,153 @@
using System.Collections.Generic;
using System.IO;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MovieImport;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MovieImport
{
[TestFixture]
public class GetSceneNameFixture : CoreTest
{
private LocalMovie _localMovie;
private string _movieName = "movie.title.2022.dvdrip.x264-ingot";
[SetUp]
public void Setup()
{
var movie = Builder<Movie>.CreateNew()
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(s => s.Path = @"C:\Test\Movies\Movie Title".AsOsAgnostic())
.Build();
_localMovie = new LocalMovie
{
Movie = movie,
Path = Path.Combine(movie.Path, "Movie Title - 2022 - Episode Title.mkv"),
Quality = new QualityModel(Quality.Bluray720p),
ReleaseGroup = "DRONE"
};
}
private void GivenExistingFileOnDisk()
{
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
.Returns(new List<MovieFile>());
}
[Test]
public void should_use_download_client_item_title_as_scene_name()
{
_localMovie.DownloadClientMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = _movieName
};
SceneNameCalculator.GetSceneName(_localMovie).Should()
.Be(_movieName);
}
[Test]
public void should_not_use_download_client_item_title_as_scene_name_if_there_are_other_video_files()
{
_localMovie.OtherVideoFiles = true;
_localMovie.DownloadClientMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = _movieName
};
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName)
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localMovie).Should()
.BeNull();
}
[Test]
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
{
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName + ".mkv")
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localMovie).Should()
.Be(_movieName);
}
[Test]
public void should_not_use_file_name_as_scenename_if_it_doesnt_look_like_scenename()
{
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName, "aaaaa.mkv")
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localMovie).Should()
.BeNull();
}
[Test]
public void should_use_folder_name_as_scenename_only_if_it_looks_like_scenename()
{
_localMovie.FolderMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = _movieName
};
SceneNameCalculator.GetSceneName(_localMovie).Should()
.Be(_movieName);
}
[Test]
public void should_not_use_folder_name_as_scenename_if_it_doesnt_look_like_scenename()
{
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName, "aaaaa.mkv")
.AsOsAgnostic();
_localMovie.FolderMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = "aaaaa"
};
SceneNameCalculator.GetSceneName(_localMovie).Should()
.BeNull();
}
[Test]
public void should_not_use_folder_name_as_scenename_if_there_are_other_video_files()
{
_localMovie.OtherVideoFiles = true;
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName, "aaaaa.mkv")
.AsOsAgnostic();
_localMovie.FolderMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = _movieName
};
SceneNameCalculator.GetSceneName(_localMovie).Should()
.BeNull();
}
[TestCase(".mkv")]
[TestCase(".par2")]
[TestCase(".nzb")]
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
{
_localMovie.DownloadClientMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = _movieName + extension
};
SceneNameCalculator.GetSceneName(_localMovie).Should()
.Be(_movieName);
}
}
}

View File

@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IEventAggregator>()
.Verify(v => v.PublishEvent(It.IsAny<MovieImportedEvent>()), Times.Once());
.Verify(v => v.PublishEvent(It.IsAny<MovieFileImportedEvent>()), Times.Once());
}
[Test]
@@ -159,84 +159,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Times.Never());
}
[Test]
public void should_use_nzb_title_as_scene_name()
{
GivenNewDownload();
_downloadClientItem.Title = "malcolm.in.the.middle.2015.dvdrip.xvid-ingot";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == _downloadClientItem.Title)));
}
[TestCase(".mkv")]
[TestCase(".par2")]
[TestCase(".nzb")]
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
{
GivenNewDownload();
var title = "malcolm.in.the.middle.2015.dvdrip.xvid-ingot";
_downloadClientItem.Title = title + extension;
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == title)));
}
[Test]
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "movie.title.2018.dvdrip.xvid-ingot.mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == "movie.title.2018.dvdrip.xvid-ingot")));
}
[Test]
public void should_not_use_file_name_as_scenename_if_it_doesnt_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == null)));
}
[Test]
public void should_use_folder_name_as_scenename_only_if_it_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
_approvedDecisions.First().LocalMovie.FolderMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = "movie.title.2018.dvdrip.xvid-ingot"
};
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == "movie.title.2018.dvdrip.xvid-ingot")));
}
[Test]
public void should_not_use_folder_name_as_scenename_if_it_doesnt_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
_approvedDecisions.First().LocalMovie.FolderMovieInfo = new ParsedMovieInfo
{
ReleaseTitle = "aaaaa.mkv"
};
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == null)));
}
[Test]
public void should_import_larger_files_first()
{
@@ -411,5 +333,18 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.OriginalFilePath == $"subfolder\\{name}.mkv".AsOsAgnostic())));
}
[Test]
public void should_include_scene_name_with_new_downloads()
{
var firstDecision = _approvedDecisions.First();
firstDecision.LocalMovie.SceneName = "Movie.Title.2022.dvdrip-DRONE";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeMovieFile(It.Is<MovieFile>(e => e.SceneName == firstDecision.LocalMovie.SceneName), _approvedDecisions.First().LocalMovie, false),
Times.Once());
}
}
}

View File

@@ -102,8 +102,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
private void GivenAugmentationSuccess()
{
Mocker.GetMock<IAggregationService>()
.Setup(s => s.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Callback<LocalMovie, DownloadClientItem, bool>((localMovie, downloadClientItem, otherFiles) =>
.Setup(s => s.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
.Callback<LocalMovie, DownloadClientItem>((localMovie, downloadClientItem) =>
{
localMovie.Movie = _localMovie.Movie;
});
@@ -173,7 +173,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
GivenSpecifications(_pass1);
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
.Throws<TestException>();
_videoFiles = new List<string>
@@ -188,7 +188,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.GetImportDecisions(_videoFiles, _movie);
Mocker.GetMock<IAggregationService>()
.Verify(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
.Verify(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()), Times.Exactly(_videoFiles.Count));
ExceptionVerification.ExpectedErrors(3);
}
@@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
var fileNames = _videoFiles.Select(System.IO.Path.GetFileName);
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
.Throws<TestException>();
}
@@ -217,7 +217,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
public void should_return_a_decision_when_exception_is_caught()
{
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
.Throws<TestException>();
_videoFiles = new List<string>

View File

@@ -11,6 +11,7 @@ using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Credits;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@@ -52,6 +53,10 @@ namespace NzbDrone.Core.Test.MovieTests
Mocker.GetMock<IProvideMovieInfo>()
.Setup(s => s.GetMovieInfo(It.IsAny<int>()))
.Callback<int>((i) => { throw new MovieNotFoundException(i); });
Mocker.GetMock<IRootFolderService>()
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
.Returns(string.Empty);
}
private void GivenNewMovieInfo(MovieMetadata movie)

View File

@@ -0,0 +1,67 @@
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 EditionTagsFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private MovieFile _movieFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_movieFile = new MovieFile { Quality = new QualityModel(), ReleaseGroup = "SonarrTest" };
_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_add_edition_tag()
{
_movieFile.Edition = "Uncut";
_namingConfig.StandardMovieFormat = "{Movie Title} [{Edition Tags}]";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South Park [Uncut]");
}
[TestCase("{Movie Title} {edition-{Edition Tags}}")]
public void should_conditional_hide_edition_tags_in_plex_format(string movieFormat)
{
_movieFile.Edition = "";
_namingConfig.StandardMovieFormat = movieFormat;
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South Park");
}
}
}

View File

@@ -20,9 +20,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[Platform(Exclude = "Win")]
[TestFixture]
public class FileNameBuilderFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
@@ -427,7 +425,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be("South.Park.H264.DTS.[EN+ES+IT]");
}
[Ignore("not currently supported")]
[Test]
public void should_format_mediainfo_3d_properly()
{
@@ -671,6 +668,27 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Should().Be(expected);
}
[TestCase("eng/deu", "", "[EN+DE]")]
[TestCase("eng/nld/deu", "", "[EN+NL+DE]")]
[TestCase("eng/deu", ":DE", "[DE]")]
[TestCase("eng/nld/deu", ":EN+NL", "[EN+NL]")]
[TestCase("eng/nld/deu", ":NL+EN", "[NL+EN]")]
[TestCase("eng/nld/deu", ":-NL", "[EN+DE]")]
[TestCase("eng/nld/deu", ":DE+", "[DE+-]")]
[TestCase("eng/nld/deu", ":DE+NO.", "[DE].")]
[TestCase("eng/nld/deu", ":-EN-", "[NL+DE]-")]
public void should_format_subtitle_languages_all(string subtitleLanguages, string format, string expected)
{
_movieFile.ReleaseGroup = null;
GivenMediaInfoModel(subtitles: subtitleLanguages);
_namingConfig.StandardMovieFormat = "{MediaInfo SubtitleLanguages" + format + "}End";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(expected + "End");
}
[TestCase(HdrFormat.None, "South.Park")]
[TestCase(HdrFormat.Hlg10, "South.Park.HDR")]
[TestCase(HdrFormat.Hdr10, "South.Park.HDR")]

View File

@@ -1,4 +1,4 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework.Internal;
@@ -47,5 +47,25 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Subject.GetMovieFolder(_movie)
.Should().Be($"Movie Title ({_movie.TmdbId})");
}
[Test]
public void should_add_imdb_tag()
{
_namingConfig.MovieFolderFormat = "{Movie Title} {imdb-{ImdbId}}";
Subject.GetMovieFolder(_movie)
.Should().Be($"Movie Title {{imdb-{_movie.ImdbId}}}");
}
[Test]
public void should_skip_imdb_tag_if_null()
{
_namingConfig.MovieFolderFormat = "{Movie Title} {imdb-{ImdbId}}";
_movie.ImdbId = null;
Subject.GetMovieFolder(_movie)
.Should().Be($"Movie Title");
}
}
}

View File

@@ -0,0 +1,23 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class AnimeVersionFixture : CoreTest
{
[TestCase("Anime Title - 2018 - (BD 1080p HEVC FLAC) [Dual Audio] [Group]", 1)]
[TestCase("Anime Title - 2018v2 - (BD 1080p HEVC FLAC) [Dual Audio] [Group]", 2)]
[TestCase("Anime Title - 2018 v2 - (BD 1080p HEVC FLAC) [Dual Audio] [Group]", 2)]
[TestCase("[SubsPlease] Anime Title - 01 (1080p) [B1F227CF]", 1)]
[TestCase("[SubsPlease] Anime Title - 01v2 (1080p) [B1F227CF]", 2)]
[TestCase("[SubsPlease] Anime Title - 01 v2 (1080p) [B1F227CF]", 2)]
public void should_be_able_to_parse_repack(string title, int version)
{
var result = QualityParser.ParseQuality(title);
result.Revision.Version.Should().Be(version);
}
}
}

View File

@@ -27,6 +27,22 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Unknown);
}
[TestCase("Movie Title - 2022.en.sub")]
[TestCase("Movie Title - 2022.EN.sub")]
[TestCase("Movie Title - 2022.eng.sub")]
[TestCase("Movie Title - 2022.ENG.sub")]
[TestCase("Movie Title - 2022.English.sub")]
[TestCase("Movie Title - 2022.english.sub")]
[TestCase("Movie Title - 2022.en.cc.sub")]
[TestCase("Movie Title - 2022.en.sdh.sub")]
[TestCase("Movie Title - 2022.en.forced.sub")]
[TestCase("Movie Title - 2022.en.sdh.forced.sub")]
public void should_parse_subtitle_language_english(string fileName)
{
var result = LanguageParser.ParseSubtitleLanguage(fileName);
result.Should().Be(Language.English);
}
[TestCase("Movie.Title.1994.French.1080p.XviD-LOL")]
[TestCase("Movie Title : Other Title 2011 AVC.1080p.Blu-ray HD.VOSTFR.VFF")]
[TestCase("Movie Title - Other Title 2011 Bluray 4k HDR HEVC AC3 VFF")]
@@ -55,6 +71,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Title (2020)[BDRemux AVC 1080p][E-AC3 DD Plus 5.1 Castellano-Inglés Subs]")]
[TestCase("Movie Title (2020) [UHDRemux2160p HDR][DTS-HD MA 5.1 AC3 5.1 Castellano - True-HD 7.1 Atmos Inglés Subs]")]
[TestCase("Movie Title (2016) [UHDRemux 2160p SDR] [Castellano DD 5.1 - Inglés DTS-HD MA 5.1 Subs]")]
[TestCase("Movie Title 2022 [HDTV 720p][Cap.101][AC3 5.1 Castellano][www.pctnew.ORG]")]
[TestCase("Movie Title 2022 [HDTV 720p][Cap.206][AC3 5.1 Español Castellano]")]
public void should_parse_language_spanish(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
@@ -342,6 +360,26 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Bengali);
}
[TestCase("Movie.Title.1994.HDTV.x264.SK-iCZi")]
[TestCase("Movie.Title.2019.1080p.HDTV.x265.iNTERNAL.SK-iCZi")]
[TestCase("Movie.Title.2018.SLOVAK.DUAL.2160p.UHD.BluRay.x265-iCZi")]
[TestCase("Movie.Title.1990.SLOVAK.HDTV.x264-iCZi")]
public void should_parse_language_slovak(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Languages.Should().BeEquivalentTo(Language.Slovak);
}
[TestCase("Movie.Title.2022.LV.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.lv.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.LATVIAN.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.Latvian.WEBRip.XviD-LOL")]
public void should_parse_language_latvian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Languages.Should().BeEquivalentTo(Language.Latvian);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

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