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

Compare commits

..

118 Commits

Author SHA1 Message Date
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
147 changed files with 5312 additions and 1796 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

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.0'
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'
@@ -549,7 +549,7 @@ stages:
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: 'ubuntu-18.04'
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
@@ -576,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: |
@@ -686,7 +687,7 @@ stages:
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: 'ubuntu-18.04'
vmImage: ${{ variables.linuxImage }}
steps:
- task: UseDotNet@2
@@ -721,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: |

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

@@ -181,13 +181,12 @@ class Blocklist extends Component {
>
<TableBody>
{
items.map((item, index) => {
items.map((item) => {
return (
<BlocklistRowConnector
key={item.id}
isSelected={selectedState[item.id] || false}
columns={columns}
index={index}
{...item}
onSelectedChange={this.onSelectedChange}
/>

View File

@@ -66,7 +66,8 @@ class CollectionFooter extends Component {
monitor,
monitored,
qualityProfileId,
minimumAvailability
minimumAvailability,
rootFolderPath
} = this.state;
const changes = {};
@@ -87,6 +88,10 @@ class CollectionFooter extends Component {
changes.minimumAvailability = minimumAvailability;
}
if (rootFolderPath !== NO_CHANGE) {
changes.rootFolderPath = rootFolderPath;
}
this.props.onUpdateSelectedPress(changes);
};

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,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,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

@@ -15,13 +15,12 @@
.tmdbId,
.movieYear {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
flex: 0 0 70px;
}
.actions {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 70px;
display: flex;
justify-content: flex-end;
flex: 1 0 auto;
padding-right: 10px;
}

View File

@@ -1,16 +1,13 @@
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 ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportListExclusion.css';
import IconButton from 'Components/Link/IconButton';
class ImportListExclusion extends Component {
@@ -58,82 +55,28 @@ class ImportListExclusion extends Component {
render() {
const {
id,
isSelected,
onSelectedChange,
columns,
movieTitle,
tmdbId,
movieYear
} = this.props;
return (
<TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
<div
className={classNames(
styles.importExclusion
)}
>
<div className={styles.tmdbId}>{tmdbId}</div>
<div className={styles.movieTitle}>{movieTitle}</div>
<div className={styles.movieYear}>{movieYear}</div>
{
columns.map((column) => {
const {
name,
isVisible
} = column;
if (!isVisible) {
return null;
}
if (name === 'tmdbId') {
return (
<TableRowCell key={name}>
{tmdbId}
</TableRowCell>
);
}
if (name === 'movieTitle') {
return (
<TableRowCell key={name}>
{movieTitle}
</TableRowCell>
);
}
if (name === 'movieYear') {
return (
<TableRowCell key={name}>
{movieYear}
</TableRowCell>
);
}
if (name === 'actions') {
return (
<TableRowCell
key={name}
className={styles.actions}
>
<IconButton
title={translate('RemoveFromBlocklist')}
name={icons.EDIT}
onPress={this.onEditImportExclusionPress}
/>
<IconButton
title={translate('RemoveFromBlocklist')}
name={icons.REMOVE}
kind={kinds.DANGER}
onPress={this.onDeleteImportExclusionPress}
/>
</TableRowCell>
);
}
return null;
})
}
<div className={styles.actions}>
<Link
onPress={this.onEditImportExclusionPress}
>
<Icon name={icons.EDIT} />
</Link>
</div>
<EditImportListExclusionModalConnector
id={id}
@@ -151,7 +94,7 @@ class ImportListExclusion extends Component {
onConfirm={this.onConfirmDeleteImportExclusion}
onCancel={this.onDeleteImportExclusionModalClose}
/>
</TableRow>
</div>
);
}
}
@@ -161,9 +104,6 @@ ImportListExclusion.propTypes = {
movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired,
movieYear: PropTypes.number.isRequired,
isSelected: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};

View File

@@ -4,12 +4,8 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion';
import styles from './ImportListExclusions.css';
@@ -23,10 +19,6 @@ class ImportListExclusions extends Component {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {},
isAddImportExclusionModalOpen: false
};
}
@@ -34,16 +26,6 @@ class ImportListExclusions extends Component {
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
};
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
};
onAddImportExclusionPress = () => {
this.setState({ isAddImportExclusionModalOpen: true });
};
@@ -59,50 +41,41 @@ class ImportListExclusions extends Component {
const {
items,
onConfirmDeleteImportExclusion,
columns,
...otherProps
} = this.props;
const {
allSelected,
allUnselected,
selectedState
} = this.state;
return (
<FieldSet legend={translate('ListExclusions')}>
<PageSectionContent
errorMessage={translate('UnableToLoadListExclusions')}
{...otherProps}
>
<div className={styles.importListExclusionsHeader}>
<div className={styles.tmdbId}>
TMDb Id
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
</div>
<div>
<Table
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns}
{...otherProps}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>
{
items.map((item, index) => {
return (
<ImportListExclusion
key={item.id}
isSelected={selectedState[item.id] || false}
{...item}
{...otherProps}
columns={columns}
index={index}
onSelectedChange={this.onSelectedChange}
onConfirmDeleteImportExclusion={onConfirmDeleteImportExclusion}
/>
);
})
}
</TableBody>
</Table>
{
items.map((item, index) => {
return (
<ImportListExclusion
key={item.id}
{...item}
{...otherProps}
index={index}
onConfirmDeleteImportExclusion={onConfirmDeleteImportExclusion}
/>
);
})
}
</div>
<div className={styles.addImportExclusion}>
@@ -128,7 +101,6 @@ class ImportListExclusions extends Component {
ImportListExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};

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

@@ -4,7 +4,6 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
import translate from 'Utilities/String/translate';
//
// Variables
@@ -49,34 +48,7 @@ export default {
items: [],
isSaving: false,
saveError: null,
pendingChanges: {},
columns: [
{
name: 'tmdbId',
label: 'TmdbId',
isSortable: true,
isVisible: true
},
{
name: 'movieTitle',
label: translate('Title'),
isSortable: true,
isVisible: true
},
{
name: 'movieYear',
label: translate('Year'),
isSortable: true,
isVisible: true
},
{
name: 'actions',
columnLabel: translate('Actions'),
isVisible: true,
isModifiable: false
}
]
pendingChanges: {}
},
//

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

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

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
}
]
};

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

@@ -227,7 +227,7 @@ export const defaultState = {
collection: function(item) {
const { collection ={} } = item;
return collection.name;
return collection.title;
},
originalLanguage: function(item) {
@@ -339,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
});
}

View File

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

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",
@@ -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

@@ -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""}")]
@@ -84,9 +85,24 @@ namespace NzbDrone.Common.Test.InstrumentationTests
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

@@ -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"),

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

@@ -10,10 +10,10 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.15.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

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

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

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

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

@@ -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")]
@@ -342,6 +358,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")]

View File

@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("A.I.Artificial.Movie.(2001)", "A.I. Artificial Movie")]
[TestCase("A.Movie.Name.(1998)", "A Movie Name")]
[TestCase("www.Torrenting.com - Movie.2008.720p.X264-DIMENSION", "Movie")]
[TestCase("www.5MovieRulz.tc - Movie (2000) Malayalam HQ HDRip - x264 - AAC - 700MB.mkv", "Movie")]
[TestCase("Movie: The Movie World 2013", "Movie: The Movie World")]
[TestCase("Movie.The.Final.Chapter.2016", "Movie The Final Chapter")]
[TestCase("Der.Movie.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", "Der Movie James")]

View File

@@ -41,6 +41,14 @@ namespace NzbDrone.Core.Test.ParserTests
ParseAndVerifyQuality(title, Source.TELESYNC, proper, Resolution.R720p);
}
[TestCase("Movie Name 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)]
[TestCase("Movie Name (2018) 720p Hindi HQ CAMrip x264 AAC 1.4GB", false)]
public void should_parse_cam(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
}
[TestCase("S07E23 .avi ", false)]
[TestCase("Movie Name S02E01 HDTV XviD 2HD", false)]
[TestCase("Movie Name S05E11 PROPER HDTV XviD 2HD", true)]

View File

@@ -50,6 +50,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("The.Movie.Title.2013.720p.BluRay.x264-ROUGH [PublicHD]", "ROUGH")]
[TestCase("Some.Really.Bad.Movie.Title.[2021].1080p.WEB-HDRip.Dual.Audio.[Hindi.[Clean]. .English].x264.AAC.DD.2.0.By.Full4Movies.mkv-xpost", null)]
[TestCase("The.Movie.Title.2013.1080p.10bit.AMZN.WEB-DL.DDP5.1.HEVC-Vyndros", "Vyndros")]
[TestCase("Movie.Name.2022.1080p.BluRay.x264-[YTS.AG]", "YTS.AG")]
public void should_parse_expected_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
@@ -100,6 +101,11 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Yet Another Anime Movie 2012 [Kametsu] [Blu-ray][MKV][h264 10-bit][1080p][FLAC 5.1][Dual Audio][Softsubs (Kametsu)]", "Kametsu")]
[TestCase("Another.Anime.Film.Name.2016.JPN.Blu-Ray.Remux.AVC.DTS-MA.BluDragon", "BluDragon")]
[TestCase("A Movie in the Name (1964) (1080p BluRay x265 r00t)", "r00t")]
[TestCase("Movie Title (2022) (2160p ATV WEB-DL Hybrid H265 DV HDR DDP Atmos 5.1 English - HONE)", "HONE")]
[TestCase("Movie Title (2009) (2160p PMTP WEB-DL Hybrid H265 DV HDR10+ DDP Atmos 5.1 English - HONE)", "HONE")]
[TestCase("Why.Cant.You.Use.Normal.Characters.2021.2160p.UHD.HDR10+.BluRay.TrueHD.Atmos.7.1.x265-ZØNEHD", "ZØNEHD")]
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
[TestCase("Movie.Title.2005.2160p.UHD.BluRay.TrueHD 7.1.Atmos.x265 - HQMUX", "HQMUX")]
public void should_parse_exception_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);

View File

