Compare commits

..

101 Commits

Author SHA1 Message Date
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
Bogdan f9454b5b5a Fixed: Initialize databases after app folder migrations
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-05-05 01:06:47 +03:00
Bogdan 9aa6d47349 Use newer Node.js task for in pipelines 2024-04-29 14:42:15 +03:00
Bogdan e09946d946 Fixed: Limit titles in task name to 10 authors
(cherry picked from commit c81ae6546118e954e481894d0b3fa6e9a20359c7)

Closes #3449
2024-04-28 13:55:18 +03:00
Stevie Robinson c9c5429120 New: Don't initially select 0 byte files in Interactive Import
(cherry picked from commit 04bd535cfca5e25c6a2d5417c6f18d5bf5180f67)

Closes #3448
2024-04-28 13:54:19 +03:00
Mark McDowall ed7bd6c66d Fixed: Improve paths longer than 256 on Windows failing to hardlink
(cherry picked from commit a97fbcc40a6247bf59678425cf460588fd4dbecd)
2024-04-28 13:52:17 +03:00
Christopher c88fe7cae8 New: Remove qBitorrent torrents that reach inactive seeding time
(cherry picked from commit d738035fed859eb475051f3df494b9c975a42e82)
2024-04-28 13:52:05 +03:00
Bogdan 68642579d0 Bump version to 0.3.26 2024-04-28 12:58:25 +03:00
Weblate f061d70d38 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fara <faraindahhh@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: maodun96 <435795439@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
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/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2024-04-27 21:13:42 +03:00
Bogdan fd4a609f51 Fixed: Retrying download on not suppressed HTTP errors 2024-04-27 21:09:36 +03:00
Bogdan 9957f734a5 Database corruption message linking to wiki 2024-04-25 11:30:34 +03:00
Bogdan 695b8b2ae1 Bump dotnet to 6.0.29 2024-04-22 09:32:41 +03:00
Bogdan 420824b279 Convert createTagsSelector to typescript 2024-04-21 12:48:48 +03:00
Servarr badc2567c3 Automated API Docs update 2024-04-21 12:44:49 +03:00
Bogdan c8c81927d9 Fixed: Re-testing edited providers will forcibly test them
(cherry picked from commit e9662544621b2d1fb133ff9d96d0eb20b8198725)

Closes #3432
2024-04-21 12:36:52 +03:00
Josh McKinney f9df843789 Add dev container workspace
Allows the linting and style settings for the frontend to be applied even when you load the main repo as a workspace

(cherry picked from commit d6278fced49b26be975c3a6039b38a94f700864b)

Closes #3428
2024-04-21 12:34:01 +03:00
Bogdan 3cd39d4ee8 Bump frontend dependencies 2024-04-21 12:30:55 +03:00
Bogdan 8a39ef4c56 Bump version to 0.3.25 2024-04-21 09:17:25 +03:00
Bogdan ba1195fc1b Fixed: Skip move when source and destination are the same
Co-authored-by: Qstick <qstick@gmail.com>
2024-04-20 18:00:00 +03:00
Bogdan 7656142db4 Bump SixLabors.ImageSharp to 3.1.4 2024-04-19 08:03:01 +03:00
Weblate 74c3b45ef8 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Gandrushka <andrew.pyndyk@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Jacopo Luca Maria Latrofa <jacopo.latrofa@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: toeiazarothis <patrickdealmeida89000@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translation: Servarr/Readarr
2024-04-18 20:00:38 +03:00
Josh McKinney f7368d3d09 Add DevContainer, VSCode config and extensions.json
(cherry picked from commit 5061dc4b5e5ea9925740496a5939a1762788b793)

Closes #3414
2024-04-14 08:35:27 +03:00
Bogdan 5d8e2300f2 Bump version to 0.3.24 2024-04-14 08:26:42 +03:00
Weblate 1fb54c0da5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: myrad2267 <myrad2267@gmail.com>
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/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/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2024-04-10 02:22:54 +03:00
Bogdan 5a9a6e593b New: Detect shfs mounts in disk space
(cherry picked from commit 1aef91041e404f76f278f430e4e53140fb125792)
2024-04-10 02:22:17 +03:00
Qstick 2d5fc655c0 Added table identifier to OrderBy to avoid column ambiguity on joins
Co-Authored-By: Richard <1252123+kharenis@users.noreply.github.com>

