Compare commits

..

137 commits

Author SHA1 Message Date
bakerboy448
d2330a3232
Bump to 2.13.3 2025-08-17 14:42:19 -04:00
Weblate
4cb306780f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: mrchonks <chonkstv@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translation: Servarr/Lidarr
2025-08-16 10:35:00 -05:00
Mark McDowall
393db165f3 New: Move auth success logging to debug
Closes #7978

(cherry picked from commit 9ebe043bd94d036fe2ab45f3bb3f882cda48e211)
2025-08-12 12:22:25 -05:00
Weblate
eb861f06d3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ArLab1 <arnaud.laberge@hotmail.com>
Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: mrchonks <chonkstv@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translation: Servarr/Lidarr
2025-08-11 20:15:07 -05:00
bakerboy448
6f1b370772
docs: add metadata notice & link GHI
[skip ci]
2025-08-05 21:27:28 -05:00
bakerboy448
074f06442a
Bump to 2.13.2 2025-08-03 01:12:50 -05:00
Meyn
fef111d396 Bump SixLabors.ImageSharp to 3.1.11
Signed-off-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2025-07-31 16:28:18 -05:00
jasonpatrickellykrause
76b7713870 Fixed: Clarify monitor language for new and future albums. 2025-07-31 16:28:18 -05:00
bakerboy448
d50ed84541 Skip tests temporally 2025-07-31 16:28:18 -05:00
Weblate
002e8f5b69 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ArLab1 <arnaud.laberge@hotmail.com>
Co-authored-by: Clean Seat <clean.seat9057@fastmail.com>
Co-authored-by: Donato Battista <donato.donelio@gmail.com>
Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Matteo Rettore <matte.rettore@gmail.com>
Co-authored-by: Oleksii Ilienko <assada.ua@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Siujeon <yeungsiujeon@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: josef <josef.holzapfel@proton.me>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_Hans/
Translation: Servarr/Lidarr
2025-07-31 16:17:49 -05:00
bakerboy448
c7b8aa8a04 Skip tests temporally 2025-07-13 20:06:05 -05:00
Bogdan
91f06801ca
Bump version to 2.13.1 2025-06-15 09:22:00 +03:00
Bogdan
dc61618711 Save Publish Dates as UTC for grabbed albums 2025-06-12 11:35:35 +03:00
Bogdan
fd00a5627c Fixed: Improve error message for queue items from Transmission
(cherry picked from commit 0ae07898ba6f85299e739d38ab1dd900c39e91d2)
2025-06-12 11:32:16 +03:00
Bogdan
66ea1b1dfb Fixed: Avoid requests without categories for FileList
(cherry picked from commit 4728fa29ef578a7ff33cf16a4e6b46689c4be1b4)
2025-06-12 11:10:06 +03:00
Bogdan
72fa05cf41 Fixed: Sending notifications for Custom Script with unparsed artist
(cherry picked from commit 76b1130b6811454fa6b1e80e0b2012c24c4ae8fa)
2025-06-12 11:06:46 +03:00
Bogdan
c51b5c6fba Log when expected track file is missing from disk on upgrade
(cherry picked from commit 1047e71b7d78812f2fa04b150fc6774efc1a6af8)
2025-06-12 11:05:21 +03:00
Bogdan
efebab9ba2 Update default log level message
(cherry picked from commit 817d13e85c89d1f10abab09a8f63272a46f5d0b6)
2025-06-12 11:03:29 +03:00
Mark McDowall
47c32c9963 Improve messaging when NZB contains invalid XML
(cherry picked from commit 728df146ada115a367bf1ce808482a4625e6098d)
2025-06-12 10:56:57 +03:00
Stevie Robinson
9f229bb684 Ensure Custom Format Maximum Size won't overflow
(cherry picked from commit a50d2562649bbe77d0feb9fbfc594d56952e0a5e)
2025-06-12 10:56:14 +03:00
Mark McDowall
f9b2e57696 Increase maximum backup restoration size to 5GB
(cherry picked from commit e38deb34221ebf131adcce9551774898f46b1f7f)
2025-06-12 10:55:49 +03:00
carrossos
4b48edab0a Treat HTTP 410 response for failed download similarly to HTTP 404
(cherry picked from commit 818ae02a7a8f0a8ea0a44e0015e2667d96453332)
2025-06-12 10:55:02 +03:00
Stevie Robinson
e087574de7 New: Ignore volumes containing .timemachine from Disk Space
(cherry picked from commit a853c537db0a6bd499a2277987dc170d2a1f5645)
2025-06-12 10:54:16 +03:00
Bogdan
8877cf99f1 Use the thrown exception in http timeout handling
(cherry picked from commit 14e324ee30694ae017a39fd6f66392dc2d104617)
2025-06-12 10:53:59 +03:00
Stevie Robinson
a56e5b3f9a New: Don't allow remote path to start with space
(cherry picked from commit 5ba3ff598770fdf9e5a53d490c8bcbdd6a59c4cc)

Fixed validation for Remote Path Mapping

(cherry picked from commit bf34b4309402ce529a8c04de70f44b28948761f4)
2025-06-12 10:52:01 +03:00
Stevie Robinson
5bb1949ea2 Fixed: Include network drive types in Disk Space
(cherry picked from commit 9ffcd141a515e99604881a4ef383dadafef31eeb)
2025-06-12 10:47:17 +03:00
Bogdan
979042948d Fixed: Quality sliders on some browsers 2025-06-12 10:47:00 +03:00
Mark McDowall
ebe59b18d9 Sync react-slider props for Quality sliders with upstream
(cherry picked from commit 9dab2ba6e4316879e4db8db47363476a5c4f13b2)
2025-06-12 10:46:53 +03:00
Bogdan
086a451dff Follow redirects for usenet grabs on non-prod builds
(cherry picked from commit 1cdca8ef3e47e19c9264db6b322161b615b20294)
2025-06-12 10:39:37 +03:00
Mark McDowall
1bcb82eed0 Prevent should refresh artists and albums from failing
(cherry picked from commit 3eed84c67938fed308e562e69cf7bcd727063803)
2025-06-12 10:38:23 +03:00
Mark McDowall
ae9b4cec75 New: Update wording when removing a root folder
(cherry picked from commit 51c17fd3122f7b96a4155593d465ba32870d0c91)
2025-06-12 10:35:09 +03:00
Mark McDowall
ed777de015 Fixed: Escape backticks in discord notifications
(cherry picked from commit 70c74fc1769f2094a14faaa103c49d744534be9f)
2025-06-12 10:30:03 +03:00
Bogdan
96f956a5d6 Fix fullscreen automation screenshots 2025-06-10 13:13:38 +03:00
Bogdan
68a8f40746 Fixed translations for the updates page 2025-06-09 23:33:06 +03:00
Bogdan
c518cf63e7
Bump version to 2.13.0 2025-06-09 20:28:15 +03:00
Bogdan
da55b8578a Bump IPAddressRange and SixLabors.ImageSharp 2025-06-09 15:21:12 +03:00
Bogdan
234c29ef49
Bump version to 2.12.4 2025-06-08 10:31:50 +03:00
Weblate
de169e8a1f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: CandyD-1992 <259215606@qq.com>
Co-authored-by: Ilbebino <tommasobellandi08@gmail.com>
Co-authored-by: NanderTGA <nander.roobaert@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2025-06-06 21:24:28 +03:00
Bogdan
4b300a448a Skip tests temporally 2025-06-06 18:06:09 +03:00
Gykes
785bcfda0b
Fixed: Sort artists by genre on index table view 2025-06-03 09:13:36 +03:00
Bogdan
94ea751ad2 Ignore Jetbrains IntelliJ Workspace Directories 2025-06-02 11:18:44 +03:00
Bogdan
0c172b58f1
Bump version to 2.12.3 2025-06-01 10:40:54 +03:00
Weblate
ea2ee70208 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Discover999 <13189912235@163.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: arsenius88 <arsenovich_andr@ukr.net>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_Hans/
Translation: Servarr/Lidarr
2025-05-25 16:19:12 +03:00
Bogdan
8b63928a25
Bump version to 2.12.2 2025-05-25 16:18:38 +03:00
Bogdan
7217e891f7 New: Real time UI updates for provider changes
(cherry picked from commit 20ef22be94f4bdb5633ddfb080e91c8d5b0229da)

Closes #5178
2025-05-22 19:36:21 +03:00
Bogdan
345bbcd992
Bump version to 2.12.1 2025-05-04 21:08:35 +03:00
Bogdan
bd9d7ba085 Fixed: Parsing FLAC24 as FLAC 24-bit 2025-05-01 17:26:52 +03:00
Bogdan
3937bebfea Add plugins branch to the bug report template 2025-05-01 15:57:05 +03:00
Bogdan
767b0930a5 Bump caniuse db 2025-04-28 15:02:42 +03:00
Bogdan
c3f0fc640c Bump core-js to 3.41 2025-04-28 15:01:30 +03:00
Bogdan
9dbcc79436 Bump version to 2.12.0 2025-04-28 15:00:29 +03:00
Bogdan
3dd04cecbf Skip spotify mapping tests 2025-04-28 12:41:35 +03:00
Bogdan
d8850af019 Increase input sizes in edit artist modal
Closes #5294
2025-04-17 12:36:02 +03:00
Bogdan
fbfd24e226
Bump version to 2.11.2 2025-04-13 10:08:22 +03:00
Weblate
d9562c701e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Ste <stefanucciu@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translation: Servarr/Lidarr
2025-04-08 15:40:25 +03:00
Servarr
d21ad2ad68 Automated API Docs update 2025-04-08 15:38:55 +03:00
Bogdan
556f0ea54b Fixed: Disallow tags creation with empty label 2025-04-08 15:30:20 +03:00
Bogdan
e4a36ca388 Log delete statements only once 2025-04-08 15:29:28 +03:00
Bogdan
1045684935 Bump Selenium.WebDriver.ChromeDriver 2025-04-08 15:28:37 +03:00
Mark McDowall
9ba71ae6b1 Update WikiUrl type in API docs
(cherry picked from commit 9bd619ccfe074abe396bbf043a36a5be18a7ba4b)
2025-04-08 15:28:06 +03:00
Mark McDowall
89b9352fef Fixed: Set output encoding to UTF-8 when running external processes
(cherry picked from commit f8e57b09856278a6d0c65f18704e96a33459687d)
2025-04-08 15:26:59 +03:00
Mark McDowall
c83332e58c New: Prevent Remote Path Mapping local folder being set to System folder or '/'
(cherry picked from commit 0f904e091702a2ac53771ee3aeb5aafe62688035)
2025-04-08 15:26:38 +03:00
Bogdan
4677a1115a
Bump linux agent to ubuntu-22.04 2025-04-02 00:11:01 +03:00
Weblate
6150a57596 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Maxime Surrel <maxime.surrel@live.fr>
Co-authored-by: Moxitech <moxitogame59@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Xing Wang <wxing82@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2025-03-30 10:17:56 +03:00
Bogdan
13f6b1a086
Bump version to 2.11.1 2025-03-30 10:17:14 +03:00
Bogdan
8027ab5d2e
Include invalid path in exception message when failing to normalize 2025-03-28 09:57:57 +02:00
Bogdan
5bdc119b98 Fixed: Include Track for history/since
Fixes #5421
2025-03-27 19:47:03 +02:00
Bogdan
1b9b57ae9b Bump browserslist-db 2025-03-25 21:13:13 +02:00
Mark McDowall
c28a97cafd Fixed: Deleting artist folder fails when files/folders aren't instantly removed
(cherry picked from commit c84699ed5d5a2f59f236c26a8999d25a1102ec02)
2025-03-25 21:11:58 +02:00
Bogdan
099d19a04d Cleanup unused sorting fields for bulk manage providers
(cherry picked from commit 6115236d3853f70a18b73aef15ebe4e18ab48e40)
2025-03-25 21:11:32 +02:00
Bogdan
d381463b60 New: Display indexer in download failed details
(cherry picked from commit a324052debf63a8db73a2f3c79201864892bb62c)
2025-03-25 21:10:19 +02:00
Bogdan
a86bd8e862 Fixed: Inherit indexer, size and release group for marked as failed history
(cherry picked from commit e08c9d5501e65aabce3456b2dd7571867508d88f)
2025-03-25 21:08:13 +02:00
Mark McDowall
4bea38ab9c Improve logging when login fails due to CryptographicException
(cherry picked from commit 1449941471cbb8885e9298317b9a30f2576d7941)
2025-03-25 21:03:41 +02:00
Bogdan
950c51bc59 Fixed: Priority validation for indexers and download clients
(cherry picked from commit f0e320f3aa501f120721503b8256f464a31be783)
2025-03-25 21:00:43 +02:00
Mark McDowall
18f13fe7f8 Fixed: Allow tables to scroll on tablets in portrait mode
(cherry picked from commit 5fb632eb46cf77ea4f61d407f6429d9c32dba766)
2025-03-25 20:58:58 +02:00
Bogdan
f8d4b3a59b Bump NLog, Npgsql, System.Memory and System.ValueTuple 2025-03-23 13:10:50 +02:00
Bogdan
5cf9624e55
Bump version to 2.11.0 2025-03-23 13:02:07 +02:00
Mark McDowall
81895f8033 Fixed: Drop downs flickering in some cases
(cherry picked from commit 3b024443c5447b7638a69a99809bf44b2419261f)

