Compare commits

...

105 Commits

Author SHA1 Message Date
gains goblin 9005860899 Fixed: Mapping Author GR ID from import lists to AuthorGoodReadsId 2024-12-15 16:28:18 -06:00
Bogdan c67f67109e Ignore metadata tests temporarily once again 2024-12-15 23:05:58 +02:00
Bogdan 51b9744e25 Fixed: Refresh backup list on deletion
(cherry picked from commit 3b00112447361b19c04851a510e63f812597a043)
2024-12-15 05:32:13 +02:00
Mark McDowall 334d824633 Fixed: Error getting processes in some cases
(cherry picked from commit b552d4e9f7ca7388404aa0d52566010a54cb0244)
2024-12-15 05:31:53 +02:00
Weblate ae01387ca9 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: hhjuhl <hans@kopula.dk>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translation: Servarr/Readarr
2024-12-14 02:35:17 +02:00
Weblate 4eb13e0938 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: 4kwins <hanszimmerme@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Ardenet <1213193613@qq.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Mizuyoru_TW <mizuyoru.tw@gmail.com>
Co-authored-by: Robin Dadswell <robin@robindadswell.tech>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: keysuck <joshkkim@gmail.com>
Co-authored-by: mryx007 <mryx@mail.de>
Co-authored-by: thelooter <evekolb2204@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_HANS/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_Hans/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2024-12-08 12:36:16 +02:00
Servarr 6dbb826f2f Automated API Docs update 2024-12-08 12:35:20 +02:00
Bogdan 52dfa57dd7 Bump version to 0.4.6 2024-12-08 12:26:41 +02:00
Mark McDowall f354b3bc47 New: Support for new SABnzbd history retention values
(cherry picked from commit e361f18837d98c089f7dc9c0190221ca8e2cf225)

Closes #3885
2024-12-04 18:03:20 +02:00
Bogdan 2d9e6788e6 Bump Polly, Npgsql, PdfSharpCore and ImageSharp 2024-12-04 18:02:21 +02:00
soup 0d121fe9c0 New: Add config file setting for CGNAT authentication bypass
(cherry picked from commit 4c41a4f368046f73f82306bbd73bec992392938b)

