Compare commits

...

139 Commits

Author SHA1 Message Date
Bogdan
e33b192881 New: Show current tags for Connections
(cherry picked from commit 2016f11b1c322fcd5f9e2f09328ca19d2114629d)

Fixes #2512
2023-05-20 02:18:34 +03:00
Weblate
00a532c656 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-05-19 04:49:15 +03:00
Servarr
501cefa2f4 Automated API Docs update 2023-05-19 04:46:42 +03:00
Qstick
1a3e5fd738 New: Advanced settings toggle in indexer edit/add modal
(cherry picked from commit 94a8ef63044f47b615facbb6e04200bdd3797189)

Fixes #2506
2023-05-19 04:33:28 +03:00
Bogdan
751ade0338 Add forceSave to the OpenAPI docs
(cherry picked from commit ef0b91b45bc7f1295efb03dc44630f1442e18be1)

Fixes #2504
2023-05-19 04:26:16 +03:00
Bogdan
e6da9d26fd Fixed: Use indexer errors from response if Content-Type is XML before processing response
(cherry picked from commit 9bdc6183663a3510e53433a30ad701065e7ee9d9)
2023-05-19 04:23:47 +03:00
Bogdan
ccd8d93e82 Fixed: Log name of mount point failure
(cherry picked from commit b5050d02d6adbaaaa0f8ae9f8426551e5606fff1)

Fixes #2503
2023-05-18 04:17:16 +03:00
Mark McDowall
a0ea9d4750 Fixed: Prevent getting disk space from returning no information when it partially fails
(cherry picked from commit 2c65e4fa41418157d0d27b34c3bab80158cff219)

Closes #2198
Closes #2199
2023-05-18 04:15:44 +03:00
Bogdan
a07b9a19ec Sort tags by label
Co-authored-by: Mark McDowall <markus.mcd5@gmail.com>
(cherry picked from commit f32a3cd41c17bb9cb829ac24732cfeec6a18d569)