Closes #5386
2025-03-23 09:42:29 +02:00
Weblate
a1c2bfa527 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2025-03-16 11:38:19 +02:00
Bogdan
33049910de
Bump version to 2.10.3 2025-03-16 10:47:47 +02:00
Bogdan
6dd87fd348
Bump version to 2.10.2 2025-03-09 11:52:12 +02:00
Bogdan
9314eb34ab Fixed: Displaying warnings for automatic failed imports in queue 2025-03-08 15:44:52 +02:00
Bogdan
84b91ba6c1 Bump Polly to 8.5.2 2025-03-07 23:18:35 +02:00
Bogdan
6c6f92fbed Bump SixLabors.ImageSharp to 3.1.7 2025-03-07 23:17:29 +02:00
Bogdan
1e42ae94aa Fix Completed Download Service tests 2025-03-06 19:49:26 +02:00
Servarr
29f5810865 Automated API Docs update 2025-03-06 19:24:11 +02:00
Bogdan
342c82aa1f Fixed: Avoid notifications on reprocessing failed items in queue 2025-03-06 19:15:59 +02:00
Bogdan
5a3f879442 Fixed: Sending import failure notifications to webhook/notifiarr 2025-03-06 17:28:36 +02:00
Mark McDowall
6e57c14e57 Fixed: Marking queued item as failed not blocking the correct Torrent Info Hash
(cherry picked from commit 4b186e894e4e229a435c077e00c65b67ca178333)

Fixes #4977
Closes #4988
2025-03-06 16:34:28 +02:00
Bogdan
9fc549b43b Fixed: Replace diacritics in Clean Title naming tokens 2025-03-05 20:12:15 +02:00
Weblate
a2201001c5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: youngjimisme <977671346@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2025-03-05 20:03:53 +02:00
Mark McDowall
8c99280f07 Fixed: Adding albums with unknown items in queue 2025-03-05 19:59:56 +02:00
Bogdan
07db508580 Fixed: Calculating custom formats for queue 2025-03-05 19:59:56 +02:00
Bogdan
031f32a52c Fixed: Refresh cache for tracked queue on artist/album add or removal
Prevents a NullRef in CompletedDownloadService.VerifyImport when publishing DownloadCompletedEvent for an already cached tracked download
2025-03-05 19:59:56 +02:00
Mark McDowall
2997c16346 Fixed: Reprocessing items that were previously blocked during importing
(cherry picked from commit bce848facf8aeaeac6a1d59c92941d00589034a4)
2025-03-05 19:59:56 +02:00
Mark McDowall
a1a53dbb5e New: Improve UI status when downloads cannot be imported automatically
(cherry picked from commit 6d5ff9c4d6993d16848980aea499a45b1b51d95c)
2025-03-05 19:59:56 +02:00
Mark McDowall
e8bb78e5bb New: Improve messaging if release is in queue because all tracks in release were not imported
(cherry picked from commit 2728bf79ca41bc372de515cb09e1034a8c006c2b)
2025-03-05 19:59:56 +02:00
Bogdan
6292f223ac Fixed: Attempt to ensure all import results are imported
Fixes #2746
Closes #4815
2025-03-05 19:53:11 +02:00
Bogdan
f4dc294ab3 Fixed: Instance name must contain application name 2025-03-01 13:35:07 +02:00
Bogdan
23611cb116
Bump version to 2.10.1 2025-02-23 12:16:19 +02:00
Bogdan
f177345d01 Fixed: Avoid checking for free space if other specifications fail first 2025-02-22 21:35:18 +02:00
Bogdan
ec050a7b3c Fixed: Prevent NullRef for webhooks when Artist Metadata is not set
Fixes #5368
2025-02-22 16:36:25 +02:00
Mark McDowall
860bd04c59 New: Add artist tags to Webhook and Notifiarr events
(cherry picked from commit cc0a284660f139d5f47b27a2c389973e5e888587)

Closes #4805
2025-02-22 16:26:26 +02:00
Mark McDowall
261f30d268 New: Genres and Images for Webhooks and Notifiarr
(cherry picked from commit fd3dd1ab7dc86cd9e231fa432cc8d2772d5a4bad)

Closes #4832
2025-02-22 16:26:00 +02:00
Weblate
36998abba0 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_Hans/
Translation: Servarr/Lidarr
2025-02-19 17:25:54 +02:00
Mark McDowall
ad12617694 Cleanse console log messages
(cherry picked from commit 609e964794e17343f63e1ecff3fef323e3d284ff)
2025-02-19 17:09:39 +02:00
Stevie Robinson
be115da157 Fixed: Fallback to Instance Name for Discord notifications
(cherry picked from commit b99e06acc0a3ecae2857d9225b35424c82c67a2b)
2025-02-19 17:06:58 +02:00
Chaz Harris
664b972494
Fixed: Custom Lists using only ArtistMusicBrainzId (#5399)
When using a JSON list that consists of only MusicBrainzId's the list is being filtered.
2025-02-16 21:03:41 +02:00
Bogdan
2b2fd5a175 Fix download links for FileList when passkey contains spaces 2025-02-16 12:20:27 +02:00
Chaz Harris
d8222c066c
Bump devcontainer nodejs version to 20 (#5398) 2025-02-15 11:47:22 +02:00
Weblate
bc6417229e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translation: Servarr/Lidarr
2025-02-07 19:15:05 -06:00
Bogdan
e0e17a2ea7 Building docs on ARM
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
(cherry picked from commit 147e732c9ca7a4c289d4f6386f1277650e11f15b)
(cherry picked from commit dd900eb7395144b6d299f10fe9475d49d194664e)
2025-02-06 00:54:20 +02:00
Bogdan
5bf2ae9e6f
Bump version to 2.10.0 2025-02-03 14:11:43 +02:00
Bogdan
8e01ba5f21
Bump version to 2.9.6 2025-02-02 12:48:53 +02:00
Mark McDowall
45e8ecffa0 Fixed: Ignore special folders inside Blackhole watch folders
(cherry picked from commit e79dd6f8e689617b1fd9f96c639ac300669112c5)
2025-02-01 23:52:09 +02:00
Bogdan
3c4b438d27 Fixed: Health warning for downloading inside root folders
(cherry picked from commit 1e9fd02e9d2bf57247adcac5728e2a0d2b084b86)

Fixes #5384
2025-02-01 23:51:13 +02:00
Bogdan
8fd79d7291 New: Prefer newer Usenet releases
(cherry picked from commit 6a439f03273b376feda713ef04a6912fc3af9d0a)
2025-02-01 23:45:27 +02:00
Weblate
477a799b8a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translation: Servarr/Lidarr
2025-02-01 23:45:21 +02:00
Bogdan
51a38bc648 Fix logging message for directory watcher error 2025-01-28 21:57:30 +02:00
Weblate
917f705695 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Pieterjan Van Saet <hybridfox2@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: ahgharaghani <ah.gharaghani@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: warkurre86 <tom.novo.86@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fa/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_Hans/
Translation: Servarr/Lidarr
2025-01-28 21:55:36 +02:00
Bogdan
5a1092b511 Prevent page crash on console.error being used with non-string values
(cherry picked from commit 963395b9695a28af6bc7dd398e9eea18c834c3c9)
2025-01-28 21:49:43 +02:00
jcassette
ef2c6366c4 New: reflink support for ZFS
(cherry picked from commit a840bb542362d58006b6cc27affd58ee6b965b80)

Closes #5369
2025-01-19 17:32:47 +02:00
Bogdan
1ffb82e364
Bump version to 2.9.5 2025-01-19 17:16:09 +02:00
Bogdan
e2f8753a6a Improve messaging for no mediums on album details 2025-01-17 00:42:08 +02:00
Gauthier
739019498f New: Add headers setting in webhook connection
(cherry picked from commit 78fb20282de73c0ea47375895a807235385d90e3)

Closes #5242
2025-01-15 23:06:47 +02:00
Bogdan
396b2ae7c1 Bump SonarCloud azure extension for UI analysis to 3.X 2025-01-14 11:19:52 +02:00
Qstick
0216616738 Bump SonarCloud azure extension to 3.X
(cherry picked from commit 5fac3486130df3b316dd882d676ca13ecb697b59)
2025-01-14 10:09:06 +02:00
Mark McDowall
82e0b628cc Fixed: Parsing of release names with colon in the title
(cherry picked from commit ec698c2cf7df1e1182ffa2f4505fe0872e2d08bc)
2025-01-14 04:16:12 +02:00
Weblate
014f8a58b1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Mickaël O <mickael.ouillon@ac-bordeaux.fr>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translation: Servarr/Lidarr
2025-01-12 15:18:26 +02:00
Bogdan
5cbb2848c7
Bump version to 2.9.4 2025-01-12 15:17:02 +02:00
Weblate
554cf8ec55 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander Balya <alexander.balya@gmail.com>
Co-authored-by: Ano10 <Ano10@users.noreply.translate.servarr.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Matti Meikäläinen <diefor-93@hotmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Tommy Au <smarttommyau@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_TW/
Translation: Servarr/Lidarr
2025-01-05 13:17:21 +02:00
Stevie Robinson
4ff6c71456 Fixed: Listening on all IPv4 Addresses
(cherry picked from commit 035c474f10c257331a5f47e863d24af82537e335)
2025-01-05 13:16:36 +02:00
Stevie Robinson
7cfcf01ae3 Fixed: qBittorrent Ratio Limit Check
(cherry picked from commit 4dcc015fb19ceb57d2e8f4985c5137e765829d1c)
2025-01-05 13:16:24 +02:00
Bogdan
17c5c66e54
Bump version to 2.9.3 2025-01-05 13:16:11 +02:00
Bogdan
40dab8deb9 Check if backup folder is writable on backup
(cherry picked from commit 8aad79fd3e14eb885724a5e5790803c289be2f25)

Closes #5348
2024-12-31 12:20:10 +02:00
Bogdan
39f0e4d989 Suggest adding IP to RPC whitelist for on failed Transmission auth
(cherry picked from commit f05e552e8e6dc02cd26444073ab9a678dcb36492)
2024-12-31 12:19:06 +02:00
Bogdan
35a46eca7b
Bump version to 2.9.2 2024-12-30 01:01:47 +02:00
214 changed files with 3894 additions and 1514 deletions

View file

@ -6,7 +6,7 @@
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"version": "20",
"nvmVersion": "latest"
}
},