@@ -0,0 +1,161 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class SlugParserFixture : CoreTest
{
[TestCase("tèst", "test")]
[TestCase("têst", "test")]
[TestCase("tëst", "test")]
[TestCase("tËst", "test")]
[TestCase("áccent", "accent")]
[TestCase("àccent", "accent")]
[TestCase("âccent", "accent")]
[TestCase("Äccent", "accent")]
[TestCase("åccent", "accent")]
[TestCase("acceñt", "accent")]
[TestCase("ßtest", "test")]
[TestCase("œtest", "test")]
[TestCase("Œtest", "test")]
[TestCase("Øtest", "test")]
public void should_replace_accents(string input, string result)
{
Parser.Parser.ToUrlSlug(input).Should().Be(result);
}
[TestCase("Test'Result")]
[TestCase("Test$Result")]
[TestCase("Test(Result")]
[TestCase("Test)Result")]
[TestCase("Test*Result")]
[TestCase("Test?Result")]
[TestCase("Test/Result")]
[TestCase("Test=Result")]
[TestCase("Test\\Result")]
public void should_replace_special_characters(string input)
{
Parser.Parser.ToUrlSlug(input).Should().Be("testresult");
}
[TestCase("ThIS IS A MiXeD CaSe SensItIvE ValUe")]
public void should_lowercase_capitals(string input)
{
Parser.Parser.ToUrlSlug(input).Should().Be("this-is-a-mixed-case-sensitive-value");
}
[TestCase("test----")]
[TestCase("test____")]
[TestCase("test-_--_")]
public void should_trim_trailing_dashes_and_underscores(string input)
{
Parser.Parser.ToUrlSlug(input).Should().Be("test");
}
[TestCase("test result")]
[TestCase("test result")]
public void should_replace_spaces_with_dash(string input)
{
Parser.Parser.ToUrlSlug(input).Should().Be("test-result");
}
[TestCase("test result", "test-result")]
[TestCase("test-----result", "test-result")]
[TestCase("test_____result", "test_result")]
public void should_replace_double_occurence(string input, string result)
{
Parser.Parser.ToUrlSlug(input).Should().Be(result);
}
[TestCase("Test'Result")]
[TestCase("Test$Result")]
[TestCase("Test(Result")]
[TestCase("Test)Result")]
[TestCase("Test*Result")]
[TestCase("Test?Result")]
[TestCase("Test/Result")]
[TestCase("Test=Result")]
[TestCase("Test\\Result")]
public void should_replace_special_characters_with_dash_when_enabled(string input)
{
Parser.Parser.ToUrlSlug(input, true).Should().Be("test-result");
}
[TestCase("Test'Result")]
[TestCase("Test$Result")]
[TestCase("Test(Result")]
[TestCase("Test)Result")]
[TestCase("Test*Result")]
[TestCase("Test?Result")]
[TestCase("Test/Result")]
[TestCase("Test=Result")]
[TestCase("Test\\Result")]
public void should__not_replace_special_characters_with_dash_when_disabled(string input)
{
Parser.Parser.ToUrlSlug(input, false).Should().Be("testresult");
}
[TestCase("test----", "-_", "test")]
[TestCase("test____", "-_", "test")]
[TestCase("test-_-_", "-_", "test")]
[TestCase("test----", "-", "test")]
[TestCase("test____", "-", "test____")]
[TestCase("test-_-_", "-", "test-_-_")]
[TestCase("test----", "_", "test----")]
[TestCase("test____", "_", "test")]
[TestCase("test-_-_", "_", "test-_-")]
[TestCase("test----", "", "test----")]
[TestCase("test____", "", "test____")]
[TestCase("test-_-_", "", "test-_-_")]
public void should_trim_trailing_dashes_and_underscores_based_on_list(string input, string trimList, string result)
{
Parser.Parser.ToUrlSlug(input, false, trimList, "").Should().Be(result);
}
[TestCase("test----result", "-_", "test-result")]
[TestCase("test____result", "-_", "test_result")]
[TestCase("test_-_-result", "-_", "test-result")]
[TestCase("test-_-_result", "-_", "test_result")]
[TestCase("test----result", "-", "test-result")]
[TestCase("test____result", "-", "test____result")]
[TestCase("test-_-_result", "-", "test-_-_result")]
[TestCase("test----result", "_", "test----result")]
[TestCase("test____result", "_", "test_result")]
[TestCase("test-_-_result", "_", "test-_-_result")]
[TestCase("test----result", "", "test----result")]
[TestCase("test____result", "", "test____result")]
[TestCase("test-_-_result", "", "test-_-_result")]
public void should_replace_duplicate_characters_based_on_list(string input, string deduplicateChars, string result)
{
Parser.Parser.ToUrlSlug(input, false, "", deduplicateChars).Should().Be(result);
}
[Test]
public void should_handle_null_trim_parameters()
{
Parser.Parser.ToUrlSlug("test", false, null, "-_").Should().Be("test");
}
[Test]
public void should_handle_null_dedupe_parameters()
{
Parser.Parser.ToUrlSlug("test", false, "-_", null).Should().Be("test");
}
[Test]
public void should_handle_empty_trim_parameters()
{
Parser.Parser.ToUrlSlug("test", false, "", "-_").Should().Be("test");
}
[Test]
public void should_handle_empty_dedupe_parameters()
{
Parser.Parser.ToUrlSlug("test", false, "-_", "").Should().Be("test");
}
}
}

View File

@@ -8,6 +8,7 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Movies;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework;
@@ -151,5 +152,107 @@ namespace NzbDrone.Core.Test.RootFolderTests
unmappedFolders.Count.Should().BeGreaterThan(0);
unmappedFolders.Should().NotContain(u => u.Name == subFolder);
}
[TestCase("")]
[TestCase(null)]
public void should_handle_non_configured_recycle_bin(string recycleBinPath)
{
var rootFolder = Builder<RootFolder>.CreateNew()
.With(r => r.Path = @"C:\Test\TV")
.Build();
if (OsInfo.IsNotWindows)
{
rootFolder = Builder<RootFolder>.CreateNew()
.With(r => r.Path = @"/Test/TV")
.Build();
}
var subFolders = new[]
{
"Series1",
"Series2",
"Series3"
};
var folders = subFolders.Select(f => Path.Combine(@"C:\Test\TV", f)).ToArray();
if (OsInfo.IsNotWindows)
{
folders = subFolders.Select(f => Path.Combine(@"/Test/TV", f)).ToArray();
}
Mocker.GetMock<IConfigService>()
.Setup(s => s.RecycleBin)
.Returns(recycleBinPath);
Mocker.GetMock<IRootFolderRepository>()
.Setup(s => s.Get(It.IsAny<int>()))
.Returns(rootFolder);
Mocker.GetMock<IMovieRepository>()
.Setup(s => s.AllMoviePaths())
.Returns(new Dictionary<int, string>());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetDirectories(rootFolder.Path))
.Returns(folders);
var unmappedFolders = Subject.Get(rootFolder.Id, true).UnmappedFolders;
unmappedFolders.Count.Should().Be(3);
}
[Test]
public void should_exclude_recycle_bin()
{
var rootFolder = Builder<RootFolder>.CreateNew()
.With(r => r.Path = @"C:\Test\TV")
.Build();
if (OsInfo.IsNotWindows)
{
rootFolder = Builder<RootFolder>.CreateNew()
.With(r => r.Path = @"/Test/TV")
.Build();
}
var subFolders = new[]
{
"Series1",
"Series2",
"Series3",
"BIN"
};
var folders = subFolders.Select(f => Path.Combine(@"C:\Test\TV", f)).ToArray();
if (OsInfo.IsNotWindows)
{
folders = subFolders.Select(f => Path.Combine(@"/Test/TV", f)).ToArray();
}
var recycleFolder = Path.Combine(OsInfo.IsNotWindows ? @"/Test/TV" : @"C:\Test\TV", "BIN");
Mocker.GetMock<IConfigService>()
.Setup(s => s.RecycleBin)
.Returns(recycleFolder);
Mocker.GetMock<IRootFolderRepository>()
.Setup(s => s.Get(It.IsAny<int>()))
.Returns(rootFolder);
Mocker.GetMock<IMovieRepository>()
.Setup(s => s.AllMoviePaths())
.Returns(new Dictionary<int, string>());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetDirectories(rootFolder.Path))
.Returns(folders);
var unmappedFolders = Subject.Get(rootFolder.Id, true).UnmappedFolders;
unmappedFolders.Count.Should().Be(3);
unmappedFolders.Should().NotContain(u => u.Name == "BIN");
}
}
}

View File

@@ -428,6 +428,8 @@ namespace NzbDrone.Core.Configuration
public CertificateValidationType CertificateValidation =>
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
private string GetValue(string key)
{
return GetValue(key, string.Empty);

View File

@@ -104,5 +104,6 @@ namespace NzbDrone.Core.Configuration
int BackupRetention { get; }
CertificateValidationType CertificateValidation { get; }
string ApplicationUrl { get; }
}
}

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
@@ -18,6 +19,8 @@ namespace NzbDrone.Core.CustomFormats
return (ICustomFormatSpecification)MemberwiseClone();
}
public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo)
{
var match = IsSatisfiedByWithoutNegate(movieInfo);

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
@@ -11,6 +12,8 @@ namespace NzbDrone.Core.CustomFormats
bool Negate { get; set; }
bool Required { get; set; }
NzbDroneValidationResult Validate();
ICustomFormatSpecification Clone();
bool IsSatisfiedBy(ParsedMovieInfo movieInfo);
}

View File

