Compare commits

..

106 Commits

Author SHA1 Message Date
Mark McDowall
e739a93a2a Fixed: Loading calendar on older browsers
(cherry picked from commit edfc12e27a00fd927df0de6ccb3961efe8f5dc3b)
2024-12-17 05:02:57 +00:00
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
186 changed files with 7694 additions and 4794 deletions

1
.gitignore vendored
View File

@@ -120,6 +120,7 @@ _artifacts
_rawPackage/
_dotTrace*
_tests/
_temp*
*.Result.xml
coverage*.xml
coverage*.json

View File

@@ -9,18 +9,18 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.3.26'
majorVersion: '0.4.6'
minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.421'
dotnetVersion: '6.0.427'
nodeVersion: '20.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11'
macImage: 'macOS-13'
trigger:
branches:
@@ -1102,7 +1102,7 @@ stages:
vmImage: ${{ variables.windowsImage }}
steps:
- checkout: self # Need history for Sonar analysis
- task: SonarCloudPrepare@1
- task: SonarCloudPrepare@2
env:
SONAR_SCANNER_OPTS: ''
inputs:
@@ -1114,7 +1114,7 @@ stages:
cliProjectName: 'ReadarrUI'
cliProjectVersion: '$(readarrVersion)'
cliSources: './frontend'
- task: SonarCloudAnalyze@1
- task: SonarCloudAnalyze@2
- job: Api_Docs
displayName: API Docs
@@ -1190,7 +1190,7 @@ stages:
submodules: true
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
- task: SonarCloudPrepare@1
- task: SonarCloudPrepare@2
condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs:
SonarCloud: 'SonarCloud'
@@ -1208,21 +1208,16 @@ stages:
./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1
- task: SonarCloudAnalyze@2
condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results
- task: reportgenerator@4
- task: reportgenerator@5.3.11
displayName: Generate Coverage Report
inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
- task: PublishCodeCoverageResults@1
displayName: Publish Coverage Report
inputs:
codeCoverageTool: 'cobertura'
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
reportDirectory: './CoverageResults/combined/'
publishCodeCoverageResults: true
- stage: Report_Out
dependsOn:

14
docs.sh
View File

@@ -1,3 +1,7 @@
#!/bin/bash
set -e
FRAMEWORK="net6.0"
PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then
@@ -21,15 +25,21 @@ slnFile=src/Readarr.sln
platform=Posix
if [ "$PLATFORM" = "Windows" ]; then
application=Readarr.Console.dll
else
application=Readarr.dll
fi
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
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

View File

@@ -9,7 +9,7 @@
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"typescript.preferences.quoteStyle": "single",

View File