View file

@ -60,6 +60,7 @@ body:
- Master
- Develop
- Nightly
- Plugins (experimental)
- Other (This issue will be closed)
validations:
required: true

28
.gitignore vendored
View file

@ -158,34 +158,12 @@ Thumbs.db
/tools/Addins/*
packages.config.md5sum
# Common IntelliJ Platform excludes
# User specific
**/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
**/.idea/.idea.Radarr.Posix
**/.idea/.idea.Radarr.Windows
# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml
# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml
# ignore node_modules symlink
node_modules
node_modules.nosync
# API doc generation
.config/
# Ignore Jetbrains IntelliJ Workspace Directories
.idea/

View file

@ -9,6 +9,9 @@
Lidarr is a music collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new tracks from your favorite artists and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
> [!WARNING]
> NOTICE - The Lidarr Metadata Server is currently down impacting adding artists, etc. Please follow [GHI 5498](https://github.com/Lidarr/Lidarr/issues/5498) or see Discord for detaila.
## Major Features Include:
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.

View file

@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.9.1'
majorVersion: '2.13.3'
minorVersion: $[counter('minorVersion', 1076)]
lidarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(lidarrVersion)'
@ -19,7 +19,7 @@ variables:
nodeVersion: '20.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
linuxImage: 'ubuntu-22.04'
macImage: 'macOS-13'
trigger:
@ -1120,19 +1120,19 @@ stages:
vmImage: ${{ variables.windowsImage }}
steps:
- checkout: self # Need history for Sonar analysis
- task: SonarCloudPrepare@2
- task: SonarCloudPrepare@3
env:
SONAR_SCANNER_OPTS: ''
inputs:
SonarCloud: 'SonarCloud'
organization: 'lidarr'
scannerMode: 'CLI'
scannerMode: 'cli'
configMode: 'manual'
cliProjectKey: 'lidarr_Lidarr.UI'
cliProjectName: 'LidarrUI'
cliProjectVersion: '$(lidarrVersion)'
cliSources: './frontend'
- task: SonarCloudAnalyze@2
- task: SonarCloudAnalyze@3
- job: Api_Docs
displayName: API Docs
@ -1208,12 +1208,12 @@ stages:
submodules: true
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
- task: SonarCloudPrepare@2
- task: SonarCloudPrepare@3
condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs:
SonarCloud: 'SonarCloud'
organization: 'lidarr'
scannerMode: 'MSBuild'
scannerMode: 'dotnet'
projectKey: 'lidarr_Lidarr'
projectName: 'Lidarr'
projectVersion: '$(lidarrVersion)'
@ -1226,7 +1226,7 @@ stages:
./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@2
- task: SonarCloudAnalyze@3
condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results
- task: reportgenerator@5.3.11

15
docs.sh
View file

@ -1,13 +1,18 @@
#!/bin/bash
set -e
FRAMEWORK="net6.0"
PLATFORM=$1
ARCHITECTURE="${2:-x64}"
if [ "$PLATFORM" = "Windows" ]; then
RUNTIME="win-x64"
RUNTIME="win-$ARCHITECTURE"
elif [ "$PLATFORM" = "Linux" ]; then
RUNTIME="linux-x64"
RUNTIME="linux-$ARCHITECTURE"
elif [ "$PLATFORM" = "Mac" ]; then
RUNTIME="osx-x64"
RUNTIME="osx-$ARCHITECTURE"
else
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
echo "Platform must be provided as first argument: Windows, Linux or Mac"
exit 1
fi
@ -35,7 +40,7 @@ dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p
dotnet new tool-manifest
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Lidarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/$application" v1 &
dotnet tool run swagger tofile --output ./src/Lidarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 &
sleep 45

View file

@ -188,7 +188,7 @@ module.exports = (env) => {
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: '3.39'
corejs: '3.41'
}
]
]

View file