@@ -1,11 +1,31 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class IndexerFlagSpecificationValidator : AbstractValidator<IndexerFlagSpecification>
{
public IndexerFlagSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((qualityValue, context) =>
{
if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue))
{
context.AddFailure(string.Format("Invalid indexer flag condition value: {0}", qualityValue));
}
});
}
}
public class IndexerFlagSpecification : CustomFormatSpecificationBase
{
private static readonly IndexerFlagSpecificationValidator Validator = new IndexerFlagSpecificationValidator();
public override int Order => 4;
public override string ImplementationName => "Indexer Flag";
@@ -17,5 +37,10 @@ namespace NzbDrone.Core.CustomFormats
var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?;
return flags?.HasFlag((IndexerFlags)Value) == true;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,31 @@
using System.Linq;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class LanguageSpecificationValidator : AbstractValidator<LanguageSpecification>
{
public LanguageSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((value, context) =>
{
if (!Language.All.Any(o => o.Id == value))
{
context.AddFailure(string.Format("Invalid Language condition value: {0}", value));
}
});
}
}
public class LanguageSpecification : CustomFormatSpecificationBase
{
private static readonly LanguageSpecificationValidator Validator = new LanguageSpecificationValidator();
public override int Order => 3;
public override string ImplementationName => "Language";
@@ -14,10 +34,15 @@ namespace NzbDrone.Core.CustomFormats
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
{
var comparedLanguage = movieInfo != null && Name == "Original" && movieInfo.ExtraInfo.ContainsKey("OriginalLanguage")
var comparedLanguage = movieInfo != null && Value == Language.Original.Id && movieInfo.ExtraInfo.ContainsKey("OriginalLanguage")
? (Language)movieInfo.ExtraInfo["OriginalLanguage"]
: (Language)Value;
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,31 @@
using System;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class QualityModifierSpecificationValidator : AbstractValidator<QualityModifierSpecification>
{
public QualityModifierSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((qualityValue, context) =>
{
if (!Enum.IsDefined(typeof(Modifier), qualityValue))
{
context.AddFailure(string.Format("Invalid quality modifier condition value: {0}", qualityValue));
}
});
}
}
public class QualityModifierSpecification : CustomFormatSpecificationBase
{
private static readonly QualityModifierSpecificationValidator Validator = new QualityModifierSpecificationValidator();
public override int Order => 7;
public override string ImplementationName => "Quality Modifier";
@@ -16,5 +36,10 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,21 +1,38 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class RegexSpecificationBaseValidator : AbstractValidator<RegexSpecificationBase>
{
public RegexSpecificationBaseValidator()
{
RuleFor(c => c.Value).NotEmpty().WithMessage("Regex Pattern must not be empty");
}
}
public abstract class RegexSpecificationBase : CustomFormatSpecificationBase
{
private static readonly RegexSpecificationBaseValidator Validator = new RegexSpecificationBaseValidator();
protected Regex _regex;
protected string _raw;
[FieldDefinition(1, Label = "Regular Expression")]
[FieldDefinition(1, Label = "Regular Expression", HelpText = "Custom Format RegEx is Case Insensitive")]
public string Value
{
get => _raw;
set
{
_raw = value;
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (value.IsNotNullOrWhiteSpace())
{
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
}
}
@@ -28,5 +45,10 @@ namespace NzbDrone.Core.CustomFormats
return _regex.IsMatch(compared);
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,23 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class ResolutionSpecificationValidator : AbstractValidator<ResolutionSpecification>
{
public ResolutionSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class ResolutionSpecification : CustomFormatSpecificationBase
{
private static readonly ResolutionSpecificationValidator Validator = new ResolutionSpecificationValidator();
public override int Order => 6;
public override string ImplementationName => "Resolution";
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,24 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class SizeSpecificationValidator : AbstractValidator<SizeSpecification>
{
public SizeSpecificationValidator()
{
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
}
}
public class SizeSpecification : CustomFormatSpecificationBase
{
private static readonly SizeSpecificationValidator Validator = new SizeSpecificationValidator();
public override int Order => 8;
public override string ImplementationName => "Size";
@@ -21,5 +34,10 @@ namespace NzbDrone.Core.CustomFormats
return size > Min.Gigabytes() && size <= Max.Gigabytes();
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,23 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class SourceSpecificationValidator : AbstractValidator<SourceSpecification>
{
public SourceSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class SourceSpecification : CustomFormatSpecificationBase
{
private static readonly SourceSpecificationValidator Validator = new SourceSpecificationValidator();
public override int Order => 5;
public override string ImplementationName => "Source";
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Data.Common;
using System.Data.SQLite;
using System.Net.Sockets;
using NLog;
using Npgsql;
using NzbDrone.Common.Disk;
@@ -124,6 +125,37 @@ namespace NzbDrone.Core.Datastore
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/radarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
}
catch (NpgsqlException e)
{
if (e.InnerException is SocketException)
{
var retryCount = 3;
while (true)
{
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
try
{
_migrationController.Migrate(connectionString, migrationContext);
}
catch (Exception ex)
{
if (--retryCount > 0)
{
System.Threading.Thread.Sleep(5000);
continue;
}
throw new RadarrStartupException(ex, "Error creating main database");
}
}
}
else
{
throw new RadarrStartupException(e, "Error creating main database");
}
}
catch (Exception e)
{
throw new RadarrStartupException(e, "Error creating main database");

View File

@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Datastore.Migration
SortTitle = Parser.Parser.NormalizeTitle(collectionName),
Added = added,
QualityProfileId = qualityProfileId,
RootFolderPath = rootFolderPath,
RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
SearchOnAdd = true,
MinimumAvailability = minimumAvailability
});

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(212)]
public class postgres_update_timestamp_columns_to_with_timezone : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Blocklist").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("Blocklist").AlterColumn("PublishedDate").AsDateTimeOffset().Nullable();
Alter.Table("Collections").AlterColumn("Added").AsDateTimeOffset().Nullable();
Alter.Table("Collections").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable();
Alter.Table("Commands").AlterColumn("QueuedAt").AsDateTimeOffset().NotNullable();
Alter.Table("Commands").AlterColumn("StartedAt").AsDateTimeOffset().Nullable();
Alter.Table("Commands").AlterColumn("EndedAt").AsDateTimeOffset().Nullable();
Alter.Table("DownloadClientStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
Alter.Table("DownloadClientStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
Alter.Table("DownloadClientStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
Alter.Table("DownloadHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("ExtraFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable();
Alter.Table("ExtraFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
Alter.Table("History").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("ImportListStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
Alter.Table("ImportListStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
Alter.Table("ImportListStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("CookiesExpirationDate").AsDateTimeOffset().Nullable();
Alter.Table("MetadataFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
Alter.Table("MetadataFiles").AlterColumn("Added").AsDateTimeOffset().Nullable();
Alter.Table("MovieFiles").AlterColumn("DateAdded").AsDateTimeOffset().NotNullable();
Alter.Table("MovieMetadata").AlterColumn("DigitalRelease").AsDateTimeOffset().Nullable();
Alter.Table("MovieMetadata").AlterColumn("InCinemas").AsDateTimeOffset().Nullable();
Alter.Table("MovieMetadata").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable();
Alter.Table("MovieMetadata").AlterColumn("PhysicalRelease").AsDateTimeOffset().Nullable();
Alter.Table("Movies").AlterColumn("Added").AsDateTimeOffset().Nullable();
Alter.Table("PendingReleases").AlterColumn("Added").AsDateTimeOffset().NotNullable();
Alter.Table("ScheduledTasks").AlterColumn("LastExecution").AsDateTimeOffset().NotNullable();
Alter.Table("ScheduledTasks").AlterColumn("LastStartTime").AsDateTimeOffset().Nullable();
Alter.Table("SubtitleFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable();
Alter.Table("SubtitleFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable();
}
protected override void LogDbUpgrade()
{
Alter.Table("Logs").AlterColumn("Time").AsDateTimeOffset().NotNullable();
Alter.Table("UpdateHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable();
}
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(214)]
public class add_language_tags_to_subtitle_files : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("SubtitleFiles").AddColumn("LanguageTags").AsString().Nullable();
}
}
}

View File

@@ -1,19 +1,23 @@
using System;
using System;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class RepackSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
private readonly Logger _logger;
public RepackSpecification(UpgradableSpecification upgradableSpecification, Logger logger)
public RepackSpecification(UpgradableSpecification upgradableSpecification, IConfigService configService, Logger logger)
{
_upgradableSpecification = upgradableSpecification;
_configService = configService;
_logger = logger;
}
@@ -22,11 +26,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
{
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
if (!subject.ParsedMovieInfo.Quality.Revision.IsRepack)
{
return Decision.Accept();
}
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Repacks are not preferred, skipping check");
return Decision.Accept();
}
if (subject.Movie.MovieFileId != 0)
{
var file = subject.Movie.MovieFile;
@@ -36,6 +48,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
var releaseGroup = subject.ParsedMovieInfo.ReleaseGroup;
var fileReleaseGroup = file.ReleaseGroup;
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{
_logger.Debug("Auto downloading of repacks is disabled");
return Decision.Reject("Repack downloading is disabled");
}
if (fileReleaseGroup.IsNullOrWhiteSpace())
{
return Decision.Reject("Unable to determine release group for the existing file");

View File

@@ -3,7 +3,6 @@ using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
@@ -31,7 +30,6 @@ namespace NzbDrone.Core.Extras
public void Handle(MovieScannedEvent message)
{
var movie = message.Movie;
var extraFiles = new List<ExtraFile>();
if (!_diskProvider.FolderExists(movie.Path))
{
@@ -43,17 +41,16 @@ namespace NzbDrone.Core.Extras
var filesOnDisk = _diskScanService.GetNonVideoFiles(movie.Path);
var possibleExtraFiles = _diskScanService.FilterPaths(movie.Path, filesOnDisk, false);
var filteredFiles = possibleExtraFiles;
var importedFiles = new List<string>();
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
{
var imported = existingExtraFileImporter.ProcessFiles(movie, filteredFiles, importedFiles);
var imported = existingExtraFileImporter.ProcessFiles(movie, possibleExtraFiles, importedFiles);
importedFiles.AddRange(imported.Select(f => Path.Combine(movie.Path, f.RelativePath)));
}
_logger.Info("Found {0} extra files", extraFiles.Count);
_logger.Info("Found {0} possible extra files, imported {1} files.", possibleExtraFiles.Count, importedFiles.Count);
}
}
}

View File

@@ -31,7 +31,6 @@ namespace NzbDrone.Core.Extras
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
private readonly List<IManageExtraFiles> _extraFileManagers;
private readonly Logger _logger;
public ExtraService(IMediaFileService mediaFileService,
IMovieService movieService,
@@ -45,7 +44,6 @@ namespace NzbDrone.Core.Extras
_diskProvider = diskProvider;
_configService = configService;
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
_logger = logger;
}
public void ImportMovie(LocalMovie localMovie, MovieFile movieFile, bool isReadOnly)
@@ -62,61 +60,42 @@ namespace NzbDrone.Core.Extras
return;
}
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.AllDirectories).Where(f => f != localMovie.Path);
var folderSearchOption = localMovie.FolderMovieInfo == null
? SearchOption.TopDirectoryOnly
: SearchOption.AllDirectories;
var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => e.Trim(' ', '.'))
.Select(e => e.Trim(' ', '.')
.Insert(0, "."))
.ToList();
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase)).ToList();
var filteredFilenames = new List<string>();
var hasNfo = false;
var sourceFolder = _diskProvider.GetParentFolder(localMovie.Path);
var files = _diskProvider.GetFiles(sourceFolder, folderSearchOption);
var managedFiles = _extraFileManagers.Select((i) => new List<string>()).ToArray();
foreach (var matchingFilename in matchingFilenames)
foreach (var file in files)
{
// Filter out duplicate NFO files
if (matchingFilename.EndsWith(".nfo", StringComparison.InvariantCultureIgnoreCase))
{
if (hasNfo)
{
continue;
}
hasNfo = true;
}
filteredFilenames.Add(matchingFilename);
}
foreach (var matchingFilename in filteredFilenames)
{
var matchingExtension = wantedExtensions.FirstOrDefault(e => matchingFilename.EndsWith(e));
var extension = Path.GetExtension(file);
var matchingExtension = wantedExtensions.FirstOrDefault(e => e.Equals(extension));
if (matchingExtension == null)
{
continue;
}
try
for (int i = 0; i < _extraFileManagers.Count; i++)
{
foreach (var extraFileManager in _extraFileManagers)
if (_extraFileManagers[i].CanImportFile(localMovie, movieFile, file, extension, isReadOnly))
{
var extension = Path.GetExtension(matchingFilename);
var extraFile = extraFileManager.Import(localMovie.Movie, movieFile, matchingFilename, extension, isReadOnly);
if (extraFile != null)
{
break;
}
managedFiles[i].Add(file);
break;
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import extra file: {0}", matchingFilename);
}
}
for (int i = 0; i < _extraFileManagers.Count; i++)
{
_extraFileManagers[i].ImportFiles(localMovie, movieFile, managedFiles[i], isReadOnly);
}
}

View File

@@ -8,6 +8,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Files
{
@@ -19,7 +20,8 @@ namespace NzbDrone.Core.Extras.Files
IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile);
IEnumerable<ExtraFile> CreateAfterMovieFolder(Movie movie, string movieFolder);
IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles);
ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly);
bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly);
IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly);
}
public abstract class ExtraFileManager<TExtraFile> : IManageExtraFiles
@@ -47,7 +49,8 @@ namespace NzbDrone.Core.Extras.Files
public abstract IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile);
public abstract IEnumerable<ExtraFile> CreateAfterMovieFolder(Movie movie, string movieFolder);
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles);
public abstract ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly);
public abstract bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly);
public abstract IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly);
protected TExtraFile ImportFile(Movie movie, MovieFile movieFile, string path, bool readOnly, string extension, string fileNameSuffix = null)
{

View File

@@ -148,11 +148,13 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
var details = new XElement("movie");
details.Add(new XElement("title", movieTranslation?.Title ?? movie.Title));
var metadataTitle = movieTranslation?.Title ?? movie.Title;
details.Add(new XElement("title", metadataTitle));
details.Add(new XElement("originaltitle", movie.MovieMetadata.Value.OriginalTitle));
details.Add(new XElement("sorttitle", movie.MovieMetadata.Value.SortTitle));
details.Add(new XElement("sorttitle", Parser.Parser.NormalizeTitle(metadataTitle)));
if (movie.MovieMetadata.Value.Ratings.Tmdb?.Votes > 0 || movie.MovieMetadata.Value.Ratings.Imdb?.Votes > 0)
{
@@ -261,7 +263,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
details.Add(new XElement("country"));
if (movie.MovieMetadata.Value.CollectionTitle != null)
if (Settings.AddCollectionName && movie.MovieMetadata.Value.CollectionTitle != null)
{
var setElement = new XElement("set");
@@ -305,6 +307,8 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
details.Add(new XElement("trailer", "plugin://plugin.video.youtube/play/?video_id=" + movie.MovieMetadata.Value.YouTubeTrailerId));
details.Add(new XElement("watched", watched));
if (movieFile.MediaInfo != null)
{
var sceneName = movieFile.GetSceneOrFileName();

View File

@@ -24,6 +24,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
MovieMetadataLanguage = (int)Language.English;
MovieImages = true;
UseMovieNfo = false;
AddCollectionName = true;
}
[FieldDefinition(0, Label = "Movie Metadata", Type = FieldType.Checkbox)]
@@ -41,6 +42,9 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
[FieldDefinition(4, Label = "Use Movie.nfo", Type = FieldType.Checkbox, HelpText = "Radarr will write metadata to movie.nfo instead of the default <movie-filename>.nfo")]
public bool UseMovieNfo { get; set; }
[FieldDefinition(5, Label = "Collection Name", Type = FieldType.Checkbox, HelpText = "Radarr will write the collection name to the .nfo file", Advanced = true)]
public bool AddCollectionName { get; set; }
public bool IsValid => true;
public NzbDroneValidationResult Validate()

View File

@@ -13,6 +13,7 @@ using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Metadata
{
@@ -191,9 +192,14 @@ namespace NzbDrone.Core.Extras.Metadata
return movedFiles;
}
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
public override bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly)
{
return null;
return false;
}
public override IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly)
{
return Enumerable.Empty<ExtraFile>();
}
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> movieMetadata)

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
@@ -7,13 +9,16 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Others
{
public class OtherExtraService : ExtraFileManager<OtherExtraFile>
{
private readonly IDiskProvider _diskProvider;
private readonly IOtherExtraFileService _otherExtraFileService;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly Logger _logger;
public OtherExtraService(IConfigService configService,
IDiskProvider diskProvider,
@@ -23,8 +28,10 @@ namespace NzbDrone.Core.Extras.Others
Logger logger)
: base(configService, diskProvider, diskTransferService, logger)
{
_diskProvider = diskProvider;
_otherExtraFileService = otherExtraFileService;
_mediaFileAttributeService = mediaFileAttributeService;
_logger = logger;
}
public override int Order => 2;
@@ -69,17 +76,79 @@ namespace NzbDrone.Core.Extras.Others
return movedFiles;
}
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
public override bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly)
{
var extraFile = ImportFile(movie, movieFile, path, readOnly, extension, null);
return true;
}
if (extraFile != null)
public override IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly)
{
var importedFiles = new List<ExtraFile>();
var filteredFiles = files.Where(f => CanImportFile(localMovie, movieFile, f, Path.GetExtension(f), isReadOnly)).ToList();
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var matchingFiles = new List<string>();
var hasNfo = false;
foreach (var file in filteredFiles)
{
_mediaFileAttributeService.SetFilePermissions(path);
_otherExtraFileService.Upsert(extraFile);
try
{
// Filter out duplicate NFO files
if (file.EndsWith(".nfo", StringComparison.InvariantCultureIgnoreCase))
{
if (hasNfo)
{
continue;
}
hasNfo = true;
}
// Filename match
if (Path.GetFileNameWithoutExtension(file).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase))
{
matchingFiles.Add(file);
continue;
}
// Movie match
var fileMovieInfo = Parser.Parser.ParseMoviePath(file) ?? new ParsedMovieInfo();
if (fileMovieInfo.MovieTitle == null)
{
continue;
}
if (fileMovieInfo.MovieTitle == localMovie.FileMovieInfo.MovieTitle &&
fileMovieInfo.Year.Equals(localMovie.FileMovieInfo.Year))
{
matchingFiles.Add(file);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import extra file: {0}", file);
}
}
return extraFile;
foreach (string file in matchingFiles)
{
try
{
var extraFile = ImportFile(localMovie.Movie, movieFile, file, isReadOnly, Path.GetExtension(file), null);
_mediaFileAttributeService.SetFilePermissions(file);
_otherExtraFileService.Upsert(extraFile);
importedFiles.Add(extraFile);
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import extra file: {0}", file);
}
}
return importedFiles;
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Languages;
@@ -5,6 +6,17 @@ namespace NzbDrone.Core.Extras.Subtitles
{
public class SubtitleFile : ExtraFile
{
public SubtitleFile()
{
LanguageTags = new List<string>();
}
public Language Language { get; set; }
public string AggregateString => Language + LanguageTagsAsString + Extension;
public List<string> LanguageTags { get; set; }
private string LanguageTagsAsString => string.Join(".", LanguageTags);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -9,13 +10,17 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MovieImport;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Subtitles
{
public class SubtitleService : ExtraFileManager<SubtitleFile>
{
private readonly IDiskProvider _diskProvider;
private readonly IDetectSample _detectSample;
private readonly ISubtitleFileService _subtitleFileService;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly Logger _logger;
@@ -23,11 +28,14 @@ namespace NzbDrone.Core.Extras.Subtitles
public SubtitleService(IConfigService configService,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IDetectSample detectSample,
ISubtitleFileService subtitleFileService,
IMediaFileAttributeService mediaFileAttributeService,
Logger logger)
: base(configService, diskProvider, diskTransferService, logger)
{
_diskProvider = diskProvider;
_detectSample = detectSample;
_subtitleFileService = subtitleFileService;
_mediaFileAttributeService = mediaFileAttributeService;
_logger = logger;
@@ -64,21 +72,16 @@ namespace NzbDrone.Core.Extras.Subtitles
foreach (var movieFile in movieFiles)
{
var groupedExtraFilesForMovieFile = subtitleFiles.Where(m => m.MovieFileId == movieFile.Id)
.GroupBy(s => s.Language + s.Extension).ToList();
.GroupBy(s => s.AggregateString).ToList();
foreach (var group in groupedExtraFilesForMovieFile)
{
var groupCount = group.Count();
var copy = 1;
if (groupCount > 1)
{
_logger.Warn("Multiple subtitle files found with the same language and extension for {0}", Path.Combine(movie.Path, movieFile.RelativePath));
}
foreach (var subtitleFile in group)
{
var suffix = GetSuffix(subtitleFile.Language, copy, groupCount > 1);
var suffix = GetSuffix(subtitleFile.Language, copy, subtitleFile.LanguageTags, groupCount > 1);
movedFiles.AddIfNotNull(MoveFile(movie, movieFile, subtitleFile, suffix));
copy++;
@@ -91,25 +94,141 @@ namespace NzbDrone.Core.Extras.Subtitles
return movedFiles;
}
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
public override bool CanImportFile(LocalMovie localEpisode, MovieFile movieFile, string path, string extension, bool readOnly)
{
if (SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(path)))
{
var language = LanguageParser.ParseSubtitleLanguage(path);
var suffix = GetSuffix(language, 1, false);
var subtitleFile = ImportFile(movie, movieFile, path, readOnly, extension, suffix);
subtitleFile.Language = language;
_mediaFileAttributeService.SetFilePermissions(path);
_subtitleFileService.Upsert(subtitleFile);
return subtitleFile;
}
return null;
return SubtitleFileExtensions.Extensions.Contains(extension.ToLowerInvariant());
}
private string GetSuffix(Language language, int copy, bool multipleCopies = false)
public override IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly)
{
var importedFiles = new List<SubtitleFile>();
var filteredFiles = files.Where(f => CanImportFile(localMovie, movieFile, f, Path.GetExtension(f), isReadOnly)).ToList();
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var matchingFiles = new List<string>();
foreach (var file in filteredFiles)
{
try
{
// Filename match
if (Path.GetFileNameWithoutExtension(file).StartsWithIgnoreCase(sourceFileName))
{
matchingFiles.Add(file);
continue;
}
// Movie match
var fileMovieInfo = Parser.Parser.ParseMoviePath(file) ?? new ParsedMovieInfo();
if (fileMovieInfo.MovieTitle == null)
{
continue;
}
if (fileMovieInfo.MovieTitle == localMovie.FileMovieInfo.MovieTitle &&
fileMovieInfo.Year.Equals(localMovie.FileMovieInfo.Year))
{
matchingFiles.Add(file);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import subtitle file: {0}", file);
}
}
// Use any sub if only episode in folder
if (matchingFiles.Count == 0 && filteredFiles.Count > 0)
{
var videoFiles = _diskProvider.GetFiles(sourceFolder, SearchOption.AllDirectories)
.Where(file => MediaFileExtensions.Extensions.Contains(Path.GetExtension(file)))
.ToList();
if (videoFiles.Count() > 2)
{
return importedFiles;
}
// Filter out samples
videoFiles = videoFiles.Where(file =>
{
var sample = _detectSample.IsSample(localMovie.Movie.MovieMetadata, file);
if (sample == DetectSampleResult.Sample)
{
return false;
}
return true;
}).ToList();
if (videoFiles.Count == 1)
{
matchingFiles.AddRange(filteredFiles);
_logger.Warn("Imported any available subtitle file for movie: {0}", localMovie);
}
}
var subtitleFiles = new List<SubtitleFile>();
foreach (string file in matchingFiles)
{
var language = LanguageParser.ParseSubtitleLanguage(file);
var extension = Path.GetExtension(file);
var languageTags = LanguageParser.ParseLanguageTags(file);
var subFile = new SubtitleFile
{
Language = language,
Extension = extension
};
subFile.LanguageTags = languageTags.ToList();
subFile.RelativePath = PathExtensions.GetRelativePath(sourceFolder, file);
subtitleFiles.Add(subFile);
}
var groupedSubtitleFiles = subtitleFiles.GroupBy(s => s.AggregateString).ToList();
foreach (var group in groupedSubtitleFiles)
{
var groupCount = group.Count();
var copy = 1;
foreach (var file in group)
{
var path = Path.Combine(sourceFolder, file.RelativePath);
var language = file.Language;
var extension = file.Extension;
var suffix = GetSuffix(language, copy, file.LanguageTags, groupCount > 1);
try
{
var subtitleFile = ImportFile(localMovie.Movie, movieFile, path, isReadOnly, extension, suffix);
subtitleFile.Language = language;
subtitleFile.LanguageTags = file.LanguageTags;
_mediaFileAttributeService.SetFilePermissions(path);
_subtitleFileService.Upsert(subtitleFile);
importedFiles.Add(subtitleFile);
copy++;
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import subtitle file: {0}", path);
}
}
}
return importedFiles;
}
private string GetSuffix(Language language, int copy, List<string> languageTags, bool multipleCopies = false)
{
var suffixBuilder = new StringBuilder();
@@ -125,6 +244,12 @@ namespace NzbDrone.Core.Extras.Subtitles
suffixBuilder.Append(IsoLanguages.Get(language).TwoLetterCode);
}
if (languageTags.Any())
{
suffixBuilder.Append(".");
suffixBuilder.Append(string.Join(".", languageTags));
}
return suffixBuilder.ToString();
}
}

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.ImportLists
private readonly IImportListStatusService _importListStatusService;
private readonly IImportListMovieService _listMovieService;
private readonly ISearchForNewMovie _movieSearch;
private readonly IProvideMovieInfo _movieInfoService;
private readonly IMovieMetadataService _movieMetadataService;
private readonly Logger _logger;
@@ -30,6 +31,7 @@ namespace NzbDrone.Core.ImportLists
IImportListStatusService importListStatusService,
IImportListMovieService listMovieService,
ISearchForNewMovie movieSearch,
IProvideMovieInfo movieInfoService,
IMovieMetadataService movieMetadataService,
Logger logger)
{
@@ -37,6 +39,7 @@ namespace NzbDrone.Core.ImportLists
_importListStatusService = importListStatusService;
_listMovieService = listMovieService;
_movieSearch = movieSearch;
_movieInfoService = movieInfoService;
_movieMetadataService = movieMetadataService;
_logger = logger;
}
@@ -84,20 +87,10 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure)
{
// TODO some opportunity to bulk map here if we had the tmdbIds
var listMovies = importListReports.Movies.Select(x =>
{
// Is it existing in result
var movie = result.Movies.FirstOrDefault(r => r.TmdbId == x.TmdbId);
if (movie != null)
{
movie.ListId = importList.Definition.Id;
}
return movie ?? MapMovieReport(x);
}).Where(x => x.TmdbId > 0).ToList();
var alreadyMapped = result.Movies.Where(x => importListReports.Movies.Any(r => r.TmdbId == x.TmdbId));
var listMovies = MapMovieReports(importListReports.Movies.Where(x => !result.Movies.Any(r => r.TmdbId == x.TmdbId)).ToList()).Where(x => x.TmdbId > 0).ToList();
listMovies.AddRange(alreadyMapped);
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
@@ -148,13 +141,10 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure)
{
// TODO some opportunity to bulk map here if we had the tmdbIds
var listMovies = importListReports.Movies.Select(x =>
{
return MapMovieReport(x);
}).Where(x => x.TmdbId > 0).ToList();
var listMovies = MapMovieReports(importListReports.Movies).Where(x => x.TmdbId > 0).ToList();
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
result.Movies.AddRange(listMovies);
_listMovieService.SyncMoviesForList(listMovies, importList.Definition.Id);
@@ -173,21 +163,31 @@ namespace NzbDrone.Core.ImportLists
return result;
}
private ImportListMovie MapMovieReport(ImportListMovie report)
private List<ImportListMovie> MapMovieReports(List<ImportListMovie> reports)
{
var mappedMovie = _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = report.Title, TmdbId = report.TmdbId, ImdbId = report.ImdbId, Year = report.Year });
var mappedMovies = reports.Select(m => _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = m.Title, TmdbId = m.TmdbId, ImdbId = m.ImdbId, Year = m.Year }))
.Where(x => x != null)
.DistinctBy(x => x.TmdbId)
.ToList();
var mappedListMovie = new ImportListMovie { ListId = report.ListId };
_movieMetadataService.UpsertMany(mappedMovies);
if (mappedMovie != null)
var mappedListMovies = new List<ImportListMovie>();
foreach (var movieMeta in mappedMovies)
{
_movieMetadataService.Upsert(mappedMovie);
var mappedListMovie = new ImportListMovie();
mappedListMovie.MovieMetadata = mappedMovie;
mappedListMovie.MovieMetadataId = mappedMovie.Id;
if (movieMeta != null)
{
mappedListMovie.MovieMetadata = movieMeta;
mappedListMovie.MovieMetadataId = movieMeta.Id;
}
mappedListMovies.Add(mappedListMovie);
}
return mappedListMovie;
return mappedListMovies;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@@ -7,6 +8,7 @@ namespace NzbDrone.Core.ImportLists.ImportListMovies
public interface IImportListMovieRepository : IBasicRepository<ImportListMovie>
{
List<ImportListMovie> GetAllForLists(List<int> listIds);
bool ExistsByMetadataId(int metadataId);
}
public class ImportListMovieRepository : BasicRepository<ImportListMovie>, IImportListMovieRepository
@@ -20,5 +22,12 @@ namespace NzbDrone.Core.ImportLists.ImportListMovies
{
return Query(x => listIds.Contains(x.ListId));
}
public bool ExistsByMetadataId(int metadataId)
{
var movies = Query(x => x.MovieMetadataId == metadataId);
return movies.Any();
}
}
}

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.ImportLists.ImportListMovies
List<ImportListMovie> SyncMoviesForList(List<ImportListMovie> listMovies, int listId);
void RemoveListMovie(ImportListMovie listMovie);
ImportListMovie GetById(int id);
bool ExistsByMetadataId(int metadataId);
}
public class ImportListMovieService : IImportListMovieService, IHandleAsync<ProviderDeletedEvent<IImportList>>
@@ -79,5 +80,10 @@ namespace NzbDrone.Core.ImportLists.ImportListMovies
var moviesOnList = _importListMovieRepository.GetAllForLists(new List<int> { message.ProviderId });
_importListMovieRepository.DeleteMany(moviesOnList);
}
public bool ExistsByMetadataId(int metadataId)
{
return _importListMovieRepository.ExistsByMetadataId(metadataId);
}
}
}