@@ -26,6 +26,7 @@ module.exports = (env) => {
const config = {
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map',
target: 'web',
stats: {
children: false
@@ -67,7 +68,7 @@ module.exports = (env) => {
output: {
path: distFolder,
publicPath: '/',
filename: '[name]-[contenthash].js',
filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
sourceMapFilename: '[file].map'
},
@@ -92,7 +93,7 @@ module.exports = (env) => {
new MiniCssExtractPlugin({
filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
}),
new HtmlWebpackPlugin({
@@ -202,7 +203,7 @@ module.exports = (env) => {
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
}
}
},

View File

@@ -32,7 +32,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector';
import Logs from 'System/Logs/Logs';
import Status from 'System/Status/Status';
import Tasks from 'System/Tasks/Tasks';
import UpdatesConnector from 'System/Updates/UpdatesConnector';
import Updates from 'System/Updates/Updates';
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
@@ -247,7 +247,7 @@ function AppRoutes(props) {
<Route
path="/system/updates"
component={UpdatesConnector}
component={Updates}
/>
<Route

View File

@@ -1,6 +1,7 @@
import AuthorsAppState from './AuthorsAppState';
import CommandAppState from './CommandAppState';
import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
import TagsAppState from './TagsAppState';
interface FilterBuilderPropOption {
@@ -35,10 +36,24 @@ export interface CustomFilter {
filers: PropertyFilter[];
}
export interface AppSectionState {
isConnected: boolean;
isReconnecting: boolean;
version: string;
prevVersion?: string;
dimensions: {
isSmallScreen: boolean;
width: number;
height: number;
};
}
interface AppState {
app: AppSectionState;
authors: AuthorsAppState;
commands: CommandAppState;
settings: SettingsAppState;
system: SystemAppState;
tags: TagsAppState;
}

View File

@@ -1,5 +1,6 @@
import AppSectionState, {
AppSectionDeleteState,
AppSectionItemState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
import DownloadClient from 'typings/DownloadClient';
@@ -7,13 +8,16 @@ import ImportList from 'typings/ImportList';
import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag';
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
extends AppSectionState<DownloadClient>,
AppSectionDeleteState,
AppSectionSaveState {}
export type GeneralAppState = AppSectionItemState<General>;
export interface ImportListAppState
extends AppSectionState<ImportList>,
AppSectionDeleteState,
@@ -33,11 +37,12 @@ export type UiSettingsAppState = AppSectionState<UiSettings>;
interface SettingsAppState {
downloadClients: DownloadClientAppState;
general: GeneralAppState;
importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState;
notifications: NotificationAppState;
uiSettings: UiSettingsAppState;
ui: UiSettingsAppState;
}
export default SettingsAppState;

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;

View File

@@ -5,6 +5,7 @@ import dimensions from 'Styles/Variables/dimensions';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import AuthorIndexOverviewInfoRow from './AuthorIndexOverviewInfoRow';
import styles from './AuthorIndexOverviewInfo.css';
@@ -76,9 +77,9 @@ function getInfoRowProps(row, props) {
};
}
if (name === 'qualityProfileId') {
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
return {
title: 'Quality Profile',
title: translate('QualityProfile'),
iconName: icons.PROFILE,
label: props.qualityProfile.name
};

View File

@@ -235,12 +235,12 @@ class AuthorIndexPoster extends Component {
</div>
}
{
showQualityProfile &&
<div className={styles.title}>
{qualityProfile.name}
</div>
}
{showQualityProfile && !!qualityProfile?.name ? (
<div className={styles.title} title={translate('QualityProfile')}>
{qualityProfile.name}
</div>
) : null}
{
nextAiring &&
<div className={styles.nextAiring}>

View File

@@ -209,7 +209,7 @@ class AuthorIndexRow extends Component {
key={name}
className={styles[name]}
>
{qualityProfile.name}
{qualityProfile?.name ?? ''}
</VirtualTableRowCell>
);
}
@@ -220,7 +220,7 @@ class AuthorIndexRow extends Component {
key={name}
className={styles[name]}
>
{metadataProfile.name}
{metadataProfile?.name ?? ''}
</VirtualTableRowCell>
);
}

View File

@@ -5,6 +5,7 @@ import dimensions from 'Styles/Variables/dimensions';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import BookIndexOverviewInfoRow from './BookIndexOverviewInfoRow';
import styles from './BookIndexOverviewInfo.css';
@@ -71,9 +72,9 @@ function getInfoRowProps(row, props) {
};
}
if (name === 'qualityProfileId') {
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
return {
title: 'Quality Profile',
title: translate('QualityProfile'),
iconName: icons.PROFILE,
label: props.qualityProfile.name
};

View File

@@ -250,12 +250,12 @@ class BookIndexPoster extends Component {
</div>
}
{
showQualityProfile &&
<div className={styles.title}>
{qualityProfile.name}
</div>
}
{showQualityProfile && !!qualityProfile?.name ? (
<div className={styles.title} title={translate('QualityProfile')}>
{qualityProfile.name}
</div>
) : null}
{
nextAiring &&
<div className={styles.nextAiring}>

View File

@@ -195,7 +195,7 @@ class BookIndexRow extends Component {
key={name}
className={styles[name]}
>
{qualityProfile.name}
{qualityProfile?.name ?? ''}
</VirtualTableRowCell>
);
}

View File

@@ -1,5 +0,0 @@
.input {
composes: input from '~Components/Form/TextInput.css';
font-family: $passwordFamily;
}

View File

@@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import TextInput from './TextInput';
import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input
function onCopy(e) {
@@ -13,17 +11,14 @@ function PasswordInput(props) {
return (
<TextInput
{...props}
type="password"
onCopy={onCopy}
/>
);
}
PasswordInput.propTypes = {
className: PropTypes.string.isRequired
};
PasswordInput.defaultProps = {
className: styles.input
...TextInput.props
};
export default PasswordInput;

View File

@@ -3,9 +3,9 @@
padding: 0;
font-size: inherit;
}
.isDisabled {
color: var(--disabledColor);
cursor: not-allowed;
&.isDisabled {
color: var(--disabledColor);
cursor: not-allowed;
}
}

View File

@@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
import ColorImpairedContext from 'App/ColorImpairedContext';
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
import SignalRConnector from 'Components/SignalRConnector';
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
import locationShape from 'Helpers/Props/Shapes/locationShape';
import PageHeader from './Header/PageHeader';
import PageSidebar from './Sidebar/PageSidebar';
@@ -75,6 +76,7 @@ class Page extends Component {
isSmallScreen,
isSidebarVisible,
enableColorImpairedMode,
authenticationEnabled,
onSidebarToggle,
onSidebarVisibleChange
} = this.props;
@@ -108,6 +110,10 @@ class Page extends Component {
isOpen={this.state.isConnectionLostModalOpen}
onModalClose={this.onConnectionLostModalClose}
/>
<AuthenticationRequiredModal
isOpen={!authenticationEnabled}
/>
</div>
</ColorImpairedContext.Provider>
);
@@ -123,6 +129,7 @@ Page.propTypes = {
isUpdated: PropTypes.bool.isRequired,
isDisconnected: PropTypes.bool.isRequired,
enableColorImpairedMode: PropTypes.bool.isRequired,
authenticationEnabled: PropTypes.bool.isRequired,
onResize: PropTypes.func.isRequired,
onSidebarToggle: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired

View File

@@ -18,6 +18,7 @@ import {
import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import ErrorPage from './ErrorPage';
import LoadingPage from './LoadingPage';
import Page from './Page';
@@ -153,18 +154,21 @@ function createMapStateToProps() {
selectErrors,
selectAppProps,
createDimensionsSelector(),
createSystemStatusSelector(),
(
enableColorImpairedMode,
isPopulated,
errors,
app,
dimensions
dimensions,
systemStatus
) => {
return {
...app,
...errors,
isPopulated,
isSmallScreen: dimensions.isSmallScreen,
authenticationEnabled: systemStatus.authentication !== 'none',
enableColorImpairedMode
};
}

View File

@@ -253,7 +253,7 @@ class SignalRConnector extends Component {
handleWantedCutoff = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'cutoffUnmet',
section: 'wanted.cutoffUnmet',
updateOnly: true,
...body.resource
});
@@ -263,7 +263,7 @@ class SignalRConnector extends Component {
handleWantedMissing = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'missing',
section: 'wanted.missing',
updateOnly: true,
...body.resource
});

View File

@@ -25,14 +25,3 @@
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');
}
/*
* 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');
}

View File

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

View File

@@ -0,0 +1,5 @@
.authRequiredAlert {
composes: alert from '~Components/Alert.css';
margin-bottom: 20px;
}

View File

@@ -1,7 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'input': string;
'authRequiredAlert': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import TextInput from 'Components/Form/TextInput';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
@@ -7,7 +8,7 @@ import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector';
@@ -127,9 +128,16 @@ class AddNewItem extends Component {
!isFetching && !!error ?
<div className={styles.message}>
<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>{getErrorMessage(error)}</div>
</div> : null
}

View File

@@ -25,3 +25,8 @@
border-radius: 4px;
background-color: var(--cardCenterBackgroundColor);
}
.customFormats {
display: flex;
flex-wrap: wrap;
}

View File

@@ -3,6 +3,7 @@
interface CssExports {
'addSpecification': string;
'center': string;
'customFormats': string;
'deleteButton': string;
'rightButtons': string;
}

View File

@@ -11,16 +11,69 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const authenticationMethodOptions = [
{ key: 'none', value: 'None' },
{ key: 'basic', value: 'Basic (Browser Popup)' },
{ key: 'forms', value: 'Forms (Login Page)' }
export const authenticationMethodOptions = [
{
key: 'none',
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 = [
{ key: 'enabled', value: 'Enabled' },
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
{ key: 'disabled', value: 'Disabled' }
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
},
{
key: 'disabled',
get value() {
return translate('Disabled');
}
}
];
class SecuritySettings extends Component {
@@ -68,8 +121,10 @@ class SecuritySettings extends Component {
const {
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation,
apiKey,
certificateValidation
} = settings;
@@ -79,26 +134,40 @@ class SecuritySettings extends Component {
return (
<FieldSet legend={translate('Security')}>
<FormGroup>
<FormLabel>
{translate('Authentication')}
</FormLabel>
<FormLabel>{translate('Authentication')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
{
authenticationEnabled &&
authenticationEnabled ?
<FormGroup>
<FormLabel>
{translate('Username')}
</FormLabel>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@@ -106,15 +175,14 @@ class SecuritySettings extends Component {
onChange={onInputChange}
{...username}
/>
</FormGroup>
</FormGroup> :
null
}
{
authenticationEnabled &&
authenticationEnabled ?
<FormGroup>
<FormLabel>
{translate('Password')}
</FormLabel>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
@@ -122,19 +190,33 @@ class SecuritySettings extends Component {
onChange={onInputChange}
{...password}
/>
</FormGroup>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup> :
null
}
<FormGroup>
<FormLabel>
{translate('APIKey')}
</FormLabel>
<FormLabel>{translate('ApiKey')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="apiKey"
readOnly={true}
helpTextWarning={translate('ApiKeyHelpTextWarning')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
buttons={[
<ClipboardButton
key="copy"
@@ -160,9 +242,7 @@ class SecuritySettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>
{translate('CertificateValidation')}
</FormLabel>
<FormLabel>{translate('CertificateValidation')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}

View File

@@ -18,7 +18,6 @@ function UpdateSettings(props) {
const {
advancedSettings,
settings,
isWindows,
packageUpdateMechanism,
onInputChange
} = props;
@@ -44,10 +43,10 @@ function UpdateSettings(props) {
value: titleCase(packageUpdateMechanism)
});
} 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 (
<FieldSet legend={translate('Updates')}>
@@ -60,8 +59,8 @@ function UpdateSettings(props) {
<FormInputGroup
type={inputTypes.AUTO_COMPLETE}
name="branch"
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')}
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr"
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
helpLink="https://wiki.servarr.com/readarr/settings#updates"
{...branch}
values={branchValues}
onChange={onInputChange}
@@ -69,62 +68,59 @@ function UpdateSettings(props) {
/>
</FormGroup>
{
!isWindows &&
<div>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Automatic')}</FormLabel>
<div>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Automatic')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="updateAutomatically"
helpText={translate('UpdateAutomaticallyHelpText')}
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Readarr' }) : undefined}
onChange={onInputChange}
{...updateAutomatically}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.CHECK}
name="updateAutomatically"
helpText={translate('UpdateAutomaticallyHelpText')}
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
onChange={onInputChange}
{...updateAutomatically}
/>
</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
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('Mechanism')}</FormLabel>
<FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="updateMechanism"
values={updateOptions}
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/readarr/faq#how-do-i-update-my-readarr"
type={inputTypes.TEXT}
name="updateScriptPath"
helpText={translate('UpdateScriptPathHelpText')}
onChange={onInputChange}
{...updateMechanism}
{...updateScriptPath}
/>
</FormGroup>
{
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>
}
}
</div>
</FieldSet>
);
}

View File

@@ -8,6 +8,7 @@ import TagListConnector from 'Components/TagListConnector';
import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector';
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageImportListsModalRow.css';
interface ManageImportListsModalRowProps {
@@ -70,7 +71,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell>
<TableRowCell className={styles.qualityProfileId}>
{qualityProfile?.name ?? 'None'}
{qualityProfile?.name ?? translate('None')}
</TableRowCell>
<TableRowCell className={styles.metadataProfileId}>
@@ -82,7 +83,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell>
<TableRowCell className={styles.enableAutomaticAdd}>
{enableAutomaticAdd ? 'Yes' : 'No'}
{enableAutomaticAdd ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.tags}>

View File

@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -15,11 +14,11 @@ function createMapStateToProps() {
(state) => state.settings.advancedSettings,
(state) => state.settings.namingExamples,
createSettingsSectionSelector(SECTION),
(advancedSettings, examples, sectionSettings) => {
(advancedSettings, namingExamples, sectionSettings) => {
return {
advancedSettings,
examples: examples.item,
examplesPopulated: !_.isEmpty(examples.item),
examples: namingExamples.item,
examplesPopulated: namingExamples.isPopulated,
...sectionSettings
};
}

View File

@@ -75,12 +75,12 @@ class RootFolder extends Component {
{path}
</Label>
<Label kind={kinds.SUCCESS}>
{qualityProfile.name}
<Label kind={qualityProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{qualityProfile?.name || translate('None')}
</Label>
<Label kind={kinds.SUCCESS}>
{metadataProfile.name}
<Label kind={metadataProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{metadataProfile?.name || translate('None')}
</Label>
</div>

View File

@@ -2,7 +2,6 @@ module.exports = {
// Families
defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif',
monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;',
passwordFamily: 'text-security-disc',
// Sizes
extraSmallFontSize: '11px',

View File

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

View File

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

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;

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;

View File

@@ -8,6 +8,7 @@ window.console.debug = window.console.debug || function() {};
window.console.warn = window.console.warn || function() {};
window.console.assert = window.console.assert || function() {};
// TODO: Remove in v5, well suppoprted in browsers
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
enumerable: false,
@@ -20,6 +21,7 @@ if (!String.prototype.startsWith) {
});
}
// TODO: Remove in v5, well suppoprted in browsers
if (!String.prototype.endsWith) {
Object.defineProperty(String.prototype, 'endsWith', {
enumerable: false,
@@ -34,8 +36,14 @@ if (!String.prototype.endsWith) {
});
}
// TODO: Remove in v5, use `includes` instead
if (!('contains' in String.prototype)) {
String.prototype.contains = function(str, startIndex) {
return String.prototype.indexOf.call(this, str, startIndex) !== -1;
};
}
// For Firefox ESR 115 support
if (!Object.groupBy) {
import('core-js/actual/object/group-by');
}

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

View File

@@ -1,4 +1,5 @@
export interface UiSettings {
export default interface UiSettings {
theme: 'auto' | 'dark' | 'light';
showRelativeDates: boolean;
shortDateFormat: string;
longDateFormat: string;

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;

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;

View File

@@ -25,34 +25,33 @@
"defaults"
],
"dependencies": {
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@fortawesome/fontawesome-free": "6.6.0",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@microsoft/signalr": "6.0.25",
"@sentry/browser": "7.51.2",
"@sentry/integrations": "7.51.2",
"@types/node": "18.19.31",
"@sentry/browser": "7.119.1",
"@sentry/integrations": "7.119.1",
"@types/node": "20.16.11",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"ansi-colors": "4.1.3",
"classnames": "2.3.2",
"classnames": "2.5.1",
"clipboard": "2.0.11",
"connected-react-router": "6.9.3",
"element-class": "0.2.2",
"filesize": "10.0.7",
"filesize": "10.1.6",
"fuse.js": "6.6.2",
"history": "4.10.1",
"jdu": "1.0.0",
"jquery": "3.7.0",
"jquery": "3.7.1",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
"moment": "2.30.1",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
"qs": "6.11.1",
"qs": "6.13.0",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -64,7 +63,7 @@
"react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3",
"react-dom": "17.0.2",
"react-focus-lock": "2.5.2",
"react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",
"react-measure": "2.5.2",
@@ -73,74 +72,71 @@
"react-redux": "7.2.4",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-slider": "1.3.1",
"react-tabs": "3.2.2",
"react-text-truncate": "0.18.0",
"react-slider": "1.3.3",
"react-tabs": "4.3.0",
"react-text-truncate": "0.19.0",
"react-virtualized": "9.21.1",
"redux": "4.1.0",
"redux": "4.2.1",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0",
"redux-thunk": "2.4.2",
"reselect": "4.1.8",
"stacktrace-js": "2.0.2",
"typescript": "5.1.6"
},
"devDependencies": {
"@babel/core": "7.24.4",
"@babel/eslint-parser": "7.24.1",
"@babel/plugin-proposal-export-default-from": "7.24.1",
"@babel/core": "7.25.8",
"@babel/eslint-parser": "7.25.8",
"@babel/plugin-proposal-export-default-from": "7.25.8",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.24.4",
"@babel/preset-react": "7.24.1",
"@babel/preset-typescript": "7.24.1",
"@babel/preset-env": "7.25.8",
"@babel/preset-react": "7.25.7",
"@babel/preset-typescript": "7.25.7",
"@types/lodash": "4.14.195",
"@types/react-lazyload": "3.2.0",
"@types/redux-actions": "2.6.2",
"@types/react-lazyload": "3.2.3",
"@types/redux-actions": "2.6.5",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.3",
"autoprefixer": "10.4.20",
"babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1",
"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-modules-typescript-loader": "4.0.1",
"eslint": "8.57.0",
"eslint": "8.57.1",
"eslint-config-prettier": "8.10.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "12.1.0",
"eslint-plugin-react": "7.37.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-simple-import-sort": "12.1.1",
"file-loader": "6.2.0",
"filemanager-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",
"mini-css-extract-plugin": "2.7.6",
"postcss": "8.4.38",
"mini-css-extract-plugin": "2.9.1",
"postcss": "8.4.47",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
"postcss-nested": "6.0.1",
"postcss-nested": "6.2.0",
"postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3",
"prettier": "2.8.8",
"require-nocache": "1.0.0",
"rimraf": "4.4.1",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.3",
"rimraf": "6.0.1",
"style-loader": "3.3.4",
"stylelint": "15.10.3",
"stylelint-order": "6.0.3",
"terser-webpack-plugin": "5.3.9",
"ts-loader": "9.4.4",
"stylelint-order": "6.0.4",
"terser-webpack-plugin": "5.3.10",
"ts-loader": "9.5.1",
"typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1",
"webpack": "5.88.2",
"webpack": "5.95.0",
"webpack-cli": "5.1.4",
"webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8"

View File

@@ -4,26 +4,27 @@
<PackageVersion Include="AutoFixture" Version="4.17.0" />
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
<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.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="Equ" Version="2.3.0" />
<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.SQLite" 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="Ical.Net" Version="4.2.0" />
<PackageVersion Include="Ical.Net" Version="4.3.1" />
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
<PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mailkit" Version="3.6.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.29" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration" 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.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="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
<PackageVersion Include="Moq" Version="4.17.2" />
@@ -33,20 +34,21 @@
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
<PackageVersion Include="NLog" Version="5.1.4" />
<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="NUnit" Version="3.14.0" />
<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" Version="106.15.0" />
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
<PackageVersion Include="Sentry" Version="3.31.0" />
<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="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.Configuration.ConfigurationManager" Version="6.0.1" />
<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.ServiceProcess.ServiceController" Version="6.0.1" />
<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="TagLibSharp-Lidarr" Version="2.2.0.19" />
</ItemGroup>

View File

@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll();
_runner.Start();
_runner.Start(true);
driver.Url = "http://localhost:8787";

View File

@@ -1,10 +1,12 @@
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using NzbDrone.Test.Common;
@@ -43,6 +45,26 @@ namespace NzbDrone.Common.Test
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.WriteAllText(configFile, It.IsAny<string>()))
.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]

View File

@@ -21,9 +21,28 @@ namespace NzbDrone.Common.Test.ExtensionTests
[TestCase("1.2.3.4")]
[TestCase("172.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)
{
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();
}
}
}

View File

@@ -89,6 +89,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
[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)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);

View File

@@ -10,6 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle;
@@ -33,6 +34,11 @@ namespace NzbDrone.Common.Test
container.RegisterInstance(new Mock<IHostLifetime>().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();
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();

View File

@@ -39,18 +39,24 @@ namespace NzbDrone.Common.Extensions
private static bool IsLocalIPv4(byte[] ipv4Bytes)
{
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
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)
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)
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)
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;
}
}
}

View File

@@ -54,7 +54,10 @@ namespace NzbDrone.Common.Instrumentation
new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// 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);

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -313,7 +313,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
processInfo.StartPath = process.MainModule.FileName;
processInfo.StartPath = process.MainModule?.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited)
{

View File

@@ -200,17 +200,9 @@ namespace NzbDrone.Core.Test.Download
var seriesTags = new HashSet<int> { 2 };
var clientTags = new HashSet<int> { 1 };
WithTorrentClient(0, clientTags);
WithTorrentClient(0, clientTags);
WithTorrentClient(0, clientTags);
WithTorrentClient(0, clientTags);
var client1 = 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();
Assert.Throws<DownloadClientUnavailableException>(() => Subject.GetDownloadClient(DownloadProtocol.Torrent, 0, false, seriesTags));
}
[Test]

View File

@@ -312,11 +312,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
[Test]
public void should_return_status_with_outputdirs()
{
var configItems = new Dictionary<string, object>();
configItems.Add("download_location", @"C:\Downloads\Downloading\deluge".AsOsAgnostic());
configItems.Add("move_completed_path", @"C:\Downloads\Finished\deluge".AsOsAgnostic());
configItems.Add("move_completed", true);
var configItems = new Dictionary<string, object>
{
{ "download_location", @"C:\Downloads\Downloading\deluge".AsOsAgnostic() },
{ "move_completed_path", @"C:\Downloads\Finished\deluge".AsOsAgnostic() },
{ "move_completed", true }
};
Mocker.GetMock<IDelugeProxy>()
.Setup(v => v.GetConfig(It.IsAny<DelugeSettings>()))
@@ -328,5 +329,18 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
result.OutputRootFolders.Should().NotBeNull();
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());
}
}
}

View File

@@ -178,8 +178,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
VerifyWarning(item);
}
[Test]
public void paused_item_should_have_required_properties()
[TestCase("pausedDL")]
[TestCase("stoppedDL")]
public void paused_item_should_have_required_properties(string state)
{
var torrent = new QBittorrentTorrent
{
@@ -188,7 +189,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000,
Progress = 0.7,
Eta = 8640000,
State = "pausedDL",
State = state,
Label = "",
SavePath = ""
};
@@ -200,6 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
[TestCase("queuedUP")]
[TestCase("uploading")]
[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"));
}
[Test]
public void api_261_should_use_content_path()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void api_261_should_use_content_path(string state)
{
var torrent = new QBittorrentTorrent
{
@@ -407,7 +410,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000,
Progress = 0.7,
Eta = 8640000,
State = "pausedUP",
State = state,
Label = "",
SavePath = @"C:\Torrents".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());
}
[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]
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();
}
[Test]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set(string state)
{
GivenGlobalSeedLimits(-1);
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
GivenCompletedTorrent(state, ratio: 1.0f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused(string state)
{
GivenGlobalSeedLimits(1.0f);
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
GivenCompletedTorrent(state, ratio: 1.0f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
{
GivenGlobalSeedLimits(2.0f);
GivenCompletedTorrent("pausedUP", ratio: 1.0f, ratioLimit: 0.8f);
GivenCompletedTorrent(state, ratio: 1.0f, ratioLimit: 0.8f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
{
GivenGlobalSeedLimits(0.2f);
GivenCompletedTorrent("pausedUP", ratio: 0.5f, ratioLimit: 0.8f);
GivenCompletedTorrent(state, ratio: 0.5f, ratioLimit: 0.8f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
@@ -711,33 +746,36 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused(string state)
{
GivenGlobalSeedLimits(-1, 20);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused(string state)
{
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();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused(string state)
{
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();
item.CanBeRemoved.Should().BeFalse();
@@ -755,66 +793,72 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused()
[TestCase("pausedUP")]
[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);
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();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused()
[TestCase("pausedUP")]
[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);
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();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused(string state)
{
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();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
[TestCase("pausedUP")]
[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);
GivenCompletedTorrent("pausedUP", ratio: 1.0f, seedingTime: 30);
GivenCompletedTorrent(state, ratio: 1.0f, seedingTime: 30);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused()
[TestCase("pausedUP")]
[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);
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();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_not_fetch_details_twice()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_not_fetch_details_twice(string state)
{
GivenGlobalSeedLimits(-1, 30);
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
var item = Subject.GetItems().Single();
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());
}
[Test]
public void should_get_category_from_the_category_if_set()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_get_category_from_the_category_if_set(string state)
{
const string category = "music-readarr";
GivenGlobalSeedLimits(1.0f);
@@ -839,7 +884,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000,
Progress = 1.0,
Eta = 8640000,
State = "pausedUP",
State = state,
Category = category,
SavePath = "",
Ratio = 1.0f
@@ -851,8 +896,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.Category.Should().Be(category);
}
[Test]
public void should_get_category_from_the_label_if_the_category_is_not_available()
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_get_category_from_the_label_if_the_category_is_not_available(string state)
{
const string category = "music-readarr";
GivenGlobalSeedLimits(1.0f);
@@ -864,7 +910,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000,
Progress = 1.0,
Eta = 8640000,
State = "pausedUP",
State = state,
Label = category,
SavePath = "",
Ratio = 1.0f

View File

@@ -478,6 +478,37 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
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", @"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")]

View File

@@ -49,10 +49,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
}
[Test]
public void magnet_download_should_not_return_the_item()
public void magnet_download_should_be_returned_as_queued()
{
PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
var item = Subject.GetItems().Single();
item.Status.Should().Be(DownloadItemStatus.Queued);
}
[Test]

View File

@@ -60,7 +60,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
public void magnet_download_should_not_return_the_item()
{
PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
var item = Subject.GetItems().Single();
item.Status.Should().Be(DownloadItemStatus.Queued);
}
[Test]

View File

@@ -7,6 +7,7 @@ using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
@@ -21,28 +22,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.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]
public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled()
{
PosixOnly();
const string startupFolder = @"/opt/nzbdrone";
var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
@@ -62,10 +45,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
{
PosixOnly();
const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI";
var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
var uiFolder = @"C:\NzbDrone\UI".AsOsAgnostic();
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
@@ -89,7 +70,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
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>()
.Setup(s => s.UpdateAutomatically)
@@ -101,7 +82,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
.Returns(@"/opt/nzbdrone");
.Returns(startupFolder);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never());

View File

@@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{
[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>
{
private MetadataProfile _metadataProfile;

View File

@@ -15,7 +15,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{
[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>
{
[SetUp]

View File

@@ -38,9 +38,9 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
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("9780439554930", 48517161)]
[TestCase("9780439554930", 3)]
public void successful_book_search(string title, int expected)
{
var result = Subject.Search(title);

View File

@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.QueueTests
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
.All()
.With(v => v.IsTrackable = true)
.With(v => v.DownloadItem = downloadItem)
.With(v => v.RemoteBook = remoteBook)
.Build()

View File

@@ -0,0 +1,8 @@
namespace NzbDrone.Core.Authentication
{
public enum AuthenticationRequiredType
{
Enabled = 0,
DisabledForLocalAddresses = 1
}
}

View File

@@ -1,9 +1,10 @@
namespace NzbDrone.Core.Authentication
namespace NzbDrone.Core.Authentication
{
public enum AuthenticationType
{
None = 0,
Basic = 1,
Forms = 2
Forms = 2,
External = 3
}
}

View File

@@ -15,18 +15,18 @@ namespace NzbDrone.Core.Books
public class BookCutoffService : IBookCutoffService
{
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;
_profileService = profileService;
_qualityProfileService = qualityProfileService;
}
public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec)
{
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
var profiles = _profileService.All();
var profiles = _qualityProfileService.All();
//Get all items less than the cutoff
foreach (var profile in profiles)

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Datastore;
@@ -32,6 +33,7 @@ namespace NzbDrone.Core.Configuration
bool EnableSsl { get; }
bool LaunchBrowser { get; }
AuthenticationType AuthenticationMethod { get; }
AuthenticationRequiredType AuthenticationRequired { get; }
bool AnalyticsEnabled { get; }
string LogLevel { get; }
string ConsoleLogLevel { get; }
@@ -51,6 +53,7 @@ namespace NzbDrone.Core.Configuration
string SyslogServer { get; }
int SyslogPort { get; }
string SyslogLevel { get; }
string Theme { get; }
string PostgresHost { get; }
int PostgresPort { get; }
string PostgresUser { get; }
@@ -58,7 +61,7 @@ namespace NzbDrone.Core.Configuration
string PostgresMainDb { get; }
string PostgresLogDb { get; }
string PostgresCacheDb { get; }
string Theme { get; }
bool TrustCgnatIpAddresses { get; }
}
public class ConfigFileProvider : IConfigFileProvider
@@ -69,6 +72,11 @@ namespace NzbDrone.Core.Configuration
private readonly IDiskProvider _diskProvider;
private readonly ICached<string> _cache;
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;
@@ -78,13 +86,23 @@ namespace NzbDrone.Core.Configuration
ICacheManager cacheManager,
IEventAggregator eventAggregator,
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());
_eventAggregator = eventAggregator;
_diskProvider = diskProvider;
_configFile = appFolderInfo.GetConfigPath();
_postgresOptions = postgresOptions.Value;
_authOptions = authOptions.Value;
_appOptions = appOptions.Value;
_serverOptions = serverOptions.Value;
_updateOptions = updateOptions.Value;
_logOptions = logOptions.Value;
}
public Dictionary<string, object> GetConfigDictionary()
@@ -140,7 +158,7 @@ namespace NzbDrone.Core.Configuration
{
const string defaultValue = "*";
var bindAddress = GetValue("BindAddress", defaultValue);
var bindAddress = _serverOptions.BindAddress ?? GetValue("BindAddress", defaultValue);
if (string.IsNullOrWhiteSpace(bindAddress))
{
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
{
get
{
var apiKey = GetValue("ApiKey", GenerateApiKey());
var apiKey = _authOptions.ApiKey ?? GetValue("ApiKey", GenerateApiKey());
if (apiKey.IsNullOrWhiteSpace())
{
@@ -178,7 +196,7 @@ namespace NzbDrone.Core.Configuration
{
get
{
var enabled = GetValueBoolean("AuthenticationEnabled", false, false);
var enabled = _authOptions.Enabled ?? GetValueBoolean("AuthenticationEnabled", false, false);
if (enabled)
{
@@ -186,17 +204,24 @@ namespace NzbDrone.Core.Configuration
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
public string Branch => GetValue("Branch", "develop").ToLowerInvariant();
public string Branch => _updateOptions.Branch ?? GetValue("Branch", "develop").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info");
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "debug").ToLowerInvariant();
public string ConsoleLogLevel => _logOptions.ConsoleLevel ?? GetValue("ConsoleLogLevel", 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);
@@ -206,18 +231,18 @@ namespace NzbDrone.Core.Configuration
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 string Theme => GetValue("Theme", "auto", persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => GetValue("SslCertPath", "");
public string SslCertPassword => GetValue("SslCertPassword", "");
public string Theme => _appOptions.Theme ?? GetValue("Theme", "auto", persist: false);
public bool LogSql => _logOptions.Sql ?? GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => _logOptions.Rotate ?? GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => _logOptions.FilterSentryEvents ?? GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => _serverOptions.SslCertPath ?? GetValue("SslCertPath", "");
public string SslCertPassword => _serverOptions.SslCertPassword ?? GetValue("SslCertPassword", "");
public string UrlBase
{
get
{
var urlBase = GetValue("UrlBase", "").Trim('/');
var urlBase = (_serverOptions.UrlBase ?? GetValue("UrlBase", "")).Trim('/');
if (urlBase.IsNullOrWhiteSpace())
{
@@ -229,19 +254,22 @@ namespace NzbDrone.Core.Configuration
}
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)
{
@@ -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 (EnableSsl && (GetValue("SslCertHash", null).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
if (EnableSsl && (GetValue("SslCertHash", string.Empty, false).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
{
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.");
}
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"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
var newXDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
newXDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc;
return newXDoc;
}
}
catch (XmlException ex)
@@ -427,5 +463,7 @@ namespace NzbDrone.Core.Configuration
{
SetValue("ApiKey", GenerateApiKey());
}
public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false);
}
}

View File

@@ -404,6 +404,12 @@ namespace NzbDrone.Core.Configuration
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
public bool TrustCgnatIpAddresses
{
get { return GetValueBoolean("TrustCgnatIpAddresses", false); }
set { SetValue("TrustCgnatIpAddresses", value); }
}
private string GetValue(string key)
{
return GetValue(key, string.Empty);

View File

@@ -219,7 +219,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
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 tableDefinitionList = new List<TableDefinition>();

View File

@@ -122,14 +122,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
}
var items = new List<DownloadClientItem>();
var ignoredCount = 0;
foreach (var torrent in torrents)
{
if (torrent.Hash == null)
// Silently ignore torrents with no hash
if (torrent.Hash.IsNullOrWhiteSpace())
{
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();
item.DownloadId = torrent.Hash.ToUpper();
item.Title = torrent.Name;
@@ -187,6 +196,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
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;
}
@@ -199,9 +213,18 @@ namespace NzbDrone.Core.Download.Clients.Deluge
{
var config = _proxy.GetConfig(Settings);
var label = _proxy.GetLabelOptions(Settings);
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
destDir = new OsPath(label.MoveCompletedPath);
@@ -217,7 +240,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
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)

View File

@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
// 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).
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)
{
@@ -248,7 +248,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
item.Message = "qBittorrent is reporting an error";
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;
break;
@@ -259,7 +260,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
item.Status = DownloadItemStatus.Queued;
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 "stalledUP": // torrent is being seeded, but no connection were made
case "queuedUP": // queuing is enabled and torrent is queued for upload
@@ -279,6 +281,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break;
case "metaDL": // torrent magnet is being downloaded
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
if (config.DhtEnabled)
{
item.Status = DownloadItemStatus.Queued;
@@ -293,7 +296,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break;
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 "downloading": // torrent is being downloaded and data is being transferred
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())
{
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)
{

View File

@@ -26,8 +26,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, 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);
}

View File

@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{
request.AddFormParameter("paused", false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{
request.AddFormParameter("paused", true);
}
@@ -178,7 +178,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{
request.AddFormParameter("paused", false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{
request.AddFormParameter("paused", true);
}
@@ -214,7 +214,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex)
{
// 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")
.Post()
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex)
{
// 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;
}
@@ -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)
{
var request = BuildRequest(settings).Resource("/command/setForceStart")

View File

@@ -246,14 +246,20 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
request.AddFormParameter("category", settings.MusicCategory);
}
// Note: ForceStart is handled by separate api call
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
// Avoid extraneous API version check if initial state is ForceStart
if ((QBittorrentState)settings.InitialState is QBittorrentState.Start or QBittorrentState.Stop)
{
request.AddFormParameter("paused", false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
{
request.AddFormParameter("paused", true);
var stoppedParameterName = GetApiVersion(settings) >= new Version(2, 11, 0) ? "stopped" : "paused";
// Note: ForceStart is handled by separate api call
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
{
request.AddFormParameter(stoppedParameterName, false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{
request.AddFormParameter(stoppedParameterName, true);
}
}
if (settings.SequentialOrder)
@@ -291,7 +297,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
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
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;
}
@@ -313,7 +319,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex)
{
// 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;
}
@@ -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)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart")

View File

@@ -1,9 +1,16 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Download.Clients.QBittorrent
{
public enum QBittorrentState
{
[FieldOption(Label = "Started")]
Start = 0,
[FieldOption(Label = "Force Started")]
ForceStart = 1,
Pause = 2
[FieldOption(Label = "Stopped")]
Stop = 2
}
}

View File

@@ -263,20 +263,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
}
if (config.Misc.history_retention.IsNullOrWhiteSpace())
{
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";
}
status.RemovesCompletedDownloads = RemovesCompletedDownloads(config);
return status;
}
@@ -518,6 +505,43 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
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)
{
var downloadItemOutputPath = downloadClientItem.OutputPath;

View File

@@ -30,6 +30,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public bool enable_date_sorting { get; set; }
public bool pre_check { 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

View File

@@ -41,12 +41,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
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);
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
@@ -97,6 +91,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString;
}
else if (torrent.TotalSize == 0)
{
item.Status = DownloadItemStatus.Queued;
}
else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
torrent.Status == TransmissionTorrentStatus.Seeding ||
torrent.Status == TransmissionTorrentStatus.SeedingWait))

View File

@@ -11,8 +11,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public bool IsFinished { get; set; }
public long Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; }
public int SecondsDownloading { get; set; }
public int SecondsSeeding { get; set; }
public long SecondsDownloading { get; set; }
public long SecondsSeeding { get; set; }
public string ErrorString { get; set; }
public long DownloadedEver { get; set; }
public long UploadedEver { get; set; }

View File

@@ -41,18 +41,23 @@ namespace NzbDrone.Core.Download
var blockedProviders = new HashSet<int>(_downloadClientStatusService.GetBlockedProviders().Select(v => v.ProviderId));
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();
availableProviders = matchingTagsClients.Count > 0 ?
matchingTagsClients :
availableProviders.Where(i => i.Definition.Tags.Empty()).ToList();
}
if (!availableProviders.Any())
{
return null;
if (!availableProviders.Any())
{
throw new DownloadClientUnavailableException("No download client was found without tags or a matching author tag. Please check your settings.");
}
}
if (indexerId > 0)

View File

@@ -39,7 +39,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI");
if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) &&
if (_configFileProvider.UpdateAutomatically &&
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
!_osInfo.IsDocker)
{

View File

@@ -184,7 +184,7 @@ namespace NzbDrone.Core.ImportLists
report.BookGoodreadsId = remoteBook.ForeignBookId;
report.Book = remoteBook.Title;
report.Author ??= remoteBook.AuthorMetadata.Value.Name;
report.AuthorGoodreadsId ??= remoteBook.AuthorMetadata.Value.Name;
report.AuthorGoodreadsId ??= remoteBook.AuthorMetadata.Value.ForeignAuthorId;
}
catch (BookNotFoundException)
{

View File

@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{
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]);
return false;
}
else
{
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
}
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
}
return true;

View File

@@ -268,26 +268,26 @@ namespace NzbDrone.Core.Indexers
protected virtual RssEnclosure[] GetEnclosures(XElement item)
{
var enclosures = item.Elements("enclosure")
.Select(v =>
{
try
{
return new RssEnclosure
{
Url = v.Attribute("url")?.Value,
Type = v.Attribute("type")?.Value,
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
};
}
catch (Exception e)
{
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title());
}
.Select(v =>
{
try
{
return new RssEnclosure
{
Url = v.Attribute("url")?.Value,
Type = v.Attribute("type")?.Value,
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
};
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
}
return null;
})
.Where(v => v != null)
.ToArray();
return null;
})
.Where(v => v != null)
.ToArray();
return enclosures;
}

View File

@@ -48,10 +48,10 @@ namespace NzbDrone.Core.Indexers
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; }
[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; }
[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)]

View File

@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{
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]);
return false;
}
else
{
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
}
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
}
return true;

View File

@@ -57,33 +57,36 @@ namespace NzbDrone.Core.Instrumentation
{
try
{
var log = new Log();
log.Time = logEvent.TimeStamp;
log.Message = CleanseLogMessage.Cleanse(logEvent.FormattedMessage);
log.Logger = logEvent.LoggerName;
var log = new Log
{
Time = logEvent.TimeStamp,
Logger = logEvent.LoggerName,
Level = logEvent.Level.Name
};
if (log.Logger.StartsWith("NzbDrone."))
{
log.Logger = log.Logger.Remove(0, 9);
}
var message = logEvent.FormattedMessage;
if (logEvent.Exception != null)
{
if (string.IsNullOrWhiteSpace(log.Message))
if (string.IsNullOrWhiteSpace(message))
{
log.Message = logEvent.Exception.Message;
message = logEvent.Exception.Message;
}
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.Level = logEvent.Level.Name;
log.Message = CleanseLogMessage.Cleanse(message);
var connectionInfo = _connectionStringFactory.LogDbConnection;

View File

@@ -3,8 +3,8 @@
"Year": "عام",
"WeekColumnHeader": "رأس عمود الأسبوع",
"Version": "الإصدار",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "فرع لاستخدامه لتحديث Radarr",
"BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
"BranchUpdate": "فرع لاستخدامه لتحديث {appName}",
"Username": "اسم المستخدم",
"UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet",
"UsenetDelay": "تأخير يوزنت",
@@ -15,7 +15,7 @@
"UpgradeAllowedHelpText": "إذا لن تتم ترقية الصفات المعوقين",
"Updates": "التحديثات",
"UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث",
"UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في Radarr",
"UpdateMechanismHelpText": "استخدم المحدث أو البرنامج النصي المدمج في {appName}",
"UpdateAutomaticallyHelpText": "تنزيل التحديثات وتثبيتها تلقائيًا. ستظل قادرًا على التثبيت من النظام: التحديثات",
"UpdateAll": "تحديث الجميع",
"UnmonitoredHelpText": "قم بتضمين الأفلام غير الخاضعة للرقابة في موجز iCal",
@@ -56,7 +56,7 @@
"URLBase": "قاعدة URL",
"UISettings": "إعدادات واجهة المستخدم",
"UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح",
"UILanguageHelpText": "اللغة التي سيستخدمها Radarr لواجهة المستخدم",
"UILanguageHelpText": "اللغة التي سيستخدمها {appName} لواجهة المستخدم",
"UILanguage": "لغة واجهة المستخدم",
"TotalFileSize": "إجمالي حجم الملف",
"Torrents": "السيول",
@@ -73,7 +73,7 @@
"Tags": "العلامات",
"TagIsNotUsedAndCanBeDeleted": "العلامة غير مستخدمة ويمكن حذفها",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "سيتم استخدامها عند استخدام البحث التفاعلي",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Radarr",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة {appName}",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "البحث غير معتمد مع هذا المفهرس",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس",
"SuccessMyWorkIsDoneNoFilesToRetag": "نجاح! تم الانتهاء من عملي ، ولا توجد ملفات لإعادة تسميتها.",
@@ -92,7 +92,7 @@
"Source": "مصدر",
"SorryThatBookCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.",
"SorryThatAuthorCannotBeFound": "آسف ، لا يمكن العثور على هذا الفيلم.",
"SkipFreeSpaceCheckWhenImportingHelpText": "استخدم عندما يتعذر على Radarr اكتشاف مساحة خالية من مجلد جذر الفيلم",
"SkipFreeSpaceCheckWhenImportingHelpText": "استخدم عندما يتعذر على {appName} اكتشاف مساحة خالية من مجلد جذر الفيلم",
"SkipFreeSpaceCheck": "تخطي فحص المساحة الخالية",
"Size": " بحجم",
"ShownAboveEachColumnWhenWeekIsTheActiveView": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
@@ -133,19 +133,19 @@
"Result": "نتيجة",
"RestoreBackup": "استرجاع النسخة الاحتياطية",
"Restore": "استعادة",
"RestartReadarr": "أعد تشغيل Radarr",
"RestartReadarr": "أعد تشغيل {appName}",
"RestartNow": "اعد البدء الان",
"Restart": "إعادة تشغيل",
"ResetAPIKeyMessageText": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟",
"ResetAPIKey": "إعادة تعيين مفتاح API",
"Reset": "إعادة تعيين",
"RescanAuthorFolderAfterRefresh": "إعادة فحص مجلد الفيلم بعد التحديث",
"RescanAfterRefreshHelpTextWarning": "لن يكتشف Radarr تلقائيًا التغييرات التي تطرأ على الملفات عند عدم تعيينه على \"دائمًا\"",
"RescanAfterRefreshHelpTextWarning": "لن يكتشف {appName} تلقائيًا التغييرات التي تطرأ على الملفات عند عدم تعيينه على \"دائمًا\"",
"RequiredPlaceHolder": "أضف قيدًا جديدًا",
"RequiredHelpText": "يجب أن يحتوي الإصدار على واحد على الأقل من هذه المصطلحات (غير حساس لحالة الأحرف)",
"ReplaceIllegalCharacters": "استبدل الأحرف غير القانونية",
"Reorder": "إعادة ترتيب",
"RenameBooksHelpText": "سيستخدم Radarr اسم الملف الحالي إذا تم تعطيل إعادة التسمية",
"RenameBooksHelpText": "سيستخدم {appName} اسم الملف الحالي إذا تم تعطيل إعادة التسمية",
"RemovedFromTaskQueue": "تمت إزالته من قائمة انتظار المهام",
"RemoveTagRemovingTag": "إزالة العلامة",
"RemoveTagExistingTag": "علامة موجودة",
@@ -174,7 +174,7 @@
"Reason": "السبب",
"Real": "حقيقة",
"ReadarrTags": "العلامات الرادار",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "يدعم Radarr أي مفهرس يستخدم معيار Newznab ، بالإضافة إلى مفهرسات أخرى مذكورة أدناه.",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "يدعم {appName} أي مفهرس يستخدم معيار Newznab ، بالإضافة إلى مفهرسات أخرى مذكورة أدناه.",
"ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات",
"RSSSyncInterval": "الفاصل الزمني لمزامنة RSS",
"RSSSync": "مزامنة RSS",
@@ -196,7 +196,7 @@
"Profiles": "مظهر",
"PreviewRename": "معاينة إعادة تسمية",
"PosterSize": "حجم الملصق",
"GrabReleaseMessageText": "لم يتمكن Radarr من تحديد الفيلم الذي كان هذا الإصدار من أجله. قد يتعذر على Radarr استيراد هذا الإصدار تلقائيًا. هل تريد انتزاع \"{0}\"؟",
"GrabReleaseMessageText": "لم يتمكن {appName} من تحديد الفيلم الذي كان هذا الإصدار من أجله. قد يتعذر على {appName} استيراد هذا الإصدار تلقائيًا. هل تريد انتزاع \"{0}\"؟",
"GrabRelease": "انتزاع الإصدار",
"GrabID": "انتزاع معرف",
"Grab": "إختطاف",
@@ -276,10 +276,10 @@
"Dates": "تواريخ",
"DatabaseMigration": "ترحيل DB",
"CutoffUnmet": "قطع غير ملباة",
"CutoffHelpText": "بمجرد الوصول إلى هذه الجودة ، لن يقوم Radarr بتنزيل الأفلام",
"CutoffHelpText": "بمجرد الوصول إلى هذه الجودة ، لن يقوم {appName} بتنزيل الأفلام",
"CreateGroup": "إنشاء مجموعة",
"CreateEmptyAuthorFoldersHelpText": "قم بإنشاء مجلدات فيلم مفقودة أثناء فحص القرص",
"CopyUsingHardlinksHelpTextWarning": "من حين لآخر ، قد تمنع أقفال الملفات إعادة تسمية الملفات التي يتم زرعها. يمكنك تعطيل البذر مؤقتًا واستخدام وظيفة إعادة تسمية Radarr كحل بديل.",
"CopyUsingHardlinksHelpTextWarning": "من حين لآخر ، قد تمنع أقفال الملفات إعادة تسمية الملفات التي يتم زرعها. يمكنك تعطيل البذر مؤقتًا واستخدام وظيفة إعادة تسمية {appName} كحل بديل.",
"CopyUsingHardlinksHelpText": "استخدم Hardlinks عند محاولة نسخ الملفات من السيول التي لا تزال تحت البذور",
"Connections": "روابط",
"ConnectSettings": "ربط الإعدادات",
@@ -291,16 +291,16 @@
"ClientPriority": "أولوية العميل",
"ClickToChangeQuality": "انقر لتغيير الجودة",
"Clear": "واضح",
"ChownGroupHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل Radarr هو مالك الملف. من الأفضل التأكد من أن عميل التنزيل يستخدم نفس مجموعة Radarr.",
"ChownGroupHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل {appName} هو مالك الملف. من الأفضل التأكد من أن عميل التنزيل يستخدم نفس مجموعة {appName}.",
"ChownGroupHelpText": "اسم المجموعة أو gid. استخدم gid لأنظمة الملفات البعيدة.",
"ChmodFolderHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل Radarr هو مالك الملف. من الأفضل التأكد من قيام عميل التنزيل بتعيين الأذونات بشكل صحيح.",
"ChmodFolderHelpTextWarning": "يعمل هذا فقط إذا كان المستخدم الذي يقوم بتشغيل {appName} هو مالك الملف. من الأفضل التأكد من قيام عميل التنزيل بتعيين الأذونات بشكل صحيح.",
"ChmodFolderHelpText": "Octal ، يتم تطبيقه أثناء الاستيراد / إعادة التسمية إلى مجلدات وملفات الوسائط (بدون تنفيذ بت)",
"ChmodFolder": "مجلد chmod",
"ChangeHasNotBeenSavedYet": "لم يتم حفظ التغيير بعد",
"ChangeFileDate": "تغيير تاريخ الملف",
"CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS",
"CertificateValidation": "التحقق من صحة الشهادة",
"CancelMessageText": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
"CancelPendingTask": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
"Cancel": "إلغاء",
"CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
"Calendar": "التقويم",
@@ -314,17 +314,17 @@
"Backups": "النسخ الاحتياطية",
"BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا",
"BackupNow": "اعمل نسخة احتياطية الان",
"BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ Radarr",
"BackupFolderHelpText": "ستكون المسارات النسبية ضمن دليل AppData الخاص بـ {appName}",
"Automatic": "تلقائي",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "الأفلام المحذوفة من القرص لا يتم مراقبتها تلقائيًا في Radarr",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "الأفلام المحذوفة من القرص لا يتم مراقبتها تلقائيًا في {appName}",
"AutoRedownloadFailedHelpText": "ابحث تلقائيًا عن إصدار مختلف وحاول تنزيله",
"AuthorClickToChangeBook": "انقر لتغيير الفيلم",
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Radarr",
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى {appName}",
"Authentication": "المصادقة",
"ApplyTags": "تطبيق العلامات",
"AppDataDirectory": "دليل AppData",
"AnalyticsEnabledHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم Radarr. يتضمن ذلك معلومات حول متصفحك ، وصفحات Radarr WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.",
"AnalyticsEnabledHelpText": "إرسال معلومات الاستخدام والخطأ المجهولة إلى خوادم {appName}. يتضمن ذلك معلومات حول متصفحك ، وصفحات {appName} WebUI التي تستخدمها ، والإبلاغ عن الأخطاء بالإضافة إلى إصدار نظام التشغيل ووقت التشغيل. سنستخدم هذه المعلومات لتحديد أولويات الميزات وإصلاحات الأخطاء.",
"Analytics": "تحليلات",
"AlternateTitles": "عنوان بديل",
"AlreadyInYourLibrary": "بالفعل في مكتبتك",
@@ -332,7 +332,6 @@
"AddingTag": "مضيفا العلامة",
"AddListExclusion": "إضافة استبعاد قائمة",
"About": "نبدة عن",
"APIKey": "مفتاح API",
"60MinutesSixty": "60 دقيقة: {0}",
"45MinutesFourtyFive": "90 دقيقة: {0}",
"20MinutesTwenty": "120 دقيقة: {0}",
@@ -396,7 +395,7 @@
"LogFiles": "ملفات الدخول",
"Local": "محلي",
"LoadingBookFilesFailed": "فشل تحميل ملفات الفيلم",
"LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة Radarr الرئيسية عند بدء التطبيق.",
"LaunchBrowserHelpText": " افتح مستعرض ويب وانتقل إلى صفحة {appName} الرئيسية عند بدء التطبيق.",
"Language": "لغة",
"IsTagUsedCannotBeDeletedWhileInUse": "لا يمكن حذفه أثناء الاستخدام",
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "قم بالترقية حتى يتم تلبية هذه الجودة أو تجاوزها",
@@ -407,7 +406,7 @@
"IndexerPriority": "أولوية المفهرس",
"Indexer": "مفهرس",
"IncludeUnmonitored": "تضمين غير مراقب",
"IncludeUnknownAuthorItemsHelpText": "إظهار العناصر بدون فيلم في قائمة الانتظار. يمكن أن يشمل ذلك الأفلام التي تمت إزالتها أو أي شيء آخر في فئة Radarr",
"IncludeUnknownAuthorItemsHelpText": "إظهار العناصر بدون فيلم في قائمة الانتظار. يمكن أن يشمل ذلك الأفلام التي تمت إزالتها أو أي شيء آخر في فئة {appName}",
"IncludeHealthWarningsHelpText": "قم بتضمين التحذيرات الصحية",
"Importing": "استيراد",
"ImportedTo": "مستورد إلى",
@@ -430,7 +429,6 @@
"HasPendingChangesNoChanges": "لا تغيرات",
"Group": "مجموعة",
"GrabSelected": "انتزاع المحدد",
"ApiKeyHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
"LoadingBooksFailed": "فشل تحميل ملفات الفيلم",
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
@@ -439,7 +437,7 @@
"SslPortHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"DownloadClientCheckDownloadingToRoot": "يقوم برنامج التنزيل {0} بوضع التنزيلات في المجلد الجذر {1}. يجب ألا تقوم بالتنزيل إلى مجلد جذر.",
"Progress": "التقدم",
"ReplaceIllegalCharactersHelpText": "استبدل الأحرف غير القانونية. إذا لم يتم تحديده ، فسوف يقوم Radarr بإزالتها بدلاً من ذلك",
"ReplaceIllegalCharactersHelpText": "استبدل الأحرف غير القانونية. إذا لم يتم تحديده ، فسوف يقوم {appName} بإزالتها بدلاً من ذلك",
"ReleaseTitle": "عنوان الإصدار",
"Actions": "أجراءات",
"Tomorrow": "غدا",
@@ -454,12 +452,12 @@
"RemoveFromBlocklist": "إزالة من القائمة السوداء",
"UnableToLoadBlocklist": "تعذر تحميل القائمة السوداء",
"Level": "مستوى",
"ReleaseBranchCheckOfficialBranchMessage": "الفرع {0} ليس فرع إصدار Radarr صالح ، لن تتلقى تحديثات",
"ReleaseBranchCheckOfficialBranchMessage": "الفرع {0} ليس فرع إصدار {appName} صالح ، لن تتلقى تحديثات",
"Time": "زمن",
"Component": "مكون",
"Blocklist": "القائمة السوداء",
"BlocklistRelease": "إصدار القائمة السوداء",
"ThisCannotBeCancelled": "لا يمكن إلغاء هذا بمجرد البدء دون إعادة تشغيل Radarr.",
"ThisCannotBeCancelled": "لا يمكن إلغاء هذا بمجرد البدء دون إعادة تشغيل {appName}.",
"UnselectAll": "إلغاء تحديد الكل",
"UpdateSelected": "تم تحديد التحديث",
"Wanted": "مطلوب",
@@ -485,12 +483,12 @@
"Filters": "منقي",
"Connect": "الاتصال",
"HealthNoIssues": "لا مشاكل مع التكوين الخاص بك",
"MissingFromDisk": "لم يتمكن Whisparr من العثور على الملف على القرص لذا تمت إزالته",
"MissingFromDisk": "لم يتمكن {appName} من العثور على الملف على القرص لذا تمت إزالته",
"IndexerStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
"Lists": "القوائم",
"Metadata": "البيانات الوصفية",
"CreateEmptyAuthorFolders": "إنشاء مجلدات أفلام فارغة",
"ReadarrSupportsAnyDownloadClient": "يدعم Whisparr أي عميل تنزيل يستخدم معيار Newznab ، بالإضافة إلى عملاء التنزيل الآخرين المدرجة أدناه.",
"ReadarrSupportsAnyDownloadClient": "يدعم {appName} أي عميل تنزيل يستخدم معيار Newznab ، بالإضافة إلى عملاء التنزيل الآخرين المدرجة أدناه.",
"Queued": "في قائمة الانتظار",
"RefreshAndScan": "التحديث والمسح الضوئي",
"RescanAfterRefreshHelpText": "أعد فحص مجلد الفيلم بعد تحديث الفيلم",
@@ -509,10 +507,10 @@
"IndexerLongTermStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل لأكثر من 6 ساعات",
"IndexerLongTermStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات لأكثر من 6 ساعات: {0}",
"IndexerPriorityHelpText": "أولوية المفهرس من 1 (الأعلى) إلى 50 (الأدنى). الافتراضي: 25.",
"IndexerRssHealthCheckNoIndexers": "لا توجد مفهرسات متاحة مع تمكين مزامنة RSS ، ولن يحصل Radarr على الإصدارات الجديدة تلقائيًا",
"IndexerSearchCheckNoAutomaticMessage": "لا تتوفر مفهرسات مع تمكين البحث التلقائي ، ولن يقدم Radarr أي نتائج بحث تلقائية",
"IndexerRssHealthCheckNoIndexers": "لا توجد مفهرسات متاحة مع تمكين مزامنة RSS ، ولن يحصل {appName} على الإصدارات الجديدة تلقائيًا",
"IndexerSearchCheckNoAutomaticMessage": "لا تتوفر مفهرسات مع تمكين البحث التلقائي ، ولن يقدم {appName} أي نتائج بحث تلقائية",
"IndexerSearchCheckNoAvailableIndexersMessage": "جميع المفهرسات القادرة على البحث غير متوفرة مؤقتًا بسبب أخطاء المفهرس الأخيرة",
"IndexerSearchCheckNoInteractiveMessage": "لا تتوفر مفهرسات مع تمكين البحث التفاعلي ، ولن يقدم Radarr أي نتائج بحث تفاعلية",
"IndexerSearchCheckNoInteractiveMessage": "لا تتوفر مفهرسات مع تمكين البحث التفاعلي ، ولن يقدم {appName} أي نتائج بحث تفاعلية",
"IndexersSettingsSummary": "المفهرسات وقيود الإصدار",
"MediaManagement": "إدارة وسائل الإعلام",
"Monitor": "مراقب",
@@ -522,11 +520,11 @@
"ProxyCheckFailedToTestMessage": "فشل اختبار الوكيل: {0}",
"QualitySettingsSummary": "أحجام الجودة والتسمية",
"QueueIsEmpty": "قائمة الانتظار فارغة",
"RestartReloadNote": "ملاحظة: سيتم إعادة تشغيل Radarr تلقائيًا وإعادة تحميل واجهة المستخدم أثناء عملية الاستعادة.",
"RestartReloadNote": "ملاحظة: سيتم إعادة تشغيل {appName} تلقائيًا وإعادة تحميل واجهة المستخدم أثناء عملية الاستعادة.",
"RootFolderCheckSingleMessage": "مجلد الجذر مفقود: {0}",
"Save": "حفظ",
"SettingsRemotePathMappingLocalPath": "مسار محلي",
"SettingsRemotePathMappingLocalPathHelpText": "المسار الذي يجب أن يستخدمه Radarr للوصول إلى المسار البعيد محليًا",
"SettingsRemotePathMappingLocalPathHelpText": "المسار الذي يجب أن يستخدمه {appName} للوصول إلى المسار البعيد محليًا",
"SettingsRemotePathMappingRemotePath": "مسار بعيد",
"DownloadClientsSettingsSummary": "تنزيل العملاء وتنزيل المناولة وتعيينات المسار البعيد",
"DownloadClientStatusCheckAllClientMessage": "جميع عملاء التنزيل غير متاحين بسبب الفشل",
@@ -556,7 +554,7 @@
"CustomFormatSettings": "إعدادات التنسيقات المخصصة",
"CustomFormat": "تنسيق مخصص",
"CustomFormats": "تنسيقات مخصصة",
"CutoffFormatScoreHelpText": "بمجرد الوصول إلى درجة التنسيق المخصص هذه ، لن يقوم Radarr بتنزيل الأفلام",
"CutoffFormatScoreHelpText": "بمجرد الوصول إلى درجة التنسيق المخصص هذه ، لن يقوم {appName} بتنزيل الأفلام",
"DeleteCustomFormat": "حذف التنسيق المخصص",
"DeleteCustomFormatMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
"DeleteFormatMessageText": "هل تريد بالتأكيد حذف علامة التنسيق {0}؟",
@@ -585,7 +583,7 @@
"DeleteConditionMessageText": "هل أنت متأكد أنك تريد حذف العلامة \"{0}\"؟",
"RemoveSelectedItemsQueueMessageText": "هل تريد بالتأكيد إزالة {0} عنصر {1} من قائمة الانتظار؟",
"NoEventsFound": "لم يتم العثور على أحداث",
"BlocklistReleaseHelpText": "يمنع Radarr من الاستيلاء على هذا الإصدار تلقائيًا مرة أخرى",
"BlocklistReleaseHelpText": "يمنع {appName} من الاستيلاء على هذا الإصدار تلقائيًا مرة أخرى",
"ApplyTagsHelpTextRemove": "إزالة: قم بإزالة العلامات التي تم إدخالها",
"ApplyTagsHelpTextReplace": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)",
"DeleteSelectedDownloadClients": "حذف Download Client",
@@ -617,7 +615,7 @@
"NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}",
"SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق",
"ConnectionLost": "انقطع الاتصال",
"ConnectionLostReconnect": "سيحاول Radarr الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
"ConnectionLostReconnect": "سيحاول {appName} الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
"LastDuration": "المدة الماضية",
"Large": "كبير",
"WhatsNew": "ما هو الجديد؟",
@@ -638,5 +636,22 @@
"SelectQuality": "حدد الجودة",
"CustomFilter": "مرشحات مخصصة",
"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": "مدمج"
}

View File

@@ -1,5 +1,4 @@
{
"ApiKeyHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"Enable": "Активиране",
"MIA": "МВР",
"Size": " Размер",
@@ -7,7 +6,6 @@
"20MinutesTwenty": "120 минути: {0}",
"45MinutesFourtyFive": "90 минути: {0}",
"60MinutesSixty": "60 минути: {0}",
"APIKey": "API ключ",
"About": "относно",
"AddListExclusion": "Добавяне на изключване от списъка",
"AddingTag": "Добавяне на таг",
@@ -15,17 +13,17 @@
"AlreadyInYourLibrary": "Вече във вашата библиотека",
"AlternateTitles": "Алтернативно заглавие",
"Analytics": "Анализ",
"AnalyticsEnabledHelpText": "Изпращайте анонимна информация за използването и грешките до сървърите на Radarr. Това включва информация за вашия браузър, кои страници на Radarr WebUI използвате, отчитане на грешки, както и версията на операционната система и времето за изпълнение. Ще използваме тази информация, за да дадем приоритет на функциите и корекциите на грешки.",
"AnalyticsEnabledHelpText": "Изпращайте анонимна информация за използването и грешките до сървърите на {appName}. Това включва информация за вашия браузър, кои страници на {appName} WebUI използвате, отчитане на грешки, както и версията на операционната система и времето за изпълнение. Ще използваме тази информация, за да дадем приоритет на функциите и корекциите на грешки.",
"AnalyticsEnabledHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"AppDataDirectory": "Директория на AppData",
"ApplyTags": "Прилагане на тагове",
"Authentication": "Удостоверяване",
"AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до Radarr",
"AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до {appName}",
"AuthorClickToChangeBook": "Щракнете, за да промените филма",
"AutoRedownloadFailedHelpText": "Автоматично търсете и се опитвайте да изтеглите различна версия",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Изтритите от диска филми автоматично се проследяват в Radarr",
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Изтритите от диска филми автоматично се проследяват в {appName}",
"Automatic": "Автоматично",
"BackupFolderHelpText": "Относителните пътища ще бъдат в директорията AppData на Radarr",
"BackupFolderHelpText": "Относителните пътища ще бъдат в директорията AppData на {appName}",
"BackupNow": "Архивиране сега",
"BackupRetentionHelpText": "Автоматичните архиви, по-стари от периода на съхранение, ще бъдат почистени автоматично",
"Backups": "Архиви",
@@ -38,16 +36,16 @@
"Calendar": "Календар",
"CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед",
"Cancel": "Отказ",
"CancelMessageText": "Наистина ли искате да отмените тази чакаща задача?",
"CancelPendingTask": "Наистина ли искате да отмените тази чакаща задача?",
"CertificateValidation": "Валидиране на сертификат",
"CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране",
"ChangeFileDate": "Промяна на датата на файла",
"ChangeHasNotBeenSavedYet": "Промяната все още не е запазена",
"ChmodFolder": "chmod папка",
"ChmodFolderHelpText": "Осмично, приложено по време на импортиране / преименуване към медийни папки и файлове (без битове за изпълнение)",
"ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с Radarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.",
"ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с {appName}, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.",
"ChownGroupHelpText": "Име на група или gid. Използвайте gid за отдалечени файлови системи.",
"ChownGroupHelpTextWarning": "Това работи само ако потребителят, работещ с Radarr, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне използва същата група като Radarr.",
"ChownGroupHelpTextWarning": "Това работи само ако потребителят, работещ с {appName}, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне използва същата група като {appName}.",
"Clear": "Ясно",
"ClickToChangeQuality": "Щракнете, за да промените качеството",
"ClientPriority": "Приоритет на клиента",
@@ -59,10 +57,10 @@
"ConnectSettings": "Настройки за свързване",
"Connections": "Връзки",
"CopyUsingHardlinksHelpText": "Използвайте твърди връзки, когато се опитвате да копирате файлове от торенти, които все още се посяват",
"CopyUsingHardlinksHelpTextWarning": "Понякога заключванията на файлове могат да попречат на преименуване на файлове, които се засяват. Можете временно да деактивирате засяването и да използвате функцията за преименуване на Radarr като работа.",
"CopyUsingHardlinksHelpTextWarning": "Понякога заключванията на файлове могат да попречат на преименуване на файлове, които се засяват. Можете временно да деактивирате засяването и да използвате функцията за преименуване на {appName} като работа.",
"CreateEmptyAuthorFoldersHelpText": "Създайте липсващи папки с филми по време на сканиране на диска",
"CreateGroup": "Създай група",
"CutoffHelpText": "След достигане на това качество Radarr вече няма да изтегля филми",
"CutoffHelpText": "След достигане на това качество {appName} вече няма да изтегля филми",
"CutoffUnmet": "Прекъсване Неудовлетворено",
"DatabaseMigration": "DB миграция",
"Dates": "Дати",
@@ -141,7 +139,7 @@
"Grab": "Грабнете",
"GrabID": "Идентификатор на грабване",
"GrabRelease": "Grab Release",
"GrabReleaseMessageText": "Radarr не успя да определи за кой филм е предназначено това издание. Radarr може да не може автоматично да импортира тази версия. Искате ли да вземете „{0}“?",
"GrabReleaseMessageText": "{appName} не успя да определи за кой филм е предназначено това издание. {appName} може да не може автоматично да импортира тази версия. Искате ли да вземете „{0}“?",
"GrabSelected": "Grab Selected",
"Group": "Група",
"HasPendingChangesNoChanges": "Без промени",
@@ -164,7 +162,7 @@
"ImportedTo": "Внос в",
"Importing": "Импортиране",
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
"IncludeUnknownAuthorItemsHelpText": "Показване на елементи без филм в опашката. Това може да включва премахнати филми или нещо друго от категорията на Radarr",
"IncludeUnknownAuthorItemsHelpText": "Показване на елементи без филм в опашката. Това може да включва премахнати филми или нещо друго от категорията на {appName}",
"IncludeUnmonitored": "Включете Без наблюдение",
"Indexer": "Индексатор",
"IndexerPriority": "Индексатор Приоритет",
@@ -175,7 +173,7 @@
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Надстройте, докато това качество бъде постигнато или надвишено",
"IsTagUsedCannotBeDeletedWhileInUse": "Не може да се изтрие, докато се използва",
"Language": "Език",
"LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на Radarr при стартиране на приложението.",
"LaunchBrowserHelpText": " Отворете уеб браузър и отворете началната страница на {appName} при стартиране на приложението.",
"LoadingBookFilesFailed": "Зареждането на филмови файлове не бе успешно",
"Local": "Местен",
"LogFiles": "Лог файлове",
@@ -256,7 +254,7 @@
"RSSSync": "RSS синхронизиране",
"RSSSyncInterval": "RSS интервал за синхронизиране",
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr поддържа всеки индексатор, който използва стандарта Newznab, както и други индексатори, изброени по-долу.",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "{appName} поддържа всеки индексатор, който използва стандарта Newznab, както и други индексатори, изброени по-долу.",
"ReadarrTags": "Радарни маркери",
"Real": "Истински",
"Reason": "Причина",
@@ -285,19 +283,19 @@
"RemoveTagExistingTag": "Съществуващ маркер",
"RemoveTagRemovingTag": "Премахване на етикет",
"RemovedFromTaskQueue": "Премахнато от опашката на задачите",
"RenameBooksHelpText": "Radarr ще използва съществуващото име на файл, ако преименуването е деактивирано",
"RenameBooksHelpText": "{appName} ще използва съществуващото име на файл, ако преименуването е деактивирано",
"Reorder": "Пренареждане",
"ReplaceIllegalCharacters": "Заменете незаконните символи",
"RequiredHelpText": "Освобождаването трябва да съдържа поне един от тези термини (без регистрация)",
"RequiredPlaceHolder": "Добавете ново ограничение",
"RescanAfterRefreshHelpTextWarning": "Radarr няма автоматично да открива промени във файлове, когато не е зададено на „Винаги“",
"RescanAfterRefreshHelpTextWarning": "{appName} няма автоматично да открива промени във файлове, когато не е зададено на „Винаги“",
"RescanAuthorFolderAfterRefresh": "Повторно сканиране на папка за филм след опресняване",
"Reset": "Нулиране",
"ResetAPIKey": "Нулиране на API ключ",
"ResetAPIKeyMessageText": "Наистина ли искате да нулирате своя API ключ?",
"Restart": "Рестартирам",
"RestartNow": "Рестартирай сега",
"RestartReadarr": "Рестартирайте Radarr",
"RestartReadarr": "Рестартирайте {appName}",
"Restore": "Възстанови",
"RestoreBackup": "Възстанови архива",
"Result": "Резултат",
@@ -337,7 +335,7 @@
"ShowSizeOnDisk": "Показване на размера на диска",
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Показва се над всяка колона, когато седмицата е активният изглед",
"SkipFreeSpaceCheck": "Пропуснете проверката на свободното пространство",
"SkipFreeSpaceCheckWhenImportingHelpText": "Използвайте, когато Radarr не е в състояние да открие свободно място от основната папка на вашия филм",
"SkipFreeSpaceCheckWhenImportingHelpText": "Използвайте, когато {appName} не е в състояние да открие свободно място от основната папка на вашия филм",
"SorryThatAuthorCannotBeFound": "За съжаление този филм не може да бъде намерен.",
"SorryThatBookCannotBeFound": "За съжаление този филм не може да бъде намерен.",
"Source": "Източник",
@@ -355,7 +353,7 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Успех! Работата ми приключи, няма файлове за преименуване.",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Търсенето не се поддържа с този индексатор",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от {appName}",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Ще се използва, когато се използва интерактивно търсене",
"TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит",
"Tags": "Етикети",
@@ -372,7 +370,7 @@
"Torrents": "Торенти",
"TotalFileSize": "Общ размер на файла",
"UILanguage": "Език на потребителския интерфейс",
"UILanguageHelpText": "Език, който Radarr ще използва за потребителски интерфейс",
"UILanguageHelpText": "Език, който {appName} ще използва за потребителски интерфейс",
"UILanguageHelpTextWarning": "Необходимо е презареждане на браузъра",
"UISettings": "Настройки на потребителския интерфейс",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не може да се добави нов клиент за изтегляне, моля, опитайте отново.",
@@ -412,7 +410,7 @@
"UnmonitoredHelpText": "Включете непроменени филми в iCal емисията",
"UpdateAll": "Актуализирай всички",
"UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates",
"UpdateMechanismHelpText": "Използвайте вградения в Radarr актуализатор или скрипт",
"UpdateMechanismHelpText": "Използвайте вградения в {appName} актуализатор или скрипт",
"UpdateScriptPathHelpText": "Път към персонализиран скрипт, който взема извлечен пакет за актуализация и обработва останалата част от процеса на актуализация",
"Updates": "Актуализации",
"UpgradeAllowedHelpText": "Ако инвалидните качества няма да бъдат надградени",
@@ -424,8 +422,8 @@
"UsenetDelay": "Usenet Delay",
"UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet",
"Username": "Потребителско име",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Клон, който да се използва за актуализиране на Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
"BranchUpdate": "Клон, който да се използва за актуализиране на {appName}",
"BranchUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
"Version": "Версия",
"WeekColumnHeader": "Заглавка на колоната на седмицата",
"Year": "Година",
@@ -438,7 +436,7 @@
"UnableToLoadMetadataProfiles": "Профилите за забавяне не могат да се заредят",
"SslCertPathHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"DownloadClientCheckDownloadingToRoot": "Клиентът за изтегляне {0} поставя изтеглянията в основната папка {1}. Не трябва да изтегляте в основна папка.",
"ReplaceIllegalCharactersHelpText": "Заменете незаконните символи. Ако не е отметнато, Radarr ще ги премахне вместо това",
"ReplaceIllegalCharactersHelpText": "Заменете незаконните символи. Ако не е отметнато, {appName} ще ги премахне вместо това",
"Actions": "Действия",
"Tomorrow": "Утре",
"Today": "Днес",
@@ -455,12 +453,12 @@
"RemoveFromBlocklist": "Премахване от черния списък",
"Time": "Време",
"UnableToLoadBlocklist": "Черният списък не може да се зареди",
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Radarr, няма да получавате актуализации",
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на {appName}, няма да получавате актуализации",
"Blocklist": "Черен списък",
"BlocklistRelease": "Освобождаване на черния списък",
"SelectAll": "Избери всичко",
"SelectedCountBooksSelectedInterp": "{0} Избран / и филм / и",
"ThisCannotBeCancelled": "Това не може да бъде отменено след стартиране без рестартиране на Radarr.",
"ThisCannotBeCancelled": "Това не може да бъде отменено след стартиране без рестартиране на {appName}.",
"UnselectAll": "Деселектирайте всички",
"UpdateSelected": "Избрана актуализация",
"Wanted": "Издирва се",
@@ -488,12 +486,12 @@
"ImportListStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
"IndexerPriorityHelpText": "Приоритет на индексатора от 1 (най-висок) до 50 (най-нисък). По подразбиране: 25.",
"IndexerRssHealthCheckNoAvailableIndexers": "Всички rss-съвместими индексатори са временно недостъпни поради скорошни грешки в индексатора",
"IndexerRssHealthCheckNoIndexers": "Няма налични индексатори с активирана RSS синхронизация, Radarr няма да взема автоматично нови версии",
"IndexerRssHealthCheckNoIndexers": "Няма налични индексатори с активирана RSS синхронизация, {appName} няма да взема автоматично нови версии",
"Lists": "Списъци",
"QueueIsEmpty": "Опашката е празна",
"RefreshAndScan": "Опресняване и сканиране",
"RescanAfterRefreshHelpText": "Повторно сканиране на папката с филм след освежаване на филма",
"RestartReloadNote": "Забележка: Radarr автоматично ще рестартира и презареди потребителския интерфейс по време на процеса на възстановяване.",
"RestartReloadNote": "Забележка: {appName} автоматично ще рестартира и презареди потребителския интерфейс по време на процеса на възстановяване.",
"SearchFiltered": "Търсене Филтрирано",
"DownloadClientCheckUnableToCommunicateMessage": "Не може да се комуникира с {0}.",
"Connect": "Свържете",
@@ -503,10 +501,10 @@
"HealthNoIssues": "Няма проблеми с вашата конфигурация",
"DownloadClientCheckNoneAvailableMessage": "Няма наличен клиент за изтегляне",
"IndexersSettingsSummary": "Индексатори и ограничения за освобождаване",
"MissingFromDisk": "Whisparr не можа да намери файла на диска, така че той беше премахнат",
"MissingFromDisk": "{appName} не можа да намери файла на диска, така че той беше премахнат",
"MediaManagement": "Управление на медиите",
"Metadata": "Метаданни",
"ReadarrSupportsAnyDownloadClient": "Whisparr поддържа всеки клиент за изтегляне, който използва стандарта Newznab, както и други клиенти за изтегляне, изброени по-долу.",
"ReadarrSupportsAnyDownloadClient": "{appName} поддържа всеки клиент за изтегляне, който използва стандарта Newznab, както и други клиенти за изтегляне, изброени по-долу.",
"RootFolderCheckSingleMessage": "Липсваща основна папка: {0}",
"SettingsRemotePathMappingLocalPath": "Местен път",
"SettingsRemotePathMappingRemotePath": "Отдалечен път",
@@ -514,9 +512,9 @@
"Yesterday": "Вчера",
"Disabled": "хора с увреждания",
"IndexerLongTermStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки за повече от 6 часа",
"IndexerSearchCheckNoAutomaticMessage": "Няма налични индексатори с активирано автоматично търсене, Radarr няма да предоставя резултати от автоматично търсене",
"IndexerSearchCheckNoAutomaticMessage": "Няма налични индексатори с активирано автоматично търсене, {appName} няма да предоставя резултати от автоматично търсене",
"IndexerSearchCheckNoAvailableIndexersMessage": "Всички индексатори с възможност за търсене са временно недостъпни поради скорошни грешки в индексатора",
"IndexerSearchCheckNoInteractiveMessage": "Няма налични индексатори с активирано интерактивно търсене, Radarr няма да предоставя никакви интерактивни резултати от търсенето",
"IndexerSearchCheckNoInteractiveMessage": "Няма налични индексатори с активирано интерактивно търсене, {appName} няма да предоставя никакви интерактивни резултати от търсенето",
"IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки",
"IndexerStatusCheckSingleClientMessage": "Индексатори не са налични поради грешки: {0}",
"MaintenanceRelease": "Издание за поддръжка: поправки на грешки и други подобрения. Вижте История на комисиите на Github за повече подробности",
@@ -531,7 +529,7 @@
"ProxyCheckFailedToTestMessage": "Неуспешно тестване на прокси: {0}",
"ProxyCheckResolveIpMessage": "Неуспешно разрешаване на IP адреса за конфигурирания прокси хост {0}",
"Queued": "На опашка",
"SettingsRemotePathMappingLocalPathHelpText": "Път, който Radarr трябва да използва за локален достъп до отдалечения път",
"SettingsRemotePathMappingLocalPathHelpText": "Път, който {appName} трябва да използва за локален достъп до отдалечения път",
"SettingsRemotePathMappingRemotePathHelpText": "Основен път към директорията, до която клиентът за изтегляне има достъп",
"ShowUnknownAuthorItems": "Показване на непознати филмови елементи",
"SystemTimeCheckMessage": "Системното време е изключено с повече от 1 ден. Планираните задачи може да не се изпълняват правилно, докато времето не бъде коригирано",
@@ -556,7 +554,7 @@
"CustomFormat": "Персонализиран формат",
"CustomFormatSettings": "Настройки на персонализирани формати",
"CustomFormats": "Персонализирани формати",
"CutoffFormatScoreHelpText": "След като бъде постигнат резултатът от този персонализиран формат, Radarr вече няма да изтегля филми",
"CutoffFormatScoreHelpText": "След като бъде постигнат резултатът от този персонализиран формат, {appName} вече няма да изтегля филми",
"DeleteCustomFormat": "Изтриване на потребителски формат",
"DeleteFormatMessageText": "Наистина ли искате да изтриете маркер за формат {0}?",
"ExportCustomFormat": "Експортиране на потребителски формат",
@@ -606,7 +604,7 @@
"Small": "Малък",
"System": "Система",
"Ui": "Потребителски интерфейс",
"ConnectionLostReconnect": "Radarr ще се опита да се свърже автоматично или можете да щракнете върху презареждане по-долу.",
"ConnectionLostReconnect": "{appName} ще се опита да се свърже автоматично или можете да щракнете върху презареждане по-долу.",
"TotalSpace": "Общо пространство",
"Events": "Събития",
"Large": "Голям",
@@ -638,5 +636,22 @@
"CustomFilter": "Персонализирани филтри",
"RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?",
"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}"
}

View File

@@ -428,25 +428,25 @@
"BlocklistRelease": "Publicació de la llista de bloqueig",
"HasPendingChangesNoChanges": "Sense Canvis",
"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",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr",
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Radarr ja no baixarà pel·lícules",
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau de l'API?",
"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, {appName} ja no baixarà pel·lícules",
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau API?",
"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ó",
"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",
"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.",
"UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"ApplicationURL": "URL de l'aplicació",
"ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base",
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Radarr",
"CancelMessageText": "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.",
"ApplicationUrlHelpText": "URL extern de l'aplicació, inclòs http(s)://, port i URL base",
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del {appName}",
"CancelPendingTask": "Esteu segur que voleu cancel·lar aquesta tasca pendent?",
"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",
"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}'?",
@@ -454,26 +454,26 @@
"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ó.",
"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",
"IndexerRssHealthCheckNoIndexers": "No hi ha indexadors disponibles amb la sincronització RSS activada, Radarr 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",
"IndexerSearchCheckNoInteractiveMessage": "No hi ha indexadors amb la cerca interactiva activada, Radarr no obtindrà cap resultat de cerca",
"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, {appName} no capturarà les noves versions automàticament",
"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, {appName} no obtindrà cap resultat de cerca",
"IsCutoffUpgradeUntilThisQualityIsMetOrExceeded": "Actualitzeu fins que s'assoleixi o superi aquesta qualitat",
"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",
"NoHistory": "Sense història",
"OnBookFileDeleteForUpgradeHelpText": "Al suprimir el fitxer de pel·lícula per a l'actualització",
"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",
"RemotePathMappingCheckDownloadPermissions": "Radarr 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.",
"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.",
"ReplaceIllegalCharactersHelpText": "Substitueix caràcters il·legals. Si no es marca, Radarr els eliminarà",
"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ò {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ò {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, {appName} els eliminarà",
"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",
"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",
"ShowBookTitleHelpText": "Mostra el títol de la pel·lícula sota el cartell",
"ShowRelativeDates": "Mostra les dates relatives",
@@ -487,11 +487,11 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.",
"TimeLeft": "Temps restant",
"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",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
"BranchUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
"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",
"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.",
@@ -500,27 +500,25 @@
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "Això s'aplicarà a tots els indexadors, si us plau, seguiu les regles establertes per ells",
"UnableToLoadHistory": "No es pot carregar l'historial",
"IconTooltip": "Programat",
"ReadarrTags": "Etiquetes de Radarr",
"ReadarrTags": "Etiquetes de {appName}",
"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",
"MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ",
"APIKey": "Clau API",
"ApiKeyHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"RescanAfterRefreshHelpTextWarning": "Radarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
"RescanAfterRefreshHelpTextWarning": "{appName} no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
"ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda",
"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",
"UnableToAddANewImportListExclusionPleaseTryAgain": "No es pot afegir una nova llista d'exclusió, torneu-ho a provar.",
"UnableToLoadReleaseProfiles": "No es poden carregar els perfils de retard",
"UnmonitoredHelpText": "Inclou pel·lícules no monitorades al canal iCal",
"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.",
"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.",
"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ó.",
"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 {appName} com a solució.",
"CouldntFindAnyResultsForTerm": "No s'ha pogut trobar cap resultat per a '{0}'",
"CreateEmptyAuthorFolders": "Creeu carpetes buides per a les pel·lícules",
"CreateEmptyAuthorFoldersHelpText": "Creeu carpetes de pel·lícules que falten durant l'exploració del disc",
@@ -538,15 +536,15 @@
"MetadataProfiles": "perfil de metadades",
"OnBookFileDelete": "Al suprimir fitxer de pel·lícula",
"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.",
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr admet qualsevol indexador que utilitzi l'estàndard Newznab, així com altres indexadors que s'enumeren a continuació.",
"ReadarrSupportsAnyDownloadClient": "{appName} admet molts clients de baixada de torrent i usenet populars.",
"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",
"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)",
"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.",
"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",
"Database": "Base de dades",
"DeleteQualityProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat '{name}'?",
@@ -555,22 +553,22 @@
"DeleteRootFolderMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
"DeleteSelectedBookFiles": "Suprimeix 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",
"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",
"RemoveTagRemovingTag": "S'està eliminant l'etiqueta",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador",
"UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
"RequiredPlaceHolder": "Afegeix una nova restricció",
"20MinutesTwenty": "60 minuts: {0}",
"20MinutesTwenty": "20 minuts: {0}",
"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",
"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",
"45MinutesFourtyFive": "60 minuts: {0}",
"45MinutesFourtyFive": "45 minuts: {0}",
"60MinutesSixty": "60 minuts: {0}",
"BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"BookIsDownloading": "La pel·lícula s'està baixant",
@@ -586,7 +584,7 @@
"BypassIfHighestQuality": "Bypass si és de màxima qualitat",
"MinimumCustomFormatScore": "Puntuació mínima 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}",
"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",
@@ -609,7 +607,7 @@
"CustomFormat": "Format personalitzat",
"CustomFormatSettings": "Configuració de 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}'?",
"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.",
@@ -658,11 +656,11 @@
"Activity": "Activitat",
"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)",
"ApplyTagsHelpTextRemove": "Eliminar: elimina les etiquetes introduïdes",
"ApplyTagsHelpTextRemove": "Eliminació: elimina les etiquetes introduïdes",
"BlocklistReleases": "Llista de llançaments bloquejats",
"AutoAdd": "Afegeix automàticament",
"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)?",
"DeleteSelectedIndexers": "Suprimeix l'indexador(s)",
"DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?",
@@ -712,5 +710,91 @@
"DownloadClientDelugeSettingsDirectory": "Directori de baixada",
"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",
"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"
}

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