@ -172,7 +172,8 @@ function HistoryDetails(props) {
if (eventType === 'downloadFailed') {
const {
message
message,
indexer
} = data;
return (
@ -192,6 +193,14 @@ function HistoryDetails(props) {
null
}
{
indexer ? (
<DescriptionListItem
title={translate('Indexer')}
data={indexer}
/>
) : null}
{
message ?
<DescriptionListItem

View file

@ -57,30 +57,40 @@ function QueueStatusCell(props) {
if (status === 'paused') {
iconName = icons.PAUSED;
title = 'Paused';
title = translate('Paused');
}
if (status === 'queued') {
iconName = icons.QUEUED;
title = 'Queued';
title = translate('Queued');
}
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = 'Downloaded';
title = translate('Downloaded');
if (trackedDownloadState === 'importBlocked') {
title += ` - ${translate('UnableToImportAutomatically')}`;
iconKind = kinds.WARNING;
}
if (trackedDownloadState === 'importFailed') {
title += ` - ${translate('ImportFailed', { sourceTitle })}`;
iconKind = kinds.WARNING;
}
if (trackedDownloadState === 'importPending') {
title += ' - Waiting to Import';
title += ` - ${translate('WaitingToImport')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ' - Importing';
title += ` - ${translate('Importing')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ' - Waiting to Process';
title += ` - ${translate('WaitingToProcess')}`;
iconKind = kinds.DANGER;
}
}
@ -91,36 +101,38 @@ function QueueStatusCell(props) {
if (status === 'delay') {
iconName = icons.PENDING;
title = 'Pending';
title = translate('Pending');
}
if (status === 'downloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = 'Pending - Download client is unavailable';
title = translate('PendingDownloadClientUnavailable');
}
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = 'Download failed';
title = translate('DownloadFailed');
}
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
const warningMessage =
errorMessage || translate('CheckDownloadClientForDetails');
title = translate('DownloadWarning', { warningMessage });
}
if (hasError) {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = `Import failed: ${sourceTitle}`;
title = translate('ImportFailed', { sourceTitle });
} else {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = 'Download failed';
title = translate('DownloadFailed');
}
}

View file

@ -8,17 +8,17 @@ function ArtistMonitorNewItemsOptionsPopoverContent() {
<DescriptionList>
<DescriptionListItem
title={translate('AllAlbums')}
data="Monitor all new albums"
data={translate('MonitorAllAlbums')}
/>
<DescriptionListItem
title={translate('NewAlbums')}
data="Monitor new albums released after the newest existing album"
data={translate('MonitorNewAlbumsData')}
/>
<DescriptionListItem
title={translate('None')}
data="Don't monitor any new albums"
data={translate('MonitorNoAlbumsData')}
/>
</DescriptionList>
);

View file

@ -205,6 +205,7 @@ class AlbumDetails extends Component {
isFetching,
isPopulated,
albumsError,
tracksError,
trackFilesError,
hasTrackFiles,
shortDateFormat,
@ -552,8 +553,9 @@ class AlbumDetails extends Component {
<div className={styles.contentContainer}>
{
!isPopulated && !albumsError && !trackFilesError &&
<LoadingIndicator />
!isPopulated && !albumsError && !tracksError && !trackFilesError ?
<LoadingIndicator /> :
null
}
{
@ -564,6 +566,14 @@ class AlbumDetails extends Component {
null
}
{
!isFetching && tracksError ?
<Alert kind={kinds.DANGER}>
{translate('TracksLoadError')}
</Alert> :
null
}
{
!isFetching && trackFilesError ?
<Alert kind={kinds.DANGER}>
@ -592,6 +602,14 @@ class AlbumDetails extends Component {
</div>
}
{
isPopulated && !media.length ?
<Alert kind={kinds.WARNING}>
{translate('NoMediumInformation')}
</Alert> :
null
}
</div>
<OrganizePreviewModalConnector
@ -686,6 +704,7 @@ AlbumDetails.propTypes = {
AlbumDetails.defaultProps = {
secondaryTypes: [],
statistics: {},
isSaving: false
};

View file

@ -15,7 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
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 { icons, inputTypes, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditArtistModalContent.css';
@ -93,7 +93,7 @@ class EditArtistModalContent extends Component {
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('Monitored')}
</FormLabel>
@ -107,9 +107,10 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
@ -132,7 +133,7 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('QualityProfile')}
</FormLabel>
@ -146,10 +147,10 @@ class EditArtistModalContent extends Component {
</FormGroup>
{
showMetadataProfile &&
<FormGroup>
showMetadataProfile ?
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
Metadata Profile
{translate('MetadataProfile')}
<Popover
anchor={
@ -173,10 +174,11 @@ class EditArtistModalContent extends Component {
{...metadataProfileId}
onChange={onInputChange}
/>
</FormGroup>
</FormGroup> :
null
}
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('Path')}
</FormLabel>
@ -189,7 +191,7 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('Tags')}
</FormLabel>
@ -209,7 +211,7 @@ class EditArtistModalContent extends Component {
kind={kinds.DANGER}
onPress={onDeleteArtistPress}
>
Delete
{translate('Delete')}
</Button>
<Button

View file

@ -25,7 +25,7 @@ const EVENT_TYPE_OPTIONS = [
{
id: 7,
get name() {
return translate('ImportFailed');
return translate('ImportCompleteFailed');
},
},
{

View file

@ -20,6 +20,8 @@ import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
import TextInput from './TextInput';
import styles from './EnhancedSelectInput.css';
const MINIMUM_DISTANCE_FROM_EDGE = 10;
function isArrowKey(keyCode) {
return keyCode === keyCodes.UP_ARROW || keyCode === keyCodes.DOWN_ARROW;
}
@ -137,18 +139,9 @@ class EnhancedSelectInput extends Component {
// Listeners
onComputeMaxHeight = (data) => {
const {
top,
bottom
} = data.offsets.reference;
const windowHeight = window.innerHeight;
if ((/^botton/).test(data.placement)) {
data.styles.maxHeight = windowHeight - bottom;
} else {
data.styles.maxHeight = top;
}
data.styles.maxHeight = windowHeight - MINIMUM_DISTANCE_FROM_EDGE;
return data;
};
@ -457,6 +450,10 @@ class EnhancedSelectInput extends Component {
order: 851,
enabled: true,
fn: this.onComputeMaxHeight
},
preventOverflow: {
enabled: true,
boundariesElement: 'viewport'
}
}}
>

View file

@ -49,12 +49,12 @@ function getComponent(type) {
case inputTypes.DEVICE:
return DeviceInputConnector;
case inputTypes.PLAYLIST:
return PlaylistInputConnector;
case inputTypes.KEY_VALUE_LIST:
return KeyValueListInput;
case inputTypes.PLAYLIST:
return PlaylistInputConnector;
case inputTypes.MONITOR_ALBUMS_SELECT:
return MonitorAlbumsSelectInput;

View file

@ -1,156 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import KeyValueListInputItem from './KeyValueListInputItem';
import styles from './KeyValueListInput.css';
class KeyValueListInput extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isFocused: false
};
}
//
// Listeners
onItemChange = (index, itemValue) => {
const {
name,
value,
onChange
} = this.props;
const newValue = [...value];
if (index == null) {
newValue.push(itemValue);
} else {
newValue.splice(index, 1, itemValue);
}
onChange({
name,
value: newValue
});
};
onRemoveItem = (index) => {
const {
name,
value,
onChange
} = this.props;
const newValue = [...value];
newValue.splice(index, 1);
onChange({
name,
value: newValue
});
};
onFocus = () => {
this.setState({
isFocused: true
});
};
onBlur = () => {
this.setState({
isFocused: false
});
const {
name,
value,
onChange
} = this.props;
const newValue = value.reduce((acc, v) => {
if (v.key || v.value) {
acc.push(v);
}
return acc;
}, []);
if (newValue.length !== value.length) {
onChange({
name,
value: newValue
});
}
};
//
// Render
render() {
const {
className,
value,
keyPlaceholder,
valuePlaceholder,
hasError,
hasWarning
} = this.props;
const { isFocused } = this.state;
return (
<div className={classNames(
className,
isFocused && styles.isFocused,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
>
{
[...value, { key: '', value: '' }].map((v, index) => {
return (
<KeyValueListInputItem
key={index}
index={index}
keyValue={v.key}
value={v.value}
keyPlaceholder={keyPlaceholder}
valuePlaceholder={valuePlaceholder}
isNew={index === value.length}
onChange={this.onItemChange}
onRemove={this.onRemoveItem}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
);
})
}
</div>
);
}
}
KeyValueListInput.propTypes = {
className: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.object).isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
keyPlaceholder: PropTypes.string,
valuePlaceholder: PropTypes.string,
onChange: PropTypes.func.isRequired
};
KeyValueListInput.defaultProps = {
className: styles.inputContainer,
value: []
};
export default KeyValueListInput;

View file

@ -0,0 +1,104 @@
import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { InputOnChange } from 'typings/inputs';
import KeyValueListInputItem from './KeyValueListInputItem';
import styles from './KeyValueListInput.css';
interface KeyValue {
key: string;
value: string;
}
export interface KeyValueListInputProps {
className?: string;
name: string;
value: KeyValue[];
hasError?: boolean;
hasWarning?: boolean;
keyPlaceholder?: string;
valuePlaceholder?: string;
onChange: InputOnChange<KeyValue[]>;
}
function KeyValueListInput({
className = styles.inputContainer,
name,
value = [],
hasError = false,
hasWarning = false,
keyPlaceholder,
valuePlaceholder,
onChange,
}: KeyValueListInputProps): JSX.Element {
const [isFocused, setIsFocused] = useState(false);
const handleItemChange = useCallback(
(index: number | null, itemValue: KeyValue) => {
const newValue = [...value];
if (index === null) {
newValue.push(itemValue);
} else {
newValue.splice(index, 1, itemValue);
}
onChange({ name, value: newValue });
},
[value, name, onChange]
);
const handleRemoveItem = useCallback(
(index: number) => {
const newValue = [...value];
newValue.splice(index, 1);
onChange({ name, value: newValue });
},
[value, name, onChange]
);
const onFocus = useCallback(() => setIsFocused(true), []);
const onBlur = useCallback(() => {
setIsFocused(false);
const newValue = value.reduce((acc: KeyValue[], v) => {
if (v.key || v.value) {
acc.push(v);
}
return acc;
}, []);
if (newValue.length !== value.length) {
onChange({ name, value: newValue });
}
}, [value, name, onChange]);
return (
<div
className={classNames(
className,
isFocused && styles.isFocused,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
>
{[...value, { key: '', value: '' }].map((v, index) => (
<KeyValueListInputItem
key={index}
index={index}
keyValue={v.key}
value={v.value}
keyPlaceholder={keyPlaceholder}
valuePlaceholder={valuePlaceholder}
isNew={index === value.length}
onChange={handleItemChange}
onRemove={handleRemoveItem}
onFocus={onFocus}
onBlur={onBlur}
/>
))}
</div>
);
}
export default KeyValueListInput;

View file

@ -5,13 +5,19 @@
&:last-child {
margin-bottom: 0;
border-bottom: 0;
}
}
.inputWrapper {
.keyInputWrapper {
flex: 1 0 0;
}
.valueInputWrapper {
flex: 1 0 0;
min-width: 40px;
}
.buttonWrapper {
flex: 0 0 22px;
}
@ -20,6 +26,10 @@
.valueInput {
width: 100%;
border: none;
background-color: var(--inputBackgroundColor);
background-color: transparent;
color: var(--textColor);
&::placeholder {
color: var(--helpTextColor);
}
}

View file

@ -2,10 +2,11 @@
// Please do not change this file!
interface CssExports {
'buttonWrapper': string;
'inputWrapper': string;
'itemContainer': string;
'keyInput': string;
'keyInputWrapper': string;
'valueInput': string;
'valueInputWrapper': string;
}
export const cssExports: CssExports;
export default cssExports;

View file

@ -1,124 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import { icons } from 'Helpers/Props';
import TextInput from './TextInput';
import styles from './KeyValueListInputItem.css';
class KeyValueListInputItem extends Component {
//
// Listeners
onKeyChange = ({ value: keyValue }) => {
const {
index,
value,
onChange
} = this.props;
onChange(index, { key: keyValue, value });
};
onValueChange = ({ value }) => {
// TODO: Validate here or validate at a lower level component
const {
index,
keyValue,
onChange
} = this.props;
onChange(index, { key: keyValue, value });
};
onRemovePress = () => {
const {
index,
onRemove
} = this.props;
onRemove(index);
};
onFocus = () => {
this.props.onFocus();
};
onBlur = () => {
this.props.onBlur();
};
//
// Render
render() {
const {
keyValue,
value,
keyPlaceholder,
valuePlaceholder,
isNew
} = this.props;
return (
<div className={styles.itemContainer}>
<div className={styles.inputWrapper}>
<TextInput
className={styles.keyInput}
name="key"
value={keyValue}
placeholder={keyPlaceholder}
onChange={this.onKeyChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</div>
<div className={styles.inputWrapper}>
<TextInput
className={styles.valueInput}
name="value"
value={value}
placeholder={valuePlaceholder}
onChange={this.onValueChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</div>
<div className={styles.buttonWrapper}>
{
isNew ?
null :
<IconButton
name={icons.REMOVE}
tabIndex={-1}
onPress={this.onRemovePress}
/>
}
</div>
</div>
);
}
}
KeyValueListInputItem.propTypes = {
index: PropTypes.number,
keyValue: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
keyPlaceholder: PropTypes.string.isRequired,
valuePlaceholder: PropTypes.string.isRequired,
isNew: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
onBlur: PropTypes.func.isRequired
};
KeyValueListInputItem.defaultProps = {
keyPlaceholder: 'Key',
valuePlaceholder: 'Value'
};
export default KeyValueListInputItem;

View file

@ -0,0 +1,89 @@
import React, { useCallback } from 'react';
import IconButton from 'Components/Link/IconButton';
import { icons } from 'Helpers/Props';
import TextInput from './TextInput';
import styles from './KeyValueListInputItem.css';
interface KeyValueListInputItemProps {
index: number;
keyValue: string;
value: string;
keyPlaceholder?: string;
valuePlaceholder?: string;
isNew: boolean;
onChange: (index: number, itemValue: { key: string; value: string }) => void;
onRemove: (index: number) => void;
onFocus: () => void;
onBlur: () => void;
}
function KeyValueListInputItem({
index,
keyValue,
value,
keyPlaceholder = 'Key',
valuePlaceholder = 'Value',
isNew,
onChange,
onRemove,
onFocus,
onBlur,
}: KeyValueListInputItemProps): JSX.Element {
const handleKeyChange = useCallback(
({ value: keyValue }: { value: string }) => {
onChange(index, { key: keyValue, value });
},
[index, value, onChange]
);
const handleValueChange = useCallback(
({ value }: { value: string }) => {
onChange(index, { key: keyValue, value });
},
[index, keyValue, onChange]
);
const handleRemovePress = useCallback(() => {
onRemove(index);
}, [index, onRemove]);
return (
<div className={styles.itemContainer}>
<div className={styles.keyInputWrapper}>
<TextInput
className={styles.keyInput}
name="key"
value={keyValue}
placeholder={keyPlaceholder}
onChange={handleKeyChange}
onFocus={onFocus}
onBlur={onBlur}
/>
</div>
<div className={styles.valueInputWrapper}>
<TextInput
className={styles.valueInput}
name="value"
value={value}
placeholder={valuePlaceholder}
onChange={handleValueChange}
onFocus={onFocus}
onBlur={onBlur}
/>
</div>
<div className={styles.buttonWrapper}>
{isNew ? null : (
<IconButton
name={icons.REMOVE}
tabIndex={-1}
onPress={handleRemovePress}
/>
)}
</div>
</div>
);
}
export default KeyValueListInputItem;

View file

@ -14,6 +14,8 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.CHECK;
case 'device':
return inputTypes.DEVICE;
case 'keyValueList':
return inputTypes.KEY_VALUE_LIST;
case 'playlist':
return inputTypes.PLAYLIST;
case 'password':

View file

@ -83,13 +83,6 @@
}
@media only screen and (max-width: $breakpointMedium) {
.modal.small,
.modal.medium {
width: 90%;
}
}
@media only screen and (max-width: $breakpointSmall) {
.modalContainer {
position: fixed;
}

View file

@ -172,7 +172,7 @@ class SignalRConnector extends Component {
const status = resource.status;
// Both successful and failed commands need to be
// completed, otherwise they spin until they timeout.
// completed, otherwise they spin until they time out.
if (status === 'completed' || status === 'failed') {
this.props.dispatchFinishCommand(resource);
@ -224,10 +224,58 @@ class SignalRConnector extends Component {
repopulatePage('trackFileUpdated');
};
handleDownloadclient = ({ action, resource }) => {
const section = 'settings.downloadClients';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleHealth = () => {
this.props.dispatchFetchHealth();
};
handleImportlist = ({ action, resource }) => {
const section = 'settings.importLists';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleIndexer = ({ action, resource }) => {
const section = 'settings.indexers';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleMetadata = ({ action, resource }) => {
const section = 'settings.metadata';
if (action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
}
};
handleNotification = ({ action, resource }) => {
const section = 'settings.notifications';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleArtist = (body) => {
const action = body.action;
const section = 'artist';

View file

@ -4,7 +4,7 @@
line-height: 1.52857143;
}
@media only screen and (max-width: $breakpointSmall) {
@media only screen and (max-width: $breakpointMedium) {
.cell {
white-space: nowrap;
}

View file

@ -7,7 +7,7 @@
white-space: nowrap;
}
@media only screen and (max-width: $breakpointSmall) {
@media only screen and (max-width: $breakpointMedium) {
.cell {
white-space: nowrap;
}

View file

@ -10,7 +10,7 @@
border-collapse: collapse;
}
@media only screen and (max-width: $breakpointSmall) {
@media only screen and (max-width: $breakpointMedium) {
.tableContainer {
min-width: 100%;
width: fit-content;

View file

@ -9,7 +9,7 @@
margin-left: 10px;
}
@media only screen and (max-width: $breakpointSmall) {
@media only screen and (max-width: $breakpointMedium) {
.headerCell {
white-space: nowrap;
}

View file

@ -60,7 +60,7 @@
height: 25px;
}
@media only screen and (max-width: $breakpointSmall) {
@media only screen and (max-width: $breakpointMedium) {
.pager {
flex-wrap: wrap;
}

View file

@ -9,7 +9,7 @@
margin-left: 10px;
}
@media only screen and (max-width: $breakpointSmall) {
@media only screen and (max-width: $breakpointMedium) {
.headerCell {
white-space: nowrap;
}

View file

@ -2,8 +2,8 @@ export const AUTO_COMPLETE = 'autoComplete';
export const CAPTCHA = 'captcha';
export const CHECK = 'check';
export const DEVICE = 'device';
export const PLAYLIST = 'playlist';
export const KEY_VALUE_LIST = 'keyValueList';
export const PLAYLIST = 'playlist';
export const MONITOR_ALBUMS_SELECT = 'monitorAlbumsSelect';
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
export const FLOAT = 'float';
@ -34,8 +34,8 @@ export const all = [
CAPTCHA,
CHECK,
DEVICE,
PLAYLIST,
KEY_VALUE_LIST,
PLAYLIST,
MONITOR_ALBUMS_SELECT,
MONITOR_NEW_ITEMS_SELECT,
FLOAT,

View file

@ -10,11 +10,11 @@ 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 Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteCustomFormats,
bulkEditCustomFormats,
@ -34,7 +34,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
typeof ManageCustomFormatsModalRow
>['onSelectedChange'];
const COLUMNS = [
const COLUMNS: Column[] = [
{
name: 'name',
label: () => translate('Name'),
@ -56,8 +56,6 @@ const COLUMNS = [
interface ManageCustomFormatsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageCustomFormatsModalContent(

View file

@ -10,11 +10,11 @@ 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 Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteDownloadClients,
bulkEditDownloadClients,
@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
typeof ManageDownloadClientsModalRow
>['onSelectedChange'];
const COLUMNS = [
const COLUMNS: Column[] = [
{
name: 'name',
label: () => translate('Name'),
@ -82,8 +82,6 @@ const COLUMNS = [
interface ManageDownloadClientsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageDownloadClientsModalContent(

View file

@ -10,11 +10,11 @@ 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 Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteIndexers,
bulkEditIndexers,
@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
typeof ManageIndexersModalRow
>['onSelectedChange'];
const COLUMNS = [
const COLUMNS: Column[] = [
{
name: 'name',
label: () => translate('Name'),
@ -82,8 +82,6 @@ const COLUMNS = [
interface ManageIndexersModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {

View file

@ -94,9 +94,9 @@ class RootFolder extends Component {
<ConfirmModal
isOpen={this.state.isDeleteRootFolderModalOpen}
kind={kinds.DANGER}
title={translate('DeleteRootFolder')}
message={translate('DeleteRootFolderMessageText', { name })}
confirmLabel={translate('Delete')}
title={translate('RemoveRootFolder')}
message={translate('RemoveRootFolderArtistsMessageText', { name })}
confirmLabel={translate('Remove')}
onConfirm={this.onConfirmDeleteRootFolder}
onCancel={this.onDeleteRootFolderModalClose}
/>

View file

@ -24,19 +24,19 @@
height: 20px;
}
.bar {
.track {
top: 9px;
margin: 0 5px;
height: 3px;
background-color: var(--sliderAccentColor);
box-shadow: 0 0 0 #000;
&:nth-child(3n+1) {
&:nth-child(3n + 1) {
background-color: #ddd;
}
}
.handle {
.thumb {
top: 1px;
z-index: 0 !important;
width: 18px;

View file

@ -1,8 +1,6 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'bar': string;
'handle': string;
'kilobitsPerSecond': string;
'quality': string;
'qualityDefinition': string;
@ -10,7 +8,9 @@ interface CssExports {
'sizeLimit': string;
'sizes': string;
'slider': string;
'thumb': string;
'title': string;
'track': string;
}
export const cssExports: CssExports;
export default cssExports;

View file

@ -55,6 +55,27 @@ class QualityDefinition extends Component {
};
}
//
// Control
trackRenderer(props, state) {
return (
<div
{...props}
className={styles.track}
/>
);
}
thumbRenderer(props, state) {
return (
<div
{...props}
className={styles.thumb}
/>
);
}
//
// Listeners
@ -174,6 +195,7 @@ class QualityDefinition extends Component {
<div className={styles.sizeLimit}>
<ReactSlider
className={styles.slider}
min={slider.min}
max={slider.max}
step={slider.step}
@ -182,9 +204,9 @@ class QualityDefinition extends Component {
withTracks={true}
allowCross={false}
snapDragDisabled={true}
className={styles.slider}
trackClassName={styles.bar}
thumbClassName={styles.handle}
pearling={true}
renderThumb={this.thumbRenderer}
renderTrack={this.trackRenderer}
onChange={this.onSliderChange}
onAfterChange={this.onAfterSliderChange}
/>

View file

@ -151,7 +151,7 @@ export const defaultState = {
{
name: 'genres',
label: () => translate('Genres'),
isSortable: false,
isSortable: true,
isVisible: false
},
{

View file

@ -150,7 +150,7 @@ export const defaultState = {
},
{
key: 'importFailed',
label: () => translate('ImportFailed'),
label: () => translate('ImportCompleteFailed'),
filters: [
{
key: 'eventType',

View file

@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@ -77,15 +77,16 @@ class LogFiles extends Component {
<PageContentBody>
<Alert>
<div>
Log files are located in: {location}
{translate('LogFilesLocation', {
location
})}
</div>
{
currentLogView === 'Log Files' &&
<div>
The log level defaults to 'Info' and can be changed in <Link to="/settings/general">General Settings</Link>
</div>
}
{currentLogView === 'Log Files' ? (
<div>
<InlineMarkdown data={translate('TheLogLevelDefault')} />
</div>
) : null}
</Alert>
{

View file

@ -270,7 +270,7 @@ function Updates() {
{generalSettingsError ? (
<Alert kind={kinds.DANGER}>
{translate('FailedToUpdateSettings')}
{translate('FailedToFetchSettings')}
</Alert>
) : null}

View file

@ -23,12 +23,13 @@ const error = console.error;
function logError(...parameters: any[]) {
const filter = parameters.find((parameter) => {
return (
parameter.includes(
typeof parameter === 'string' &&
(parameter.includes(
'Support for defaultProps will be removed from function components in a future major release'
) ||
parameter.includes(
'findDOMNode is deprecated and will be removed in the next major release'
)
parameter.includes(
'findDOMNode is deprecated and will be removed in the next major release'
))
);
});

View file

@ -1,3 +1,10 @@
export type InputChanged<T = unknown> = {
name: string;
value: T;
};
export type InputOnChange<T> = (change: InputChanged<T>) => void;
export type CheckInputChanged = {
name: string;
value: boolean;

View file

@ -109,7 +109,7 @@
"babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.39.0",
"core-js": "3.41.0",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
"eslint": "8.57.1",

View file

@ -33,7 +33,6 @@ namespace Lidarr.Api.V1.Config
SharedValidator.RuleFor(c => c.BindAddress)
.ValidIpAddress()
.NotListenAllIp4Address()
.When(c => c.BindAddress != "*" && c.BindAddress != "localhost");
SharedValidator.RuleFor(c => c.Port).ValidPort();

View file

@ -1,5 +1,7 @@
using FluentValidation;
using Lidarr.Http;
using NzbDrone.Core.Download;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.DownloadClient
{
@ -9,9 +11,10 @@ namespace Lidarr.Api.V1.DownloadClient
public static readonly DownloadClientResourceMapper ResourceMapper = new ();
public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new ();
public DownloadClientController(IDownloadClientFactory downloadClientFactory)
: base(downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
public DownloadClientController(IBroadcastSignalRMessage signalRBroadcaster, IDownloadClientFactory downloadClientFactory)
: base(signalRBroadcaster, downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
{
SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50);
}
}
}

View file

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Lidarr.Http.REST;
using NzbDrone.Common.Http;
using NzbDrone.Core.HealthCheck;
namespace Lidarr.Api.V1.Health
@ -11,7 +10,7 @@ namespace Lidarr.Api.V1.Health
public string Source { get; set; }
public HealthCheckResult Type { get; set; }
public string Message { get; set; }
public HttpUri WikiUrl { get; set; }
public string WikiUrl { get; set; }
}
public static class HealthResourceMapper
@ -29,7 +28,7 @@ namespace Lidarr.Api.V1.Health
Source = model.Source.Name,
Type = model.Type,
Message = model.Message,
WikiUrl = model.WikiUrl
WikiUrl = model.WikiUrl.FullUri
};
}

View file

@ -3,6 +3,7 @@ using Lidarr.Http;
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.ImportLists
{
@ -12,11 +13,12 @@ namespace Lidarr.Api.V1.ImportLists
public static readonly ImportListResourceMapper ResourceMapper = new ();
public static readonly ImportListBulkResourceMapper BulkResourceMapper = new ();
public ImportListController(IImportListFactory importListFactory,
RootFolderExistsValidator rootFolderExistsValidator,
QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper)
public ImportListController(IBroadcastSignalRMessage signalRBroadcaster,
IImportListFactory importListFactory,
RootFolderExistsValidator rootFolderExistsValidator,
QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(signalRBroadcaster, importListFactory, "importlist", ResourceMapper, BulkResourceMapper)
{
SharedValidator.RuleFor(c => c.RootFolderPath).Cascade(CascadeMode.Stop)
.IsValidPath()

View file

@ -1,6 +1,8 @@
using FluentValidation;
using Lidarr.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Validation;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.Indexers
{
@ -10,9 +12,12 @@ namespace Lidarr.Api.V1.Indexers
public static readonly IndexerResourceMapper ResourceMapper = new ();
public static readonly IndexerBulkResourceMapper BulkResourceMapper = new ();
public IndexerController(IndexerFactory indexerFactory, DownloadClientExistsValidator downloadClientExistsValidator)
: base(indexerFactory, "indexer", ResourceMapper, BulkResourceMapper)
public IndexerController(IBroadcastSignalRMessage signalRBroadcaster,
IndexerFactory indexerFactory,
DownloadClientExistsValidator downloadClientExistsValidator)
: base(signalRBroadcaster, indexerFactory, "indexer", ResourceMapper, BulkResourceMapper)
{
SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50);
SharedValidator.RuleFor(c => c.DownloadClientId).SetValidator(downloadClientExistsValidator);
}
}

View file

@ -13,7 +13,7 @@
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="Ical.Net" Version="4.3.1" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
</ItemGroup>
</Project>

View file

@ -2,6 +2,7 @@ using System;
using Lidarr.Http;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Extras.Metadata;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.Metadata
{
@ -11,8 +12,8 @@ namespace Lidarr.Api.V1.Metadata
public static readonly MetadataResourceMapper ResourceMapper = new ();
public static readonly MetadataBulkResourceMapper BulkResourceMapper = new ();
public MetadataController(IMetadataFactory metadataFactory)
: base(metadataFactory, "metadata", ResourceMapper, BulkResourceMapper)
public MetadataController(IBroadcastSignalRMessage signalRBroadcaster, IMetadataFactory metadataFactory)
: base(signalRBroadcaster, metadataFactory, "metadata", ResourceMapper, BulkResourceMapper)
{
}

View file

@ -2,6 +2,7 @@ using System;
using Lidarr.Http;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Notifications;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.Notifications
{
@ -11,8 +12,8 @@ namespace Lidarr.Api.V1.Notifications
public static readonly NotificationResourceMapper ResourceMapper = new ();
public static readonly NotificationBulkResourceMapper BulkResourceMapper = new ();
public NotificationController(NotificationFactory notificationFactory)
: base(notificationFactory, "notification", ResourceMapper, BulkResourceMapper)
public NotificationController(IBroadcastSignalRMessage signalRBroadcaster, NotificationFactory notificationFactory)
: base(signalRBroadcaster, notificationFactory, "notification", ResourceMapper, BulkResourceMapper)
{
}

View file

@ -7,12 +7,19 @@ using Lidarr.Http.REST.Attributes;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.ThingiProvider.Events;
using NzbDrone.Core.Validation;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1
{
public abstract class ProviderControllerBase<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestController<TProviderResource>
public abstract class ProviderControllerBase<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestControllerWithSignalR<TProviderResource, TProviderDefinition>,
IHandle<ProviderAddedEvent<TProvider>>,
IHandle<ProviderUpdatedEvent<TProvider>>,
IHandle<ProviderDeletedEvent<TProvider>>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
where TProviderResource : ProviderResource<TProviderResource>, new()
@ -22,11 +29,13 @@ namespace Lidarr.Api.V1
private readonly ProviderResourceMapper<TProviderResource, TProviderDefinition> _resourceMapper;
private readonly ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> _bulkResourceMapper;
protected ProviderControllerBase(IProviderFactory<TProvider,
protected ProviderControllerBase(IBroadcastSignalRMessage signalRBroadcaster,
IProviderFactory<TProvider,
TProviderDefinition> providerFactory,
string resource,
ProviderResourceMapper<TProviderResource, TProviderDefinition> resourceMapper,
ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> bulkResourceMapper)
: base(signalRBroadcaster)
{
_providerFactory = providerFactory;
_resourceMapper = resourceMapper;
@ -261,6 +270,24 @@ namespace Lidarr.Api.V1
return Content(data.ToJson(), "application/json");
}
[NonAction]
public virtual void Handle(ProviderAddedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Created, message.Definition.Id);
}
[NonAction]
public virtual void Handle(ProviderUpdatedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Updated, message.Definition.Id);
}
[NonAction]
public virtual void Handle(ProviderDeletedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Deleted, message.ProviderId);
}
protected virtual void Validate(TProviderDefinition definition, bool includeWarnings)
{
var validationResult = definition.Settings.Validate();

View file

@ -302,7 +302,7 @@ namespace Lidarr.Api.V1.Queue
if (blocklist)
{
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload);
_failedDownloadService.MarkAsFailed(trackedDownload, skipRedownload);
}
if (!removeFromClient && !blocklist && !changeCategory)

View file

@ -4,6 +4,7 @@ using Lidarr.Http;
using Lidarr.Http.REST;
using Lidarr.Http.REST.Attributes;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation.Paths;
@ -21,17 +22,28 @@ namespace Lidarr.Api.V1.RemotePathMappings
_remotePathMappingService = remotePathMappingService;
SharedValidator.RuleFor(c => c.Host)
.NotEmpty();
.NotEmpty();
// We cannot use IsValidPath here, because it's a remote path, possibly other OS.
SharedValidator.RuleFor(c => c.RemotePath)
.NotEmpty();
.NotEmpty();
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.StartsWith(" "))
.WithMessage("Remote Path '{PropertyValue}' must not start with a space");
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.EndsWith(" "))
.WithMessage("Remote Path '{PropertyValue}' must not end with a space");
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.Stop)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
.Cascade(CascadeMode.Stop)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator)
.SetValidator(new SystemFolderValidator())
.NotEqual("/")
.WithMessage("Cannot be set to '/'");
}
public override RemotePathMappingResource GetResourceById(int id)
@ -41,7 +53,7 @@ namespace Lidarr.Api.V1.RemotePathMappings
[RestPostById]
[Consumes("application/json")]
public ActionResult<RemotePathMappingResource> CreateMapping(RemotePathMappingResource resource)
public ActionResult<RemotePathMappingResource> CreateMapping([FromBody] RemotePathMappingResource resource)
{
var model = resource.ToModel();
@ -62,7 +74,7 @@ namespace Lidarr.Api.V1.RemotePathMappings
}
[RestPutById]
public ActionResult<RemotePathMappingResource> UpdateMapping(RemotePathMappingResource resource)
public ActionResult<RemotePathMappingResource> UpdateMapping([FromBody] RemotePathMappingResource resource)
{
var mapping = resource.ToModel();

View file

@ -92,7 +92,7 @@ namespace Lidarr.Api.V1.System.Backup
}
[HttpPost("restore/upload")]
[RequestFormLimits(MultipartBodyLengthLimit = 1000000000)]
[RequestFormLimits(MultipartBodyLengthLimit = 5000000000)]
public object UploadAndRestore()
{
var files = Request.Form.Files;

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using FluentValidation;
using Lidarr.Http;
using Lidarr.Http.REST;
using Lidarr.Http.REST.Attributes;
@ -23,6 +24,8 @@ namespace Lidarr.Api.V1.Tags
: base(signalRBroadcaster)
{
_tagService = tagService;
SharedValidator.RuleFor(c => c.Label).NotEmpty();
}
public override TagResource GetResourceById(int id)

View file

@ -9808,7 +9808,8 @@
"nullable": true
},
"wikiUrl": {
"$ref": "#/components/schemas/HttpUri"
"type": "string",
"nullable": true
}
},
"additionalProperties": false
@ -10062,48 +10063,6 @@
},
"additionalProperties": false
},
"HttpUri": {
"type": "object",
"properties": {
"fullUri": {
"type": "string",
"nullable": true,
"readOnly": true
},
"scheme": {
"type": "string",
"nullable": true,
"readOnly": true
},
"host": {
"type": "string",
"nullable": true,
"readOnly": true
},
"port": {
"type": "integer",
"format": "int32",
"nullable": true,
"readOnly": true
},
"path": {
"type": "string",
"nullable": true,
"readOnly": true
},
"query": {
"type": "string",
"nullable": true,
"readOnly": true
},
"fragment": {
"type": "string",
"nullable": true,
"readOnly": true
}
},
"additionalProperties": false
},
"ImportListBulkResource": {
"type": "object",
"properties": {
@ -12922,6 +12881,7 @@
"downloading",
"downloadFailed",
"downloadFailedPending",
"importBlocked",
"importPending",
"importing",
"importFailed",

View file

@ -1,9 +1,14 @@
using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
@ -16,11 +21,15 @@ namespace Lidarr.Http.Authentication
{
private readonly IAuthenticationService _authService;
private readonly IConfigFileProvider _configFileProvider;
private readonly IAppFolderInfo _appFolderInfo;
private readonly Logger _logger;
public AuthenticationController(IAuthenticationService authService, IConfigFileProvider configFileProvider)
public AuthenticationController(IAuthenticationService authService, IConfigFileProvider configFileProvider, IAppFolderInfo appFolderInfo, Logger logger)
{
_authService = authService;
_configFileProvider = configFileProvider;
_appFolderInfo = appFolderInfo;
_logger = logger;
}
[HttpPost("login")]
@ -45,7 +54,23 @@ namespace Lidarr.Http.Authentication
IsPersistent = resource.RememberMe == "on"
};
await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties);
try
{
await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties);
}
catch (CryptographicException e)
{
if (e.InnerException is XmlException)
{
_logger.Error(e, "Failed to authenticate user due to corrupt XML. Please remove all XML files from {0} and restart Lidarr", Path.Combine(_appFolderInfo.AppDataFolder, "asp"));
}
else
{
_logger.Error(e, "Failed to authenticate user. {0}", e.Message);
}
return Unauthorized();
}
if (returnUrl.IsNullOrWhiteSpace() || !Url.IsLocalUrl(returnUrl))
{

View file

@ -77,7 +77,7 @@ namespace Lidarr.Http.Authentication
private void LogSuccess(HttpRequest context, string username)
{
_authLogger.Info("Auth-Success ip {0} username '{1}'", context.GetRemoteIP(), username);
_authLogger.Debug("Auth-Success ip {0} username '{1}'", context.GetRemoteIP(), username);
}
private void LogLogout(HttpRequest context, string username)

View file

@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="ImpromptuInterface" Version="7.0.1" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog" Version="5.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Lidarr.Core.csproj" />

View file

@ -40,15 +40,16 @@ namespace NzbDrone.Automation.Test
var service = ChromeDriverService.CreateDefaultService();
// Timeout as windows automation tests seem to take alot longer to get going
driver = new ChromeDriver(service, options, new TimeSpan(0, 3, 0));
driver = new ChromeDriver(service, options, TimeSpan.FromMinutes(3));
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
driver.Manage().Window.FullScreen();
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll();
_runner.Start(true);
driver.Url = "http://localhost:8686";
driver.Navigate().GoToUrl("http://localhost:8686");
var page = new PageBase(driver);
page.WaitForNoSpinner();
@ -68,7 +69,7 @@ namespace NzbDrone.Automation.Test
{
try
{
var image = ((ITakesScreenshot)driver).GetScreenshot();
var image = (driver as ITakesScreenshot).GetScreenshot();
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
}
catch (Exception ex)

View file

@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="111.0.5563.6400" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="134.0.6998.16500" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Lidarr.Test.Common.csproj" />

View file

@ -1,19 +1,17 @@
using System;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.UI;
namespace NzbDrone.Automation.Test.PageModel
{
public class PageBase
{
private readonly RemoteWebDriver _driver;
private readonly IWebDriver _driver;
public PageBase(RemoteWebDriver driver)
public PageBase(IWebDriver driver)
{
_driver = driver;
driver.Manage().Window.Maximize();
}
public IWebElement FindByClass(string className, int timeout = 5)

View file

@ -42,17 +42,18 @@ namespace NzbDrone.Common
public void CreateZip(string path, IEnumerable<string> files)
{
using (var zipFile = ZipFile.Create(path))
_logger.Debug("Creating archive {0}", path);
using var zipFile = ZipFile.Create(path);
zipFile.BeginUpdate();
foreach (var file in files)
{
zipFile.BeginUpdate();
foreach (var file in files)
{
zipFile.Add(file, Path.GetFileName(file));
}
zipFile.CommitUpdate();
zipFile.Add(file, Path.GetFileName(file));
}
zipFile.CommitUpdate();
}
private void ExtractZip(string compressedFile, string destination)

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
@ -306,9 +307,26 @@ namespace NzbDrone.Common.Disk
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var files = GetFiles(path, recursive);
var files = GetFiles(path, recursive).ToList();
files.ToList().ForEach(RemoveReadOnly);
files.ForEach(RemoveReadOnly);
var attempts = 0;
while (attempts < 3 && files.Any())
{
EmptyFolder(path);
if (GetFiles(path, recursive).Any())
{
// Wait for IO operations to complete after emptying the folder since they aren't always
// instantly removed and it can lead to false positives that files are still present.
Thread.Sleep(3000);
}
attempts++;
files = GetFiles(path, recursive).ToList();
}
_fileSystem.Directory.Delete(path, recursive);
}

View file

@ -21,7 +21,7 @@ namespace NzbDrone.Common.Disk
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
private static readonly string[] _reflinkFilesystems = { "btrfs", "xfs" };
private static readonly string[] ReflinkFilesystems = { "btrfs", "xfs", "zfs" };
public DiskTransferService(IDiskProvider diskProvider, Logger logger)
{
@ -343,7 +343,7 @@ namespace NzbDrone.Common.Disk
var targetDriveFormat = targetMount?.DriveFormat ?? string.Empty;
var isCifs = targetDriveFormat == "cifs";
var tryReflink = sourceDriveFormat == targetDriveFormat && _reflinkFilesystems.Contains(sourceDriveFormat);
var tryReflink = sourceDriveFormat == targetDriveFormat && ReflinkFilesystems.Contains(sourceDriveFormat);
if (mode.HasFlag(TransferMode.Copy))
{

View file

@ -17,37 +17,6 @@ namespace NzbDrone.Common.Disk
private readonly IDiskProvider _diskProvider;
private readonly IRuntimeInfo _runtimeInfo;
private readonly HashSet<string> _setToRemove = new HashSet<string>
{
// Windows
"boot",
"bootmgr",
"cache",
"msocache",
"recovery",
"$recycle.bin",
"recycler",
"system volume information",
"temporary internet files",
"windows",
// OS X
".fseventd",
".spotlight",
".trashes",
".vol",
"cachedmessages",
"caches",
"trash",
// QNAP
".@__thumb",
// Synology
"@eadir",
"#recycle"
};
public FileSystemLookupService(IDiskProvider diskProvider, IRuntimeInfo runtimeInfo)
{
_diskProvider = diskProvider;
@ -158,7 +127,7 @@ namespace NzbDrone.Common.Disk
})
.ToList();
directories.RemoveAll(d => _setToRemove.Contains(d.Name.ToLowerInvariant()));
directories.RemoveAll(d => SpecialFolders.IsSpecialFolder(d.Name));
return directories;
}

View file

@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace NzbDrone.Common.Disk;
public static class SpecialFolders
{
private static readonly HashSet<string> _specialFolders = new HashSet<string>
{
// Windows
"boot",
"bootmgr",
"cache",
"msocache",
"recovery",
"$recycle.bin",
"recycler",
"system volume information",
"temporary internet files",
"windows",
// OS X
".fseventd",
".spotlight",
".trashes",
".vol",
"cachedmessages",
"caches",
"trash",
// QNAP
".@__thumb",
// Synology
"@eadir",
"#recycle"
};
public static bool IsSpecialFolder(string folder)
{
if (folder == null)
{
return false;
}
return _specialFolders.Contains(folder.ToLowerInvariant());
}
}

View file

@ -141,7 +141,7 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null);
throw new WebException("Http request timed out", ex, WebExceptionStatus.Timeout, null);
}
}

View file

@ -0,0 +1,21 @@
using System.Text;
using NLog;
using NLog.Layouts.ClefJsonLayout;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Instrumentation;
public class CleansingClefLogLayout : CompactJsonLayout
{
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
base.RenderFormattedMessage(logEvent, target);
if (RuntimeInfo.IsProduction)
{
var result = CleanseLogMessage.Cleanse(target.ToString());
target.Clear();
target.Append(result);
}
}
}

View file

@ -0,0 +1,26 @@
using System.Text;
using NLog;
using NLog.Layouts;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Instrumentation;
public class CleansingConsoleLogLayout : SimpleLayout
{
public CleansingConsoleLogLayout(string format)
: base(format)
{
}
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
base.RenderFormattedMessage(logEvent, target);
if (RuntimeInfo.IsProduction)
{
var result = CleanseLogMessage.Cleanse(target.ToString());
target.Clear();
target.Append(result);
}
}
}

View file

@ -4,7 +4,7 @@ using NLog.Targets;
namespace NzbDrone.Common.Instrumentation
{
public class NzbDroneFileTarget : FileTarget
public class CleansingFileTarget : FileTarget
{
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{

View file

@ -3,7 +3,6 @@ using System.Diagnostics;
using System.IO;
using NLog;
using NLog.Config;
using NLog.Layouts.ClefJsonLayout;
using NLog.Targets;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@ -13,9 +12,11 @@ namespace NzbDrone.Common.Instrumentation
{
public static class NzbDroneLogger
{
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
public const string ConsoleLogLayout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
public static CompactJsonLayout ClefLogLayout = new CompactJsonLayout();
private const string FileLogLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private const string ConsoleFormat = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private static readonly CleansingConsoleLogLayout CleansingConsoleLayout = new (ConsoleFormat);
private static readonly CleansingClefLogLayout ClefLogLayout = new ();
private static bool _isConfigured;
@ -118,11 +119,7 @@ namespace NzbDrone.Common.Instrumentation
? formatEnumValue
: ConsoleLogFormat.Standard;
coloredConsoleTarget.Layout = logFormat switch
{
ConsoleLogFormat.Clef => ClefLogLayout,
_ => ConsoleLogLayout
};
ConfigureConsoleLayout(coloredConsoleTarget, logFormat);
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
@ -139,7 +136,7 @@ namespace NzbDrone.Common.Instrumentation
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
{
var fileTarget = new NzbDroneFileTarget();
var fileTarget = new CleansingFileTarget();
fileTarget.Name = name;
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName);
@ -152,7 +149,7 @@ namespace NzbDrone.Common.Instrumentation
fileTarget.MaxArchiveFiles = maxArchiveFiles;
fileTarget.EnableFileDelete = true;
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
fileTarget.Layout = FILE_LOG_LAYOUT;
fileTarget.Layout = FileLogLayout;
var loggingRule = new LoggingRule("*", minLogLevel, fileTarget);
@ -171,7 +168,7 @@ namespace NzbDrone.Common.Instrumentation
fileTarget.ConcurrentWrites = false;
fileTarget.ConcurrentWriteAttemptDelay = 50;
fileTarget.ConcurrentWriteAttempts = 100;
fileTarget.Layout = FILE_LOG_LAYOUT;
fileTarget.Layout = FileLogLayout;
var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
@ -216,6 +213,15 @@ namespace NzbDrone.Common.Instrumentation
{
return GetLogger(obj.GetType());
}
public static void ConfigureConsoleLayout(ColoredConsoleTarget target, ConsoleLogFormat format)
{
target.Layout = format switch
{
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
_ => NzbDroneLogger.CleansingConsoleLayout
};
}
}
public enum ConsoleLogFormat

View file

@ -6,17 +6,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="IPAddressRange" Version="6.1.0" />
<PackageReference Include="IPAddressRange" Version="6.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.2" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
<PackageReference Include="System.Text.Json" Version="6.0.10" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@ -6,7 +7,7 @@ namespace NzbDrone.Common
{
public class PathEqualityComparer : IEqualityComparer<string>
{
public static readonly PathEqualityComparer Instance = new PathEqualityComparer();
public static readonly PathEqualityComparer Instance = new ();
private PathEqualityComparer()
{
@ -19,12 +20,19 @@ namespace NzbDrone.Common
public int GetHashCode(string obj)
{
if (OsInfo.IsWindows)
try
{
return obj.CleanFilePath().Normalize().ToLower().GetHashCode();
}
if (OsInfo.IsWindows)
{
return obj.CleanFilePath().Normalize().ToLower().GetHashCode();
}
return obj.CleanFilePath().Normalize().GetHashCode();
return obj.CleanFilePath().Normalize().GetHashCode();
}
catch (ArgumentException ex)
{
throw new ArgumentException($"Invalid path: {obj}", ex);
}
}
}
}

View file

@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Model;
@ -117,7 +118,9 @@ namespace NzbDrone.Common.Processes
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true
RedirectStandardInput = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
if (environmentVariables != null)

View file

@ -34,7 +34,8 @@ namespace NzbDrone.Common.Reflection
|| type == typeof(string)
|| type == typeof(DateTime)
|| type == typeof(Version)
|| type == typeof(decimal);
|| type == typeof(decimal)
|| (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>));
}
public static bool IsReadable(this PropertyInfo propertyInfo)

View file

@ -103,6 +103,7 @@ namespace NzbDrone.Core.Test.DiskSpace
[TestCase("/var/lib/docker")]
[TestCase("/some/place/docker/aufs")]
[TestCase("/etc/network")]
[TestCase("/Volumes/.timemachine/ABC123456-A1BC-12A3B45678C9/2025-05-13-181401.backup")]
public void should_not_check_diskspace_for_irrelevant_mounts(string path)
{
var mount = new Mock<IMount>();

View file

@ -183,6 +183,8 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
{
GivenArtistMatch();
var tracks = Builder<Track>.CreateListOfSize(3).BuildList();
_trackedDownload.RemoteAlbum.Albums = new List<Album>
{
CreateAlbum(1, 3)
@ -192,9 +194,9 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { tracks[0] } })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { tracks[1] } })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { tracks[2] } })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
});
@ -290,6 +292,9 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
[Test]
public void should_mark_as_imported_if_all_tracks_were_imported()
{
var track1 = new Track { Id = 1 };
var track2 = new Track { Id = 2 };
_trackedDownload.RemoteAlbum.Albums = new List<Album>
{
CreateAlbum(1, 2)
@ -301,11 +306,11 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
{
new ImportResult(
new ImportDecision<LocalTrack>(
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { track1 } })),
new ImportResult(
new ImportDecision<LocalTrack>(
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }))
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic(), Tracks = new List<Track> { track2 } }))
});
Subject.Import(_trackedDownload);
@ -367,11 +372,13 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
{
GivenABadlyNamedDownload();
var track1 = new Track { Id = 1 };
Mocker.GetMock<IDownloadedTracksImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { track1 } }))
});
Mocker.GetMock<IArtistService>()

View file

@ -99,5 +99,22 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
VerifySingleItem(DownloadItemStatus.Completed);
}
[TestCase("@eaDir")]
[TestCase(".@__thumb")]
public void GetItems_should_not_include_special_subfolders(string folderName)
{
GivenCompletedItem();
var targetDir = Path.Combine(_completedDownloadFolder, folderName);
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetDirectories(_completedDownloadFolder))
.Returns(new[] { targetDir });
var items = Subject.GetItems(_completedDownloadFolder, TimeSpan.FromMilliseconds(50)).ToList();
items.Count.Should().Be(0);
}
}
}

View file

@ -710,6 +710,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeTrue();
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_after_rounding_and_paused(string state)
{
GivenGlobalSeedLimits(1.0f);
GivenCompletedTorrent(state, ratio: 1.1006066990976857f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_just_under_max_ratio_reached_after_rounding_and_paused(string state)
{
GivenGlobalSeedLimits(1.0f);
GivenCompletedTorrent(state, ratio: 0.9999f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
@ -722,6 +746,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeTrue();
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_after_rounding_and_paused(string state)
{
GivenGlobalSeedLimits(2.0f);
GivenCompletedTorrent(state, ratio: 1.1006066990976857f, ratioLimit: 1.1f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_be_removable_and_should_allow_move_files_if_just_under_overridden_max_ratio_reached_after_rounding_and_paused(string state)
{
GivenGlobalSeedLimits(2.0f);
GivenCompletedTorrent(state, ratio: 0.9999f, ratioLimit: 1.0f);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[TestCase("pausedUP")]
[TestCase("stoppedUP")]
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)

View file

@ -76,6 +76,19 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Subject.Check().ShouldBeWarning(wikiFragment: "downloads-in-root-folder");
}
[Test]
public void should_return_warning_if_downloading_inside_root_folder()
{
var rootFolderPath = "c:\\Test".AsOsAgnostic();
var downloadRootPath = "c:\\Test\\Downloads".AsOsAgnostic();
GivenRootFolder(rootFolderPath);
_clientStatus.OutputRootFolders = new List<OsPath> { new (downloadRootPath) };
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_ok_if_not_downloading_to_root_folder()
{
@ -87,7 +100,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
}
[Test]
[TestCaseSource("DownloadClientExceptions")]
[TestCaseSource(nameof(DownloadClientExceptions))]
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
{
_downloadClient.Setup(s => s.GetStatus())

View file

@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.ImportListTests
}
[Test]
[Ignore("Pending mapping fixes", Until = "2025-04-20 00:00:00Z")]
[Ignore("Pending mapping fixes", Until = "2025-10-20 00:00:00Z")]
public void map_artist_should_work()
{
UseRealHttp();
@ -159,7 +159,7 @@ namespace NzbDrone.Core.Test.ImportListTests
}
[Test]
[Ignore("Pending mapping fixes", Until = "2025-04-20 00:00:00Z")]
[Ignore("Pending mapping fixes", Until = "2025-10-20 00:00:00Z")]
public void map_album_should_work()
{
UseRealHttp();

View file

@ -14,6 +14,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-09-01 00:00:00Z")]
public class SkyHookProxyFixture : CoreTest<SkyHookProxy>
{
private MetadataProfile _metadataProfile;

View file

@ -12,6 +12,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-09-01 00:00:00Z")]
public class SkyHookProxySearchFixture : CoreTest<SkyHookProxy>
{
[SetUp]

View file

@ -64,7 +64,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
}
[TestCase("Florence + the Machine", "Florence + the Machine")]
[TestCase("Beyoncé X10", "Beyoncé X10")]
[TestCase("Beyoncé X10", "Beyonce X10")]
[TestCase("Girlfriends' Guide to Divorce", "Girlfriends Guide to Divorce")]
[TestCase("Rule #23: Never Lie to the Kids", "Rule #23 Never Lie to the Kids")]
[TestCase("Anne Hathaway/Florence + The Machine", "Anne Hathaway Florence + The Machine")]
@ -81,6 +81,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("[a] title", "a title")]
[TestCase("backslash \\ backlash", "backslash backlash")]
[TestCase("I'm the Boss", "Im the Boss")]
[TestCase("Joker: Folie à deux", "Joker Folie a deux")]
public void should_get_expected_title_back(string name, string expected)
{
_artist.Name = name;

View file

@ -143,7 +143,7 @@ namespace NzbDrone.Core.Test.ParserTests
// [TestCase("ADELE 25 CD FLAC 2015 PERFECT", "Adele", "25")]
[TestCase("A.I. - Sex & Robots [2007/MP3/V0(VBR)]", "A I", "Sex & Robots")]
[TestCase("Jay-Z - 4:44 (Deluxe Edition) (2017) 320", "Jay-Z", "444")]
[TestCase("Jay-Z - 4:44 (Deluxe Edition) (2017) 320", "Jay-Z", "4:44")]
// [TestCase("Roberta Flack 2006 - The Very Best of", "Roberta Flack", "The Very Best of")]
[TestCase("VA - NOW Thats What I Call Music 96 (2017) [Mp3~Kbps]", "VA", "NOW Thats What I Call Music 96")]
@ -175,6 +175,9 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("(Heavy Metal) Aria - Discography(46 CD) [1985 - 2015], FLAC(image + .cue), lossless", "Aria", "Discography", true)]
[TestCase("(Heavy Metal) [CD] Forces United - Discography(6 CDs), 2014-2016, FLAC(image + .cue), lossless", "Forces United", "Discography", true)]
[TestCase("Gorillaz - The now now - 2018 [FLAC]", "Gorillaz", "The now now")]
[TestCase("Bone Thugs-n-Harmony - UNI5: The World's Enemy (2010) [Album] [FLAC Lossless / CD / Log (100%) / Cue]", "Bone Thugs-n-Harmony", "UNI5: The World's Enemy")]
[TestCase("Guru - Jazzmatazz, Volume 3: Streetsoul (2000) [Album] [FLAC Lossless / CD / Log (100%) / Cue]", "Guru", "Jazzmatazz, Volume 3: Streetsoul")]
[TestCase("Bad Movie Cast - Bad: The Soundtrack (2024) [FLAC (M4A) Lossless] [WEB]", "Bad Movie Cast", "Bad: The Soundtrack")]
// Regex Works on below, but ParseAlbumMatchCollection cleans the "..." and converts it to spaces
// [TestCase("Metallica - ...And Justice for All (1988) [FLAC Lossless]", "Metallica", "...And Justice for All")]

View file

@ -129,6 +129,9 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Green Day - Father Of All [FLAC (M4A) 24-bit Lossless]", null, 0, 0)]
[TestCase("Green_Day-Father_Of_All_FLAC_M4A_24_bit_Lossless", null, 0, 0)]
[TestCase("Green.Day-Father.Of.All.FLAC.M4A.24.bit.Lossless", null, 0, 0)]
[TestCase("Linkin Park - Studio Collection 2000-2012 (2013) [WEB FLAC24-44.1]", null, 0, 0)]
[TestCase("Linkin Park - Studio Collection 2000-2012 (2013) [WEB FLAC24bit]", null, 0, 0)]
[TestCase("Linkin Park - Studio Collection 2000-2012 (2013) [WEB FLAC24-bit]", null, 0, 0)]
public void should_parse_flac_24bit_quality(string title, string desc, int bitrate, int sampleSize)
{
ParseAndVerifyQuality(title, desc, bitrate, Quality.FLAC_24, sampleSize);

View file

@ -87,7 +87,8 @@ namespace NzbDrone.Core.Annotations
RootFolder,
QualityProfile,
MetadataProfile,
ArtistTag
ArtistTag,
KeyValueList,
}
public enum HiddenType

View file

@ -66,12 +66,19 @@ namespace NzbDrone.Core.Backup
{
_logger.ProgressInfo("Starting Backup");
var backupFolder = GetBackupFolder(backupType);
_diskProvider.EnsureFolder(_backupTempFolder);
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
_diskProvider.EnsureFolder(backupFolder);
if (!_diskProvider.FolderWritable(backupFolder))
{
throw new UnauthorizedAccessException($"Backup folder {backupFolder} is not writable");
}
var dateNow = DateTime.Now;
var backupFilename = $"lidarr_backup_v{BuildInfo.Version}_{dateNow:yyyy.MM.dd_HH.mm.ss}.zip";
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
var backupPath = Path.Combine(backupFolder, backupFilename);
Cleanup();

View file

@ -185,7 +185,9 @@ namespace NzbDrone.Core.Blocklisting
Indexer = message.Data.GetValueOrDefault("indexer"),
Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")),
Message = message.Message,
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash")
TorrentInfoHash = message.TrackedDownload?.Protocol == DownloadProtocol.Torrent
? message.TrackedDownload.DownloadItem.DownloadId
: message.Data.GetValueOrDefault("torrentInfoHash", null)
};
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))

View file

@ -263,7 +263,21 @@ namespace NzbDrone.Core.Configuration
}
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName => _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
public string InstanceName
{
get
{
var instanceName = _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
if (instanceName.Contains(BuildInfo.AppName, StringComparison.OrdinalIgnoreCase))
{
return instanceName;
}
return BuildInfo.AppName;
}
}
public bool UpdateAutomatically => _updateOptions.Automatically ?? GetValueBoolean("UpdateAutomatically", OsInfo.IsWindows, false);

View file

@ -11,6 +11,7 @@ namespace NzbDrone.Core.CustomFormats
{
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
RuleFor(c => c.Max).LessThanOrEqualTo(double.MaxValue);
}
}

View file

@ -252,7 +252,7 @@ namespace NzbDrone.Core.Datastore
protected void Delete(SqlBuilder builder)
{
var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery();
var sql = builder.AddDeleteTemplate(typeof(TModel));
using (var conn = _database.OpenConnection())
{

View file

@ -163,7 +163,7 @@ namespace NzbDrone.Core.DecisionEngine
return 10;
}
return 1;
return Math.Round(Math.Log10(age)) * -1;
});
}

View file

@ -20,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger = logger;
}
public SpecificationPriority Priority => SpecificationPriority.Default;
public SpecificationPriority Priority => SpecificationPriority.Disk;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)

View file

@ -21,7 +21,7 @@ namespace NzbDrone.Core.DiskSpace
private readonly IRootFolderService _rootFolderService;
private readonly Logger _logger;
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
private static readonly Regex _regexSpecialDrive = new Regex(@"^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)|/\.timemachine", RegexOptions.Compiled);
public DiskSpaceService(IDiskProvider diskProvider,
IRootFolderService rootFolderService,
@ -38,7 +38,10 @@ namespace NzbDrone.Core.DiskSpace
var optionalRootFolders = GetFixedDisksRootPaths().Except(importantRootFolders).Distinct().ToList();
var diskSpace = GetDiskSpace(importantRootFolders).Concat(GetDiskSpace(optionalRootFolders, true)).ToList();
var diskSpace = GetDiskSpace(importantRootFolders)
.Concat(GetDiskSpace(optionalRootFolders, true))
.OrderBy(d => d.Path, StringComparer.OrdinalIgnoreCase)
.ToList();
return diskSpace;
}
@ -54,7 +57,7 @@ namespace NzbDrone.Core.DiskSpace
private IEnumerable<string> GetFixedDisksRootPaths()
{
return _diskProvider.GetMounts()
.Where(d => d.DriveType == DriveType.Fixed)
.Where(d => d.DriveType is DriveType.Fixed or DriveType.Network)
.Where(d => !_regexSpecialDrive.IsMatch(d.RootDirectory))
.Select(d => d.RootDirectory);
}

View file

@ -25,6 +25,11 @@ namespace NzbDrone.Core.Download.Aggregation
public RemoteAlbum Augment(RemoteAlbum remoteAlbum)
{
if (remoteAlbum == null)
{
return null;
}
foreach (var augmenter in _augmenters)
{
try

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