Closes #3903
2024-12-04 17:56:17 +02:00
Emmanuel Ferdman 892c34fe35 Fix license link in API docs (#3910)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2024-12-02 17:15:09 +02:00
Gylesie 24f6007594 Remove unnecessary heap allocations in local IP check
(cherry picked from commit ed536a85ad5f2062bf6f01f80efddb19fa935f63)
2024-12-02 02:34:56 +02:00
Mark McDowall 5028ed4027 Webpack web target
(cherry picked from commit a90866a73e6cff9a286c23e60c74672f4c0d317a)
2024-11-27 12:25:08 +02:00
Bogdan 05f303436b Bump version to 0.4.5 2024-11-23 19:52:18 +02:00
Bogdan 5635de96a8 Fixed: Initial state for qBittorrent v5.0
(cherry picked from commit ff724b7f4099284b8062f1625cf07b7822782edf)
2024-11-15 20:01:26 -06:00
Mickaël Thomas ce59f32023 New: Support stoppedUP and stoppedDL states from qBittorrent
(cherry picked from commit 73a4bdea5247ee87e6bbae95f5325e1f03c88a7f)
2024-11-15 20:01:26 -06:00
bakerboy448 6d675a5207 Fix Goodreads test 2024-11-15 17:57:51 -06:00
Bogdan b093b23900 Pin ReportGenerator in Azure Pipelines for .NET 6
(cherry picked from commit 50ce480abf043140e209d2d2959fbea8dd5dd2ab)
2024-11-15 15:45:16 -06:00
Weblate 884ac2cb6f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2024-11-07 17:04:03 +02:00
Weblate 295a6c4255 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2024-11-05 02:26:50 +02:00
Mark McDowall 74a59d5790 Use current time for cache break in development
(cherry picked from commit 020ed32fcfab1c6fbe57af5ea650300272c93fd7)
2024-11-05 02:25:32 +02:00
Bogdan ae23e5f187 Bump version to 0.4.4 2024-11-03 11:43:03 +02:00
Bogdan ba2add0d54 Fix app name in translations 2024-11-02 21:33:05 +02:00
Servarr b6ebeb31c8 Multiple Translations updated by Weblate (#3723)
ignore-downstream












Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Ardenet <1213193613@qq.com>
Co-authored-by: Kuzmich <kuzmich55@gmail.com>
Co-authored-by: Mathias <mathias@rodilbach.dk>
Co-authored-by: angelsky11 <angelsky11@gmail.com>
Co-authored-by: genoher <genoher@gmail.com>
Co-authored-by: jsain <josip.sain@gmail.com>
Co-authored-by: liuwqq <843384478@qq.com>
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2024-11-02 21:29:00 +02:00
Bogdan b8bd645560 Fixed: Prevent UI errors for authors with invalid quality and metadata profiles 2024-11-02 01:04:11 +02:00
Bogdan e0d904fa69 Improve message for grab errors due to no matching tags
Co-authored-by: zakary <zak@ary.dev>
(cherry picked from commit df672487cf1d5f067849367a2bfb0068defc315d)

Closes #3814
2024-10-27 09:50:14 +02:00
Bogdan cb532caca4 Fixed: Status check for completed directories in Deluge
(cherry picked from commit 33139d4b53c1adad769c7e2b0510e8990c66b84a)
2024-10-27 09:47:15 +02:00
Bogdan e1af8ad37f Bump version to 0.4.3 2024-10-27 09:46:46 +02:00
Bogdan c4f30da648 Cleanse exceptions in event logs
(cherry picked from commit 404e6d68ea526ab521cd39ecda1bf3b02285765d)
2024-10-27 09:45:22 +02:00
Bogdan b83a760873 Bump frontend packages 2024-10-20 13:22:14 +03:00
Bogdan 22ab50f76d Bump dotnet to 6.0.35 2024-10-20 13:22:14 +03:00
Mark McDowall 66758ca006 New: Show update settings on all platforms
(cherry picked from commit c023fc700896c7f0751c4ac63c4e1a89d6e1a9bb)
2024-10-20 11:43:51 +03:00
Mark McDowall e7d7bc79f4 New: Allow major version updates to be installed
(cherry picked from commit 0e95ba2021b23cc65bce0a0620dd48e355250dab)
2024-10-20 11:43:51 +03:00
Bogdan cfccb4f9c3 Bump version to 0.4.2 2024-10-20 08:07:10 +03:00
Mark McDowall 9312f17041 New: Use 307 redirect for requests missing URL Base
(cherry picked from commit 39074b0b1d040969f86d787c2346d5ed5a9f72dc)
2024-10-08 02:20:37 +03:00
Bogdan 8192c22910 Bump macOS runner version to 13 2024-10-06 16:30:37 +03:00
ManiMatter 0b1d6b677a Add '.temp*' to .gitignore (#3778) 2024-10-02 22:53:34 +03:00
Bogdan d666df0189 Bump version to 0.4.1 2024-09-29 08:19:59 +03:00
Bogdan 10d8f345c1 Display naming example errors when all fields are empty
(cherry picked from commit 768af433d1655c587a9eee9b100f306ba4345f88)
2024-09-28 05:18:41 +03:00
Robin Dadswell fb720b8714 Fixed: Telegram log message including token
(cherry picked from commit a7cb264cc8013d9a56aee7d5e41acfd76cde5f96)
2024-09-28 05:18:29 +03:00
Servarr e8131b5791 Automated API Docs update 2024-09-25 10:34:30 +03:00
Bogdan 4f793f6b93 Remove $ from Discord delete notifications 2024-09-25 10:28:00 +03:00
Bogdan 4215c21c94 Add package needed for RemoveDiacritics 2024-09-23 05:46:26 +03:00
Paul DiLoreto 6913789adc New: Use instance name in forms authentication cookie name (#3761)
(cherry picked from commit 97ebaf279650082c6baee9563ef179921c5ed25a)
(cherry picked from commit faf9173b3b4a298e3afa9a186e66ba6764ac055e)
(cherry picked from commit 75fae9262c6ca003d24df9fcf035d75b1e90f994)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-09-23 05:45:01 +03:00
Mark McDowall 09e0c40792 Fixed: Limit redirects after login to local paths
(cherry picked from commit 14005d8d1054eafaba808337a109d5812f3e79e6)
2024-09-23 05:41:21 +03:00
Mark McDowall baff805551 New: Return downloading magnets from Transmission
(cherry picked from commit 11a9dcb3890eaf99602900f37e64007f2fbf9b8e)
2024-09-23 05:40:25 +03:00
Bogdan c885fe43cd Fix disabled style for monitor toggle button
(cherry picked from commit dde28cbd7e16b85f78d38c8dde7cf6bbb6119bb3)
2024-09-23 05:39:36 +03:00
Treycos 464a777722 Updated code action fixall value for VSCode
(cherry picked from commit 8af4246ff9baee4c291550102769a1186f65dc29)
2024-09-23 05:39:19 +03:00
momo 89e5999c85 Fix description for API key as query parameter
(cherry picked from commit 30c36fdc3baa686102ff124833c7963fc786f251)
2024-09-23 05:36:05 +03:00
Bogdan b6fa332550 Ignore metadata tests temporarily once again 2024-09-23 05:35:27 +03:00
Bogdan 05f262dc0a Don't persist value for SslCertHash when checking for existence
(cherry picked from commit 98c4cbdd13dc49ad30e91343897b8bd006002489)
2024-09-07 16:26:28 -05:00
Bogdan 699b765ee9 Remove provider status on provider deletion
(cherry picked from commit f45713bff815b2a49a5cdad4afe62a53bbdf6a6e)
2024-09-07 16:26:11 -05:00
Mark McDowall 84beba2383 Don't hash files in development builds
(cherry picked from commit bc7799139e52b92956eb595fb87f44d7dda9a320)
2024-09-07 13:46:26 -05:00
Mark McDowall 62eceb9148 New: Default file log level changed to debug
(cherry picked from commit 9b528eb82914a05cfc3b67d4d6146ce51e86f68d)
2024-09-07 13:45:57 -05:00
Servarr f46070d4b0 Translations update from Servarr Weblate (#3578)
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: Gabriel Markowski <gmarkowski62@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Kerk en IT <info@kerkenit.nl>
Co-authored-by: MattiaPell <mattiapellegrini16@gmail.com>
Co-authored-by: Nota Inutilis <hugo@notainutilis.fr>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: iMohmmedSA <i.mohmmed.i+1@gmail.com>
2024-09-07 13:44:59 -05:00
Bogdan 73979c416a Bump ImageSharp to 3.1.5
https://github.com/advisories/GHSA-63p8-c4ww-9cg7
2024-07-26 00:09:31 +03:00
ManiMatter 348e8f9c27 Treat forcedMetaDL from qBit as queued instead of downloading
(cherry picked from commit 9a613afa355fbc8cdf29c4d1b8eb1f1586405eb7)
2024-07-25 08:15:40 +03:00
Bogdan 38bdb5a75d New: Ignore Litestream tables in Database
(cherry picked from commit 2a26c6722afa5c657fde162cbddbe9e8731f3a0c)
2024-07-25 07:38:19 +03:00
Bogdan 5e4c51e2f7 Bump version to 0.4.0 2024-07-21 18:08:49 +03:00
Bogdan 99a65246a9 New: Bump dotnet to 6.0.32 2024-07-19 23:27:15 +03:00
Qstick 598ce9a9d2 Update SonarCloud pipeline versions
* Update SonarCloud pipeline versions

* Update reportgenerator to remove PublishCodeCoverage dep warnings

(cherry picked from commit a2a12d245000a0713946cec732d853dd7cdc58c2)
(cherry picked from commit 1423ad6aa4094d11efecc1986a3d0571f310bda6)

Update SonarCloud pipeline versions for UI

(cherry picked from commit 558043f1b2cae371b474a19ba5784df8345d38d2)
(cherry picked from commit 9045dea5364ff8a0677adc25e478e908e6b0ee11)
2024-07-19 23:27:15 +03:00
Weblate 42d6b9e703 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Kshitij Burman <kburman6@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: MattiaPell <mattiapellegrini16@gmail.com>
Co-authored-by: Mipiaceanutella <remix-polity-0l@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: PouleY <pouley@bellemainp.fr>
Co-authored-by: Rauniik <raunerjakub@gmail.com>
Co-authored-by: Serhii Matrunchyk <serhii@digitalidea.studio>
Co-authored-by: Taylan Tatlı <taylantatli90@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yongzz <zy28341215@icloud.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: quek76 <quek@libertysurf.fr>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2024-07-19 00:08:01 +03:00
Marc Carbonell 8f595838aa Remove extraneous indentation in RemoveFileExtension
(cherry picked from commit dca5239420e21f91c1d67bc8bbb14cdb13c8d5d9)

Closes #3562
2024-07-18 20:13:29 +03:00
Stevie Robinson 3d9d7d3582 New: Wrap specifications in Custom Format modals
(cherry picked from commit 7b8d606a1bed6257d7942de47576c1505fd9cb57)

Towards #3570
2024-07-18 20:11:53 +03:00
servarr[bot] 77cf28bd78 Fixed: Assume category path from qBittorent starting with '//' is a Windows UNC path
* Fixed: Assume category path from qBittorent starting with '//' is a Windows UNC path

(cherry picked from commit 19466aa29050e1b13b1db8cc61662b10d76a82e4)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2024-07-18 20:06:41 +03:00
Bogdan 2fb1b8af20 Bump version to 0.3.32 2024-07-14 12:31:38 +03:00
Bogdan af1f389f8e Fixed: Validate metadata and quality profiles for root folders
Don't allow `0` as possible value for metadata and quality profiles, and permit to edit root folders with bad values in UI.
2024-07-12 16:37:16 +03:00
Bogdan b5334da253 Fixed: Creating root folders without default tags 2024-07-12 16:32:19 +03:00
Bogdan 68b3904382 Bump version to 0.3.31 2024-07-07 21:55:01 +03:00
Bogdan c8b09b9e29 Fixed: Already imported downloads appearing in Queue briefly
(cherry picked from commit 8099ba10afded446779290de29b1baaf0be932c3)

Closes #3538
2024-07-01 08:45:01 +03:00
Bogdan d910fc42ab Bump mac image to 12 2024-06-26 23:51:49 +03:00
Mark McDowall a6db8bfe0e New: Ignore Deluge torrents without a title
(cherry picked from commit a0d29331341320268552660658b949179c963793)
2024-06-26 02:46:53 +03:00
Bogdan 2033d7e411 Fixed: Exclude invalid releases from Newznab and Torznab parsers
(cherry picked from commit fb060730c7d52cd342484dc68595698a9430df7b)
2024-06-26 02:46:39 +03:00
dependabot[bot] 4a04e54ceb Bump ws from 7.5.9 to 7.5.10
Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-23 22:41:14 +03:00
Bogdan d57a9ab9b0 Bump version to 0.3.30 2024-06-23 21:53:58 +03:00
dependabot[bot] d333204194 Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 15:07:54 +03:00
Servarr c3676f8d33 Automated API Docs update 2024-06-11 15:03:22 +03:00
Bogdan 932356be61 Ignore Grabbed from API docs
Run application in docs.sh specific to platform

(cherry picked from commit c331c8bd119fa9f85a53e96db04f541b2d90bbd3)

Closes #3515
2024-06-11 14:14:57 +03:00
servarr[bot] 5b1b2a2d67 Fixed: Improve error messaging if config file isn't formatted correctly 2024-06-11 14:05:24 +03:00
Bogdan c362e8c467 Fixed: Ignore case when resolving indexer by name in release push
(cherry picked from commit a90ab1a8fd50126d7f60eaa684eac1e0cd98e2b7)
2024-06-11 14:02:24 +03:00
Bogdan 67c00a8cc7 Fixed: Ignore case for name validation in providers
(cherry picked from commit 0edc5ba99a15c5f80305b387a053f35fc3f6e51b)
2024-06-11 14:02:04 +03:00
Bogdan 27a086dfff Bump version to 0.3.29 2024-06-09 12:58:59 +03:00
sillock1 8ee0df9c65 Fixed: Trimming slashes from UrlBase when using environment variable 2024-06-06 14:47:25 +03:00
sillock1 da30b55902 New: Optionally use Environment Variables for settings in config.xml 2024-06-06 14:47:25 +03:00
Servarr c7226fc85f Automated API Docs update 2024-06-06 12:27:57 +03:00
Mark McDowall 84f22dbadc New: Require password confirmation when setting or changing password
(cherry picked from commit b248163df598dc611ee919d525eb7357256d73d5)

Closes #3089
2024-06-06 11:46:54 +03:00
Mark McDowall 06a53ef9ca New: Authentication is now required
(cherry picked from commit d3018fb5015af26a897281f0e892b706cdb6e821)

Closes #1807
Closes #2878
Closes #2873
2024-06-06 11:46:48 +03:00
Mark McDowall b5ef0cda1e New: Setting to disable authentication for local addresses
(cherry picked from commit b154b00c6156512e7fbd0a2c4833c116a74f23ca)

Closes #1804
Closes #2077
2024-06-06 11:44:37 +03:00
Weblate 1b1290efac Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yi Cao <caoyi06@qq.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: mm519897405 <baiya@vip.qq.com>
Co-authored-by: r0bertreh <Robert.reh@live.de>
Co-authored-by: topnew <sznetim@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2024-06-03 09:15:48 +03:00
Bogdan dcbc3ea3f8 Update the wanted section for missing and cutoff unmet
(cherry picked from commit 9b4ff657af41e67aeb5866ee3056f1a8f2a901ea)
2024-05-22 03:20:10 +03:00
Bogdan 9a7b2cb818 Fix Goodreads test 2024-05-19 19:33:19 +03:00
Bogdan f9cba39f0a Ignore metadata tests temporarily 2024-05-19 19:33:19 +03:00
Bogdan 6b6ff4fe76 New: Link to "Why Searches Could be Failing" wiki section 2024-05-19 19:33:19 +03:00
Bogdan 05d0fe2da6 Bump version to 0.3.28 2024-05-13 15:31:57 +03:00
Bogdan 7aab2b49e2 Bump Npgsql to 7.0.7 2024-05-13 15:26:43 +03:00
Weblate 8887df92ed Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translation: Servarr/Readarr
2024-05-10 14:05:03 +03:00
Bogdan 9ee651d6c0 Refactor PasswordInput to use type password
(cherry picked from commit c7c1e3ac9e5bffd4d92298fed70916e3808613fd)
2024-05-10 14:04:07 +03:00
Bogdan 5544e169a6 Use number input for seed ratio
(cherry picked from commit 1eddf3a152fae04142263c02a3e3b317ff2feeb2)

Plus translations

Closes #3470
2024-05-10 01:37:15 +03:00
Bogdan 11d83165e5 Fixed: Notifications with only On Rename enabled 2024-05-10 01:34:57 +03:00
Bogdan 9e6d1c581c Fixed: Parsing long downloading/seeding values from Transmission
(cherry picked from commit 8360dd7a7bab1dfb49a40aae382b47e9253d9fd1)
2024-05-09 05:50:16 +03:00
Weblate 66e20a0aec Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translation: Servarr/Readarr
2024-05-05 12:35:40 +03:00
Bogdan e639b36283 Fixed: Indexer flags for torrent release pushes
(cherry picked from commit 47ba002806fe2c2004a649aa193ae318343a84e4)
2024-05-05 12:35:12 +03:00
Mark McDowall c9f4fb141f Forward X-Forwarded-Host header
(cherry picked from commit 3fbe4361386e9fb8dafdf82ad9f00f02bec746cc)
2024-05-05 12:35:02 +03:00
Bogdan 29a43fc2fd Bump version to 0.3.27 2024-05-05 12:34:33 +03:00
185 changed files with 7686 additions and 4794 deletions
+1
View File
@@ -120,6 +120,7 @@ _artifacts
_rawPackage/ _rawPackage/
_dotTrace* _dotTrace*
_tests/ _tests/
_temp*
*.Result.xml *.Result.xml
coverage*.xml coverage*.xml
coverage*.json coverage*.json
+9 -14
View File
@@ -9,18 +9,18 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.3.26' majorVersion: '0.4.6'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)' readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)' buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.421' dotnetVersion: '6.0.427'
nodeVersion: '20.X' nodeVersion: '20.X'
innoVersion: '6.2.0' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04' linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11' macImage: 'macOS-13'
trigger: trigger:
branches: branches:
@@ -1102,7 +1102,7 @@ stages:
vmImage: ${{ variables.windowsImage }} vmImage: ${{ variables.windowsImage }}
steps: steps:
- checkout: self # Need history for Sonar analysis - checkout: self # Need history for Sonar analysis
- task: SonarCloudPrepare@1 - task: SonarCloudPrepare@2
env: env:
SONAR_SCANNER_OPTS: '' SONAR_SCANNER_OPTS: ''
inputs: inputs:
@@ -1114,7 +1114,7 @@ stages:
cliProjectName: 'ReadarrUI' cliProjectName: 'ReadarrUI'
cliProjectVersion: '$(readarrVersion)' cliProjectVersion: '$(readarrVersion)'
cliSources: './frontend' cliSources: './frontend'
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@2
- job: Api_Docs - job: Api_Docs
displayName: API Docs displayName: API Docs
@@ -1190,7 +1190,7 @@ stages:
submodules: true submodules: true
- powershell: Set-Service SCardSvr -StartupType Manual - powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service displayName: Enable Windows Test Service
- task: SonarCloudPrepare@1 - task: SonarCloudPrepare@2
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs: inputs:
SonarCloud: 'SonarCloud' SonarCloud: 'SonarCloud'
@@ -1208,21 +1208,16 @@ stages:
./build.sh --backend -f net6.0 -r win-x64 ./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@2
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results displayName: Publish SonarCloud Results
- task: reportgenerator@4 - task: reportgenerator@5.3.11
displayName: Generate Coverage Report displayName: Generate Coverage Report
inputs: inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml' reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined' targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
- task: PublishCodeCoverageResults@1 publishCodeCoverageResults: true
displayName: Publish Coverage Report
inputs:
codeCoverageTool: 'cobertura'
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
reportDirectory: './CoverageResults/combined/'
- stage: Report_Out - stage: Report_Out
dependsOn: dependsOn:
+12 -2
View File
@@ -1,3 +1,7 @@
#!/bin/bash
set -e
FRAMEWORK="net6.0"
PLATFORM=$1 PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then if [ "$PLATFORM" = "Windows" ]; then
@@ -21,15 +25,21 @@ slnFile=src/Readarr.sln
platform=Posix platform=Posix
if [ "$PLATFORM" = "Windows" ]; then
application=Readarr.Console.dll
else
application=Readarr.dll
fi
dotnet clean $slnFile -c Debug dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest dotnet new tool-manifest
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/Readarr.console.dll" v1 & dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 &
sleep 45 sleep 45
+1 -1
View File
@@ -9,7 +9,7 @@
"editor.formatOnSave": false, "editor.formatOnSave": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll": "explicit"
}, },
"typescript.preferences.quoteStyle": "single", "typescript.preferences.quoteStyle": "single",
+4 -3
View File
@@ -26,6 +26,7 @@ module.exports = (env) => {
const config = { const config = {
mode: isProduction ? 'production' : 'development', mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map', devtool: isProduction ? 'source-map' : 'eval-source-map',
target: 'web',
stats: { stats: {
children: false children: false
@@ -67,7 +68,7 @@ module.exports = (env) => {
output: { output: {
path: distFolder, path: distFolder,
publicPath: '/', publicPath: '/',
filename: '[name]-[contenthash].js', filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
sourceMapFilename: '[file].map' sourceMapFilename: '[file].map'
}, },
@@ -92,7 +93,7 @@ module.exports = (env) => {
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: 'Content/styles.css', filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css' chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
@@ -202,7 +203,7 @@ module.exports = (env) => {
options: { options: {
importLoaders: 1, importLoaders: 1,
modules: { modules: {
localIdentName: '[name]/[local]/[hash:base64:5]' localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
} }
} }
}, },
+2 -2
View File
@@ -32,7 +32,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector';
import Logs from 'System/Logs/Logs'; import Logs from 'System/Logs/Logs';
import Status from 'System/Status/Status'; import Status from 'System/Status/Status';
import Tasks from 'System/Tasks/Tasks'; import Tasks from 'System/Tasks/Tasks';
import UpdatesConnector from 'System/Updates/UpdatesConnector'; import Updates from 'System/Updates/Updates';
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector'; import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
@@ -247,7 +247,7 @@ function AppRoutes(props) {
<Route <Route
path="/system/updates" path="/system/updates"
component={UpdatesConnector} component={Updates}
/> />
<Route <Route
+15
View File
@@ -1,6 +1,7 @@
import AuthorsAppState from './AuthorsAppState'; import AuthorsAppState from './AuthorsAppState';
import CommandAppState from './CommandAppState'; import CommandAppState from './CommandAppState';
import SettingsAppState from './SettingsAppState'; import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
import TagsAppState from './TagsAppState'; import TagsAppState from './TagsAppState';
interface FilterBuilderPropOption { interface FilterBuilderPropOption {
@@ -35,10 +36,24 @@ export interface CustomFilter {
filers: PropertyFilter[]; filers: PropertyFilter[];
} }
export interface AppSectionState {
isConnected: boolean;
isReconnecting: boolean;
version: string;
prevVersion?: string;
dimensions: {
isSmallScreen: boolean;
width: number;
height: number;
};
}
interface AppState { interface AppState {
app: AppSectionState;
authors: AuthorsAppState; authors: AuthorsAppState;
commands: CommandAppState; commands: CommandAppState;
settings: SettingsAppState; settings: SettingsAppState;
system: SystemAppState;
tags: TagsAppState; tags: TagsAppState;
} }
+7 -2
View File
@@ -1,5 +1,6 @@
import AppSectionState, { import AppSectionState, {
AppSectionDeleteState, AppSectionDeleteState,
AppSectionItemState,
AppSectionSaveState, AppSectionSaveState,
} from 'App/State/AppSectionState'; } from 'App/State/AppSectionState';
import DownloadClient from 'typings/DownloadClient'; import DownloadClient from 'typings/DownloadClient';
@@ -7,13 +8,16 @@ import ImportList from 'typings/ImportList';
import Indexer from 'typings/Indexer'; import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag'; import IndexerFlag from 'typings/IndexerFlag';
import Notification from 'typings/Notification'; import Notification from 'typings/Notification';
import { UiSettings } from 'typings/UiSettings'; import General from 'typings/Settings/General';
import UiSettings from 'typings/Settings/UiSettings';
export interface DownloadClientAppState export interface DownloadClientAppState
extends AppSectionState<DownloadClient>, extends AppSectionState<DownloadClient>,
AppSectionDeleteState, AppSectionDeleteState,
AppSectionSaveState {} AppSectionSaveState {}
export type GeneralAppState = AppSectionItemState<General>;
export interface ImportListAppState export interface ImportListAppState
extends AppSectionState<ImportList>, extends AppSectionState<ImportList>,
AppSectionDeleteState, AppSectionDeleteState,
@@ -33,11 +37,12 @@ export type UiSettingsAppState = AppSectionState<UiSettings>;
interface SettingsAppState { interface SettingsAppState {
downloadClients: DownloadClientAppState; downloadClients: DownloadClientAppState;
general: GeneralAppState;
importLists: ImportListAppState; importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState; indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState; indexers: IndexerAppState;
notifications: NotificationAppState; notifications: NotificationAppState;
uiSettings: UiSettingsAppState; ui: UiSettingsAppState;
} }
export default SettingsAppState; export default SettingsAppState;
+13
View File
@@ -0,0 +1,13 @@
import SystemStatus from 'typings/SystemStatus';
import Update from 'typings/Update';
import AppSectionState, { AppSectionItemState } from './AppSectionState';
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
export type UpdateAppState = AppSectionState<Update>;
interface SystemAppState {
updates: UpdateAppState;
status: SystemStatusAppState;
}
export default SystemAppState;
@@ -5,6 +5,7 @@ import dimensions from 'Styles/Variables/dimensions';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate'; import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import AuthorIndexOverviewInfoRow from './AuthorIndexOverviewInfoRow'; import AuthorIndexOverviewInfoRow from './AuthorIndexOverviewInfoRow';
import styles from './AuthorIndexOverviewInfo.css'; import styles from './AuthorIndexOverviewInfo.css';
@@ -76,9 +77,9 @@ function getInfoRowProps(row, props) {
}; };
} }
if (name === 'qualityProfileId') { if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
return { return {
title: 'Quality Profile', title: translate('QualityProfile'),
iconName: icons.PROFILE, iconName: icons.PROFILE,
label: props.qualityProfile.name label: props.qualityProfile.name
}; };
@@ -235,12 +235,12 @@ class AuthorIndexPoster extends Component {
</div> </div>
} }
{ {showQualityProfile && !!qualityProfile?.name ? (
showQualityProfile && <div className={styles.title} title={translate('QualityProfile')}>
<div className={styles.title}> {qualityProfile.name}
{qualityProfile.name} </div>
</div> ) : null}
}
{ {
nextAiring && nextAiring &&
<div className={styles.nextAiring}> <div className={styles.nextAiring}>
@@ -209,7 +209,7 @@ class AuthorIndexRow extends Component {
key={name} key={name}
className={styles[name]} className={styles[name]}
> >
{qualityProfile.name} {qualityProfile?.name ?? ''}
</VirtualTableRowCell> </VirtualTableRowCell>
); );
} }
@@ -220,7 +220,7 @@ class AuthorIndexRow extends Component {
key={name} key={name}
className={styles[name]} className={styles[name]}
> >
{metadataProfile.name} {metadataProfile?.name ?? ''}
</VirtualTableRowCell> </VirtualTableRowCell>
); );
} }
@@ -5,6 +5,7 @@ import dimensions from 'Styles/Variables/dimensions';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate'; import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import BookIndexOverviewInfoRow from './BookIndexOverviewInfoRow'; import BookIndexOverviewInfoRow from './BookIndexOverviewInfoRow';
import styles from './BookIndexOverviewInfo.css'; import styles from './BookIndexOverviewInfo.css';
@@ -71,9 +72,9 @@ function getInfoRowProps(row, props) {
}; };
} }
if (name === 'qualityProfileId') { if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
return { return {
title: 'Quality Profile', title: translate('QualityProfile'),
iconName: icons.PROFILE, iconName: icons.PROFILE,
label: props.qualityProfile.name label: props.qualityProfile.name
}; };
@@ -250,12 +250,12 @@ class BookIndexPoster extends Component {
</div> </div>
} }
{ {showQualityProfile && !!qualityProfile?.name ? (
showQualityProfile && <div className={styles.title} title={translate('QualityProfile')}>
<div className={styles.title}> {qualityProfile.name}
{qualityProfile.name} </div>
</div> ) : null}
}
{ {
nextAiring && nextAiring &&
<div className={styles.nextAiring}> <div className={styles.nextAiring}>
@@ -195,7 +195,7 @@ class BookIndexRow extends Component {
key={name} key={name}
className={styles[name]} className={styles[name]}
> >
{qualityProfile.name} {qualityProfile?.name ?? ''}
</VirtualTableRowCell> </VirtualTableRowCell>
); );
} }
@@ -1,5 +0,0 @@
.input {
composes: input from '~Components/Form/TextInput.css';
font-family: $passwordFamily;
}
@@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import TextInput from './TextInput'; import TextInput from './TextInput';
import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input // Prevent a user from copying (or cutting) the password from the input
function onCopy(e) { function onCopy(e) {
@@ -13,17 +11,14 @@ function PasswordInput(props) {
return ( return (
<TextInput <TextInput
{...props} {...props}
type="password"
onCopy={onCopy} onCopy={onCopy}
/> />
); );
} }
PasswordInput.propTypes = { PasswordInput.propTypes = {
className: PropTypes.string.isRequired ...TextInput.props
};
PasswordInput.defaultProps = {
className: styles.input
}; };
export default PasswordInput; export default PasswordInput;
@@ -3,9 +3,9 @@
padding: 0; padding: 0;
font-size: inherit; font-size: inherit;
}
.isDisabled { &.isDisabled {
color: var(--disabledColor); color: var(--disabledColor);
cursor: not-allowed; cursor: not-allowed;
}
} }
+7
View File
@@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
import ColorImpairedContext from 'App/ColorImpairedContext'; import ColorImpairedContext from 'App/ColorImpairedContext';
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector'; import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
import SignalRConnector from 'Components/SignalRConnector'; import SignalRConnector from 'Components/SignalRConnector';
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
import locationShape from 'Helpers/Props/Shapes/locationShape'; import locationShape from 'Helpers/Props/Shapes/locationShape';
import PageHeader from './Header/PageHeader'; import PageHeader from './Header/PageHeader';
import PageSidebar from './Sidebar/PageSidebar'; import PageSidebar from './Sidebar/PageSidebar';
@@ -75,6 +76,7 @@ class Page extends Component {
isSmallScreen, isSmallScreen,
isSidebarVisible, isSidebarVisible,
enableColorImpairedMode, enableColorImpairedMode,
authenticationEnabled,
onSidebarToggle, onSidebarToggle,
onSidebarVisibleChange onSidebarVisibleChange
} = this.props; } = this.props;
@@ -108,6 +110,10 @@ class Page extends Component {
isOpen={this.state.isConnectionLostModalOpen} isOpen={this.state.isConnectionLostModalOpen}
onModalClose={this.onConnectionLostModalClose} onModalClose={this.onConnectionLostModalClose}
/> />
<AuthenticationRequiredModal
isOpen={!authenticationEnabled}
/>
</div> </div>
</ColorImpairedContext.Provider> </ColorImpairedContext.Provider>
); );
@@ -123,6 +129,7 @@ Page.propTypes = {
isUpdated: PropTypes.bool.isRequired, isUpdated: PropTypes.bool.isRequired,
isDisconnected: PropTypes.bool.isRequired, isDisconnected: PropTypes.bool.isRequired,
enableColorImpairedMode: PropTypes.bool.isRequired, enableColorImpairedMode: PropTypes.bool.isRequired,
authenticationEnabled: PropTypes.bool.isRequired,
onResize: PropTypes.func.isRequired, onResize: PropTypes.func.isRequired,
onSidebarToggle: PropTypes.func.isRequired, onSidebarToggle: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired onSidebarVisibleChange: PropTypes.func.isRequired
@@ -18,6 +18,7 @@ import {
import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions'; import { fetchTags } from 'Store/Actions/tagActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import ErrorPage from './ErrorPage'; import ErrorPage from './ErrorPage';
import LoadingPage from './LoadingPage'; import LoadingPage from './LoadingPage';
import Page from './Page'; import Page from './Page';
@@ -153,18 +154,21 @@ function createMapStateToProps() {
selectErrors, selectErrors,
selectAppProps, selectAppProps,
createDimensionsSelector(), createDimensionsSelector(),
createSystemStatusSelector(),
( (
enableColorImpairedMode, enableColorImpairedMode,
isPopulated, isPopulated,
errors, errors,
app, app,
dimensions dimensions,
systemStatus
) => { ) => {
return { return {
...app, ...app,
...errors, ...errors,
isPopulated, isPopulated,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
authenticationEnabled: systemStatus.authentication !== 'none',
enableColorImpairedMode enableColorImpairedMode
}; };
} }
+2 -2
View File
@@ -253,7 +253,7 @@ class SignalRConnector extends Component {
handleWantedCutoff = (body) => { handleWantedCutoff = (body) => {
if (body.action === 'updated') { if (body.action === 'updated') {
this.props.dispatchUpdateItem({ this.props.dispatchUpdateItem({
section: 'cutoffUnmet', section: 'wanted.cutoffUnmet',
updateOnly: true, updateOnly: true,
...body.resource ...body.resource
}); });
@@ -263,7 +263,7 @@ class SignalRConnector extends Component {
handleWantedMissing = (body) => { handleWantedMissing = (body) => {
if (body.action === 'updated') { if (body.action === 'updated') {
this.props.dispatchUpdateItem({ this.props.dispatchUpdateItem({
section: 'missing', section: 'wanted.missing',
updateOnly: true, updateOnly: true,
...body.resource ...body.resource
}); });
-11
View File
@@ -25,14 +25,3 @@
font-family: 'Ubuntu Mono'; font-family: 'Ubuntu Mono';
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype'); src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
} }
/*
* text-security-disc
*/
@font-face {
font-weight: normal;
font-style: normal;
font-family: 'text-security-disc';
src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
function onModalClose() {
// No-op
}
function AuthenticationRequiredModal(props) {
const {
isOpen
} = props;
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>
<AuthenticationRequiredModalContentConnector
onModalClose={onModalClose}
/>
</Modal>
);
}
AuthenticationRequiredModal.propTypes = {
isOpen: PropTypes.bool.isRequired
};
export default AuthenticationRequiredModal;
@@ -0,0 +1,5 @@
.authRequiredAlert {
composes: alert from '~Components/Alert.css';
margin-bottom: 20px;
}
@@ -1,7 +1,7 @@
// This file is automatically generated. // This file is automatically generated.
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'input': string; 'authRequiredAlert': string;
} }
export const cssExports: CssExports; export const cssExports: CssExports;
export default cssExports; export default cssExports;
@@ -0,0 +1,170 @@
import PropTypes from 'prop-types';
import React, { useEffect, useRef } from 'react';
import Alert from 'Components/Alert';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
function onModalClose() {
// No-op
}
function AuthenticationRequiredModalContent(props) {
const {
isPopulated,
error,
isSaving,
settings,
onInputChange,
onSavePress,
dispatchFetchStatus
} = props;
const {
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation
} = settings;
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
const didMount = useRef(false);
useEffect(() => {
if (!isSaving && didMount.current) {
dispatchFetchStatus();
}
didMount.current = true;
}, [isSaving, dispatchFetchStatus]);
return (
<ModalContent
showCloseButton={false}
onModalClose={onModalClose}
>
<ModalHeader>
{translate('AuthenticationRequired')}
</ModalHeader>
<ModalBody>
<Alert
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
{translate('AuthenticationRequiredWarning')}
</Alert>
{
isPopulated && !error ?
<div>
<FormGroup>
<FormLabel>{translate('AuthenticationMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
helpLink="https://wiki.servarr.com/readarr/faq#forced-authentication"
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
helpTextWarning={username?.value ? undefined : translate('AuthenticationRequiredUsernameHelpTextWarning')}
{...username}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
helpTextWarning={password?.value ? undefined : translate('AuthenticationRequiredPasswordHelpTextWarning')}
{...password}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
{...passwordConfirmation}
/>
</FormGroup>
</div> :
null
}
{
!isPopulated && !error ? <LoadingIndicator /> : null
}
</ModalBody>
<ModalFooter>
<SpinnerButton
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!authenticationEnabled}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
AuthenticationRequiredModalContent.propTypes = {
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired
};
export default AuthenticationRequiredModalContent;
@@ -0,0 +1,86 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
const SECTION = 'general';
function createMapStateToProps() {
return createSelector(
createSettingsSectionSelector(SECTION),
(sectionSettings) => {
return {
...sectionSettings
};
}
);
}
const mapDispatchToProps = {
dispatchClearPendingChanges: clearPendingChanges,
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
dispatchSaveGeneralSettings: saveGeneralSettings,
dispatchFetchGeneralSettings: fetchGeneralSettings,
dispatchFetchStatus: fetchStatus
};
class AuthenticationRequiredModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchGeneralSettings();
}
componentWillUnmount() {
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.dispatchSetGeneralSettingsValue({ name, value });
};
onSavePress = () => {
this.props.dispatchSaveGeneralSettings();
};
//
// Render
render() {
const {
dispatchClearPendingChanges,
dispatchFetchGeneralSettings,
dispatchSetGeneralSettingsValue,
dispatchSaveGeneralSettings,
...otherProps
} = this.props;
return (
<AuthenticationRequiredModalContent
{...otherProps}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
/>
);
}
}
AuthenticationRequiredModalContentConnector.propTypes = {
dispatchClearPendingChanges: PropTypes.func.isRequired,
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);
+11 -3
View File
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert';
import TextInput from 'Components/Form/TextInput'; import TextInput from 'Components/Form/TextInput';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
@@ -7,7 +8,7 @@ import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import { icons } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector'; import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector';
@@ -127,9 +128,16 @@ class AddNewItem extends Component {
!isFetching && !!error ? !isFetching && !!error ?
<div className={styles.message}> <div className={styles.message}>
<div className={styles.helpText}> <div className={styles.helpText}>
Failed to load search results, please try again. {translate('FailedLoadingSearchResults')}
</div>
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
<div>
<Link to="https://wiki.servarr.com/readarr/troubleshooting#invalid-response-received-from-metadata-api">
{translate('WhySearchesCouldBeFailing')}
</Link>
</div> </div>
<div>{getErrorMessage(error)}</div>
</div> : null </div> : null
} }
@@ -25,3 +25,8 @@
border-radius: 4px; border-radius: 4px;
background-color: var(--cardCenterBackgroundColor); background-color: var(--cardCenterBackgroundColor);
} }
.customFormats {
display: flex;
flex-wrap: wrap;
}
@@ -3,6 +3,7 @@
interface CssExports { interface CssExports {
'addSpecification': string; 'addSpecification': string;
'center': string; 'center': string;
'customFormats': string;
'deleteButton': string; 'deleteButton': string;
'rightButtons': string; 'rightButtons': string;
} }
+107 -27
View File
@@ -11,16 +11,69 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, inputTypes, kinds } from 'Helpers/Props'; import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
const authenticationMethodOptions = [ export const authenticationMethodOptions = [
{ key: 'none', value: 'None' }, {
{ key: 'basic', value: 'Basic (Browser Popup)' }, key: 'none',
{ key: 'forms', value: 'Forms (Login Page)' } get value() {
return translate('None');
},
isDisabled: true
},
{
key: 'external',
get value() {
return translate('External');
},
isHidden: true
},
{
key: 'basic',
get value() {
return translate('AuthBasic');
}
},
{
key: 'forms',
get value() {
return translate('AuthForm');
}
}
];
export const authenticationRequiredOptions = [
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
}
]; ];
const certificateValidationOptions = [ const certificateValidationOptions = [
{ key: 'enabled', value: 'Enabled' }, {
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }, key: 'enabled',
{ key: 'disabled', value: 'Disabled' } get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
},
{
key: 'disabled',
get value() {
return translate('Disabled');
}
}
]; ];
class SecuritySettings extends Component { class SecuritySettings extends Component {
@@ -68,8 +121,10 @@ class SecuritySettings extends Component {
const { const {
authenticationMethod, authenticationMethod,
authenticationRequired,
username, username,
password, password,
passwordConfirmation,
apiKey, apiKey,
certificateValidation certificateValidation
} = settings; } = settings;
@@ -79,26 +134,40 @@ class SecuritySettings extends Component {
return ( return (
<FieldSet legend={translate('Security')}> <FieldSet legend={translate('Security')}>
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('Authentication')}</FormLabel>
{translate('Authentication')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
name="authenticationMethod" name="authenticationMethod"
values={authenticationMethodOptions} values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')} helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange} onChange={onInputChange}
{...authenticationMethod} {...authenticationMethod}
/> />
</FormGroup> </FormGroup>
{ {
authenticationEnabled && authenticationEnabled ?
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('AuthenticationRequired')}</FormLabel>
{translate('Username')}
</FormLabel> <FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
@@ -106,15 +175,14 @@ class SecuritySettings extends Component {
onChange={onInputChange} onChange={onInputChange}
{...username} {...username}
/> />
</FormGroup> </FormGroup> :
null
} }
{ {
authenticationEnabled && authenticationEnabled ?
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('Password')}</FormLabel>
{translate('Password')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.PASSWORD} type={inputTypes.PASSWORD}
@@ -122,19 +190,33 @@ class SecuritySettings extends Component {
onChange={onInputChange} onChange={onInputChange}
{...password} {...password}
/> />
</FormGroup> </FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup> :
null
} }
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('ApiKey')}</FormLabel>
{translate('APIKey')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
name="apiKey" name="apiKey"
readOnly={true} readOnly={true}
helpTextWarning={translate('ApiKeyHelpTextWarning')} helpTextWarning={translate('RestartRequiredHelpTextWarning')}
buttons={[ buttons={[
<ClipboardButton <ClipboardButton
key="copy" key="copy"
@@ -160,9 +242,7 @@ class SecuritySettings extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('CertificateValidation')}</FormLabel>
{translate('CertificateValidation')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
+46 -50
View File
@@ -18,7 +18,6 @@ function UpdateSettings(props) {
const { const {
advancedSettings, advancedSettings,
settings, settings,
isWindows,
packageUpdateMechanism, packageUpdateMechanism,
onInputChange onInputChange
} = props; } = props;
@@ -44,10 +43,10 @@ function UpdateSettings(props) {
value: titleCase(packageUpdateMechanism) value: titleCase(packageUpdateMechanism)
}); });
} else { } else {
updateOptions.push({ key: 'builtIn', value: 'Built-In' }); updateOptions.push({ key: 'builtIn', value: translate('BuiltIn') });
} }
updateOptions.push({ key: 'script', value: 'Script' }); updateOptions.push({ key: 'script', value: translate('Script') });
return ( return (
<FieldSet legend={translate('Updates')}> <FieldSet legend={translate('Updates')}>
@@ -60,8 +59,8 @@ function UpdateSettings(props) {
<FormInputGroup <FormInputGroup
type={inputTypes.AUTO_COMPLETE} type={inputTypes.AUTO_COMPLETE}
name="branch" name="branch"
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')} helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr" helpLink="https://wiki.servarr.com/readarr/settings#updates"
{...branch} {...branch}
values={branchValues} values={branchValues}
onChange={onInputChange} onChange={onInputChange}
@@ -69,62 +68,59 @@ function UpdateSettings(props) {
/> />
</FormGroup> </FormGroup>
{ <div>
!isWindows && <FormGroup
<div> advancedSettings={advancedSettings}
<FormGroup isAdvanced={true}
advancedSettings={advancedSettings} size={sizes.MEDIUM}
isAdvanced={true} >
size={sizes.MEDIUM} <FormLabel>{translate('Automatic')}</FormLabel>
>
<FormLabel>{translate('Automatic')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="updateAutomatically" name="updateAutomatically"
helpText={translate('UpdateAutomaticallyHelpText')} helpText={translate('UpdateAutomaticallyHelpText')}
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Readarr' }) : undefined} helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
onChange={onInputChange} onChange={onInputChange}
{...updateAutomatically} {...updateAutomatically}
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('Mechanism')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="updateMechanism"
values={updateOptions}
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/readarr/settings#updates"
onChange={onInputChange}
{...updateMechanism}
/>
</FormGroup>
{
updateMechanism.value === 'script' &&
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
> >
<FormLabel>{translate('Mechanism')}</FormLabel> <FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.TEXT}
name="updateMechanism" name="updateScriptPath"
values={updateOptions} helpText={translate('UpdateScriptPathHelpText')}
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/readarr/faq#how-do-i-update-my-readarr"
onChange={onInputChange} onChange={onInputChange}
{...updateMechanism} {...updateScriptPath}
/> />
</FormGroup> </FormGroup>
}
{ </div>
updateMechanism.value === 'script' &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="updateScriptPath"
helpText={translate('UpdateScriptPathHelpText')}
onChange={onInputChange}
{...updateScriptPath}
/>
</FormGroup>
}
</div>
}
</FieldSet> </FieldSet>
); );
} }
@@ -8,6 +8,7 @@ import TagListConnector from 'Components/TagListConnector';
import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector'; import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector';
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector'; import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
import { SelectStateInputProps } from 'typings/props'; import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageImportListsModalRow.css'; import styles from './ManageImportListsModalRow.css';
interface ManageImportListsModalRowProps { interface ManageImportListsModalRowProps {
@@ -70,7 +71,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.qualityProfileId}> <TableRowCell className={styles.qualityProfileId}>
{qualityProfile?.name ?? 'None'} {qualityProfile?.name ?? translate('None')}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.metadataProfileId}> <TableRowCell className={styles.metadataProfileId}>
@@ -82,7 +83,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.enableAutomaticAdd}> <TableRowCell className={styles.enableAutomaticAdd}>
{enableAutomaticAdd ? 'Yes' : 'No'} {enableAutomaticAdd ? translate('Yes') : translate('No')}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.tags}> <TableRowCell className={styles.tags}>
@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -15,11 +14,11 @@ function createMapStateToProps() {
(state) => state.settings.advancedSettings, (state) => state.settings.advancedSettings,
(state) => state.settings.namingExamples, (state) => state.settings.namingExamples,
createSettingsSectionSelector(SECTION), createSettingsSectionSelector(SECTION),
(advancedSettings, examples, sectionSettings) => { (advancedSettings, namingExamples, sectionSettings) => {
return { return {
advancedSettings, advancedSettings,
examples: examples.item, examples: namingExamples.item,
examplesPopulated: !_.isEmpty(examples.item), examplesPopulated: namingExamples.isPopulated,
...sectionSettings ...sectionSettings
}; };
} }
@@ -75,12 +75,12 @@ class RootFolder extends Component {
{path} {path}
</Label> </Label>
<Label kind={kinds.SUCCESS}> <Label kind={qualityProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{qualityProfile.name} {qualityProfile?.name || translate('None')}
</Label> </Label>
<Label kind={kinds.SUCCESS}> <Label kind={metadataProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{metadataProfile.name} {metadataProfile?.name || translate('None')}
</Label> </Label>
</div> </div>
-1
View File
@@ -2,7 +2,6 @@ module.exports = {
// Families // Families
defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif', defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif',
monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;', monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;',
passwordFamily: 'text-security-disc',
// Sizes // Sizes
extraSmallFontSize: '11px', extraSmallFontSize: '11px',
@@ -1,50 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import styles from './UpdateChanges.css';
class UpdateChanges extends Component {
//
// Render
render() {
const {
title,
changes
} = this.props;
if (changes.length === 0) {
return null;
}
return (
<div>
<div className={styles.title}>{title}</div>
<ul>
{
changes.map((change, index) => {
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
return `[${match}](https://github.com/Readarr/Readarr/issues/${match.substring(1)})`;
});
return (
<li key={index}>
<InlineMarkdown data={checkChange} />
</li>
);
})
}
</ul>
</div>
);
}
}
UpdateChanges.propTypes = {
title: PropTypes.string.isRequired,
changes: PropTypes.arrayOf(PropTypes.string)
};
export default UpdateChanges;
@@ -0,0 +1,43 @@
import React from 'react';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import styles from './UpdateChanges.css';
interface UpdateChangesProps {
title: string;
changes: string[];
}
function UpdateChanges(props: UpdateChangesProps) {
const { title, changes } = props;
if (changes.length === 0) {
return null;
}
const uniqueChanges = [...new Set(changes)];
return (
<div>
<div className={styles.title}>{title}</div>
<ul>
{uniqueChanges.map((change, index) => {
const checkChange = change.replace(
/#\d{4,5}\b/g,
(match) =>
`[${match}](https://github.com/Readarr/Readarr/issues/${match.substring(
1
)})`
);
return (
<li key={index}>
<InlineMarkdown data={checkChange} />
</li>
);
})}
</ul>
</div>
);
}
export default UpdateChanges;
-252
View File
@@ -1,252 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import translate from 'Utilities/String/translate';
import UpdateChanges from './UpdateChanges';
import styles from './Updates.css';
class Updates extends Component {
//
// Render
render() {
const {
currentVersion,
isFetching,
isPopulated,
updatesError,
generalSettingsError,
items,
isInstallingUpdate,
updateMechanism,
isDocker,
updateMechanismMessage,
shortDateFormat,
longDateFormat,
timeFormat,
onInstallLatestPress
} = this.props;
const hasError = !!(updatesError || generalSettingsError);
const hasUpdates = isPopulated && !hasError && items.length > 0;
const noUpdates = isPopulated && !hasError && !items.length;
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const externalUpdaterPrefix = 'Unable to update Readarr directly,';
const externalUpdaterMessages = {
external: 'Readarr is configured to use an external update mechanism',
apt: 'use apt to install the update',
docker: 'update the docker container to receive the update'
};
return (
<PageContent title={translate('Updates')}>
<PageContentBody>
{
!isPopulated && !hasError &&
<LoadingIndicator />
}
{
noUpdates &&
<Alert kind={kinds.INFO}>
{translate('NoUpdatesAreAvailable')}
</Alert>
}
{
hasUpdateToInstall &&
<div className={styles.messageContainer}>
{
(updateMechanism === 'builtIn' || updateMechanism === 'script') && !isDocker ?
<SpinnerButton
className={styles.updateAvailable}
kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate}
onPress={onInstallLatestPress}
>
Install Latest
</SpinnerButton> :
<Fragment>
<Icon
name={icons.WARNING}
kind={kinds.WARNING}
size={30}
/>
<div className={styles.message}>
{externalUpdaterPrefix} <InlineMarkdown data={updateMechanismMessage || externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external} />
</div>
</Fragment>
}
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
</div>
}
{
noUpdateToInstall &&
<div className={styles.messageContainer}>
<Icon
className={styles.upToDateIcon}
name={icons.CHECK_CIRCLE}
size={30}
/>
<div className={styles.message}>
The latest version of Readarr is already installed
</div>
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
</div>
}
{
hasUpdates &&
<div>
{
items.map((update) => {
const hasChanges = !!update.changes;
return (
<div
key={update.version}
className={styles.update}
>
<div className={styles.info}>
<div className={styles.version}>{update.version}</div>
<div className={styles.space}>&mdash;</div>
<div
className={styles.date}
title={formatDateTime(update.releaseDate, longDateFormat, timeFormat)}
>
{formatDate(update.releaseDate, shortDateFormat)}
</div>
{
update.branch === 'master' ?
null :
<Label
className={styles.label}
>
{update.branch}
</Label>
}
{
update.version === currentVersion ?
<Label
className={styles.label}
kind={kinds.SUCCESS}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
>
Currently Installed
</Label> :
null
}
{
update.version !== currentVersion && update.installedOn ?
<Label
className={styles.label}
kind={kinds.INVERSE}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
>
Previously Installed
</Label> :
null
}
</div>
{
!hasChanges &&
<div>
{translate('MaintenanceRelease')}
</div>
}
{
hasChanges &&
<div className={styles.changes}>
<UpdateChanges
title={translate('New')}
changes={update.changes.new}
/>
<UpdateChanges
title={translate('Fixed')}
changes={update.changes.fixed}
/>
</div>
}
</div>
);
})
}
</div>
}
{
!!updatesError &&
<div>
Failed to fetch updates
</div>
}
{
!!generalSettingsError &&
<div>
Failed to update settings
</div>
}
</PageContentBody>
</PageContent>
);
}
}
Updates.propTypes = {
currentVersion: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
updatesError: PropTypes.object,
generalSettingsError: PropTypes.object,
items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
updateMechanism: PropTypes.string,
updateMechanismMessage: PropTypes.string,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired
};
export default Updates;
+303
View File
@@ -0,0 +1,303 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import * as commandNames from 'Commands/commandNames';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { icons, kinds } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import { fetchGeneralSettings } from 'Store/Actions/settingsActions';
import { fetchUpdates } from 'Store/Actions/systemActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { UpdateMechanism } from 'typings/Settings/General';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import translate from 'Utilities/String/translate';
import UpdateChanges from './UpdateChanges';
import styles from './Updates.css';
const VERSION_REGEX = /\d+\.\d+\.\d+\.\d+/i;
function createUpdatesSelector() {
return createSelector(
(state: AppState) => state.system.updates,
(state: AppState) => state.settings.general,
(updates, generalSettings) => {
const { error: updatesError, items } = updates;
const isFetching = updates.isFetching || generalSettings.isFetching;
const isPopulated = updates.isPopulated && generalSettings.isPopulated;
return {
isFetching,
isPopulated,
updatesError,
generalSettingsError: generalSettings.error,
items,
updateMechanism: generalSettings.item.updateMechanism,
};
}
);
}
function Updates() {
const currentVersion = useSelector((state: AppState) => state.app.version);
const { packageUpdateMechanismMessage } = useSelector(
createSystemStatusSelector()
);
const { shortDateFormat, longDateFormat, timeFormat } = useSelector(
createUISettingsSelector()
);
const isInstallingUpdate = useSelector(
createCommandExecutingSelector(commandNames.APPLICATION_UPDATE)
);
const {
isFetching,
isPopulated,
updatesError,
generalSettingsError,
items,
updateMechanism,
} = useSelector(createUpdatesSelector());
const dispatch = useDispatch();
const [isMajorUpdateModalOpen, setIsMajorUpdateModalOpen] = useState(false);
const hasError = !!(updatesError || generalSettingsError);
const hasUpdates = isPopulated && !hasError && items.length > 0;
const noUpdates = isPopulated && !hasError && !items.length;
const externalUpdaterPrefix = translate('UpdateAppDirectlyLoadError');
const externalUpdaterMessages: Partial<Record<UpdateMechanism, string>> = {
external: translate('ExternalUpdater'),
apt: translate('AptUpdater'),
docker: translate('DockerUpdater'),
};
const { isMajorUpdate, hasUpdateToInstall } = useMemo(() => {
const majorVersion = parseInt(
currentVersion.match(VERSION_REGEX)?.[0] ?? '0'
);
const latestVersion = items[0]?.version;
const latestMajorVersion = parseInt(
latestVersion?.match(VERSION_REGEX)?.[0] ?? '0'
);
return {
isMajorUpdate: latestMajorVersion > majorVersion,
hasUpdateToInstall: items.some(
(update) => update.installable && update.latest
),
};
}, [currentVersion, items]);
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const handleInstallLatestPress = useCallback(() => {
if (isMajorUpdate) {
setIsMajorUpdateModalOpen(true);
} else {
dispatch(executeCommand({ name: commandNames.APPLICATION_UPDATE }));
}
}, [isMajorUpdate, setIsMajorUpdateModalOpen, dispatch]);
const handleInstallLatestMajorVersionPress = useCallback(() => {
setIsMajorUpdateModalOpen(false);
dispatch(
executeCommand({
name: commandNames.APPLICATION_UPDATE,
installMajorUpdate: true,
})
);
}, [setIsMajorUpdateModalOpen, dispatch]);
const handleCancelMajorVersionPress = useCallback(() => {
setIsMajorUpdateModalOpen(false);
}, [setIsMajorUpdateModalOpen]);
useEffect(() => {
dispatch(fetchUpdates());
dispatch(fetchGeneralSettings());
}, [dispatch]);
return (
<PageContent title={translate('Updates')}>
<PageContentBody>
{isPopulated || hasError ? null : <LoadingIndicator />}
{noUpdates ? (
<Alert kind={kinds.INFO}>{translate('NoUpdatesAreAvailable')}</Alert>
) : null}
{hasUpdateToInstall ? (
<div className={styles.messageContainer}>
{updateMechanism === 'builtIn' || updateMechanism === 'script' ? (
<SpinnerButton
kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate}
onPress={handleInstallLatestPress}
>
{translate('InstallLatest')}
</SpinnerButton>
) : (
<>
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
<div className={styles.message}>
{externalUpdaterPrefix}{' '}
<InlineMarkdown
data={
packageUpdateMechanismMessage ||
externalUpdaterMessages[updateMechanism] ||
externalUpdaterMessages.external
}
/>
</div>
</>
)}
{isFetching ? (
<LoadingIndicator className={styles.loading} size={20} />
) : null}
</div>
) : null}
{noUpdateToInstall && (
<div className={styles.messageContainer}>
<Icon
className={styles.upToDateIcon}
name={icons.CHECK_CIRCLE}
size={30}
/>
<div className={styles.message}>{translate('OnLatestVersion')}</div>
{isFetching && (
<LoadingIndicator className={styles.loading} size={20} />
)}
</div>
)}
{hasUpdates && (
<div>
{items.map((update) => {
return (
<div key={update.version} className={styles.update}>
<div className={styles.info}>
<div className={styles.version}>{update.version}</div>
<div className={styles.space}>&mdash;</div>
<div
className={styles.date}
title={formatDateTime(
update.releaseDate,
longDateFormat,
timeFormat
)}
>
{formatDate(update.releaseDate, shortDateFormat)}
</div>
{update.branch === 'master' ? null : (
<Label className={styles.label}>{update.branch}</Label>
)}
{update.version === currentVersion ? (
<Label
className={styles.label}
kind={kinds.SUCCESS}
title={formatDateTime(
update.installedOn,
longDateFormat,
timeFormat
)}
>
{translate('CurrentlyInstalled')}
</Label>
) : null}
{update.version !== currentVersion && update.installedOn ? (
<Label
className={styles.label}
kind={kinds.INVERSE}
title={formatDateTime(
update.installedOn,
longDateFormat,
timeFormat
)}
>
{translate('PreviouslyInstalled')}
</Label>
) : null}
</div>
{update.changes ? (
<div>
<UpdateChanges
title={translate('New')}
changes={update.changes.new}
/>
<UpdateChanges
title={translate('Fixed')}
changes={update.changes.fixed}
/>
</div>
) : (
<div>{translate('MaintenanceRelease')}</div>
)}
</div>
);
})}
</div>
)}
{updatesError ? (
<Alert kind={kinds.WARNING}>
{translate('FailedToFetchUpdates')}
</Alert>
) : null}
{generalSettingsError ? (
<Alert kind={kinds.DANGER}>
{translate('FailedToFetchSettings')}
</Alert>
) : null}
<ConfirmModal
isOpen={isMajorUpdateModalOpen}
kind={kinds.WARNING}
title={translate('InstallMajorVersionUpdate')}
message={
<div>
<div>{translate('InstallMajorVersionUpdateMessage')}</div>
<div>
<InlineMarkdown
data={translate('InstallMajorVersionUpdateMessageLink', {
domain: 'readarr.com',
url: 'https://readarr.com/#downloads',
})}
/>
</div>
</div>
}
confirmLabel={translate('Install')}
onConfirm={handleInstallLatestMajorVersionPress}
onCancel={handleCancelMajorVersionPress}
/>
</PageContentBody>
</PageContent>
);
}
export default Updates;
+45
View File
@@ -0,0 +1,45 @@
export type UpdateMechanism =
| 'builtIn'
| 'script'
| 'external'
| 'apt'
| 'docker';
export default interface General {
bindAddress: string;
port: number;
sslPort: number;
enableSsl: boolean;
launchBrowser: boolean;
authenticationMethod: string;
authenticationRequired: string;
analyticsEnabled: boolean;
username: string;
password: string;
passwordConfirmation: string;
logLevel: string;
consoleLogLevel: string;
branch: string;
apiKey: string;
sslCertPath: string;
sslCertPassword: string;
urlBase: string;
instanceName: string;
applicationUrl: string;
updateAutomatically: boolean;
updateMechanism: UpdateMechanism;
updateScriptPath: string;
proxyEnabled: boolean;
proxyType: string;
proxyHostname: string;
proxyPort: number;
proxyUsername: string;
proxyPassword: string;
proxyBypassFilter: string;
proxyBypassLocalAddresses: boolean;
certificateValidation: string;
backupFolder: string;
backupInterval: number;
backupRetention: number;
id: number;
}
@@ -1,4 +1,5 @@
export interface UiSettings { export default interface UiSettings {
theme: 'auto' | 'dark' | 'light';
showRelativeDates: boolean; showRelativeDates: boolean;
shortDateFormat: string; shortDateFormat: string;
longDateFormat: string; longDateFormat: string;
+32
View File
@@ -0,0 +1,32 @@
interface SystemStatus {
appData: string;
appName: string;
authentication: string;
branch: string;
buildTime: string;
instanceName: string;
isAdmin: boolean;
isDebug: boolean;
isDocker: boolean;
isLinux: boolean;
isNetCore: boolean;
isOsx: boolean;
isProduction: boolean;
isUserInteractive: boolean;
isWindows: boolean;
migrationVersion: number;
mode: string;
osName: string;
osVersion: string;
packageUpdateMechanism: string;
packageUpdateMechanismMessage: string;
runtimeName: string;
runtimeVersion: string;
sqliteVersion: string;
startTime: string;
startupPath: string;
urlBase: string;
version: string;
}
export default SystemStatus;
+20
View File
@@ -0,0 +1,20 @@
export interface Changes {
new: string[];
fixed: string[];
}
interface Update {
version: string;
branch: string;
releaseDate: string;
fileName: string;
url: string;
installed: boolean;
installedOn: string;
installable: boolean;
latest: boolean;
changes: Changes | null;
hash: string;
}
export default Update;
+45 -49
View File
@@ -25,34 +25,33 @@
"defaults" "defaults"
], ],
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "6.4.0", "@fortawesome/fontawesome-free": "6.6.0",
"@fortawesome/fontawesome-svg-core": "6.4.0", "@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.2",
"@microsoft/signalr": "6.0.25", "@microsoft/signalr": "6.0.25",
"@sentry/browser": "7.51.2", "@sentry/browser": "7.119.1",
"@sentry/integrations": "7.51.2", "@sentry/integrations": "7.119.1",
"@types/node": "18.19.31", "@types/node": "20.16.11",
"@types/react": "18.2.79", "@types/react": "18.2.79",
"@types/react-dom": "18.2.25", "@types/react-dom": "18.2.25",
"ansi-colors": "4.1.3", "classnames": "2.5.1",
"classnames": "2.3.2",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"connected-react-router": "6.9.3", "connected-react-router": "6.9.3",
"element-class": "0.2.2", "element-class": "0.2.2",
"filesize": "10.0.7", "filesize": "10.1.6",
"fuse.js": "6.6.2", "fuse.js": "6.6.2",
"history": "4.10.1", "history": "4.10.1",
"jdu": "1.0.0", "jdu": "1.0.0",
"jquery": "3.7.0", "jquery": "3.7.1",
"lodash": "4.17.21", "lodash": "4.17.21",
"mobile-detect": "1.4.5", "mobile-detect": "1.4.5",
"moment": "2.29.4", "moment": "2.30.1",
"mousetrap": "1.6.5", "mousetrap": "1.6.5",
"normalize.css": "8.0.1", "normalize.css": "8.0.1",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"qs": "6.11.1", "qs": "6.13.0",
"react": "17.0.2", "react": "17.0.2",
"react-addons-shallow-compare": "15.6.3", "react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0", "react-async-script": "1.2.0",
@@ -64,7 +63,7 @@
"react-dnd-touch-backend": "14.1.1", "react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3", "react-document-title": "2.0.3",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-focus-lock": "2.5.2", "react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0", "react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0", "react-lazyload": "3.2.0",
"react-measure": "2.5.2", "react-measure": "2.5.2",
@@ -73,74 +72,71 @@
"react-redux": "7.2.4", "react-redux": "7.2.4",
"react-router": "5.2.0", "react-router": "5.2.0",
"react-router-dom": "5.2.0", "react-router-dom": "5.2.0",
"react-slider": "1.3.1", "react-slider": "1.3.3",
"react-tabs": "3.2.2", "react-tabs": "4.3.0",
"react-text-truncate": "0.18.0", "react-text-truncate": "0.19.0",
"react-virtualized": "9.21.1", "react-virtualized": "9.21.1",
"redux": "4.1.0", "redux": "4.2.1",
"redux-actions": "2.6.5", "redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0", "redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1", "redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0", "redux-thunk": "2.4.2",
"reselect": "4.1.8", "reselect": "4.1.8",
"stacktrace-js": "2.0.2", "stacktrace-js": "2.0.2",
"typescript": "5.1.6" "typescript": "5.1.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.24.4", "@babel/core": "7.25.8",
"@babel/eslint-parser": "7.24.1", "@babel/eslint-parser": "7.25.8",
"@babel/plugin-proposal-export-default-from": "7.24.1", "@babel/plugin-proposal-export-default-from": "7.25.8",
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.24.4", "@babel/preset-env": "7.25.8",
"@babel/preset-react": "7.24.1", "@babel/preset-react": "7.25.7",
"@babel/preset-typescript": "7.24.1", "@babel/preset-typescript": "7.25.7",
"@types/lodash": "4.14.195", "@types/lodash": "4.14.195",
"@types/react-lazyload": "3.2.0", "@types/react-lazyload": "3.2.3",
"@types/redux-actions": "2.6.2", "@types/redux-actions": "2.6.5",
"@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0", "@typescript-eslint/parser": "6.21.0",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.20",
"babel-loader": "9.1.3", "babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1", "babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24", "babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.37.0", "core-js": "3.38.1",
"css-loader": "6.8.1", "css-loader": "6.8.1",
"css-modules-typescript-loader": "4.0.1", "css-modules-typescript-loader": "4.0.1",
"eslint": "8.57.0", "eslint": "8.57.1",
"eslint-config-prettier": "8.10.0", "eslint-config-prettier": "8.10.0",
"eslint-plugin-filenames": "1.3.2", "eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.31.0",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.34.1", "eslint-plugin-react": "7.37.1",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-simple-import-sort": "12.1.0", "eslint-plugin-simple-import-sort": "12.1.1",
"file-loader": "6.2.0", "file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0", "filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "8.0.0", "fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.3", "html-webpack-plugin": "5.6.0",
"loader-utils": "^3.2.1", "loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.6", "mini-css-extract-plugin": "2.9.1",
"postcss": "8.4.38", "postcss": "8.4.47",
"postcss-color-function": "4.1.0", "postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0", "postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4", "postcss-mixins": "9.0.4",
"postcss-nested": "6.0.1", "postcss-nested": "6.2.0",
"postcss-simple-vars": "7.0.1", "postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3", "postcss-url": "10.1.3",
"prettier": "2.8.8", "prettier": "2.8.8",
"require-nocache": "1.0.0", "require-nocache": "1.0.0",
"rimraf": "4.4.1", "rimraf": "6.0.1",
"run-sequence": "2.2.1", "style-loader": "3.3.4",
"streamqueue": "1.1.2",
"style-loader": "3.3.3",
"stylelint": "15.10.3", "stylelint": "15.10.3",
"stylelint-order": "6.0.3", "stylelint-order": "6.0.4",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.10",
"ts-loader": "9.4.4", "ts-loader": "9.5.1",
"typescript-plugin-css-modules": "5.0.1", "typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"webpack": "5.88.2", "webpack": "5.95.0",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-livereload-plugin": "3.0.2", "webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8" "worker-loader": "3.0.8"
+12 -10
View File
@@ -4,26 +4,27 @@
<PackageVersion Include="AutoFixture" Version="4.17.0" /> <PackageVersion Include="AutoFixture" Version="4.17.0" />
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" /> <PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
<PackageVersion Include="Dapper" Version="2.0.151" /> <PackageVersion Include="Dapper" Version="2.0.151" />
<PackageVersion Include="Diacritical.Net" Version="1.0.4" />
<PackageVersion Include="DryIoc.dll" Version="5.4.3" /> <PackageVersion Include="DryIoc.dll" Version="5.4.3" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /> <PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="Equ" Version="2.3.0" /> <PackageVersion Include="Equ" Version="2.3.0" />
<PackageVersion Include="FluentAssertions" Version="5.10.3" /> <PackageVersion Include="FluentAssertions" Version="5.10.3" />
<PackageVersion Include="Polly" Version="8.3.1" /> <PackageVersion Include="Polly" Version="8.5.0" />
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" /> <PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" /> <PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" /> <PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageVersion Include="FluentValidation" Version="9.5.4" /> <PackageVersion Include="FluentValidation" Version="9.5.4" />
<PackageVersion Include="Ical.Net" Version="4.2.0" /> <PackageVersion Include="Ical.Net" Version="4.3.1" />
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" /> <PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
<PackageVersion Include="LazyCache" Version="2.4.0" /> <PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mailkit" Version="3.6.0" /> <PackageVersion Include="Mailkit" Version="3.6.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.29" /> <PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" /> <PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" /> <PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" /> <PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
<PackageVersion Include="Moq" Version="4.17.2" /> <PackageVersion Include="Moq" Version="4.17.2" />
@@ -33,20 +34,21 @@
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" /> <PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
<PackageVersion Include="NLog" Version="5.1.4" /> <PackageVersion Include="NLog" Version="5.1.4" />
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageVersion Include="Npgsql" Version="7.0.6" /> <PackageVersion Include="Npgsql" Version="7.0.9" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit" Version="3.14.0" /> <PackageVersion Include="NUnit" Version="3.14.0" />
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" /> <PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
<PackageVersion Include="PdfSharpCore" Version="1.3.32" /> <PackageVersion Include="PdfSharpCore" Version="1.3.65" />
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" /> <PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
<PackageVersion Include="RestSharp" Version="106.15.0" /> <PackageVersion Include="RestSharp" Version="106.15.0" />
<PackageVersion Include="Selenium.Support" Version="3.141.0" /> <PackageVersion Include="Selenium.Support" Version="3.141.0" />
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" /> <PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
<PackageVersion Include="Sentry" Version="3.31.0" /> <PackageVersion Include="Sentry" Version="3.31.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.4" /> <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" /> <PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
<PackageVersion Include="System.Buffers" Version="4.5.1" /> <PackageVersion Include="System.Buffers" Version="4.5.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" /> <PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
@@ -60,7 +62,7 @@
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" /> <PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" /> <PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" /> <PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageVersion Include="System.Text.Json" Version="6.0.9" /> <PackageVersion Include="System.Text.Json" Version="6.0.10" />
<PackageVersion Include="System.ValueTuple" Version="4.5.0" /> <PackageVersion Include="System.ValueTuple" Version="4.5.0" />
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" /> <PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
</ItemGroup> </ItemGroup>
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null); _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll(); _runner.KillAll();
_runner.Start(); _runner.Start(true);
driver.Url = "http://localhost:8787"; driver.Url = "http://localhost:8787";
@@ -1,10 +1,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
@@ -43,6 +45,26 @@ namespace NzbDrone.Common.Test
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(v => v.WriteAllText(configFile, It.IsAny<string>())) .Setup(v => v.WriteAllText(configFile, It.IsAny<string>()))
.Callback<string, string>((p, t) => _configFileContents = t); .Callback<string, string>((p, t) => _configFileContents = t);
Mocker.GetMock<IOptions<AuthOptions>>()
.Setup(v => v.Value)
.Returns(new AuthOptions());
Mocker.GetMock<IOptions<AppOptions>>()
.Setup(v => v.Value)
.Returns(new AppOptions());
Mocker.GetMock<IOptions<ServerOptions>>()
.Setup(v => v.Value)
.Returns(new ServerOptions());
Mocker.GetMock<IOptions<LogOptions>>()
.Setup(v => v.Value)
.Returns(new LogOptions());
Mocker.GetMock<IOptions<UpdateOptions>>()
.Setup(v => v.Value)
.Returns(new UpdateOptions());
} }
[Test] [Test]
@@ -21,9 +21,28 @@ namespace NzbDrone.Common.Test.ExtensionTests
[TestCase("1.2.3.4")] [TestCase("1.2.3.4")]
[TestCase("172.55.0.1")] [TestCase("172.55.0.1")]
[TestCase("192.55.0.1")] [TestCase("192.55.0.1")]
[TestCase("100.64.0.1")]
[TestCase("100.127.255.254")]
public void should_return_false_for_public_ip_address(string ipAddress) public void should_return_false_for_public_ip_address(string ipAddress)
{ {
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse(); IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse();
} }
[TestCase("100.64.0.1")]
[TestCase("100.127.255.254")]
[TestCase("100.100.100.100")]
public void should_return_true_for_cgnat_ip_address(string ipAddress)
{
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeTrue();
}
[TestCase("1.2.3.4")]
[TestCase("192.168.5.1")]
[TestCase("100.63.255.255")]
[TestCase("100.128.0.0")]
public void should_return_false_for_non_cgnat_ip_address(string ipAddress)
{
IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeFalse();
}
} }
} }
@@ -89,6 +89,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://discord.com/api/webhooks/mySecret")] [TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")] [TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
// Telegram
[TestCase(@"https://api.telegram.org/bot1234567890:mySecret/sendmessage: chat_id=123456&parse_mode=HTML&text=<text>")]
[TestCase(@"https://api.telegram.org/bot1234567890:mySecret/")]
public void should_clean_message(string message) public void should_clean_message(string message)
{ {
var cleansedMessage = CleanseLogMessage.Cleanse(message); var cleansedMessage = CleanseLogMessage.Cleanse(message);
@@ -10,6 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
@@ -33,6 +34,11 @@ namespace NzbDrone.Common.Test
container.RegisterInstance(new Mock<IHostLifetime>().Object); container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object); container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<AppOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<AuthOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<ServerOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<LogOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<UpdateOptions>>().Object);
var serviceProvider = container.GetServiceProvider(); var serviceProvider = container.GetServiceProvider();
serviceProvider.GetRequiredService<IAppFolderFactory>().Register(); serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
@@ -39,18 +39,24 @@ namespace NzbDrone.Common.Extensions
private static bool IsLocalIPv4(byte[] ipv4Bytes) private static bool IsLocalIPv4(byte[] ipv4Bytes)
{ {
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16) // Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254; var isLinkLocal = ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
// Class A private range: 10.0.0.0 10.255.255.255 (10.0.0.0/8) // Class A private range: 10.0.0.0 10.255.255.255 (10.0.0.0/8)
bool IsClassA() => ipv4Bytes[0] == 10; var isClassA = ipv4Bytes[0] == 10;
// Class B private range: 172.16.0.0 172.31.255.255 (172.16.0.0/12) // Class B private range: 172.16.0.0 172.31.255.255 (172.16.0.0/12)
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31; var isClassB = ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
// Class C private range: 192.168.0.0 192.168.255.255 (192.168.0.0/16) // Class C private range: 192.168.0.0 192.168.255.255 (192.168.0.0/16)
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168; var isClassC = ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB(); return isLinkLocal || isClassA || isClassC || isClassB;
}
public static bool IsCgnatIpAddress(this IPAddress ipAddress)
{
var bytes = ipAddress.GetAddressBytes();
return bytes.Length == 4 && bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127;
} }
} }
} }
@@ -54,7 +54,10 @@ namespace NzbDrone.Common.Instrumentation
new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord // Discord
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase) new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Telegram
new (@"api.telegram.org/bot(?<id>[\d]+):(?<secret>[\w-]+)/", RegexOptions.Compiled | RegexOptions.IgnoreCase)
}; };
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled); private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
@@ -0,0 +1,8 @@
namespace NzbDrone.Common.Options;
public class AppOptions
{
public string InstanceName { get; set; }
public string Theme { get; set; }
public bool? LaunchBrowser { get; set; }
}
@@ -0,0 +1,10 @@
namespace NzbDrone.Common.Options;
public class AuthOptions
{
public string ApiKey { get; set; }
public bool? Enabled { get; set; }
public string Method { get; set; }
public string Required { get; set; }
public bool? TrustCgnatIpAddresses { get; set; }
}
+14
View File
@@ -0,0 +1,14 @@
namespace NzbDrone.Common.Options;
public class LogOptions
{
public string Level { get; set; }
public bool? FilterSentryEvents { get; set; }
public int? Rotate { get; set; }
public bool? Sql { get; set; }
public string ConsoleLevel { get; set; }
public bool? AnalyticsEnabled { get; set; }
public string SyslogServer { get; set; }
public int? SyslogPort { get; set; }
public string SyslogLevel { get; set; }
}
@@ -0,0 +1,12 @@
namespace NzbDrone.Common.Options;
public class ServerOptions
{
public string UrlBase { get; set; }
public string BindAddress { get; set; }
public int? Port { get; set; }
public bool? EnableSsl { get; set; }
public int? SslPort { get; set; }
public string SslCertPath { get; set; }
public string SslCertPassword { get; set; }
}
@@ -0,0 +1,9 @@
namespace NzbDrone.Common.Options;
public class UpdateOptions
{
public string Mechanism { get; set; }
public bool? Automatically { get; set; }
public string ScriptPath { get; set; }
public string Branch { get; set; }
}
@@ -313,7 +313,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo(); processInfo = new ProcessInfo();
processInfo.Id = process.Id; processInfo.Id = process.Id;
processInfo.Name = process.ProcessName; processInfo.Name = process.ProcessName;
processInfo.StartPath = process.MainModule.FileName; processInfo.StartPath = process.MainModule?.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited) if (process.Id != GetCurrentProcessId() && process.HasExited)
{ {
@@ -200,17 +200,9 @@ namespace NzbDrone.Core.Test.Download
var seriesTags = new HashSet<int> { 2 }; var seriesTags = new HashSet<int> { 2 };
var clientTags = new HashSet<int> { 1 }; var clientTags = new HashSet<int> { 1 };
WithTorrentClient(0, clientTags);
WithTorrentClient(0, clientTags);
WithTorrentClient(0, clientTags);
WithTorrentClient(0, clientTags); WithTorrentClient(0, clientTags);
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags); Assert.Throws<DownloadClientUnavailableException>(() => Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags));
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags);
Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags).Should().BeNull();
} }
[Test] [Test]
@@ -312,11 +312,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
[Test] [Test]
public void should_return_status_with_outputdirs() public void should_return_status_with_outputdirs()
{ {
var configItems = new Dictionary<string, object>(); var configItems = new Dictionary<string, object>
{
configItems.Add("download_location", @"C:\Downloads\Downloading\deluge".AsOsAgnostic()); { "download_location", @"C:\Downloads\Downloading\deluge".AsOsAgnostic() },
configItems.Add("move_completed_path", @"C:\Downloads\Finished\deluge".AsOsAgnostic()); { "move_completed_path", @"C:\Downloads\Finished\deluge".AsOsAgnostic() },
configItems.Add("move_completed", true); { "move_completed", true }
};
Mocker.GetMock<IDelugeProxy>() Mocker.GetMock<IDelugeProxy>()
.Setup(v => v.GetConfig(It.IsAny<DelugeSettings>())) .Setup(v => v.GetConfig(It.IsAny<DelugeSettings>()))
@@ -328,5 +329,18 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
result.OutputRootFolders.Should().NotBeNull(); result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\deluge".AsOsAgnostic()); result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\deluge".AsOsAgnostic());
} }
[Test]
public void should_return_status_with_outputdirs_for_directories_in_settings()
{
Subject.Definition.Settings.As<DelugeSettings>().DownloadDirectory = @"D:\Downloads\Downloading\deluge".AsOsAgnostic();
Subject.Definition.Settings.As<DelugeSettings>().CompletedDirectory = @"D:\Downloads\Finished\deluge".AsOsAgnostic();
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"D:\Downloads\Finished\deluge".AsOsAgnostic());
}
} }
} }
@@ -178,8 +178,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
VerifyWarning(item); VerifyWarning(item);
} }
[Test] [TestCase("pausedDL")]
public void paused_item_should_have_required_properties() [TestCase("stoppedDL")]
public void paused_item_should_have_required_properties(string state)
{ {
var torrent = new QBittorrentTorrent var torrent = new QBittorrentTorrent
{ {
@@ -188,7 +189,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 0.7, Progress = 0.7,
Eta = 8640000, Eta = 8640000,
State = "pausedDL", State = state,
Label = "", Label = "",
SavePath = "" SavePath = ""
}; };
@@ -200,6 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
} }
[TestCase("pausedUP")] [TestCase("pausedUP")]
[TestCase("stoppedUP")]
[TestCase("queuedUP")] [TestCase("queuedUP")]
[TestCase("uploading")] [TestCase("uploading")]
[TestCase("stalledUP")] [TestCase("stalledUP")]
@@ -397,8 +399,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
result.OutputPath.FullPath.Should().Be(Path.Combine(torrent.SavePath, "Droned.S01.12")); result.OutputPath.FullPath.Should().Be(Path.Combine(torrent.SavePath, "Droned.S01.12"));
} }
[Test] [TestCase("pausedUP")]
public void api_261_should_use_content_path() [TestCase("stoppedUP")]
public void api_261_should_use_content_path(string state)
{ {
var torrent = new QBittorrentTorrent var torrent = new QBittorrentTorrent
{ {
@@ -407,7 +410,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 0.7, Progress = 0.7,
Eta = 8640000, Eta = 8640000,
State = "pausedUP", State = state,
Label = "", Label = "",
SavePath = @"C:\Torrents".AsOsAgnostic(), SavePath = @"C:\Torrents".AsOsAgnostic(),
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic() ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
@@ -557,6 +560,34 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic()); result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
} }
[Test]
public void should_correct_category_output_path()
{
var config = new QBittorrentPreferences
{
SavePath = @"C:\Downloads\Finished\QBittorrent".AsOsAgnostic()
};
Mocker.GetMock<IQBittorrentProxy>()
.Setup(v => v.GetConfig(It.IsAny<QBittorrentSettings>()))
.Returns(config);
Mocker.GetMock<IQBittorrentProxy>()
.Setup(v => v.GetApiVersion(It.IsAny<QBittorrentSettings>()))
.Returns(new Version(2, 0));
Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.GetLabels(It.IsAny<QBittorrentSettings>()))
.Returns(new Dictionary<string, QBittorrentLabel>
{ { "music", new QBittorrentLabel { Name = "music", SavePath = "//server/store/downloads" } } });
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"\\server\store\downloads");
}
[Test] [Test]
public async Task Download_should_handle_http_redirect_to_magnet() public async Task Download_should_handle_http_redirect_to_magnet()
{ {
@@ -656,44 +687,48 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[Test] [TestCase("pausedUP")]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set() [TestCase("stoppedUP")]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set(string state)
{ {
GivenGlobalSeedLimits(-1); GivenGlobalSeedLimits(-1);
GivenCompletedTorrent("pausedUP", ratio: 1.0f); GivenCompletedTorrent(state, ratio: 1.0f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(1.0f); GivenGlobalSeedLimits(1.0f);
GivenCompletedTorrent("pausedUP", ratio: 1.0f); GivenCompletedTorrent(state, ratio: 1.0f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(2.0f); GivenGlobalSeedLimits(2.0f);
GivenCompletedTorrent("pausedUP", ratio: 1.0f, ratioLimit: 0.8f); GivenCompletedTorrent(state, ratio: 1.0f, ratioLimit: 0.8f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused() [TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(0.2f); GivenGlobalSeedLimits(0.2f);
GivenCompletedTorrent("pausedUP", ratio: 0.5f, ratioLimit: 0.8f); GivenCompletedTorrent(state, ratio: 0.5f, ratioLimit: 0.8f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
@@ -711,33 +746,36 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, 20); GivenGlobalSeedLimits(-1, 20);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20); GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, 40); GivenGlobalSeedLimits(-1, 40);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10); GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused() [TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, 20); GivenGlobalSeedLimits(-1, 20);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40); GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
@@ -755,66 +793,72 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); GivenCompletedTorrent(state, ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40); GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds()); GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused() [TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds()); GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused(string state)
{ {
GivenGlobalSeedLimits(2.0f, 20); GivenGlobalSeedLimits(2.0f, 20);
GivenCompletedTorrent("pausedUP", ratio: 1.0f, seedingTime: 30); GivenCompletedTorrent(state, ratio: 1.0f, seedingTime: 30);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused() [TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused(string state)
{ {
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20); GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); GivenCompletedTorrent(state, ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[Test] [TestCase("pausedUP")]
public void should_not_fetch_details_twice() [TestCase("stoppedUP")]
public void should_not_fetch_details_twice(string state)
{ {
GivenGlobalSeedLimits(-1, 30); GivenGlobalSeedLimits(-1, 30);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20); GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
@@ -826,8 +870,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
.Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once()); .Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
} }
[Test] [TestCase("pausedUP")]
public void should_get_category_from_the_category_if_set() [TestCase("stoppedUP")]
public void should_get_category_from_the_category_if_set(string state)
{ {
const string category = "music-readarr"; const string category = "music-readarr";
GivenGlobalSeedLimits(1.0f); GivenGlobalSeedLimits(1.0f);
@@ -839,7 +884,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 1.0, Progress = 1.0,
Eta = 8640000, Eta = 8640000,
State = "pausedUP", State = state,
Category = category, Category = category,
SavePath = "", SavePath = "",
Ratio = 1.0f Ratio = 1.0f
@@ -851,8 +896,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.Category.Should().Be(category); item.Category.Should().Be(category);
} }
[Test] [TestCase("pausedUP")]
public void should_get_category_from_the_label_if_the_category_is_not_available() [TestCase("stoppedUP")]
public void should_get_category_from_the_label_if_the_category_is_not_available(string state)
{ {
const string category = "music-readarr"; const string category = "music-readarr";
GivenGlobalSeedLimits(1.0f); GivenGlobalSeedLimits(1.0f);
@@ -864,7 +910,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 1.0, Progress = 1.0,
Eta = 8640000, Eta = 8640000,
State = "pausedUP", State = state,
Label = category, Label = category,
SavePath = "", SavePath = "",
Ratio = 1.0f Ratio = 1.0f
@@ -478,6 +478,37 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue(); downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
} }
[TestCase("all", 0)]
[TestCase("days-archive", 15)]
[TestCase("days-delete", 15)]
public void should_set_history_removes_completed_downloads_false_for_separate_properties(string option, int number)
{
_config.Misc.history_retention_option = option;
_config.Misc.history_retention_number = number;
var downloadClientInfo = Subject.GetStatus();
downloadClientInfo.RemovesCompletedDownloads.Should().BeFalse();
}
[TestCase("number-archive", 10)]
[TestCase("number-delete", 10)]
[TestCase("number-archive", 0)]
[TestCase("number-delete", 0)]
[TestCase("days-archive", 3)]
[TestCase("days-delete", 3)]
[TestCase("all-archive", 0)]
[TestCase("all-delete", 0)]
public void should_set_history_removes_completed_downloads_true_for_separate_properties(string option, int number)
{
_config.Misc.history_retention_option = option;
_config.Misc.history_retention_number = number;
var downloadClientInfo = Subject.GetStatus();
downloadClientInfo.RemovesCompletedDownloads.Should().BeTrue();
}
[TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")] [TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")]
[TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")] [TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")]
[TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")] [TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")]
@@ -49,10 +49,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
} }
[Test] [Test]
public void magnet_download_should_not_return_the_item() public void magnet_download_should_be_returned_as_queued()
{ {
PrepareClientToReturnMagnetItem(); PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
var item = Subject.GetItems().Single();
item.Status.Should().Be(DownloadItemStatus.Queued);
} }
[Test] [Test]
@@ -60,7 +60,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
public void magnet_download_should_not_return_the_item() public void magnet_download_should_not_return_the_item()
{ {
PrepareClientToReturnMagnetItem(); PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
var item = Subject.GetItems().Single();
item.Status.Should().Be(DownloadItemStatus.Queued);
} }
[Test] [Test]
@@ -7,6 +7,7 @@ using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update; using NzbDrone.Core.Update;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck.Checks namespace NzbDrone.Core.Test.HealthCheck.Checks
{ {
@@ -21,28 +22,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns("Some Warning Message"); .Returns("Some Warning Message");
} }
[Test]
public void should_return_error_when_app_folder_is_write_protected()
{
WindowsOnly();
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
.Returns(@"C:\NzbDrone");
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderWritable(It.IsAny<string>()))
.Returns(false);
Subject.Check().ShouldBeError();
}
[Test] [Test]
public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled() public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled()
{ {
PosixOnly(); var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
const string startupFolder = @"/opt/nzbdrone";
Mocker.GetMock<IConfigFileProvider>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically) .Setup(s => s.UpdateAutomatically)
@@ -62,10 +45,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test] [Test]
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled() public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
{ {
PosixOnly(); var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
var uiFolder = @"C:\NzbDrone\UI".AsOsAgnostic();
const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI";
Mocker.GetMock<IConfigFileProvider>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically) .Setup(s => s.UpdateAutomatically)
@@ -89,7 +70,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test] [Test]
public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled() public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled()
{ {
PosixOnly(); var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
Mocker.GetMock<IConfigFileProvider>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically) .Setup(s => s.UpdateAutomatically)
@@ -101,7 +82,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<IAppFolderInfo>() Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder) .Setup(s => s.StartUpFolder)
.Returns(@"/opt/nzbdrone"); .Returns(startupFolder);
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never()); .Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never());
@@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MetadataSource.Goodreads namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-05-15 00:00:00Z")]
public class BookInfoProxyFixture : CoreTest<BookInfoProxy> public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
{ {
private MetadataProfile _metadataProfile; private MetadataProfile _metadataProfile;
@@ -15,7 +15,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MetadataSource.Goodreads namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-05-15 00:00:00Z")]
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy> public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
{ {
[SetUp] [SetUp]
@@ -38,9 +38,9 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
ExceptionVerification.IgnoreWarns(); ExceptionVerification.IgnoreWarns();
} }
[TestCase("Harry Potter and the sorcerer's stone a detailed summary", 61800696)] [TestCase("Harry Potter and the sorcerer's stone a detailed summary", 72245296)]
[TestCase("B0192CTMYG", 61209488)] [TestCase("B0192CTMYG", 61209488)]
[TestCase("9780439554930", 48517161)] [TestCase("9780439554930", 3)]
public void successful_book_search(string title, int expected) public void successful_book_search(string title, int expected)
{ {
var result = Subject.Search(title); var result = Subject.Search(title);
@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.QueueTests
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1) _trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
.All() .All()
.With(v => v.IsTrackable = true)
.With(v => v.DownloadItem = downloadItem) .With(v => v.DownloadItem = downloadItem)
.With(v => v.RemoteBook = remoteBook) .With(v => v.RemoteBook = remoteBook)
.Build() .Build()
@@ -0,0 +1,8 @@
namespace NzbDrone.Core.Authentication
{
public enum AuthenticationRequiredType
{
Enabled = 0,
DisabledForLocalAddresses = 1
}
}
@@ -1,9 +1,10 @@
namespace NzbDrone.Core.Authentication namespace NzbDrone.Core.Authentication
{ {
public enum AuthenticationType public enum AuthenticationType
{ {
None = 0, None = 0,
Basic = 1, Basic = 1,
Forms = 2 Forms = 2,
External = 3
} }
} }
@@ -15,18 +15,18 @@ namespace NzbDrone.Core.Books
public class BookCutoffService : IBookCutoffService public class BookCutoffService : IBookCutoffService
{ {
private readonly IBookRepository _bookRepository; private readonly IBookRepository _bookRepository;
private readonly IProfileService _profileService; private readonly IQualityProfileService _qualityProfileService;
public BookCutoffService(IBookRepository bookRepository, IProfileService profileService) public BookCutoffService(IBookRepository bookRepository, IQualityProfileService qualityProfileService)
{ {
_bookRepository = bookRepository; _bookRepository = bookRepository;
_profileService = profileService; _qualityProfileService = qualityProfileService;
} }
public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec) public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec)
{ {
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>(); var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
var profiles = _profileService.All(); var profiles = _qualityProfileService.All();
//Get all items less than the cutoff //Get all items less than the cutoff
foreach (var profile in profiles) foreach (var profile in profiles)
@@ -9,6 +9,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@@ -32,6 +33,7 @@ namespace NzbDrone.Core.Configuration
bool EnableSsl { get; } bool EnableSsl { get; }
bool LaunchBrowser { get; } bool LaunchBrowser { get; }
AuthenticationType AuthenticationMethod { get; } AuthenticationType AuthenticationMethod { get; }
AuthenticationRequiredType AuthenticationRequired { get; }
bool AnalyticsEnabled { get; } bool AnalyticsEnabled { get; }
string LogLevel { get; } string LogLevel { get; }
string ConsoleLogLevel { get; } string ConsoleLogLevel { get; }
@@ -51,6 +53,7 @@ namespace NzbDrone.Core.Configuration
string SyslogServer { get; } string SyslogServer { get; }
int SyslogPort { get; } int SyslogPort { get; }
string SyslogLevel { get; } string SyslogLevel { get; }
string Theme { get; }
string PostgresHost { get; } string PostgresHost { get; }
int PostgresPort { get; } int PostgresPort { get; }
string PostgresUser { get; } string PostgresUser { get; }
@@ -58,7 +61,7 @@ namespace NzbDrone.Core.Configuration
string PostgresMainDb { get; } string PostgresMainDb { get; }
string PostgresLogDb { get; } string PostgresLogDb { get; }
string PostgresCacheDb { get; } string PostgresCacheDb { get; }
string Theme { get; } bool TrustCgnatIpAddresses { get; }
} }
public class ConfigFileProvider : IConfigFileProvider public class ConfigFileProvider : IConfigFileProvider
@@ -69,6 +72,11 @@ namespace NzbDrone.Core.Configuration
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly ICached<string> _cache; private readonly ICached<string> _cache;
private readonly PostgresOptions _postgresOptions; private readonly PostgresOptions _postgresOptions;
private readonly AuthOptions _authOptions;
private readonly AppOptions _appOptions;
private readonly ServerOptions _serverOptions;
private readonly UpdateOptions _updateOptions;
private readonly LogOptions _logOptions;
private readonly string _configFile; private readonly string _configFile;
@@ -78,13 +86,23 @@ namespace NzbDrone.Core.Configuration
ICacheManager cacheManager, ICacheManager cacheManager,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IOptions<PostgresOptions> postgresOptions) IOptions<PostgresOptions> postgresOptions,
IOptions<AuthOptions> authOptions,
IOptions<AppOptions> appOptions,
IOptions<ServerOptions> serverOptions,
IOptions<UpdateOptions> updateOptions,
IOptions<LogOptions> logOptions)
{ {
_cache = cacheManager.GetCache<string>(GetType()); _cache = cacheManager.GetCache<string>(GetType());
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_configFile = appFolderInfo.GetConfigPath(); _configFile = appFolderInfo.GetConfigPath();
_postgresOptions = postgresOptions.Value; _postgresOptions = postgresOptions.Value;
_authOptions = authOptions.Value;
_appOptions = appOptions.Value;
_serverOptions = serverOptions.Value;
_updateOptions = updateOptions.Value;
_logOptions = logOptions.Value;
} }
public Dictionary<string, object> GetConfigDictionary() public Dictionary<string, object> GetConfigDictionary()
@@ -140,7 +158,7 @@ namespace NzbDrone.Core.Configuration
{ {
const string defaultValue = "*"; const string defaultValue = "*";
var bindAddress = GetValue("BindAddress", defaultValue); var bindAddress = _serverOptions.BindAddress ?? GetValue("BindAddress", defaultValue);
if (string.IsNullOrWhiteSpace(bindAddress)) if (string.IsNullOrWhiteSpace(bindAddress))
{ {
return defaultValue; return defaultValue;
@@ -150,19 +168,19 @@ namespace NzbDrone.Core.Configuration
} }
} }
public int Port => GetValueInt("Port", 8787); public int Port => _serverOptions.Port ?? GetValueInt("Port", 8787);
public int SslPort => GetValueInt("SslPort", 6868); public int SslPort => _serverOptions.SslPort ?? GetValueInt("SslPort", 6868);
public bool EnableSsl => GetValueBoolean("EnableSsl", false); public bool EnableSsl => _serverOptions.EnableSsl ?? GetValueBoolean("EnableSsl", false);
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true); public bool LaunchBrowser => _appOptions.LaunchBrowser ?? GetValueBoolean("LaunchBrowser", true);
public string ApiKey public string ApiKey
{ {
get get
{ {
var apiKey = GetValue("ApiKey", GenerateApiKey()); var apiKey = _authOptions.ApiKey ?? GetValue("ApiKey", GenerateApiKey());
if (apiKey.IsNullOrWhiteSpace()) if (apiKey.IsNullOrWhiteSpace())
{ {
@@ -178,7 +196,7 @@ namespace NzbDrone.Core.Configuration
{ {
get get
{ {
var enabled = GetValueBoolean("AuthenticationEnabled", false, false); var enabled = _authOptions.Enabled ?? GetValueBoolean("AuthenticationEnabled", false, false);
if (enabled) if (enabled)
{ {
@@ -186,17 +204,24 @@ namespace NzbDrone.Core.Configuration
return AuthenticationType.Basic; return AuthenticationType.Basic;
} }
return GetValueEnum("AuthenticationMethod", AuthenticationType.None); return Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
? enumValue
: GetValueEnum("AuthenticationMethod", AuthenticationType.None);
} }
} }
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false); public AuthenticationRequiredType AuthenticationRequired =>
Enum.TryParse<AuthenticationRequiredType>(_authOptions.Required, out var enumValue)
? enumValue
: GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled);
public bool AnalyticsEnabled => _logOptions.AnalyticsEnabled ?? GetValueBoolean("AnalyticsEnabled", true, persist: false);
// TODO: Change back to "master" for the first stable release // TODO: Change back to "master" for the first stable release
public string Branch => GetValue("Branch", "develop").ToLowerInvariant(); public string Branch => _updateOptions.Branch ?? GetValue("Branch", "develop").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info"); public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "debug").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); public string ConsoleLogLevel => _logOptions.ConsoleLevel ?? GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false); public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false); public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
@@ -206,18 +231,18 @@ namespace NzbDrone.Core.Configuration
public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false); public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false);
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false); public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
public string Theme => GetValue("Theme", "auto", persist: false); public string Theme => _appOptions.Theme ?? GetValue("Theme", "auto", persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public bool LogSql => _logOptions.Sql ?? GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public int LogRotate => _logOptions.Rotate ?? GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); public bool FilterSentryEvents => _logOptions.FilterSentryEvents ?? GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => GetValue("SslCertPath", ""); public string SslCertPath => _serverOptions.SslCertPath ?? GetValue("SslCertPath", "");
public string SslCertPassword => GetValue("SslCertPassword", ""); public string SslCertPassword => _serverOptions.SslCertPassword ?? GetValue("SslCertPassword", "");
public string UrlBase public string UrlBase
{ {
get get
{ {
var urlBase = GetValue("UrlBase", "").Trim('/'); var urlBase = (_serverOptions.UrlBase ?? GetValue("UrlBase", "")).Trim('/');
if (urlBase.IsNullOrWhiteSpace()) if (urlBase.IsNullOrWhiteSpace())
{ {
@@ -229,19 +254,22 @@ namespace NzbDrone.Core.Configuration
} }
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI"; public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName); public string InstanceName => _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false); public bool UpdateAutomatically => _updateOptions.Automatically ?? GetValueBoolean("UpdateAutomatically", OsInfo.IsWindows, false);
public UpdateMechanism UpdateMechanism => GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false); public UpdateMechanism UpdateMechanism =>
Enum.TryParse<UpdateMechanism>(_updateOptions.Mechanism, out var enumValue)
? enumValue
: GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false);
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false); public string UpdateScriptPath => _updateOptions.ScriptPath ?? GetValue("UpdateScriptPath", "", false);
public string SyslogServer => GetValue("SyslogServer", "", persist: false); public string SyslogServer => _logOptions.SyslogServer ?? GetValue("SyslogServer", "", persist: false);
public int SyslogPort => GetValueInt("SyslogPort", 514, persist: false); public int SyslogPort => _logOptions.SyslogPort ?? GetValueInt("SyslogPort", 514, persist: false);
public string SyslogLevel => GetValue("SyslogLevel", LogLevel, false).ToLowerInvariant(); public string SyslogLevel => _logOptions.SyslogLevel ?? GetValue("SyslogLevel", LogLevel, persist: false).ToLowerInvariant();
public int GetValueInt(string key, int defaultValue, bool persist = true) public int GetValueInt(string key, int defaultValue, bool persist = true)
{ {
@@ -330,7 +358,7 @@ namespace NzbDrone.Core.Configuration
} }
// If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL // If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL
if (EnableSsl && (GetValue("SslCertHash", null).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace())) if (EnableSsl && (GetValue("SslCertHash", string.Empty, false).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
{ {
SetValue("EnableSsl", false); SetValue("EnableSsl", false);
} }
@@ -377,13 +405,21 @@ namespace NzbDrone.Core.Configuration
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Readarr will recreate it."); throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Readarr will recreate it.");
} }
return XDocument.Parse(_diskProvider.ReadAllText(_configFile)); var xDoc = XDocument.Parse(_diskProvider.ReadAllText(_configFile));
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).ToList();
if (config.Count != 1)
{
throw new InvalidConfigFileException($"{_configFile} is invalid. Please delete the config file and Readarr will recreate it.");
}
return xDoc;
} }
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes")); var newXDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME)); newXDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc; return newXDoc;
} }
} }
catch (XmlException ex) catch (XmlException ex)
@@ -427,5 +463,7 @@ namespace NzbDrone.Core.Configuration
{ {
SetValue("ApiKey", GenerateApiKey()); SetValue("ApiKey", GenerateApiKey());
} }
public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false);
} }
} }
@@ -404,6 +404,12 @@ namespace NzbDrone.Core.Configuration
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty); public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
public bool TrustCgnatIpAddresses
{
get { return GetValueBoolean("TrustCgnatIpAddresses", false); }
set { SetValue("TrustCgnatIpAddresses", value); }
}
private string GetValue(string key) private string GetValue(string key)
{ {
return GetValue(key, string.Empty); return GetValue(key, string.Empty);
@@ -219,7 +219,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
protected virtual IList<TableDefinition> ReadTables() protected virtual IList<TableDefinition> ReadTables()
{ {
const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;"; const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name;";
var dtTable = Read(sqlCommand).Tables[0]; var dtTable = Read(sqlCommand).Tables[0];
var tableDefinitionList = new List<TableDefinition>(); var tableDefinitionList = new List<TableDefinition>();
@@ -122,14 +122,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
} }
var items = new List<DownloadClientItem>(); var items = new List<DownloadClientItem>();
var ignoredCount = 0;
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
if (torrent.Hash == null) // Silently ignore torrents with no hash
if (torrent.Hash.IsNullOrWhiteSpace())
{ {
continue; continue;
} }
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
if (torrent.Name.IsNullOrWhiteSpace())
{
ignoredCount++;
continue;
}
var item = new DownloadClientItem(); var item = new DownloadClientItem();
item.DownloadId = torrent.Hash.ToUpper(); item.DownloadId = torrent.Hash.ToUpper();
item.Title = torrent.Name; item.Title = torrent.Name;
@@ -187,6 +196,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
items.Add(item); items.Add(item);
} }
if (ignoredCount > 0)
{
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
}
return items; return items;
} }
@@ -199,9 +213,18 @@ namespace NzbDrone.Core.Download.Clients.Deluge
{ {
var config = _proxy.GetConfig(Settings); var config = _proxy.GetConfig(Settings);
var label = _proxy.GetLabelOptions(Settings); var label = _proxy.GetLabelOptions(Settings);
OsPath destDir; OsPath destDir;
if (label != null && label.ApplyMoveCompleted && label.MoveCompleted) if (Settings.CompletedDirectory.IsNotNullOrWhiteSpace())
{
destDir = new OsPath(Settings.CompletedDirectory);
}
else if (Settings.DownloadDirectory.IsNotNullOrWhiteSpace())
{
destDir = new OsPath(Settings.DownloadDirectory);
}
else if (label is { ApplyMoveCompleted: true, MoveCompleted: true })
{ {
// if label exists and a label completed path exists and is enabled use it instead of global // if label exists and a label completed path exists and is enabled use it instead of global
destDir = new OsPath(label.MoveCompletedPath); destDir = new OsPath(label.MoveCompletedPath);
@@ -217,7 +240,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
var status = new DownloadClientInfo var status = new DownloadClientInfo
{ {
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost" IsLocalhost = Settings.Host is "127.0.0.1" or "localhost"
}; };
if (!destDir.IsEmpty) if (!destDir.IsEmpty)
@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
// Avoid removing torrents that haven't reached the global max ratio. // Avoid removing torrents that haven't reached the global max ratio.
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api). // Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
item.CanMoveFiles = item.CanBeRemoved = torrent.State == "pausedUP" && HasReachedSeedLimit(torrent, config); item.CanMoveFiles = item.CanBeRemoved = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config);
switch (torrent.State) switch (torrent.State)
{ {
@@ -248,7 +248,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
item.Message = "qBittorrent is reporting an error"; item.Message = "qBittorrent is reporting an error";
break; break;
case "pausedDL": // torrent is paused and has NOT finished downloading case "stoppedDL": // torrent is stopped and has NOT finished downloading
case "pausedDL": // torrent is paused and has NOT finished downloading (qBittorrent < 5)
item.Status = DownloadItemStatus.Paused; item.Status = DownloadItemStatus.Paused;
break; break;
@@ -259,7 +260,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
item.Status = DownloadItemStatus.Queued; item.Status = DownloadItemStatus.Queued;
break; break;
case "pausedUP": // torrent is paused and has finished downloading case "pausedUP": // torrent is paused and has finished downloading (qBittorent < 5)
case "stoppedUP": // torrent is stopped and has finished downloading
case "uploading": // torrent is being seeded and data is being transferred case "uploading": // torrent is being seeded and data is being transferred
case "stalledUP": // torrent is being seeded, but no connection were made case "stalledUP": // torrent is being seeded, but no connection were made
case "queuedUP": // queuing is enabled and torrent is queued for upload case "queuedUP": // queuing is enabled and torrent is queued for upload
@@ -279,6 +281,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break; break;
case "metaDL": // torrent magnet is being downloaded case "metaDL": // torrent magnet is being downloaded
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
if (config.DhtEnabled) if (config.DhtEnabled)
{ {
item.Status = DownloadItemStatus.Queued; item.Status = DownloadItemStatus.Queued;
@@ -293,7 +296,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break; break;
case "forcedDL": // torrent is being downloaded, and was forced started case "forcedDL": // torrent is being downloaded, and was forced started
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
case "moving": // torrent is being moved from a folder case "moving": // torrent is being moved from a folder
case "downloading": // torrent is being downloaded and data is being transferred case "downloading": // torrent is being downloaded and data is being transferred
item.Status = DownloadItemStatus.Downloading; item.Status = DownloadItemStatus.Downloading;
@@ -375,7 +377,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
if (Proxy.GetLabels(Settings).TryGetValue(Settings.MusicCategory, out var label) && label.SavePath.IsNotNullOrWhiteSpace()) if (Proxy.GetLabels(Settings).TryGetValue(Settings.MusicCategory, out var label) && label.SavePath.IsNotNullOrWhiteSpace())
{ {
var labelDir = new OsPath(label.SavePath); var savePath = label.SavePath;
if (savePath.StartsWith("//"))
{
_logger.Trace("Replacing double forward slashes in path '{0}'. If this is not meant to be a Windows UNC path fix the 'Save Path' in qBittorrent's {1} category", savePath, Settings.MusicCategory);
savePath = savePath.Replace('/', '\\');
}
var labelDir = new OsPath(savePath);
if (labelDir.IsRooted) if (labelDir.IsRooted)
{ {
@@ -26,8 +26,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings); Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings); void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings); void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
void PauseTorrent(string hash, QBittorrentSettings settings);
void ResumeTorrent(string hash, QBittorrentSettings settings);
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings); void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
} }
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
request.AddFormParameter("paused", false); request.AddFormParameter("paused", false);
} }
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause) else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{ {
request.AddFormParameter("paused", true); request.AddFormParameter("paused", true);
} }
@@ -178,7 +178,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
request.AddFormParameter("paused", false); request.AddFormParameter("paused", false);
} }
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause) else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{ {
request.AddFormParameter("paused", true); request.AddFormParameter("paused", true);
} }
@@ -214,7 +214,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5 // if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound) if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound)
{ {
var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel") var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel")
.Post() .Post()
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled // qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Forbidden) if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Forbidden)
{ {
return; return;
} }
@@ -266,22 +266,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
} }
} }
public void PauseTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/command/pause")
.Post()
.AddFormParameter("hash", hash);
ProcessRequest(request, settings);
}
public void ResumeTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/command/resume")
.Post()
.AddFormParameter("hash", hash);
ProcessRequest(request, settings);
}
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings) public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
{ {
var request = BuildRequest(settings).Resource("/command/setForceStart") var request = BuildRequest(settings).Resource("/command/setForceStart")
@@ -246,14 +246,20 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
request.AddFormParameter("category", settings.MusicCategory); request.AddFormParameter("category", settings.MusicCategory);
} }
// Note: ForceStart is handled by separate api call // Avoid extraneous API version check if initial state is ForceStart
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start) if ((QBittorrentState)settings.InitialState is QBittorrentState.Start or QBittorrentState.Stop)
{ {
request.AddFormParameter("paused", false); var stoppedParameterName = GetApiVersion(settings) >= new Version(2, 11, 0) ? "stopped" : "paused";
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause) // Note: ForceStart is handled by separate api call
{ if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
request.AddFormParameter("paused", true); {
request.AddFormParameter(stoppedParameterName, false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{
request.AddFormParameter(stoppedParameterName, true);
}
} }
if (settings.SequentialOrder) if (settings.SequentialOrder)
@@ -291,7 +297,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// setShareLimits was added in api v2.0.1 so catch it case of the unlikely event that someone has api v2.0 // setShareLimits was added in api v2.0.1 so catch it case of the unlikely event that someone has api v2.0
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound) if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound)
{ {
return; return;
} }
@@ -313,7 +319,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled // qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Conflict) if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Conflict)
{ {
return; return;
} }
@@ -322,22 +328,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
} }
} }
public void PauseTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/pause")
.Post()
.AddFormParameter("hashes", hash);
ProcessRequest(request, settings);
}
public void ResumeTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/resume")
.Post()
.AddFormParameter("hashes", hash);
ProcessRequest(request, settings);
}
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings) public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
{ {
var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart") var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart")
@@ -1,9 +1,16 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Download.Clients.QBittorrent namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
public enum QBittorrentState public enum QBittorrentState
{ {
[FieldOption(Label = "Started")]
Start = 0, Start = 0,
[FieldOption(Label = "Force Started")]
ForceStart = 1, ForceStart = 1,
Pause = 2
[FieldOption(Label = "Stopped")]
Stop = 2
} }
} }
@@ -263,20 +263,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) }; status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
} }
if (config.Misc.history_retention.IsNullOrWhiteSpace()) status.RemovesCompletedDownloads = RemovesCompletedDownloads(config);
{
status.RemovesCompletedDownloads = false;
}
else if (config.Misc.history_retention.EndsWith("d"))
{
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
out var daysRetention);
status.RemovesCompletedDownloads = daysRetention < 14;
}
else
{
status.RemovesCompletedDownloads = config.Misc.history_retention != "0";
}
return status; return status;
} }
@@ -518,6 +505,43 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
return categories.Contains(category); return categories.Contains(category);
} }
private bool RemovesCompletedDownloads(SabnzbdConfig config)
{
var retention = config.Misc.history_retention;
var option = config.Misc.history_retention_option;
var number = config.Misc.history_retention_number;
switch (option)
{
case "all":
return false;
case "number-archive":
case "number-delete":
return true;
case "days-archive":
case "days-delete":
return number < 14;
case "all-archive":
case "all-delete":
return true;
}
// TODO: Remove these checks once support for SABnzbd < 4.3 is removed
if (retention.IsNullOrWhiteSpace())
{
return false;
}
if (retention.EndsWith("d"))
{
int.TryParse(config.Misc.history_retention.AsSpan(0, config.Misc.history_retention.Length - 1),
out var daysRetention);
return daysRetention < 14;
}
return retention != "0";
}
private bool ValidatePath(DownloadClientItem downloadClientItem) private bool ValidatePath(DownloadClientItem downloadClientItem)
{ {
var downloadItemOutputPath = downloadClientItem.OutputPath; var downloadItemOutputPath = downloadClientItem.OutputPath;
@@ -30,6 +30,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public bool enable_date_sorting { get; set; } public bool enable_date_sorting { get; set; }
public bool pre_check { get; set; } public bool pre_check { get; set; }
public string history_retention { get; set; } public string history_retention { get; set; }
public string history_retention_option { get; set; }
public int history_retention_number { get; set; }
} }
public class SabnzbdCategory public class SabnzbdCategory
@@ -41,12 +41,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
// If totalsize == 0 the torrent is a magnet downloading metadata
if (torrent.TotalSize == 0)
{
continue;
}
var outputPath = new OsPath(torrent.DownloadDir); var outputPath = new OsPath(torrent.DownloadDir);
if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
@@ -97,6 +91,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Warning; item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString; item.Message = torrent.ErrorString;
} }
else if (torrent.TotalSize == 0)
{
item.Status = DownloadItemStatus.Queued;
}
else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped || else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
torrent.Status == TransmissionTorrentStatus.Seeding || torrent.Status == TransmissionTorrentStatus.Seeding ||
torrent.Status == TransmissionTorrentStatus.SeedingWait)) torrent.Status == TransmissionTorrentStatus.SeedingWait))
@@ -11,8 +11,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public bool IsFinished { get; set; } public bool IsFinished { get; set; }
public long Eta { get; set; } public long Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; } public TransmissionTorrentStatus Status { get; set; }
public int SecondsDownloading { get; set; } public long SecondsDownloading { get; set; }
public int SecondsSeeding { get; set; } public long SecondsSeeding { get; set; }
public string ErrorString { get; set; } public string ErrorString { get; set; }
public long DownloadedEver { get; set; } public long DownloadedEver { get; set; }
public long UploadedEver { get; set; } public long UploadedEver { get; set; }
@@ -41,18 +41,23 @@ namespace NzbDrone.Core.Download
var blockedProviders = new HashSet<int>(_downloadClientStatusService.GetBlockedProviders().Select(v => v.ProviderId)); var blockedProviders = new HashSet<int>(_downloadClientStatusService.GetBlockedProviders().Select(v => v.ProviderId));
var availableProviders = _downloadClientFactory.GetAvailableProviders().Where(v => v.Protocol == downloadProtocol).ToList(); var availableProviders = _downloadClientFactory.GetAvailableProviders().Where(v => v.Protocol == downloadProtocol).ToList();
if (tags != null) if (!availableProviders.Any())
{
return null;
}
if (tags is { Count: > 0 })
{ {
var matchingTagsClients = availableProviders.Where(i => i.Definition.Tags.Intersect(tags).Any()).ToList(); var matchingTagsClients = availableProviders.Where(i => i.Definition.Tags.Intersect(tags).Any()).ToList();
availableProviders = matchingTagsClients.Count > 0 ? availableProviders = matchingTagsClients.Count > 0 ?
matchingTagsClients : matchingTagsClients :
availableProviders.Where(i => i.Definition.Tags.Empty()).ToList(); availableProviders.Where(i => i.Definition.Tags.Empty()).ToList();
}
if (!availableProviders.Any()) if (!availableProviders.Any())
{ {
return null; throw new DownloadClientUnavailableException("No download client was found without tags or a matching author tag. Please check your settings.");
}
} }
if (indexerId > 0) if (indexerId > 0)
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
var startupFolder = _appFolderInfo.StartUpFolder; var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI"); var uiFolder = Path.Combine(startupFolder, "UI");
if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) && if (_configFileProvider.UpdateAutomatically &&
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
!_osInfo.IsDocker) !_osInfo.IsDocker)
{ {
@@ -184,7 +184,7 @@ namespace NzbDrone.Core.ImportLists
report.BookGoodreadsId = remoteBook.ForeignBookId; report.BookGoodreadsId = remoteBook.ForeignBookId;
report.Book = remoteBook.Title; report.Book = remoteBook.Title;
report.Author ??= remoteBook.AuthorMetadata.Value.Name; report.Author ??= remoteBook.AuthorMetadata.Value.Name;
report.AuthorGoodreadsId ??= remoteBook.AuthorMetadata.Value.Name; report.AuthorGoodreadsId ??= remoteBook.AuthorMetadata.Value.ForeignAuthorId;
} }
catch (BookNotFoundException) catch (BookNotFoundException)
{ {
@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases) protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{ {
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray(); var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty()) if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{ {
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any()) if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
{ {
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]); _logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
return false;
} }
else
{ _logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
}
} }
return true; return true;
+19 -19
View File
@@ -268,26 +268,26 @@ namespace NzbDrone.Core.Indexers
protected virtual RssEnclosure[] GetEnclosures(XElement item) protected virtual RssEnclosure[] GetEnclosures(XElement item)
{ {
var enclosures = item.Elements("enclosure") var enclosures = item.Elements("enclosure")
.Select(v => .Select(v =>
{ {
try try
{ {
return new RssEnclosure return new RssEnclosure
{ {
Url = v.Attribute("url")?.Value, Url = v.Attribute("url")?.Value,
Type = v.Attribute("type")?.Value, Type = v.Attribute("type")?.Value,
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0 Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
}; };
} }
catch (Exception e) catch (Exception ex)
{ {
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title()); _logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
} }
return null; return null;
}) })
.Where(v => v != null) .Where(v => v != null)
.ToArray(); .ToArray();
return enclosures; return enclosures;
} }
@@ -48,10 +48,10 @@ namespace NzbDrone.Core.Indexers
public class SeedCriteriaSettings public class SeedCriteriaSettings
{ {
[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")] [FieldDefinition(0, Type = FieldType.Number, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
public double? SeedRatio { get; set; } public double? SeedRatio { get; set; }
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] [FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
public int? SeedTime { get; set; } public int? SeedTime { get; set; }
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Discography Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] [FieldDefinition(2, Type = FieldType.Textbox, Label = "Discography Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]
@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases) protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{ {
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray(); var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty()) if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{ {
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any()) if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
{ {
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]); _logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
return false;
} }
else
{ _logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
}
} }
return true; return true;
@@ -57,33 +57,36 @@ namespace NzbDrone.Core.Instrumentation
{ {
try try
{ {
var log = new Log(); var log = new Log
log.Time = logEvent.TimeStamp; {
log.Message = CleanseLogMessage.Cleanse(logEvent.FormattedMessage); Time = logEvent.TimeStamp,
Logger = logEvent.LoggerName,
log.Logger = logEvent.LoggerName; Level = logEvent.Level.Name
};
if (log.Logger.StartsWith("NzbDrone.")) if (log.Logger.StartsWith("NzbDrone."))
{ {
log.Logger = log.Logger.Remove(0, 9); log.Logger = log.Logger.Remove(0, 9);
} }
var message = logEvent.FormattedMessage;
if (logEvent.Exception != null) if (logEvent.Exception != null)
{ {
if (string.IsNullOrWhiteSpace(log.Message)) if (string.IsNullOrWhiteSpace(message))
{ {
log.Message = logEvent.Exception.Message; message = logEvent.Exception.Message;
} }
else else
{ {
log.Message += ": " + logEvent.Exception.Message; message += ": " + logEvent.Exception.Message;
} }
log.Exception = logEvent.Exception.ToString(); log.Exception = CleanseLogMessage.Cleanse(logEvent.Exception.ToString());
log.ExceptionType = logEvent.Exception.GetType().ToString(); log.ExceptionType = logEvent.Exception.GetType().ToString();
} }
log.Level = logEvent.Level.Name; log.Message = CleanseLogMessage.Cleanse(message);
var connectionInfo = _connectionStringFactory.LogDbConnection; var connectionInfo = _connectionStringFactory.LogDbConnection;
+53 -38
View File
@@ -3,8 +3,8 @@
"Year": "عام", "Year": "عام",
"WeekColumnHeader": "رأس عمود الأسبوع", "WeekColumnHeader": "رأس عمود الأسبوع",
"Version": "الإصدار", "Version": "الإصدار",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية", "BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "فرع لاستخدامه لتحديث Radarr", "BranchUpdate": "فرع لاستخدامه لتحديث {appName}",
"Username": "اسم المستخدم", "Username": "اسم المستخدم",
"UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet", "UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet",
"UsenetDelay": "تأخير يوزنت", "UsenetDelay": "تأخير يوزنت",
@@ -15,7 +15,7 @@
"UpgradeAllowedHelpText": "إذا لن تتم ترقية الصفات المعوقين", "UpgradeAllowedHelpText": "إذا لن تتم ترقية الصفات المعوقين",
"Updates": "التحديثات", "Updates": "التحديثات",
"UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث", "UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث",
"UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في Radarr", "UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في {appName}",
"UpdateAutomaticallyHelpText": "تنزيل التحديثات وتثبيتها تلقائيًا. ستظل قادرًا على التثبيت من النظام: التحديثات", "UpdateAutomaticallyHelpText": "تنزيل التحديثات وتثبيتها تلقائيًا. ستظل قادرًا على التثبيت من النظام: التحديثات",
"UpdateAll": "تحديث الجميع", "UpdateAll": "تحديث الجميع",
"UnmonitoredHelpText": "قم بتضمين الأفلام غير الخاضعة للرقابة في موجز iCal", "UnmonitoredHelpText": "قم بتضمين الأفلام غير الخاضعة للرقابة في موجز iCal",
@@ -56,7 +56,7 @@
"URLBase": "قاعدة URL", "URLBase": "قاعدة URL",
"UISettings": "إعدادات واجهة المستخدم", "UISettings": "إعدادات واجهة المستخدم",
"UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح", "UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح",
"UILanguageHelpText": "اللغة التي سيستخدمها Radarr لواجهة المستخدم", "UILanguageHelpText": "اللغة التي سيستخدمها {appName} لواجهة المستخدم",
"UILanguage": "لغة واجهة المستخدم", "UILanguage": "لغة واجهة المستخدم",
"TotalFileSize": "إجمالي حجم الملف", "TotalFileSize": "إجمالي حجم الملف",
"Torrents": "السيول", "Torrents": "السيول",
@@ -73,7 +73,7 @@
"Tags": "العلامات", "Tags": "العلامات",
"TagIsNotUsedAndCanBeDeleted": "العلامة غير مستخدمة ويمكن حذفها", "TagIsNotUsedAndCanBeDeleted": "العلامة غير مستخدمة ويمكن حذفها",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "سيتم استخدامها عند استخدام البحث التفاعلي", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "سيتم استخدامها عند استخدام البحث التفاعلي",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Radarr", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة {appName}",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "البحث غير معتمد مع هذا المفهرس", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "البحث غير معتمد مع هذا المفهرس",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس",
"SuccessMyWorkIsDoneNoFilesToRetag": "نجاح! تم الانتهاء من عملي ، ولا توجد ملفات لإعادة تسميتها.", "SuccessMyWorkIsDoneNoFilesToRetag": "نجاح! تم الانتهاء من عملي ، ولا توجد ملفات لإعادة تسميتها.",
@@ -92,7 +92,7 @@
"Source": "مصدر", "Source": "مصدر",
"SorryThatBookCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.", "SorryThatBookCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.",
"SorryThatAuthorCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.", "SorryThatAuthorCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.",
"SkipFreeSpaceCheckWhenImportingHelpText": "استخدم عندما يتعذر على Radarr اكتشاف مساحة خالية من مجلد جذر الفيلم", "SkipFreeSpaceCheckWhenImportingHelpText": "استخدم عندما يتعذر على {appName} اكتشاف مساحة خالية من مجلد جذر الفيلم",
"SkipFreeSpaceCheck": "تخطي فحص المساحة الخالية", "SkipFreeSpaceCheck": "تخطي فحص المساحة الخالية",
"Size": " بحجم", "Size": " بحجم",
"ShownAboveEachColumnWhenWeekIsTheActiveView": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط", "ShownAboveEachColumnWhenWeekIsTheActiveView": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
@@ -133,19 +133,19 @@
"Result": "نتيجة", "Result": "نتيجة",
"RestoreBackup": "استرجاع النسخة الاحتياطية", "RestoreBackup": "استرجاع النسخة الاحتياطية",
"Restore": "استعادة", "Restore": "استعادة",
"RestartReadarr": "أعد تشغيل Radarr", "RestartReadarr": "أعد تشغيل {appName}",
"RestartNow": "اعد البدء الان", "RestartNow": "اعد البدء الان",
"Restart": "إعادة تشغيل", "Restart": "إعادة تشغيل",
"ResetAPIKeyMessageText": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟", "ResetAPIKeyMessageText": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟",
"ResetAPIKey": "إعادة تعيين مفتاح API", "ResetAPIKey": "إعادة تعيين مفتاح API",
"Reset": "إعادة تعيين", "Reset": "إعادة تعيين",
"RescanAuthorFolderAfterRefresh": "إعادة فحص مجلد الفيلم بعد التحديث", "RescanAuthorFolderAfterRefresh": "إعادة فحص مجلد الفيلم بعد التحديث",
"RescanAfterRefreshHelpTextWarning": "لن يكتشف Radarr تلقائيًا التغييرات التي تطرأ على الملفات عند عدم تعيينه على \"دائمًا\"", "RescanAfterRefreshHelpTextWarning": "لن يكتشف {appName} تلقائيًا التغييرات التي تطرأ على الملفات عند عدم تعيينه على \"دائمًا\"",
"RequiredPlaceHolder": "أضف قيدًا جديدًا", "RequiredPlaceHolder": "أضف قيدًا جديدًا",
"RequiredHelpText": "يجب أن يحتوي الإصدار على واحد على الأقل من هذه المصطلحات (غير حساس لحالة الأحرف)", "RequiredHelpText": "يجب أن يحتوي الإصدار على واحد على الأقل من هذه المصطلحات (غير حساس لحالة الأحرف)",
"ReplaceIllegalCharacters": "استبدل الأحرف غير القانونية", "ReplaceIllegalCharacters": "استبدل الأحرف غير القانونية",
"Reorder": "إعادة ترتيب", "Reorder": "إعادة ترتيب",
"RenameBooksHelpText": "سيستخدم Radarr اسم الملف الحالي إذا تم تعطيل إعادة التسمية", "RenameBooksHelpText": "سيستخدم {appName} اسم الملف الحالي إذا تم تعطيل إعادة التسمية",
"RemovedFromTaskQueue": "تمت إزالته من قائمة انتظار المهام", "RemovedFromTaskQueue": "تمت إزالته من قائمة انتظار المهام",
"RemoveTagRemovingTag": "إزالة العلامة", "RemoveTagRemovingTag": "إزالة العلامة",
"RemoveTagExistingTag": "علامة موجودة", "RemoveTagExistingTag": "علامة موجودة",
@@ -174,7 +174,7 @@
"Reason": "السبب", "Reason": "السبب",
"Real": "حقيقة", "Real": "حقيقة",
"ReadarrTags": "العلامات الرادار", "ReadarrTags": "العلامات الرادار",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "يدعم Radarr أي مفهرس يستخدم معيار Newznab ، بالإضافة إلى مفهرسات أخرى مذكورة أدناه.", "ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "يدعم {appName} أي مفهرس يستخدم معيار Newznab ، بالإضافة إلى مفهرسات أخرى مذكورة أدناه.",
"ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات", "ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات",
"RSSSyncInterval": "الفاصل الزمني لمزامنة RSS", "RSSSyncInterval": "الفاصل الزمني لمزامنة RSS",
"RSSSync": "مزامنة RSS", "RSSSync": "مزامنة RSS",
@@ -196,7 +196,7 @@
"Profiles": "مظهر", "Profiles": "مظهر",
"PreviewRename": "معاينة إعادة تسمية", "PreviewRename": "معاينة إعادة تسمية",
"PosterSize": "حجم الملصق", "PosterSize": "حجم الملصق",
"GrabReleaseMessageText": "لم يتمكن Radarr من تحديد الفيلم الذي كان هذا الإصدار من أجله. قد يتعذر على Radarr استيراد هذا الإصدار تلقائيًا. هل تريد انتزاع \"{0}\"؟", "GrabReleaseMessageText": "لم يتمكن {appName} من تحديد الفيلم الذي كان هذا الإصدار من أجله. قد يتعذر على {appName} استيراد هذا الإصدار تلقائيًا. هل تريد انتزاع \"{0}\"؟",
"GrabRelease": "انتزاع الإصدار", "GrabRelease": "انتزاع الإصدار",
"GrabID": "انتزاع معرف", "GrabID": "انتزاع معرف",
"Grab": "إختطاف", "Grab": "إختطاف",
@@ -276,10 +276,10 @@
"Dates": "تواريخ", "Dates": "تواريخ",
"DatabaseMigration": "ترحيل DB", "DatabaseMigration": "ترحيل DB",
"CutoffUnmet": "قطع غير ملباة", "CutoffUnmet": "قطع غير ملباة",
"CutoffHelpText": "بمجرد الوصول إلى هذه الجودة ، لن يقوم Radarr بتنزيل الأفلام", "CutoffHelpText": "بمجرد الوصول إلى هذه الجودة ، لن يقوم {appName} بتنزيل الأفلام",
"CreateGroup": "إنشاء مجموعة", "CreateGroup": "إنشاء مجموعة",
"CreateEmptyAuthorFoldersHelpText": "قم بإنشاء مجلدات فيلم مفقودة أثناء فحص القرص", "CreateEmptyAuthorFoldersHelpText": "قم بإنشاء مجلدات فيلم مفقودة أثناء فحص القرص",
"CopyUsingHardlinksHelpTextWarning": "من حين لآخر ، قد تمنع أقفال الملفات إعادة تسمية الملفات التي يتم زرعها. يمكنك تعطيل البذر مؤقتًا واستخدام وظيفة إعادة تسمية Radarr كحل بديل.", "CopyUsingHardlinksHelpTextWarning": "من حين لآخر ، قد تمنع أقفال الملفات إعادة تسمية الملفات التي يتم زرعها. يمكنك تعطيل البذر مؤقتًا واستخدام وظيفة إعادة تسمية {appName} كحل بديل.",
"CopyUsingHardlinksHelpText": "استخدم Hardlinks عند محاولة نسخ الملفات من السيول التي لا تزال تحت البذور", "CopyUsingHardlinksHelpText": "استخدم Hardlinks عند محاولة نسخ الملفات من السيول التي لا تزال تحت البذور",
"Connections": "روابط", "Connections": "روابط",
"ConnectSettings": "ربط الإعدادات", "ConnectSettings": "ربط الإعدادات",
@@ -291,16 +291,16 @@
"ClientPriority": "أولوية العميل", "ClientPriority": "أولوية العميل",
"ClickToChangeQuality": "انقر لتغيير الجودة", "ClickToChangeQuality": "انقر لتغيير الجودة",
"Clear": "واضح", "Clear": "واضح",
"ChownGroupHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل Radarr هو مالك الملف. من الأفضل التأكد من أن عميل التنزيل يستخدم نفس مجموعة Radarr.", "ChownGroupHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل {appName} هو مالك الملف. من الأفضل التأكد من أن عميل التنزيل يستخدم نفس مجموعة {appName}.",
"ChownGroupHelpText": "اسم المجموعة أو gid. استخدم gid لأنظمة الملفات البعيدة.", "ChownGroupHelpText": "اسم المجموعة أو gid. استخدم gid لأنظمة الملفات البعيدة.",
"ChmodFolderHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل Radarr هو مالك الملف. من الأفضل التأكد من قيام عميل التنزيل بتعيين الأذونات بشكل صحيح.", "ChmodFolderHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل {appName} هو مالك الملف. من الأفضل التأكد من قيام عميل التنزيل بتعيين الأذونات بشكل صحيح.",
"ChmodFolderHelpText": "Octal ، يتم تطبيقه أثناء الاستيراد / إعادة التسمية إلى مجلدات وملفات الوسائط (بدون تنفيذ بت)", "ChmodFolderHelpText": "Octal ، يتم تطبيقه أثناء الاستيراد / إعادة التسمية إلى مجلدات وملفات الوسائط (بدون تنفيذ بت)",
"ChmodFolder": "مجلد chmod", "ChmodFolder": "مجلد chmod",
"ChangeHasNotBeenSavedYet": "لم يتم حفظ التغيير بعد", "ChangeHasNotBeenSavedYet": "لم يتم حفظ التغيير بعد",
"ChangeFileDate": "تغيير تاريخ الملف", "ChangeFileDate": "تغيير تاريخ الملف",
"CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS", "CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS",
"CertificateValidation": "التحقق من صحة الشهادة", "CertificateValidation": "التحقق من صحة الشهادة",
"CancelMessageText": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟", "CancelPendingTask": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
"Cancel": "إلغاء", "Cancel": "إلغاء",
"CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط", "CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
"Calendar": "التقويم", "Calendar": "التقويم",
@@ -314,17 +314,17 @@
"Backups": "النسخ الاحتياطية", "Backups": "النسخ الاحتياطية",
"BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا", "BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا",
"BackupNow": "اعمل نسخة احتياطية الان", "BackupNow": "اعمل نسخة احتياطية الان",
"BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ Radarr", "BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ {appName}",
"Automatic": "تلقائي", "Automatic": "تلقائي",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "الأفلام المحذوفة من القرص لا يتم مراقبتها تلقائيًا في Radarr", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "الأفلام المحذوفة من القرص لا يتم مراقبتها تلقائيًا في {appName}",
"AutoRedownloadFailedHelpText": "ابحث تلقائيًا عن إصدار مختلف وحاول تنزيله", "AutoRedownloadFailedHelpText": "ابحث تلقائيًا عن إصدار مختلف وحاول تنزيله",
"AuthorClickToChangeBook": "انقر لتغيير الفيلم", "AuthorClickToChangeBook": "انقر لتغيير الفيلم",
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Radarr", "AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى {appName}",
"Authentication": "المصادقة", "Authentication": "المصادقة",
"ApplyTags": "تطبيق العلامات", "ApplyTags": "تطبيق العلامات",
"AppDataDirectory": "دليل AppData", "AppDataDirectory": "دليل AppData",
"AnalyticsEnabledHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", "AnalyticsEnabledHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم Radarr. يتضمن ذلك معلومات حول متصفحك ، وصفحات Radarr WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.", "AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم {appName}. يتضمن ذلك معلومات حول متصفحك ، وصفحات {appName} WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.",
"Analytics": "تحليلات", "Analytics": "تحليلات",
"AlternateTitles": "عنوان بديل", "AlternateTitles": "عنوان بديل",
"AlreadyInYourLibrary": "بالفعل في مكتبتك", "AlreadyInYourLibrary": "بالفعل في مكتبتك",
@@ -332,7 +332,6 @@
"AddingTag": "مضيفا العلامة", "AddingTag": "مضيفا العلامة",
"AddListExclusion": "إضافة استبعاد قائمة", "AddListExclusion": "إضافة استبعاد قائمة",
"About": "نبدة عن", "About": "نبدة عن",
"APIKey": "مفتاح API",
"60MinutesSixty": "60 دقيقة: {0}", "60MinutesSixty": "60 دقيقة: {0}",
"45MinutesFourtyFive": "90 دقيقة: {0}", "45MinutesFourtyFive": "90 دقيقة: {0}",
"20MinutesTwenty": "120 دقيقة: {0}", "20MinutesTwenty": "120 دقيقة: {0}",
@@ -396,7 +395,7 @@
"LogFiles": "ملفات الدخول", "LogFiles": "ملفات الدخول",
"Local": "محلي", "Local": "محلي",
"LoadingBookFilesFailed": "فشل تحميل ملفات الفيلم", "LoadingBookFilesFailed": "فشل تحميل ملفات الفيلم",
"LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة Radarr الرئيسية عند بدء التطبيق.", "LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة {appName} الرئيسية عند بدء التطبيق.",
"Language": "لغة", "Language": "لغة",
"IsTagUsedCannotBeDeletedWhileInUse": "لا يمكن حذفه أثناء الاستخدام", "IsTagUsedCannotBeDeletedWhileInUse": "لا يمكن حذفه أثناء الاستخدام",
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "قم بالترقية حتى يتم تلبية هذه الجودة أو تجاوزها", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "قم بالترقية حتى يتم تلبية هذه الجودة أو تجاوزها",
@@ -407,7 +406,7 @@
"IndexerPriority": "أولوية المفهرس", "IndexerPriority": "أولوية المفهرس",
"Indexer": "مفهرس", "Indexer": "مفهرس",
"IncludeUnmonitored": "تضمين غير مراقب", "IncludeUnmonitored": "تضمين غير مراقب",
"IncludeUnknownAuthorItemsHelpText": "إظهار العناصر بدون فيلم في قائمة الانتظار. يمكن أن يشمل ذلك الأفلام التي تمت إزالتها أو أي شيء آخر في فئة Radarr", "IncludeUnknownAuthorItemsHelpText": "إظهار العناصر بدون فيلم في قائمة الانتظار. يمكن أن يشمل ذلك الأفلام التي تمت إزالتها أو أي شيء آخر في فئة {appName}",
"IncludeHealthWarningsHelpText": "قم بتضمين التحذيرات الصحية", "IncludeHealthWarningsHelpText": "قم بتضمين التحذيرات الصحية",
"Importing": "استيراد", "Importing": "استيراد",
"ImportedTo": "مستورد إلى", "ImportedTo": "مستورد إلى",
@@ -430,7 +429,6 @@
"HasPendingChangesNoChanges": "لا تغيرات", "HasPendingChangesNoChanges": "لا تغيرات",
"Group": "مجموعة", "Group": "مجموعة",
"GrabSelected": "انتزاع المحدد", "GrabSelected": "انتزاع المحدد",
"ApiKeyHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟", "DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
"LoadingBooksFailed": "فشل تحميل ملفات الفيلم", "LoadingBooksFailed": "فشل تحميل ملفات الفيلم",
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.", "ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
@@ -439,7 +437,7 @@
"SslPortHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول", "SslPortHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"DownloadClientCheckDownloadingToRoot": "يقوم برنامج التنزيل {0} بوضع التنزيلات في المجلد الجذر {1}. يجب ألا تقوم بالتنزيل إلى مجلد جذر.", "DownloadClientCheckDownloadingToRoot": "يقوم برنامج التنزيل {0} بوضع التنزيلات في المجلد الجذر {1}. يجب ألا تقوم بالتنزيل إلى مجلد جذر.",
"Progress": "التقدم", "Progress": "التقدم",
"ReplaceIllegalCharactersHelpText": "استبدل الأحرف غير القانونية. إذا لم يتم تحديده ، فسوف يقوم Radarr بإزالتها بدلاً من ذلك", "ReplaceIllegalCharactersHelpText": "استبدل الأحرف غير القانونية. إذا لم يتم تحديده ، فسوف يقوم {appName} بإزالتها بدلاً من ذلك",
"ReleaseTitle": "عنوان الإصدار", "ReleaseTitle": "عنوان الإصدار",
"Actions": "أجراءات", "Actions": "أجراءات",
"Tomorrow": "غدا", "Tomorrow": "غدا",
@@ -454,12 +452,12 @@
"RemoveFromBlocklist": "إزالة من القائمة السوداء", "RemoveFromBlocklist": "إزالة من القائمة السوداء",
"UnableToLoadBlocklist": "تعذر تحميل القائمة السوداء", "UnableToLoadBlocklist": "تعذر تحميل القائمة السوداء",
"Level": "مستوى", "Level": "مستوى",
"ReleaseBranchCheckOfficialBranchMessage": "الفرع {0} ليس فرع إصدار Radarr صالح ، لن تتلقى تحديثات", "ReleaseBranchCheckOfficialBranchMessage": "الفرع {0} ليس فرع إصدار {appName} صالح ، لن تتلقى تحديثات",
"Time": "زمن", "Time": "زمن",
"Component": "مكون", "Component": "مكون",
"Blocklist": "القائمة السوداء", "Blocklist": "القائمة السوداء",
"BlocklistRelease": "إصدار القائمة السوداء", "BlocklistRelease": "إصدار القائمة السوداء",
"ThisCannotBeCancelled": "لا يمكن إلغاء هذا بمجرد البدء دون إعادة تشغيل Radarr.", "ThisCannotBeCancelled": "لا يمكن إلغاء هذا بمجرد البدء دون إعادة تشغيل {appName}.",
"UnselectAll": "إلغاء تحديد الكل", "UnselectAll": "إلغاء تحديد الكل",
"UpdateSelected": "تم تحديد التحديث", "UpdateSelected": "تم تحديد التحديث",
"Wanted": "مطلوب", "Wanted": "مطلوب",
@@ -485,12 +483,12 @@
"Filters": "منقي", "Filters": "منقي",
"Connect": "الاتصال", "Connect": "الاتصال",
"HealthNoIssues": "لا مشاكل مع التكوين الخاص بك", "HealthNoIssues": "لا مشاكل مع التكوين الخاص بك",
"MissingFromDisk": "لم يتمكن Whisparr من العثور على الملف على القرص لذا تمت إزالته", "MissingFromDisk": "لم يتمكن {appName} من العثور على الملف على القرص لذا تمت إزالته",
"IndexerStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}", "IndexerStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
"Lists": "القوائم", "Lists": "القوائم",
"Metadata": "البيانات الوصفية", "Metadata": "البيانات الوصفية",
"CreateEmptyAuthorFolders": "إنشاء مجلدات أفلام فارغة", "CreateEmptyAuthorFolders": "إنشاء مجلدات أفلام فارغة",
"ReadarrSupportsAnyDownloadClient": "يدعم Whisparr أي عميل تنزيل يستخدم معيار Newznab ، بالإضافة إلى عملاء التنزيل الآخرين المدرجة أدناه.", "ReadarrSupportsAnyDownloadClient": "يدعم {appName} أي عميل تنزيل يستخدم معيار Newznab ، بالإضافة إلى عملاء التنزيل الآخرين المدرجة أدناه.",
"Queued": "في قائمة الانتظار", "Queued": "في قائمة الانتظار",
"RefreshAndScan": "التحديث والمسح الضوئي", "RefreshAndScan": "التحديث والمسح الضوئي",
"RescanAfterRefreshHelpText": "أعد فحص مجلد الفيلم بعد تحديث الفيلم", "RescanAfterRefreshHelpText": "أعد فحص مجلد الفيلم بعد تحديث الفيلم",
@@ -509,10 +507,10 @@
"IndexerLongTermStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل لأكثر من 6 ساعات", "IndexerLongTermStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل لأكثر من 6 ساعات",
"IndexerLongTermStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات لأكثر من 6 ساعات: {0}", "IndexerLongTermStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات لأكثر من 6 ساعات: {0}",
"IndexerPriorityHelpText": "أولوية المفهرس من 1 (الأعلى) إلى 50 (الأدنى). الافتراضي: 25.", "IndexerPriorityHelpText": "أولوية المفهرس من 1 (الأعلى) إلى 50 (الأدنى). الافتراضي: 25.",
"IndexerRssHealthCheckNoIndexers": "لا توجد مفهرسات متاحة مع تمكين مزامنة RSS ، ولن يحصل Radarr على الإصدارات الجديدة تلقائيًا", "IndexerRssHealthCheckNoIndexers": "لا توجد مفهرسات متاحة مع تمكين مزامنة RSS ، ولن يحصل {appName} على الإصدارات الجديدة تلقائيًا",
"IndexerSearchCheckNoAutomaticMessage": "لا تتوفر مفهرسات مع تمكين البحث التلقائي ، ولن يقدم Radarr أي نتائج بحث تلقائية", "IndexerSearchCheckNoAutomaticMessage": "لا تتوفر مفهرسات مع تمكين البحث التلقائي ، ولن يقدم {appName} أي نتائج بحث تلقائية",
"IndexerSearchCheckNoAvailableIndexersMessage": "جميع المفهرسات القادرة على البحث غير متوفرة مؤقتًا بسبب أخطاء المفهرس الأخيرة", "IndexerSearchCheckNoAvailableIndexersMessage": "جميع المفهرسات القادرة على البحث غير متوفرة مؤقتًا بسبب أخطاء المفهرس الأخيرة",
"IndexerSearchCheckNoInteractiveMessage": "لا تتوفر مفهرسات مع تمكين البحث التفاعلي ، ولن يقدم Radarr أي نتائج بحث تفاعلية", "IndexerSearchCheckNoInteractiveMessage": "لا تتوفر مفهرسات مع تمكين البحث التفاعلي ، ولن يقدم {appName} أي نتائج بحث تفاعلية",
"IndexersSettingsSummary": "المفهرسات وقيود الإصدار", "IndexersSettingsSummary": "المفهرسات وقيود الإصدار",
"MediaManagement": "إدارة وسائل الإعلام", "MediaManagement": "إدارة وسائل الإعلام",
"Monitor": "مراقب", "Monitor": "مراقب",
@@ -522,11 +520,11 @@
"ProxyCheckFailedToTestMessage": "فشل اختبار الوكيل: {0}", "ProxyCheckFailedToTestMessage": "فشل اختبار الوكيل: {0}",
"QualitySettingsSummary": "أحجام الجودة والتسمية", "QualitySettingsSummary": "أحجام الجودة والتسمية",
"QueueIsEmpty": "قائمة الانتظار فارغة", "QueueIsEmpty": "قائمة الانتظار فارغة",
"RestartReloadNote": "ملاحظة: سيتم إعادة تشغيل Radarr تلقائيًا وإعادة تحميل واجهة المستخدم أثناء عملية الاستعادة.", "RestartReloadNote": "ملاحظة: سيتم إعادة تشغيل {appName} تلقائيًا وإعادة تحميل واجهة المستخدم أثناء عملية الاستعادة.",
"RootFolderCheckSingleMessage": "مجلد الجذر مفقود: {0}", "RootFolderCheckSingleMessage": "مجلد الجذر مفقود: {0}",
"Save": "حفظ", "Save": "حفظ",
"SettingsRemotePathMappingLocalPath": "مسار محلي", "SettingsRemotePathMappingLocalPath": "مسار محلي",
"SettingsRemotePathMappingLocalPathHelpText": "المسار الذي يجب أن يستخدمه Radarr للوصول إلى المسار البعيد محليًا", "SettingsRemotePathMappingLocalPathHelpText": "المسار الذي يجب أن يستخدمه {appName} للوصول إلى المسار البعيد محليًا",
"SettingsRemotePathMappingRemotePath": "مسار بعيد", "SettingsRemotePathMappingRemotePath": "مسار بعيد",
"DownloadClientsSettingsSummary": "تنزيل العملاء وتنزيل المناولة وتعيينات المسار البعيد", "DownloadClientsSettingsSummary": "تنزيل العملاء وتنزيل المناولة وتعيينات المسار البعيد",
"DownloadClientStatusCheckAllClientMessage": "جميع عملاء التنزيل غير متاحين بسبب الفشل", "DownloadClientStatusCheckAllClientMessage": "جميع عملاء التنزيل غير متاحين بسبب الفشل",
@@ -556,7 +554,7 @@
"CustomFormatSettings": "إعدادات التنسيقات المخصصة", "CustomFormatSettings": "إعدادات التنسيقات المخصصة",
"CustomFormat": "تنسيق مخصص", "CustomFormat": "تنسيق مخصص",
"CustomFormats": "تنسيقات مخصصة", "CustomFormats": "تنسيقات مخصصة",
"CutoffFormatScoreHelpText": "بمجرد الوصول إلى درجة التنسيق المخصص هذه ، لن يقوم Radarr بتنزيل الأفلام", "CutoffFormatScoreHelpText": "بمجرد الوصول إلى درجة التنسيق المخصص هذه ، لن يقوم {appName} بتنزيل الأفلام",
"DeleteCustomFormat": "حذف التنسيق المخصص", "DeleteCustomFormat": "حذف التنسيق المخصص",
"DeleteCustomFormatMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟", "DeleteCustomFormatMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
"DeleteFormatMessageText": "هل تريد بالتأكيد حذف علامة التنسيق {0}؟", "DeleteFormatMessageText": "هل تريد بالتأكيد حذف علامة التنسيق {0}؟",
@@ -585,7 +583,7 @@
"DeleteConditionMessageText": "هل أنت متأكد أنك تريد حذف العلامة \"{0}\"؟", "DeleteConditionMessageText": "هل أنت متأكد أنك تريد حذف العلامة \"{0}\"؟",
"RemoveSelectedItemsQueueMessageText": "هل تريد بالتأكيد إزالة {0} عنصر {1} من قائمة الانتظار؟", "RemoveSelectedItemsQueueMessageText": "هل تريد بالتأكيد إزالة {0} عنصر {1} من قائمة الانتظار؟",
"NoEventsFound": "لم يتم العثور على أحداث", "NoEventsFound": "لم يتم العثور على أحداث",
"BlocklistReleaseHelpText": "يمنع Radarr من الاستيلاء على هذا الإصدار تلقائيًا مرة أخرى", "BlocklistReleaseHelpText": "يمنع {appName} من الاستيلاء على هذا الإصدار تلقائيًا مرة أخرى",
"ApplyTagsHelpTextRemove": "إزالة: قم بإزالة العلامات التي تم إدخالها", "ApplyTagsHelpTextRemove": "إزالة: قم بإزالة العلامات التي تم إدخالها",
"ApplyTagsHelpTextReplace": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)", "ApplyTagsHelpTextReplace": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)",
"DeleteSelectedDownloadClients": "حذف Download Client", "DeleteSelectedDownloadClients": "حذف Download Client",
@@ -617,7 +615,7 @@
"NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}", "NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}",
"SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق", "SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق",
"ConnectionLost": "انقطع الاتصال", "ConnectionLost": "انقطع الاتصال",
"ConnectionLostReconnect": "سيحاول Radarr الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.", "ConnectionLostReconnect": "سيحاول {appName} الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
"LastDuration": "المدة الماضية", "LastDuration": "المدة الماضية",
"Large": "كبير", "Large": "كبير",
"WhatsNew": "ما هو الجديد؟", "WhatsNew": "ما هو الجديد؟",
@@ -638,5 +636,22 @@
"SelectQuality": "حدد الجودة", "SelectQuality": "حدد الجودة",
"CustomFilter": "مرشحات مخصصة", "CustomFilter": "مرشحات مخصصة",
"IndexerFlags": "أعلام المفهرس", "IndexerFlags": "أعلام المفهرس",
"InteractiveSearchModalHeader": "بحث تفاعلي" "InteractiveSearchModalHeader": "بحث تفاعلي",
"ApiKey": "مفتاح API",
"AuthBasic": "أساسي (المتصفح المنبثق)",
"AuthForm": "النماذج (صفحة تسجيل الدخول)",
"Enabled": "ممكن",
"FailedLoadingSearchResults": "فشل تحميل نتائج البحث ، يرجى المحاولة مرة أخرى.",
"DisabledForLocalAddresses": "معطل بسبب العناوين المحلية",
"AptUpdater": "استخدم apt لتثبيت التحديث",
"CurrentlyInstalled": "مثبتة حاليا",
"DockerUpdater": "تحديث حاوية عامل الإرساء لتلقي التحديث",
"ExternalUpdater": "تم تكوين {appName} لاستخدام آلية تحديث خارجية",
"InstallLatest": "تثبيت الأحدث",
"OnLatestVersion": "تم بالفعل تثبيت أحدث إصدار من {appName}",
"Script": "النصي",
"UnmappedFiles": "المجلدات غير المعينة",
"UpdateAppDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،",
"Clone": "قريب",
"BuiltIn": "مدمج"
} }
+52 -37
View File
@@ -1,5 +1,4 @@
{ {
"ApiKeyHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"Enable": "Активиране", "Enable": "Активиране",
"MIA": "МВР", "MIA": "МВР",
"Size": " Размер", "Size": " Размер",
@@ -7,7 +6,6 @@
"20MinutesTwenty": "120 минути: {0}", "20MinutesTwenty": "120 минути: {0}",
"45MinutesFourtyFive": "90 минути: {0}", "45MinutesFourtyFive": "90 минути: {0}",
"60MinutesSixty": "60 минути: {0}", "60MinutesSixty": "60 минути: {0}",
"APIKey": "API ключ",
"About": "относно", "About": "относно",
"AddListExclusion": "Добавяне на изключване от списъка", "AddListExclusion": "Добавяне на изключване от списъка",
"AddingTag": "Добавяне на таг", "AddingTag": "Добавяне на таг",
@@ -15,17 +13,17 @@
"AlreadyInYourLibrary": "Вече във вашата библиотека", "AlreadyInYourLibrary": "Вече във вашата библиотека",
"AlternateTitles": "Алтернативно заглавие", "AlternateTitles": "Алтернативно заглавие",
"Analytics": "Анализ", "Analytics": "Анализ",
"AnalyticsEnabledHelpText": "Изпращайте анонимна информация за използването и грешките до сървърите на Radarr. Това включва информация за вашия браузър, кои страници на Radarr WebUI използвате, отчитане на грешки, както и версията на операционната система и времето за изпълнение. Ще използваме тази информация, за да дадем приоритет на функциите и корекциите на грешки.", "AnalyticsEnabledHelpText": "Изпращайте анонимна информация за използването и грешките до сървърите на {appName}. Това включва информация за вашия браузър, кои страници на {appName} WebUI използвате, отчитане на грешки, както и версията на операционната система и времето за изпълнение. Ще използваме тази информация, за да дадем приоритет на функциите и корекциите на грешки.",
"AnalyticsEnabledHelpTextWarning": "Изисква рестартиране, за да влезе в сила", "AnalyticsEnabledHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"AppDataDirectory": "Директория на AppData", "AppDataDirectory": "Директория на AppData",
"ApplyTags": "Прилагане на тагове", "ApplyTags": "Прилагане на тагове",
"Authentication": "Удостоверяване", "Authentication": "Удостоверяване",
"AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до Radarr", "AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до {appName}",
"AuthorClickToChangeBook": "Щракнете, за да промените филма", "AuthorClickToChangeBook": "Щракнете, за да промените филма",
"AutoRedownloadFailedHelpText": "Автоматично търсете и се опитвайте да изтеглите различна версия", "AutoRedownloadFailedHelpText": "Автоматично търсете и се опитвайте да изтеглите различна версия",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Изтритите от диска филми автоматично се проследяват в Radarr", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Изтритите от диска филми автоматично се проследяват в {appName}",
"Automatic": "Автоматично", "Automatic": "Автоматично",
"BackupFolderHelpText": "Относителните пътища ще бъдат в директорията AppData на Radarr", "BackupFolderHelpText": "Относителните пътища ще бъдат в директорията AppData на {appName}",
"BackupNow": "Архивиране сега", "BackupNow": "Архивиране сега",
"BackupRetentionHelpText": "Автоматичните архиви, по-стари от периода на съхранение, ще бъдат почистени автоматично", "BackupRetentionHelpText": "Автоматичните архиви, по-стари от периода на съхранение, ще бъдат почистени автоматично",
"Backups": "Архиви", "Backups": "Архиви",
@@ -38,16 +36,16 @@
"Calendar": "Календар", "Calendar": "Календар",
"CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед", "CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед",
"Cancel": "Отказ", "Cancel": "Отказ",
"CancelMessageText": "Наистина ли искате да отмените тази чакаща задача?", "CancelPendingTask": "Наистина ли искате да отмените тази чакаща задача?",
"CertificateValidation": "Валидиране на сертификат", "CertificateValidation": "Валидиране на сертификат",
"CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране", "CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране",
"ChangeFileDate": "Промяна на датата на файла", "ChangeFileDate": "Промяна на датата на файла",
"ChangeHasNotBeenSavedYet": "Промяната все още не е запазена", "ChangeHasNotBeenSavedYet": "Промяната все още не е запазена",
"ChmodFolder": "chmod папка", "ChmodFolder": "chmod папка",
"ChmodFolderHelpText": "Осмично, приложено по време на импортиране / преименуване към медийни папки и файлове (без битове за изпълнение)", "ChmodFolderHelpText": "Осмично, приложено по време на импортиране / преименуване към медийни папки и файлове (без битове за изпълнение)",
"ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с Radarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.", "ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с {appName}, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.",
"ChownGroupHelpText": "Име на група или gid. Използвайте gid за отдалечени файлови системи.", "ChownGroupHelpText": "Име на група или gid. Използвайте gid за отдалечени файлови системи.",
"ChownGroupHelpTextWarning": "Това работи само ако потребителят, работещ с Radarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне използва същата група като Radarr.", "ChownGroupHelpTextWarning": "Това работи само ако потребителят, работещ с {appName}, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне използва същата група като {appName}.",
"Clear": "Ясно", "Clear": "Ясно",
"ClickToChangeQuality": "Щракнете, за да промените качеството", "ClickToChangeQuality": "Щракнете, за да промените качеството",
"ClientPriority": "Приоритет на клиента", "ClientPriority": "Приоритет на клиента",
@@ -59,10 +57,10 @@
"ConnectSettings": "Настройки за свързване", "ConnectSettings": "Настройки за свързване",
"Connections": "Връзки", "Connections": "Връзки",
"CopyUsingHardlinksHelpText": "Използвайте твърди връзки, когато се опитвате да копирате файлове от торенти, които все още се посяват", "CopyUsingHardlinksHelpText": "Използвайте твърди връзки, когато се опитвате да копирате файлове от торенти, които все още се посяват",
"CopyUsingHardlinksHelpTextWarning": "Понякога заключванията на файлове могат да попречат на преименуване на файлове, които се засяват. Можете временно да деактивирате засяването и да използвате функцията за преименуване на Radarr като работа.", "CopyUsingHardlinksHelpTextWarning": "Понякога заключванията на файлове могат да попречат на преименуване на файлове, които се засяват. Можете временно да деактивирате засяването и да използвате функцията за преименуване на {appName} като работа.",
"CreateEmptyAuthorFoldersHelpText": "Създайте липсващи папки с филми по време на сканиране на диска", "CreateEmptyAuthorFoldersHelpText": "Създайте липсващи папки с филми по време на сканиране на диска",
"CreateGroup": "Създай група", "CreateGroup": "Създай група",
"CutoffHelpText": "След достигане на това качество Radarr вече няма да изтегля филми", "CutoffHelpText": "След достигане на това качество {appName} вече няма да изтегля филми",
"CutoffUnmet": "Прекъсване Неудовлетворено", "CutoffUnmet": "Прекъсване Неудовлетворено",
"DatabaseMigration": "DB миграция", "DatabaseMigration": "DB миграция",
"Dates": "Дати", "Dates": "Дати",
@@ -141,7 +139,7 @@
"Grab": "Грабнете", "Grab": "Грабнете",
"GrabID": "Идентификатор на грабване", "GrabID": "Идентификатор на грабване",
"GrabRelease": "Grab Release", "GrabRelease": "Grab Release",
"GrabReleaseMessageText": "Radarr не успя да определи за кой филм е предназначено това издание. Radarr може да не може автоматично да импортира тази версия. Искате ли да вземете „{0}“?", "GrabReleaseMessageText": "{appName} не успя да определи за кой филм е предназначено това издание. {appName} може да не може автоматично да импортира тази версия. Искате ли да вземете „{0}“?",
"GrabSelected": "Grab Selected", "GrabSelected": "Grab Selected",
"Group": "Група", "Group": "Група",
"HasPendingChangesNoChanges": "Без промени", "HasPendingChangesNoChanges": "Без промени",
@@ -164,7 +162,7 @@
"ImportedTo": "Внос в", "ImportedTo": "Внос в",
"Importing": "Импортиране", "Importing": "Импортиране",
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения", "IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
"IncludeUnknownAuthorItemsHelpText": "Показване на елементи без филм в опашката. Това може да включва премахнати филми или нещо друго от категорията на Radarr", "IncludeUnknownAuthorItemsHelpText": "Показване на елементи без филм в опашката. Това може да включва премахнати филми или нещо друго от категорията на {appName}",
"IncludeUnmonitored": "Включете Без наблюдение", "IncludeUnmonitored": "Включете Без наблюдение",
"Indexer": "Индексатор", "Indexer": "Индексатор",
"IndexerPriority": "Индексатор Приоритет", "IndexerPriority": "Индексатор Приоритет",
@@ -175,7 +173,7 @@
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Надстройте, докато това качество бъде постигнато или надвишено", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Надстройте, докато това качество бъде постигнато или надвишено",
"IsTagUsedCannotBeDeletedWhileInUse": "Не може да се изтрие, докато се използва", "IsTagUsedCannotBeDeletedWhileInUse": "Не може да се изтрие, докато се използва",
"Language": "Език", "Language": "Език",
"LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на Radarr при стартиране на приложението.", "LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на {appName} при стартиране на приложението.",
"LoadingBookFilesFailed": "Зареждането на филмови файлове не бе успешно", "LoadingBookFilesFailed": "Зареждането на филмови файлове не бе успешно",
"Local": "Местен", "Local": "Местен",
"LogFiles": "Лог файлове", "LogFiles": "Лог файлове",
@@ -256,7 +254,7 @@
"RSSSync": "RSS синхронизиране", "RSSSync": "RSS синхронизиране",
"RSSSyncInterval": "RSS интервал за синхронизиране", "RSSSyncInterval": "RSS интервал за синхронизиране",
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация", "ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr поддържа всеки индексатор, който използва стандарта Newznab, както и други индексатори, изброени по-долу.", "ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "{appName} поддържа всеки индексатор, който използва стандарта Newznab, както и други индексатори, изброени по-долу.",
"ReadarrTags": "Радарни маркери", "ReadarrTags": "Радарни маркери",
"Real": "Истински", "Real": "Истински",
"Reason": "Причина", "Reason": "Причина",
@@ -285,19 +283,19 @@
"RemoveTagExistingTag": "Съществуващ маркер", "RemoveTagExistingTag": "Съществуващ маркер",
"RemoveTagRemovingTag": "Премахване на етикет", "RemoveTagRemovingTag": "Премахване на етикет",
"RemovedFromTaskQueue": "Премахнато от опашката на задачите", "RemovedFromTaskQueue": "Премахнато от опашката на задачите",
"RenameBooksHelpText": "Radarr ще използва съществуващото име на файл, ако преименуването е деактивирано", "RenameBooksHelpText": "{appName} ще използва съществуващото име на файл, ако преименуването е деактивирано",
"Reorder": "Пренареждане", "Reorder": "Пренареждане",
"ReplaceIllegalCharacters": "Заменете незаконните символи", "ReplaceIllegalCharacters": "Заменете незаконните символи",
"RequiredHelpText": "Освобождаването трябва да съдържа поне един от тези термини (без регистрация)", "RequiredHelpText": "Освобождаването трябва да съдържа поне един от тези термини (без регистрация)",
"RequiredPlaceHolder": "Добавете ново ограничение", "RequiredPlaceHolder": "Добавете ново ограничение",
"RescanAfterRefreshHelpTextWarning": "Radarr няма автоматично да открива промени във файлове, когато не е зададено на „Винаги“", "RescanAfterRefreshHelpTextWarning": "{appName} няма автоматично да открива промени във файлове, когато не е зададено на „Винаги“",
"RescanAuthorFolderAfterRefresh": "Повторно сканиране на папка за филм след опресняване", "RescanAuthorFolderAfterRefresh": "Повторно сканиране на папка за филм след опресняване",
"Reset": "Нулиране", "Reset": "Нулиране",
"ResetAPIKey": "Нулиране на API ключ", "ResetAPIKey": "Нулиране на API ключ",
"ResetAPIKeyMessageText": "Наистина ли искате да нулирате своя API ключ?", "ResetAPIKeyMessageText": "Наистина ли искате да нулирате своя API ключ?",
"Restart": "Рестартирам", "Restart": "Рестартирам",
"RestartNow": "Рестартирай сега", "RestartNow": "Рестартирай сега",
"RestartReadarr": "Рестартирайте Radarr", "RestartReadarr": "Рестартирайте {appName}",
"Restore": "Възстанови", "Restore": "Възстанови",
"RestoreBackup": "Възстанови архива", "RestoreBackup": "Възстанови архива",
"Result": "Резултат", "Result": "Резултат",
@@ -337,7 +335,7 @@
"ShowSizeOnDisk": "Показване на размера на диска", "ShowSizeOnDisk": "Показване на размера на диска",
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Показва се над всяка колона, когато седмицата е активният изглед", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Показва се над всяка колона, когато седмицата е активният изглед",
"SkipFreeSpaceCheck": "Пропуснете проверката на свободното пространство", "SkipFreeSpaceCheck": "Пропуснете проверката на свободното пространство",
"SkipFreeSpaceCheckWhenImportingHelpText": "Използвайте, когато Radarr не е в състояние да открие свободно място от основната папка на вашия филм", "SkipFreeSpaceCheckWhenImportingHelpText": "Използвайте, когато {appName} не е в състояние да открие свободно място от основната папка на вашия филм",
"SorryThatAuthorCannotBeFound": "За съжаление този филм не може да бъде намерен.", "SorryThatAuthorCannotBeFound": "За съжаление този филм не може да бъде намерен.",
"SorryThatBookCannotBeFound": "За съжаление този филм не може да бъде намерен.", "SorryThatBookCannotBeFound": "За съжаление този филм не може да бъде намерен.",
"Source": "Източник", "Source": "Източник",
@@ -355,7 +353,7 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Успех! Работата ми приключи, няма файлове за преименуване.", "SuccessMyWorkIsDoneNoFilesToRetag": "Успех! Работата ми приключи, няма файлове за преименуване.",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Търсенето не се поддържа с този индексатор", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Търсенето не се поддържа с този индексатор",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от {appName}",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Ще се използва, когато се използва интерактивно търсене", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Ще се използва, когато се използва интерактивно търсене",
"TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит", "TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит",
"Tags": "Етикети", "Tags": "Етикети",
@@ -372,7 +370,7 @@
"Torrents": "Торенти", "Torrents": "Торенти",
"TotalFileSize": "Общ размер на файла", "TotalFileSize": "Общ размер на файла",
"UILanguage": "Език на потребителския интерфейс", "UILanguage": "Език на потребителския интерфейс",
"UILanguageHelpText": "Език, който Radarr ще използва за потребителски интерфейс", "UILanguageHelpText": "Език, който {appName} ще използва за потребителски интерфейс",
"UILanguageHelpTextWarning": "Необходимо е презареждане на браузъра", "UILanguageHelpTextWarning": "Необходимо е презареждане на браузъра",
"UISettings": "Настройки на потребителския интерфейс", "UISettings": "Настройки на потребителския интерфейс",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не може да се добави нов клиент за изтегляне, моля, опитайте отново.", "UnableToAddANewDownloadClientPleaseTryAgain": "Не може да се добави нов клиент за изтегляне, моля, опитайте отново.",
@@ -412,7 +410,7 @@
"UnmonitoredHelpText": "Включете непроменени филми в iCal емисията", "UnmonitoredHelpText": "Включете непроменени филми в iCal емисията",
"UpdateAll": "Актуализирай всички", "UpdateAll": "Актуализирай всички",
"UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates", "UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates",
"UpdateMechanismHelpText": "Използвайте вградения в Radarr актуализатор или скрипт", "UpdateMechanismHelpText": "Използвайте вградения в {appName} актуализатор или скрипт",
"UpdateScriptPathHelpText": "Път към персонализиран скрипт, който взема извлечен пакет за актуализация и обработва останалата част от процеса на актуализация", "UpdateScriptPathHelpText": "Път към персонализиран скрипт, който взема извлечен пакет за актуализация и обработва останалата част от процеса на актуализация",
"Updates": "Актуализации", "Updates": "Актуализации",
"UpgradeAllowedHelpText": "Ако инвалидните качества няма да бъдат надградени", "UpgradeAllowedHelpText": "Ако инвалидните качества няма да бъдат надградени",
@@ -424,8 +422,8 @@
"UsenetDelay": "Usenet Delay", "UsenetDelay": "Usenet Delay",
"UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet", "UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet",
"Username": "Потребителско име", "Username": "Потребителско име",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Клон, който да се използва за актуализиране на Radarr", "BranchUpdate": "Клон, който да се използва за актуализиране на {appName}",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Клон, използван от външен механизъм за актуализация", "BranchUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
"Version": "Версия", "Version": "Версия",
"WeekColumnHeader": "Заглавка на колоната на седмицата", "WeekColumnHeader": "Заглавка на колоната на седмицата",
"Year": "Година", "Year": "Година",
@@ -438,7 +436,7 @@
"UnableToLoadMetadataProfiles": "Профилите за забавяне не могат да се заредят", "UnableToLoadMetadataProfiles": "Профилите за забавяне не могат да се заредят",
"SslCertPathHelpTextWarning": "Изисква рестартиране, за да влезе в сила", "SslCertPathHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"DownloadClientCheckDownloadingToRoot": "Клиентът за изтегляне {0} поставя изтеглянията в основната папка {1}. Не трябва да изтегляте в основна папка.", "DownloadClientCheckDownloadingToRoot": "Клиентът за изтегляне {0} поставя изтеглянията в основната папка {1}. Не трябва да изтегляте в основна папка.",
"ReplaceIllegalCharactersHelpText": "Заменете незаконните символи. Ако не е отметнато, Radarr ще ги премахне вместо това", "ReplaceIllegalCharactersHelpText": "Заменете незаконните символи. Ако не е отметнато, {appName} ще ги премахне вместо това",
"Actions": "Действия", "Actions": "Действия",
"Tomorrow": "Утре", "Tomorrow": "Утре",
"Today": "Днес", "Today": "Днес",
@@ -455,12 +453,12 @@
"RemoveFromBlocklist": "Премахване от черния списък", "RemoveFromBlocklist": "Премахване от черния списък",
"Time": "Време", "Time": "Време",
"UnableToLoadBlocklist": "Черният списък не може да се зареди", "UnableToLoadBlocklist": "Черният списък не може да се зареди",
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Radarr, няма да получавате актуализации", "ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на {appName}, няма да получавате актуализации",
"Blocklist": "Черен списък", "Blocklist": "Черен списък",
"BlocklistRelease": "Освобождаване на черния списък", "BlocklistRelease": "Освобождаване на черния списък",
"SelectAll": "Избери всичко", "SelectAll": "Избери всичко",
"SelectedCountBooksSelectedInterp": "{0} Избран / и филм / и", "SelectedCountBooksSelectedInterp": "{0} Избран / и филм / и",
"ThisCannotBeCancelled": "Това не може да бъде отменено след стартиране без рестартиране на Radarr.", "ThisCannotBeCancelled": "Това не може да бъде отменено след стартиране без рестартиране на {appName}.",
"UnselectAll": "Деселектирайте всички", "UnselectAll": "Деселектирайте всички",
"UpdateSelected": "Избрана актуализация", "UpdateSelected": "Избрана актуализация",
"Wanted": "Издирва се", "Wanted": "Издирва се",
@@ -488,12 +486,12 @@
"ImportListStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}", "ImportListStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
"IndexerPriorityHelpText": "Приоритет на индексатора от 1 (най-висок) до 50 (най-нисък). По подразбиране: 25.", "IndexerPriorityHelpText": "Приоритет на индексатора от 1 (най-висок) до 50 (най-нисък). По подразбиране: 25.",
"IndexerRssHealthCheckNoAvailableIndexers": "Всички rss-съвместими индексатори са временно недостъпни поради скорошни грешки в индексатора", "IndexerRssHealthCheckNoAvailableIndexers": "Всички rss-съвместими индексатори са временно недостъпни поради скорошни грешки в индексатора",
"IndexerRssHealthCheckNoIndexers": "Няма налични индексатори с активирана RSS синхронизация, Radarr няма да взема автоматично нови версии", "IndexerRssHealthCheckNoIndexers": "Няма налични индексатори с активирана RSS синхронизация, {appName} няма да взема автоматично нови версии",
"Lists": "Списъци", "Lists": "Списъци",
"QueueIsEmpty": "Опашката е празна", "QueueIsEmpty": "Опашката е празна",
"RefreshAndScan": "Опресняване и сканиране", "RefreshAndScan": "Опресняване и сканиране",
"RescanAfterRefreshHelpText": "Повторно сканиране на папката с филм след освежаване на филма", "RescanAfterRefreshHelpText": "Повторно сканиране на папката с филм след освежаване на филма",
"RestartReloadNote": "Забележка: Radarr автоматично ще рестартира и презареди потребителския интерфейс по време на процеса на възстановяване.", "RestartReloadNote": "Забележка: {appName} автоматично ще рестартира и презареди потребителския интерфейс по време на процеса на възстановяване.",
"SearchFiltered": "Търсене Филтрирано", "SearchFiltered": "Търсене Филтрирано",
"DownloadClientCheckUnableToCommunicateMessage": "Не може да се комуникира с {0}.", "DownloadClientCheckUnableToCommunicateMessage": "Не може да се комуникира с {0}.",
"Connect": "Свържете", "Connect": "Свържете",
@@ -503,10 +501,10 @@
"HealthNoIssues": "Няма проблеми с вашата конфигурация", "HealthNoIssues": "Няма проблеми с вашата конфигурация",
"DownloadClientCheckNoneAvailableMessage": "Няма наличен клиент за изтегляне", "DownloadClientCheckNoneAvailableMessage": "Няма наличен клиент за изтегляне",
"IndexersSettingsSummary": "Индексатори и ограничения за освобождаване", "IndexersSettingsSummary": "Индексатори и ограничения за освобождаване",
"MissingFromDisk": "Whisparr не можа да намери файла на диска, така че той беше премахнат", "MissingFromDisk": "{appName} не можа да намери файла на диска, така че той беше премахнат",
"MediaManagement": "Управление на медиите", "MediaManagement": "Управление на медиите",
"Metadata": "Метаданни", "Metadata": "Метаданни",
"ReadarrSupportsAnyDownloadClient": "Whisparr поддържа всеки клиент за изтегляне, който използва стандарта Newznab, както и други клиенти за изтегляне, изброени по-долу.", "ReadarrSupportsAnyDownloadClient": "{appName} поддържа всеки клиент за изтегляне, който използва стандарта Newznab, както и други клиенти за изтегляне, изброени по-долу.",
"RootFolderCheckSingleMessage": "Липсваща основна папка: {0}", "RootFolderCheckSingleMessage": "Липсваща основна папка: {0}",
"SettingsRemotePathMappingLocalPath": "Местен път", "SettingsRemotePathMappingLocalPath": "Местен път",
"SettingsRemotePathMappingRemotePath": "Отдалечен път", "SettingsRemotePathMappingRemotePath": "Отдалечен път",
@@ -514,9 +512,9 @@
"Yesterday": "Вчера", "Yesterday": "Вчера",
"Disabled": "хора с увреждания", "Disabled": "хора с увреждания",
"IndexerLongTermStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки за повече от 6 часа", "IndexerLongTermStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки за повече от 6 часа",
"IndexerSearchCheckNoAutomaticMessage": "Няма налични индексатори с активирано автоматично търсене, Radarr няма да предоставя резултати от автоматично търсене", "IndexerSearchCheckNoAutomaticMessage": "Няма налични индексатори с активирано автоматично търсене, {appName} няма да предоставя резултати от автоматично търсене",
"IndexerSearchCheckNoAvailableIndexersMessage": "Всички индексатори с възможност за търсене са временно недостъпни поради скорошни грешки в индексатора", "IndexerSearchCheckNoAvailableIndexersMessage": "Всички индексатори с възможност за търсене са временно недостъпни поради скорошни грешки в индексатора",
"IndexerSearchCheckNoInteractiveMessage": "Няма налични индексатори с активирано интерактивно търсене, Radarr няма да предоставя никакви интерактивни резултати от търсенето", "IndexerSearchCheckNoInteractiveMessage": "Няма налични индексатори с активирано интерактивно търсене, {appName} няма да предоставя никакви интерактивни резултати от търсенето",
"IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки", "IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки",
"IndexerStatusCheckSingleClientMessage": "Индексатори не са налични поради грешки: {0}", "IndexerStatusCheckSingleClientMessage": "Индексатори не са налични поради грешки: {0}",
"MaintenanceRelease": "Издание за поддръжка: поправки на грешки и други подобрения. Вижте История на комисиите на Github за повече подробности", "MaintenanceRelease": "Издание за поддръжка: поправки на грешки и други подобрения. Вижте История на комисиите на Github за повече подробности",
@@ -531,7 +529,7 @@
"ProxyCheckFailedToTestMessage": "Неуспешно тестване на прокси: {0}", "ProxyCheckFailedToTestMessage": "Неуспешно тестване на прокси: {0}",
"ProxyCheckResolveIpMessage": "Неуспешно разрешаване на IP адреса за конфигурирания прокси хост {0}", "ProxyCheckResolveIpMessage": "Неуспешно разрешаване на IP адреса за конфигурирания прокси хост {0}",
"Queued": "На опашка", "Queued": "На опашка",
"SettingsRemotePathMappingLocalPathHelpText": "Път, който Radarr трябва да използва за локален достъп до отдалечения път", "SettingsRemotePathMappingLocalPathHelpText": "Път, който {appName} трябва да използва за локален достъп до отдалечения път",
"SettingsRemotePathMappingRemotePathHelpText": "Основен път към директорията, до която клиентът за изтегляне има достъп", "SettingsRemotePathMappingRemotePathHelpText": "Основен път към директорията, до която клиентът за изтегляне има достъп",
"ShowUnknownAuthorItems": "Показване на непознати филмови елементи", "ShowUnknownAuthorItems": "Показване на непознати филмови елементи",
"SystemTimeCheckMessage": "Системното време е изключено с повече от 1 ден. Планираните задачи може да не се изпълняват правилно, докато времето не бъде коригирано", "SystemTimeCheckMessage": "Системното време е изключено с повече от 1 ден. Планираните задачи може да не се изпълняват правилно, докато времето не бъде коригирано",
@@ -556,7 +554,7 @@
"CustomFormat": "Персонализиран формат", "CustomFormat": "Персонализиран формат",
"CustomFormatSettings": "Настройки на персонализирани формати", "CustomFormatSettings": "Настройки на персонализирани формати",
"CustomFormats": "Персонализирани формати", "CustomFormats": "Персонализирани формати",
"CutoffFormatScoreHelpText": "След като бъде постигнат резултатът от този персонализиран формат, Radarr вече няма да изтегля филми", "CutoffFormatScoreHelpText": "След като бъде постигнат резултатът от този персонализиран формат, {appName} вече няма да изтегля филми",
"DeleteCustomFormat": "Изтриване на потребителски формат", "DeleteCustomFormat": "Изтриване на потребителски формат",
"DeleteFormatMessageText": "Наистина ли искате да изтриете маркер за формат {0}?", "DeleteFormatMessageText": "Наистина ли искате да изтриете маркер за формат {0}?",
"ExportCustomFormat": "Експортиране на потребителски формат", "ExportCustomFormat": "Експортиране на потребителски формат",
@@ -606,7 +604,7 @@
"Small": "Малък", "Small": "Малък",
"System": "Система", "System": "Система",
"Ui": "Потребителски интерфейс", "Ui": "Потребителски интерфейс",
"ConnectionLostReconnect": "Radarr ще се опита да се свърже автоматично или можете да щракнете върху презареждане по-долу.", "ConnectionLostReconnect": "{appName} ще се опита да се свърже автоматично или можете да щракнете върху презареждане по-долу.",
"TotalSpace": "Общо пространство", "TotalSpace": "Общо пространство",
"Events": "Събития", "Events": "Събития",
"Large": "Голям", "Large": "Голям",
@@ -638,5 +636,22 @@
"CustomFilter": "Персонализирани филтри", "CustomFilter": "Персонализирани филтри",
"RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?", "RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?",
"IndexerFlags": "Индексиращи знамена", "IndexerFlags": "Индексиращи знамена",
"InteractiveSearchModalHeader": "Интерактивно търсене" "InteractiveSearchModalHeader": "Интерактивно търсене",
"FailedLoadingSearchResults": "Неуспешно зареждане на резултатите от търсенето, моля, опитайте отново.",
"ApiKey": "API ключ",
"AuthBasic": "Основно (изскачащ прозорец на браузъра)",
"AuthForm": "Формуляри (Страница за вход)",
"DisabledForLocalAddresses": "Забранено за местни адреси",
"Enabled": "Активирано",
"AptUpdater": "Използвайте apt, за да инсталирате актуализацията",
"BuiltIn": "Вграден",
"CurrentlyInstalled": "Понастоящем инсталиран",
"ExternalUpdater": "{appName} е конфигуриран да използва външен механизъм за актуализация",
"Script": "Сценарий",
"UnmappedFiles": "Немапирани папки",
"UpdateAppDirectlyLoadError": "Не може да се актуализира {appName} директно,",
"Clone": "Близо",
"DockerUpdater": "актуализирайте контейнера на докера, за да получите актуализацията",
"InstallLatest": "Инсталирайте най-новите",
"OnLatestVersion": "Вече е инсталирана най-новата версия на {appName}"
} }
+132 -48
View File
@@ -428,25 +428,25 @@
"BlocklistRelease": "Publicació de la llista de bloqueig", "BlocklistRelease": "Publicació de la llista de bloqueig",
"HasPendingChangesNoChanges": "Sense Canvis", "HasPendingChangesNoChanges": "Sense Canvis",
"ManualImportSelectEdition": "Importació manual - Seleccioneu la pel·lícula", "ManualImportSelectEdition": "Importació manual - Seleccioneu la pel·lícula",
"MissingFromDisk": "Radarr no ha pogut trobar el fitxer al disc, de manera que el fitxer es desenllaçarà de la pel·lícula a la base de dades", "MissingFromDisk": "{appName} no ha pogut trobar el fitxer al disc, de manera que el fitxer es desenllaçarà de la pel·lícula a la base de dades",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per {appName}",
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Radarr ja no baixarà pel·lícules", "CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, {appName} ja no baixarà pel·lícules",
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau de l'API?", "ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau API?",
"PropersAndRepacks": "Propietats i Repacks", "PropersAndRepacks": "Propietats i Repacks",
"RemotePathMappingCheckFolderPermissions": "Radarr pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.", "RemotePathMappingCheckFolderPermissions": "{appName} pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.",
"RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització", "RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització",
"RescanAfterRefreshHelpText": "Torneu a escanejar la carpeta de la pel·lícula després d'actualitzar la pel·lícula", "RescanAfterRefreshHelpText": "Torneu a escanejar la carpeta de la pel·lícula després d'actualitzar la pel·lícula",
"RestartReadarr": "Reinicia Radarr", "RestartReadarr": "Reinicia {appName}",
"ShowRelativeDatesHelpText": "Mostra dates relatives (avui/ahir/etc) o absolutes", "ShowRelativeDatesHelpText": "Mostra dates relatives (avui/ahir/etc) o absolutes",
"ShowSearchActionHelpText": "Mostra el botó de cerca al passar el cursor", "ShowSearchActionHelpText": "Mostra el botó de cerca al passar el cursor",
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.", "TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.",
"UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte", "UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"ApplicationURL": "URL de l'aplicació", "ApplicationURL": "URL de l'aplicació",
"ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base", "ApplicationUrlHelpText": "URL extern de l'aplicació, inclòs http(s)://, port i URL base",
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Radarr", "BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del {appName}",
"CancelMessageText": "Esteu segur que voleu cancel·lar aquesta tasca pendent?", "CancelPendingTask": "Esteu segur que voleu cancel·lar aquesta tasca pendent?",
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Radarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Radarr.", "ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa {appName} és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que {appName}.",
"ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats", "ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats",
"DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules", "DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules",
"DeleteImportListMessageText": "Esteu segur que voleu suprimir la llista '{name}'?", "DeleteImportListMessageText": "Esteu segur que voleu suprimir la llista '{name}'?",
@@ -454,26 +454,26 @@
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Per obtenir més informació sobre els clients de baixada individuals, feu clic als botons de més informació.", "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Per obtenir més informació sobre els clients de baixada individuals, feu clic als botons de més informació.",
"ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Per obtenir més informació sobre els indexadors individuals, feu clic als botons d'informació.", "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Per obtenir més informació sobre els indexadors individuals, feu clic als botons d'informació.",
"ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Per obtenir més informació sobre les llistes d'importació individuals, feu clic als botons d'informació.", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Per obtenir més informació sobre les llistes d'importació individuals, feu clic als botons d'informació.",
"IndexerPriorityHelpText": "Prioritat de l'indexador d'1 (la més alta) a 50 (la més baixa). Per defecte: 25. S'utilitza quan s'agafa llançaments com a desempat per a versions iguals, Radarr encara utilitzarà tots els indexadors habilitats per a la sincronització i la cerca RSS", "IndexerPriorityHelpText": "Prioritat de l'indexador d'1 (la més alta) a 50 (la més baixa). Per defecte: 25. S'utilitza quan s'agafa llançaments com a desempat per a versions iguals, {appName} encara utilitzarà tots els indexadors habilitats per a la sincronització i la cerca RSS",
"IndexerRssHealthCheckNoIndexers": "No hi ha indexadors disponibles amb la sincronització RSS activada, Radarr no capturarà les noves versions automàticament", "IndexerRssHealthCheckNoIndexers": "No hi ha indexadors disponibles amb la sincronització RSS activada, {appName} no capturarà les noves versions automàticament",
"IndexerSearchCheckNoAutomaticMessage": "No hi ha indexadors disponibles amb la cerca automàtica activada, Radarr no proporcionarà cap resultat de cerca automàtica", "IndexerSearchCheckNoAutomaticMessage": "No hi ha indexadors disponibles amb la cerca automàtica activada, {appName} no proporcionarà cap resultat de cerca automàtica",
"IndexerSearchCheckNoInteractiveMessage": "No hi ha indexadors amb la cerca interactiva activada, Radarr no obtindrà cap resultat de cerca", "IndexerSearchCheckNoInteractiveMessage": "No hi ha indexadors amb la cerca interactiva activada, {appName} no obtindrà cap resultat de cerca",
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Actualitzeu fins que s'assoleixi o superi aquesta qualitat", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Actualitzeu fins que s'assoleixi o superi aquesta qualitat",
"IsTagUsedCannotBeDeletedWhileInUse": "No es pot suprimir mentre està en ús", "IsTagUsedCannotBeDeletedWhileInUse": "No es pot suprimir mentre està en ús",
"LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de Lidarr a l'inici de l'aplicació.", "LaunchBrowserHelpText": " Obriu un navegador web i navegueu a la pàgina d'inici de {appName} a l'inici de l'aplicació.",
"LoadingBookFilesFailed": "No s'han pogut carregar els fitxers de pel·lícules", "LoadingBookFilesFailed": "No s'han pogut carregar els fitxers de pel·lícules",
"NoHistory": "Sense història", "NoHistory": "Sense història",
"OnBookFileDeleteForUpgradeHelpText": "Al suprimir el fitxer de pel·lícula per a l'actualització", "OnBookFileDeleteForUpgradeHelpText": "Al suprimir el fitxer de pel·lícula per a l'actualització",
"OnBookFileDeleteHelpText": "Al suprimir fitxer de pel·lícula", "OnBookFileDeleteHelpText": "Al suprimir fitxer de pel·lícula",
"ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de Radarr vàlida, no rebreu actualitzacions", "ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de {appName} vàlida, no rebreu actualitzacions",
"ReleaseDate": "Dates de llançament", "ReleaseDate": "Dates de llançament",
"RemotePathMappingCheckDownloadPermissions": "Radarr pot veure però no accedir a la pel·lícula baixada {0}. Error de permisos probable.", "RemotePathMappingCheckDownloadPermissions": "{appName} pot veure però no accedir a la pel·lícula baixada {0}. Error de permisos probable.",
"RemotePathMappingCheckFilesGenericPermissions": "El client de baixada {0} ha informat de fitxers a {1} però Radarr no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.", "RemotePathMappingCheckFilesGenericPermissions": "El client de baixada {0} ha informat de fitxers a {1} però {appName} no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.",
"RemotePathMappingCheckGenericPermissions": "El client de baixada {0} col·loca les baixades a {1} però Radarr no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.", "RemotePathMappingCheckGenericPermissions": "El client de baixada {0} col·loca les baixades a {1} però {appName} no pot veure aquest directori. És possible que hàgiu d'ajustar els permisos de la carpeta.",
"ReplaceIllegalCharactersHelpText": "Substitueix caràcters il·legals. Si no es marca, Radarr els eliminarà", "ReplaceIllegalCharactersHelpText": "Substitueix caràcters il·legals. Si no es marca, {appName} els eliminarà",
"RssSyncIntervalHelpText": "Interval en minuts. Establiu a zero per desactivar (això aturarà tota captura automàtica de llançaments)", "RssSyncIntervalHelpText": "Interval en minuts. Establiu a zero per desactivar (això aturarà tota captura automàtica de llançaments)",
"SelectedCountBooksSelectedInterp": "S'han seleccionat {0} pel·lícules", "SelectedCountBooksSelectedInterp": "S'han seleccionat {0} pel·lícules",
"SettingsRemotePathMappingLocalPathHelpText": "Camí que Radarr hauria d'utilitzar per accedir al camí remot localment", "SettingsRemotePathMappingLocalPathHelpText": "Camí que {appName} hauria d'utilitzar per accedir al camí remot localment",
"ShortDateFormat": "Format de data curta", "ShortDateFormat": "Format de data curta",
"ShowBookTitleHelpText": "Mostra el títol de la pel·lícula sota el cartell", "ShowBookTitleHelpText": "Mostra el títol de la pel·lícula sota el cartell",
"ShowRelativeDates": "Mostra les dates relatives", "ShowRelativeDates": "Mostra les dates relatives",
@@ -487,11 +487,11 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.", "SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.",
"TimeLeft": "Temps restant", "TimeLeft": "Temps restant",
"UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista", "UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branca que s'utilitza per actualitzar Radarr", "BranchUpdate": "Branca que s'utilitza per actualitzar {appName}",
"UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API", "UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern", "BranchUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
"WriteTagsNo": "Mai", "WriteTagsNo": "Mai",
"RestartReloadNote": "Nota: Radarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.", "RestartReloadNote": "Nota: {appName} es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.",
"Series": "Sèries", "Series": "Sèries",
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Es mostra a sobre de cada columna quan la setmana és la visualització activa", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
"SorryThatAuthorCannotBeFound": "Ho sentim, aquesta pel·lícula no s'ha trobat.", "SorryThatAuthorCannotBeFound": "Ho sentim, aquesta pel·lícula no s'ha trobat.",
@@ -500,27 +500,25 @@
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Això s'aplicarà a tots els indexadors, si us plau, seguiu les regles establertes per ells", "ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Això s'aplicarà a tots els indexadors, si us plau, seguiu les regles establertes per ells",
"UnableToLoadHistory": "No es pot carregar l'historial", "UnableToLoadHistory": "No es pot carregar l'historial",
"IconTooltip": "Programat", "IconTooltip": "Programat",
"ReadarrTags": "Etiquetes de Radarr", "ReadarrTags": "Etiquetes de {appName}",
"DownloadPropersAndRepacksHelpTexts1": "Si s'ha d'actualitzar automàticament o no a Propers/Repacks", "DownloadPropersAndRepacksHelpTexts1": "Si s'ha d'actualitzar automàticament o no a Propers/Repacks",
"GrabReleaseMessageText": "Lidarr no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que Lidarr no pugui importar automàticament aquesta versió. Voleu capturar \"{0}\"?", "GrabReleaseMessageText": "{appName} no ha pogut determinar per a quina pel·lícula era aquest llançament. És possible que {appName} no pugui importar automàticament aquesta versió. Voleu capturar \"{0}\"?",
"IsCutoffCutoff": "Requisit", "IsCutoffCutoff": "Requisit",
"MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ", "MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ",
"APIKey": "Clau API", "RescanAfterRefreshHelpTextWarning": "{appName} no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
"ApiKeyHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"RescanAfterRefreshHelpTextWarning": "Radarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
"ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda", "ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda",
"Size": " Mida", "Size": " Mida",
"SkipFreeSpaceCheckWhenImportingHelpText": "Utilitzeu-lo quan Lidarr no pugui detectar espai lliure a la carpeta arrel de la pel·lícula", "SkipFreeSpaceCheckWhenImportingHelpText": "Utilitzeu-lo quan {appName} no pugui detectar espai lliure a la carpeta arrel de la pel·lícula",
"StandardBookFormat": "Format de pel·lícula estàndard", "StandardBookFormat": "Format de pel·lícula estàndard",
"UnableToAddANewImportListExclusionPleaseTryAgain": "No es pot afegir una nova llista d'exclusió, torneu-ho a provar.", "UnableToAddANewImportListExclusionPleaseTryAgain": "No es pot afegir una nova llista d'exclusió, torneu-ho a provar.",
"UnableToLoadReleaseProfiles": "No es poden carregar els perfils de retard", "UnableToLoadReleaseProfiles": "No es poden carregar els perfils de retard",
"UnmonitoredHelpText": "Inclou pel·lícules no monitorades al canal iCal", "UnmonitoredHelpText": "Inclou pel·lícules no monitorades al canal iCal",
"UpdateAll": "Actualitzar-ho tot", "UpdateAll": "Actualitzar-ho tot",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Les pel·lícules suprimides del disc no es descarten automàticament al Radarr", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Les pel·lícules suprimides del disc no es descarten automàticament al {appName}",
"ChownGroupHelpText": "Nom del grup o gid. Utilitzeu gid per a sistemes de fitxers remots.", "ChownGroupHelpText": "Nom del grup o gid. Utilitzeu gid per a sistemes de fitxers remots.",
"AuthorClickToChangeBook": "Feu clic per canviar la pel·lícula", "AuthorClickToChangeBook": "Feu clic per canviar la pel·lícula",
"ChmodFolderHelpTextWarning": "Això només funciona si l'usuari que executa Radarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega estableixi correctament els permisos.", "ChmodFolderHelpTextWarning": "Això només funciona si l'usuari que executa {appName} és el propietari del fitxer. És millor assegurar-se que el client de descàrrega estableixi correctament els permisos.",
"CopyUsingHardlinksHelpTextWarning": "De tant en tant, els bloquejos de fitxers poden impedir reanomenar els fitxers que s'estan sembrant. Podeu desactivar temporalment la compartició i utilitzar la funció de reanomenar de Radarr com a solució.", "CopyUsingHardlinksHelpTextWarning": "De tant en tant, els bloquejos de fitxers poden impedir reanomenar els fitxers que s'estan sembrant. Podeu desactivar temporalment la compartició i utilitzar la funció de reanomenar de {appName} com a solució.",
"CouldntFindAnyResultsForTerm": "No s'ha pogut trobar cap resultat per a '{0}'", "CouldntFindAnyResultsForTerm": "No s'ha pogut trobar cap resultat per a '{0}'",
"CreateEmptyAuthorFolders": "Creeu carpetes buides per a les pel·lícules", "CreateEmptyAuthorFolders": "Creeu carpetes buides per a les pel·lícules",
"CreateEmptyAuthorFoldersHelpText": "Creeu carpetes de pel·lícules que falten durant l'exploració del disc", "CreateEmptyAuthorFoldersHelpText": "Creeu carpetes de pel·lícules que falten durant l'exploració del disc",
@@ -538,15 +536,15 @@
"MetadataProfiles": "perfil de metadades", "MetadataProfiles": "perfil de metadades",
"OnBookFileDelete": "Al suprimir fitxer de pel·lícula", "OnBookFileDelete": "Al suprimir fitxer de pel·lícula",
"OnBookFileDeleteForUpgrade": "Al suprimir el fitxer de pel·lícula per a l'actualització", "OnBookFileDeleteForUpgrade": "Al suprimir el fitxer de pel·lícula per a l'actualització",
"ReadarrSupportsAnyDownloadClient": "Radarr admet molts clients de baixada de torrent i usenet populars.", "ReadarrSupportsAnyDownloadClient": "{appName} admet molts clients de baixada de torrent i usenet populars.",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr admet qualsevol indexador que utilitzi l'estàndard Newznab, així com altres indexadors que s'enumeren a continuació.", "ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "{appName} admet qualsevol indexador que utilitzi l'estàndard Newznab, així com altres indexadors que s'enumeren a continuació.",
"RecycleBinHelpText": "Els fitxers de pel·lícula aniran aquí quan se suprimeixin en lloc de suprimir-se permanentment", "RecycleBinHelpText": "Els fitxers de pel·lícula aniran aquí quan se suprimeixin en lloc de suprimir-se permanentment",
"RenameBooksHelpText": "Radarr utilitzarà el nom del fitxer existent si el reanomenat està desactivat", "RenameBooksHelpText": "{appName} utilitzarà el nom del fitxer existent si el reanomenat està desactivat",
"RequiredHelpText": "El llançament ha de contenir almenys un d'aquests termes (no distingeix entre majúscules i minúscules)", "RequiredHelpText": "El llançament ha de contenir almenys un d'aquests termes (no distingeix entre majúscules i minúscules)",
"UILanguageHelpText": "Idioma que utilitzarà Radarr per a la interfície d'usuari", "UILanguageHelpText": "Idioma que utilitzarà {appName} per a la interfície d'usuari",
"UnableToAddANewRootFolderPleaseTryAgain": "No es pot afegir un indexador nou, torneu-ho a provar.", "UnableToAddANewRootFolderPleaseTryAgain": "No es pot afegir un indexador nou, torneu-ho a provar.",
"UnableToLoadMetadataProfiles": "No es poden carregar els perfils de qualitat", "UnableToLoadMetadataProfiles": "No es poden carregar els perfils de qualitat",
"UpdateMechanismHelpText": "Utilitzeu l'actualitzador integrat de Radarr o un script", "UpdateMechanismHelpText": "Utilitzeu l'actualitzador integrat de {appName} o un script",
"UpdateSelected": "Actualització seleccionada", "UpdateSelected": "Actualització seleccionada",
"Database": "Base de dades", "Database": "Base de dades",
"DeleteQualityProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat '{name}'?", "DeleteQualityProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat '{name}'?",
@@ -555,22 +553,22 @@
"DeleteRootFolderMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?", "DeleteRootFolderMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
"DeleteSelectedBookFiles": "Suprimeix els fitxers de pel·lícules seleccionats", "DeleteSelectedBookFiles": "Suprimeix els fitxers de pel·lícules seleccionats",
"DeleteSelectedBookFilesMessageText": "Esteu segur que voleu suprimir els fitxers de pel·lícules seleccionats?", "DeleteSelectedBookFilesMessageText": "Esteu segur que voleu suprimir els fitxers de pel·lícules seleccionats?",
"IncludeUnknownAuthorItemsHelpText": "Mostra elements sense pel·lícula a la cua. Això podria incloure pel·lícules eliminades o qualsevol altra cosa de la categoria de Lidarr", "IncludeUnknownAuthorItemsHelpText": "Mostra elements sense pel·lícula a la cua. Això podria incloure pel·lícules eliminades o qualsevol altra cosa de la categoria de {appName}",
"LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "El registre de traça només s'hauria d'habilitar temporalment", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "El registre de traça només s'hauria d'habilitar temporalment",
"PortHelpTextWarning": "Cal reiniciar perquè tingui efecte", "PortHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"RemotePathMappingCheckImportFailed": "Radarr no ha pogut importar una pel·lícula. Comproveu els vostres registres per obtenir més informació.", "RemotePathMappingCheckImportFailed": "{appName} no ha pogut importar una pel·lícula. Comproveu els vostres registres per obtenir més informació.",
"RemoveTagExistingTag": "Etiqueta existent", "RemoveTagExistingTag": "Etiqueta existent",
"RemoveTagRemovingTag": "S'està eliminant l'etiqueta", "RemoveTagRemovingTag": "S'està eliminant l'etiqueta",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador",
"UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.", "UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
"RequiredPlaceHolder": "Afegeix una nova restricció", "RequiredPlaceHolder": "Afegeix una nova restricció",
"20MinutesTwenty": "60 minuts: {0}", "20MinutesTwenty": "20 minuts: {0}",
"AlternateTitles": "Títols alternatius", "AlternateTitles": "Títols alternatius",
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Radarr. Això inclou informació sobre el vostre navegador, quines pàgines Radarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.", "AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de {appName}. Això inclou informació sobre el vostre navegador, quines pàgines {appName} WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.",
"AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte", "AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar", "AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
"CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa", "CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
"45MinutesFourtyFive": "60 minuts: {0}", "45MinutesFourtyFive": "45 minuts: {0}",
"60MinutesSixty": "60 minuts: {0}", "60MinutesSixty": "60 minuts: {0}",
"BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte", "BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"BookIsDownloading": "La pel·lícula s'està baixant", "BookIsDownloading": "La pel·lícula s'està baixant",
@@ -586,7 +584,7 @@
"BypassIfHighestQuality": "Bypass si és de màxima qualitat", "BypassIfHighestQuality": "Bypass si és de màxima qualitat",
"MinimumCustomFormatScore": "Puntuació mínima de format personalitzat", "MinimumCustomFormatScore": "Puntuació mínima de format personalitzat",
"CustomFormatScore": "Puntuació de format personalitzat", "CustomFormatScore": "Puntuació de format personalitzat",
"EnableRssHelpText": "S'utilitzarà quan Radarr cerqui publicacions periòdicament mitjançant RSS Sync", "EnableRssHelpText": "S'utilitzarà quan {appName} cerqui publicacions periòdicament mitjançant RSS Sync",
"ImportListMultipleMissingRoots": "Falten diverses carpetes arrel per a les llistes d'importació: {0}", "ImportListMultipleMissingRoots": "Falten diverses carpetes arrel per a les llistes d'importació: {0}",
"IndexerDownloadClientHelpText": "Especifiqueu quin client de baixada s'utilitza per a capturar des d'aquest indexador", "IndexerDownloadClientHelpText": "Especifiqueu quin client de baixada s'utilitza per a capturar des d'aquest indexador",
"ThemeHelpText": "Canvieu el tema de la interfície d'usuari de l'aplicació, el tema \"Automàtic\" utilitzarà el tema del vostre sistema operatiu per configurar el mode clar o fosc. Inspirat en Theme.Park", "ThemeHelpText": "Canvieu el tema de la interfície d'usuari de l'aplicació, el tema \"Automàtic\" utilitzarà el tema del vostre sistema operatiu per configurar el mode clar o fosc. Inspirat en Theme.Park",
@@ -609,7 +607,7 @@
"CustomFormat": "Format personalitzat", "CustomFormat": "Format personalitzat",
"CustomFormatSettings": "Configuració de formats personalitzats", "CustomFormatSettings": "Configuració de formats personalitzats",
"CustomFormats": "Formats personalitzats", "CustomFormats": "Formats personalitzats",
"CutoffFormatScoreHelpText": "Un cop s'arribi a aquesta puntuació de format personalitzat, Radarr ja no baixarà pel·lícules", "CutoffFormatScoreHelpText": "Un cop s'arribi a aquesta puntuació de format personalitzat, {appName} ja no baixarà pel·lícules",
"DeleteCustomFormatMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?", "DeleteCustomFormatMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
"ImportListMissingRoot": "Falta la carpeta arrel per a les llistes d'importació: {0}", "ImportListMissingRoot": "Falta la carpeta arrel per a les llistes d'importació: {0}",
"IndexerTagsHelpText": "Utilitzeu aquest indexador només per a pel·lícules amb almenys una etiqueta coincident. Deixeu-ho en blanc per utilitzar-ho amb totes les pel·lícules.", "IndexerTagsHelpText": "Utilitzeu aquest indexador només per a pel·lícules amb almenys una etiqueta coincident. Deixeu-ho en blanc per utilitzar-ho amb totes les pel·lícules.",
@@ -658,11 +656,11 @@
"Activity": "Activitat", "Activity": "Activitat",
"AddNew": "Afegeix nou", "AddNew": "Afegeix nou",
"ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)", "ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)",
"ApplyTagsHelpTextRemove": "Eliminar: elimina les etiquetes introduïdes", "ApplyTagsHelpTextRemove": "Eliminació: elimina les etiquetes introduïdes",
"BlocklistReleases": "Llista de llançaments bloquejats", "BlocklistReleases": "Llista de llançaments bloquejats",
"AutoAdd": "Afegeix automàticament", "AutoAdd": "Afegeix automàticament",
"Backup": "Còpia de seguretat", "Backup": "Còpia de seguretat",
"ApplyTagsHelpTextAdd": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent", "ApplyTagsHelpTextAdd": "Afegiment: afegeix les etiquetes a la llista d'etiquetes existent",
"DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?", "DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?",
"DeleteSelectedIndexers": "Suprimeix l'indexador(s)", "DeleteSelectedIndexers": "Suprimeix l'indexador(s)",
"DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?", "DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?",
@@ -712,5 +710,91 @@
"DownloadClientDelugeSettingsDirectory": "Directori de baixada", "DownloadClientDelugeSettingsDirectory": "Directori de baixada",
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat", "DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge", "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge" "DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"WhatsNew": "Novetats",
"SelectDropdown": "Seleccioneu...",
"NoCutoffUnmetItems": "No hi ha elements de tall no assolits",
"ApplyTagsHelpTextHowToApplyAuthors": "Com aplicar etiquetes a les pel·lícules seleccionades",
"DeleteConditionMessageText": "Esteu segur que voleu suprimir la condició '{name}'?",
"NoChange": "Cap canvi",
"SetTags": "Estableix etiquetes",
"NoResultsFound": "Sense resultats",
"Author": "Autor",
"ResetQualityDefinitions": "Restableix les definicions de qualitat",
"Small": "Petita",
"TotalSpace": "Espai total",
"BlocklistReleaseHelpText": "Impedeix que {appName} torni a capturar aquesta versió automàticament",
"CatalogNumber": "número de catàleg",
"LastWriteTime": "La darrera hora d'escriptura",
"NextExecution": "Propera execució",
"RemoveCompleted": "S'ha eliminat",
"SelectReleaseGroup": "Seleccioneu grup de llançament",
"CountDownloadClientsSelected": "{count} client(s) de baixada seleccionat(s)",
"Authors": "Autor",
"FreeSpace": "Espai lliure",
"ExtraFileExtensionsHelpText": "Llista separada per comes de fitxers addicionals per importar (.nfo s'importarà com a .nfo-orig)",
"BypassIfAboveCustomFormatScore": "Ometre si està per sobre de la puntuació de format personalitzada",
"BypassIfAboveCustomFormatScoreHelpText": "Habiliteu l'omissió quan la versió tingui una puntuació superior a la puntuació mínima per al format personalitzat",
"RedownloadFailed": "Tornar a baixar les baixades fallades",
"ExistingTag": "Etiqueta existent",
"RemoveFailed": "Ha fallat l'eliminació",
"ImportLists": "llista d'importació",
"RemovingTag": "S'està eliminant l'etiqueta",
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
"ExtraFileExtensionsHelpTextsExamples": "Exemples: '.sub, .nfo' o 'sub,nfo'",
"SourceTitle": "Títol de la font",
"NoEventsFound": "No s'han trobat esdeveniments",
"InteractiveSearchModalHeader": "Cerca interactiva",
"NotificationStatusSingleClientHealthCheckMessage": "Llistes no disponibles a causa d'errors: {0}",
"Medium": "Suport",
"RecentChanges": "Canvis recents",
"Rejections": "Rebutjats",
"StatusEndedContinuing": "Continua",
"DeleteBookFileMessageText": "Esteu segur que voleu suprimir '{path}'?",
"DownloadClientTagHelpText": "Utilitzeu aquest indexador només per a pel·lícules amb almenys una etiqueta coincident. Deixeu-ho en blanc per utilitzar-ho amb totes les pel·lícules.",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "El client de baixada {downloadClientName} està configurat per eliminar les baixades completades. Això pot provocar que les baixades s'eliminin del vostre client abans que {1} pugui importar-les.",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Cerqueu i intenteu baixar automàticament una versió diferent quan es trobi una versió fallida a la cerca interactiva",
"FailedLoadingSearchResults": "No s'han pogut carregar els resultats de la cerca, torneu-ho a provar.",
"IndexerFlags": "Indicadors de l'indexador",
"Large": "Gran",
"LastDuration": "Darrera durada",
"LastExecution": "Darrere execució",
"Library": "Biblioteca",
"ListsSettingsSummary": "llista d'importació",
"Loading": "Carregant",
"MinimumCustomFormatScoreHelpText": "Puntuació mínima de format personalitzada necessaria per a evitar el retard del protocol preferit",
"ProfilesSettingsSummary": "Perfils de qualitat, idioma, retard i llançament",
"RemoveDownloadsAlert": "La configuració d'eliminació s'ha mogut a la configuració del client de baixada a la taula anterior.",
"RemoveQueueItemConfirmation": "Esteu segur que voleu eliminar '{sourceTitle}' de la cua?",
"RemoveSelectedItem": "Elimina l'element seleccionat",
"RemoveSelectedItems": "Elimina els elements seleccionats",
"RemoveSelectedItemsQueueMessageText": "Esteu segur que voleu eliminar {0} de la cua?",
"SelectQuality": "Seleccioneu Qualitat",
"SomeResultsAreHiddenByTheAppliedFilter": "Alguns resultats estan ocults pel filtre aplicat",
"AuthBasic": "Basic (finestra emergent del navegador)",
"AuthForm": "Formularis (pàgina d'inici de sessió)",
"AuthenticationMethod": "Mètode d'autenticació",
"AuthenticationMethodHelpTextWarning": "Seleccioneu un mètode d'autenticació vàlid",
"AuthenticationRequired": "Autenticació necessària",
"AuthenticationRequiredHelpText": "Canvia per a quines sol·licituds cal autenticar. No canvieu tret que entengueu els riscos.",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmeu la nova contrasenya",
"AuthenticationRequiredPasswordHelpTextWarning": "Introduïu una contrasenya nova",
"AuthenticationRequiredUsernameHelpTextWarning": "Introduïu un nom d'usuari nou",
"AuthenticationRequiredWarning": "Per a evitar l'accés remot sense autenticació, ara {appName} requereix que l'autenticació estigui activada. Opcionalment, podeu desactivar l'autenticació des d'adreces locals.",
"DisabledForLocalAddresses": "Desactivat per a adreces locals",
"Enabled": "Habilitat",
"External": "Extern",
"ApiKey": "Clau API",
"FailedToFetchUpdates": "No s'han pogut obtenir les actualitzacions",
"AptUpdater": "Utilitzeu apt per a instal·lar l'actualització",
"BuiltIn": "Integrat",
"CurrentlyInstalled": "Instal·lat actualment",
"DockerUpdater": "actualitzeu el contenidor Docker per a rebre l'actualització",
"ExternalUpdater": "{appName} està configurat per a utilitzar un mecanisme d'actualització extern",
"InstallLatest": "Instal·la l'últim",
"OnLatestVersion": "La darrera versió de {appName} ja està instal·lada",
"Script": "Script",
"UnmappedFiles": "Carpetes sense mapejar",
"UpdateAppDirectlyLoadError": "No es pot actualitzar {appName} directament,",
"AddMissing": "Afegeix faltants"
} }
+68 -37
View File
@@ -1,5 +1,4 @@
{ {
"ApiKeyHelpTextWarning": "Vyžaduje restart, aby se projevilo",
"AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo", "AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo",
"DeleteRootFolderMessageText": "Opravdu chcete odstranit indexer „{0}“?", "DeleteRootFolderMessageText": "Opravdu chcete odstranit indexer „{0}“?",
"Group": "Skupina", "Group": "Skupina",
@@ -12,7 +11,6 @@
"20MinutesTwenty": "120 minut: {0}", "20MinutesTwenty": "120 minut: {0}",
"45MinutesFourtyFive": "90 minut: {0}", "45MinutesFourtyFive": "90 minut: {0}",
"60MinutesSixty": "60 minut: {0}", "60MinutesSixty": "60 minut: {0}",
"APIKey": "Klíč API",
"About": "O aplikaci", "About": "O aplikaci",
"AddListExclusion": "Přidat vyloučení seznamu", "AddListExclusion": "Přidat vyloučení seznamu",
"AddingTag": "Přidání značky", "AddingTag": "Přidání značky",
@@ -20,16 +18,16 @@
"AlreadyInYourLibrary": "Již máte ve své knihovně", "AlreadyInYourLibrary": "Již máte ve své knihovně",
"AlternateTitles": "Alternativní název", "AlternateTitles": "Alternativní název",
"Analytics": "Analýzy", "Analytics": "Analýzy",
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.", "AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery {appName}u. To zahrnuje informace o vašem prohlížeči, které stránky {appName} WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
"AppDataDirectory": "Adresář AppData", "AppDataDirectory": "Adresář AppData",
"ApplyTags": "Použít značky", "ApplyTags": "Použít značky",
"Authentication": "Ověřování", "Authentication": "Ověřování",
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Radarr", "AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k {appName}",
"AuthorClickToChangeBook": "Kliknutím změníte film", "AuthorClickToChangeBook": "Kliknutím změníte film",
"AutoRedownloadFailedHelpText": "Automatické vyhledání a pokus o stažení jiného vydání", "AutoRedownloadFailedHelpText": "Automatické vyhledání a pokus o stažení jiného vydání",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmy odstraněné z disku jsou automaticky sledovány v Radarru", "AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmy odstraněné z disku jsou automaticky sledovány v {appName}u",
"Automatic": "Automatický", "Automatic": "Automatický",
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Radarr", "BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti {appName}",
"BackupNow": "Ihned zálohovat", "BackupNow": "Ihned zálohovat",
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny", "BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny",
"Backups": "Zálohy", "Backups": "Zálohy",
@@ -43,16 +41,16 @@
"Calendar": "Kalendář", "Calendar": "Kalendář",
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden", "CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
"Cancel": "Zrušit", "Cancel": "Zrušit",
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?", "CancelPendingTask": "Opravdu chcete zrušit tento nevyřízený úkol?",
"CertificateValidation": "Ověření certifikátu", "CertificateValidation": "Ověření certifikátu",
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.", "CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
"ChangeFileDate": "Změnit datum souboru", "ChangeFileDate": "Změnit datum souboru",
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena", "ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
"ChmodFolder": "Složka chmod", "ChmodFolder": "Složka chmod",
"ChmodFolderHelpText": "Octal, aplikováno během importu / přejmenování na mediální složky a soubory (bez provádění bitů)", "ChmodFolderHelpText": "Octal, aplikováno během importu / přejmenování na mediální složky a soubory (bez provádění bitů)",
"ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.", "ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru {appName} vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.",
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.", "ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
"ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako Radarr.", "ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru {appName} vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako {appName}.",
"Clear": "Vyčistit", "Clear": "Vyčistit",
"ClickToChangeQuality": "Kliknutím změníte kvalitu", "ClickToChangeQuality": "Kliknutím změníte kvalitu",
"ClientPriority": "Priorita klienta", "ClientPriority": "Priorita klienta",
@@ -64,11 +62,11 @@
"ConnectSettings": "Nastavení připojení", "ConnectSettings": "Nastavení připojení",
"Connections": "Připojení", "Connections": "Připojení",
"CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají", "CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají",
"CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci Radarr pro přejmenování.", "CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci {appName} pro přejmenování.",
"CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů", "CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
"CreateGroup": "Vytvořit skupinu", "CreateGroup": "Vytvořit skupinu",
"CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy", "CutoffHelpText": "Jakmile je této kvality dosaženo, {appName} již nebude stahovat filmy",
"CutoffUnmet": "Vynechat nevyhovující", "CutoffUnmet": "Mezní hodnota nesplněna",
"DatabaseMigration": "Migrace databáze", "DatabaseMigration": "Migrace databáze",
"Dates": "Termíny", "Dates": "Termíny",
"DelayProfile": "Profil zpoždění", "DelayProfile": "Profil zpoždění",
@@ -147,7 +145,7 @@
"Grab": "Urvat", "Grab": "Urvat",
"GrabID": "Chyť ID", "GrabID": "Chyť ID",
"GrabRelease": "Uchopte uvolnění", "GrabRelease": "Uchopte uvolnění",
"GrabReleaseMessageText": "Radarr nebyl schopen určit, pro který film je toto vydání určeno. Radarr nemusí být schopen toto vydání automaticky importovat. Chcete chytit „{0}“?", "GrabReleaseMessageText": "{appName} nebyl schopen určit, pro který film je toto vydání určeno. {appName} nemusí být schopen toto vydání automaticky importovat. Chcete chytit „{0}“?",
"GrabSelected": "Chyťte vybrané", "GrabSelected": "Chyťte vybrané",
"HasPendingChangesNoChanges": "Žádné změny", "HasPendingChangesNoChanges": "Žádné změny",
"HasPendingChangesSaveChanges": "Uložit změny", "HasPendingChangesSaveChanges": "Uložit změny",
@@ -169,7 +167,7 @@
"ImportedTo": "Importováno do", "ImportedTo": "Importováno do",
"Importing": "Import", "Importing": "Import",
"IncludeHealthWarningsHelpText": "Zahrnout zdravotní varování", "IncludeHealthWarningsHelpText": "Zahrnout zdravotní varování",
"IncludeUnknownAuthorItemsHelpText": "Zobrazit položky bez filmu ve frontě. To by mohlo zahrnovat odstraněné filmy nebo cokoli jiného v kategorii Radarr", "IncludeUnknownAuthorItemsHelpText": "Zobrazit položky bez filmu ve frontě. To by mohlo zahrnovat odstraněné filmy nebo cokoli jiného v kategorii {appName}",
"IncludeUnmonitored": "Zahrnout Nesledováno", "IncludeUnmonitored": "Zahrnout Nesledováno",
"Indexer": "Indexer", "Indexer": "Indexer",
"IndexerPriority": "Priorita indexování", "IndexerPriority": "Priorita indexování",
@@ -180,7 +178,7 @@
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Upgradujte, dokud nebude tato kvalita splněna nebo překročena", "IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Upgradujte, dokud nebude tato kvalita splněna nebo překročena",
"IsTagUsedCannotBeDeletedWhileInUse": "Během používání nelze smazat", "IsTagUsedCannotBeDeletedWhileInUse": "Během používání nelze smazat",
"Language": "Jazyk", "Language": "Jazyk",
"LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Radarr.", "LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku {appName}.",
"LoadingBookFilesFailed": "Načítání filmových souborů se nezdařilo", "LoadingBookFilesFailed": "Načítání filmových souborů se nezdařilo",
"Local": "Místní", "Local": "Místní",
"LogFiles": "Záznam souborů", "LogFiles": "Záznam souborů",
@@ -261,8 +259,8 @@
"RSSSync": "RSS synchronizace", "RSSSync": "RSS synchronizace",
"RSSSyncInterval": "Interval synchronizace RSS", "RSSSyncInterval": "Interval synchronizace RSS",
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki", "ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr podporuje jakýkoli indexer, který používá standard Newznab, stejně jako další indexery uvedené níže.", "ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "{appName} podporuje jakýkoli indexer, který používá standard Newznab, stejně jako další indexery uvedené níže.",
"ReadarrTags": "Radarr tagy", "ReadarrTags": "{appName} tagy",
"Real": "Nemovitý", "Real": "Nemovitý",
"Reason": "Důvod", "Reason": "Důvod",
"RecycleBinCleanupDaysHelpText": "Nastavením na 0 zakážete automatické čištění", "RecycleBinCleanupDaysHelpText": "Nastavením na 0 zakážete automatické čištění",
@@ -290,19 +288,19 @@
"RemoveTagExistingTag": "Stávající značka", "RemoveTagExistingTag": "Stávající značka",
"RemoveTagRemovingTag": "Odebírání značky", "RemoveTagRemovingTag": "Odebírání značky",
"RemovedFromTaskQueue": "Odebráno z fronty úkolů", "RemovedFromTaskQueue": "Odebráno z fronty úkolů",
"RenameBooksHelpText": "Pokud je přejmenování zakázáno, použije Radarr stávající název souboru", "RenameBooksHelpText": "Pokud je přejmenování zakázáno, použije {appName} stávající název souboru",
"Reorder": "Přeobjednat", "Reorder": "Přeobjednat",
"ReplaceIllegalCharacters": "Nahraďte nelegální znaky", "ReplaceIllegalCharacters": "Nahraďte nelegální znaky",
"RequiredHelpText": "Vydání musí obsahovat alespoň jeden z těchto výrazů (nerozlišují se malá a velká písmena)", "RequiredHelpText": "Vydání musí obsahovat alespoň jeden z těchto výrazů (nerozlišují se malá a velká písmena)",
"RequiredPlaceHolder": "Přidat nové omezení", "RequiredPlaceHolder": "Přidat nové omezení",
"RescanAfterRefreshHelpTextWarning": "Radarr nebude automaticky detekovat změny souborů, pokud není nastaveno na „Vždy“", "RescanAfterRefreshHelpTextWarning": "{appName} nebude automaticky detekovat změny souborů, pokud není nastaveno na „Vždy“",
"RescanAuthorFolderAfterRefresh": "Po obnovení znovu vyhledejte složku filmu", "RescanAuthorFolderAfterRefresh": "Po obnovení znovu vyhledejte složku filmu",
"Reset": "Resetovat", "Reset": "Resetovat",
"ResetAPIKey": "Resetovat klíč API", "ResetAPIKey": "Resetovat klíč API",
"ResetAPIKeyMessageText": "Opravdu chcete resetovat klíč API?", "ResetAPIKeyMessageText": "Opravdu chcete resetovat klíč API?",
"Restart": "Restartujte", "Restart": "Restartujte",
"RestartNow": "Restartovat nyní", "RestartNow": "Restartovat nyní",
"RestartReadarr": "Restartujte Radarr", "RestartReadarr": "Restartujte {appName}",
"Restore": "Obnovit", "Restore": "Obnovit",
"RestoreBackup": "Obnovit zálohu", "RestoreBackup": "Obnovit zálohu",
"Result": "Výsledek", "Result": "Výsledek",
@@ -343,7 +341,7 @@
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
"Size": " Velikost", "Size": " Velikost",
"SkipFreeSpaceCheck": "Přeskočit kontrolu volného prostoru", "SkipFreeSpaceCheck": "Přeskočit kontrolu volného prostoru",
"SkipFreeSpaceCheckWhenImportingHelpText": "Použijte, když Radarr nedokáže detekovat volné místo z kořenové složky filmu", "SkipFreeSpaceCheckWhenImportingHelpText": "Použijte, když {appName} nedokáže detekovat volné místo z kořenové složky filmu",
"SorryThatAuthorCannotBeFound": "Je nám líto, ale tento film nelze najít.", "SorryThatAuthorCannotBeFound": "Je nám líto, ale tento film nelze najít.",
"SorryThatBookCannotBeFound": "Je nám líto, ale tento film nelze najít.", "SorryThatBookCannotBeFound": "Je nám líto, ale tento film nelze najít.",
"Source": "Zdroj", "Source": "Zdroj",
@@ -361,7 +359,7 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Úspěch! Moje práce je hotová, žádné soubory k přejmenování.", "SuccessMyWorkIsDoneNoFilesToRetag": "Úspěch! Moje práce je hotová, žádné soubory k přejmenování.",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Vyhledávání není u tohoto indexeru podporováno", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Vyhledávání není u tohoto indexeru podporováno",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Radarr", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo {appName}",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Bude použito při použití interaktivního vyhledávání", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Bude použito při použití interaktivního vyhledávání",
"TagIsNotUsedAndCanBeDeleted": "Značka se nepoužívá a lze ji smazat", "TagIsNotUsedAndCanBeDeleted": "Značka se nepoužívá a lze ji smazat",
"Tags": "Značky", "Tags": "Značky",
@@ -378,7 +376,7 @@
"Torrents": "Torrenty", "Torrents": "Torrenty",
"TotalFileSize": "Celková velikost souboru", "TotalFileSize": "Celková velikost souboru",
"UILanguage": "Jazyk uživatelského rozhraní", "UILanguage": "Jazyk uživatelského rozhraní",
"UILanguageHelpText": "Jazyk, který Radarr použije pro uživatelské rozhraní", "UILanguageHelpText": "Jazyk, který {appName} použije pro uživatelské rozhraní",
"UILanguageHelpTextWarning": "Vyžaduje se opětovné načtení prohlížeče", "UILanguageHelpTextWarning": "Vyžaduje se opětovné načtení prohlížeče",
"UISettings": "Nastavení uživatelského rozhraní", "UISettings": "Nastavení uživatelského rozhraní",
"URLBase": "URL Base", "URLBase": "URL Base",
@@ -419,7 +417,7 @@
"UnmonitoredHelpText": "Zahrnout nemonitorované filmy do zdroje iCal", "UnmonitoredHelpText": "Zahrnout nemonitorované filmy do zdroje iCal",
"UpdateAll": "Aktualizovat vše", "UpdateAll": "Aktualizovat vše",
"UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace", "UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace",
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript", "UpdateMechanismHelpText": "Použijte vestavěný aktualizátor {appName} nebo skript",
"UpdateScriptPathHelpText": "Cesta k vlastnímu skriptu, který přebírá extrahovaný balíček aktualizace a zpracovává zbytek procesu aktualizace", "UpdateScriptPathHelpText": "Cesta k vlastnímu skriptu, který přebírá extrahovaný balíček aktualizace a zpracovává zbytek procesu aktualizace",
"Updates": "Aktualizace", "Updates": "Aktualizace",
"UpgradeAllowedHelpText": "Pokud budou deaktivovány vlastnosti, nebudou upgradovány", "UpgradeAllowedHelpText": "Pokud budou deaktivovány vlastnosti, nebudou upgradovány",
@@ -431,8 +429,8 @@
"UsenetDelay": "Usenet Zpoždění", "UsenetDelay": "Usenet Zpoždění",
"UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu", "UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu",
"Username": "Uživatelské jméno", "Username": "Uživatelské jméno",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Pobočka, která se má použít k aktualizaci Radarr", "BranchUpdate": "Pobočka, která se má použít k aktualizaci {appName}",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace", "BranchUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace",
"Version": "Verze", "Version": "Verze",
"WeekColumnHeader": "Záhlaví sloupce týdne", "WeekColumnHeader": "Záhlaví sloupce týdne",
"Year": "Rok", "Year": "Rok",
@@ -455,12 +453,12 @@
"UnableToLoadBlocklist": "Nelze načíst černou listinu", "UnableToLoadBlocklist": "Nelze načíst černou listinu",
"Component": "Komponenta", "Component": "Komponenta",
"Level": "Úroveň", "Level": "Úroveň",
"ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání Radarr, nebudete dostávat aktualizace", "ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání {appName}, nebudete dostávat aktualizace",
"Time": "Čas", "Time": "Čas",
"Blocklist": "Blocklist", "Blocklist": "Blocklist",
"BlocklistRelease": "Blocklist pro vydání", "BlocklistRelease": "Blocklist pro vydání",
"ShowUnknownAuthorItems": "Zobrazit neznámé položky filmu", "ShowUnknownAuthorItems": "Zobrazit neznámé položky filmu",
"ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování Radarru.", "ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování {appName}u.",
"UnselectAll": "Odznačit vše", "UnselectAll": "Odznačit vše",
"UpdateSelected": "Aktualizace vybrána", "UpdateSelected": "Aktualizace vybrána",
"All": "Vše", "All": "Vše",
@@ -492,7 +490,7 @@
"OnBookFileDeleteForUpgrade": "Na filmovém souboru Odstranit pro upgrade", "OnBookFileDeleteForUpgrade": "Na filmovém souboru Odstranit pro upgrade",
"OnBookFileDeleteForUpgradeHelpText": "Na filmovém souboru Odstranit pro upgrade", "OnBookFileDeleteForUpgradeHelpText": "Na filmovém souboru Odstranit pro upgrade",
"OnBookFileDeleteHelpText": "Při mazání filmových souborů", "OnBookFileDeleteHelpText": "Při mazání filmových souborů",
"ReadarrSupportsAnyDownloadClient": "Whisparr podporuje libovolného klienta pro stahování, který používá standard Newznab, stejně jako další klienty pro stahování uvedené níže.", "ReadarrSupportsAnyDownloadClient": "{appName} podporuje libovolného klienta pro stahování, který používá standard Newznab, stejně jako další klienty pro stahování uvedené níže.",
"TimeLeft": "Zbývající čas", "TimeLeft": "Zbývající čas",
"Disabled": "Zakázáno", "Disabled": "Zakázáno",
"CouldntFindAnyResultsForTerm": "Nelze najít žádné výsledky pro dotaz „{0}“", "CouldntFindAnyResultsForTerm": "Nelze najít žádné výsledky pro dotaz „{0}“",
@@ -509,15 +507,15 @@
"ImportListStatusCheckSingleClientMessage": "Seznamy nejsou k dispozici z důvodu selhání: {0}", "ImportListStatusCheckSingleClientMessage": "Seznamy nejsou k dispozici z důvodu selhání: {0}",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {0}", "IndexerLongTermStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {0}",
"IndexerPriorityHelpText": "Priorita indexování od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25.", "IndexerPriorityHelpText": "Priorita indexování od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 25.",
"IndexerRssHealthCheckNoIndexers": "Nejsou k dispozici žádné indexery se zapnutou synchronizací RSS, Radarr nové verze automaticky nezachytí", "IndexerRssHealthCheckNoIndexers": "Nejsou k dispozici žádné indexery se zapnutou synchronizací RSS, {appName} nové verze automaticky nezachytí",
"IndexerSearchCheckNoAutomaticMessage": "Nejsou k dispozici žádné indexery se zapnutým automatickým vyhledáváním, Radarr neposkytne žádné automatické výsledky hledání", "IndexerSearchCheckNoAutomaticMessage": "Nejsou k dispozici žádné indexery se zapnutým automatickým vyhledáváním, {appName} neposkytne žádné automatické výsledky hledání",
"IndexerSearchCheckNoAvailableIndexersMessage": "Všechny indexery podporující vyhledávání jsou dočasně nedostupné kvůli nedávným chybám indexeru", "IndexerSearchCheckNoAvailableIndexersMessage": "Všechny indexery podporující vyhledávání jsou dočasně nedostupné kvůli nedávným chybám indexeru",
"IndexerSearchCheckNoInteractiveMessage": "Pokud je povoleno interaktivní vyhledávání, nejsou k dispozici žádné indexery, Radarr neposkytne žádné interaktivní výsledky hledání", "IndexerSearchCheckNoInteractiveMessage": "Pokud je povoleno interaktivní vyhledávání, nejsou k dispozici žádné indexery, {appName} neposkytne žádné interaktivní výsledky hledání",
"IndexerStatusCheckAllClientMessage": "Všechny indexery nejsou k dispozici z důvodu selhání", "IndexerStatusCheckAllClientMessage": "Všechny indexery nejsou k dispozici z důvodu selhání",
"MaintenanceRelease": "Údržbové vydání: opravy chyb a další vylepšení. Další podrobnosti najdete v GitHub Commit History", "MaintenanceRelease": "Údržbové vydání: opravy chyb a další vylepšení. Další podrobnosti najdete v GitHub Commit History",
"MediaManagement": "Správa médií", "MediaManagement": "Správa médií",
"Metadata": "Metadata", "Metadata": "Metadata",
"MissingFromDisk": "Radarr nemohl najít soubor na disku, takže byl odstraněn", "MissingFromDisk": "{appName} nemohl najít soubor na disku, takže byl odstraněn",
"Monitor": "Monitor", "Monitor": "Monitor",
"OnRename": "Při přejmenování", "OnRename": "Při přejmenování",
"OnUpgrade": "Při upgradu", "OnUpgrade": "Při upgradu",
@@ -525,11 +523,11 @@
"ProxyCheckFailedToTestMessage": "Nepodařilo se otestovat proxy: {0}", "ProxyCheckFailedToTestMessage": "Nepodařilo se otestovat proxy: {0}",
"QualitySettingsSummary": "Kvalitní velikosti a pojmenování", "QualitySettingsSummary": "Kvalitní velikosti a pojmenování",
"QueueIsEmpty": "Fronta je prázdná", "QueueIsEmpty": "Fronta je prázdná",
"RestartReloadNote": "Poznámka: Radarr se během procesu obnovy automaticky restartuje a znovu načte uživatelské rozhraní.", "RestartReloadNote": "Poznámka: {appName} se během procesu obnovy automaticky restartuje a znovu načte uživatelské rozhraní.",
"RootFolderCheckMultipleMessage": "Chybí více kořenových složek: {0}", "RootFolderCheckMultipleMessage": "Chybí více kořenových složek: {0}",
"Save": "Uložit", "Save": "Uložit",
"SearchFiltered": "Hledat filtrováno", "SearchFiltered": "Hledat filtrováno",
"SettingsRemotePathMappingLocalPathHelpText": "Cesta, kterou by Radarr měl použít pro místní přístup ke vzdálené cestě", "SettingsRemotePathMappingLocalPathHelpText": "Cesta, kterou by {appName} měl použít pro místní přístup ke vzdálené cestě",
"SettingsRemotePathMappingRemotePathHelpText": "Kořenová cesta k adresáři, do kterého stahovací klient přistupuje", "SettingsRemotePathMappingRemotePathHelpText": "Kořenová cesta k adresáři, do kterého stahovací klient přistupuje",
"SystemTimeCheckMessage": "Systémový čas je vypnutý o více než 1 den. Naplánované úlohy nemusí fungovat správně, dokud nebude čas opraven", "SystemTimeCheckMessage": "Systémový čas je vypnutý o více než 1 den. Naplánované úlohy nemusí fungovat správně, dokud nebude čas opraven",
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "Složka filmu „{0}“ a veškerý její obsah budou smazány.", "TheAuthorFolderAndAllOfItsContentWillBeDeleted": "Složka filmu „{0}“ a veškerý její obsah budou smazány.",
@@ -565,7 +563,7 @@
"UpgradesAllowed": "Upgrady povoleny", "UpgradesAllowed": "Upgrady povoleny",
"CustomFormatSettings": "Nastavení vlastních formátů", "CustomFormatSettings": "Nastavení vlastních formátů",
"CustomFormats": "Vlastní formáty", "CustomFormats": "Vlastní formáty",
"CutoffFormatScoreHelpText": "Jakmile je dosaženo tohoto skóre vlastního formátu, Radarr již nebude stahovat filmy", "CutoffFormatScoreHelpText": "Jakmile je dosaženo tohoto skóre vlastního formátu, {appName} již nebude stahovat filmy",
"DeleteFormatMessageText": "Opravdu chcete smazat značku formátu {0}?", "DeleteFormatMessageText": "Opravdu chcete smazat značku formátu {0}?",
"HiddenClickToShow": "Skryté, kliknutím zobrazíte", "HiddenClickToShow": "Skryté, kliknutím zobrazíte",
"HideAdvanced": "Skrýt pokročilé", "HideAdvanced": "Skrýt pokročilé",
@@ -693,5 +691,38 @@
"CustomFilter": "Vlastní filtry", "CustomFilter": "Vlastní filtry",
"SelectQuality": "Vyberte kvalitu", "SelectQuality": "Vyberte kvalitu",
"IndexerFlags": "Příznaky indexeru", "IndexerFlags": "Příznaky indexeru",
"InteractiveSearchModalHeader": "Interaktivní vyhledávání" "InteractiveSearchModalHeader": "Interaktivní vyhledávání",
"FailedLoadingSearchResults": "Výsledky vyhledávání se nepodařilo načíst, zkuste to prosím znovu.",
"CustomFormatsSpecificationFlag": "Vlajka",
"CustomFormatsSpecificationRegularExpressionHelpText": "Vlastní formát RegEx nerozlišuje velká a malá písmena",
"BlocklistAndSearch": "Seznam blokovaných a vyhledávání",
"ChangeCategory": "Změnit kategorii",
"BlocklistMultipleOnlyHint": "Blokovat a nehledat náhradu",
"CustomFormatsSettingsTriggerInfo": "Vlastní formát se použije na vydání nebo soubor, pokud odpovídá alespoň jednomu z různých typů zvolených podmínek.",
"ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
"AuthBasic": "Základní (vyskakovací okno prohlížeče)",
"AuthenticationMethod": "Metoda ověřování",
"AuthenticationMethodHelpTextWarning": "Prosím vyberte platnou metodu ověřování",
"AuthenticationRequired": "Vyžadované ověření",
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Pokud nerozumíte rizikům, neměňte je.",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo",
"AuthenticationRequiredPasswordHelpTextWarning": "Vložte nové heslo",
"AuthenticationRequiredUsernameHelpTextWarning": "Vložte nové uživatelské jméno",
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName} povolení ověření. Ověřování z místních adres můžete volitelně zakázat.",
"BlocklistOnlyHint": "Blokovat a nehledat náhradu",
"Enabled": "Povoleno",
"ApiKey": "Klíč API",
"AuthForm": "Formuláře (přihlašovací stránka)",
"DisabledForLocalAddresses": "Zakázáno pro místní adresy",
"DockerUpdater": "aktualizujte kontejner dockeru, abyste aktualizaci obdrželi",
"ExternalUpdater": "{appName} je nakonfigurován pro použití externího aktualizačního mechanismu",
"FailedToFetchUpdates": "Nepodařilo se načíst aktualizace",
"OnLatestVersion": "Nejnovější verze aplikace {appName} je již nainstalována",
"Script": "Skript",
"UpdateAppDirectlyLoadError": "{appName} nelze aktualizovat přímo,",
"BuiltIn": "Vestavěný",
"InstallLatest": "Nainstalujte nejnovější",
"CurrentlyInstalled": "Aktuálně nainstalováno",
"UnmappedFiles": "Nezmapované složky",
"AptUpdater": "K instalaci aktualizace použijte apt"
} }

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