Fixes #2500
2023-05-18 04:11:50 +03:00
Bogdan
9ba1caaf94 Fix checking for SameTorrent when the indexer is null
(cherry picked from commit 3ece1533d86c559ec1bf7689c908802f31e38e91)
2023-05-18 04:08:27 +03:00
Weblate
d90a6ebbb1 Translated using Weblate (Indonesian) [skip ci]
Currently translated at 2.7% (25 of 920 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 66.6% (613 of 920 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 15.3% (141 of 920 strings)

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

Currently translated at 15.7% (145 of 920 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 67.7% (623 of 920 strings)

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

Currently translated at 0.7% (7 of 920 strings)

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

Currently translated at 98.9% (910 of 920 strings)

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

Currently translated at 100.0% (920 of 920 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.9% (708 of 920 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 61.4% (565 of 920 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 67.6% (622 of 920 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 65.6% (604 of 920 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 73.0% (672 of 920 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 98.2% (904 of 920 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 63.9% (588 of 920 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 78.4% (722 of 920 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 88.3% (813 of 920 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 98.8% (909 of 920 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.2% (904 of 920 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.9% (625 of 920 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 67.2% (619 of 920 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: RudyBzh <rudybzh@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/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/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/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/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sk/
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
2023-05-17 17:42:07 +03:00
Weblate
d7575f38a5 Translated using Weblate (French) [skip ci]
Currently translated at 77.2% (711 of 920 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 77.2% (711 of 920 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 77.2% (711 of 920 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: RudyBzh <rudybzh@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translation: Servarr/Readarr
2023-05-16 12:21:08 +03:00
Servarr
833fb9347f Automated API Docs update 2023-05-15 18:10:57 +03:00
Bogdan
b44f4237d2 Use string interpolation in formatBytes
(cherry picked from commit 3508cb63dc0b2fbe0eddb00c8396a22705030f6f)

Closes #2495
2023-05-15 18:05:39 +03:00
Bogdan
19eec0cb88 Fix validation for boolean query parameters
(cherry picked from commit 2fecd280016630d5afe2d60e52cbb52338c155b5)

Closes #2493
2023-05-15 18:05:39 +03:00
Anonymous
43367504a4 Translated using Weblate (Turkish) [skip ci]
Currently translated at 62.1% (567 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
2023-05-14 21:58:24 -05:00
Anonymous
a11b6088dd Translated using Weblate (Romanian) [skip ci]
Currently translated at 60.5% (552 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
2023-05-14 21:58:18 -05:00
Anonymous
ba6a3ef564 Translated using Weblate (Icelandic) [skip ci]
Currently translated at 62.0% (566 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
2023-05-14 21:58:10 -05:00
Anonymous
d536e2c582 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 98.7% (901 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
2023-05-14 21:58:10 -05:00
Anonymous
72f1d1cf4d Translated using Weblate (Hebrew) [skip ci]
Currently translated at 64.1% (585 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
2023-05-14 21:58:08 -05:00
Anonymous
07a3ee76aa Translated using Weblate (German) [skip ci]
Currently translated at 98.6% (900 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
2023-05-14 21:58:02 -05:00
Anonymous
b34cc0790b Translated using Weblate (Bulgarian) [skip ci]
Currently translated at 62.0% (566 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
2023-05-14 21:58:02 -05:00
Anonymous
1c59aa1ac4 Translated using Weblate (Arabic) [skip ci]
Currently translated at 62.1% (567 of 912 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
2023-05-14 21:58:02 -05:00
Weblate
16753a9fc7 Translated using Weblate (Danish) [skip ci]
Currently translated at 62.5% (570 of 912 strings)

Update translation files [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translation: Servarr/Readarr
2023-05-14 21:57:39 -05:00
Weblate
32a62aec2d Translated using Weblate (Danish) [skip ci]
Currently translated at 62.5% (570 of 912 strings)

Update translation files [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translation: Servarr/Readarr
2023-05-14 21:52:24 -05:00
Bogdan
045f1a85df Fixed: Prevent unknown settings implementation from failing to deserialize
(cherry picked from commit 0e2cc7851f556e928e52bb2886c7d60c13b0741e)

Log removal of invalid definitions as warnings

(cherry picked from commit 3d61719a2cc9c87ac3c92b5358bb5034aed4c2ff)

(cherry picked from commit 498722b240c17d310863ed604c441a9c507ddcd6)

Closes #2491
2023-05-14 21:09:16 +03:00
Servarr
9f3c0cf914 Automated API Docs update 2023-05-13 22:02:05 +03:00
Mark McDowall
b7fb42345c New: Health check for import lists with missing root folders
New: Show missing root folder path in edit for Import List

(cherry picked from commit ae196af2ad368d49fde2358f0987ed7650c7f29c)

Closes #821
2023-05-13 21:37:55 +03:00
Mark McDowall
44673eb4ee Fixed: Quality cutoff updating in UI when adding/removing qualities
(cherry picked from commit fea66cb7bccc7e94523614db38b157fa38f55ea5)

Closes #2051
2023-05-13 21:24:09 +03:00
Qiming Chen
ff4594aa08 New: Link indexer to specific download client
Co-authored-by: Qstick <qstick@gmail.com>

(cherry picked from commit 13aaa20f1bf1448fa804738804205cb16f0d91f9)

Closes #1485
2023-05-13 21:06:54 +03:00
bakerboy448
1495fa183f New: Various HealthCheck Improvements
(cherry picked from commit 0f6f6814388b25b8fb3fccb6f33bfa16e962549f)

Closes #2310
2023-05-13 20:42:02 +03:00
Qstick
2f7d7fb220 Fixed: Provider health checks persist after add until next scheduled check
(cherry picked from commit 202449c40c82c6dfd2d15844c578436bbe3c8872)

Closes #2485
2023-05-13 18:56:41 +03:00
Mark McDowall
3f58693780 Fix function name and use out var for try get in DownloadClientProvider
(cherry picked from commit a953d1a6c50bc1ee05a2fc4183d2b4d9aba5b586)
2023-05-13 18:55:05 +03:00
Bogdan
d7b1a36a50 Fix NewznabRequestGenerator tests 2023-05-13 04:38:59 +03:00
Bogdan
b55c09ba3d Add tier for search by book title only 2023-05-13 04:18:04 +03:00
Bogdan
5358b7f7ec Update UI dependencies 2023-05-13 01:53:27 +03:00
Benjamin Staneck
8e95a87ec3 Add inset to stylelintrc
(cherry picked from commit 6a49f3989a17898c957df8777f0cbb19af647804)

Closes #2456
2023-05-13 01:46:32 +03:00
Benjamin Staneck
d6112d1d8e Remove unused babel plugins and fix build with profiling
(cherry picked from commit d79f42351fd3d61d180a224d4b8fb51184eb347e)

Close #2455
2023-05-13 01:45:48 +03:00
Benjamin Staneck
72ef8b91d4 Update all relevant dev tool deps
Delete esformatter

Address lint failures

Delete unknown component property

remove deprecated stylelint rules

Address stylelint violation

Update rimraf

(cherry picked from commit 4aba540b894729c730640f03b2f96c451af2dba0)

Close #2454
Close #2460
2023-05-13 01:43:33 +03:00
Bogdan
9423ddeb34 Fixed: Add search by book title only
Close #2481
2023-05-13 00:27:02 +03:00
Bogdan
f7b2bba2e7 Fix SupportedBookSearchParameters in Newznab 2023-05-13 00:21:51 +03:00
Bogdan
5609dfa590 Add Pull Request Labeler 2023-05-12 21:46:59 +03:00
Mark McDowall
c43e9eb208 New: Log additional information when processing completed torrents from rTorrent
(cherry picked from commit c7d39579b45adbe1b9da3baff587b2d7b7c9724b)

Closes #2482
2023-05-12 18:39:36 +03:00
Bogdan
0411102f57 Remove unused imports
Closes #2475
2023-05-11 21:28:22 +03:00
Qstick
f26fd39709 Use Environment.ProcessPath instead of GetCurrentProcess().MainModule.FileName
GetCurrentProcess().MainModule.FileName is expensive, Environment.ProcessPath added in net6

(cherry picked from commit f928ee7cad304115a002da00e218fb987e02b279)

Close #2260
2023-05-11 21:24:30 +03:00
Qstick
55308bef8b Prefer AsSpan to Substring to avoid unnecessary allocation
(cherry picked from commit 4db10e6283fa5c5c167604a6cdade7299d567f4d)

Close #2263
2023-05-11 21:05:40 +03:00
Qstick
6827ac5670 Use span-based string.Concat to avoid unnecessary allocation
Calling Substring produces a copy of the extracted substring. By using AsSpan instead of Substring and calling the overload of string.Concat that accepts spans, you can eliminate the unnecessary string allocation.

(cherry picked from commit e8aff90582fb50b2d48dea3a4c2139c2745f1554)

Fixes #2262
2023-05-11 21:04:42 +03:00
Mark McDowall
0572bde41e Why rename many files when few file do trick
(cherry picked from commit eaa4a358e8eb93e15203001d16e868e22aded5c3)

Closes #2465
2023-05-11 19:32:24 +03:00
Mark McDowall
0eb88cb516 GracePeriod not Graceperiod
(cherry picked from commit 993c69530ed34460800f40ecf8a0b7bc9a2f7d48)

Closes #2462
2023-05-11 19:31:22 +03:00
Bogdan
a39be51d3e Remove empty constructors
Closes #2474
2023-05-11 19:29:57 +03:00
Bogdan
b37fd60b85 API key improvements
Fixed: Special characters in API key
New: Add heathcheck for API Key

(cherry picked from commit 9325140b90f8ac625ae5b26075748c22f6f06158)

Closes #2466
2023-05-09 14:17:14 +03:00
ta264
c827859ba0 Fixed: Don't buffer update package to memory when downloading
(cherry picked from commit 63654b99f22b87b42acd699ac5b453f2de20211f)

Closes #2467
2023-05-09 14:09:44 +03:00
Servarr
35b466e4ca Automated API Docs update 2023-05-08 00:45:47 +03:00
Qstick
486ec14ca8 Check for nullable last activity list sync
(cherry picked from commit 4a740acb801a04bc2ead45d272d493f4ec46f7e8)
2023-05-07 23:15:24 +03:00
Qstick
86d1250831 New: Rework List sync interval logic
(cherry picked from commit c522cd120d08757e7e43c2348be4d7f05a254fac)
2023-05-07 23:15:24 +03:00
Qstick
145422e00a Fixed: Don't enforce minimum on single list fetch
(cherry picked from commit 62354dfac83e36f2d6a8cf24fc96fa25392bc438)
2023-05-07 23:15:24 +03:00
Bogdan
3a274bdc4a Fixed: Ensure indexer errors are handled before processing response
(cherry picked from commit 76f93c8415419f0c3dab90582d47a1c9a653263c)

Closes #2458
2023-05-07 23:12:17 +03:00
Bogdan
e9ada0b43d Fixed: custom script error when importing some downloads
Co-authored-by: Qstick <376117+Qstick@users.noreply.github.com>

(cherry picked from commit 8f482c534f15c14a9b3097313a4f5e9273549d88)

Closes #2457
2023-05-07 23:11:30 +03:00
Benjamin Staneck
a2832cf329 Delete various old config files
Delete `jsconfig.json`

This file actually did nothing since we have a `tsconfig.json`. Behavior does not change since `checkJs` is `false` in both.

Delete `.jsbeautifyrc`

Was not used from what I could tell and we have a ESFormatter config file as well and that is basically the successor.

Delete `.csscomb.json`

Was not used from what I could tell, also the project seems dead, last publish 4 years ago. Also we have stylelint in place that covers CSS.

(cherry picked from commit 0da89478cc7a5eec7a35bff47e34b824487661a1)

Closes #2449
2023-05-07 23:10:14 +03:00
Bogdan
bbdecb343b Log invalid config file exceptions
(cherry picked from commit a95317446c452926819ad24f892a00770b1b23fc)

Closes #2448
2023-05-07 23:09:46 +03:00
Benjamin Staneck
a857e7c6f4 Add VSCode extension recommendations
To make it easier for new contributors, suggest extensions for the tools we use

(cherry picked from commit 9ebd2f96adb19db7c7357336a37f7b989d21797d)
2023-05-07 23:00:26 +03:00
Benjamin Staneck
906fb30179 Move vscode settings to the frontend folder
Since it applies to all of frontend, I think it makes more sense to have it here instead of src

(cherry picked from commit e12c679cd8961ec9d2ef744761303831b81e64fb)
2023-05-07 22:59:57 +03:00
Stepan Goremykin
28f64d9a46 Migrate to FluentValidation 9
(cherry picked from commit 40e54685b9e83ed24a3979bfe965c453339ad02e)
2023-05-07 17:57:58 +03:00
Mark McDowall
816969d0f5 Add support for custom RetryAfter in RequestLimitReachedException
(cherry picked from commit 47cf8e6430b7f7704ce2f1524fa9e3c8e6f9b47a)
2023-05-07 16:00:40 +03:00
Mark McDowall
63506e5a72 New: Only add version header for API requests
(cherry picked from commit 453891e620459ff38f7bc43b207004b240fc5fb8)
2023-05-07 13:47:10 +03:00
Bogdan
817ea75288 New: Add token authentication for ntfy.sh notifications
Co-authored-by: KucharczykL <lukas@kucharczyk.xyz>

(cherry picked from commit 5bb03a9ddf4d2d33976dfdc39fc70bcf56bf1b49)
2023-05-07 12:13:25 +03:00
Bogdan
7e0eca5657 Fix parameters generation in FileListRequestGenerator 2023-05-07 03:10:00 +03:00
Stevie Robinson
5587af7806 Fixed: Mass Editor Footer on Smaller Screens
(cherry picked from commit 9afcec8b1ffc11da93ae50b73f77f5ebe6e12391)
2023-05-06 17:06:34 -05:00
Servarr
a90f5f7b4e Automated API Docs update 2023-05-06 17:00:37 -05:00
6cUbi57z
b97d63cb5b New: Add tag support to indexers
(cherry picked from commit c3d54b312ef18b837d54605ea78f1a263fd6900b)
2023-05-07 00:55:46 +03:00
Bogdan
10e230cc06 New: Log content for invalid torrent files
(cherry picked from commit e3f71ca79c3c92015e6c3fc292ae3124dab63410)
2023-05-06 23:26:31 +03:00
Bogdan
5c5c362d96 Update UI Dependencies 2023-05-06 21:08:11 +03:00
Mark McDowall
0aec2382fe Switch to eslint for linting
(cherry picked from commit a18c3774661f466727ab46315211aecb43ef1def)
2023-05-06 21:08:11 +03:00
Bogdan
677d5d3374 Revert "Fixed: Indexer tags"
This reverts commit 761a6a9136.
2023-05-06 20:11:17 +03:00
Bogdan
ca0f2be194 Fix NewznabRequestGenerator tests 2023-05-06 19:46:34 +03:00
Mark McDowall
780df3250f Use string interpolation for Newznab request generation
(cherry picked from commit ba9651b241510ff585aaac689dc0d0be4f7fbcf3)

Closes #1945
2023-05-06 19:38:33 +03:00
Bogdan
508b2d7c8d Revert "Fixed: Don't enforce minimum on single list fetch"
This reverts commit c34418b984.
2023-05-06 18:29:43 +03:00
bakerboy448
96aeb022ab Fixed: Escape Characters as needed for *znab queries
(cherry picked from commit f678775e5c3deb0c520f88a137198331fee2831f)
2023-05-06 17:10:52 +03:00
Marty Zalega
b27f852154 Don't lowercase UrlBase in ConfigFileProvider
UrlBase should honour the case it is given.

(cherry picked from commit e1de523c89f7649e64f520b090bbdb2f56cc4b85)
2023-05-06 17:04:50 +03:00
Mark McDowall
3c03413d5a New: Return static response to requests while app is starting
(cherry picked from commit 303fc5d786ccf2ad14c8523fc239696c5d37ea53)
2023-05-06 16:59:53 +03:00
Qstick
c34418b984 Fixed: Don't enforce minimum on single list fetch
(cherry picked from commit 62354dfac83e36f2d6a8cf24fc96fa25392bc438)
2023-05-06 16:52:19 +03:00
Mark McDowall
761a6a9136 Fixed: Indexer tags
(cherry picked from commit 6f5467e39e059bdfec0d7dd49485028195c38586)
2023-05-06 16:49:49 +03:00
Qstick
92c59e158d Convert method to static that doesn't use instance data
(cherry picked from commit a42f97229acb713719c616851db572100f319ad7)
2023-05-06 16:47:00 +03:00
Lars
5bc917c9dc New: Option to use Telegram topics for notifications
(cherry picked from commit c8933d812490ba375c6f7e07cd4355921dc513ac)
2023-05-06 16:42:14 +03:00
Mark McDowall
7c03ca5cdf Fixed IsValidPath usages
(cherry picked from commit 033936dce7e13c8ab2e38407782dc9cdd949460e)

Closes #2313
2023-05-06 15:28:28 +03:00
Mark McDowall
b3cf903a3b New: Improve path validation when handling paths from different OSes
(cherry picked from commit 0321368cc392d7a0a488409bf6bd586ba45497af)

Closes #2309
2023-05-06 15:26:15 +03:00
Stepan Goremykin
a4930474a5 Use MinBy and MaxBy instead of OrderBy + First
(cherry picked from commit 6ea3d8c127eafbcf9d1b6e9338b737e91e256875)

Closes #2323
2023-05-06 15:00:25 +03:00
Mark McDowall
06baae060d New: More information on on why hardlinks should be used over copying
(cherry picked from commit 83a9d15ff8721c8effdc2c8055e37bfb757022d4)

Closes #2352
2023-05-06 14:51:04 +03:00
Bogdan
4e5c7bc0a3 New: Add version and timestamp to backup archive
(cherry picked from commit ed3d880974ae6a1430866eebaf72533f35258f6f)

Closes #2357
2023-05-06 14:21:09 +03:00
Bogdan
a939adb2b1 Create cache db in integration tests 2023-05-05 22:59:54 +03:00
Bogdan
6858db686c Fix tests 2023-05-05 15:00:22 +03:00
Mark McDowall
7a5e2c248c Fixed: File browser
(cherry picked from commit f7ce5c7b115ea0d12ab63f19960c473e09e30f3d)
2023-05-05 14:54:17 +03:00
Bogdan
f2d57c6c5e Update caniuse-lite 2023-05-05 14:53:00 +03:00
Qstick
32c9734d70 Auto-reply for Log Label
(cherry picked from commit d851ecdf2f826f25b9d2d67d3b7e9e3642bc5299)
2023-05-04 20:17:38 +03:00
Bogdan
2d732f0454 Bump dotnet to 6.0.16 2023-05-04 14:55:29 +03:00
Mark McDowall
20835291e6 New: Report health error if Recycling Bin folder is not writable
(cherry picked from commit 8c50cd061e691914d9fcce119b9f838f1276950c)
2023-05-04 06:51:08 +03:00
Bogdan
f5d6b2de11 Increase retry count in CachedFixture 2023-05-03 20:00:40 +03:00
Lars
d51f7cc02b New: Filter Sonarr synchronization based on Root Folders
(cherry picked from commit ff3327483a233ebe54d8f178c355abaeb6f9d11e)

Closes #2399
2023-05-03 12:04:23 +03:00
bakerboy448
8d41d0106a add trace log checkbox to bug report
(cherry picked from commit 6dc558cbf6ad4155ed35d4111e8368dcf57a8826)

Closes #2428
Closes #2429
2023-05-03 11:09:00 +03:00
santschi
fd08e9d2c4 Fixed: Check for unexpected parent tags to fix GoodReads Series 2023-05-02 22:26:31 +03:00
Mark McDowall
a1ee6aa8ac Fixed: Manual Import without selecting Import Mode
Closes #5036

(cherry picked from commit b542dd0ddd6463321a0d75c1ce4b1ecec679f213)
2023-05-02 16:28:34 +03:00
Bogdan
ddbb8b211f bump github actions to latest [skip ci]
Fix for nodejs 12 deprecation
2023-05-02 15:17:14 +03:00
Bogdan
bea61edb4e Fix downloading releases without an indexer
(cherry picked from commit ca8b26138e3ebd793cc1a5fd51522ce3eda8a2e1)

Closes #2426
2023-05-02 09:18:13 +03:00
Bogdan
db7bb14491 Remove BasicAuthString 2023-04-30 21:33:48 +03:00
Bogdan
7a7039b1f7 Build download requests from indexer implementation
(cherry picked from commit a0b08f6c6f106d92cdb12fbb959dd2605c22fe6a)
2023-04-30 21:33:48 +03:00
Benjamin Staneck
1978a726bb Fixed some aria violations
(cherry picked from commit 7aa846343815105e3576e6aa20eac64fcb0edf8d)

Closes #2422
2023-04-30 11:36:46 +03:00
Bogdan
2412b38333 Fix loading eslintrc
(cherry picked from commit b0773ae7e3f860ff381d8d98d23d612609048e38)

Closes #2424
2023-04-30 11:34:07 +03:00
Benjamin Staneck
dbed46dd5b New: Updated button and calendar outline colors for dark theme
(cherry picked from commit 5d873fafec07c9d67ad8d2c16eecea195a78eecf)
2023-04-30 11:31:53 +03:00
Weblate
63670f55b0 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-04-29 23:38:54 +03:00
Mark McDowall
60cc22b543 Fixed: Permissions after installing on Windows and opening Firewall port
(cherry picked from commit ff2e8ffc372a34d08028db3c49f603cdfb87d832)
2023-04-29 23:36:36 +03:00
Weblate
3229d3ef59 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-04-29 23:35:07 +03:00
Bakerboy448
349a19855a Fixed: Missing Translates 2023-04-29 23:32:25 +03:00
Bogdan
b8b364b48e Bump node version to 16.x 2023-04-29 23:23:39 +03:00
Stevie Robinson
571805d05f Fixed: Border hover colors in dark theme
(cherry picked from commit bead56893f080a1ac1df230908314f3ba8e0c6ce)

Closes #2385
2023-04-29 23:16:12 +03:00
Bogdan
8de7f48b80 Fixed: Config file settings do not need to be case-sensitive
(cherry picked from commit 2107635b7e7e5392624f2957af7d7b88ba6be283)

Closes #2351
2023-04-29 23:07:06 +03:00
Bogdan
37a3799c66 Fixed: Ensure default config file on starting app
(cherry picked from commit e747af9f448368e2add0d2869a3749efa9e93ae0)

(cherry picked from commit 5326a102e23eacfc1132eb544a92af737a531df5)
2023-04-29 23:05:18 +03:00
Mark McDowall
b1d22b6339 Fixed: Number input changing while scrolling
(cherry picked from commit cc46ed56b4b70fe1f1443c0a927383f19c989c47)
2023-04-29 23:04:04 +03:00
Bogdan
a28fd4415e Rename to FolderChmodValidator to match class name
(cherry picked from commit 6f614b7d476216466d0cb021c9fdc425d0fa35f3)
2023-04-29 23:03:35 +03:00
Benjamin Staneck
c95ffdc4d6 Update core-js and use defaults for browserlist
(cherry picked from commit de4cfefde4d00aba829356541b02e8f9a7729977)

Closes #2411
2023-04-29 23:02:40 +03:00
Benjamin Staneck
e5d8d01105 Update webpack and webpack-cli
(cherry picked from commit 8bcaa17e25161aeedd9ca8488a4bb6f4c423de2b)

Closes #2408
2023-04-29 23:02:40 +03:00
Servarr
bb9bf743d8 Automated API Docs update 2023-04-29 22:56:15 +03:00
Fabricio Silva
99d3e80d0c Fixed: Edit Quality Profile not opening
Closes #2412

(cherry picked from commit 83cee4b00e8c64d749d2ff1d8575d31570beecba)
2023-04-29 22:53:37 +03:00
Bogdan
640c0f5d52 Remove CustomSchemaIds for swagger 2023-04-29 22:47:58 +03:00
Bogdan
861e569422 Bump Swashbuckle to 6.5.0 2023-04-29 21:31:58 +03:00
Qstick
d7eedb6079 Update .gitignore 2023-04-29 13:27:15 -05:00
Benjamin Staneck
0a1066eee7 Remove unused gulpFile
(cherry picked from commit 0d48ebe8dedb637a133c5234bd7d9fd8963111af)
2023-04-29 21:26:29 +03:00
Bogdan
0f9d8d61a2 Bump Swashbuckle to 6.5.0 2023-04-29 21:23:15 +03:00
Qstick
a2c9ed0b59 Fix pipeline errors 2023-04-29 02:10:59 -05:00
Bogdan
ea91b3df17 New: (Notifications) Add Apprise 2023-04-29 01:05:18 -05:00
Robin Dadswell
f9d5fa37a3 Frontend Placeholders from the Backend
(cherry picked from commit 69f5963f6f1e80e3f598bdb13792b7413fcc13b1)
2023-04-29 01:05:18 -05:00
Weblate
fc6a02c2e2 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (901 of 901 strings)

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

Currently translated at 99.0% (892 of 901 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.2% (894 of 901 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.3% (895 of 901 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 61.8% (557 of 901 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 76.4% (689 of 901 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.6% (691 of 901 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 63.8% (575 of 901 strings)

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

Currently translated at 15.0% (136 of 901 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 73.1% (659 of 901 strings)

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

Currently translated at 100.0% (901 of 901 strings)

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

Currently translated at 100.0% (901 of 901 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 11.8% (107 of 901 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 10.9% (99 of 901 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 10.5% (95 of 901 strings)

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

Currently translated at 99.0% (892 of 901 strings)

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

Currently translated at 100.0% (901 of 901 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.5% (690 of 901 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.3% (607 of 901 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.3% (607 of 901 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.3% (607 of 901 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 9.6% (87 of 901 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 9.6% (87 of 901 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 67.0% (604 of 901 strings)

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

Currently translated at 100.0% (901 of 901 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 66.1% (596 of 901 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 66.9% (603 of 901 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.5% (690 of 901 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 61.7% (556 of 901 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 61.8% (557 of 901 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 61.7% (556 of 901 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 88.4% (797 of 901 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 67.2% (606 of 901 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 60.1% (542 of 901 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 65.5% (591 of 901 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 61.9% (558 of 901 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 61.7% (556 of 901 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 73.1% (659 of 901 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 61.7% (556 of 901 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 99.3% (895 of 901 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 61.7% (556 of 901 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 63.7% (574 of 901 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 76.3% (688 of 901 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 88.9% (801 of 901 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.3% (895 of 901 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 62.1% (560 of 901 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 61.8% (557 of 901 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 61.7% (556 of 901 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 61.8% (557 of 901 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.0% (604 of 901 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 67.2% (606 of 901 strings)

Added translation using Weblate (Tamil) [skip ci]

Added translation using Weblate (Indonesian) [skip ci]

Added translation using Weblate (Estonian) [skip ci]

Added translation using Weblate (Serbian) [skip ci]

Added translation using Weblate (Croatian) [skip ci]

Added translation using Weblate (Bosnian) [skip ci]

Added translation using Weblate (Spanish (Mexico)) [skip ci]

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Bendik Remoy <Bendikremoy@hotmail.com>
Co-authored-by: Cassio Rizzi <clrizzi@gmail.com>
Co-authored-by: Deflector8249 <lh2jwko5@gomail.me>
Co-authored-by: Florian <sephrat.flo@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Nir Israel Hen <nirisraelh@gmail.com>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: SHUAI.W <x@ousui.org>
Co-authored-by: Sascha Brockel <gsydaydreamer@gmail.com>
Co-authored-by: TheHrle <Hpranjkovic@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: federicofortini <federico.fortini@yahoo.it>
Co-authored-by: fiego14 <alvaross_96@hotmail.com>
Co-authored-by: libsu <libsu@qq.com>
Co-authored-by: pedrom20 <pedrom20@gmail.com>
Co-authored-by: tomas15420 <tomas15420@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/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/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/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-04-29 01:01:55 -05:00
Benjamin Staneck
362401a847 Fix typo in calendarBackgroundColor CSS variable
(cherry picked from commit e34d2504400faaa12c7fc8572264477ccd58619d)
2023-04-29 01:00:33 -05:00
Benjamin Staneck
e495f8bcc9 Use minified jquery
(cherry picked from commit bb77bb640c0529ca3e6386ec657e64ebafad02f4)
2023-04-29 01:00:04 -05:00
Bogdan
934563656c Serve plain text files (eg. logs) as UTF-8 2023-04-29 00:58:39 -05:00
bakerboy448
6323cae373 Fixed: Default update branch as develop 2023-04-23 11:11:43 -07:00
Bogdan
55999a8bad Rename CC to Cc 2023-04-16 19:53:01 -05:00
Qstick
0b3d49db32 Remove mono process detection
(cherry picked from commit 5a046026725084bc880a7b63d7105dcf4d882128)
2023-04-16 19:52:11 -05:00
Bakerboy448
b3cc5740ee Fixed: DrunkenSlug Default URL
(cherry picked from commit 128f62488d8ec8c4efd096f6cb03ed1d2ce1f89a)
2023-04-13 20:58:53 -05:00
Qstick
e5ad7407a7 Update API Docs 2023-04-03 07:30:35 -05:00
424 changed files with 8077 additions and 6199 deletions

View File

@@ -40,6 +40,9 @@ csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Using directive is unnecessary.
dotnet_diagnostic.IDE0005.severity = error
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none

View File

@@ -1,9 +0,0 @@
{
"paths": [
"frontend/src/**/*.js"
],
"ignored": [
"**/node_modules/**/*"
],
"port": 5004
}

View File

@@ -73,3 +73,10 @@ body:
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations:
required: true
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
required: true

28
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
'Area: API':
- src/Readarr.Api.V1/**/*
'Area: Db-migration':
- src/NzbDrone.Core/Datastore/Migration/*
'Area: Download Clients':
- src/NzbDrone.Core/Download/Clients/**/*
'Area: Import Lists':
- src/NzbDrone.Core/ImportLists/**/*
'Area: Indexer':
- src/NzbDrone.Core/Indexers/**/*
'Area: Notifications':
- src/NzbDrone.Core/Notifications/**/*
'Area: Organizer':
- src/NzbDrone.Core/Organizer/**/*
'Area: Parser':
- src/NzbDrone.Core/Parser/**/*
'Area: UI':
- frontend/**/*
- package.json
- yarn.lock

12
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4

View File

@@ -9,13 +9,13 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
- uses: dessant/lock-threads@v4
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '90'
issue-exclude-created-before: ''
issue-exclude-labels: ''
issue-lock-labels: ''
issue-lock-comment: ''
issue-inactive-days: '90'
exclude-issue-created-before: ''
exclude-any-issue-labels: ''
add-issue-labels: ''
issue-comment: ''
issue-lock-reason: 'resolved'
process-only: ''

View File

@@ -8,7 +8,7 @@ jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
@@ -18,4 +18,15 @@ jobs:
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
or [Subreddit](https://reddit.com/r/readarr)
close-issue: true
lock-issue: false
lock-issue: false
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Status: Logs Needed'
issue-comment: >
:wave: @{issue-author}, In order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files).
close-issue: false
lock-issue: false

3
.gitignore vendored
View File

@@ -148,3 +148,6 @@ _temp_*/**/*
## Merge any idea folder
*/**/.idea
*.iml
# API doc generation
.config/

View File

@@ -15,7 +15,8 @@ variables:
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.302'
dotnetVersion: '6.0.408'
nodeVersion: '16.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
@@ -182,7 +183,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '12.x'
versionSpec: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1
@@ -917,7 +918,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '12.x'
versionSpec: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1
@@ -955,6 +956,55 @@ stages:
cliProjectVersion: '$(readarrVersion)'
cliSources: './frontend'
- task: SonarCloudAnalyze@1
- job: Api_Docs
displayName: API Docs
condition: |
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
pool:
vmImage: ${{ variables.windowsImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: self
submodules: true
persistCredentials: true
fetchDepth: 1
- bash: ./docs.sh Windows
displayName: Create openapi.json
- bash: |
git config --global user.email "development@lidarr.audio"
git config --global user.name "Servarr"
git checkout -b api-docs
git add .
git status
if git status | grep modified
then
git commit -am 'Automated API Docs update'
git push -f --set-upstream origin api-docs
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
else
echo "No changes since last run"
fi
displayName: Commit API Doc Change
continueOnError: true
env:
GITHUBTOKEN: $(githubToken)
- task: CopyFiles@2
displayName: 'Copy openapi.json to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*openapi.json
TargetFolder: '$(Build.ArtifactStagingDirectory)/api_docs'
- publish: $(Build.ArtifactStagingDirectory)/api_docs
artifact: 'APIDocs'
displayName: Publish API Docs Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- job: Analyze_Backend
displayName: Backend

38
docs.sh Normal file
View File

@@ -0,0 +1,38 @@
PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then
RUNTIME="win-x64"
elif [ "$PLATFORM" = "Linux" ]; then
RUNTIME="linux-x64"
elif [ "$PLATFORM" = "Mac" ]; then
RUNTIME="osx-x64"
else
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
exit 1
fi
outputFolder='_output'
testPackageFolder='_tests'
rm -rf $outputFolder
rm -rf $testPackageFolder
slnFile=src/Readarr.sln
platform=Posix
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/Readarr.console.dll" v1 &
sleep 45
kill %1
exit 0

View File

@@ -1,25 +0,0 @@
{
"remove-empty-rulesets": true,
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": false,
"element-case": "lower",
"eof-newline": true,
"leading-zero": true,
"quotes": "double",
"sort-order-fallback": "abc",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-after-selector-delimiter": " ",
"space-before-selector-delimiter": "",
"space-before-closing-brace": "\n",
"strip-spaces": true,
"tab-size": true,
"unitless-zero": false
}

View File

@@ -1,335 +0,0 @@
{
"indent": {
"value": " ",
"FunctionExpression": 1,
"ArrayExpression": 1,
"ObjectExpression": 1
},
"lineBreak": {
"value": "\n",
"before": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": ">=1",
"ArrowFunctionExpressionOpeningBrace": 0,
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": ">=1",
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": 0,
"CatchClosingBrace": ">=1",
"CatchKeyword": 0,
"CatchOpeningBrace": 0,
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": 0,
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": ">=1",
"DoWhileStatementOpeningBrace": 0,
"ElseIfStatement": 0,
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": 0,
"ElseStatement": 0,
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": 0,
"EmptyStatement": -1,
"EndOfFile": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": 0,
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 0,
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": "<2",
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 0,
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace":0,
"IIFEClosingParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": 0,
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": -1,
"MethodDefinition": ">=1",
"ObjectExpressionClosingBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": "<=2",
"PropertyValue": 0,
"ReturnStatement": -1,
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": 0,
"ThisExpression": -1,
"ThrowStatement": ">=1",
"TryClosingBrace": ">=1",
"TryKeyword": -1,
"TryOpeningBrace": 0,
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": 0,
"VariableDeclarationWithoutInit": ">=1",
"VariableName": ">=1",
"VariableValue": 0,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": 0
},
"after": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": -1,
"ArrowFunctionExpressionOpeningBrace": ">=1",
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": -1,
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": -1,
"CatchClosingBrace": ">=0",
"CatchKeyword": 0,
"CatchOpeningBrace": ">=1",
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": ">=1",
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": 0,
"DoWhileStatementOpeningBrace": ">=1",
"ElseIfStatement": ">=1",
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": ">=1",
"ElseStatement": ">=1",
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": ">=1",
"EmptyStatement": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": ">=1",
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": -1,
"ForInStatementExpressionOpening": "<2",
"ForInStatementOpeningBrace": ">=1",
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": -1,
"ForStatementExpressionOpening": "<2",
"ForStatementOpeningBrace": ">=1",
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": ">=1",
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": -1,
"FunctionExpressionOpeningBrace": 1,
"IIFEOpeningParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": ">=1",
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": 0,
"MethodDefinition": ">=1",
"ObjectExpressionOpeningBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": -1,
"PropertyName": 0,
"ReturnStatement": -1,
"SwitchCaseColon": ">=1",
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": ">=1",
"ThisExpression": 0,
"ThrowStatement": ">=1",
"TryClosingBrace": 0,
"TryKeyword": -1,
"TryOpeningBrace": ">=1",
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": ">=1",
"VariableValue": -1,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": ">=1"
}
},
"whiteSpace": {
"value": " ",
"removeTrailing": 1,
"before": {
"ArgumentComma": 0,
"ArgumentList": 0,
"ArgumentListArrayExpression": 0,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 0,
"ArrayExpressionOpening": 1,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 1,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 0,
"ConditionalExpressionAlternate": 1,
"ConditionalExpressionConsequent": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementConditional": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionClosingParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 1,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 0,
"ForStatementExpressionOpening": 1,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 0,
"FunctionDeclarationClosingBrace": 1,
"FunctionDeclarationOpeningBrace": 1,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace": 1,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 1,
"IfStatementOpeningBrace": 1,
"LineComment": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionClosing": 0,
"ObjectExpressionClosingBrace": 1,
"ParameterComma": 0,
"ParameterList": 0,
"Property": 1,
"PropertyName": 1,
"PropertyValue": 1,
"SwitchDiscriminantClosing": 0,
"SwitchDiscriminantOpening": 1,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"VariableValue": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 0,
"WhileStatementConditionalOpening": 1,
"WhileStatementOpeningBrace": 1
},
"after": {
"ArgumentComma": 1,
"ArgumentList": 0,
"ArgumentListArrayExpression": 1,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 1,
"ArrayExpressionOpening": 0,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 0,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 1,
"ConditionalExpressionConsequent": 1,
"ConditionalExpressionTest": 1,
"DoWhileStatementBody": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionOpeningParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 1,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 1,
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 1,
"FunctionDeclarationClosingBrace": 0,
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpressionClosingBrace": 0,
"FunctionExpressionOpeningBrace": 0,
"FunctionName": 0,
"FunctionReservedWord": 0,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 0,
"IfStatementOpeningBrace": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionOpening": 0,
"ObjectExpressionClosingBrace": 0,
"ObjectExpressionOpeningBrace": 1,
"ParameterComma": 1,
"ParameterList": 0,
"PropertyName": 0,
"PropertyValue": 0,
"SwitchDiscriminantClosing": 1,
"SwitchDiscriminantOpening": 0,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 1,
"WhileStatementConditionalOpening": 0,
"WhileStatementOpeningBrace": 1
}
}
}

View File

@@ -1,14 +1,17 @@
const fs = require('fs');
const path = require('path');
const frontendFolder = __dirname;
const dirs = fs
.readdirSync('frontend/src', { withFileTypes: true })
.readdirSync(path.join(frontendFolder, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
.join('|');
const frontendFolder = __dirname;
module.exports = {
root: true,
parser: '@babel/eslint-parser',
env: {
@@ -28,7 +31,7 @@ module.exports = {
ecmaVersion: 6,
sourceType: 'module',
babelOptions: {
configFile: `${frontendFolder}/babel.config.js`,
configFile: `${frontendFolder}/babel.config.js`
},
ecmaFeatures: {
modules: true,

View File

@@ -1,12 +0,0 @@
{
"js": {
"indent_size": 2,
"indent_char": " ",
"indent_level": 2,
"indent_with_tabs": false,
"preserve_newlines": true,
"brace_style": "collapse",
"max_preserve_newlines": 2,
"jslint_happy": true
}
}

View File

@@ -1,12 +1,12 @@
{
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"at-rule-empty-line-before": [
"always",
{
@@ -15,9 +15,6 @@
]
}
],
"at-rule-name-case": "lower",
"at-rule-name-newline-after": "always-multi-line",
"at-rule-name-space-after": "always",
"at-rule-no-unknown": [
true,
{
@@ -28,83 +25,36 @@
}
],
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-closing-brace-space-after": "always-single-line",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-newline-before": "never-single-line",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-named": "never",
"color-no-invalid-hex": true,
"comment-whitespace-inside": "always",
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignoreProperties": [
"composes"
"composes"
]
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"font-family-name-quotes": "always-unless-keyword",
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "never-multi-line",
"function-comma-newline-before": "never-multi-line",
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-name-case": "lower",
"function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never",
"function-url-quotes": "always",
"function-url-scheme-disallowed-list": [
"data"
],
"function-whitespace-after": "always",
"indentation": 2,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": true,
"max-empty-lines": 1,
"max-line-length": [
100,
{
"ignore": [
"non-comments"
]
}
],
"max-nesting-depth": 2,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-vendor-prefix": true,
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"order/order": [
"custom-properties",
"dollar-variables",
@@ -132,6 +82,7 @@
"right",
"bottom",
"left",
"inset",
"z-index",
"display",
"visibility",
@@ -343,54 +294,33 @@
]
}
],
"property-case": "lower",
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
"first-nested"
],
"ignore": [
"after-comment"
"after-comment"
]
}
],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-attribute-quotes": "never",
"selector-class-pattern": "^[A-Za-z0-9]+$",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-before": "never",
"selector-max-attribute": 0,
"selector-max-class": 3,
"selector-max-compound-selectors": 3,
"selector-max-empty-lines": 0,
"selector-max-id": 0,
"selector-max-universal": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"shorthand-property-no-redundant-values": true,
"string-no-newline": true,
"string-quotes": "single",
"time-min-milliseconds": 100,
"unit-case": "lower",
"unit-no-unknown": true,
"value-list-comma-newline-after": "never-multi-line",
"value-list-comma-newline-before": "never-multi-line",
"value-list-comma-space-after": "always",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"value-no-vendor-prefix": true
}
}
}

7
frontend/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@@ -44,7 +44,7 @@ module.exports = (env) => {
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery',
jquery: 'jquery/dist/jquery.min',
'react-middle-truncate': 'react-middle-truncate/lib/react-middle-truncate'
},
fallback: {
@@ -253,18 +253,19 @@ module.exports = (env) => {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
sourceMap: true, // Must be set to true if using source-maps in production
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
]
};
}
return config;

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line filenames/match-exported
const loaderUtils = require('loader-utils');
module.exports = function cssVariablesLoader(source) {

View File

@@ -107,7 +107,7 @@ function HistoryDetails(props) {
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title="Custom Format Score"
title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)}
/> :
null
@@ -224,7 +224,7 @@ function HistoryDetails(props) {
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title="Custom Format Score"
title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)}
/> :
null
@@ -270,7 +270,7 @@ function HistoryDetails(props) {
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title="Custom Format Score"
title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)}
/> :
null

View File

@@ -74,7 +74,7 @@ class AuthorDetailsPageConnector extends Component {
if (isFetching && !isPopulated) {
return (
<PageContent title='loading'>
<PageContent title={translate('Loading')}>
<PageContentBody>
<LoadingIndicator />
</PageContentBody>

View File

@@ -1,4 +1,3 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
@@ -10,15 +9,11 @@ function createMapStateToProps() {
createAuthorSelector(),
createTagsSelector(),
(author, tagList) => {
const tags = _.reduce(author.tags, (acc, tag) => {
const matchingTag = _.find(tagList, { id: tag });
if (matchingTag) {
acc.push(matchingTag.label);
}
return acc;
}, []);
const tags = author.tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
.map((tag) => tag.label)
.sort((a, b) => a.localeCompare(b));
return {
tags

View File

@@ -87,7 +87,7 @@ class BookDetailsPageConnector extends Component {
if ((isFetching || !this.state.hasMounted) ||
(!isFetching && !isPopulated)) {
return (
<PageContent title='loading'>
<PageContent title={translate('Loading')}>
<PageContentBody>
<LoadingIndicator />
</PageContentBody>

View File

@@ -1,6 +1,6 @@
.dayOfWeek {
flex: 1 0 14.28%;
background-color: var(--calendarBackgroudColor);
background-color: var(--calendarBackgroundColor);
text-align: center;
}

View File

@@ -33,7 +33,7 @@ function getUrls(state) {
icalUrl += `tags=${tags.toString()}&`;
}
icalUrl += `pastDays=${pastDays}&futureDays=${futureDays}&apikey=${window.Readarr.apiKey}`;
icalUrl += `pastDays=${pastDays}&futureDays=${futureDays}&apikey=${encodeURIComponent(window.Readarr.apiKey)}`;
const iCalHttpUrl = `${window.location.protocol}//${icalUrl}`;
const iCalWebCalUrl = `webcal://${icalUrl}`;

View File

@@ -17,7 +17,7 @@ class DescriptionListItem extends Component {
} = this.props;
return (
<span>
<div>
<DescriptionListItemTitle
className={titleClassName}
>
@@ -29,7 +29,7 @@ class DescriptionListItem extends Component {
>
{data}
</DescriptionListItemDescription>
</span>
</div>
);
}
}

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Alert from 'Components/Alert';
import PathInput from 'Components/Form/PathInput';
import Button from 'Components/Link/Button';
@@ -39,7 +38,7 @@ class FileBrowserModalContent extends Component {
constructor(props, context) {
super(props, context);
this._scrollerNode = null;
this._scrollerRef = React.createRef();
this.state = {
isFileBrowserModalOpen: false,
@@ -57,21 +56,10 @@ class FileBrowserModalContent extends Component {
currentPath !== prevState.currentPath
) {
this.setState({ currentPath });
this._scrollerNode.scrollTop = 0;
this._scrollerRef.current.scrollTop = 0;
}
}
//
// Control
setScrollerRef = (ref) => {
if (ref) {
this._scrollerNode = ReactDOM.findDOMNode(ref);
} else {
this._scrollerNode = null;
}
};
//
// Listeners
@@ -143,7 +131,7 @@ class FileBrowserModalContent extends Component {
/>
<Scroller
ref={this.setScrollerRef}
ref={this._scrollerRef}
className={styles.scroller}
scrollDirection={scrollDirections.BOTH}
>

View File

@@ -0,0 +1,100 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.downloadClients,
(state, { includeAny }) => includeAny,
(state, { protocol }) => protocol,
(downloadClients, includeAny, protocolFilter) => {
const {
isFetching,
isPopulated,
error,
items
} = downloadClients;
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return {
key: downloadClient.id,
value: downloadClient.name
};
});
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
});
}
return {
isFetching,
isPopulated,
error,
values
};
}
);
}
const mapDispatchToProps = {
dispatchFetchDownloadClients: fetchDownloadClients
};
class DownloadClientSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchDownloadClients();
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
};
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
DownloadClientSelectInputConnector.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeAny: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchDownloadClients: PropTypes.func.isRequired
};
DownloadClientSelectInputConnector.defaultProps = {
includeAny: false,
protocol: 'torrent'
};
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);

View File

@@ -113,10 +113,12 @@ class EnhancedSelectInput extends Component {
this._scheduleUpdate();
}
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
if (!Array.isArray(this.props.value)) {
if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
}
}
}
@@ -332,6 +334,11 @@ class EnhancedSelectInput extends Component {
const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values);
let selectedValue = value;
if (!values.length) {
selectedValue = isMultiSelect ? [] : '';
}
return (
<div>
@@ -372,15 +379,17 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
{
isFetching &&
isFetching ?
<LoadingIndicator
className={styles.loading}
size={20}
/>
/> :
null
}
{
!isFetching &&
isFetching ?
null :
<Icon
name={icons.CARET_DOWN}
/>
@@ -400,7 +409,7 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
<SelectedValueComponent
value={value}
value={selectedValue}
values={values}
{...selectedValueOptions}
{...selectedOption}
@@ -418,15 +427,17 @@ class EnhancedSelectInput extends Component {
>
{
isFetching &&
isFetching ?
<LoadingIndicator
className={styles.loading}
size={20}
/>
/> :
null
}
{
!isFetching &&
isFetching ?
null :
<Icon
name={icons.CARET_DOWN}
/>
@@ -505,7 +516,7 @@ class EnhancedSelectInput extends Component {
</Manager>
{
isMobile &&
isMobile ?
<Modal
className={styles.optionsModal}
size={sizes.EXTRA_SMALL}
@@ -555,7 +566,8 @@ class EnhancedSelectInput extends Component {
}
</Scroller>
</ModalBody>
</Modal>
</Modal> :
null
}
</div>
);

View File

@@ -10,6 +10,7 @@ import BookshelfInputConnector from './BookshelfInputConnector';
import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector';
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
@@ -81,6 +82,9 @@ function getComponent(type) {
case inputTypes.INDEXER_SELECT:
return IndexerSelectInputConnector;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInputConnector;

View File

@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
>
<div className={styles.valueText}>
{
isMultiSelect &&
isMultiSelect ?
value.map((key, index) => {
const v = valuesMap[key];
return (
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key}
</Label>
);
})
}) :
null
}
{
!isMultiSelect && value
isMultiSelect ? null : value
}
</div>
{
hint != null && includeHint &&
hint != null && includeHint ?
<div className={styles.hintText}>
{hint}
</div>
</div> :
null
}
</EnhancedSelectInputSelectedValue>
);
}
HintedSelectInputSelectedValue.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,

View File

@@ -63,6 +63,7 @@ function ProviderFieldFormGroup(props) {
label,
helpText,
helpLink,
placeholder,
value,
type,
advanced,
@@ -95,6 +96,7 @@ function ProviderFieldFormGroup(props) {
label={label}
helpText={helpText}
helpLink={helpLink}
placeholder={placeholder}
value={value}
values={getSelectValues(selectOptions)}
errors={errors}
@@ -120,6 +122,7 @@ ProviderFieldFormGroup.propTypes = {
label: PropTypes.string.isRequired,
helpText: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,

View File

@@ -9,14 +9,17 @@ const ADD_NEW_KEY = 'addNew';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.rootFolders,
(state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, includeNoChange) => {
(rootFolders, value, includeMissingValue, includeNoChange) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
name: rootFolder.name,
freeSpace: rootFolder.freeSpace
freeSpace: rootFolder.freeSpace,
isMissing: false
};
});
@@ -25,7 +28,8 @@ function createMapStateToProps() {
key: 'noChange',
value: '',
name: 'No Change',
isDisabled: true
isDisabled: true,
isMissing: false
});
}
@@ -39,6 +43,15 @@ function createMapStateToProps() {
});
}
if (includeMissingValue && !values.find((v) => v.key === value)) {
values.push({
key: value,
value,
isMissing: true,
isDisabled: true
});
}
values.push({
key: ADD_NEW_KEY,
value: '',

View File

@@ -18,3 +18,9 @@
color: var(--darkGray);
font-size: $smallFontSize;
}
.isMissing {
margin-left: 15px;
color: var(--dangerColor);
font-size: $smallFontSize;
}

View File

@@ -10,6 +10,7 @@ function RootFolderSelectInputOption(props) {
value,
name,
freeSpace,
isMissing,
isMobile,
...otherProps
} = props;
@@ -29,11 +30,20 @@ function RootFolderSelectInputOption(props) {
<div>{text}</div>
{
freeSpace != null &&
freeSpace == null ?
null :
<div className={styles.freeSpace}>
{formatBytes(freeSpace)} Free
</div>
}
{
isMissing ?
<div className={styles.isMissing}>
Missing
</div> :
null
}
</div>
</EnhancedSelectInputOption>
);
@@ -43,6 +53,7 @@ RootFolderSelectInputOption.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
isMissing: PropTypes.bool,
isMobile: PropTypes.bool.isRequired
};

View File

@@ -1,8 +1,5 @@
.inputContainer {
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
inset: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;

View File

@@ -36,7 +36,6 @@ class TagInputInput extends Component {
<div
ref={forwardedRef}
className={className}
component="div"
onMouseDown={this.onMouseDown}
>
{

View File

@@ -112,6 +112,12 @@ class TextInput extends Component {
this._isMouseTarget = false;
};
onWheel = () => {
if (this.props.type === 'number') {
this._input.blur();
}
};
//
// Render
@@ -161,6 +167,7 @@ class TextInput extends Component {
onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onWheel={this.onWheel}
/>
);
}

View File

@@ -23,6 +23,7 @@ function IconButton(props) {
className,
isDisabled && styles.isDisabled
)}
aria-label="Table Options Button"
isDisabled={isDisabled}
{...otherProps}
>

View File

@@ -6,6 +6,7 @@ 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 translate from 'Utilities/String/translate';
import styles from './ModalError.css';
function ModalError(props) {
@@ -25,7 +26,7 @@ function ModalError(props) {
messageClassName={styles.message}
detailsClassName={styles.details}
{...otherProps}
message='There was an error loading this item'
message={translate('ThereWasAnErrorLoadingThisItem')}
/>
</ModalBody>

View File

@@ -61,6 +61,7 @@ class PageHeader extends Component {
<img
className={styles.logo}
src={`${window.Readarr.urlBase}/Content/Images/logo.svg`}
alt="Readarr Logo"
/>
</Link>
</div>
@@ -79,6 +80,7 @@ class PageHeader extends Component {
<IconButton
className={styles.donate}
name={icons.HEART}
aria-label="Donate"
to="https://opencollective.com/readarr"
size={14}
/>

View File

@@ -20,7 +20,7 @@ function PageHeaderActionsMenu(props) {
return (
<div>
<Menu alignMenu={align.RIGHT}>
<MenuButton className={styles.menuButton}>
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
/>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import ErrorBoundaryError from 'Components/Error/ErrorBoundaryError';
import translate from 'Utilities/String/translate';
import PageContentBody from './PageContentBody';
import styles from './PageContentError.css';
@@ -9,7 +10,7 @@ function PageContentError(props) {
<PageContentBody>
<ErrorBoundaryError
{...props}
message='There was an error loading this page'
message={translate('ThereWasAnErrorLoadingThisPage')}
/>
</PageContentBody>
</div>

View File

@@ -19,7 +19,7 @@
}
}
@media only screen and (max-width: $breakpointLarge) {
@media only screen and (max-width: $breakpointExtraLarge) {
.contentFooter {
flex-wrap: wrap;
}

View File

@@ -56,7 +56,9 @@ function ProgressBar(props) {
styles[kind],
enableColorImpairedMode && 'colorImpaired'
)}
aria-valuenow={progress}
role="meter"
aria-label={`Progress Bar at ${progress.toFixed(0)}%`}
aria-valuenow={progress.toFixed(0)}
aria-valuemin="0"
aria-valuemax="100"
style={{ width: progressPercent }}

View File

@@ -62,7 +62,7 @@ function Logger(minimumLogLevel) {
}
Logger.prototype.cleanse = function(message) {
const apikey = new RegExp(`access_token=${window.Readarr.apiKey}`, 'g');
const apikey = new RegExp(`access_token=${encodeURIComponent(window.Readarr.apiKey)}`, 'g');
return message.replace(apikey, 'access_token=(removed)');
};
@@ -106,7 +106,7 @@ class SignalRConnector extends Component {
this.connection = new signalR.HubConnectionBuilder()
.configureLogging(new Logger(signalR.LogLevel.Information))
.withUrl(`${url}?access_token=${window.Readarr.apiKey}`)
.withUrl(`${url}?access_token=${encodeURIComponent(window.Readarr.apiKey)}`)
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (retryContext) => {
if (retryContext.elapsedMilliseconds > 180000) {

View File

@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { kinds } from 'Helpers/Props';
@@ -6,16 +5,15 @@ import Label from './Label';
import styles from './TagList.css';
function TagList({ tags, tagList }) {
const sortedTags = tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
.sort((a, b) => a.label.localeCompare(b.label));
return (
<div className={styles.tags}>
{
tags.map((t) => {
const tag = _.find(tagList, { id: t });
if (!tag) {
return null;
}
sortedTags.map((tag) => {
return (
<Label
key={tag.id}

View File

@@ -14,6 +14,7 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
export const BOOK_EDITION_SELECT = 'bookEditionSelect';
export const INDEXER_SELECT = 'indexerSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
@@ -40,6 +41,7 @@ export const all = [
METADATA_PROFILE_SELECT,
BOOK_EDITION_SELECT,
INDEXER_SELECT,
DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT,
SELECT,
DYNAMIC_SELECT,

View File

@@ -120,7 +120,7 @@ class InteractiveImportModalContentConnector extends Component {
onImportSelectedPress = (selected, importMode) => {
const files = [];
if (importMode === 'chooseImportMethod') {
if (importMode === 'chooseImportMode') {
this.setState({ interactiveImportErrorMessage: 'An import mode must be selected' });
return;
}

View File

@@ -293,7 +293,7 @@ class InteractiveImportRow extends Component {
anchor={
<Icon name={icons.INTERACTIVE} />
}
title="Formats"
title={translate('Formats')}
body={
<div className={styles.customFormatTooltip}>
<BookFormats formats={customFormats} />

View File

@@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectReleaseGroupModalContent.css';
class SelectReleaseGroupModalContent extends Component {
@@ -64,7 +65,9 @@ class SelectReleaseGroupModalContent extends Component {
>
<Form>
<FormGroup>
<FormLabel>Release Group</FormLabel>
<FormLabel>
{translate('ReleaseGroup')}
</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}

View File

@@ -10,13 +10,14 @@ import styles from './AdvancedSettingsButton.css';
function AdvancedSettingsButton(props) {
const {
advancedSettings,
onAdvancedSettingsPress
onAdvancedSettingsPress,
showLabel
} = props;
return (
<Link
className={styles.button}
title={advancedSettings ? translate('AdvancedSettingsShownClickToHide') : translate('AdvancedSettingsHiddenClickToShow')}
title={advancedSettings ? translate('ShownClickToHide') : translate('HiddenClickToShow')}
onPress={onAdvancedSettingsPress}
>
<Icon
@@ -43,18 +44,27 @@ function AdvancedSettingsButton(props) {
/>
</span>
<div className={styles.labelContainer}>
<div className={styles.label}>
{advancedSettings ? 'Hide Advanced' : 'Show Advanced'}
</div>
</div>
{
showLabel ?
<div className={styles.labelContainer}>
<div className={styles.label}>
{advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')}
</div>
</div> :
null
}
</Link>
);
}
AdvancedSettingsButton.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired
onAdvancedSettingsPress: PropTypes.func.isRequired,
showLabel: PropTypes.bool.isRequired
};
AdvancedSettingsButton.defaultProps = {
showLabel: true
};
export default AdvancedSettingsButton;

View File

@@ -4,6 +4,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
class CustomFormatSettingsConnector extends Component {
@@ -13,7 +14,7 @@ class CustomFormatSettingsConnector extends Component {
render() {
return (
<PageContent title="Custom Format Settings">
<PageContent title={translate('CustomFormatSettings')}>
<SettingsToolbarConnector
showSave={false}
/>

View File

@@ -5,6 +5,7 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
import ExportCustomFormatModal from './ExportCustomFormatModal';
import styles from './CustomFormat.css';
@@ -92,14 +93,14 @@ class CustomFormat extends Component {
<div className={styles.buttons}>
<IconButton
className={styles.cloneButton}
title="Clone Custom Format"
title={translate('CloneCustomFormat')}
name={icons.CLONE}
onPress={this.onCloneCustomFormatPress}
/>
<IconButton
className={styles.cloneButton}
title="Export Custom Format"
title={translate('ExportCustomFormat')}
name={icons.EXPORT}
onPress={this.onExportCustomFormatPress}
/>
@@ -150,9 +151,9 @@ class CustomFormat extends Component {
<ConfirmModal
isOpen={this.state.isDeleteCustomFormatModalOpen}
kind={kinds.DANGER}
title="Delete Custom Format"
message={`Are you sure you want to delete the custom format '${name}'?`}
confirmLabel="Delete"
title={translate('DeleteCustomFormat')}
message={translate('DeleteCustomFormatMessageText', [name])}
confirmLabel={translate('Delete')}
isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteCustomFormat}
onCancel={this.onDeleteCustomFormatModalClose}

View File

@@ -5,6 +5,7 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import CustomFormat from './CustomFormat';
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
import styles from './CustomFormats.css';
@@ -58,9 +59,9 @@ class CustomFormats extends Component {
} = this.props;
return (
<FieldSet legend="Custom Formats">
<FieldSet legend={translate('CustomFormats')}>
<PageSectionContent
errorMessage="Unable to load custom formats"
errorMessage={translate('UnableToLoadCustomFormats')}
{...otherProps}c={true}
>
<div className={styles.customFormats}>

View File

@@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import ImportCustomFormatModal from './ImportCustomFormatModal';
import AddSpecificationModal from './Specifications/AddSpecificationModal';
import EditSpecificationModalConnector from './Specifications/EditSpecificationModalConnector';
@@ -141,14 +142,14 @@ class EditCustomFormatModalContent extends Component {
<FormInputGroup
type={inputTypes.CHECK}
name="includeCustomFormatWhenRenaming"
helpText={'Include in {Custom Formats} renaming format'}
helpText={translate('IncludeCustomFormatWhenRenamingHelpText')}
{...includeCustomFormatWhenRenaming}
onChange={onInputChange}
/>
</FormGroup>
</Form>
<FieldSet legend={'Conditions'}>
<FieldSet legend={translate('Conditions')}>
<div className={styles.customFormats}>
{
specifications.map((tag) => {

View File

@@ -8,6 +8,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ExportCustomFormatModalContent.css';
class ExportCustomFormatModalContent extends Component {
@@ -59,7 +60,7 @@ class ExportCustomFormatModalContent extends Component {
<ClipboardButton
className={styles.button}
value={json}
title="Copy to clipboard"
title={translate('CopyToClipboard')}
kind={kinds.DEFAULT}
/>
<Button

View File

@@ -14,6 +14,7 @@ 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 translate from 'Utilities/String/translate';
import styles from './EditSpecificationModalContent.css';
function EditSpecificationModalContent(props) {
@@ -98,7 +99,7 @@ function EditSpecificationModalContent(props) {
type={inputTypes.CHECK}
name="negate"
{...negate}
helpText={`If checked, the custom format will not apply if this ${implementationName} condition matches.`}
helpText={translate('NegateHelpText', [implementationName])}
onChange={onInputChange}
/>
</FormGroup>
@@ -112,7 +113,7 @@ function EditSpecificationModalContent(props) {
type={inputTypes.CHECK}
name="required"
{...required}
helpText={`This ${implementationName} condition must match for the custom format to apply. Otherwise a single ${implementationName} match is sufficient.`}
helpText={translate('RequiredHelpText', [implementationName, implementationName])}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -5,6 +5,7 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditSpecificationModalConnector from './EditSpecificationModal';
import styles from './Specification.css';
@@ -77,7 +78,7 @@ class Specification extends Component {
<IconButton
className={styles.cloneButton}
title="Clone"
title={translate('Clone')}
name={icons.CLONE}
onPress={this.onCloneSpecificationPress}
/>
@@ -113,9 +114,9 @@ class Specification extends Component {
<ConfirmModal
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title="Delete Format"
message={`Are you sure you want to delete format tag ${name} ?`}
confirmLabel="Delete"
title={translate('DeleteFormat')}
message={translate('DeleteFormatMessageText', [name])}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}
/>

View File

@@ -13,3 +13,9 @@
.labelIcon {
margin-left: 8px;
}
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

View File

@@ -20,6 +20,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
@@ -74,6 +75,7 @@ function EditImportListModalContent(props) {
id,
name,
enableAutomaticAdd,
minRefreshInterval,
shouldMonitor,
shouldMonitorExisting,
shouldSearch,
@@ -118,6 +120,13 @@ function EditImportListModalContent(props) {
</Alert>
}
<Alert
kind={kinds.INFO}
className={styles.message}
>
{translate('ListWillRefreshEveryInterp', [formatShortTimeSpan(minRefreshInterval.value)])}
</Alert>
<FieldSet legend={translate('ImportListSettings')} >
<FormGroup>
<FormLabel>
@@ -213,6 +222,7 @@ function EditImportListModalContent(props) {
name="rootFolderPath"
helpText={translate('RootFolderPathHelpText')}
{...rootFolderPath}
includeMissingValue={true}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -4,6 +4,7 @@ import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import EditImportListModalConnector from './EditImportListModalConnector';
import styles from './ImportList.css';
@@ -56,6 +57,7 @@ class ImportList extends Component {
id,
name,
enableAutomaticAdd,
minRefreshInterval,
shouldSearch
} = this.props;
@@ -85,6 +87,15 @@ class ImportList extends Component {
}
</div>
<div className={styles.enabled}>
<Label
kind={kinds.INFO}
title={translate('ListRefreshInterval')}
>
{`${translate('Refresh')}: ${formatShortTimeSpan(minRefreshInterval)}`}
</Label>
</div>
<EditImportListModalConnector
id={id}
isOpen={this.state.isEditImportListModalOpen}
@@ -110,6 +121,7 @@ ImportList.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enableAutomaticAdd: PropTypes.bool.isRequired,
minRefreshInterval: PropTypes.string.isRequired,
shouldSearch: PropTypes.bool.isRequired,
onConfirmDeleteImportList: PropTypes.func.isRequired
};

View File

@@ -13,6 +13,7 @@ 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 AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditIndexerModalContent.css';
@@ -31,6 +32,7 @@ function EditIndexerModalContent(props) {
onSavePress,
onTestPress,
onDeleteIndexerPress,
onAdvancedSettingsPress,
...otherProps
} = props;
@@ -43,8 +45,11 @@ function EditIndexerModalContent(props) {
enableInteractiveSearch,
supportsRss,
supportsSearch,
tags,
fields,
priority
priority,
protocol,
downloadClientId
} = item;
return (
@@ -144,6 +149,7 @@ function EditIndexerModalContent(props) {
);
})
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
@@ -162,6 +168,37 @@ function EditIndexerModalContent(props) {
onChange={onInputChange}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('DownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
name="downloadClientId"
helpText={translate('IndexerDownloadClientHelpText')}
{...downloadClientId}
includeAny={true}
protocol={protocol.value}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('Tags')}
</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('IndexerTagsHelpText')}
{...tags}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>
@@ -177,6 +214,12 @@ function EditIndexerModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -216,6 +259,7 @@ EditIndexerModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteIndexerPress: PropTypes.func
};

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer } from 'Store/Actions/settingsActions';
import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer, toggleAdvancedSettings } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditIndexerModalContent from './EditIndexerModalContent';
@@ -23,7 +23,8 @@ const mapDispatchToProps = {
setIndexerValue,
setIndexerFieldValue,
saveIndexer,
testIndexer
testIndexer,
toggleAdvancedSettings
};
class EditIndexerModalContentConnector extends Component {
@@ -56,6 +57,10 @@ class EditIndexerModalContentConnector extends Component {
this.props.testIndexer({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +70,7 @@ class EditIndexerModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -80,6 +86,7 @@ EditIndexerModalContentConnector.propTypes = {
item: PropTypes.object.isRequired,
setIndexerValue: PropTypes.func.isRequired,
setIndexerFieldValue: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
saveIndexer: PropTypes.func.isRequired,
testIndexer: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -4,6 +4,7 @@ import Card from 'Components/Card';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditIndexerModalConnector from './EditIndexerModalConnector';
@@ -68,6 +69,8 @@ class Indexer extends Component {
enableRss,
enableAutomaticSearch,
enableInteractiveSearch,
tags,
tagList,
supportsRss,
supportsSearch,
priority,
@@ -133,6 +136,11 @@ class Indexer extends Component {
}
</div>
<TagList
tags={tags}
tagList={tagList}
/>
<EditIndexerModalConnector
id={id}
isOpen={this.state.isEditIndexerModalOpen}
@@ -161,6 +169,8 @@ Indexer.propTypes = {
enableRss: PropTypes.bool.isRequired,
enableAutomaticSearch: PropTypes.bool.isRequired,
enableInteractiveSearch: PropTypes.bool.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
supportsRss: PropTypes.bool.isRequired,
supportsSearch: PropTypes.bool.isRequired,
showPriority: PropTypes.bool.isRequired,

View File

@@ -54,6 +54,7 @@ class Indexers extends Component {
render() {
const {
items,
tagList,
dispatchCloneIndexer,
onConfirmDeleteIndexer,
...otherProps
@@ -79,6 +80,7 @@ class Indexers extends Component {
<Indexer
key={item.id}
{...item}
tagList={tagList}
showPriority={showPriority}
onCloneIndexerPress={this.onCloneIndexerPress}
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
@@ -119,6 +121,7 @@ Indexers.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchCloneIndexer: PropTypes.func.isRequired,
onConfirmDeleteIndexer: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Indexers from './Indexers';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.indexers', sortByName),
(indexers) => indexers
createTagsSelector(),
(indexers, tagList) => {
return {
...indexers,
tagList
};
}
);
}

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditNotificationModalConnector from './EditNotificationModalConnector';
@@ -80,7 +81,9 @@ class Notification extends Component {
supportsOnDownloadFailure,
supportsOnImportFailure,
supportsOnBookRetag,
supportsOnApplicationUpdate
supportsOnApplicationUpdate,
tags,
tagList
} = this.props;
return (
@@ -208,6 +211,11 @@ class Notification extends Component {
null
}
<TagList
tags={tags}
tagList={tagList}
/>
<EditNotificationModalConnector
id={id}
isOpen={this.state.isEditNotificationModalOpen}
@@ -258,6 +266,8 @@ Notification.propTypes = {
supportsOnImportFailure: PropTypes.bool.isRequired,
supportsOnBookRetag: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -49,6 +49,7 @@ class Notifications extends Component {
render() {
const {
items,
tagList,
onConfirmDeleteNotification,
...otherProps
} = this.props;
@@ -71,6 +72,7 @@ class Notifications extends Component {
<Notification
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteNotification={onConfirmDeleteNotification}
/>
);
@@ -109,6 +111,7 @@ Notifications.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Notifications from './Notifications';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.notifications', sortByName),
(notifications) => notifications
createTagsSelector(),
(notifications, tagList) => {
return {
...notifications,
tagList
};
}
);
}

View File

@@ -214,7 +214,7 @@ class EditQualityProfileModalContent extends Component {
type={inputTypes.NUMBER}
name="minFormatScore"
{...minFormatScore}
helpText="Minimum custom format score allowed to download"
helpText={translate('MinFormatScoreHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@@ -231,14 +231,14 @@ class EditQualityProfileModalContent extends Component {
type={inputTypes.NUMBER}
name="cutoffFormatScore"
{...cutoffFormatScore}
helpText="Once this custom format score is reached Readarr will no longer grab book releases"
helpText={translate('CutoffFormatScoreHelpText')}
onChange={onInputChange}
/>
</FormGroup>
}
<div className={styles.formatItemLarge}>
{getCustomFormatRender(formatItems, ...otherProps)}
{getCustomFormatRender(formatItems, otherProps)}
</div>
</div>

View File

@@ -72,7 +72,7 @@ class Quality extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Reset Definitions"
label={translate('ResetDefinitions')}
iconName={icons.REFRESH}
isSpinning={isResettingQualityDefinitions}
onPress={this.onResetQualityDefinitionsPress}

View File

@@ -9,6 +9,7 @@ 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 translate from 'Utilities/String/translate';
import styles from './ResetQualityDefinitionsModalContent.css';
class ResetQualityDefinitionsModalContent extends Component {
@@ -63,13 +64,15 @@ class ResetQualityDefinitionsModalContent extends Component {
</div>
<FormGroup>
<FormLabel>Reset Titles</FormLabel>
<FormLabel>
{translate('ResetTitles')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="resetDefinitionTitles"
value={resetDefinitionTitles}
helpText="Reset definition titles as well as values"
helpText={translate('ResetDefinitionTitlesHelpText')}
onChange={this.onResetDefinitionTitlesChange}
/>
</FormGroup>

View File

@@ -21,6 +21,7 @@ function TagDetailsModalContent(props) {
importLists,
notifications,
releaseProfiles,
indexers,
onModalClose,
onDeleteTagPress
} = props;
@@ -40,7 +41,7 @@ function TagDetailsModalContent(props) {
}
{
!!author.length &&
author.length ?
<FieldSet legend={translate('Authors')}>
{
author.map((item) => {
@@ -51,11 +52,12 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet>
</FieldSet> :
null
}
{
!!delayProfiles.length &&
delayProfiles.length ?
<FieldSet legend={translate('DelayProfile')}>
{
delayProfiles.map((item) => {
@@ -80,11 +82,12 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet>
</FieldSet> :
null
}
{
!!notifications.length &&
notifications.length ?
<FieldSet legend={translate('Connections')}>
{
notifications.map((item) => {
@@ -95,11 +98,12 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet>
</FieldSet> :
null
}
{
!!importLists.length &&
importLists.length ?
<FieldSet legend={translate('ImportLists')}>
{
importLists.map((item) => {
@@ -110,11 +114,12 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet>
</FieldSet> :
null
}
{
!!releaseProfiles.length &&
releaseProfiles.length ?
<FieldSet legend={translate('ReleaseProfiles')}>
{
releaseProfiles.map((item) => {
@@ -150,13 +155,30 @@ function TagDetailsModalContent(props) {
</Label>
);
})
}
}s
</div>
</div>
);
})
}
</FieldSet>
</FieldSet> :
null
}
{
indexers.length ?
<FieldSet legend={translate('Indexers')}>
{
indexers.map((item) => {
return (
<div key={item.id}>
{item.name}
</div>
);
})
}
</FieldSet> :
null
}
</ModalBody>
@@ -191,6 +213,7 @@ TagDetailsModalContent.propTypes = {
importLists: PropTypes.arrayOf(PropTypes.object).isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired
};

View File

@@ -69,6 +69,14 @@ function createMatchingReleaseProfilesSelector() {
);
}
function createMatchingIndexersSelector() {
return createSelector(
(state, { indexerIds }) => indexerIds,
(state) => state.settings.indexers.items,
findMatchingItems
);
}
function createMapStateToProps() {
return createSelector(
createMatchingAuthorSelector(),
@@ -76,13 +84,15 @@ function createMapStateToProps() {
createMatchingImportListsSelector(),
createMatchingNotificationsSelector(),
createMatchingReleaseProfilesSelector(),
(author, delayProfiles, importLists, notifications, releaseProfiles) => {
createMatchingIndexersSelector(),
(author, delayProfiles, importLists, notifications, releaseProfiles, indexers) => {
return {
author,
delayProfiles,
importLists,
notifications,
releaseProfiles
releaseProfiles,
indexers
};
}
);

View File

@@ -57,6 +57,7 @@ class Tag extends Component {
importListIds,
notificationIds,
restrictionIds,
indexerIds,
authorIds
} = this.props;
@@ -70,6 +71,7 @@ class Tag extends Component {
importListIds.length ||
notificationIds.length ||
restrictionIds.length ||
indexerIds.length ||
authorIds.length
);
@@ -120,6 +122,14 @@ class Tag extends Component {
{restrictionIds.length} restriction{restrictionIds.length > 1 && 's'}
</div>
}
{
indexerIds.length ?
<div>
{indexerIds.length} indexer{indexerIds.length > 1 && 's'}
</div> :
null
}
</div>
}
@@ -138,6 +148,7 @@ class Tag extends Component {
importListIds={importListIds}
notificationIds={notificationIds}
restrictionIds={restrictionIds}
indexerIds={indexerIds}
isOpen={isDetailsModalOpen}
onModalClose={this.onDetailsModalClose}
onDeleteTagPress={this.onDeleteTagPress}
@@ -164,6 +175,7 @@ Tag.propTypes = {
importListIds: PropTypes.arrayOf(PropTypes.number).isRequired,
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired,
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onConfirmDeleteTag: PropTypes.func.isRequired
};
@@ -173,6 +185,7 @@ Tag.defaultProps = {
importListIds: [],
notificationIds: [],
restrictionIds: [],
indexerIds: [],
authorIds: []
};

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDelayProfiles, fetchImportLists, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
import { fetchDelayProfiles, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions';
import Tags from './Tags';
@@ -29,7 +29,8 @@ const mapDispatchToProps = {
dispatchFetchDelayProfiles: fetchDelayProfiles,
dispatchFetchImportLists: fetchImportLists,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchReleaseProfiles: fetchReleaseProfiles
dispatchFetchReleaseProfiles: fetchReleaseProfiles,
dispatchFetchIndexers: fetchIndexers
};
class MetadatasConnector extends Component {
@@ -43,7 +44,8 @@ class MetadatasConnector extends Component {
dispatchFetchDelayProfiles,
dispatchFetchImportLists,
dispatchFetchNotifications,
dispatchFetchReleaseProfiles
dispatchFetchReleaseProfiles,
dispatchFetchIndexers
} = this.props;
dispatchFetchTagDetails();
@@ -51,6 +53,7 @@ class MetadatasConnector extends Component {
dispatchFetchImportLists();
dispatchFetchNotifications();
dispatchFetchReleaseProfiles();
dispatchFetchIndexers();
}
//
@@ -70,7 +73,8 @@ MetadatasConnector.propTypes = {
dispatchFetchDelayProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchReleaseProfiles: PropTypes.func.isRequired
dispatchFetchReleaseProfiles: PropTypes.func.isRequired,
dispatchFetchIndexers: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);

View File

@@ -198,7 +198,9 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormLabel>
{translate('EnableColorImpairedMode')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableColorImpairedMode"

View File

@@ -93,29 +93,29 @@ module.exports = {
defaultButtonTextColor: '#eee',
defaultBackgroundColor: '#333',
defaultBorderColor: '#eaeaea',
defaultBorderColor: '#393f45',
defaultHoverBackgroundColor: '#444',
defaultHoverBorderColor: '#d6d6d6;',
defaultHoverBorderColor: '#5a6265',
primaryBackgroundColor: '#0b8750',
primaryBorderColor: '#1d563d',
primaryHoverBackgroundColor: '#097948',
primaryHoverBorderColor: '#1D563D;',
primaryHoverBorderColor: '#1D563D',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',
@@ -207,8 +207,8 @@ module.exports = {
// Calendar
calendarTodayBackgroundColor: '#3e3e3e',
calendarBackgroudColor: '#2a2a2a',
calendarBorderColor: '#cecece',
calendarBackgroundColor: '#2a2a2a',
calendarBorderColor: '#393f45',
calendarTextDim: '#eee',
calendarTextDimAlternate: '#fff',

View File

@@ -91,27 +91,27 @@ module.exports = {
defaultBackgroundColor: '#fff',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#f5f5f5',
defaultHoverBorderColor: '#d6d6d6;',
defaultHoverBorderColor: '#d6d6d6',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;',
primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',
@@ -203,7 +203,7 @@ module.exports = {
// Calendar
calendarTodayBackgroundColor: '#c5c5c5',
calendarBackgroudColor: '#e4eaec',
calendarBackgroundColor: '#e4eaec',
calendarBorderColor: '#cecece',
//

View File

@@ -0,0 +1,25 @@
import moment from 'moment';
function formatShortTimeSpan(timeSpan) {
if (!timeSpan) {
return '';
}
const duration = moment.duration(timeSpan);
const hours = Math.floor(duration.asHours());
const minutes = Math.floor(duration.asMinutes());
const seconds = Math.floor(duration.asSeconds());
if (hours > 0) {
return `${hours} hour(s)`;
}
if (minutes > 0) {
return `${minutes} minute(s)`;
}
return `${seconds} second(s)`;
}
export default formatShortTimeSpan;

View File

@@ -1,4 +1,4 @@
import filesize from 'filesize';
import { filesize } from 'filesize';
function formatBytes(input, showBits = false) {
const size = Number(input);
@@ -7,11 +7,11 @@ function formatBytes(input, showBits = false) {
return '';
}
return filesize(size, {
return `${filesize(size, {
base: 2,
round: 1,
bits: showBits
});
})}`;
}
export default formatBytes;

View File

@@ -1 +0,0 @@
require('./frontend/gulp/gulpFile.js');

View File

@@ -5,11 +5,11 @@
"scripts": {
"build": "webpack --config ./frontend/build/webpack.config.js",
"prebuild": "yarn clean",
"clean": "rimraf ./_output/UI && rimraf \"**/*.js.map\"",
"clean": "rimraf ./_output/UI && rimraf --glob \"**/*.js.map\"",
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "esprint check",
"lint-fix": "esprint check --fix",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
"lint-fix": "yarn lint --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
},
@@ -22,37 +22,34 @@
"readmeFilename": "readme.md",
"main": "index.js",
"browserslist": [
">0.25%",
"not ie 11",
"not op_mini all",
"not chrome < 60"
"defaults"
],
"dependencies": {
"@fortawesome/fontawesome-free": "6.1.0",
"@fortawesome/fontawesome-svg-core": "6.1.0",
"@fortawesome/free-regular-svg-icons": "6.1.0",
"@fortawesome/free-solid-svg-icons": "6.1.0",
"@fortawesome/react-fontawesome": "0.1.18",
"@microsoft/signalr": "6.0.7",
"@sentry/browser": "6.18.2",
"@sentry/integrations": "6.18.2",
"ansi-colors": "4.1.1",
"classnames": "2.3.1",
"clipboard": "2.0.10",
"connected-react-router": "6.9.1",
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@microsoft/signalr": "6.0.16",
"@sentry/browser": "7.51.2",
"@sentry/integrations": "7.51.2",
"ansi-colors": "4.1.3",
"classnames": "2.3.2",
"clipboard": "2.0.11",
"connected-react-router": "6.9.3",
"element-class": "0.2.2",
"filesize": "7.0.0",
"fuse.js": "6.4.6",
"filesize": "10.0.7",
"fuse.js": "6.6.2",
"history": "4.10.1",
"jdu": "1.0.0",
"jquery": "3.6.0",
"jquery": "3.7.0",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.7.2",
"qs": "6.10.3",
"prop-types": "15.8.1",
"qs": "6.11.1",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -82,59 +79,55 @@
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0",
"reselect": "4.0.0"
"reselect": "4.1.8"
},
"devDependencies": {
"@babel/core": "7.17.8",
"@babel/eslint-parser": "7.17.0",
"@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/plugin-proposal-decorators": "7.17.8",
"@babel/plugin-proposal-export-default-from": "7.16.7",
"@babel/plugin-proposal-export-namespace-from": "7.16.7",
"@babel/plugin-proposal-function-sent": "7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
"@babel/plugin-proposal-numeric-separator": "7.16.7",
"@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/plugin-proposal-throw-expressions": "7.16.7",
"@babel/core": "7.21.8",
"@babel/eslint-parser": "7.21.8",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-export-default-from": "7.18.10",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.16.11",
"@babel/preset-react": "7.16.7",
"autoprefixer": "10.3.1",
"babel-loader": "8.2.3",
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.18.6",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.2",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.15.2",
"css-loader": "6.5.1",
"eslint": "8.11.0",
"core-js": "3.30.2",
"css-loader": "6.7.3",
"eslint": "8.40.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "3.3.0",
"eslint-plugin-simple-import-sort": "10.0.0",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "6.1.4",
"html-webpack-plugin": "5.3.2",
"filemanager-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.1",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.1.0",
"postcss": "8.3.6",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.23",
"postcss-color-function": "4.1.0",
"postcss-loader": "6.2.0",
"postcss-mixins": "8.1.0",
"postcss-nested": "5.0.6",
"postcss-simple-vars": "6.0.3",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
"postcss-nested": "6.0.1",
"postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3",
"require-nocache": "1.0.0",
"rimraf": "3.0.2",
"rimraf": "4.4.1",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.1",
"stylelint": "14.6.0",
"stylelint-order": "5.0.0",
"style-loader": "3.3.2",
"stylelint": "15.6.1",
"stylelint-order": "6.0.3",
"terser-webpack-plugin": "5.3.8",
"url-loader": "4.1.1",
"webpack": "5.64.2",
"webpack-cli": "4.9.1",
"webpack": "5.82.1",
"webpack-cli": "5.1.1",
"webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8"
},

View File

@@ -2,6 +2,7 @@
<!-- Common to all Readarr Projects -->
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
@@ -51,7 +52,7 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<!-- Test projects need bindingRedirects -->
<PropertyGroup Condition="'$(ReadarrOutputType)'=='Test'">
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -64,7 +65,7 @@
<Product>Readarr</Product>
<Company>readarr.com</Company>
<Copyright>Copyright 2017-$([System.DateTime]::Now.ToString('yyyy')) readarr.com (GNU General Public v3)</Copyright>
<!-- Should be replaced by CI -->
<AssemblyVersion>10.0.0.*</AssemblyVersion>
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
@@ -73,7 +74,7 @@
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
</PropertyGroup>

View File

@@ -4,35 +4,35 @@
<PackageVersion Include="AutoFixture" Version="4.17.0" />
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
<PackageVersion Include="Dapper" Version="2.0.123" />
<PackageVersion Include="DryIoc.dll" Version="5.2.0" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.0.2" />
<PackageVersion Include="DryIoc.dll" Version="5.4.0" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="Equ" Version="2.3.0" />
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageVersion Include="FluentValidation" Version="8.6.2" />
<PackageVersion Include="FluentValidation" Version="9.5.4" />
<PackageVersion Include="Ical.Net" Version="4.2.0" />
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
<PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mailkit" Version="3.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.7" />
<PackageVersion Include="Mailkit" Version="3.6.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.16" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
<PackageVersion Include="Moq" Version="4.17.2" />
<PackageVersion Include="MonoTorrent" Version="2.0.7" />
<PackageVersion Include="NBuilder" Version="6.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.1.0" />
<PackageVersion Include="NLog" Version="5.0.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
<PackageVersion Include="NLog" Version="5.1.4" />
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageVersion Include="Npgsql" Version="6.0.8" />
<PackageVersion Include="Npgsql" Version="6.0.9" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
@@ -41,12 +41,13 @@
<PackageVersion Include="RestSharp" Version="106.15.0" />
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
<PackageVersion Include="Sentry" Version="3.25.0" />
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageVersion Include="Sentry" Version="3.31.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageVersion Include="System.Buffers" Version="4.5.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<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.IO.Abstractions.TestingHelpers" Version="17.0.24" />
<PackageVersion Include="System.IO.Abstractions" Version="17.0.24" />
@@ -58,8 +59,8 @@
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageVersion Include="System.Text.Json" Version="6.0.5" />
<PackageVersion Include="System.Text.Json" Version="6.0.7" />
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
</ItemGroup>
</Project>
</Project>

View File

@@ -103,7 +103,7 @@ namespace NzbDrone.Common.Test.CacheTests
}
[Test]
[Retry(3)]
[Retry(10)]
[Platform(Exclude = "MacOsX")]
public void should_clear_expired_when_they_expire()
{

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;

View File

@@ -1,4 +1,5 @@
using NUnit.Framework;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Test.Common;
@@ -12,14 +13,14 @@ namespace NzbDrone.Common.Test.EnsureTest
public void EnsureWindowsPath(string path)
{
WindowsOnly();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
}
[TestCase(@"/var/user/file with, comma.mp3")]
public void EnsureLinuxPath(string path)
{
PosixOnly();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;

View File

@@ -1,5 +1,4 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using NUnit.Framework;
using NzbDrone.Common.Extensions;

View File

@@ -141,7 +141,7 @@ namespace NzbDrone.Common.Test.Http
var request = new HttpRequest($"https://expired.badssl.com");
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
ExceptionVerification.ExpectedErrors(2);
ExceptionVerification.ExpectedErrors(1);
}
[Test]

View File

@@ -35,6 +35,7 @@ namespace NzbDrone.Common.Test
[TestCase(@"\\Testserver\Test\file.ext", @"\\Testserver\Test\file.ext")]
[TestCase(@"\\Testserver\Test\file.ext\\", @"\\Testserver\Test\file.ext")]
[TestCase(@"\\Testserver\Test\file.ext \\", @"\\Testserver\Test\file.ext")]
[TestCase(@"//CAPITAL//lower// ", @"\\CAPITAL\lower")]
public void Clean_Path_Windows(string dirty, string clean)
{
WindowsOnly();

View File

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Cache
{

View File

@@ -5,8 +5,6 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Composition

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Common.Disk
private void CheckFolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FolderExists(path))
{
@@ -82,7 +82,7 @@ namespace NzbDrone.Common.Disk
private void CheckFileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FileExists(path))
{
@@ -100,19 +100,19 @@ namespace NzbDrone.Common.Disk
public bool FolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.Directory.Exists(path);
}
public bool FileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return FileExists(path, PathStringComparison);
}
public bool FileExists(string path, StringComparison stringComparison)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
switch (stringComparison)
{
@@ -132,7 +132,7 @@ namespace NzbDrone.Common.Disk
public bool FolderWritable(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
try
{
@@ -151,42 +151,42 @@ namespace NzbDrone.Common.Disk
public bool FolderEmpty(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.Directory.EnumerateFileSystemEntries(path).Empty();
}
public string[] GetDirectories(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.Directory.GetDirectories(path);
}
public string[] GetDirectories(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.Directory.GetDirectories(path, "*", searchOption);
}
public string[] GetFiles(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.Directory.GetFiles(path, "*.*", searchOption);
}
public long GetFolderSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return GetFiles(path, SearchOption.AllDirectories).Sum(e => _fileSystem.FileInfo.FromFileName(e).Length);
}
public long GetFileSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FileExists(path))
{
@@ -199,13 +199,13 @@ namespace NzbDrone.Common.Disk
public void CreateFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
_fileSystem.Directory.CreateDirectory(path);
}
public void DeleteFile(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Logger.Trace("Deleting file: {0}", path);
RemoveReadOnly(path);
@@ -215,8 +215,8 @@ namespace NzbDrone.Common.Disk
public void CloneFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@@ -233,8 +233,8 @@ namespace NzbDrone.Common.Disk
public void CopyFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@@ -251,8 +251,8 @@ namespace NzbDrone.Common.Disk
public void MoveFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@@ -270,8 +270,8 @@ namespace NzbDrone.Common.Disk
public void MoveFolder(string source, string destination)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Directory.Move(source, destination);
}
@@ -300,7 +300,7 @@ namespace NzbDrone.Common.Disk
public void DeleteFolder(string path, bool recursive)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var files = _fileSystem.Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Array.ForEach(files, RemoveReadOnly);
@@ -310,14 +310,14 @@ namespace NzbDrone.Common.Disk
public string ReadAllText(string filePath)
{
Ensure.That(filePath, () => filePath).IsValidPath();
Ensure.That(filePath, () => filePath).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.File.ReadAllText(filePath);
}
public void WriteAllText(string filename, string contents)
{
Ensure.That(filename, () => filename).IsValidPath();
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
RemoveReadOnly(filename);
// File.WriteAllText is broken on net core when writing to some CIFS mounts
@@ -333,14 +333,14 @@ namespace NzbDrone.Common.Disk
public void FolderSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
_fileSystem.Directory.SetLastWriteTimeUtc(path, dateTime);
}
public void FileSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
_fileSystem.File.SetLastWriteTime(path, dateTime);
}
@@ -362,14 +362,14 @@ namespace NzbDrone.Common.Disk
public string GetPathRoot(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Path.GetPathRoot(path);
}
public string GetParentFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var parent = _fileSystem.Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
@@ -402,7 +402,7 @@ namespace NzbDrone.Common.Disk
public void EmptyFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
{
@@ -473,12 +473,11 @@ namespace NzbDrone.Common.Disk
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
drive.RootDirectory.IsParentPath(path))
.OrderByDescending(drive => drive.RootDirectory.Length)
.FirstOrDefault();
.MaxBy(drive => drive.RootDirectory.Length);
}
catch (Exception ex)
{
Logger.Debug(ex, string.Format("Failed to get mount for path {0}", path));
Logger.Debug(ex, $"Failed to get mount for path {path}");
return null;
}
}
@@ -492,7 +491,7 @@ namespace NzbDrone.Common.Disk
public List<IDirectoryInfo> GetDirectoryInfos(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
@@ -501,13 +500,13 @@ namespace NzbDrone.Common.Disk
public IDirectoryInfo GetDirectoryInfo(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.DirectoryInfo.FromDirectoryName(path);
}
public List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
@@ -516,7 +515,7 @@ namespace NzbDrone.Common.Disk
public IFileInfo GetFileInfo(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return _fileSystem.FileInfo.FromFileName(path);
}

View File

@@ -44,8 +44,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -141,8 +141,8 @@ namespace NzbDrone.Common.Disk
{
var filesCopied = 0;
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -256,8 +256,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Common.Disk
if (
allowFoldersWithoutTrailingSlashes &&
query.IsPathValid() &&
query.IsPathValid(PathValidationType.CurrentOs) &&
_diskProvider.FolderExists(query))
{
return GetResult(query, includeFiles);

View File

@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Security.AccessControl;
using System.Security.Principal;
namespace NzbDrone.Common.Disk
{

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@@ -162,7 +161,7 @@ namespace NzbDrone.Common.Disk
}
}
public bool IsValid => _path.IsPathValid();
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
private int GetFileNameIndex()
{

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