View File

@@ -28,10 +28,10 @@ namespace NzbDrone.Core.ImportLists.Radarr
TagIds = Array.Empty<int>();
}
[FieldDefinition(0, Label = "Full URL", HelpText = "URL, including port, of the Radarr V3 instance to import from")]
[FieldDefinition(0, Label = "Full URL", HelpText = "URL, including port, of the Radarr instance to import from (Radarr 3.0 or higher)")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "Apikey of the Radarr V3 instance to import from")]
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "Apikey of the Radarr instance to import from (Radarr 3.0 or higher)")]
public string ApiKey { get; set; }
[FieldDefinition(2, Type = FieldType.Select, SelectOptionsProviderAction = "getProfiles", Label = "Profiles", HelpText = "Profiles from the source instance to import from")]

View File

@@ -27,7 +27,13 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
{
var link = string.Empty;
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim());
// Trakt slug rules:
// - replace all special characters with a dash
// - replaces multiple dashes with a single dash
// - allows underscore as a valid character
// - does not trim underscore from the end
// - allows multiple underscores in a row
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim(), true, "-", "-");
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));

View File

@@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.ImportLists.Trakt.Popular
@@ -9,6 +11,24 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
: base()
{
RuleFor(c => c.TraktListType).NotNull();
// Loose validation @TODO
RuleFor(c => c.Rating)
.Matches(@"^\d+\-\d+$", RegexOptions.IgnoreCase)
.When(c => c.Rating.IsNotNullOrWhiteSpace())
.WithMessage("Not a valid rating");
// Any valid certification
RuleFor(c => c.Certification)
.Matches(@"^\bNR\b|\bG\b|\bPG\b|\bPG\-13\b|\bR\b|\bNC\-17\b$", RegexOptions.IgnoreCase)
.When(c => c.Certification.IsNotNullOrWhiteSpace())
.WithMessage("Not a valid cerification");
// Loose validation @TODO
RuleFor(c => c.Years)
.Matches(@"^\d+(\-\d+)?$", RegexOptions.IgnoreCase)
.When(c => c.Years.IsNotNullOrWhiteSpace())
.WithMessage("Not a valid year or range of years");
}
}
@@ -19,9 +39,25 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
public TraktPopularSettings()
{
TraktListType = (int)TraktPopularListType.Popular;
Rating = "0-100";
Certification = "NR,G,PG,PG-13,R,NC-17";
Genres = "";
Years = "";
}
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktPopularListType), HelpText = "Type of list you're seeking to import from")]
public int TraktListType { get; set; }
[FieldDefinition(2, Label = "Rating", HelpText = "Filter movies by rating range (0-100)")]
public string Rating { get; set; }
[FieldDefinition(3, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
public string Certification { get; set; }
[FieldDefinition(4, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
public string Genres { get; set; }
[FieldDefinition(5, Label = "Years", HelpText = "Filter movies by year or year range")]
public string Years { get; set; }
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
@@ -29,24 +28,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
.WithMessage("Must authenticate with Trakt")
.When(c => c.AccessToken.IsNotNullOrWhiteSpace() && c.RefreshToken.IsNotNullOrWhiteSpace());
// Loose validation @TODO
RuleFor(c => c.Rating)
.Matches(@"^\d+\-\d+$", RegexOptions.IgnoreCase)
.When(c => c.Rating.IsNotNullOrWhiteSpace())
.WithMessage("Not a valid rating");
// Any valid certification
RuleFor(c => c.Certification)
.Matches(@"^\bNR\b|\bG\b|\bPG\b|\bPG\-13\b|\bR\b|\bNC\-17\b$", RegexOptions.IgnoreCase)
.When(c => c.Certification.IsNotNullOrWhiteSpace())
.WithMessage("Not a valid cerification");
// Loose validation @TODO
RuleFor(c => c.Years)
.Matches(@"^\d+(\-\d+)?$", RegexOptions.IgnoreCase)
.When(c => c.Years.IsNotNullOrWhiteSpace())
.WithMessage("Not a valid year or range of years");
// Limit not smaller than 1 and not larger than 100
RuleFor(c => c.Limit)
.GreaterThan(0)
@@ -62,10 +43,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
public TraktSettingsBase()
{
SignIn = "startOAuth";
Rating = "0-100";
Certification = "NR,G,PG,PG-13,R,NC-17";
Genres = "";
Years = "";
Limit = 100;
}
@@ -84,18 +61,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
[FieldDefinition(0, Label = "Auth User", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
public string AuthUser { get; set; }
[FieldDefinition(1, Label = "Rating", HelpText = "Filter movies by rating range (0-100)")]
public string Rating { get; set; }
[FieldDefinition(2, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
public string Certification { get; set; }
[FieldDefinition(3, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
public string Genres { get; set; }
[FieldDefinition(4, Label = "Years", HelpText = "Filter movies by year or year range")]
public string Years { get; set; }
[FieldDefinition(5, Label = "Limit", HelpText = "Limit the number of movies to get")]
public int Limit { get; set; }

View File

@@ -42,14 +42,15 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var movieId in message.MovieIds)
{
var movie = _movieService.GetMovie(movieId);
var userInvokedSearch = message.Trigger == CommandTrigger.Manual;
if (!movie.Monitored && message.Trigger != CommandTrigger.Manual)
if (!movie.Monitored && !userInvokedSearch)
{
_logger.Debug("Movie {0} is not monitored, skipping search", movie.Title);
continue;
}
var decisions = _releaseSearchService.MovieSearch(movieId, false, false);
var decisions = _releaseSearchService.MovieSearch(movieId, userInvokedSearch, false);
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
}

View File

@@ -31,10 +31,12 @@ namespace NzbDrone.Core.Indexers.Rarbg
if (jsonResponse.Resource.error_code.HasValue)
{
if (jsonResponse.Resource.error_code == 20 || jsonResponse.Resource.error_code == 8
|| jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10)
if (jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.error_code == 8
|| jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10
|| jsonResponse.Resource.error_code == 13 || jsonResponse.Resource.error_code == 14
|| jsonResponse.Resource.error_code == 20)
{
// No results or imdbid not found
// No results, rate limit, tmdbid not found/invalid, or imdbid not found/invalid
return results;
}

View File

@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Indexers
{
private static readonly SeedCriteriaSettingsValidator Validator = new SeedCriteriaSettingsValidator();
[FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default", Advanced = true)]
[FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default. Ratio should be at least 1.0 and follow the indexers rules")]
public double? SeedRatio { get; set; }
[FieldDefinition(1, Type = FieldType.Number, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]

View File

@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Indexers
{
if (InfoHashElementName.IsNotNullOrWhiteSpace())
{
return item.FindDecendants(InfoHashElementName).FirstOrDefault().Value;
return item.FindDecendants(InfoHashElementName).FirstOrDefault()?.Value;
}
var magnetUrl = GetMagnetUrl(item);
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Indexers
{
if (MagnetElementName.IsNotNullOrWhiteSpace())
{
var magnetURL = item.FindDecendants(MagnetElementName).FirstOrDefault().Value;
var magnetURL = item.FindDecendants(MagnetElementName).FirstOrDefault()?.Value;
if (magnetURL.IsNotNullOrWhiteSpace() && magnetURL.StartsWith("magnet:"))
{
return magnetURL;

View File

@@ -105,6 +105,8 @@ namespace NzbDrone.Core.Languages
public static Language Ukrainian => new Language(32, "Ukrainian");
public static Language Persian => new Language(33, "Persian");
public static Language Bengali => new Language(34, "Bengali");
public static Language Slovak => new Language(35, "Slovak");
public static Language Latvian => new Language(36, "Latvian");
public static Language Any => new Language(-1, "Any");
public static Language Original => new Language(-2, "Original");
@@ -149,6 +151,8 @@ namespace NzbDrone.Core.Languages
Ukrainian,
Persian,
Bengali,
Slovak,
Latvian,
Any,
Original
};

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,7 @@
"AlternativeTitle": "Alternative Titel",
"AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Age": "Alter",
"AddNewMovie": "Neuer Film...",
"AddNewMovie": "Neuen Film hinzufügen",
"AddList": "Liste hinzufügen",
"SystemTimeCheckMessage": "Die Systemzeit ist um einen Tag versetzt. Bis die Zeit korrigiert wurde, könnten die geplanten Aufgaben nicht korrekt ausgeführt werden",
"UnsavedChanges": "Ungespeicherte Änderungen",
@@ -339,7 +339,7 @@
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
"CheckForFinishedDownloadsInterval": "Intervall zum prüfen von fertigen Downloads",
"CleanLibraryLevel": "Mediathek aufräumen",
"ClickToChangeLanguage": "Sprache ändern...",
"ClickToChangeLanguage": "Sprache ändern ...",
"ClickToChangeQuality": "Hier klicken um die Qualität zu ändern",
"ClientPriority": "Priorität",
"CloneFormatTag": "Format Tag kopieren",
@@ -639,8 +639,8 @@
"RetryingDownloadInterp": "Herunterladen nochmal versuchen {0} um {1}",
"RemovingTag": "Tag entfernen",
"ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}",
"Queued": "Eingereiht",
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen?",
"Queued": "In der Warteschlange",
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen",
"Pending": "Ausstehend",
"Paused": "Pausiert",
"NegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
@@ -653,7 +653,7 @@
"MarkAsFailedMessageText": "'{0}' wirklich als fehlgeschlagen markieren?",
"Manual": "Manuell",
"LogLevelTraceHelpTextWarning": "Trace logging sollte nur kurzzeitig aktiviert werden",
"LastDuration": "Letzte Laufzeit",
"LastDuration": "Letzte Dauer",
"IncludeRecommendationsHelpText": "Radarrs Empfehlungen in der Entdeckungsansicht mit einbeziehen",
"IncludeRadarrRecommendations": "Radarr Empfehlungen einbeziehen",
"ImportFailedInterp": "Import fehlgeschlagen: {0}",
@@ -889,7 +889,7 @@
"RadarrUpdated": "Radarr wurde aktualisiert",
"RadarrCalendarFeed": "Radarr Kalender Feed",
"QueueIsEmpty": "Warteschlange ist leer",
"QualityProfileInUse": "Ein Qualitätsprofil mit zugeordneten Filmen kann nicht gelöscht werden",
"QualityProfileInUse": "Ein Qualitätsprofil mit zugeordneten Filmen, Listen oder Sammlungen kann nicht gelöscht werden",
"QualityOrLangCutoffHasNotBeenMet": "Qualität oder Sprache haben die Schwelle noch nicht erreicht",
"QualityLimitsHelpText": "Limitierungen werden automatisch an die Filmlänge angepasst.",
"QualitiesHelpText": "Qualitätsprofile oben sind mehr bevorzugt. Profile in einer Gruppe sind gleichgestellt. Nur ausgewählte werden gesucht",
@@ -1047,7 +1047,7 @@
"RemotePathMappingCheckFilesLocalWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"RemotePathMappingCheckFilesBadDockerPath": "Docker erkannt; Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckFilesWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"RemotePathMappingCheckLocalFolderMissing": "Downloader {0} speichert Downloads in {1}, aber dieses Verzeichnis scheint nicht zu existieren. Möglicherweise eine fehlende oder falsche Remote-Pfadzuordnung.",
@@ -1112,10 +1112,10 @@
"OriginalTitle": "Originaler Titel",
"OriginalLanguage": "Originale Sprache",
"Database": "Datenbank",
"AllCollectionsHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Collections": "Sammlung",
"AllCollectionsHiddenDueToFilter": "Alle Filme sind auf Grund des Filters ausgeblendet.",
"Collections": "Sammlungen",
"MonitorMovies": "Film beobachten",
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren.",
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren",
"RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )",
"CollectionOptions": "Sammlung Optionen",
"ChooseImportMode": "Wähle eine Importmethode",
@@ -1127,7 +1127,7 @@
"ScrollMovies": "Filme scrollen",
"ShowCollectionDetails": "Status der Sammlung anzeigen",
"RefreshCollections": "Sammlungen aktualisieren",
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (Min. 1 Minute).",
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (min. 1 Minute)",
"ShowOverview": "Übersicht anzeigen",
"ShowPosters": "Plakate anzeigen",
"CollectionShowDetailsHelpText": "Status und Eigenschaften der Sammlung anzeigen",
@@ -1141,5 +1141,9 @@
"OnMovieAddedHelpText": "Ausführen wenn ein Film hinzugefügt wurde",
"UnableToLoadCollections": "Sammlungen können nicht geladen werden",
"InstanceName": "Instanzname",
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname"
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
"RottenTomatoesRating": "Tomato Bewertung",
"TotalMovies": "Filme insgesamt",
"ApplicationURL": "Anwendungs-URL",
"ApplicationUrlHelpText": "Die externe URL der Anwendung inklusive http(s)://, Port und URL-Basis"
}

View File

@@ -50,6 +50,8 @@
"ApiKey": "API Key",
"AppDataDirectory": "AppData directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
"ApplicationURL": "Application URL",
"ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base",
"Apply": "Apply",
"ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "How to apply tags to the selected movies",
@@ -735,7 +737,7 @@
"QualityOrLangCutoffHasNotBeenMet": "Quality or Language cutoff has not been met",
"QualityProfile": "Quality Profile",
"QualityProfileDeleteConfirm": "Are you sure you want to delete the quality profile {0}",
"QualityProfileInUse": "Can't delete a quality profile that is attached to a movie",
"QualityProfileInUse": "Can't delete a quality profile that is attached to a movie, list, or collection",
"QualityProfiles": "Quality Profiles",
"QualitySettings": "Quality Settings",
"QualitySettingsSummary": "Quality sizes and naming",
@@ -1027,6 +1029,7 @@
"Torrents": "Torrents",
"TorrentsDisabled": "Torrents Disabled",
"TotalFileSize": "Total File Size",
"TotalMovies": "Total Movies",
"TotalSpace": "Total Space",
"Trace": "Trace",
"Trailer": "Trailer",

View File

@@ -91,7 +91,7 @@
"Search": "Buscar",
"Scheduled": "Programado",
"SaveChanges": "Guardar Cambios",
"RSSSync": "Sincronización RSS",
"RSSSync": "Sincronizar RSS",
"RootFolders": "Carpetas de Origen",
"RootFolderCheckSingleMessage": "La carpeta de origen no existe: {0}",
"RootFolderCheckMultipleMessage": "Varias carpetas de origen no existen: {0}",
@@ -232,7 +232,7 @@
"OAuthPopupMessage": "Pop-ups bloqueados por su navegador",
"Name": "Nombre",
"MoveFiles": "Mover Archivos",
"Monitored": "Monitoreadas",
"Monitored": "Monitoreada",
"Message": "Mensaje",
"Location": "Localización",
"Level": "Nivel",
@@ -461,7 +461,7 @@
"ShowMonitoredHelpText": "Mostrar el estado de monitoreo debajo del poster",
"ShowCutoffUnmetIconHelpText": "Mostrar el icono para los ficheros cuando no se ha alcanzado el corte",
"ShowAsAllDayEvents": "Mostrar como Eventos de Todo el Día",
"ShouldMonitorHelpText": "Si se habilita, las películas añadidas desde esta lista serán también monitoreadas",
"ShouldMonitorHelpText": "Si las películas o colecciones en esta lista deberían ser agregadas como monitoreadas",
"SetPermissionsLinuxHelpTextWarning": "Si no estas seguro de lo que hacen estos ajustes, no los modifiques.",
"SetPermissionsLinuxHelpText": "Debe chmod ser ejecutado una vez los archivos hayan sido importados/renombrados?",
"SetPermissions": "Ajustar Permisos",
@@ -614,8 +614,8 @@
"CleanLibraryLevel": "Limpiar el Nivel de la Librería",
"BindAddress": "Dirección de Ligado",
"OpenBrowserOnStart": "Abrir navegador al arrancar",
"OnUpgradeHelpText": "Al Actualizar",
"OnRenameHelpText": "En Renombrado",
"OnUpgradeHelpText": "Al Mejorar La Calidad",
"OnRenameHelpText": "Al Renombrar",
"OnHealthIssueHelpText": "En Problema de Salud",
"OnGrabHelpText": "Al Capturar",
"SettingsRuntimeFormat": "Formato de Tiempo de ejecución",
@@ -633,7 +633,7 @@
"ShownClickToHide": "Mostrado, clic para ocultar",
"ShowGenres": "Mostrar Géneros",
"ShowCertification": "Mostrar Certificación",
"SearchOnAddHelpText": "Buscar películas en esta lista cuando se añadan a Radarr",
"SearchOnAddHelpText": "Buscar películas en esta lista cuando se añada a la biblioteca",
"RSSSyncIntervalHelpTextWarning": "Se aplicará a todos los indexers, por favor sigue las reglas de los mismos",
"RSSIsNotSupportedWithThisIndexer": "RSS no son soportadas por este indexer",
"RetryingDownloadInterp": "Re-intentando descarga {0} en {1}",
@@ -874,11 +874,11 @@
"MovieFilesTotaling": "Totalización de archivos de película",
"OnGrab": "Al Capturar",
"OnHealthIssue": "En Problema de Salud",
"OnImport": "Al importar",
"OnImport": "Al Importar",
"OnLatestVersion": "La última versión de Radarr ya está instalada",
"OnlyTorrent": "Solo Torrent",
"OnlyUsenet": "Solo Usenet",
"OnRename": "En Renombrado",
"OnRename": "Al Renombrar",
"PreferTorrent": "Prefiero Torrent",
"PreferUsenet": "Prefiero Usenet",
"Presets": "Preajustes",
@@ -988,12 +988,12 @@
"Score": "Puntuación",
"Script": "Guión",
"SearchCutoffUnmet": "Corte de búsqueda no alcanzado",
"SearchMissing": "Búsqueda que falta",
"SearchMissing": "Buscar Faltantes",
"Seconds": "Segundos",
"SelectDotDot": "Seleccione...",
"SelectLanguage": "Seleccione el idioma",
"SelectMovie": "Seleccionar película",
"SelectQuality": "Seleccionar calidad",
"SelectLanguage": "Seleccionar Idioma",
"SelectMovie": "Seleccionar Película",
"SelectQuality": "Seleccionar Calidad",
"Small": "Pequeña",
"Socks4": "Calcetines4",
"Socks5": "Socks5 (soporte TOR)",
@@ -1031,13 +1031,13 @@
"showCinemaReleaseHelpText": "Mostrar la fecha de estreno en el cine debajo del cartel",
"ShowReleaseDate": "Mostrar fecha de lanzamiento",
"ShowReleaseDateHelpText": "Mostrar la fecha de lanzamiento debajo del cartel",
"OnMovieDelete": "Al eliminar la película",
"OnMovieDeleteHelpText": "Al eliminar la película",
"OnMovieFileDelete": "Al eliminar archivo de película",
"OnMovieFileDeleteHelpText": "Al eliminar archivo de película",
"OnMovieFileDeleteForUpgrade": "En archivo de película Eliminar para actualizar",
"OnMovieFileDeleteForUpgradeHelpText": "En archivo de película Eliminar para actualizar",
"OnUpgrade": "Al Actualizar",
"OnMovieDelete": "Al Eliminar Película",
"OnMovieDeleteHelpText": "Al Eliminar Película",
"OnMovieFileDelete": "Al Eliminar Archivo De Película",
"OnMovieFileDeleteHelpText": "Al Eliminar Archivo De Película",
"OnMovieFileDeleteForUpgrade": "Al Eliminar Archivo De Pelicula Para Mejorar La Calidad",
"OnMovieFileDeleteForUpgradeHelpText": "Al Eliminar Archivo De Pelicula Para Mejorar La Calidad",
"OnUpgrade": "Al Mejorar La Calidad",
"Reddit": "Reddit",
"More": "Más",
"Download": "Descargar",
@@ -1064,7 +1064,7 @@
"List": "Lista",
"ManualImportSetReleaseGroup": "Importación manual - Configurar el grupo de lanzamiento",
"NotificationTriggersHelpText": "Seleccione qué eventos deben activar esta notificación",
"OnApplicationUpdate": "Al actualizar la aplicación",
"OnApplicationUpdate": "Al Actualizar La Aplicación",
"RemotePathMappingCheckFileRemoved": "El archivo {0} fue eliminado a mitad del proceso.",
"RemotePath": "Ruta remota",
"RemotePathMappingCheckBadDockerPath": "Está utilizando docker; el cliente de descarga {0} coloca las descargas en {1} pero esta no es una ruta válida {2}. Revisa tus mapeos de rutas remotas y la configuración del cliente de descarga.",
@@ -1072,7 +1072,7 @@
"RemotePathMappingCheckFilesBadDockerPath": "Está utilizando docker; el cliente de descarga {0} informó de archivos en {1} pero esta no es una ruta válida {2}. Revise sus mapeos de rutas remotas y la configuración del cliente de descarga.",
"RemotePathMappingCheckLocalWrongOSPath": "El cliente de descarga local {0} coloca las descargas en {1} pero ésta no es una ruta válida {2}. Revise la configuración de su cliente de descarga.",
"RemotePathMappingCheckRemoteDownloadClient": "El cliente de descarga remota {0} informó de la existencia de archivos en {1} pero este directorio no parece existir. Probablemente falta mapear la ruta remota.",
"SelectLanguages": "Seleccionar idiomas",
"SelectLanguages": "Seleccionar Idiomas",
"IndexerDownloadClientHelpText": "Especifica qué cliente de descargas se utiliza para las descargas de este indexador",
"AnnouncedMsg": "Película anunciada",
"Auto": "Auto",
@@ -1097,7 +1097,7 @@
"TmdbRating": "TMDb valoración",
"TmdbVotes": "Votos en TMDb",
"Filters": "Filtros",
"OnApplicationUpdateHelpText": "Al actualizar la aplicación",
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
"OriginalTitle": "Título original",
"OriginalLanguage": "Idioma original",
"Rating": "Puntuación",
@@ -1108,7 +1108,7 @@
"RemotePathMappingCheckFilesLocalWrongOSPath": "El cliente de descarga local {0} informó de la existencia de archivos en {1}, pero no es una ruta válida {2}. Revise la configuración de su cliente de descarga.",
"RemoveSelectedItem": "Eliminar el elemento seleccionado",
"RemoveSelectedItems": "Eliminar los elementos seleccionados",
"SelectReleaseGroup": "Seleccionar el grupo de lanzamiento",
"SelectReleaseGroup": "Seleccionar Grupo De Lanzamiento",
"SetReleaseGroup": "Configurar el grupo de lanzamiento",
"TaskUserAgentTooltip": "User-Agent proporcionado por la aplicación llamó a la API",
"RssSyncHelpText": "Intervalo en minutos. Ajustar a cero para inhabilitar (esto dentendrá toda captura de estrenos automática)",
@@ -1119,5 +1119,28 @@
"ChooseImportMode": "Elegir Modo de Importación",
"CollectionOptions": "Opciones de la Colección",
"CollectionShowDetailsHelpText": "Mostrar el estado y propiedades de la colección",
"CollectionShowOverviewsHelpText": "Mostrar resumenes de la colección"
"CollectionShowOverviewsHelpText": "Mostrar resumenes de la colección",
"CollectionShowPostersHelpText": "Mostrar póster de artículos de colección",
"RefreshMonitoredIntervalHelpText": "Cada cuánto actualizar las descargas monitoreadeas desde los clientes de descarga, mínimo 1 minuto",
"SearchOnAddCollectionHelpText": "Buscar películas en esta colección cuando se añada a la biblioteca",
"TotalMovies": "Películas Totales",
"CollectionsSelectedInterp": "{0} Colecciones Seleccionadas",
"EditCollection": "Editar Colección",
"MonitorCollection": "Monitorear Colección",
"MonitoredCollectionHelpText": "Monitorear para que las películas de esta colección se añadan automáticamente a la biblioteca",
"MovieAndCollection": "Película y Colección",
"MovieCollectionMultipleMissingRoots": "Múltiples carpetas raices faltantes para colecciones de películas: {0}",
"OnMovieAdded": "Al Agregar Pelicula",
"OnMovieAddedHelpText": "Al Agregar Película",
"RefreshCollections": "Actualizar Colecciones",
"ShowCollectionDetails": "Mostrar Estado De Colección",
"InstanceName": "Nombre de Instancia",
"InstanceNameHelpText": "Nombre de instancia en pestaña y para nombre de aplicación en Syslog",
"RottenTomatoesRating": "Tomato Rating",
"ScrollMovies": "Desplazar Películas",
"ShowPosters": "Mostrar Posters",
"UnableToLoadCollections": "No se han podido cargar las colecciones",
"MovieCollectionMissingRoot": "Carpeta raíz faltante para colección de películas: {0}",
"MovieOnly": "Solo Película",
"Database": "Base de Datos"
}

View File

@@ -14,7 +14,7 @@
"RadarrSupportsAnyDownloadClient": "Radarr tukee kaikkien Newznab-yhteensopivien lataustyökalujen ohella myös monia muita alla listattuja torrent- ja Usenet-lataustyökaluja.",
"Timeleft": "Aikaa jäljellä",
"UpdateCheckStartupNotWritableMessage": "Päivitystä ei voi asentaa, koska käyttäjällä '{1}' ei ole kirjoitusoikeutta käynnistyskansioon '{0}'.",
"ApplyTagsHelpTexts4": " 'Korvaa' kaikki aiemmat tunnisteet tai poista kaikki tunnisteet jättämällä tyhjäksi",
"ApplyTagsHelpTexts4": "- \"Korvaa\" nykyiset tunnisteet syötetyillä tai tyhjennä kaikki tunnisteet jättämällä tyhjäksi.",
"UnableToLoadUISettings": "Käyttöliittymän asetuksien lataus epäonnistui.",
"NotAvailable": "Ei saatavilla",
"AddingTag": "Tunniste lisätään",
@@ -92,9 +92,9 @@
"DBMigration": "Tietokannan siirto",
"ApiKey": "API-avain",
"AcceptConfirmationModal": "Hyväksy vahvistus",
"ApplyTagsHelpTexts1": "Miten tunnisteita sovelletaan valittuihin elokuviin",
"ApplyTagsHelpTexts2": " 'Lisää' syötetyt tunnisteet aiempiin tunnisteisiin",
"ApplyTagsHelpTexts3": " 'Poista' ainoastaan syötetyt tunnisteet",
"ApplyTagsHelpTexts1": "Tunnistetoimenpiteiden selitykset:",
"ApplyTagsHelpTexts2": "- \"Lisää\" syötetyt tunnisteet aiempiin tunnisteisiin.",
"ApplyTagsHelpTexts3": "- \"Poista\" tyhjentää syötetyt tunnisteet.",
"AvailabilityDelay": "Saatavuusviive",
"AvailabilityDelayHelpText": "Aika ennen käytettävissä olevaa päivämäärää tai sen jälkeen elokuvan etsimiseen",
"BackupIntervalHelpText": "Prowlarrin tietokannan ja asetusten automaattisen varmuuskopioinnin suoritusaikaväli.",
@@ -160,16 +160,16 @@
"AddToDownloadQueue": "Lisää latausjonoon",
"AfterManualRefresh": "Manuaalisen päivityksen jälkeen",
"AllFiles": "Kaikki tiedostot",
"AllMoviesInPathHaveBeenImported": "Kaikki kansion '{0}' elokuvat on tuotu",
"AllResultsHiddenFilter": "Aktiivinen suodatin on piilottanut kaikki tulokset",
"AllMoviesInPathHaveBeenImported": "Kaikki kansion {0} elokuvat on tuotu",
"AllResultsHiddenFilter": "Kaikki tulokset on piilotettu aktiivisen suodattimen johdosta.",
"Always": "Aina",
"AnalyseVideoFiles": "Analysoi videotiedostot",
"AnalyticsEnabledHelpText": "Lähetä nimettömiä käyttö- ja virhetietoja sovelluksen palvelimille. Tämä sisältää tietoja selaimestasi, verkkokäyttöliittymän sivujen käytöstä, virheraportoinnista sekä käyttöjärjestelmästäsi ja versiosta. Käytämme näitä tietoja ominaisuuksien ja virhekorjauksien painotukseen.",
"AptUpdater": "Käytä APT:ta päivityksen asennukseen",
"AreYouSureYouWantToDeleteFormat": "Haluatko varmasti poistaa muotoilutagin '{0}'?",
"AreYouSureYouWantToDeleteThisImportListExclusion": "Haluatko varmasti poistaa poikkeussäännön?",
"AreYouSureYouWantToDeleteFormat": "Haluatko varmasti poistaa muotoilutunnisteen \"{0}\"?",
"AreYouSureYouWantToDeleteThisImportListExclusion": "Haluatko varmasti poistaa tämän poikkeussäännön?",
"AreYouSureYouWantToRemoveSelectedItemsFromQueue": "Haluatko varmasti poistaa jonosta {0} kohdetta?",
"ApplyTags": "Toimenpide tunnisteille",
"ApplyTags": "Tunnistetoimenpide",
"AuthBasic": "Perus (selaimen ponnahdus)",
"AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana.",
"AuthForm": "Lomake (kirjautumissivu)",
@@ -220,7 +220,7 @@
"MoveFiles": "Siirrä tiedostoja",
"MovieFiles": "Elokuvatiedostot",
"MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella",
"NextExecution": "Seuraava toteutus",
"NextExecution": "Seuraava suoritus",
"NoAltTitle": "Ei vaihtoehtoisia nimikkeitä.",
"NoEventsFound": "Tapahtumia ei löytynyt",
"NoLinks": "Ei linkkejä",
@@ -253,7 +253,7 @@
"ProxyType": "Välityspalvelimen tyyppi",
"PtpOldSettingsCheckMessage": "Seuraavat PassThePopcorn-tietolähteet sisältävät vanhentuneita asetuksia, jotka olisi syytä päivittää: {0}",
"QualitiesHelpText": "Listalla ylempänä olevia laatuja painotetaan. Saman ryhmän laadut ovat tasaveroisia. Valitse vain haluamasi laadut.",
"QualityProfileInUse": "Elokuvaan liitettyä laatuprofiilia ei voi poistaa.",
"QualityProfileInUse": "Elokuvaan, listaan tai kokelmaan liitettyä laatuprofiilia ei voida poistaa.",
"QueueIsEmpty": "Jono on tyhjä",
"RadarrCalendarFeed": "Radarr-kalenterisyöte",
"RadarrUpdated": "Radarr Päivitetty",
@@ -320,8 +320,8 @@
"ImportErrors": "Tuontivirheet",
"Imported": "Tuodut",
"InvalidFormat": "Väärä muoto",
"LastDuration": "Viimeisin kesto",
"LastExecution": "Viimeinen toteutus",
"LastDuration": "Edellinen kesto",
"LastExecution": "Edellinen suoritus",
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
"ListExclusions": "Listojen poikkeussäännöt",
"Indexer": "Tietolähde",
@@ -397,7 +397,7 @@
"Path": "Sijainti",
"Paused": "Keskeytetty",
"Permissions": "Käyttöoikeudet",
"AllMoviesHiddenDueToFilter": "Käytössä oleva suodatin on piilottanut kaikki elokuvat.",
"AllMoviesHiddenDueToFilter": "Kaikki elokuvat on piilotettu aktiivisen suodattimen johdosta.",
"AllowHardcodedSubs": "Salli poltetut tekstitykset",
"PhysicalRelease": "Fyysinen julkaisu",
"Port": "Portti",
@@ -409,7 +409,7 @@
"ProtocolHelpText": "Valitse käytettävä (t) protokolla (t) ja mikä on suositeltava, kun valitset muuten yhtä suurten julkaisujen välillä",
"EditDelayProfile": "Muokkaa viiveprofiilia",
"ProxyBypassFilterHelpText": "Käytä erottimena ',' ja '*.' jokerimerkkinä aliverkkotunnuksille (esim. www.esimerkki.fi,*.esimerkki.fi)",
"AllowHardcodedSubsHelpText": "Havaitut poltetut tekstitykset ladataan automaattisesti",
"AllowHardcodedSubsHelpText": "Havaitut poltetut tekstitykset ladataan automaattisesti.",
"QualityProfile": "Laatuprofiili",
"QualityProfiles": "Laatuprofiilit",
"QuickImport": "Siirrä automaattisesti",
@@ -443,7 +443,7 @@
"TotalFileSize": "Tiedoston koko",
"TotalSpace": "Kokonaistila",
"UpgradeAllowedHelpText": "Jos käytöstä poistettuja laatuja ei päivitetä.",
"AreYouSureYouWantToDeleteThisDelayProfile": "Haluatko varmasti poistaa viiveprofiilin?",
"AreYouSureYouWantToDeleteThisDelayProfile": "Haluatko varmasti poistaa tämän viiveprofiilin?",
"Username": "Käyttäjätunnus",
"WaitingToImport": "Odottaa tuontia",
"DigitalRelease": "Digitaalinen julkaisu",
@@ -473,8 +473,8 @@
"ConnectionLost": "Yhteys on katkennut",
"Backup": "Varmuuskopio",
"ConnectionLostAutomaticMessage": "Radarr pyrkii muodostamaan yhteyden automaattisesti tai voit painaa alta \"Lataa uudelleen\".",
"AddNewMessage": "On helppoa lisätä uusi elokuva: ala vain kirjoittamaan haluamasi elokuvan nimeä.",
"AddNewTmdbIdMessage": "Voit tehdä hakuja myös elokuvan TMDb-tunnuksella (esim. 'tmdb:71663').",
"AddNewMessage": "Uuden elokuvan lisäys on helppoa. Aloita vain haluamasi elokuvan nimen kirjoitus.",
"AddNewTmdbIdMessage": "Voit etsiä myös elokuvan TMDb-tunnisteella (esim. 'tmdb:71663').",
"BackupFolderHelpText": "Suhteelliset polut kohdistuvat sovelluksen AppData-kansioon.",
"Connections": "Kytkennät",
"ConnectSettings": "Kytkentöjen asetukset",
@@ -787,7 +787,7 @@
"RenameMovies": "Uudelleennimeä elokuvat",
"RenameMoviesHelpText": "Jos uudelleennimeäminen ei ole käytössä, käytetään olemassa olevaa tiedostonimeä.",
"Reorder": "Järjestä uudelleen",
"Replace": "Korvata",
"Replace": "Korvaa",
"ReplaceIllegalCharacters": "Korvaa kielletyt merkit",
"ReplaceIllegalCharactersHelpText": "Korvaa laittomat merkit. Jos ei käytössä, laittomat merkit poistetaan.",
"ReplaceWithSpaceDashSpace": "Korvus: välilyönti-väliviiva-välilyönti",
@@ -1116,7 +1116,7 @@
"RssSyncHelpText": "Aikaväli minuutteina. Arvo nolla kytkee toiminnon pois käytöstä ja lopettaen samalla automaattisen julkaisujen kaappauksen täysin.",
"InstanceName": "Instanssin nimi",
"InstanceNameHelpText": "Instanssin nimi välilehdellä ja järjestelmälokissa",
"AllCollectionsHiddenDueToFilter": "Käytössä oleva suodatin on piilottanut kaikki kokoelmat.",
"AllCollectionsHiddenDueToFilter": "Kaikki kokoelmat on piilotettu aktiivisen suodattimen johdosta.",
"Collections": "Kokoelmat",
"MonitorMovies": "Valvo elokuvia",
"NoCollections": "Kokoelmia ei löytynyt. Aloita lisäämällä uusi elokuva tai tuo joitakin olemassa olevia.",
@@ -1142,5 +1142,8 @@
"CollectionOptions": "Kokoelmien valinnat",
"CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet",
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet",
"RottenTomatoesRating": "Tomaattiarvio"
"RottenTomatoesRating": "Tomaattiarvio",
"TotalMovies": "Elokuvia yhteensä",
"ApplicationURL": "Sovelluksen URL-osoite",
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta."
}

View File

@@ -106,7 +106,7 @@
"RemotePathMappings": "Mappages de chemins distants",
"ReleaseBranchCheckOfficialBranchMessage": "La branche {0} n'est pas une branche de version Radarr valide, vous ne recevrez pas de mises à jour",
"RefreshAndScan": "Actualiser et analyser",
"Refresh": "Rafraîchir",
"Refresh": "Actualiser",
"Ratings": "Évaluations",
"Queue": "File d'attente",
"QualitySettingsSummary": "Tailles qualité et dénomination",
@@ -318,7 +318,7 @@
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
"ChangeFileDate": "Changer la date du fichier",
"CertificationCountryHelpText": "Choisir un pays pour les classifications de films",
"CertificateValidationHelpText": "Change la rigueur de la vérification du certificat HTTPS. Ne changez rien sauf si vous comprenez les risques.",
"CertificateValidationHelpText": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne pas changer à moins que vous ne compreniez les risques.",
"CertificateValidation": "Validation du certificat",
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
"Branch": "Branche",
@@ -1114,10 +1114,33 @@
"SetReleaseGroup": "Régler le groupe de publication",
"RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute",
"AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.",
"Collections": "Collection",
"Collections": "Collections",
"MonitorMovies": "Surveiller le film",
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
"RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)",
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
"ChooseImportMode": "Mode d'importation"
"ChooseImportMode": "Mode d'importation",
"CollectionOptions": "Options de collection",
"CollectionShowDetailsHelpText": "Afficher l'état et les propriétés de la collection",
"CollectionShowOverviewsHelpText": "Afficher les aperçus des collections",
"OnMovieAddedHelpText": "À l'ajout d'un film",
"MovieAndCollection": "Film et collection",
"ShowOverview": "Afficher l'aperçu",
"MovieCollectionMissingRoot": "Dossier racine manquant pour la collection de films : {0}",
"OnMovieAdded": "À l'ajout d'un film",
"MonitorCollection": "Surveiller la collection",
"MonitoredCollectionHelpText": "Surveiller pour ajouter automatiquement les films de cette collection à la bibliothèque",
"MovieCollectionMultipleMissingRoots": "Plusieurs dossiers racine manquent pour les collections de films: {0}",
"MovieOnly": "Film seulement",
"RefreshCollections": "Actualiser les collections",
"SearchOnAddCollectionHelpText": "Rechercher des films dans cette collection lorsqu'ils sont ajoutés à Radarr",
"UnableToLoadCollections": "Impossible de charger les collections",
"RottenTomatoesRating": "Classement Rotten Tomatoes",
"ShowCollectionDetails": "Afficher l'état de la collection",
"EditCollection": "Modifier la collection",
"ApplicationURL": "URL de l'application",
"ApplicationUrlHelpText": "URL externe de cette application, y compris http(s)://, le port et l'URL de base",
"InstanceName": "Nom de l'instance",
"InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog",
"CollectionShowPostersHelpText": "Afficher les affiches des éléments de la collection"
}

View File

@@ -21,7 +21,7 @@
"AddExclusion": "Kivétel hozzáadása",
"Activity": "Aktivitás",
"Error": "Hiba",
"Ended": "Befejezve",
"Ended": "Vége lett",
"EnableCompletedDownloadHandlingHelpText": "A befejezett letöltések automatikus importálása a letöltési kliensből",
"EnableColorImpairedModeHelpText": "Megváltoztatott színek, hogy a színvak felhasználók jobban meg tudják különböztetni a színkódolt információkat",
"EnableAutomaticSearchHelpTextWarning": "Interaktív keresés esetén is felhasználható",
@@ -434,7 +434,7 @@
"RadarrSupportsAnyIndexer": "A Radarr minden indexert támogat, amely a Newznab szabványt használja, valamint az alább felsorolt egyéb indexereket.",
"RadarrSupportsAnyDownloadClient": "A Radarr számos népszerű torrent és usenet letöltési ügyfelet támogat.",
"QuickImport": "Automatikus Áthelyezés",
"Queued": "Sorban",
"Queued": "Sorba helyezve",
"Queue": "Várakozási sor",
"QualitySettingsSummary": "Minőségi méretek és elnevezés",
"QualitySettings": "Minőségi beállítások",
@@ -616,7 +616,7 @@
"Level": "Szint",
"LaunchBrowserHelpText": " Nyisson meg egy böngészőt, és az alkalmazás indításakor lépjen a Radarr kezdőlapjára.",
"LastWriteTime": "Utolsó írási idő",
"LastDuration": "Utolsó Hossza",
"LastDuration": "Utolsó időtartam",
"Languages": "Nyelvek",
"LanguageHelpText": "Nyelv a Releasekhez",
"Language": "Nyelv",
@@ -896,7 +896,7 @@
"HomePage": "Kezdőlap",
"LogOnly": "Csak naplózás",
"LastUsed": "Utoljára használt",
"LastExecution": "Utoljára végrehajtott",
"LastExecution": "Utol végrehajtás",
"Large": "Óriási",
"KeepAndUnmonitorMovie": "Megtartás és megfigyelés kikapcsolása a filmnél",
"InvalidFormat": "Érvénytelen Formátum",
@@ -963,7 +963,7 @@
"RadarrUpdated": "A Radarr frissítve",
"RadarrCalendarFeed": "Radarr Naptár Feed",
"QueueIsEmpty": "A várakozási sor üres",
"QualityProfileInUse": "A minőség profil ami már filmhez van rendelve, nem törölhető",
"QualityProfileInUse": "A filmhez, listához vagy gyűjteményhez csatolt minőségi profil nem törölhető",
"QualityLimitsHelpText": "A korlátozások automatikusan beállítódnak a film hossza szerint.",
"QualitiesHelpText": "A nagyobb minőség a listában jobban preferált. Ugyenazon minőségek a csoportban egyenlőek. Csak a bejelölt minőségek a kívánt minőségek",
"Qualities": "Minőségek",
@@ -992,7 +992,7 @@
"NoLinks": "Nincsenek Linkek",
"NoEventsFound": "Nem található esemény",
"NoAltTitle": "Nincs alternatív cím.",
"NextExecution": "Következő futtatás",
"NextExecution": "Következő végrehajtás",
"Negated": "Megtagadva",
"Negate": "Megtagadás",
"MultiLanguage": "Többnyelvű",
@@ -1105,7 +1105,7 @@
"Never": "Soha",
"Rating": "Értékelés",
"SetReleaseGroup": "Kiadási csoport beállítása",
"Started": "Elindult",
"Started": "Elkezdődött",
"Waiting": "Várakozás",
"OnApplicationUpdateHelpText": "Alkalmazásfrissítésről",
"SizeLimit": "Méretkorlát",
@@ -1142,5 +1142,8 @@
"OnMovieAddedHelpText": "Film hozzáadásakor",
"ShowPosters": "Poszterek megjelenítése",
"InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve",
"RottenTomatoesRating": "Tomato Értékelés"
"RottenTomatoesRating": "Tomato Értékelés",
"TotalMovies": "Összes film",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is",
"ApplicationURL": "Alkalmazás URL-je"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -254,5 +254,6 @@
"Language": "språk",
"List": "Liste",
"New": "Ny",
"RemotePathMappings": "Ekstern portmapping"
"RemotePathMappings": "Ekstern portmapping",
"Languages": "språk"
}

View File

@@ -1006,7 +1006,7 @@
"TorrentDelayTime": "Torrent-vertraging: {0}",
"TorrentsDisabled": "Torrents uitgeschakeld",
"Trace": "Spoor",
"Trailer": "Aanhangwagen",
"Trailer": "Trailer",
"Trakt": "Trakt",
"Trigger": "In gang zetten",
"UnableToImportCheckLogs": "Gedownload - Kan niet importeren: controleer de logboeken voor details",
@@ -1105,5 +1105,7 @@
"RssSyncHelpText": "Tussentijd in minuten. Schakel uit met 0 (dit stopt het automatisch ophalen van uitgaves)",
"NoCollections": "Geen films gevonden, om te beginnen, voeg een nieuwe film toe of importeer bestaande films.",
"Collections": "Collectie",
"MonitorMovies": "Bewaak Film"
"MonitorMovies": "Bewaak Film",
"ApplicationURL": "Applicatie URL",
"ApplicationUrlHelpText": "De externe URL van deze applicatie inclusief http(s)://,Port en URL base"
}

View File

@@ -289,7 +289,7 @@
"Presets": "Presety",
"ProxyType": "Typ proxy",
"PtpOldSettingsCheckMessage": "Następujące indeksatory PassThePopcorn mają przestarzałe ustawienia i należy je zaktualizować: {0}",
"QualityProfileInUse": "Nie można usunąć profilu jakości dołączonego do filmu",
"QualityProfileInUse": "Nie można usunąć profilu jakości przypisanego do filmu, listy bądź kolekcji",
"QueueIsEmpty": "Kolejka jest pusta",
"RadarrCalendarFeed": "Kanał kalendarza radarowego",
"RadarrUpdated": "Radarr zaktualizowany",
@@ -1060,9 +1060,9 @@
"SelectLanguages": "Wybierz język",
"AllCollectionsHiddenDueToFilter": "Wszystkie kolekcje są ukryte ze względu na zastosowany filtr.",
"Collections": "Kolekcje",
"RssSyncHelpText": "Odstęp w minutach. Ustaw na zero, aby wyłączyć (zatrzyma to wszystkie automatyczne przechwytywanie zwolnień)",
"RssSyncHelpText": "Odstęp w minutach. Ustaw na zero, aby wyłączyć (zatrzyma to wszystkie automatyczne przechwytywanie)",
"NoCollections": "Nie znaleziono żadnych filmów. Aby rozpocząć, musisz dodać nowy film lub zaimportować istniejące",
"MonitorMovies": "Monitoruj film",
"MonitorMovies": "Monitoruj filmy",
"ClickToChangeReleaseGroup": "Kliknij, by zmienić grupę wydającą",
"RemotePathMappingCheckDownloadPermissions": "Radarr widzi film {0}, lecz nie ma do niego dostępu. Najprawdopodobniej to wynik błędu w uprawnieniach dostępu.",
"NotificationTriggersHelpText": "Wybierz zdarzenia, które mają uruchamiać to powiadomienie",
@@ -1078,7 +1078,7 @@
"ManualImportSetReleaseGroup": "Import ręczny - podaj nazwę grupy",
"Never": "Nigdy",
"RemotePathMappingCheckFilesWrongOSPath": "Zdalny klient pobierania {0} zgłosił pliki w {1}, lecz nie jest to poprawna ścieżka {2}. Zmień ustawienia zdalnego mapowania ścieżek i klienta pobierania.",
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, lecz Radarr nie widzi tego folderu. Być może należy zmienić uprawnienia dostępu do folderu.",
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, ale Radarr nie widzi tego folderu. Prawdopodobnie należy zmienić uprawnienia dostępu do folderu.",
"RemoveFailed": "Usuń nieudane",
"RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.",
"ShowCollectionDetails": "Pokaż stan kolekcji",
@@ -1141,5 +1141,9 @@
"CollectionShowPostersHelpText": "Pokaż plakaty elementów kolekcji",
"CollectionOptions": "Opcje Kolekcji",
"CollectionShowDetailsHelpText": "Pokaż status i właściwości kolekcji",
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji"
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji",
"TotalMovies": "Filmów całkowicie",
"RottenTomatoesRating": "Ocena Tomato",
"ApplicationUrlHelpText": "Zewnętrzny URL tej aplikacji zawiera http(s)://, port i adres URL",
"ApplicationURL": "Link do aplikacji"
}

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