(cherry picked from commit c57ceac4debf7419be84096f997ba7b75c906586)
2024-04-08 18:47:02 +03:00
Bogdan cfcc9a5856 Bump version to 0.3.23 2024-04-07 07:58:18 +03:00
180 changed files with 7481 additions and 4202 deletions
+13
View File
@@ -0,0 +1,13 @@
// This file is used to open the backend and frontend in the same workspace, which is necessary as
// the frontend has vscode settings that are distinct from the backend
{
"folders": [
{
"path": ".."
},
{
"path": "../frontend"
}
],
"settings": {}
}
+19
View File
@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "Readarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"nvmVersion": "latest"
}
},
"forwardPorts": [8787],
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}
+12
View File
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
+2
View File
@@ -120,11 +120,13 @@ _artifacts
_rawPackage/ _rawPackage/
_dotTrace* _dotTrace*
_tests/ _tests/
_temp*
*.Result.xml *.Result.xml
coverage*.xml coverage*.xml
coverage*.json coverage*.json
setup/Output/ setup/Output/
*.~is *.~is
.mono
# .NET Core # .NET Core
project.lock.json project.lock.json
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-dotnettools.csdevkit",
"ms-vscode-remote.remote-containers"
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": "Run Readarr",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Readarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
+44
View File
@@ -0,0 +1,44 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build dotnet",
"command": "dotnet",
"type": "process",
"args": [
"msbuild",
"-restore",
"${workspaceFolder}/src/Readarr.sln",
"-p:GenerateFullPaths=true",
"-p:Configuration=Debug",
"-p:Platform=Posix",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Readarr.sln",
"-property:GenerateFullPaths=true",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/Readarr.sln"
],
"problemMatcher": "$msCompile"
}
]
}
+13 -18
View File
@@ -9,18 +9,18 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.3.22' majorVersion: '0.4.2'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)' readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)' buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417' dotnetVersion: '6.0.427'
nodeVersion: '20.X' nodeVersion: '20.X'
innoVersion: '6.2.0' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04' linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11' macImage: 'macOS-13'
trigger: trigger:
branches: branches:
@@ -166,10 +166,10 @@ stages:
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
- task: NodeTool@0 - task: UseNode@1
displayName: Set Node.js version displayName: Set Node.js version
inputs: inputs:
versionSpec: $(nodeVersion) version: $(nodeVersion)
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
@@ -1075,10 +1075,10 @@ stages:
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
- task: NodeTool@0 - task: UseNode@1
displayName: Set Node.js version displayName: Set Node.js version
inputs: inputs:
versionSpec: $(nodeVersion) version: $(nodeVersion)
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
@@ -1102,7 +1102,7 @@ stages:
vmImage: ${{ variables.windowsImage }} vmImage: ${{ variables.windowsImage }}
steps: steps:
- checkout: self # Need history for Sonar analysis - checkout: self # Need history for Sonar analysis
- task: SonarCloudPrepare@1 - task: SonarCloudPrepare@2
env: env:
SONAR_SCANNER_OPTS: '' SONAR_SCANNER_OPTS: ''
inputs: inputs:
@@ -1114,7 +1114,7 @@ stages:
cliProjectName: 'ReadarrUI' cliProjectName: 'ReadarrUI'
cliProjectVersion: '$(readarrVersion)' cliProjectVersion: '$(readarrVersion)'
cliSources: './frontend' cliSources: './frontend'
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@2
- job: Api_Docs - job: Api_Docs
displayName: API Docs displayName: API Docs
@@ -1190,7 +1190,7 @@ stages:
submodules: true submodules: true
- powershell: Set-Service SCardSvr -StartupType Manual - powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service displayName: Enable Windows Test Service
- task: SonarCloudPrepare@1 - task: SonarCloudPrepare@2
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs: inputs:
SonarCloud: 'SonarCloud' SonarCloud: 'SonarCloud'
@@ -1208,21 +1208,16 @@ stages:
./build.sh --backend -f net6.0 -r win-x64 ./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@2
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results displayName: Publish SonarCloud Results
- task: reportgenerator@4 - task: reportgenerator@5
displayName: Generate Coverage Report displayName: Generate Coverage Report
inputs: inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml' reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined' targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
- task: PublishCodeCoverageResults@1 publishCodeCoverageResults: true
displayName: Publish Coverage Report
inputs:
codeCoverageTool: 'cobertura'
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
reportDirectory: './CoverageResults/combined/'
- stage: Report_Out - stage: Report_Out
dependsOn: dependsOn:
+12 -2
View File
@@ -1,3 +1,7 @@
#!/bin/bash
set -e
FRAMEWORK="net6.0"
PLATFORM=$1 PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then if [ "$PLATFORM" = "Windows" ]; then
@@ -21,15 +25,21 @@ slnFile=src/Readarr.sln
platform=Posix platform=Posix
if [ "$PLATFORM" = "Windows" ]; then
application=Readarr.Console.dll
else
application=Readarr.dll
fi
dotnet clean $slnFile -c Debug dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest dotnet new tool-manifest
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/Readarr.console.dll" v1 & dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 &
sleep 45 sleep 45
+1 -1
View File
@@ -9,7 +9,7 @@
"editor.formatOnSave": false, "editor.formatOnSave": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll": "explicit"
}, },
"typescript.preferences.quoteStyle": "single", "typescript.preferences.quoteStyle": "single",
+3 -3
View File
@@ -67,7 +67,7 @@ module.exports = (env) => {
output: { output: {
path: distFolder, path: distFolder,
publicPath: '/', publicPath: '/',
filename: '[name]-[contenthash].js', filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
sourceMapFilename: '[file].map' sourceMapFilename: '[file].map'
}, },
@@ -92,7 +92,7 @@ module.exports = (env) => {
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: 'Content/styles.css', filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css' chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
@@ -202,7 +202,7 @@ module.exports = (env) => {
options: { options: {
importLoaders: 1, importLoaders: 1,
modules: { modules: {
localIdentName: '[name]/[local]/[hash:base64:5]' localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
} }
} }
}, },
+2 -2
View File
@@ -32,7 +32,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector';
import Logs from 'System/Logs/Logs'; import Logs from 'System/Logs/Logs';
import Status from 'System/Status/Status'; import Status from 'System/Status/Status';
import Tasks from 'System/Tasks/Tasks'; import Tasks from 'System/Tasks/Tasks';
import UpdatesConnector from 'System/Updates/UpdatesConnector'; import Updates from 'System/Updates/Updates';
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector'; import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
@@ -247,7 +247,7 @@ function AppRoutes(props) {
<Route <Route
path="/system/updates" path="/system/updates"
component={UpdatesConnector} component={Updates}
/> />
<Route <Route
+15
View File
@@ -1,6 +1,7 @@
import AuthorsAppState from './AuthorsAppState'; import AuthorsAppState from './AuthorsAppState';
import CommandAppState from './CommandAppState'; import CommandAppState from './CommandAppState';
import SettingsAppState from './SettingsAppState'; import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
import TagsAppState from './TagsAppState'; import TagsAppState from './TagsAppState';
interface FilterBuilderPropOption { interface FilterBuilderPropOption {
@@ -35,10 +36,24 @@ export interface CustomFilter {
filers: PropertyFilter[]; filers: PropertyFilter[];
} }
export interface AppSectionState {
isConnected: boolean;
isReconnecting: boolean;
version: string;
prevVersion?: string;
dimensions: {
isSmallScreen: boolean;
width: number;
height: number;
};
}
interface AppState { interface AppState {
app: AppSectionState;
authors: AuthorsAppState; authors: AuthorsAppState;
commands: CommandAppState; commands: CommandAppState;
settings: SettingsAppState; settings: SettingsAppState;
system: SystemAppState;
tags: TagsAppState; tags: TagsAppState;
} }
+7 -2
View File
@@ -1,5 +1,6 @@
import AppSectionState, { import AppSectionState, {
AppSectionDeleteState, AppSectionDeleteState,
AppSectionItemState,
AppSectionSaveState, AppSectionSaveState,
} from 'App/State/AppSectionState'; } from 'App/State/AppSectionState';
import DownloadClient from 'typings/DownloadClient'; import DownloadClient from 'typings/DownloadClient';
@@ -7,13 +8,16 @@ import ImportList from 'typings/ImportList';
import Indexer from 'typings/Indexer'; import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag'; import IndexerFlag from 'typings/IndexerFlag';
import Notification from 'typings/Notification'; import Notification from 'typings/Notification';
import { UiSettings } from 'typings/UiSettings'; import General from 'typings/Settings/General';
import UiSettings from 'typings/Settings/UiSettings';
export interface DownloadClientAppState export interface DownloadClientAppState
extends AppSectionState<DownloadClient>, extends AppSectionState<DownloadClient>,
AppSectionDeleteState, AppSectionDeleteState,
AppSectionSaveState {} AppSectionSaveState {}
export type GeneralAppState = AppSectionItemState<General>;
export interface ImportListAppState export interface ImportListAppState
extends AppSectionState<ImportList>, extends AppSectionState<ImportList>,
AppSectionDeleteState, AppSectionDeleteState,
@@ -33,11 +37,12 @@ export type UiSettingsAppState = AppSectionState<UiSettings>;
interface SettingsAppState { interface SettingsAppState {
downloadClients: DownloadClientAppState; downloadClients: DownloadClientAppState;
general: GeneralAppState;
importLists: ImportListAppState; importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState; indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState; indexers: IndexerAppState;
notifications: NotificationAppState; notifications: NotificationAppState;
uiSettings: UiSettingsAppState; ui: UiSettingsAppState;
} }
export default SettingsAppState; export default SettingsAppState;
+13
View File
@@ -0,0 +1,13 @@
import SystemStatus from 'typings/SystemStatus';
import Update from 'typings/Update';
import AppSectionState, { AppSectionItemState } from './AppSectionState';
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
export type UpdateAppState = AppSectionState<Update>;
interface SystemAppState {
updates: UpdateAppState;
status: SystemStatusAppState;
}
export default SystemAppState;
@@ -1,5 +0,0 @@
.input {
composes: input from '~Components/Form/TextInput.css';
font-family: $passwordFamily;
}
@@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import TextInput from './TextInput'; import TextInput from './TextInput';
import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input // Prevent a user from copying (or cutting) the password from the input
function onCopy(e) { function onCopy(e) {
@@ -13,17 +11,14 @@ function PasswordInput(props) {
return ( return (
<TextInput <TextInput
{...props} {...props}
type="password"
onCopy={onCopy} onCopy={onCopy}
/> />
); );
} }
PasswordInput.propTypes = { PasswordInput.propTypes = {
className: PropTypes.string.isRequired ...TextInput.props
};
PasswordInput.defaultProps = {
className: styles.input
}; };
export default PasswordInput; export default PasswordInput;
@@ -3,9 +3,9 @@
padding: 0; padding: 0;
font-size: inherit; font-size: inherit;
}
.isDisabled { &.isDisabled {
color: var(--disabledColor); color: var(--disabledColor);
cursor: not-allowed; cursor: not-allowed;
}
} }
+7
View File
@@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
import ColorImpairedContext from 'App/ColorImpairedContext'; import ColorImpairedContext from 'App/ColorImpairedContext';
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector'; import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
import SignalRConnector from 'Components/SignalRConnector'; import SignalRConnector from 'Components/SignalRConnector';
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
import locationShape from 'Helpers/Props/Shapes/locationShape'; import locationShape from 'Helpers/Props/Shapes/locationShape';
import PageHeader from './Header/PageHeader'; import PageHeader from './Header/PageHeader';
import PageSidebar from './Sidebar/PageSidebar'; import PageSidebar from './Sidebar/PageSidebar';
@@ -75,6 +76,7 @@ class Page extends Component {
isSmallScreen, isSmallScreen,
isSidebarVisible, isSidebarVisible,
enableColorImpairedMode, enableColorImpairedMode,
authenticationEnabled,
onSidebarToggle, onSidebarToggle,
onSidebarVisibleChange onSidebarVisibleChange
} = this.props; } = this.props;
@@ -108,6 +110,10 @@ class Page extends Component {
isOpen={this.state.isConnectionLostModalOpen} isOpen={this.state.isConnectionLostModalOpen}
onModalClose={this.onConnectionLostModalClose} onModalClose={this.onConnectionLostModalClose}
/> />
<AuthenticationRequiredModal
isOpen={!authenticationEnabled}
/>
</div> </div>
</ColorImpairedContext.Provider> </ColorImpairedContext.Provider>
); );
@@ -123,6 +129,7 @@ Page.propTypes = {
isUpdated: PropTypes.bool.isRequired, isUpdated: PropTypes.bool.isRequired,
isDisconnected: PropTypes.bool.isRequired, isDisconnected: PropTypes.bool.isRequired,
enableColorImpairedMode: PropTypes.bool.isRequired, enableColorImpairedMode: PropTypes.bool.isRequired,
authenticationEnabled: PropTypes.bool.isRequired,
onResize: PropTypes.func.isRequired, onResize: PropTypes.func.isRequired,
onSidebarToggle: PropTypes.func.isRequired, onSidebarToggle: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired onSidebarVisibleChange: PropTypes.func.isRequired
@@ -18,6 +18,7 @@ import {
import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions'; import { fetchTags } from 'Store/Actions/tagActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import ErrorPage from './ErrorPage'; import ErrorPage from './ErrorPage';
import LoadingPage from './LoadingPage'; import LoadingPage from './LoadingPage';
import Page from './Page'; import Page from './Page';
@@ -153,18 +154,21 @@ function createMapStateToProps() {
selectErrors, selectErrors,
selectAppProps, selectAppProps,
createDimensionsSelector(), createDimensionsSelector(),
createSystemStatusSelector(),
( (
enableColorImpairedMode, enableColorImpairedMode,
isPopulated, isPopulated,
errors, errors,
app, app,
dimensions dimensions,
systemStatus
) => { ) => {
return { return {
...app, ...app,
...errors, ...errors,
isPopulated, isPopulated,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
authenticationEnabled: systemStatus.authentication !== 'none',
enableColorImpairedMode enableColorImpairedMode
}; };
} }
+2 -2
View File
@@ -253,7 +253,7 @@ class SignalRConnector extends Component {
handleWantedCutoff = (body) => { handleWantedCutoff = (body) => {
if (body.action === 'updated') { if (body.action === 'updated') {
this.props.dispatchUpdateItem({ this.props.dispatchUpdateItem({
section: 'cutoffUnmet', section: 'wanted.cutoffUnmet',
updateOnly: true, updateOnly: true,
...body.resource ...body.resource
}); });
@@ -263,7 +263,7 @@ class SignalRConnector extends Component {
handleWantedMissing = (body) => { handleWantedMissing = (body) => {
if (body.action === 'updated') { if (body.action === 'updated') {
this.props.dispatchUpdateItem({ this.props.dispatchUpdateItem({
section: 'missing', section: 'wanted.missing',
updateOnly: true, updateOnly: true,
...body.resource ...body.resource
}); });
-11
View File
@@ -25,14 +25,3 @@
font-family: 'Ubuntu Mono'; font-family: 'Ubuntu Mono';
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype'); src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
} }
/*
* text-security-disc
*/
@font-face {
font-weight: normal;
font-style: normal;
font-family: 'text-security-disc';
src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
function onModalClose() {
// No-op
}
function AuthenticationRequiredModal(props) {
const {
isOpen
} = props;
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>
<AuthenticationRequiredModalContentConnector
onModalClose={onModalClose}
/>
</Modal>
);
}
AuthenticationRequiredModal.propTypes = {
isOpen: PropTypes.bool.isRequired
};
export default AuthenticationRequiredModal;
@@ -0,0 +1,5 @@
.authRequiredAlert {
composes: alert from '~Components/Alert.css';
margin-bottom: 20px;
}
@@ -1,7 +1,7 @@
// This file is automatically generated. // This file is automatically generated.
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'input': string; 'authRequiredAlert': string;
} }
export const cssExports: CssExports; export const cssExports: CssExports;
export default cssExports; export default cssExports;
@@ -0,0 +1,170 @@
import PropTypes from 'prop-types';
import React, { useEffect, useRef } from 'react';
import Alert from 'Components/Alert';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
function onModalClose() {
// No-op
}
function AuthenticationRequiredModalContent(props) {
const {
isPopulated,
error,
isSaving,
settings,
onInputChange,
onSavePress,
dispatchFetchStatus
} = props;
const {
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation
} = settings;
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
const didMount = useRef(false);
useEffect(() => {
if (!isSaving && didMount.current) {
dispatchFetchStatus();
}
didMount.current = true;
}, [isSaving, dispatchFetchStatus]);
return (
<ModalContent
showCloseButton={false}
onModalClose={onModalClose}
>
<ModalHeader>
{translate('AuthenticationRequired')}
</ModalHeader>
<ModalBody>
<Alert
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
{translate('AuthenticationRequiredWarning')}
</Alert>
{
isPopulated && !error ?
<div>
<FormGroup>
<FormLabel>{translate('AuthenticationMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
helpLink="https://wiki.servarr.com/readarr/faq#forced-authentication"
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
helpTextWarning={username?.value ? undefined : translate('AuthenticationRequiredUsernameHelpTextWarning')}
{...username}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
helpTextWarning={password?.value ? undefined : translate('AuthenticationRequiredPasswordHelpTextWarning')}
{...password}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
{...passwordConfirmation}
/>
</FormGroup>
</div> :
null
}
{
!isPopulated && !error ? <LoadingIndicator /> : null
}
</ModalBody>
<ModalFooter>
<SpinnerButton
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!authenticationEnabled}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
AuthenticationRequiredModalContent.propTypes = {
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired
};
export default AuthenticationRequiredModalContent;
@@ -0,0 +1,86 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
const SECTION = 'general';
function createMapStateToProps() {
return createSelector(
createSettingsSectionSelector(SECTION),
(sectionSettings) => {
return {
...sectionSettings
};
}
);
}
const mapDispatchToProps = {
dispatchClearPendingChanges: clearPendingChanges,
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
dispatchSaveGeneralSettings: saveGeneralSettings,
dispatchFetchGeneralSettings: fetchGeneralSettings,
dispatchFetchStatus: fetchStatus
};
class AuthenticationRequiredModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchGeneralSettings();
}
componentWillUnmount() {
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.dispatchSetGeneralSettingsValue({ name, value });
};
onSavePress = () => {
this.props.dispatchSaveGeneralSettings();
};
//
// Render
render() {
const {
dispatchClearPendingChanges,
dispatchFetchGeneralSettings,
dispatchSetGeneralSettingsValue,
dispatchSaveGeneralSettings,
...otherProps
} = this.props;
return (
<AuthenticationRequiredModalContent
{...otherProps}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
/>
);
}
}
AuthenticationRequiredModalContentConnector.propTypes = {
dispatchClearPendingChanges: PropTypes.func.isRequired,
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);
@@ -47,14 +47,16 @@ class InteractiveImportRow extends Component {
author, author,
book, book,
foreignEditionId, foreignEditionId,
quality quality,
size
} = this.props; } = this.props;
if ( if (
author && author &&
book != null && book != null &&
foreignEditionId && foreignEditionId &&
quality quality &&
size > 0
) { ) {
this.props.onSelectedChange({ id, value: true }); this.props.onSelectedChange({ id, value: true });
} }
+11 -3
View File
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert';
import TextInput from 'Components/Form/TextInput'; import TextInput from 'Components/Form/TextInput';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
@@ -7,7 +8,7 @@ import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import { icons } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector'; import AddNewAuthorSearchResultConnector from './Author/AddNewAuthorSearchResultConnector';
@@ -127,9 +128,16 @@ class AddNewItem extends Component {
!isFetching && !!error ? !isFetching && !!error ?
<div className={styles.message}> <div className={styles.message}>
<div className={styles.helpText}> <div className={styles.helpText}>
Failed to load search results, please try again. {translate('FailedLoadingSearchResults')}
</div>
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
<div>
<Link to="https://wiki.servarr.com/readarr/troubleshooting#invalid-response-received-from-metadata-api">
{translate('WhySearchesCouldBeFailing')}
</Link>
</div> </div>
<div>{getErrorMessage(error)}</div>
</div> : null </div> : null
} }
@@ -25,3 +25,8 @@
border-radius: 4px; border-radius: 4px;
background-color: var(--cardCenterBackgroundColor); background-color: var(--cardCenterBackgroundColor);
} }
.customFormats {
display: flex;
flex-wrap: wrap;
}
@@ -3,6 +3,7 @@
interface CssExports { interface CssExports {
'addSpecification': string; 'addSpecification': string;
'center': string; 'center': string;
'customFormats': string;
'deleteButton': string; 'deleteButton': string;
'rightButtons': string; 'rightButtons': string;
} }
+107 -27
View File
@@ -11,16 +11,69 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, inputTypes, kinds } from 'Helpers/Props'; import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
const authenticationMethodOptions = [ export const authenticationMethodOptions = [
{ key: 'none', value: 'None' }, {
{ key: 'basic', value: 'Basic (Browser Popup)' }, key: 'none',
{ key: 'forms', value: 'Forms (Login Page)' } get value() {
return translate('None');
},
isDisabled: true
},
{
key: 'external',
get value() {
return translate('External');
},
isHidden: true
},
{
key: 'basic',
get value() {
return translate('AuthBasic');
}
},
{
key: 'forms',
get value() {
return translate('AuthForm');
}
}
];
export const authenticationRequiredOptions = [
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
}
]; ];
const certificateValidationOptions = [ const certificateValidationOptions = [
{ key: 'enabled', value: 'Enabled' }, {
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }, key: 'enabled',
{ key: 'disabled', value: 'Disabled' } get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
},
{
key: 'disabled',
get value() {
return translate('Disabled');
}
}
]; ];
class SecuritySettings extends Component { class SecuritySettings extends Component {
@@ -68,8 +121,10 @@ class SecuritySettings extends Component {
const { const {
authenticationMethod, authenticationMethod,
authenticationRequired,
username, username,
password, password,
passwordConfirmation,
apiKey, apiKey,
certificateValidation certificateValidation
} = settings; } = settings;
@@ -79,26 +134,40 @@ class SecuritySettings extends Component {
return ( return (
<FieldSet legend={translate('Security')}> <FieldSet legend={translate('Security')}>
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('Authentication')}</FormLabel>
{translate('Authentication')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
name="authenticationMethod" name="authenticationMethod"
values={authenticationMethodOptions} values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')} helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange} onChange={onInputChange}
{...authenticationMethod} {...authenticationMethod}
/> />
</FormGroup> </FormGroup>
{ {
authenticationEnabled && authenticationEnabled ?
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('AuthenticationRequired')}</FormLabel>
{translate('Username')}
</FormLabel> <FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
@@ -106,15 +175,14 @@ class SecuritySettings extends Component {
onChange={onInputChange} onChange={onInputChange}
{...username} {...username}
/> />
</FormGroup> </FormGroup> :
null
} }
{ {
authenticationEnabled && authenticationEnabled ?
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('Password')}</FormLabel>
{translate('Password')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.PASSWORD} type={inputTypes.PASSWORD}
@@ -122,19 +190,33 @@ class SecuritySettings extends Component {
onChange={onInputChange} onChange={onInputChange}
{...password} {...password}
/> />
</FormGroup> </FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup> :
null
} }
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('ApiKey')}</FormLabel>
{translate('APIKey')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
name="apiKey" name="apiKey"
readOnly={true} readOnly={true}
helpTextWarning={translate('ApiKeyHelpTextWarning')} helpTextWarning={translate('RestartRequiredHelpTextWarning')}
buttons={[ buttons={[
<ClipboardButton <ClipboardButton
key="copy" key="copy"
@@ -160,9 +242,7 @@ class SecuritySettings extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>{translate('CertificateValidation')}</FormLabel>
{translate('CertificateValidation')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
+46 -50
View File
@@ -18,7 +18,6 @@ function UpdateSettings(props) {
const { const {
advancedSettings, advancedSettings,
settings, settings,
isWindows,
packageUpdateMechanism, packageUpdateMechanism,
onInputChange onInputChange
} = props; } = props;
@@ -44,10 +43,10 @@ function UpdateSettings(props) {
value: titleCase(packageUpdateMechanism) value: titleCase(packageUpdateMechanism)
}); });
} else { } else {
updateOptions.push({ key: 'builtIn', value: 'Built-In' }); updateOptions.push({ key: 'builtIn', value: translate('BuiltIn') });
} }
updateOptions.push({ key: 'script', value: 'Script' }); updateOptions.push({ key: 'script', value: translate('Script') });
return ( return (
<FieldSet legend={translate('Updates')}> <FieldSet legend={translate('Updates')}>
@@ -60,8 +59,8 @@ function UpdateSettings(props) {
<FormInputGroup <FormInputGroup
type={inputTypes.AUTO_COMPLETE} type={inputTypes.AUTO_COMPLETE}
name="branch" name="branch"
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')} helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr" helpLink="https://wiki.servarr.com/readarr/settings#updates"
{...branch} {...branch}
values={branchValues} values={branchValues}
onChange={onInputChange} onChange={onInputChange}
@@ -69,62 +68,59 @@ function UpdateSettings(props) {
/> />
</FormGroup> </FormGroup>
{ <div>
!isWindows && <FormGroup
<div> advancedSettings={advancedSettings}
<FormGroup isAdvanced={true}
advancedSettings={advancedSettings} size={sizes.MEDIUM}
isAdvanced={true} >
size={sizes.MEDIUM} <FormLabel>{translate('Automatic')}</FormLabel>
>
<FormLabel>{translate('Automatic')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="updateAutomatically" name="updateAutomatically"
helpText={translate('UpdateAutomaticallyHelpText')} helpText={translate('UpdateAutomaticallyHelpText')}
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Readarr' }) : undefined} helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
onChange={onInputChange} onChange={onInputChange}
{...updateAutomatically} {...updateAutomatically}
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('Mechanism')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="updateMechanism"
values={updateOptions}
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/readarr/settings#updates"
onChange={onInputChange}
{...updateMechanism}
/>
</FormGroup>
{
updateMechanism.value === 'script' &&
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
> >
<FormLabel>{translate('Mechanism')}</FormLabel> <FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.TEXT}
name="updateMechanism" name="updateScriptPath"
values={updateOptions} helpText={translate('UpdateScriptPathHelpText')}
helpText={translate('UpdateMechanismHelpText')}
helpLink="https://wiki.servarr.com/readarr/faq#how-do-i-update-my-readarr"
onChange={onInputChange} onChange={onInputChange}
{...updateMechanism} {...updateScriptPath}
/> />
</FormGroup> </FormGroup>
}
{ </div>
updateMechanism.value === 'script' &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="updateScriptPath"
helpText={translate('UpdateScriptPathHelpText')}
onChange={onInputChange}
{...updateScriptPath}
/>
</FormGroup>
}
</div>
}
</FieldSet> </FieldSet>
); );
} }
@@ -8,6 +8,7 @@ import TagListConnector from 'Components/TagListConnector';
import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector'; import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector';
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector'; import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
import { SelectStateInputProps } from 'typings/props'; import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageImportListsModalRow.css'; import styles from './ManageImportListsModalRow.css';
interface ManageImportListsModalRowProps { interface ManageImportListsModalRowProps {
@@ -70,7 +71,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.qualityProfileId}> <TableRowCell className={styles.qualityProfileId}>
{qualityProfile?.name ?? 'None'} {qualityProfile?.name ?? translate('None')}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.metadataProfileId}> <TableRowCell className={styles.metadataProfileId}>
@@ -82,7 +83,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.enableAutomaticAdd}> <TableRowCell className={styles.enableAutomaticAdd}>
{enableAutomaticAdd ? 'Yes' : 'No'} {enableAutomaticAdd ? translate('Yes') : translate('No')}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.tags}> <TableRowCell className={styles.tags}>
@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -15,11 +14,11 @@ function createMapStateToProps() {
(state) => state.settings.advancedSettings, (state) => state.settings.advancedSettings,
(state) => state.settings.namingExamples, (state) => state.settings.namingExamples,
createSettingsSectionSelector(SECTION), createSettingsSectionSelector(SECTION),
(advancedSettings, examples, sectionSettings) => { (advancedSettings, namingExamples, sectionSettings) => {
return { return {
advancedSettings, advancedSettings,
examples: examples.item, examples: namingExamples.item,
examplesPopulated: !_.isEmpty(examples.item), examplesPopulated: namingExamples.isPopulated,
...sectionSettings ...sectionSettings
}; };
} }
@@ -75,12 +75,12 @@ class RootFolder extends Component {
{path} {path}
</Label> </Label>
<Label kind={kinds.SUCCESS}> <Label kind={qualityProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{qualityProfile.name} {qualityProfile?.name || translate('None')}
</Label> </Label>
<Label kind={kinds.SUCCESS}> <Label kind={metadataProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{metadataProfile.name} {metadataProfile?.name || translate('None')}
</Label> </Label>
</div> </div>
@@ -1,8 +1,11 @@
import $ from 'jquery';
import _ from 'lodash';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState'; import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions'; import { set } from '../baseActions';
const abortCurrentRequests = {}; const abortCurrentRequests = {};
let lastTestData = null;
export function createCancelTestProviderHandler(section) { export function createCancelTestProviderHandler(section) {
return function(getState, payload, dispatch) { return function(getState, payload, dispatch) {
@@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
return function(getState, payload, dispatch) { return function(getState, payload, dispatch) {
dispatch(set({ section, isTesting: true })); dispatch(set({ section, isTesting: true }));
const testData = getProviderState(payload, getState, section); const {
queryParams = {},
...otherPayload
} = payload;
const testData = getProviderState({ ...otherPayload }, getState, section);
const params = { ...queryParams };
// If the user is re-testing the same provider without changes
// force it to be tested.
if (_.isEqual(testData, lastTestData)) {
params.forceTest = true;
}
lastTestData = testData;
const ajaxOptions = { const ajaxOptions = {
url: `${url}/test`, url: `${url}/test?${$.param(params, true)}`,
method: 'POST', method: 'POST',
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
@@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
abortCurrentRequests[section] = abortRequest; abortCurrentRequests[section] = abortRequest;
request.done((data) => { request.done((data) => {
lastTestData = null;
dispatch(set({ dispatch(set({
section, section,
isTesting: false, isTesting: false,
@@ -1,8 +1,9 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createTagsSelector() { function createTagsSelector() {
return createSelector( return createSelector(
(state) => state.tags.items, (state: AppState) => state.tags.items,
(tags) => { (tags) => {
return tags; return tags;
} }
-1
View File
@@ -2,7 +2,6 @@ module.exports = {
// Families // Families
defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif', defaultFontFamily: 'Roboto, "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif',
monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;', monoSpaceFontFamily: '"Ubuntu Mono", Menlo, Monaco, Consolas, "Courier New", monospace;',
passwordFamily: 'text-security-disc',
// Sizes // Sizes
extraSmallFontSize: '11px', extraSmallFontSize: '11px',
@@ -6,6 +6,22 @@ import createMultiAuthorsSelector from 'Store/Selectors/createMultiAuthorsSelect
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRowNameCell.css'; import styles from './QueuedTaskRowNameCell.css';
function formatTitles(titles: string[]) {
if (!titles) {
return null;
}
if (titles.length > 11) {
return (
<span title={titles.join(', ')}>
{titles.slice(0, 10).join(', ')}, {titles.length - 10} more
</span>
);
}
return <span>{titles.join(', ')}</span>;
}
export interface QueuedTaskRowNameCellProps { export interface QueuedTaskRowNameCellProps {
commandName: string; commandName: string;
body: CommandBody; body: CommandBody;
@@ -32,7 +48,7 @@ export default function QueuedTaskRowNameCell(
<span className={styles.commandName}> <span className={styles.commandName}>
{commandName} {commandName}
{sortedAuthors.length ? ( {sortedAuthors.length ? (
<span> - {sortedAuthors.map((a) => a.authorName).join(', ')}</span> <span> - {formatTitles(sortedAuthors.map((a) => a.authorName))}</span>
) : null} ) : null}
</span> </span>
@@ -1,50 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import styles from './UpdateChanges.css';
class UpdateChanges extends Component {
//
// Render
render() {
const {
title,
changes
} = this.props;
if (changes.length === 0) {
return null;
}
return (
<div>
<div className={styles.title}>{title}</div>
<ul>
{
changes.map((change, index) => {
const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => {
return `[${match}](https://github.com/Readarr/Readarr/issues/${match.substring(1)})`;
});
return (
<li key={index}>
<InlineMarkdown data={checkChange} />
</li>
);
})
}
</ul>
</div>
);
}
}
UpdateChanges.propTypes = {
title: PropTypes.string.isRequired,
changes: PropTypes.arrayOf(PropTypes.string)
};
export default UpdateChanges;
@@ -0,0 +1,43 @@
import React from 'react';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import styles from './UpdateChanges.css';
interface UpdateChangesProps {
title: string;
changes: string[];
}
function UpdateChanges(props: UpdateChangesProps) {
const { title, changes } = props;
if (changes.length === 0) {
return null;
}
const uniqueChanges = [...new Set(changes)];
return (
<div>
<div className={styles.title}>{title}</div>
<ul>
{uniqueChanges.map((change, index) => {
const checkChange = change.replace(
/#\d{4,5}\b/g,
(match) =>
`[${match}](https://github.com/Readarr/Readarr/issues/${match.substring(
1
)})`
);
return (
<li key={index}>
<InlineMarkdown data={checkChange} />
</li>
);
})}
</ul>
</div>
);
}
export default UpdateChanges;
-252
View File
@@ -1,252 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import translate from 'Utilities/String/translate';
import UpdateChanges from './UpdateChanges';
import styles from './Updates.css';
class Updates extends Component {
//
// Render
render() {
const {
currentVersion,
isFetching,
isPopulated,
updatesError,
generalSettingsError,
items,
isInstallingUpdate,
updateMechanism,
isDocker,
updateMechanismMessage,
shortDateFormat,
longDateFormat,
timeFormat,
onInstallLatestPress
} = this.props;
const hasError = !!(updatesError || generalSettingsError);
const hasUpdates = isPopulated && !hasError && items.length > 0;
const noUpdates = isPopulated && !hasError && !items.length;
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const externalUpdaterPrefix = 'Unable to update Readarr directly,';
const externalUpdaterMessages = {
external: 'Readarr is configured to use an external update mechanism',
apt: 'use apt to install the update',
docker: 'update the docker container to receive the update'
};
return (
<PageContent title={translate('Updates')}>
<PageContentBody>
{
!isPopulated && !hasError &&
<LoadingIndicator />
}
{
noUpdates &&
<Alert kind={kinds.INFO}>
{translate('NoUpdatesAreAvailable')}
</Alert>
}
{
hasUpdateToInstall &&
<div className={styles.messageContainer}>
{
(updateMechanism === 'builtIn' || updateMechanism === 'script') && !isDocker ?
<SpinnerButton
className={styles.updateAvailable}
kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate}
onPress={onInstallLatestPress}
>
Install Latest
</SpinnerButton> :
<Fragment>
<Icon
name={icons.WARNING}
kind={kinds.WARNING}
size={30}
/>
<div className={styles.message}>
{externalUpdaterPrefix} <InlineMarkdown data={updateMechanismMessage || externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external} />
</div>
</Fragment>
}
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
</div>
}
{
noUpdateToInstall &&
<div className={styles.messageContainer}>
<Icon
className={styles.upToDateIcon}
name={icons.CHECK_CIRCLE}
size={30}
/>
<div className={styles.message}>
The latest version of Readarr is already installed
</div>
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
</div>
}
{
hasUpdates &&
<div>
{
items.map((update) => {
const hasChanges = !!update.changes;
return (
<div
key={update.version}
className={styles.update}
>
<div className={styles.info}>
<div className={styles.version}>{update.version}</div>
<div className={styles.space}>&mdash;</div>
<div
className={styles.date}
title={formatDateTime(update.releaseDate, longDateFormat, timeFormat)}
>
{formatDate(update.releaseDate, shortDateFormat)}
</div>
{
update.branch === 'master' ?
null :
<Label
className={styles.label}
>
{update.branch}
</Label>
}
{
update.version === currentVersion ?
<Label
className={styles.label}
kind={kinds.SUCCESS}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
>
Currently Installed
</Label> :
null
}
{
update.version !== currentVersion && update.installedOn ?
<Label
className={styles.label}
kind={kinds.INVERSE}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
>
Previously Installed
</Label> :
null
}
</div>
{
!hasChanges &&
<div>
{translate('MaintenanceRelease')}
</div>
}
{
hasChanges &&
<div className={styles.changes}>
<UpdateChanges
title={translate('New')}
changes={update.changes.new}
/>
<UpdateChanges
title={translate('Fixed')}
changes={update.changes.fixed}
/>
</div>
}
</div>
);
})
}
</div>
}
{
!!updatesError &&
<div>
Failed to fetch updates
</div>
}
{
!!generalSettingsError &&
<div>
Failed to update settings
</div>
}
</PageContentBody>
</PageContent>
);
}
}
Updates.propTypes = {
currentVersion: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
updatesError: PropTypes.object,
generalSettingsError: PropTypes.object,
items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
updateMechanism: PropTypes.string,
updateMechanismMessage: PropTypes.string,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired
};
export default Updates;
+303
View File
@@ -0,0 +1,303 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import * as commandNames from 'Commands/commandNames';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { icons, kinds } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import { fetchGeneralSettings } from 'Store/Actions/settingsActions';
import { fetchUpdates } from 'Store/Actions/systemActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { UpdateMechanism } from 'typings/Settings/General';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import translate from 'Utilities/String/translate';
import UpdateChanges from './UpdateChanges';
import styles from './Updates.css';
const VERSION_REGEX = /\d+\.\d+\.\d+\.\d+/i;
function createUpdatesSelector() {
return createSelector(
(state: AppState) => state.system.updates,
(state: AppState) => state.settings.general,
(updates, generalSettings) => {
const { error: updatesError, items } = updates;
const isFetching = updates.isFetching || generalSettings.isFetching;
const isPopulated = updates.isPopulated && generalSettings.isPopulated;
return {
isFetching,
isPopulated,
updatesError,
generalSettingsError: generalSettings.error,
items,
updateMechanism: generalSettings.item.updateMechanism,
};
}
);
}
function Updates() {
const currentVersion = useSelector((state: AppState) => state.app.version);
const { packageUpdateMechanismMessage } = useSelector(
createSystemStatusSelector()
);
const { shortDateFormat, longDateFormat, timeFormat } = useSelector(
createUISettingsSelector()
);
const isInstallingUpdate = useSelector(
createCommandExecutingSelector(commandNames.APPLICATION_UPDATE)
);
const {
isFetching,
isPopulated,
updatesError,
generalSettingsError,
items,
updateMechanism,
} = useSelector(createUpdatesSelector());
const dispatch = useDispatch();
const [isMajorUpdateModalOpen, setIsMajorUpdateModalOpen] = useState(false);
const hasError = !!(updatesError || generalSettingsError);
const hasUpdates = isPopulated && !hasError && items.length > 0;
const noUpdates = isPopulated && !hasError && !items.length;
const externalUpdaterPrefix = translate('UpdateAppDirectlyLoadError');
const externalUpdaterMessages: Partial<Record<UpdateMechanism, string>> = {
external: translate('ExternalUpdater'),
apt: translate('AptUpdater'),
docker: translate('DockerUpdater'),
};
const { isMajorUpdate, hasUpdateToInstall } = useMemo(() => {
const majorVersion = parseInt(
currentVersion.match(VERSION_REGEX)?.[0] ?? '0'
);
const latestVersion = items[0]?.version;
const latestMajorVersion = parseInt(
latestVersion?.match(VERSION_REGEX)?.[0] ?? '0'
);
return {
isMajorUpdate: latestMajorVersion > majorVersion,
hasUpdateToInstall: items.some(
(update) => update.installable && update.latest
),
};
}, [currentVersion, items]);
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const handleInstallLatestPress = useCallback(() => {
if (isMajorUpdate) {
setIsMajorUpdateModalOpen(true);
} else {
dispatch(executeCommand({ name: commandNames.APPLICATION_UPDATE }));
}
}, [isMajorUpdate, setIsMajorUpdateModalOpen, dispatch]);
const handleInstallLatestMajorVersionPress = useCallback(() => {
setIsMajorUpdateModalOpen(false);
dispatch(
executeCommand({
name: commandNames.APPLICATION_UPDATE,
installMajorUpdate: true,
})
);
}, [setIsMajorUpdateModalOpen, dispatch]);
const handleCancelMajorVersionPress = useCallback(() => {
setIsMajorUpdateModalOpen(false);
}, [setIsMajorUpdateModalOpen]);
useEffect(() => {
dispatch(fetchUpdates());
dispatch(fetchGeneralSettings());
}, [dispatch]);
return (
<PageContent title={translate('Updates')}>
<PageContentBody>
{isPopulated || hasError ? null : <LoadingIndicator />}
{noUpdates ? (
<Alert kind={kinds.INFO}>{translate('NoUpdatesAreAvailable')}</Alert>
) : null}
{hasUpdateToInstall ? (
<div className={styles.messageContainer}>
{updateMechanism === 'builtIn' || updateMechanism === 'script' ? (
<SpinnerButton
kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate}
onPress={handleInstallLatestPress}
>
{translate('InstallLatest')}
</SpinnerButton>
) : (
<>
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
<div className={styles.message}>
{externalUpdaterPrefix}{' '}
<InlineMarkdown
data={
packageUpdateMechanismMessage ||
externalUpdaterMessages[updateMechanism] ||
externalUpdaterMessages.external
}
/>
</div>
</>
)}
{isFetching ? (
<LoadingIndicator className={styles.loading} size={20} />
) : null}
</div>
) : null}
{noUpdateToInstall && (
<div className={styles.messageContainer}>
<Icon
className={styles.upToDateIcon}
name={icons.CHECK_CIRCLE}
size={30}
/>
<div className={styles.message}>{translate('OnLatestVersion')}</div>
{isFetching && (
<LoadingIndicator className={styles.loading} size={20} />
)}
</div>
)}
{hasUpdates && (
<div>
{items.map((update) => {
return (
<div key={update.version} className={styles.update}>
<div className={styles.info}>
<div className={styles.version}>{update.version}</div>
<div className={styles.space}>&mdash;</div>
<div
className={styles.date}
title={formatDateTime(
update.releaseDate,
longDateFormat,
timeFormat
)}
>
{formatDate(update.releaseDate, shortDateFormat)}
</div>
{update.branch === 'master' ? null : (
<Label className={styles.label}>{update.branch}</Label>
)}
{update.version === currentVersion ? (
<Label
className={styles.label}
kind={kinds.SUCCESS}
title={formatDateTime(
update.installedOn,
longDateFormat,
timeFormat
)}
>
{translate('CurrentlyInstalled')}
</Label>
) : null}
{update.version !== currentVersion && update.installedOn ? (
<Label
className={styles.label}
kind={kinds.INVERSE}
title={formatDateTime(
update.installedOn,
longDateFormat,
timeFormat
)}
>
{translate('PreviouslyInstalled')}
</Label>
) : null}
</div>
{update.changes ? (
<div>
<UpdateChanges
title={translate('New')}
changes={update.changes.new}
/>
<UpdateChanges
title={translate('Fixed')}
changes={update.changes.fixed}
/>
</div>
) : (
<div>{translate('MaintenanceRelease')}</div>
)}
</div>
);
})}
</div>
)}
{updatesError ? (
<Alert kind={kinds.WARNING}>
{translate('FailedToFetchUpdates')}
</Alert>
) : null}
{generalSettingsError ? (
<Alert kind={kinds.DANGER}>
{translate('FailedToFetchSettings')}
</Alert>
) : null}
<ConfirmModal
isOpen={isMajorUpdateModalOpen}
kind={kinds.WARNING}
title={translate('InstallMajorVersionUpdate')}
message={
<div>
<div>{translate('InstallMajorVersionUpdateMessage')}</div>
<div>
<InlineMarkdown
data={translate('InstallMajorVersionUpdateMessageLink', {
domain: 'readarr.com',
url: 'https://readarr.com/#downloads',
})}
/>
</div>
</div>
}
confirmLabel={translate('Install')}
onConfirm={handleInstallLatestMajorVersionPress}
onCancel={handleCancelMajorVersionPress}
/>
</PageContentBody>
</PageContent>
);
}
export default Updates;
+45
View File
@@ -0,0 +1,45 @@
export type UpdateMechanism =
| 'builtIn'
| 'script'
| 'external'
| 'apt'
| 'docker';
export default interface General {
bindAddress: string;
port: number;
sslPort: number;
enableSsl: boolean;
launchBrowser: boolean;
authenticationMethod: string;
authenticationRequired: string;
analyticsEnabled: boolean;
username: string;
password: string;
passwordConfirmation: string;
logLevel: string;
consoleLogLevel: string;
branch: string;
apiKey: string;
sslCertPath: string;
sslCertPassword: string;
urlBase: string;
instanceName: string;
applicationUrl: string;
updateAutomatically: boolean;
updateMechanism: UpdateMechanism;
updateScriptPath: string;
proxyEnabled: boolean;
proxyType: string;
proxyHostname: string;
proxyPort: number;
proxyUsername: string;
proxyPassword: string;
proxyBypassFilter: string;
proxyBypassLocalAddresses: boolean;
certificateValidation: string;
backupFolder: string;
backupInterval: number;
backupRetention: number;
id: number;
}
@@ -1,4 +1,5 @@
export interface UiSettings { export default interface UiSettings {
theme: 'auto' | 'dark' | 'light';
showRelativeDates: boolean; showRelativeDates: boolean;
shortDateFormat: string; shortDateFormat: string;
longDateFormat: string; longDateFormat: string;
+32
View File
@@ -0,0 +1,32 @@
interface SystemStatus {
appData: string;
appName: string;
authentication: string;
branch: string;
buildTime: string;
instanceName: string;
isAdmin: boolean;
isDebug: boolean;
isDocker: boolean;
isLinux: boolean;
isNetCore: boolean;
isOsx: boolean;
isProduction: boolean;
isUserInteractive: boolean;
isWindows: boolean;
migrationVersion: number;
mode: string;
osName: string;
osVersion: string;
packageUpdateMechanism: string;
packageUpdateMechanismMessage: string;
runtimeName: string;
runtimeVersion: string;
sqliteVersion: string;
startTime: string;
startupPath: string;
urlBase: string;
version: string;
}
export default SystemStatus;
+20
View File
@@ -0,0 +1,20 @@
export interface Changes {
new: string[];
fixed: string[];
}
interface Update {
version: string;
branch: string;
releaseDate: string;
fileName: string;
url: string;
installed: boolean;
installedOn: string;
installable: boolean;
latest: boolean;
changes: Changes | null;
hash: string;
}
export default Update;
+52 -56
View File
@@ -25,34 +25,33 @@
"defaults" "defaults"
], ],
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "6.4.0", "@fortawesome/fontawesome-free": "6.6.0",
"@fortawesome/fontawesome-svg-core": "6.4.0", "@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.2",
"@microsoft/signalr": "6.0.25", "@microsoft/signalr": "6.0.25",
"@sentry/browser": "7.51.2", "@sentry/browser": "7.119.1",
"@sentry/integrations": "7.51.2", "@sentry/integrations": "7.119.1",
"@types/node": "18.16.16", "@types/node": "20.16.11",
"@types/react": "18.2.7", "@types/react": "18.2.79",
"@types/react-dom": "18.2.4", "@types/react-dom": "18.2.25",
"ansi-colors": "4.1.3", "classnames": "2.5.1",
"classnames": "2.3.2",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"connected-react-router": "6.9.3", "connected-react-router": "6.9.3",
"element-class": "0.2.2", "element-class": "0.2.2",
"filesize": "10.0.7", "filesize": "10.1.6",
"fuse.js": "6.6.2", "fuse.js": "6.6.2",
"history": "4.10.1", "history": "4.10.1",
"jdu": "1.0.0", "jdu": "1.0.0",
"jquery": "3.7.0", "jquery": "3.7.1",
"lodash": "4.17.21", "lodash": "4.17.21",
"mobile-detect": "1.4.5", "mobile-detect": "1.4.5",
"moment": "2.29.4", "moment": "2.30.1",
"mousetrap": "1.6.5", "mousetrap": "1.6.5",
"normalize.css": "8.0.1", "normalize.css": "8.0.1",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"qs": "6.11.1", "qs": "6.13.0",
"react": "17.0.2", "react": "17.0.2",
"react-addons-shallow-compare": "15.6.3", "react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0", "react-async-script": "1.2.0",
@@ -64,7 +63,7 @@
"react-dnd-touch-backend": "14.1.1", "react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3", "react-document-title": "2.0.3",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-focus-lock": "2.5.2", "react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0", "react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0", "react-lazyload": "3.2.0",
"react-measure": "2.5.2", "react-measure": "2.5.2",
@@ -73,74 +72,71 @@
"react-redux": "7.2.4", "react-redux": "7.2.4",
"react-router": "5.2.0", "react-router": "5.2.0",
"react-router-dom": "5.2.0", "react-router-dom": "5.2.0",
"react-slider": "1.3.1", "react-slider": "1.3.3",
"react-tabs": "3.2.2", "react-tabs": "4.3.0",
"react-text-truncate": "0.18.0", "react-text-truncate": "0.19.0",
"react-virtualized": "9.21.1", "react-virtualized": "9.21.1",
"redux": "4.1.0", "redux": "4.2.1",
"redux-actions": "2.6.5", "redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0", "redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1", "redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0", "redux-thunk": "2.4.2",
"reselect": "4.1.8", "reselect": "4.1.8",
"stacktrace-js": "2.0.2", "stacktrace-js": "2.0.2",
"typescript": "4.9.5" "typescript": "5.1.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.22.11", "@babel/core": "7.25.8",
"@babel/eslint-parser": "7.22.11", "@babel/eslint-parser": "7.25.8",
"@babel/plugin-proposal-export-default-from": "7.22.5", "@babel/plugin-proposal-export-default-from": "7.25.8",
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.22.15", "@babel/preset-env": "7.25.8",
"@babel/preset-react": "7.22.5", "@babel/preset-react": "7.25.7",
"@babel/preset-typescript": "7.22.11", "@babel/preset-typescript": "7.25.7",
"@types/lodash": "4.14.197", "@types/lodash": "4.14.195",
"@types/react-lazyload": "3.2.1", "@types/react-lazyload": "3.2.3",
"@types/redux-actions": "2.6.2", "@types/redux-actions": "2.6.5",
"@typescript-eslint/eslint-plugin": "6.5.0", "@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.5.0", "@typescript-eslint/parser": "6.21.0",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.20",
"babel-loader": "9.1.3", "babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1", "babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24", "babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.32.1", "core-js": "3.38.1",
"css-loader": "6.8.1", "css-loader": "6.8.1",
"css-modules-typescript-loader": "4.0.1", "css-modules-typescript-loader": "4.0.1",
"eslint": "8.44.0", "eslint": "8.57.1",
"eslint-config-prettier": "8.8.0", "eslint-config-prettier": "8.10.0",
"eslint-plugin-filenames": "1.3.2", "eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.31.0",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.32.2", "eslint-plugin-react": "7.37.1",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-simple-import-sort": "12.1.1",
"file-loader": "6.2.0", "file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0", "filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "8.0.0", "fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.3", "html-webpack-plugin": "5.6.0",
"loader-utils": "^3.2.1", "loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.6", "mini-css-extract-plugin": "2.9.1",
"postcss": "8.4.31", "postcss": "8.4.47",
"postcss-color-function": "4.1.0", "postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0", "postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4", "postcss-mixins": "9.0.4",
"postcss-nested": "6.0.1", "postcss-nested": "6.2.0",
"postcss-simple-vars": "7.0.1", "postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3", "postcss-url": "10.1.3",
"prettier": "2.8.8", "prettier": "2.8.8",
"require-nocache": "1.0.0", "require-nocache": "1.0.0",
"rimraf": "4.4.1", "rimraf": "6.0.1",
"run-sequence": "2.2.1", "style-loader": "3.3.4",
"streamqueue": "1.1.2",
"style-loader": "3.3.3",
"stylelint": "15.10.3", "stylelint": "15.10.3",
"stylelint-order": "6.0.3", "stylelint-order": "6.0.4",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.10",
"ts-loader": "9.4.4", "ts-loader": "9.5.1",
"typescript-plugin-css-modules": "5.0.1", "typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"webpack": "5.88.2", "webpack": "5.95.0",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-livereload-plugin": "3.0.2", "webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8" "worker-loader": "3.0.8"
+32 -2
View File
@@ -139,16 +139,46 @@
</Otherwise> </Otherwise>
</Choose> </Choose>
<!--
Set architecture to RuntimeInformation.ProcessArchitecture if not specified -->
<Choose>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X64'">
<PropertyGroup>
<Architecture>x64</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X86'">
<PropertyGroup>
<Architecture>x86</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm64'">
<PropertyGroup>
<Architecture>arm64</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm'">
<PropertyGroup>
<Architecture>arm</Architecture>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<Architecture></Architecture>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition="'$(IsWindows)' == 'true' and <PropertyGroup Condition="'$(IsWindows)' == 'true' and
'$(RuntimeIdentifier)' == ''"> '$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier> <_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-$(Architecture)</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)' == 'true' and <PropertyGroup Condition="'$(IsLinux)' == 'true' and
'$(RuntimeIdentifier)' == ''"> '$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier> <_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-$(Architecture)</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)' == 'true' and <PropertyGroup Condition="'$(IsOSX)' == 'true' and
+13 -11
View File
@@ -3,27 +3,28 @@
<ItemGroup> <ItemGroup>
<PackageVersion Include="AutoFixture" Version="4.17.0" /> <PackageVersion Include="AutoFixture" Version="4.17.0" />
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" /> <PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
<PackageVersion Include="Dapper" Version="2.0.123" /> <PackageVersion Include="Dapper" Version="2.0.151" />
<PackageVersion Include="Diacritical.Net" Version="1.0.4" />
<PackageVersion Include="DryIoc.dll" Version="5.4.3" /> <PackageVersion Include="DryIoc.dll" Version="5.4.3" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /> <PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="Equ" Version="2.3.0" /> <PackageVersion Include="Equ" Version="2.3.0" />
<PackageVersion Include="FluentAssertions" Version="5.10.3" /> <PackageVersion Include="FluentAssertions" Version="5.10.3" />
<PackageVersion Include="Polly" Version="8.3.1" /> <PackageVersion Include="Polly" Version="8.4.2" />
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" /> <PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" /> <PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" /> <PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageVersion Include="FluentValidation" Version="9.5.4" /> <PackageVersion Include="FluentValidation" Version="9.5.4" />
<PackageVersion Include="Ical.Net" Version="4.2.0" /> <PackageVersion Include="Ical.Net" Version="4.3.1" />
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" /> <PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
<PackageVersion Include="LazyCache" Version="2.4.0" /> <PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mailkit" Version="3.6.0" /> <PackageVersion Include="Mailkit" Version="3.6.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.25" /> <PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" /> <PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" /> <PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" /> <PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
<PackageVersion Include="Moq" Version="4.17.2" /> <PackageVersion Include="Moq" Version="4.17.2" />
@@ -33,9 +34,9 @@
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" /> <PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
<PackageVersion Include="NLog" Version="5.1.4" /> <PackageVersion Include="NLog" Version="5.1.4" />
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageVersion Include="Npgsql" Version="7.0.6" /> <PackageVersion Include="Npgsql" Version="7.0.8" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.14.0" />
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" /> <PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
<PackageVersion Include="PdfSharpCore" Version="1.3.32" /> <PackageVersion Include="PdfSharpCore" Version="1.3.32" />
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" /> <PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
@@ -44,9 +45,10 @@
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" /> <PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
<PackageVersion Include="Sentry" Version="3.31.0" /> <PackageVersion Include="Sentry" Version="3.31.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" /> <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" /> <PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
<PackageVersion Include="System.Buffers" Version="4.5.1" /> <PackageVersion Include="System.Buffers" Version="4.5.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" /> <PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
@@ -60,7 +62,7 @@
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" /> <PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" /> <PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" /> <PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageVersion Include="System.Text.Json" Version="6.0.9" /> <PackageVersion Include="System.Text.Json" Version="6.0.10" />
<PackageVersion Include="System.ValueTuple" Version="4.5.0" /> <PackageVersion Include="System.ValueTuple" Version="4.5.0" />
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" /> <PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
</ItemGroup> </ItemGroup>
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null); _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll(); _runner.KillAll();
_runner.Start(); _runner.Start(true);
driver.Url = "http://localhost:8787"; driver.Url = "http://localhost:8787";
@@ -1,10 +1,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
@@ -43,6 +45,26 @@ namespace NzbDrone.Common.Test
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(v => v.WriteAllText(configFile, It.IsAny<string>())) .Setup(v => v.WriteAllText(configFile, It.IsAny<string>()))
.Callback<string, string>((p, t) => _configFileContents = t); .Callback<string, string>((p, t) => _configFileContents = t);
Mocker.GetMock<IOptions<AuthOptions>>()
.Setup(v => v.Value)
.Returns(new AuthOptions());
Mocker.GetMock<IOptions<AppOptions>>()
.Setup(v => v.Value)
.Returns(new AppOptions());
Mocker.GetMock<IOptions<ServerOptions>>()
.Setup(v => v.Value)
.Returns(new ServerOptions());
Mocker.GetMock<IOptions<LogOptions>>()
.Setup(v => v.Value)
.Returns(new LogOptions());
Mocker.GetMock<IOptions<UpdateOptions>>()
.Setup(v => v.Value)
.Returns(new UpdateOptions());
} }
[Test] [Test]
@@ -89,6 +89,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://discord.com/api/webhooks/mySecret")] [TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")] [TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
// Telegram
[TestCase(@"https://api.telegram.org/bot1234567890:mySecret/sendmessage: chat_id=123456&parse_mode=HTML&text=<text>")]
[TestCase(@"https://api.telegram.org/bot1234567890:mySecret/")]
public void should_clean_message(string message) public void should_clean_message(string message)
{ {
var cleansedMessage = CleanseLogMessage.Cleanse(message); var cleansedMessage = CleanseLogMessage.Cleanse(message);
@@ -10,6 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
@@ -33,6 +34,11 @@ namespace NzbDrone.Common.Test
container.RegisterInstance(new Mock<IHostLifetime>().Object); container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object); container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<AppOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<AuthOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<ServerOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<LogOptions>>().Object);
container.RegisterInstance(new Mock<IOptions<UpdateOptions>>().Object);
var serviceProvider = container.GetServiceProvider(); var serviceProvider = container.GetServiceProvider();
serviceProvider.GetRequiredService<IAppFolderFactory>().Register(); serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
@@ -54,7 +54,10 @@ namespace NzbDrone.Common.Instrumentation
new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord // Discord
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase) new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Telegram
new (@"api.telegram.org/bot(?<id>[\d]+):(?<secret>[\w-]+)/", RegexOptions.Compiled | RegexOptions.IgnoreCase)
}; };
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled); private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
@@ -0,0 +1,8 @@
namespace NzbDrone.Common.Options;
public class AppOptions
{
public string InstanceName { get; set; }
public string Theme { get; set; }
public bool? LaunchBrowser { get; set; }
}
@@ -0,0 +1,9 @@
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; }
}
+14
View File
@@ -0,0 +1,14 @@
namespace NzbDrone.Common.Options;
public class LogOptions
{
public string Level { get; set; }
public bool? FilterSentryEvents { get; set; }
public int? Rotate { get; set; }
public bool? Sql { get; set; }
public string ConsoleLevel { get; set; }
public bool? AnalyticsEnabled { get; set; }
public string SyslogServer { get; set; }
public int? SyslogPort { get; set; }
public string SyslogLevel { get; set; }
}
@@ -0,0 +1,12 @@
namespace NzbDrone.Common.Options;
public class ServerOptions
{
public string UrlBase { get; set; }
public string BindAddress { get; set; }
public int? Port { get; set; }
public bool? EnableSsl { get; set; }
public int? SslPort { get; set; }
public string SslCertPath { get; set; }
public string SslCertPassword { get; set; }
}
@@ -0,0 +1,9 @@
namespace NzbDrone.Common.Options;
public class UpdateOptions
{
public string Mechanism { get; set; }
public bool? Automatically { get; set; }
public string ScriptPath { get; set; }
public string Branch { get; set; }
}
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Subject.Definition.Settings.As<QBittorrentSettings>().RecentTvPriority = (int)QBittorrentPriority.First; Subject.Definition.Settings.As<QBittorrentSettings>().RecentTvPriority = (int)QBittorrentPriority.First;
} }
protected void GivenGlobalSeedLimits(float maxRatio, int maxSeedingTime = -1, QBittorrentMaxRatioAction maxRatioAction = QBittorrentMaxRatioAction.Pause) protected void GivenGlobalSeedLimits(float maxRatio, int maxSeedingTime = -1, int maxInactiveSeedingTime = -1, QBittorrentMaxRatioAction maxRatioAction = QBittorrentMaxRatioAction.Pause)
{ {
Mocker.GetMock<IQBittorrentProxy>() Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>())) .Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
@@ -118,7 +118,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
MaxRatio = maxRatio, MaxRatio = maxRatio,
MaxRatioEnabled = maxRatio >= 0, MaxRatioEnabled = maxRatio >= 0,
MaxSeedingTime = maxSeedingTime, MaxSeedingTime = maxSeedingTime,
MaxSeedingTimeEnabled = maxSeedingTime >= 0 MaxSeedingTimeEnabled = maxSeedingTime >= 0,
MaxInactiveSeedingTime = maxInactiveSeedingTime,
MaxInactiveSeedingTimeEnabled = maxInactiveSeedingTime >= 0
}); });
} }
@@ -555,6 +557,34 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic()); result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\QBittorrent".AsOsAgnostic());
} }
[Test]
public void should_correct_category_output_path()
{
var config = new QBittorrentPreferences
{
SavePath = @"C:\Downloads\Finished\QBittorrent".AsOsAgnostic()
};
Mocker.GetMock<IQBittorrentProxy>()
.Setup(v => v.GetConfig(It.IsAny<QBittorrentSettings>()))
.Returns(config);
Mocker.GetMock<IQBittorrentProxy>()
.Setup(v => v.GetApiVersion(It.IsAny<QBittorrentSettings>()))
.Returns(new Version(2, 0));
Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.GetLabels(It.IsAny<QBittorrentSettings>()))
.Returns(new Dictionary<string, QBittorrentLabel>
{ { "music", new QBittorrentLabel { Name = "music", SavePath = "//server/store/downloads" } } });
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"\\server\store\downloads");
}
[Test] [Test]
public async Task Download_should_handle_http_redirect_to_magnet() public async Task Download_should_handle_http_redirect_to_magnet()
{ {
@@ -610,7 +640,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
float ratio = 0.1f, float ratio = 0.1f,
float ratioLimit = -2, float ratioLimit = -2,
int seedingTime = 1, int seedingTime = 1,
int seedingTimeLimit = -2) int seedingTimeLimit = -2,
int inactiveSeedingTimeLimit = -2,
long lastActivity = -1)
{ {
var torrent = new QBittorrentTorrent var torrent = new QBittorrentTorrent
{ {
@@ -624,7 +656,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
SavePath = "", SavePath = "",
Ratio = ratio, Ratio = ratio,
RatioLimit = ratioLimit, RatioLimit = ratioLimit,
SeedingTimeLimit = seedingTimeLimit SeedingTimeLimit = seedingTimeLimit,
InactiveSeedingTimeLimit = inactiveSeedingTimeLimit,
LastActivity = lastActivity == -1 ? DateTimeOffset.UtcNow.ToUnixTimeSeconds() : lastActivity
}; };
GivenTorrents(new List<QBittorrentTorrent>() { torrent }); GivenTorrents(new List<QBittorrentTorrent>() { torrent });
@@ -738,6 +772,50 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[Test]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_inactive_seedingtime_reached_and_not_paused()
{
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("uploading", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).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_inactive_seedingtime_reached_and_paused()
{
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("pausedUP", 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()
{
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
GivenCompletedTorrent("pausedUP", 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()
{
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("pausedUP", 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] [Test]
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused() public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
{ {
@@ -749,6 +827,17 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.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()
{
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
GivenCompletedTorrent("pausedUP", 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] [Test]
public void should_not_fetch_details_twice() public void should_not_fetch_details_twice()
{ {
@@ -49,10 +49,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
} }
[Test] [Test]
public void magnet_download_should_not_return_the_item() public void magnet_download_should_be_returned_as_queued()
{ {
PrepareClientToReturnMagnetItem(); PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
var item = Subject.GetItems().Single();
item.Status.Should().Be(DownloadItemStatus.Queued);
} }
[Test] [Test]
@@ -60,7 +60,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
public void magnet_download_should_not_return_the_item() public void magnet_download_should_not_return_the_item()
{ {
PrepareClientToReturnMagnetItem(); PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
var item = Subject.GetItems().Single();
item.Status.Should().Be(DownloadItemStatus.Queued);
} }
[Test] [Test]
@@ -7,6 +7,7 @@ using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update; using NzbDrone.Core.Update;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck.Checks namespace NzbDrone.Core.Test.HealthCheck.Checks
{ {
@@ -21,28 +22,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns("Some Warning Message"); .Returns("Some Warning Message");
} }
[Test]
public void should_return_error_when_app_folder_is_write_protected()
{
WindowsOnly();
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
.Returns(@"C:\NzbDrone");
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderWritable(It.IsAny<string>()))
.Returns(false);
Subject.Check().ShouldBeError();
}
[Test] [Test]
public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled() public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled()
{ {
PosixOnly(); var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
const string startupFolder = @"/opt/nzbdrone";
Mocker.GetMock<IConfigFileProvider>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically) .Setup(s => s.UpdateAutomatically)
@@ -62,10 +45,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test] [Test]
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled() public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
{ {
PosixOnly(); var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
var uiFolder = @"C:\NzbDrone\UI".AsOsAgnostic();
const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI";
Mocker.GetMock<IConfigFileProvider>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically) .Setup(s => s.UpdateAutomatically)
@@ -89,7 +70,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test] [Test]
public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled() public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled()
{ {
PosixOnly(); var startupFolder = @"C:\NzbDrone".AsOsAgnostic();
Mocker.GetMock<IConfigFileProvider>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically) .Setup(s => s.UpdateAutomatically)
@@ -101,7 +82,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<IAppFolderInfo>() Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder) .Setup(s => s.StartUpFolder)
.Returns(@"/opt/nzbdrone"); .Returns(startupFolder);
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never()); .Verify(c => c.FolderWritable(It.IsAny<string>()), Times.Never());
@@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MetadataSource.Goodreads namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2024-12-15 00:00:00Z")]
public class BookInfoProxyFixture : CoreTest<BookInfoProxy> public class BookInfoProxyFixture : CoreTest<BookInfoProxy>
{ {
private MetadataProfile _metadataProfile; private MetadataProfile _metadataProfile;
@@ -15,7 +15,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MetadataSource.Goodreads namespace NzbDrone.Core.Test.MetadataSource.Goodreads
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2024-05-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2024-12-15 00:00:00Z")]
public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy> public class BookInfoProxySearchFixture : CoreTest<BookInfoProxy>
{ {
[SetUp] [SetUp]
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
ExceptionVerification.IgnoreWarns(); ExceptionVerification.IgnoreWarns();
} }
[TestCase("Harry Potter and the sorcerer's stone a detailed summary", 61800696)] [TestCase("Harry Potter and the sorcerer's stone a detailed summary", 72245296)]
[TestCase("B0192CTMYG", 61209488)] [TestCase("B0192CTMYG", 61209488)]
[TestCase("9780439554930", 48517161)] [TestCase("9780439554930", 48517161)]
public void successful_book_search(string title, int expected) public void successful_book_search(string title, int expected)
@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.QueueTests
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1) _trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
.All() .All()
.With(v => v.IsTrackable = true)
.With(v => v.DownloadItem = downloadItem) .With(v => v.DownloadItem = downloadItem)
.With(v => v.RemoteBook = remoteBook) .With(v => v.RemoteBook = remoteBook)
.Build() .Build()
@@ -0,0 +1,8 @@
namespace NzbDrone.Core.Authentication
{
public enum AuthenticationRequiredType
{
Enabled = 0,
DisabledForLocalAddresses = 1
}
}
@@ -1,9 +1,10 @@
namespace NzbDrone.Core.Authentication namespace NzbDrone.Core.Authentication
{ {
public enum AuthenticationType public enum AuthenticationType
{ {
None = 0, None = 0,
Basic = 1, Basic = 1,
Forms = 2 Forms = 2,
External = 3
} }
} }
@@ -15,18 +15,18 @@ namespace NzbDrone.Core.Books
public class BookCutoffService : IBookCutoffService public class BookCutoffService : IBookCutoffService
{ {
private readonly IBookRepository _bookRepository; private readonly IBookRepository _bookRepository;
private readonly IProfileService _profileService; private readonly IQualityProfileService _qualityProfileService;
public BookCutoffService(IBookRepository bookRepository, IProfileService profileService) public BookCutoffService(IBookRepository bookRepository, IQualityProfileService qualityProfileService)
{ {
_bookRepository = bookRepository; _bookRepository = bookRepository;
_profileService = profileService; _qualityProfileService = qualityProfileService;
} }
public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec) public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec)
{ {
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>(); var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
var profiles = _profileService.All(); var profiles = _qualityProfileService.All();
//Get all items less than the cutoff //Get all items less than the cutoff
foreach (var profile in profiles) foreach (var profile in profiles)
@@ -1,6 +1,7 @@
using System.IO; using System.IO;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Books.Commands; using NzbDrone.Core.Books.Commands;
using NzbDrone.Core.Books.Events; using NzbDrone.Core.Books.Events;
@@ -55,6 +56,12 @@ namespace NzbDrone.Core.Books
_logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", author.Name, sourcePath, destinationPath); _logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", author.Name, sourcePath, destinationPath);
} }
if (sourcePath.PathEquals(destinationPath))
{
_logger.ProgressInfo("{0} is already in the specified location '{1}'.", author, destinationPath);
return;
}
try try
{ {
_rootFolderWatchingService.ReportFileSystemChangeBeginning(sourcePath, destinationPath); _rootFolderWatchingService.ReportFileSystemChangeBeginning(sourcePath, destinationPath);
@@ -9,6 +9,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@@ -32,6 +33,7 @@ namespace NzbDrone.Core.Configuration
bool EnableSsl { get; } bool EnableSsl { get; }
bool LaunchBrowser { get; } bool LaunchBrowser { get; }
AuthenticationType AuthenticationMethod { get; } AuthenticationType AuthenticationMethod { get; }
AuthenticationRequiredType AuthenticationRequired { get; }
bool AnalyticsEnabled { get; } bool AnalyticsEnabled { get; }
string LogLevel { get; } string LogLevel { get; }
string ConsoleLogLevel { get; } string ConsoleLogLevel { get; }
@@ -69,6 +71,11 @@ namespace NzbDrone.Core.Configuration
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly ICached<string> _cache; private readonly ICached<string> _cache;
private readonly PostgresOptions _postgresOptions; private readonly PostgresOptions _postgresOptions;
private readonly AuthOptions _authOptions;
private readonly AppOptions _appOptions;
private readonly ServerOptions _serverOptions;
private readonly UpdateOptions _updateOptions;
private readonly LogOptions _logOptions;
private readonly string _configFile; private readonly string _configFile;
@@ -78,13 +85,23 @@ namespace NzbDrone.Core.Configuration
ICacheManager cacheManager, ICacheManager cacheManager,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IOptions<PostgresOptions> postgresOptions) IOptions<PostgresOptions> postgresOptions,
IOptions<AuthOptions> authOptions,
IOptions<AppOptions> appOptions,
IOptions<ServerOptions> serverOptions,
IOptions<UpdateOptions> updateOptions,
IOptions<LogOptions> logOptions)
{ {
_cache = cacheManager.GetCache<string>(GetType()); _cache = cacheManager.GetCache<string>(GetType());
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_configFile = appFolderInfo.GetConfigPath(); _configFile = appFolderInfo.GetConfigPath();
_postgresOptions = postgresOptions.Value; _postgresOptions = postgresOptions.Value;
_authOptions = authOptions.Value;
_appOptions = appOptions.Value;
_serverOptions = serverOptions.Value;
_updateOptions = updateOptions.Value;
_logOptions = logOptions.Value;
} }
public Dictionary<string, object> GetConfigDictionary() public Dictionary<string, object> GetConfigDictionary()
@@ -140,7 +157,7 @@ namespace NzbDrone.Core.Configuration
{ {
const string defaultValue = "*"; const string defaultValue = "*";
var bindAddress = GetValue("BindAddress", defaultValue); var bindAddress = _serverOptions.BindAddress ?? GetValue("BindAddress", defaultValue);
if (string.IsNullOrWhiteSpace(bindAddress)) if (string.IsNullOrWhiteSpace(bindAddress))
{ {
return defaultValue; return defaultValue;
@@ -150,19 +167,19 @@ namespace NzbDrone.Core.Configuration
} }
} }
public int Port => GetValueInt("Port", 8787); public int Port => _serverOptions.Port ?? GetValueInt("Port", 8787);
public int SslPort => GetValueInt("SslPort", 6868); public int SslPort => _serverOptions.SslPort ?? GetValueInt("SslPort", 6868);
public bool EnableSsl => GetValueBoolean("EnableSsl", false); public bool EnableSsl => _serverOptions.EnableSsl ?? GetValueBoolean("EnableSsl", false);
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true); public bool LaunchBrowser => _appOptions.LaunchBrowser ?? GetValueBoolean("LaunchBrowser", true);
public string ApiKey public string ApiKey
{ {
get get
{ {
var apiKey = GetValue("ApiKey", GenerateApiKey()); var apiKey = _authOptions.ApiKey ?? GetValue("ApiKey", GenerateApiKey());
if (apiKey.IsNullOrWhiteSpace()) if (apiKey.IsNullOrWhiteSpace())
{ {
@@ -178,7 +195,7 @@ namespace NzbDrone.Core.Configuration
{ {
get get
{ {
var enabled = GetValueBoolean("AuthenticationEnabled", false, false); var enabled = _authOptions.Enabled ?? GetValueBoolean("AuthenticationEnabled", false, false);
if (enabled) if (enabled)
{ {
@@ -186,17 +203,24 @@ namespace NzbDrone.Core.Configuration
return AuthenticationType.Basic; return AuthenticationType.Basic;
} }
return GetValueEnum("AuthenticationMethod", AuthenticationType.None); return Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
? enumValue
: GetValueEnum("AuthenticationMethod", AuthenticationType.None);
} }
} }
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false); public AuthenticationRequiredType AuthenticationRequired =>
Enum.TryParse<AuthenticationRequiredType>(_authOptions.Required, out var enumValue)
? enumValue
: GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled);
public bool AnalyticsEnabled => _logOptions.AnalyticsEnabled ?? GetValueBoolean("AnalyticsEnabled", true, persist: false);
// TODO: Change back to "master" for the first stable release // TODO: Change back to "master" for the first stable release
public string Branch => GetValue("Branch", "develop").ToLowerInvariant(); public string Branch => _updateOptions.Branch ?? GetValue("Branch", "develop").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info"); public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "debug").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); public string ConsoleLogLevel => _logOptions.ConsoleLevel ?? GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false); public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false); public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
@@ -206,18 +230,18 @@ namespace NzbDrone.Core.Configuration
public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false); public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false);
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false); public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
public string Theme => GetValue("Theme", "auto", persist: false); public string Theme => _appOptions.Theme ?? GetValue("Theme", "auto", persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public bool LogSql => _logOptions.Sql ?? GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public int LogRotate => _logOptions.Rotate ?? GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); public bool FilterSentryEvents => _logOptions.FilterSentryEvents ?? GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => GetValue("SslCertPath", ""); public string SslCertPath => _serverOptions.SslCertPath ?? GetValue("SslCertPath", "");
public string SslCertPassword => GetValue("SslCertPassword", ""); public string SslCertPassword => _serverOptions.SslCertPassword ?? GetValue("SslCertPassword", "");
public string UrlBase public string UrlBase
{ {
get get
{ {
var urlBase = GetValue("UrlBase", "").Trim('/'); var urlBase = (_serverOptions.UrlBase ?? GetValue("UrlBase", "")).Trim('/');
if (urlBase.IsNullOrWhiteSpace()) if (urlBase.IsNullOrWhiteSpace())
{ {
@@ -229,19 +253,22 @@ namespace NzbDrone.Core.Configuration
} }
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI"; public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName); public string InstanceName => _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false); public bool UpdateAutomatically => _updateOptions.Automatically ?? GetValueBoolean("UpdateAutomatically", OsInfo.IsWindows, false);
public UpdateMechanism UpdateMechanism => GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false); public UpdateMechanism UpdateMechanism =>
Enum.TryParse<UpdateMechanism>(_updateOptions.Mechanism, out var enumValue)
? enumValue
: GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false);
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false); public string UpdateScriptPath => _updateOptions.ScriptPath ?? GetValue("UpdateScriptPath", "", false);
public string SyslogServer => GetValue("SyslogServer", "", persist: false); public string SyslogServer => _logOptions.SyslogServer ?? GetValue("SyslogServer", "", persist: false);
public int SyslogPort => GetValueInt("SyslogPort", 514, persist: false); public int SyslogPort => _logOptions.SyslogPort ?? GetValueInt("SyslogPort", 514, persist: false);
public string SyslogLevel => GetValue("SyslogLevel", LogLevel, false).ToLowerInvariant(); public string SyslogLevel => _logOptions.SyslogLevel ?? GetValue("SyslogLevel", LogLevel, persist: false).ToLowerInvariant();
public int GetValueInt(string key, int defaultValue, bool persist = true) public int GetValueInt(string key, int defaultValue, bool persist = true)
{ {
@@ -330,7 +357,7 @@ namespace NzbDrone.Core.Configuration
} }
// If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL // If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL
if (EnableSsl && (GetValue("SslCertHash", null).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace())) if (EnableSsl && (GetValue("SslCertHash", string.Empty, false).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
{ {
SetValue("EnableSsl", false); SetValue("EnableSsl", false);
} }
@@ -377,13 +404,21 @@ namespace NzbDrone.Core.Configuration
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Readarr will recreate it."); throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Readarr will recreate it.");
} }
return XDocument.Parse(_diskProvider.ReadAllText(_configFile)); var xDoc = XDocument.Parse(_diskProvider.ReadAllText(_configFile));
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).ToList();
if (config.Count != 1)
{
throw new InvalidConfigFileException($"{_configFile} is invalid. Please delete the config file and Readarr will recreate it.");
}
return xDoc;
} }
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes")); var newXDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME)); newXDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc; return newXDoc;
} }
} }
catch (XmlException ex) catch (XmlException ex)
@@ -455,7 +455,7 @@ namespace NzbDrone.Core.Datastore
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey); var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC"; var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize; var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}"); builder.OrderBy($"\"{sortKey.Table ?? _table}\".\"{sortKey.Column}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
return queryFunc(builder).ToList(); return queryFunc(builder).ToList();
} }
@@ -16,12 +16,12 @@ namespace NzbDrone.Core.Datastore
} }
public CorruptDatabaseException(string message, Exception innerException, params object[] args) public CorruptDatabaseException(string message, Exception innerException, params object[] args)
: base(message, innerException, args) : base(innerException, message, args)
{ {
} }
public CorruptDatabaseException(string message, Exception innerException) public CorruptDatabaseException(string message, Exception innerException)
: base(message, innerException) : base(innerException, message)
{ {
} }
} }
@@ -219,7 +219,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
protected virtual IList<TableDefinition> ReadTables() protected virtual IList<TableDefinition> ReadTables()
{ {
const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;"; const string sqlCommand = @"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name;";
var dtTable = Read(sqlCommand).Tables[0]; var dtTable = Read(sqlCommand).Tables[0];
var tableDefinitionList = new List<TableDefinition>(); var tableDefinitionList = new List<TableDefinition>();
+8 -13
View File
@@ -91,33 +91,28 @@ namespace NzbDrone.Core.Datastore
return true; return true;
} }
public string GetSortKey(string sortKey) public (string Table, string Column) GetSortKey(string sortKey)
{ {
string table = null; string table = null;
if (sortKey.Contains('.')) if (sortKey.Contains('.'))
{ {
var split = sortKey.Split('.'); var split = sortKey.Split('.');
if (split.Length != 2) if (split.Length == 2)
{ {
return sortKey; table = split[0];
sortKey = split[1];
} }
table = split[0];
sortKey = split[1];
} }
if (table != null && !TableMap.Values.Contains(table, StringComparer.OrdinalIgnoreCase)) if (table != null)
{ {
return sortKey; table = TableMap.Values.FirstOrDefault(x => x.Equals(table, StringComparison.OrdinalIgnoreCase)) ?? table;
} }
if (!_allowedOrderBy.Contains(sortKey)) sortKey = _allowedOrderBy.FirstOrDefault(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase)) ?? sortKey;
{
return sortKey;
}
return _allowedOrderBy.First(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase)); return (table, sortKey);
} }
} }
@@ -122,14 +122,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
} }
var items = new List<DownloadClientItem>(); var items = new List<DownloadClientItem>();
var ignoredCount = 0;
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
if (torrent.Hash == null) // Silently ignore torrents with no hash
if (torrent.Hash.IsNullOrWhiteSpace())
{ {
continue; continue;
} }
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
if (torrent.Name.IsNullOrWhiteSpace())
{
ignoredCount++;
continue;
}
var item = new DownloadClientItem(); var item = new DownloadClientItem();
item.DownloadId = torrent.Hash.ToUpper(); item.DownloadId = torrent.Hash.ToUpper();
item.Title = torrent.Name; item.Title = torrent.Name;
@@ -187,6 +196,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
items.Add(item); items.Add(item);
} }
if (ignoredCount > 0)
{
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
}
return items; return items;
} }
@@ -279,6 +279,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break; break;
case "metaDL": // torrent magnet is being downloaded case "metaDL": // torrent magnet is being downloaded
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
if (config.DhtEnabled) if (config.DhtEnabled)
{ {
item.Status = DownloadItemStatus.Queued; item.Status = DownloadItemStatus.Queued;
@@ -293,7 +294,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
break; break;
case "forcedDL": // torrent is being downloaded, and was forced started case "forcedDL": // torrent is being downloaded, and was forced started
case "forcedMetaDL": // torrent metadata is being forcibly downloaded
case "moving": // torrent is being moved from a folder case "moving": // torrent is being moved from a folder
case "downloading": // torrent is being downloaded and data is being transferred case "downloading": // torrent is being downloaded and data is being transferred
item.Status = DownloadItemStatus.Downloading; item.Status = DownloadItemStatus.Downloading;
@@ -375,7 +375,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
if (Proxy.GetLabels(Settings).TryGetValue(Settings.MusicCategory, out var label) && label.SavePath.IsNotNullOrWhiteSpace()) if (Proxy.GetLabels(Settings).TryGetValue(Settings.MusicCategory, out var label) && label.SavePath.IsNotNullOrWhiteSpace())
{ {
var labelDir = new OsPath(label.SavePath); var savePath = label.SavePath;
if (savePath.StartsWith("//"))
{
_logger.Trace("Replacing double forward slashes in path '{0}'. If this is not meant to be a Windows UNC path fix the 'Save Path' in qBittorrent's {1} category", savePath, Settings.MusicCategory);
savePath = savePath.Replace('/', '\\');
}
var labelDir = new OsPath(savePath);
if (labelDir.IsRooted) if (labelDir.IsRooted)
{ {
@@ -623,7 +631,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
} }
} }
if (HasReachedSeedingTimeLimit(torrent, config)) if (HasReachedSeedingTimeLimit(torrent, config) || HasReachedInactiveSeedingTimeLimit(torrent, config))
{ {
return true; return true;
} }
@@ -695,6 +703,26 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return false; return false;
} }
protected bool HasReachedInactiveSeedingTimeLimit(QBittorrentTorrent torrent, QBittorrentPreferences config)
{
long inactiveSeedingTimeLimit;
if (torrent.InactiveSeedingTimeLimit >= 0)
{
inactiveSeedingTimeLimit = torrent.InactiveSeedingTimeLimit * 60;
}
else if (torrent.InactiveSeedingTimeLimit == -2 && config.MaxInactiveSeedingTimeEnabled)
{
inactiveSeedingTimeLimit = config.MaxInactiveSeedingTime * 60;
}
else
{
return false;
}
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() - torrent.LastActivity > inactiveSeedingTimeLimit;
}
protected void FetchTorrentDetails(QBittorrentTorrent torrent) protected void FetchTorrentDetails(QBittorrentTorrent torrent)
{ {
var torrentProperties = Proxy.GetTorrentProperties(torrent.Hash, Settings); var torrentProperties = Proxy.GetTorrentProperties(torrent.Hash, Settings);
@@ -28,6 +28,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[JsonProperty(PropertyName = "max_seeding_time")] [JsonProperty(PropertyName = "max_seeding_time")]
public long MaxSeedingTime { get; set; } // Get the global share time limit in minutes public long MaxSeedingTime { get; set; } // Get the global share time limit in minutes
[JsonProperty(PropertyName = "max_inactive_seeding_time_enabled")]
public bool MaxInactiveSeedingTimeEnabled { get; set; } // True if share inactive time limit is enabled
[JsonProperty(PropertyName = "max_inactive_seeding_time")]
public long MaxInactiveSeedingTime { get; set; } // Get the global share inactive time limit in minutes
[JsonProperty(PropertyName = "max_ratio_act")] [JsonProperty(PropertyName = "max_ratio_act")]
public QBittorrentMaxRatioAction MaxRatioAction { get; set; } // Action performed when a torrent reaches the maximum share ratio. public QBittorrentMaxRatioAction MaxRatioAction { get; set; } // Action performed when a torrent reaches the maximum share ratio.
@@ -37,6 +37,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited) [JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited)
public long SeedingTimeLimit { get; set; } = -2; public long SeedingTimeLimit { get; set; } = -2;
[JsonProperty(PropertyName = "inactive_seeding_time_limit")] // Per torrent inactive seeding time limit (-2 = use global, -1 = unlimited)
public long InactiveSeedingTimeLimit { get; set; } = -2;
[JsonProperty(PropertyName = "last_activity")] // Timestamp in unix seconds when a chunk was last downloaded/uploaded
public long LastActivity { get; set; }
} }
public class QBittorrentTorrentProperties public class QBittorrentTorrentProperties
@@ -41,12 +41,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
// If totalsize == 0 the torrent is a magnet downloading metadata
if (torrent.TotalSize == 0)
{
continue;
}
var outputPath = new OsPath(torrent.DownloadDir); var outputPath = new OsPath(torrent.DownloadDir);
if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
@@ -97,6 +91,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Warning; item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString; item.Message = torrent.ErrorString;
} }
else if (torrent.TotalSize == 0)
{
item.Status = DownloadItemStatus.Queued;
}
else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped || else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
torrent.Status == TransmissionTorrentStatus.Seeding || torrent.Status == TransmissionTorrentStatus.Seeding ||
torrent.Status == TransmissionTorrentStatus.SeedingWait)) torrent.Status == TransmissionTorrentStatus.SeedingWait))
@@ -11,8 +11,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public bool IsFinished { get; set; } public bool IsFinished { get; set; }
public long Eta { get; set; } public long Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; } public TransmissionTorrentStatus Status { get; set; }
public int SecondsDownloading { get; set; } public long SecondsDownloading { get; set; }
public int SecondsSeeding { get; set; } public long SecondsSeeding { get; set; }
public string ErrorString { get; set; } public string ErrorString { get; set; }
public long DownloadedEver { get; set; } public long DownloadedEver { get; set; }
public long UploadedEver { get; set; } public long UploadedEver { get; set; }
@@ -32,6 +32,7 @@ namespace NzbDrone.Core.Download
{ {
{ Result.HasHttpServerError: true } => PredicateResult.True(), { Result.HasHttpServerError: true } => PredicateResult.True(),
{ Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(), { Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(),
{ Exception: HttpException { Response.HasHttpServerError: true } } => PredicateResult.True(),
_ => PredicateResult.False() _ => PredicateResult.False()
}, },
Delay = TimeSpan.FromSeconds(3), Delay = TimeSpan.FromSeconds(3),
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
var startupFolder = _appFolderInfo.StartUpFolder; var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI"); var uiFolder = Path.Combine(startupFolder, "UI");
if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) && if (_configFileProvider.UpdateAutomatically &&
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
!_osInfo.IsDocker) !_osInfo.IsDocker)
{ {
@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases) protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{ {
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray(); var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty()) if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{ {
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any()) if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
{ {
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]); _logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
return false;
} }
else
{ _logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
}
} }
return true; return true;
+19 -19
View File
@@ -268,26 +268,26 @@ namespace NzbDrone.Core.Indexers
protected virtual RssEnclosure[] GetEnclosures(XElement item) protected virtual RssEnclosure[] GetEnclosures(XElement item)
{ {
var enclosures = item.Elements("enclosure") var enclosures = item.Elements("enclosure")
.Select(v => .Select(v =>
{ {
try try
{ {
return new RssEnclosure return new RssEnclosure
{ {
Url = v.Attribute("url")?.Value, Url = v.Attribute("url")?.Value,
Type = v.Attribute("type")?.Value, Type = v.Attribute("type")?.Value,
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0 Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
}; };
} }
catch (Exception e) catch (Exception ex)
{ {
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title()); _logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
} }
return null; return null;
}) })
.Where(v => v != null) .Where(v => v != null)
.ToArray(); .ToArray();
return enclosures; return enclosures;
} }
@@ -48,10 +48,10 @@ namespace NzbDrone.Core.Indexers
public class SeedCriteriaSettings public class SeedCriteriaSettings
{ {
[FieldDefinition(0, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is download client's default. Ratio should be at least 1.0 and follow the indexers rules")] [FieldDefinition(0, Type = FieldType.Number, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
public double? SeedRatio { get; set; } public double? SeedRatio { get; set; }
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] [FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
public int? SeedTime { get; set; } public int? SeedTime { get; set; }
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Discography Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)] [FieldDefinition(2, Type = FieldType.Textbox, Label = "Discography Seed Time", Unit = "minutes", HelpText = "The time a torrent should be seeded before stopping, empty is download client's default", Advanced = true)]
@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases) protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{ {
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray(); var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty()) if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{ {
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any()) if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
{ {
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]); _logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
return false;
} }
else
{ _logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
}
} }
return true; return true;
+13 -7
View File
@@ -3,8 +3,8 @@
"Year": "عام", "Year": "عام",
"WeekColumnHeader": "رأس عمود الأسبوع", "WeekColumnHeader": "رأس عمود الأسبوع",
"Version": "الإصدار", "Version": "الإصدار",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية", "BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "فرع لاستخدامه لتحديث Radarr", "BranchUpdate": "فرع لاستخدامه لتحديث Radarr",
"Username": "اسم المستخدم", "Username": "اسم المستخدم",
"UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet", "UsenetDelayHelpText": "تأخر بالدقائق للانتظار قبل الحصول على إصدار من Usenet",
"UsenetDelay": "تأخير يوزنت", "UsenetDelay": "تأخير يوزنت",
@@ -300,7 +300,7 @@
"ChangeFileDate": "تغيير تاريخ الملف", "ChangeFileDate": "تغيير تاريخ الملف",
"CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS", "CertificateValidationHelpText": "تغيير مدى صرامة التحقق من صحة شهادة HTTPS",
"CertificateValidation": "التحقق من صحة الشهادة", "CertificateValidation": "التحقق من صحة الشهادة",
"CancelMessageText": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟", "CancelPendingTask": "هل أنت متأكد أنك تريد إلغاء هذه المهمة المعلقة؟",
"Cancel": "إلغاء", "Cancel": "إلغاء",
"CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط", "CalendarWeekColumnHeaderHelpText": "يظهر فوق كل عمود عندما يكون الأسبوع هو العرض النشط",
"Calendar": "التقويم", "Calendar": "التقويم",
@@ -332,7 +332,6 @@
"AddingTag": "مضيفا العلامة", "AddingTag": "مضيفا العلامة",
"AddListExclusion": "إضافة استبعاد قائمة", "AddListExclusion": "إضافة استبعاد قائمة",
"About": "نبدة عن", "About": "نبدة عن",
"APIKey": "مفتاح API",
"60MinutesSixty": "60 دقيقة: {0}", "60MinutesSixty": "60 دقيقة: {0}",
"45MinutesFourtyFive": "90 دقيقة: {0}", "45MinutesFourtyFive": "90 دقيقة: {0}",
"20MinutesTwenty": "120 دقيقة: {0}", "20MinutesTwenty": "120 دقيقة: {0}",
@@ -430,7 +429,6 @@
"HasPendingChangesNoChanges": "لا تغيرات", "HasPendingChangesNoChanges": "لا تغيرات",
"Group": "مجموعة", "Group": "مجموعة",
"GrabSelected": "انتزاع المحدد", "GrabSelected": "انتزاع المحدد",
"ApiKeyHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
"DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟", "DeleteRootFolderMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
"LoadingBooksFailed": "فشل تحميل ملفات الفيلم", "LoadingBooksFailed": "فشل تحميل ملفات الفيلم",
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.", "ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
@@ -617,7 +615,7 @@
"NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}", "NotificationStatusSingleClientHealthCheckMessage": "القوائم غير متاحة بسبب الإخفاقات: {0}",
"SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق", "SomeResultsAreHiddenByTheAppliedFilter": "بعض النتائج مخفية بواسطة عامل التصفية المطبق",
"ConnectionLost": "انقطع الاتصال", "ConnectionLost": "انقطع الاتصال",
"ConnectionLostReconnect": "سيحاول Radarr الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.", "ConnectionLostReconnect": "سيحاول {appName} الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه.",
"LastDuration": "المدة الماضية", "LastDuration": "المدة الماضية",
"Large": "كبير", "Large": "كبير",
"WhatsNew": "ما هو الجديد؟", "WhatsNew": "ما هو الجديد؟",
@@ -636,5 +634,13 @@
"ListsSettingsSummary": "القوائم", "ListsSettingsSummary": "القوائم",
"SelectDropdown": "'تحديد...", "SelectDropdown": "'تحديد...",
"SelectQuality": "حدد الجودة", "SelectQuality": "حدد الجودة",
"CustomFilter": "مرشحات مخصصة" "CustomFilter": "مرشحات مخصصة",
"IndexerFlags": "أعلام المفهرس",
"InteractiveSearchModalHeader": "بحث تفاعلي",
"ApiKey": "مفتاح API",
"AuthBasic": "أساسي (المتصفح المنبثق)",
"AuthForm": "النماذج (صفحة تسجيل الدخول)",
"Enabled": "ممكن",
"FailedLoadingSearchResults": "فشل تحميل نتائج البحث ، يرجى المحاولة مرة أخرى.",
"DisabledForLocalAddresses": "معطل بسبب العناوين المحلية"
} }
+12 -6
View File
@@ -1,5 +1,4 @@
{ {
"ApiKeyHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
"Enable": "Активиране", "Enable": "Активиране",
"MIA": "МВР", "MIA": "МВР",
"Size": " Размер", "Size": " Размер",
@@ -7,7 +6,6 @@
"20MinutesTwenty": "120 минути: {0}", "20MinutesTwenty": "120 минути: {0}",
"45MinutesFourtyFive": "90 минути: {0}", "45MinutesFourtyFive": "90 минути: {0}",
"60MinutesSixty": "60 минути: {0}", "60MinutesSixty": "60 минути: {0}",
"APIKey": "API ключ",
"About": "относно", "About": "относно",
"AddListExclusion": "Добавяне на изключване от списъка", "AddListExclusion": "Добавяне на изключване от списъка",
"AddingTag": "Добавяне на таг", "AddingTag": "Добавяне на таг",
@@ -38,7 +36,7 @@
"Calendar": "Календар", "Calendar": "Календар",
"CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед", "CalendarWeekColumnHeaderHelpText": "Показва се над всяка колона, когато седмицата е активният изглед",
"Cancel": "Отказ", "Cancel": "Отказ",
"CancelMessageText": "Наистина ли искате да отмените тази чакаща задача?", "CancelPendingTask": "Наистина ли искате да отмените тази чакаща задача?",
"CertificateValidation": "Валидиране на сертификат", "CertificateValidation": "Валидиране на сертификат",
"CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране", "CertificateValidationHelpText": "Променете колко строго е валидирането на HTTPS сертифициране",
"ChangeFileDate": "Промяна на датата на файла", "ChangeFileDate": "Промяна на датата на файла",
@@ -424,8 +422,8 @@
"UsenetDelay": "Usenet Delay", "UsenetDelay": "Usenet Delay",
"UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet", "UsenetDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете съобщение от Usenet",
"Username": "Потребителско име", "Username": "Потребителско име",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Клон, който да се използва за актуализиране на Radarr", "BranchUpdate": "Клон, който да се използва за актуализиране на Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Клон, използван от външен механизъм за актуализация", "BranchUpdateMechanism": "Клон, използван от външен механизъм за актуализация",
"Version": "Версия", "Version": "Версия",
"WeekColumnHeader": "Заглавка на колоната на седмицата", "WeekColumnHeader": "Заглавка на колоната на седмицата",
"Year": "Година", "Year": "Година",
@@ -636,5 +634,13 @@
"SelectDropdown": "„Изберете ...", "SelectDropdown": "„Изберете ...",
"SelectQuality": "Изберете Качество", "SelectQuality": "Изберете Качество",
"CustomFilter": "Персонализирани филтри", "CustomFilter": "Персонализирани филтри",
"RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?" "RemoveQueueItemConfirmation": "Наистина ли искате да премахнете {0} елемент {1} от опашката?",
"IndexerFlags": "Индексиращи знамена",
"InteractiveSearchModalHeader": "Интерактивно търсене",
"FailedLoadingSearchResults": "Неуспешно зареждане на резултатите от търсенето, моля, опитайте отново.",
"ApiKey": "API ключ",
"AuthBasic": "Основно (изскачащ прозорец на браузъра)",
"AuthForm": "Формуляри (Страница за вход)",
"DisabledForLocalAddresses": "Забранено за местни адреси",
"Enabled": "Активирано"
} }
+84 -12
View File
@@ -432,7 +432,7 @@
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "S'utilitzarà quan es realitzin cerques automàtiques mitjançant la interfície d'usuari o per Radarr",
"CutoffHelpText": "Un cop s'assoleixi aquesta qualitat, Radarr ja no baixarà pel·lícules", "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?", "ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau API?",
"PropersAndRepacks": "Propietats i Repacks", "PropersAndRepacks": "Propietats i Repacks",
"RemotePathMappingCheckFolderPermissions": "Radarr pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.", "RemotePathMappingCheckFolderPermissions": "Radarr pot veure però no accedir al directori de descàrregues {0}. Error de permisos probable.",
"RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització", "RescanAuthorFolderAfterRefresh": "Torna a escanejar la carpeta de pel·lícules després de l'actualització",
@@ -443,9 +443,9 @@
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.", "TheAuthorFolderAndAllOfItsContentWillBeDeleted": "La carpeta de pel·lícules '{0}' i tot el seu contingut es suprimiran.",
"UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte", "UrlBaseHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"ApplicationURL": "URL de l'aplicació", "ApplicationURL": "URL de l'aplicació",
"ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base", "ApplicationUrlHelpText": "URL extern de l'aplicació, inclòs http(s)://, port i URL base",
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Radarr", "BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del Radarr",
"CancelMessageText": "Esteu segur que voleu cancel·lar aquesta tasca pendent?", "CancelPendingTask": "Esteu segur que voleu cancel·lar aquesta tasca pendent?",
"ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Radarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Radarr.", "ChownGroupHelpTextWarning": "Això només funciona si l'usuari que executa Radarr és el propietari del fitxer. És millor assegurar-se que el client de descàrrega utilitza el mateix grup que Radarr.",
"ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats", "ConnectSettingsSummary": "Notificacions, connexions a servidors/reproductors multimèdia i scripts personalitzats",
"DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules", "DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules",
@@ -487,9 +487,9 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.", "SuccessMyWorkIsDoneNoFilesToRetag": "Èxit! La feina està acabada, no hi ha fitxers per canviar el nom.",
"TimeLeft": "Temps restant", "TimeLeft": "Temps restant",
"UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista", "UnableToLoadImportListExclusions": "No es poden carregar les exclusions de la llista",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branca que s'utilitza per actualitzar Radarr", "BranchUpdate": "Branca que s'utilitza per actualitzar Radarr",
"UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API", "UserAgentProvidedByTheAppThatCalledTheAPI": "Agent d'usuari proporcionat per l'aplicació per fer peticions a l'API",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern", "BranchUpdateMechanism": "Branca utilitzada pel mecanisme d'actualització extern",
"WriteTagsNo": "Mai", "WriteTagsNo": "Mai",
"RestartReloadNote": "Nota: Radarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.", "RestartReloadNote": "Nota: Radarr es reiniciarà i tornarà a carregar automàticament la interfície d'usuari durant el procés de restauració.",
"Series": "Sèries", "Series": "Sèries",
@@ -505,8 +505,6 @@
"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": "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}\"?",
"IsCutoffCutoff": "Requisit", "IsCutoffCutoff": "Requisit",
"MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ", "MountCheckMessage": "El muntatge que conté una ruta de pel·lícula es munta com a només de lectura: ",
"APIKey": "Clau API",
"ApiKeyHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"RescanAfterRefreshHelpTextWarning": "Radarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"", "RescanAfterRefreshHelpTextWarning": "Radarr no detectarà automàticament els canvis als fitxers quan no estigui configurat com a \"Sempre\"",
"ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda", "ShowUnknownAuthorItems": "Mostra elements de pel·lícula desconeguda",
"Size": " Mida", "Size": " Mida",
@@ -564,13 +562,13 @@
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "La cerca no és compatible amb aquest indexador",
"UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.", "UnableToAddANewMetadataProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
"RequiredPlaceHolder": "Afegeix una nova restricció", "RequiredPlaceHolder": "Afegeix una nova restricció",
"20MinutesTwenty": "60 minuts: {0}", "20MinutesTwenty": "20 minuts: {0}",
"AlternateTitles": "Títols alternatius", "AlternateTitles": "Títols alternatius",
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de Radarr. Això inclou informació sobre el vostre navegador, quines pàgines Radarr WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.", "AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de 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.",
"AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte", "AnalyticsEnabledHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar", "AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
"CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa", "CalendarWeekColumnHeaderHelpText": "Es mostra a sobre de cada columna quan la setmana és la visualització activa",
"45MinutesFourtyFive": "60 minuts: {0}", "45MinutesFourtyFive": "45 minuts: {0}",
"60MinutesSixty": "60 minuts: {0}", "60MinutesSixty": "60 minuts: {0}",
"BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte", "BindAddressHelpTextWarning": "Cal reiniciar perquè tingui efecte",
"BookIsDownloading": "La pel·lícula s'està baixant", "BookIsDownloading": "La pel·lícula s'està baixant",
@@ -658,11 +656,11 @@
"Activity": "Activitat", "Activity": "Activitat",
"AddNew": "Afegeix nou", "AddNew": "Afegeix nou",
"ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)", "ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)",
"ApplyTagsHelpTextRemove": "Eliminar: elimina les etiquetes introduïdes", "ApplyTagsHelpTextRemove": "Eliminació: elimina les etiquetes introduïdes",
"BlocklistReleases": "Llista de llançaments bloquejats", "BlocklistReleases": "Llista de llançaments bloquejats",
"AutoAdd": "Afegeix automàticament", "AutoAdd": "Afegeix automàticament",
"Backup": "Còpia de seguretat", "Backup": "Còpia de seguretat",
"ApplyTagsHelpTextAdd": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent", "ApplyTagsHelpTextAdd": "Afegiment: afegeix les etiquetes a la llista d'etiquetes existent",
"DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?", "DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?",
"DeleteSelectedIndexers": "Suprimeix l'indexador(s)", "DeleteSelectedIndexers": "Suprimeix l'indexador(s)",
"DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?", "DeleteRemotePathMappingMessageText": "Esteu segur que voleu suprimir aquesta assignació de camins remots?",
@@ -712,5 +710,79 @@
"DownloadClientDelugeSettingsDirectory": "Directori de baixada", "DownloadClientDelugeSettingsDirectory": "Directori de baixada",
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat", "DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge", "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge" "DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"WhatsNew": "Novetats",
"SelectDropdown": "Seleccioneu...",
"NoCutoffUnmetItems": "No hi ha elements de tall no assolits",
"ApplyTagsHelpTextHowToApplyAuthors": "Com aplicar etiquetes a les pel·lícules seleccionades",
"DeleteConditionMessageText": "Esteu segur que voleu suprimir la condició '{name}'?",
"NoChange": "Cap canvi",
"SetTags": "Estableix etiquetes",
"NoResultsFound": "Sense resultats",
"Author": "Autor",
"ResetQualityDefinitions": "Restableix les definicions de qualitat",
"Small": "Petita",
"TotalSpace": "Espai total",
"BlocklistReleaseHelpText": "Impedeix que {appName} torni a capturar aquesta versió automàticament",
"CatalogNumber": "número de catàleg",
"LastWriteTime": "La darrera hora d'escriptura",
"NextExecution": "Propera execució",
"RemoveCompleted": "S'ha eliminat",
"SelectReleaseGroup": "Seleccioneu grup de llançament",
"CountDownloadClientsSelected": "{count} client(s) de baixada seleccionat(s)",
"Authors": "Autor",
"FreeSpace": "Espai lliure",
"ExtraFileExtensionsHelpText": "Llista separada per comes de fitxers addicionals per importar (.nfo s'importarà com a .nfo-orig)",
"BypassIfAboveCustomFormatScore": "Ometre si està per sobre de la puntuació de format personalitzada",
"BypassIfAboveCustomFormatScoreHelpText": "Habiliteu l'omissió quan la versió tingui una puntuació superior a la puntuació mínima per al format personalitzat",
"RedownloadFailed": "Tornar a baixar les baixades fallades",
"ExistingTag": "Etiqueta existent",
"RemoveFailed": "Ha fallat l'eliminació",
"ImportLists": "llista d'importació",
"RemovingTag": "S'està eliminant l'etiqueta",
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
"ExtraFileExtensionsHelpTextsExamples": "Exemples: '.sub, .nfo' o 'sub,nfo'",
"SourceTitle": "Títol de la font",
"NoEventsFound": "No s'han trobat esdeveniments",
"InteractiveSearchModalHeader": "Cerca interactiva",
"NotificationStatusSingleClientHealthCheckMessage": "Llistes no disponibles a causa d'errors: {0}",
"Medium": "Suport",
"RecentChanges": "Canvis recents",
"Rejections": "Rebutjats",
"StatusEndedContinuing": "Continua",
"DeleteBookFileMessageText": "Esteu segur que voleu suprimir '{path}'?",
"DownloadClientTagHelpText": "Utilitzeu aquest indexador només per a pel·lícules amb almenys una etiqueta coincident. Deixeu-ho en blanc per utilitzar-ho amb totes les pel·lícules.",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "El client de baixada {downloadClientName} està configurat per eliminar les baixades completades. Això pot provocar que les baixades s'eliminin del vostre client abans que {1} pugui importar-les.",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Cerqueu i intenteu baixar automàticament una versió diferent quan es trobi una versió fallida a la cerca interactiva",
"FailedLoadingSearchResults": "No s'han pogut carregar els resultats de la cerca, torneu-ho a provar.",
"IndexerFlags": "Indicadors de l'indexador",
"Large": "Gran",
"LastDuration": "Darrera durada",
"LastExecution": "Darrere execució",
"Library": "Biblioteca",
"ListsSettingsSummary": "llista d'importació",
"Loading": "Carregant",
"MinimumCustomFormatScoreHelpText": "Puntuació mínima de format personalitzada necessaria per a evitar el retard del protocol preferit",
"ProfilesSettingsSummary": "Perfils de qualitat, idioma, retard i llançament",
"RemoveDownloadsAlert": "La configuració d'eliminació s'ha mogut a la configuració del client de baixada a la taula anterior.",
"RemoveQueueItemConfirmation": "Esteu segur que voleu eliminar '{sourceTitle}' de la cua?",
"RemoveSelectedItem": "Elimina l'element seleccionat",
"RemoveSelectedItems": "Elimina els elements seleccionats",
"RemoveSelectedItemsQueueMessageText": "Esteu segur que voleu eliminar {0} de la cua?",
"SelectQuality": "Seleccioneu Qualitat",
"SomeResultsAreHiddenByTheAppliedFilter": "Alguns resultats estan ocults pel filtre aplicat",
"AuthBasic": "Basic (finestra emergent del navegador)",
"AuthForm": "Formularis (pàgina d'inici de sessió)",
"AuthenticationMethod": "Mètode d'autenticació",
"AuthenticationMethodHelpTextWarning": "Seleccioneu un mètode d'autenticació vàlid",
"AuthenticationRequired": "Autenticació necessària",
"AuthenticationRequiredHelpText": "Canvia per a quines sol·licituds cal autenticar. No canvieu tret que entengueu els riscos.",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmeu la nova contrasenya",
"AuthenticationRequiredPasswordHelpTextWarning": "Introduïu una contrasenya nova",
"AuthenticationRequiredUsernameHelpTextWarning": "Introduïu un nom d'usuari nou",
"AuthenticationRequiredWarning": "Per a evitar l'accés remot sense autenticació, ara {appName} requereix que l'autenticació estigui activada. Opcionalment, podeu desactivar l'autenticació des d'adreces locals.",
"DisabledForLocalAddresses": "Desactivat per a adreces locals",
"Enabled": "Habilitat",
"External": "Extern",
"ApiKey": "Clau API"
} }
+29 -7
View File
@@ -1,5 +1,4 @@
{ {
"ApiKeyHelpTextWarning": "Vyžaduje restart, aby se projevilo",
"AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo", "AnalyticsEnabledHelpTextWarning": "Vyžaduje restart, aby se projevilo",
"DeleteRootFolderMessageText": "Opravdu chcete odstranit indexer „{0}“?", "DeleteRootFolderMessageText": "Opravdu chcete odstranit indexer „{0}“?",
"Group": "Skupina", "Group": "Skupina",
@@ -12,7 +11,6 @@
"20MinutesTwenty": "120 minut: {0}", "20MinutesTwenty": "120 minut: {0}",
"45MinutesFourtyFive": "90 minut: {0}", "45MinutesFourtyFive": "90 minut: {0}",
"60MinutesSixty": "60 minut: {0}", "60MinutesSixty": "60 minut: {0}",
"APIKey": "Klíč API",
"About": "O aplikaci", "About": "O aplikaci",
"AddListExclusion": "Přidat vyloučení seznamu", "AddListExclusion": "Přidat vyloučení seznamu",
"AddingTag": "Přidání značky", "AddingTag": "Přidání značky",
@@ -43,7 +41,7 @@
"Calendar": "Kalendář", "Calendar": "Kalendář",
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden", "CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
"Cancel": "Zrušit", "Cancel": "Zrušit",
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?", "CancelPendingTask": "Opravdu chcete zrušit tento nevyřízený úkol?",
"CertificateValidation": "Ověření certifikátu", "CertificateValidation": "Ověření certifikátu",
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.", "CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
"ChangeFileDate": "Změnit datum souboru", "ChangeFileDate": "Změnit datum souboru",
@@ -68,7 +66,7 @@
"CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů", "CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
"CreateGroup": "Vytvořit skupinu", "CreateGroup": "Vytvořit skupinu",
"CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy", "CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy",
"CutoffUnmet": "Vynechat nevyhovující", "CutoffUnmet": "Mezní hodnota nesplněna",
"DatabaseMigration": "Migrace databáze", "DatabaseMigration": "Migrace databáze",
"Dates": "Termíny", "Dates": "Termíny",
"DelayProfile": "Profil zpoždění", "DelayProfile": "Profil zpoždění",
@@ -431,8 +429,8 @@
"UsenetDelay": "Usenet Zpoždění", "UsenetDelay": "Usenet Zpoždění",
"UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu", "UsenetDelayHelpText": "Zpoždění v minutách čekání před uvolněním z Usenetu",
"Username": "Uživatelské jméno", "Username": "Uživatelské jméno",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Pobočka, která se má použít k aktualizaci Radarr", "BranchUpdate": "Pobočka, která se má použít k aktualizaci Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace", "BranchUpdateMechanism": "Pobočka používaná mechanismem externí aktualizace",
"Version": "Verze", "Version": "Verze",
"WeekColumnHeader": "Záhlaví sloupce týdne", "WeekColumnHeader": "Záhlaví sloupce týdne",
"Year": "Rok", "Year": "Rok",
@@ -691,5 +689,29 @@
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání", "AutoRedownloadFailedFromInteractiveSearchHelpText": "Automaticky vyhledat a pokusit se o stažení jiného vydání, pokud bylo neúspěšné vydání zachyceno z interaktivního vyhledávání",
"SelectDropdown": "'Vybrat...", "SelectDropdown": "'Vybrat...",
"CustomFilter": "Vlastní filtry", "CustomFilter": "Vlastní filtry",
"SelectQuality": "Vyberte kvalitu" "SelectQuality": "Vyberte kvalitu",
"IndexerFlags": "Příznaky indexeru",
"InteractiveSearchModalHeader": "Interaktivní vyhledávání",
"FailedLoadingSearchResults": "Výsledky vyhledávání se nepodařilo načíst, zkuste to prosím znovu.",
"CustomFormatsSpecificationFlag": "Vlajka",
"CustomFormatsSpecificationRegularExpressionHelpText": "Vlastní formát RegEx nerozlišuje velká a malá písmena",
"BlocklistAndSearch": "Seznam blokovaných a vyhledávání",
"ChangeCategory": "Změnit kategorii",
"BlocklistMultipleOnlyHint": "Blokovat a nehledat náhradu",
"CustomFormatsSettingsTriggerInfo": "Vlastní formát se použije na vydání nebo soubor, pokud odpovídá alespoň jednomu z různých typů zvolených podmínek.",
"ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
"AuthBasic": "Základní (vyskakovací okno prohlížeče)",
"AuthenticationMethod": "Metoda ověřování",
"AuthenticationMethodHelpTextWarning": "Prosím vyberte platnou metodu ověřování",
"AuthenticationRequired": "Vyžadované ověření",
"AuthenticationRequiredHelpText": "Změnit, pro které požadavky je vyžadováno ověření. Pokud nerozumíte rizikům, neměňte je.",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo",
"AuthenticationRequiredPasswordHelpTextWarning": "Vložte nové heslo",
"AuthenticationRequiredUsernameHelpTextWarning": "Vložte nové uživatelské jméno",
"AuthenticationRequiredWarning": "Aby se zabránilo vzdálenému přístupu bez ověření, vyžaduje nyní {appName} povolení ověření. Ověřování z místních adres můžete volitelně zakázat.",
"BlocklistOnlyHint": "Blokovat a nehledat náhradu",
"Enabled": "Povoleno",
"ApiKey": "Klíč API",
"AuthForm": "Formuláře (přihlašovací stránka)",
"DisabledForLocalAddresses": "Zakázáno pro místní adresy"
} }
+16 -6
View File
@@ -2,7 +2,6 @@
"20MinutesTwenty": "20 minutter: {0}", "20MinutesTwenty": "20 minutter: {0}",
"45MinutesFourtyFive": "45 minutter: {0}", "45MinutesFourtyFive": "45 minutter: {0}",
"60MinutesSixty": "60 minutter: {0}", "60MinutesSixty": "60 minutter: {0}",
"APIKey": "API-nøgle",
"About": "Om", "About": "Om",
"AddListExclusion": "Tilføj ekskludering af liste", "AddListExclusion": "Tilføj ekskludering af liste",
"AddingTag": "Tilføjer tag", "AddingTag": "Tilføjer tag",
@@ -39,7 +38,7 @@
"Calendar": "Kalender", "Calendar": "Kalender",
"CalendarWeekColumnHeaderHelpText": "Vist over hver kolonne, når ugen er den aktive visning", "CalendarWeekColumnHeaderHelpText": "Vist over hver kolonne, når ugen er den aktive visning",
"Cancel": "Afbryd", "Cancel": "Afbryd",
"CancelMessageText": "Er du sikker på, at du vil annullere denne afventende opgave?", "CancelPendingTask": "Er du sikker på, at du vil annullere denne afventende opgave?",
"CertificateValidation": "Validering af certifikat", "CertificateValidation": "Validering af certifikat",
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er", "CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
"ChangeFileDate": "Skift fildato", "ChangeFileDate": "Skift fildato",
@@ -425,13 +424,12 @@
"UsenetDelay": "Usenet-forsinkelse", "UsenetDelay": "Usenet-forsinkelse",
"UsenetDelayHelpText": "Forsink i minutter, før du tager fat i en frigivelse fra Usenet", "UsenetDelayHelpText": "Forsink i minutter, før du tager fat i en frigivelse fra Usenet",
"Username": "Brugernavn", "Username": "Brugernavn",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Filial, der skal bruges til at opdatere Radarr", "BranchUpdate": "Filial, der skal bruges til at opdatere Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Gren brugt af ekstern opdateringsmekanisme", "BranchUpdateMechanism": "Gren brugt af ekstern opdateringsmekanisme",
"Version": "Version", "Version": "Version",
"WeekColumnHeader": "Ugens kolonneoverskrift", "WeekColumnHeader": "Ugens kolonneoverskrift",
"Year": "År", "Year": "År",
"YesCancel": "Ja, Annuller", "YesCancel": "Ja, Annuller",
"ApiKeyHelpTextWarning": "Kræver genstart for at træde i kraft",
"DeleteRootFolderMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?", "DeleteRootFolderMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?",
"LoadingBooksFailed": "Indlæsning af filmfiler mislykkedes", "LoadingBooksFailed": "Indlæsning af filmfiler mislykkedes",
"ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.", "ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
@@ -644,5 +642,17 @@
"CustomFilter": "Bruger Tilpassede Filtere", "CustomFilter": "Bruger Tilpassede Filtere",
"SelectDropdown": "'Vælg...", "SelectDropdown": "'Vælg...",
"SelectQuality": "Vælg Kvalitet", "SelectQuality": "Vælg Kvalitet",
"ApplyChanges": "Anvend ændringer" "ApplyChanges": "Anvend ændringer",
"Series": "Serie",
"IndexerFlags": "Indexer Flag",
"InteractiveSearchModalHeader": "Interaktiv søgning",
"Theme": "Tema",
"Publisher": "Udgiver",
"CatalogNumber": "katalognummer",
"FailedLoadingSearchResults": "Kunne ikke indlæse søgeresultater. Prøv igen.",
"AuthBasic": "Grundlæggende (pop op-browser)",
"AuthForm": "Formularer (login-side)",
"DisabledForLocalAddresses": "Deaktiveret for lokale adresser",
"Enabled": "Aktiveret",
"ApiKey": "API-nøgle"
} }
+93 -28
View File
@@ -5,11 +5,10 @@
"20MinutesTwenty": "20 Minuten: {0}", "20MinutesTwenty": "20 Minuten: {0}",
"45MinutesFourtyFive": "45 Minuten: {0}", "45MinutesFourtyFive": "45 Minuten: {0}",
"60MinutesSixty": "60 Minuten: {0}", "60MinutesSixty": "60 Minuten: {0}",
"APIKey": "API-Schlüssel",
"About": "Über", "About": "Über",
"AddListExclusion": "Listenausschluss hinzufügen", "AddListExclusion": "Listenausschluss hinzufügen",
"AddingTag": "Tag hinzufügen", "AddingTag": "Tag hinzufügen",
"AgeWhenGrabbed": "Alter (zum Zeitpunkt der Entführung)", "AgeWhenGrabbed": "Alter (bei Erfassung)",
"AlreadyInYourLibrary": "Bereits in Ihrer Bibliothek", "AlreadyInYourLibrary": "Bereits in Ihrer Bibliothek",
"AlternateTitles": "Alternative Titel", "AlternateTitles": "Alternative Titel",
"Analytics": "Analysen", "Analytics": "Analysen",
@@ -36,7 +35,7 @@
"Calendar": "Kalender", "Calendar": "Kalender",
"CalendarWeekColumnHeaderHelpText": "Wird in der Wochenansicht über jeder Spalte angezeigt", "CalendarWeekColumnHeaderHelpText": "Wird in der Wochenansicht über jeder Spalte angezeigt",
"Cancel": "Abbrechen", "Cancel": "Abbrechen",
"CancelMessageText": "Diese laufende Aufgabe wirklich abbrechen?", "CancelPendingTask": "Diese laufende Aufgabe wirklich abbrechen?",
"CertificateValidation": "Zertifikatsvalidierung", "CertificateValidation": "Zertifikatsvalidierung",
"CertificateValidationHelpText": "Ändern Sie, wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nichts, es sei denn, Sie verstehen die Risiken.", "CertificateValidationHelpText": "Ändern Sie, wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nichts, es sei denn, Sie verstehen die Risiken.",
"ChangeFileDate": "Ändern Sie das Dateidatum", "ChangeFileDate": "Ändern Sie das Dateidatum",
@@ -61,7 +60,7 @@
"CreateEmptyAuthorFoldersHelpText": "Leere Filmordner für fehlende Filme beim Scan erstellen", "CreateEmptyAuthorFoldersHelpText": "Leere Filmordner für fehlende Filme beim Scan erstellen",
"CreateGroup": "Gruppe erstellen", "CreateGroup": "Gruppe erstellen",
"CutoffHelpText": "Sobald diese Qualität erreicht wird, werden keine neuen Releases erfasst", "CutoffHelpText": "Sobald diese Qualität erreicht wird, werden keine neuen Releases erfasst",
"CutoffUnmet": " Schwelle nicht erreicht", "CutoffUnmet": "Schwelle nicht erreicht",
"DatabaseMigration": "DB Migration", "DatabaseMigration": "DB Migration",
"Dates": "Termine", "Dates": "Termine",
"DelayProfile": "Verzögerungsprofil", "DelayProfile": "Verzögerungsprofil",
@@ -69,24 +68,24 @@
"DelayingDownloadUntilInterp": "Download verzögern bis {0} um {1}", "DelayingDownloadUntilInterp": "Download verzögern bis {0} um {1}",
"Delete": "Löschen", "Delete": "Löschen",
"DeleteBackup": "Sicherung löschen", "DeleteBackup": "Sicherung löschen",
"DeleteBackupMessageText": "Sind Sie sicher, dass Sie die Sicherung „{name}“ löschen möchten?", "DeleteBackupMessageText": "Soll das Backup '{name}' wirklich gelöscht werden?",
"DeleteDelayProfile": "Verzögerungsprofil löschen", "DeleteDelayProfile": "Verzögerungsprofil löschen",
"DeleteDelayProfileMessageText": "Sind Sie sicher, dass Sie dieses Verzögerungsprofil löschen möchten?", "DeleteDelayProfileMessageText": "Sind Sie sicher, dass Sie dieses Verzögerungsprofil löschen möchten?",
"DeleteDownloadClient": "Download-Client löschen", "DeleteDownloadClient": "Download-Client löschen",
"DeleteDownloadClientMessageText": "Sind Sie sicher, dass Sie den Download-Client {name} löschen möchten?", "DeleteDownloadClientMessageText": "Bist du sicher, dass du den Download Client '{name}' wirklich löschen willst?",
"DeleteEmptyFolders": "Leere Ordner löschen", "DeleteEmptyFolders": "Leere Ordner löschen",
"DeleteEmptyFoldersHelpText": "Lösche leere Autorordner während des Scans oder wenn Buchdateien gelöscht werden", "DeleteEmptyFoldersHelpText": "Lösche leere Autorordner während des Scans oder wenn Buchdateien gelöscht werden",
"DeleteImportListMessageText": "Sind Sie sicher, dass Sie die Liste {name}“ löschen möchten?", "DeleteImportListMessageText": "Bist du sicher, dass du die Liste '{name}' wirklich löschen willst?",
"DeleteIndexer": "Indexer löschen", "DeleteIndexer": "Indexer löschen",
"DeleteIndexerMessageText": "Sind Sie sicher, dass Sie den Indexer {name}“ löschen möchten?", "DeleteIndexerMessageText": "Bist du sicher, dass du den Indexer '{name}' wirklich löschen willst?",
"DeleteMetadataProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?", "DeleteMetadataProfileMessageText": "Bist du sicher, dass du das Qualitätsprofil '{name}' wirklich löschen willst?",
"DeleteNotification": "Benachrichtigung löschen", "DeleteNotification": "Benachrichtigung löschen",
"DeleteNotificationMessageText": "Sind Sie sicher, dass Sie die Benachrichtigung {name}“ löschen möchten?", "DeleteNotificationMessageText": "Bist du sicher, dass du die Benachrichtigung '{name}' wirklich löschen willst?",
"DeleteQualityProfile": "Qualitätsprofil löschen", "DeleteQualityProfile": "Qualitätsprofil löschen",
"DeleteQualityProfileMessageText": "Sind Sie sicher, dass Sie das Qualitätsprofil {name}“ löschen möchten?", "DeleteQualityProfileMessageText": "Bist du sicher, dass du das Qualitätsprofil '{name}' wirklich löschen willst?",
"DeleteReleaseProfile": "Release-Profil löschen", "DeleteReleaseProfile": "Release-Profil löschen",
"DeleteReleaseProfileMessageText": "Bist du sicher, dass du dieses Release-Profil löschen willst?", "DeleteReleaseProfileMessageText": "Bist du sicher, dass du dieses Release-Profil löschen willst?",
"DeleteRootFolderMessageText": "Indexer '{0}' wirklich löschen?", "DeleteRootFolderMessageText": "Bist du sicher, dass du den Root-Ordner '{name}' wirklich löschen willst?",
"DeleteSelectedBookFiles": "Ausgewählte Filmdateien löschen", "DeleteSelectedBookFiles": "Ausgewählte Filmdateien löschen",
"DeleteSelectedBookFilesMessageText": "Ausgewählte Filme wirklich löschen?", "DeleteSelectedBookFilesMessageText": "Ausgewählte Filme wirklich löschen?",
"DeleteTag": "Tag löschen", "DeleteTag": "Tag löschen",
@@ -274,7 +273,7 @@
"RemoveCompletedDownloadsHelpText": "Importierte Downloads aus dem Downloader Verlauf entfernen", "RemoveCompletedDownloadsHelpText": "Importierte Downloads aus dem Downloader Verlauf entfernen",
"RemoveFailedDownloadsHelpText": "Fehlgeschlagene Downloads aus dem Downloader Verlauf entfernen", "RemoveFailedDownloadsHelpText": "Fehlgeschlagene Downloads aus dem Downloader Verlauf entfernen",
"RemoveFilter": "Filter entfernen", "RemoveFilter": "Filter entfernen",
"RemoveFromDownloadClient": "Vom Download-Client entfernen", "RemoveFromDownloadClient": "Aus dem Download Client entfernen",
"RemoveFromQueue": "Aus der Warteschlage entfernen", "RemoveFromQueue": "Aus der Warteschlage entfernen",
"RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.", "RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
"RemoveSelected": "Auswahl entfernen", "RemoveSelected": "Auswahl entfernen",
@@ -416,8 +415,8 @@
"UsenetDelay": "Usenet-Verzögerung", "UsenetDelay": "Usenet-Verzögerung",
"UsenetDelayHelpText": "Verzögerung in Minuten, bevor Sie eine Veröffentlichung aus dem Usenet erhalten", "UsenetDelayHelpText": "Verzögerung in Minuten, bevor Sie eine Veröffentlichung aus dem Usenet erhalten",
"Username": "Nutzername", "Username": "Nutzername",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branch zum updaten von Radarr", "BranchUpdate": "Branch zum updaten von Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branch für den externen Updateablauf", "BranchUpdateMechanism": "Branch für den externen Updateablauf",
"Version": "Version", "Version": "Version",
"WeekColumnHeader": "Spaltenüberschrift „Woche“.", "WeekColumnHeader": "Spaltenüberschrift „Woche“.",
"Year": "Jahr", "Year": "Jahr",
@@ -427,7 +426,6 @@
"ProxyUsernameHelpText": "Sie müssen nur einen Benutzernamen und ein Passwort eingeben, wenn dies erforderlich ist. Andernfalls lassen Sie sie leer.", "ProxyUsernameHelpText": "Sie müssen nur einen Benutzernamen und ein Passwort eingeben, wenn dies erforderlich ist. Andernfalls lassen Sie sie leer.",
"SslPortHelpTextWarning": "Erfordert einen Neustart", "SslPortHelpTextWarning": "Erfordert einen Neustart",
"UnableToLoadMetadataProfiles": "Verzögerungsprofile konnten nicht geladen werden", "UnableToLoadMetadataProfiles": "Verzögerungsprofile konnten nicht geladen werden",
"ApiKeyHelpTextWarning": "Erfordert einen Neustart",
"Book": "Buch", "Book": "Buch",
"Authors": "Autoren", "Authors": "Autoren",
"AuthorFolderFormat": "Autor Orderformat", "AuthorFolderFormat": "Autor Orderformat",
@@ -464,7 +462,7 @@
"DownloadClientCheckDownloadingToRoot": "Download-Client {0} legt Downloads im Stammordner {1} ab. Sie sollten nicht in einen Stammordner herunterladen.", "DownloadClientCheckDownloadingToRoot": "Download-Client {0} legt Downloads im Stammordner {1} ab. Sie sollten nicht in einen Stammordner herunterladen.",
"Series": "Serien", "Series": "Serien",
"Publisher": "Herausgeber", "Publisher": "Herausgeber",
"MaintenanceRelease": "Wartung: Fehlerbehebung und andere Verbesserungen. Siehe Github Commit History für weitere Details", "MaintenanceRelease": "Maintenance Release: Fehlerbehebungen und andere Verbesserungen. Siehe Github Commit Verlauf für weitere Details",
"LogRotation": "Logrotation", "LogRotation": "Logrotation",
"LogRotateHelpText": "Max. Anzahl der zu behaltenden Logdateien", "LogRotateHelpText": "Max. Anzahl der zu behaltenden Logdateien",
"Label": "Label", "Label": "Label",
@@ -606,7 +604,7 @@
"OnImportFailureHelpText": "Bei fehlgeschlagenem Import", "OnImportFailureHelpText": "Bei fehlgeschlagenem Import",
"OnReleaseImport": "Bei Veröffentlichungsimport", "OnReleaseImport": "Bei Veröffentlichungsimport",
"OnReleaseImportHelpText": "Bei Veröffentlichungsimport", "OnReleaseImportHelpText": "Bei Veröffentlichungsimport",
"OnRename": "Bei Umbenennen", "OnRename": "Bei Umbenennung",
"PastDaysHelpText": "Tage für iCal-Feed, um in die Vergangenheit zu schauen", "PastDaysHelpText": "Tage für iCal-Feed, um in die Vergangenheit zu schauen",
"PathHelpTextWarning": "Dies muss ein anderes Verzeichnis sein als das, in dem der Download Client die Dateien ablegt", "PathHelpTextWarning": "Dies muss ein anderes Verzeichnis sein als das, in dem der Download Client die Dateien ablegt",
"ReleaseProfiles": "Veröffentlichungsprofile", "ReleaseProfiles": "Veröffentlichungsprofile",
@@ -705,7 +703,7 @@
"SelectedCountAuthorsSelectedInterp": "{0} Künstler ausgewählt", "SelectedCountAuthorsSelectedInterp": "{0} Künstler ausgewählt",
"SizeLimit": "Grössenlimit", "SizeLimit": "Grössenlimit",
"TheBooksFilesWillBeDeleted": "Die Dateien des Albums werden gelöscht.", "TheBooksFilesWillBeDeleted": "Die Dateien des Albums werden gelöscht.",
"MediaManagement": "Medien", "MediaManagement": "Medienverwaltung",
"Metadata": "Metadaten", "Metadata": "Metadaten",
"MountCheckMessage": "Der Einhängepunkt, welcher einen Filmpfad enthält, ist schreibgeschützt eingehängt: ", "MountCheckMessage": "Der Einhängepunkt, welcher einen Filmpfad enthält, ist schreibgeschützt eingehängt: ",
"OnBookFileDelete": "Bei Filmdatei löschen", "OnBookFileDelete": "Bei Filmdatei löschen",
@@ -875,7 +873,7 @@
"CustomFormatSettings": "Einstellungen für eigene Formate", "CustomFormatSettings": "Einstellungen für eigene Formate",
"DataFutureBooks": "Überwachung von Alben die noch nicht veröffentlicht wurden", "DataFutureBooks": "Überwachung von Alben die noch nicht veröffentlicht wurden",
"DeleteCustomFormat": "Benutzerdefiniertes Format löschen", "DeleteCustomFormat": "Benutzerdefiniertes Format löschen",
"DeleteCustomFormatMessageText": "Bist du sicher, dass du das eigene Format '{0}' löschen willst?", "DeleteCustomFormatMessageText": "Bist du sicher, dass du das benutzerdefinierte Format '{name}' wirklich löschen willst?",
"IncludeCustomFormatWhenRenamingHelpText": "In {Custom Formats} umbennenungs Format", "IncludeCustomFormatWhenRenamingHelpText": "In {Custom Formats} umbennenungs Format",
"ResetTitles": "Titel zurücksetzen", "ResetTitles": "Titel zurücksetzen",
"UnableToLoadCustomFormats": "Eigene Formate konnten nicht geladen werden", "UnableToLoadCustomFormats": "Eigene Formate konnten nicht geladen werden",
@@ -931,13 +929,13 @@
"RemoveFailed": "Fehlgeschlagene entfernen", "RemoveFailed": "Fehlgeschlagene entfernen",
"RemoveCompleted": "Abgeschlossene entfernen", "RemoveCompleted": "Abgeschlossene entfernen",
"ApplyChanges": "Änderungen anwenden", "ApplyChanges": "Änderungen anwenden",
"ApplyTagsHelpTextAdd": "Hinzufügen: Fügen Sie die Tags der vorhandenen Tag-Liste hinzu", "ApplyTagsHelpTextAdd": "Hinzufügen: Füge Tags zu den bestehenden Tags hinzu",
"ApplyTagsHelpTextHowToApplyImportLists": "So wenden Sie Tags auf die ausgewählten Importlisten an", "ApplyTagsHelpTextHowToApplyImportLists": "Wie Tags den selektierten Importlisten hinzugefügt werden können",
"ApplyTagsHelpTextHowToApplyIndexers": "So wenden Sie Tags auf die ausgewählten Indexer an", "ApplyTagsHelpTextHowToApplyIndexers": "Wie Tags zu den selektierten Indexern hinzugefügt werden können",
"AutomaticAdd": "Automatisch hinzufügen", "AutomaticAdd": "Automatisch hinzufügen",
"ApplyTagsHelpTextRemove": "Entfernen: Die eingegebenen Tags entfernen", "ApplyTagsHelpTextRemove": "Entfernen: Entferne die hinterlegten Tags",
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetzen Sie die Tags durch die eingegebenen Tags (geben Sie keine Tags ein, um alle Tags zu löschen).", "ApplyTagsHelpTextReplace": "Ersetzen: Ersetze die Tags mit den eingegebenen Tags (keine Tags eingeben um alle Tags zu löschen)",
"ApplyTagsHelpTextHowToApplyDownloadClients": "So wenden Sie Tags auf die ausgewählten Download-Clients an", "ApplyTagsHelpTextHowToApplyDownloadClients": "Wie Tags zu den selektierten Downloadclients hinzugefügt werden können",
"CountIndexersSelected": "{0} Indexer ausgewählt", "CountIndexersSelected": "{0} Indexer ausgewählt",
"DeleteSelectedDownloadClients": "Lösche Download Client(s)", "DeleteSelectedDownloadClients": "Lösche Download Client(s)",
"DeleteSelectedDownloadClientsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Download-Clients löschen möchten?", "DeleteSelectedDownloadClientsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Download-Clients löschen möchten?",
@@ -988,7 +986,7 @@
"Clone": "Klonen", "Clone": "Klonen",
"AutomaticUpdatesDisabledDocker": "Automatische Updates werden bei Verwendung des Docker-Update-Mechanismus nicht direkt unterstützt. Sie müssen das Container-Image außerhalb von {appName} aktualisieren oder ein Skript verwenden", "AutomaticUpdatesDisabledDocker": "Automatische Updates werden bei Verwendung des Docker-Update-Mechanismus nicht direkt unterstützt. Sie müssen das Container-Image außerhalb von {appName} aktualisieren oder ein Skript verwenden",
"AutoAdd": "Automatisch hinzufügen", "AutoAdd": "Automatisch hinzufügen",
"WouldYouLikeToRestoreBackup": "Möchten Sie die Sicherung „{name} wiederherstellen?", "WouldYouLikeToRestoreBackup": "Willst du das Backup '{name}' wiederherstellen?",
"Unmonitored": "Nicht beobachtet", "Unmonitored": "Nicht beobachtet",
"Retention": "Aufbewahrung ( Retention )", "Retention": "Aufbewahrung ( Retention )",
"ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern", "ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern",
@@ -998,5 +996,72 @@
"BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen", "BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen",
"BlocklistOnly": "Nur der Sperrliste hinzufügen", "BlocklistOnly": "Nur der Sperrliste hinzufügen",
"BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen", "BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen",
"ChangeCategory": "Kategorie wechseln" "ChangeCategory": "Kategorie wechseln",
"TagIsNotUsedAndCanBeDeleted": "Tag wird nicht benutzt und kann gelöscht werden",
"ExtraFileExtensionsHelpText": "Kommaseparierte Liste von Dateiendungen die als Extra Dateien importiert werden sollen ( .nfo wird in .nfo-orig umbenannt )",
"DeleteImportListExclusion": "Importlisten Ausschluss löschen",
"IndexerFlags": "Indexer-Flags",
"CountAuthorsSelected": "{selectedCount} Künstler ausgewählt",
"ExtraFileExtensionsHelpTextsExamples": "Vorschläge: sub, nfo, srt, jpg",
"DownloadClient": "Downloader",
"ShowMonitoredHelpText": "Beobachtungsstatus unter dem Plakat anzeigen",
"CountDownloadClientsSelected": "{count} Download-Client(s) ausgewählt",
"CountImportListsSelected": "{selectedCount} Künstler ausgewählt",
"CustomFormatsSpecificationRegularExpression": "Regulären Ausdruck",
"DeleteImportListExclusionMessageText": "Bist du sicher, dass du diesen Importlisten Ausschluss löschen willst?",
"CustomFilter": "Benutzerdefinierter Filter",
"AutoRedownloadFailed": "Erneuter Download fehlgeschlagen",
"SourceTitle": "Quellentitel",
"Tags": "Tags",
"DownloadClientDelugeSettingsDirectoryHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
"ErrorLoadingContent": "Beim Laden des Eintrags ist ein Fehler aufgetreten",
"BypassIfAboveCustomFormatScore": "Umgehen, wenn über dem Wert des benutzerdefinierten Formats liegt",
"BypassIfAboveCustomFormatScoreHelpText": "Aktivieren Sie die Umgehung, wenn die Veröffentlichung einen Wert hat, der höher ist als der konfigurierte Mindestwert für das benutzerdefinierte Format",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Der Download-Client {downloadClientName} ist so eingestellt, dass abgeschlossene Downloads entfernt werden. Dies kann dazu führen, dass Downloads von Ihrem Client entfernt werden, bevor {appName} sie importieren kann.",
"AutoRedownloadFailedFromInteractiveSearch": "Erneuter Download aus Interaktiver Suche fehlgeschlagen",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Suchen Sie automatisch nach einer anderen Version und versuchen Sie, sie herunterzuladen, wenn eine fehlerhafte Version aus der interaktiven Suche ausgewählt wurde",
"FailedLoadingSearchResults": "Die Suchergebnisse konnten nicht geladen werden. Bitte versuchen Sie es erneut.",
"InteractiveSearchModalHeader": "Interaktive Suche",
"MinimumCustomFormatScoreHelpText": "Mindestwert für benutzerdefiniertes Format, der erforderlich ist, um Verzögerungen für das bevorzugte Protokoll zu umgehen",
"Rejections": "Ablehnungen",
"RemoveQueueItemConfirmation": "Bist du sicher, dass du {0} Einträge aus der Warteschlange entfernen willst?",
"SelectDropdown": "Auswählen...",
"SelectQuality": "Qualität auswählen",
"SelectReleaseGroup": "Wähle Release-Gruppe",
"ShowMonitored": "Beobachtete anzeigen",
"DeleteSelected": "Markierte löschen",
"Bookshelf": "Bücherregal",
"BypassIfHighestQualityHelpText": "Umgehen der Verzögerung, wenn die Freigabe die höchste aktivierte Qualität im Qualitätsprofil hat",
"CustomFormatsSpecificationFlag": "Markierung",
"DeleteFormat": "Formatierung löschen",
"CustomFormatsSpecificationRegularExpressionHelpText": "Benutzerdefiniertes Format RegEx ist nicht groß-/kleinschreibungssensitiv",
"IndexerDownloadClientHealthCheckMessage": "Indexer mit ungültigen Downloader: {0}.",
"AuthorProgressBarText": "{availableBookCount} / {bookCount} (Gesamt: {totalBookCount}, Dateien: {bookFileCount})",
"BookProgressBarText": "{bookCount} / {totalBookCount} (Dateien: {bookFileCount})",
"ChangeCategoryHint": "Änderung des Downloads in die 'Post-Import-Kategorie' vom Download-Client",
"ChangeCategoryMultipleHint": "Änderung der Downloads in die 'Post-Import-Kategorie' vom Download-Client",
"DoNotBlocklist": "Nicht Sperren",
"DoNotBlocklistHint": "Entfernen ohne Sperren",
"CustomFormatsSettingsTriggerInfo": "Ein Eigenes Format wird auf eine Veröffentlichung oder Datei angewandt, wenn sie mindestens einer der verschiedenen ausgewählten Bedingungen entspricht.",
"ConnectionSettingsUrlBaseHelpText": "Fügt ein Präfix zur {connectionName} URL hinzu, z. B. {url}",
"DownloadClientDelugeSettingsDirectory": "Download Verzeichnis",
"DownloadClientDelugeSettingsDirectoryCompleted": "Verschieben, wenn Verzeichnis abgeschlossen",
"IgnoreDownloadHint": "Hält {appName} von der weiteren Verarbeitung dieses Downloads ab",
"IgnoreDownloads": "Downloads ignorieren",
"IgnoreDownload": "Download ignorieren",
"IgnoreDownloadsHint": "Hindert {appName}, diese Downloads weiter zu verarbeiten",
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
"ApiKey": "API-Schlüssel",
"AuthBasic": "Basis (Browser-Popup)",
"AuthForm": "Formulare (Anmeldeseite)",
"AuthenticationMethod": "Authentifizierungsmethode",
"AuthenticationMethodHelpTextWarning": "Bitte wähle eine gültige Authentifizierungsmethode aus",
"AuthenticationRequired": "Authentifizierung benötigt",
"AuthenticationRequiredHelpText": "Ändern, welche anfragen Authentifizierung benötigen. Ändere nichts wenn du dir nicht des Risikos bewusst bist.",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Neues Passwort bestätigen",
"AuthenticationRequiredPasswordHelpTextWarning": "Gib ein neues Passwort ein",
"AuthenticationRequiredUsernameHelpTextWarning": "Gib einen neuen Benutzernamen ein",
"AuthenticationRequiredWarning": "Um unberechtigte Fernzugriffe zu vermeiden benötigt {appName} jetzt , dass Authentifizierung eingeschaltet ist. Du kannst Authentifizierung optional für lokale Adressen ausschalten.",
"DisabledForLocalAddresses": "Für lokale Adressen deaktiviert",
"Enabled": "Aktiviert"
} }
+25 -6
View File
@@ -9,7 +9,6 @@
"20MinutesTwenty": "20 λεπτά: {0}", "20MinutesTwenty": "20 λεπτά: {0}",
"45MinutesFourtyFive": "45 λεπτά: {0}", "45MinutesFourtyFive": "45 λεπτά: {0}",
"60MinutesSixty": "60 λεπτά: {0}", "60MinutesSixty": "60 λεπτά: {0}",
"APIKey": "Κλειδί API",
"About": "Σχετικά", "About": "Σχετικά",
"AddListExclusion": "Προσθήκη εξαίρεσης λίστας", "AddListExclusion": "Προσθήκη εξαίρεσης λίστας",
"AddingTag": "Προσθήκη ετικέτας", "AddingTag": "Προσθήκη ετικέτας",
@@ -41,7 +40,7 @@
"Calendar": "Ημερολόγιο", "Calendar": "Ημερολόγιο",
"CalendarWeekColumnHeaderHelpText": "Εμφανίζεται πάνω από κάθε στήλη όταν η εβδομάδα είναι η ενεργή προβολή", "CalendarWeekColumnHeaderHelpText": "Εμφανίζεται πάνω από κάθε στήλη όταν η εβδομάδα είναι η ενεργή προβολή",
"Cancel": "Ακύρωση", "Cancel": "Ακύρωση",
"CancelMessageText": "Είστε βέβαιοι ότι θέλετε να ακυρώσετε αυτήν την εργασία σε εκκρεμότητα;", "CancelPendingTask": "Είστε βέβαιοι ότι θέλετε να ακυρώσετε αυτήν την εργασία σε εκκρεμότητα;",
"CertificateValidation": "Επικύρωση πιστοποιητικού", "CertificateValidation": "Επικύρωση πιστοποιητικού",
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS.", "CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS.",
"ChangeFileDate": "Αλλαγή ημερομηνίας αρχείου", "ChangeFileDate": "Αλλαγή ημερομηνίας αρχείου",
@@ -425,13 +424,12 @@
"UsenetDelay": "Καθυστέρηση Usenet", "UsenetDelay": "Καθυστέρηση Usenet",
"UsenetDelayHelpText": "Καθυστέρηση σε λίγα λεπτά για να περιμένετε πριν πάρετε μια κυκλοφορία από το Usenet", "UsenetDelayHelpText": "Καθυστέρηση σε λίγα λεπτά για να περιμένετε πριν πάρετε μια κυκλοφορία από το Usenet",
"Username": "Όνομα χρήστη", "Username": "Όνομα χρήστη",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Υποκατάστημα για χρήση για την ενημέρωση του Radarr", "BranchUpdate": "Υποκατάστημα για χρήση για την ενημέρωση του Radarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Υποκατάστημα που χρησιμοποιείται από εξωτερικό μηχανισμό ενημέρωσης", "BranchUpdateMechanism": "Υποκατάστημα που χρησιμοποιείται από εξωτερικό μηχανισμό ενημέρωσης",
"Version": "Εκδοχή", "Version": "Εκδοχή",
"WeekColumnHeader": "Κεφαλίδα στήλης εβδομάδας", "WeekColumnHeader": "Κεφαλίδα στήλης εβδομάδας",
"Year": "Ετος", "Year": "Ετος",
"YesCancel": "Ναι, Ακύρωση", "YesCancel": "Ναι, Ακύρωση",
"ApiKeyHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ",
"LoadingBooksFailed": "Η φόρτωση αρχείων ταινίας απέτυχε", "LoadingBooksFailed": "Η φόρτωση αρχείων ταινίας απέτυχε",
"ProxyUsernameHelpText": "Πρέπει να εισαγάγετε ένα όνομα χρήστη και έναν κωδικό πρόσβασης μόνο εάν απαιτείται. Αφήστε τα κενά διαφορετικά.", "ProxyUsernameHelpText": "Πρέπει να εισαγάγετε ένα όνομα χρήστη και έναν κωδικό πρόσβασης μόνο εάν απαιτείται. Αφήστε τα κενά διαφορετικά.",
"SslCertPathHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ", "SslCertPathHelpTextWarning": "Απαιτείται επανεκκίνηση για να τεθεί σε ισχύ",
@@ -993,5 +991,26 @@
"AppUpdated": "{appName} Ενημερώθηκε", "AppUpdated": "{appName} Ενημερώθηκε",
"AppUpdatedVersion": "ξαναφορτωθεί", "AppUpdatedVersion": "ξαναφορτωθεί",
"AutoAdd": "Προσθήκη", "AutoAdd": "Προσθήκη",
"ErrorLoadingContent": "Υπήρξε ένα σφάλμα κατά τη φόρτωση του αρχείου" "ErrorLoadingContent": "Υπήρξε ένα σφάλμα κατά τη φόρτωση του αρχείου",
"ExtraFileExtensionsHelpTextsExamples": "Παραδείγματα: «.sub, .nfo» ή «sub, nfo»",
"IndexerFlags": "Σημαίες ευρετηρίου",
"InteractiveSearchModalHeader": "Διαδραστική αναζήτηση",
"ExtraFileExtensionsHelpText": "Λίστα πρόσθετων αρχείων που διαχωρίζονται με κόμμα για εισαγωγή (το .nfo θα εισαχθεί ως .nfo-orig)",
"CustomFilter": "Custom Φιλτρα",
"DeleteSelected": "Διαγραφή επιλεγμένων",
"SourceTitle": "Τίτλος πηγής",
"AutoRedownloadFailed": "Η λήψη απέτυχε",
"FailedLoadingSearchResults": "Αποτυχία φόρτωσης αποτελεσμάτων αναζήτησης, δοκιμάστε ξανά.",
"RemoveQueueItemConfirmation": "Είστε σίγουροι πως θέλετε να διαγράψετε {0} αντικείμενα από την ουρά;",
"SelectDropdown": "'Επιλέγω...",
"SelectQuality": "Επιλέξτε Ποιότητα",
"SelectReleaseGroup": "Επιλέξτε Ομάδα έκδοσης",
"AuthBasic": "Βασικό (Αναδυόμενο παράθυρο προγράμματος περιήγησης)",
"AuthForm": "Φόρμες (σελίδα σύνδεσης)",
"AuthenticationRequired": "Απαιτείται πιστοποίηση",
"AuthenticationRequiredHelpText": "Αλλαγή για τα οποία απαιτείται έλεγχος ταυτότητας. Μην αλλάζετε αν δεν κατανοήσετε τους κινδύνους.",
"AuthenticationRequiredWarning": "Για να αποτρέψει την απομακρυσμένη πρόσβαση χωρίς έλεγχο ταυτότητας, το {appName} απαιτεί τώρα να ενεργοποιηθεί ο έλεγχος ταυτότητας. Διαμορφώστε τη μέθοδο ελέγχου ταυτότητας και τα διαπιστευτήριά σας. Μπορείτε προαιρετικά να απενεργοποιήσετε τον έλεγχο ταυτότητας από τοπικές διευθύνσεις. Ανατρέξτε στις Συχνές Ερωτήσεις για πρόσθετες πληροφορίες.",
"Enabled": "Ενεργοποιήθηκε",
"ApiKey": "Κλειδί API",
"DisabledForLocalAddresses": "Απενεργοποιήθηκε για τοπικές διευθύνσεις"
} }
+41 -6
View File
@@ -2,7 +2,6 @@
"20MinutesTwenty": "20 Minutes: {0}", "20MinutesTwenty": "20 Minutes: {0}",
"45MinutesFourtyFive": "45 Minutes: {0}", "45MinutesFourtyFive": "45 Minutes: {0}",
"60MinutesSixty": "60 Minutes: {0}", "60MinutesSixty": "60 Minutes: {0}",
"APIKey": "API Key",
"ASIN": "ASIN", "ASIN": "ASIN",
"About": "About", "About": "About",
"Actions": "Actions", "Actions": "Actions",
@@ -33,7 +32,7 @@
"AnalyticsEnabledHelpText": "Send anonymous usage and error information to Readarr's servers. This includes information on your browser, which Readarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.", "AnalyticsEnabledHelpText": "Send anonymous usage and error information to Readarr's servers. This includes information on your browser, which Readarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.",
"AnalyticsEnabledHelpTextWarning": "Requires restart to take effect", "AnalyticsEnabledHelpTextWarning": "Requires restart to take effect",
"AnyEditionOkHelpText": "Readarr will automatically switch to the edition best matching downloaded files", "AnyEditionOkHelpText": "Readarr will automatically switch to the edition best matching downloaded files",
"ApiKeyHelpTextWarning": "Requires restart to take effect", "ApiKey": "API Key",
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file", "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
"AppDataDirectory": "AppData Directory", "AppDataDirectory": "AppData Directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update", "AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
@@ -50,9 +49,20 @@
"ApplyTagsHelpTextHowToApplyIndexers": "How to apply tags to the selected indexers", "ApplyTagsHelpTextHowToApplyIndexers": "How to apply tags to the selected indexers",
"ApplyTagsHelpTextRemove": "Remove: Remove the entered tags", "ApplyTagsHelpTextRemove": "Remove: Remove the entered tags",
"ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)", "ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)",
"AptUpdater": "Use apt to install the update",
"AudioFileMetadata": "Write Metadata to Audio Files", "AudioFileMetadata": "Write Metadata to Audio Files",
"AuthBasic": "Basic (Browser Popup)",
"AuthForm": "Forms (Login Page)",
"Authentication": "Authentication", "Authentication": "Authentication",
"AuthenticationMethodHelpText": "Require Username and Password to access Readarr", "AuthenticationMethod": "Authentication Method",
"AuthenticationMethodHelpText": "Require Username and Password to access {appName}",
"AuthenticationMethodHelpTextWarning": "Please select a valid authentication method",
"AuthenticationRequired": "Authentication Required",
"AuthenticationRequiredHelpText": "Change which requests authentication is required for. Do not change unless you understand the risks.",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirm new password",
"AuthenticationRequiredPasswordHelpTextWarning": "Enter a new password",
"AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username",
"AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.",
"Author": "Author", "Author": "Author",
"AuthorClickToChangeBook": "Click to change book", "AuthorClickToChangeBook": "Click to change book",
"AuthorEditor": "Author Editor", "AuthorEditor": "Author Editor",
@@ -107,6 +117,9 @@
"BooksTotal": "Books ({0})", "BooksTotal": "Books ({0})",
"Bookshelf": "Bookshelf", "Bookshelf": "Bookshelf",
"Branch": "Branch", "Branch": "Branch",
"BranchUpdate": "Branch to use to update {appName}",
"BranchUpdateMechanism": "Branch used by external update mechanism",
"BuiltIn": "Built-In",
"BypassIfAboveCustomFormatScore": "Bypass if Above Custom Format Score", "BypassIfAboveCustomFormatScore": "Bypass if Above Custom Format Score",
"BypassIfAboveCustomFormatScoreHelpText": "Enable bypass when release has a score higher than the configured minimum custom format score", "BypassIfAboveCustomFormatScoreHelpText": "Enable bypass when release has a score higher than the configured minimum custom format score",
"BypassIfHighestQuality": "Bypass if Highest Quality", "BypassIfHighestQuality": "Bypass if Highest Quality",
@@ -128,7 +141,7 @@
"CalibreUrlBase": "Calibre Url Base", "CalibreUrlBase": "Calibre Url Base",
"CalibreUsername": "Calibre Username", "CalibreUsername": "Calibre Username",
"Cancel": "Cancel", "Cancel": "Cancel",
"CancelMessageText": "Are you sure you want to cancel this pending task?", "CancelPendingTask": "Are you sure you want to cancel this pending task?",
"CatalogNumber": "Catalog Number", "CatalogNumber": "Catalog Number",
"CertificateValidation": "Certificate Validation", "CertificateValidation": "Certificate Validation",
"CertificateValidationHelpText": "Change how strict HTTPS certification validation is. Do not change unless you understand the risks.", "CertificateValidationHelpText": "Change how strict HTTPS certification validation is. Do not change unless you understand the risks.",
@@ -188,6 +201,7 @@
"CreateEmptyAuthorFolders": "Create empty author folders", "CreateEmptyAuthorFolders": "Create empty author folders",
"CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan", "CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan",
"CreateGroup": "Create group", "CreateGroup": "Create group",
"CurrentlyInstalled": "Currently Installed",
"CustomFilter": "Custom Filter", "CustomFilter": "Custom Filter",
"CustomFormat": "Custom Format", "CustomFormat": "Custom Format",
"CustomFormatScore": "Custom Format Score", "CustomFormatScore": "Custom Format Score",
@@ -277,12 +291,14 @@
"DetailedProgressBarHelpText": "Show text on progress bar", "DetailedProgressBarHelpText": "Show text on progress bar",
"Development": "Development", "Development": "Development",
"Disabled": "Disabled", "Disabled": "Disabled",
"DisabledForLocalAddresses": "Disabled for Local Addresses",
"DiscCount": "Disc Count", "DiscCount": "Disc Count",
"DiscNumber": "Disc Number", "DiscNumber": "Disc Number",
"DiskSpace": "Disk Space", "DiskSpace": "Disk Space",
"DoNotBlocklist": "Do not Blocklist", "DoNotBlocklist": "Do not Blocklist",
"DoNotBlocklistHint": "Remove without blocklisting", "DoNotBlocklistHint": "Remove without blocklisting",
"Docker": "Docker", "Docker": "Docker",
"DockerUpdater": "Update the docker container to receive the update",
"DownloadClient": "Download Client", "DownloadClient": "Download Client",
"DownloadClientCheckDownloadingToRoot": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", "DownloadClientCheckDownloadingToRoot": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.",
"DownloadClientCheckNoneAvailableMessage": "No download client is available", "DownloadClientCheckNoneAvailableMessage": "No download client is available",
@@ -331,6 +347,7 @@
"EnableRssHelpText": "Will be used when Readarr periodically looks for releases via RSS Sync", "EnableRssHelpText": "Will be used when Readarr periodically looks for releases via RSS Sync",
"EnableSSL": "Enable SSL", "EnableSSL": "Enable SSL",
"EnableSslHelpText": " Requires restart running as administrator to take effect", "EnableSslHelpText": " Requires restart running as administrator to take effect",
"Enabled": "Enabled",
"EnabledHelpText": "Check to enable release profile", "EnabledHelpText": "Check to enable release profile",
"Ended": "Ended", "Ended": "Ended",
"EndedAllBooksDownloaded": "Ended (All books downloaded)", "EndedAllBooksDownloaded": "Ended (All books downloaded)",
@@ -345,9 +362,14 @@
"ExistingTag": "Existing tag", "ExistingTag": "Existing tag",
"ExistingTagsScrubbed": "Existing tags scrubbed", "ExistingTagsScrubbed": "Existing tags scrubbed",
"ExportCustomFormat": "Export Custom Format", "ExportCustomFormat": "Export Custom Format",
"External": "External",
"ExternalUpdater": "{appName} is configured to use an external update mechanism",
"ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)", "ExtraFileExtensionsHelpText": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
"ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'", "ExtraFileExtensionsHelpTextsExamples": "Examples: '.sub, .nfo' or 'sub,nfo'",
"FailedDownloadHandling": "Failed Download Handling", "FailedDownloadHandling": "Failed Download Handling",
"FailedLoadingSearchResults": "Failed to load search results, please try again.",
"FailedToFetchSettings": "Failed to fetch settings",
"FailedToFetchUpdates": "Failed to fetch updates",
"FailedToLoadQueue": "Failed to load Queue", "FailedToLoadQueue": "Failed to load Queue",
"FileDateHelpText": "Change file date on import/rescan", "FileDateHelpText": "Change file date on import/rescan",
"FileDetails": "File Details", "FileDetails": "File Details",
@@ -456,11 +478,20 @@
"IndexerSearchCheckNoAvailableIndexersMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerSearchCheckNoAvailableIndexersMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors",
"IndexerSearchCheckNoInteractiveMessage": "No indexers available with Interactive Search enabled, Readarr will not provide any interactive search results", "IndexerSearchCheckNoInteractiveMessage": "No indexers available with Interactive Search enabled, Readarr will not provide any interactive search results",
"IndexerSettings": "Indexer Settings", "IndexerSettings": "Indexer Settings",
"IndexerSettingsSeedRatio": "Seed Ratio",
"IndexerSettingsSeedRatioHelpText": "The ratio a torrent should reach before stopping, empty uses the download client's default. Ratio should be at least 1.0 and follow the indexers rules",
"IndexerSettingsSeedTime": "Seed Time",
"IndexerSettingsSeedTimeHelpText": "The time a torrent should be seeded before stopping, empty uses the download client's default",
"IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures", "IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures",
"IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}", "IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}",
"IndexerTagsHelpText": "Only use this indexer for authors with at least one matching tag. Leave blank to use with all authors.", "IndexerTagsHelpText": "Only use this indexer for authors with at least one matching tag. Leave blank to use with all authors.",
"Indexers": "Indexers", "Indexers": "Indexers",
"IndexersSettingsSummary": "Indexers and release restrictions", "IndexersSettingsSummary": "Indexers and release restrictions",
"Install": "Install",
"InstallLatest": "Install Latest",
"InstallMajorVersionUpdate": "Install Update",
"InstallMajorVersionUpdateMessage": "This update will install a new major version and may not be compatible with your system. Are you sure you want to install this update?",
"InstallMajorVersionUpdateMessageLink": "Please check [{domain}]({url}) for more information.",
"InstanceName": "Instance Name", "InstanceName": "Instance Name",
"InstanceNameHelpText": "Instance name in tab and for Syslog app name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name",
"InteractiveSearchModalHeader": "Interactive Search", "InteractiveSearchModalHeader": "Interactive Search",
@@ -657,6 +688,7 @@
"OnHealthIssueHelpText": "On Health Issue", "OnHealthIssueHelpText": "On Health Issue",
"OnImportFailure": "On Import Failure", "OnImportFailure": "On Import Failure",
"OnImportFailureHelpText": "On Import Failure", "OnImportFailureHelpText": "On Import Failure",
"OnLatestVersion": "The latest version of {appName} is already installed",
"OnReleaseImport": "On Release Import", "OnReleaseImport": "On Release Import",
"OnReleaseImportHelpText": "On Release Import", "OnReleaseImportHelpText": "On Release Import",
"OnRename": "On Rename", "OnRename": "On Rename",
@@ -674,6 +706,7 @@
"PageSize": "Page Size", "PageSize": "Page Size",
"PageSizeHelpText": "Number of items to show on each page", "PageSizeHelpText": "Number of items to show on each page",
"Password": "Password", "Password": "Password",
"PasswordConfirmation": "Password Confirmation",
"PasswordHelpText": "Calibre content server password", "PasswordHelpText": "Calibre content server password",
"PastDays": "Past Days", "PastDays": "Past Days",
"PastDaysHelpText": "Days for iCal feed to look into the past", "PastDaysHelpText": "Days for iCal feed to look into the past",
@@ -688,6 +721,7 @@
"PosterSize": "Poster Size", "PosterSize": "Poster Size",
"PreviewRename": "Preview Rename", "PreviewRename": "Preview Rename",
"PreviewRetag": "Preview Retag", "PreviewRetag": "Preview Retag",
"PreviouslyInstalled": "Previously Installed",
"Profiles": "Profiles", "Profiles": "Profiles",
"ProfilesSettingsSummary": "Quality, Metadata, Delay, and Release profiles", "ProfilesSettingsSummary": "Quality, Metadata, Delay, and Release profiles",
"Progress": "Progress", "Progress": "Progress",
@@ -840,6 +874,7 @@
"SSLPort": "SSL Port", "SSLPort": "SSL Port",
"Save": "Save", "Save": "Save",
"Scheduled": "Scheduled", "Scheduled": "Scheduled",
"Script": "Script",
"ScriptPath": "Script Path", "ScriptPath": "Script Path",
"Search": "Search", "Search": "Search",
"SearchAll": "Search All", "SearchAll": "Search All",
@@ -1033,6 +1068,7 @@
"UnmonitoredHelpText": "Include unmonitored books in the iCal feed", "UnmonitoredHelpText": "Include unmonitored books in the iCal feed",
"UnselectAll": "Unselect All", "UnselectAll": "Unselect All",
"UpdateAll": "Update all", "UpdateAll": "Update all",
"UpdateAppDirectlyLoadError": "Unable to update {appName} directly,",
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates", "UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
"UpdateAvailable": "New update is available", "UpdateAvailable": "New update is available",
"UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", "UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.",
@@ -1061,14 +1097,13 @@
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent provided by the app that called the API", "UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent provided by the app that called the API",
"Username": "Username", "Username": "Username",
"UsernameHelpText": "Calibre content server username", "UsernameHelpText": "Calibre content server username",
"UsingExternalUpdateMechanismBranchToUseToUpdateReadarr": "Branch to use to update Readarr",
"UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism": "Branch used by external update mechanism",
"Version": "Version", "Version": "Version",
"Wanted": "Wanted", "Wanted": "Wanted",
"WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder", "WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder",
"WatchRootFoldersForFileChanges": "Watch Root Folders for file changes", "WatchRootFoldersForFileChanges": "Watch Root Folders for file changes",
"WeekColumnHeader": "Week Column Header", "WeekColumnHeader": "Week Column Header",
"WhatsNew": "What's New?", "WhatsNew": "What's New?",
"WhySearchesCouldBeFailing": "Click here to find out why searches could be failing",
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?", "WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
"WriteAudioTags": "Tag Audio Files with Metadata", "WriteAudioTags": "Tag Audio Files with Metadata",
"WriteAudioTagsScrub": "Scrub Existing Tags", "WriteAudioTagsScrub": "Scrub Existing Tags",

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