Compare commits

...

114 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
179 changed files with 2562 additions and 758 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.5'
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:

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

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

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

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

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

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

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

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

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

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

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

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

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

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Aria2
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -30,9 +31,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_scanWatchFolder = scanWatchFolder;

View file

@ -7,6 +7,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -25,8 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
{
_scanWatchFolder = scanWatchFolder;

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -27,9 +28,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -11,6 +11,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -37,9 +38,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Flood.Models;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Flood
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
_downloadSeedConfigProvider = downloadSeedConfigProvider;

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -8,6 +8,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Hadouken.Models;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -25,9 +26,10 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -8,6 +8,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@ -24,8 +25,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
{
_proxy = proxy;
}

View file

@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@ -28,8 +29,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
{
_proxy = proxy;
}

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -23,8 +24,9 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
Logger logger)
: base(configService, diskProvider, remotePathMappingService, logger)
: base(configService, diskProvider, remotePathMappingService, localizationService, logger)
{
_httpClient = httpClient;
}

View file

@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -35,9 +36,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ICacheManager cacheManager,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxySelector = proxySelector;

View file

@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@ -26,8 +27,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
{
_proxy = proxy;
}

View file

@ -8,6 +8,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.RemotePathMappings;
@ -24,9 +25,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
}

View file

@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -28,9 +29,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
}
@ -101,7 +103,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
if (!torrent.ErrorString.IsNullOrWhiteSpace())
{
item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString;
item.Message = _localizationService.GetLocalizedString("DownloadClientItemErrorMessage", new Dictionary<string, object>
{
{ "clientName", Name },
{ "message", torrent.ErrorString }
});
}
else if (torrent.TotalSize == 0)
{

View file

@ -5,6 +5,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.RemotePathMappings;
@ -23,9 +24,10 @@ namespace NzbDrone.Core.Download.Clients.Vuze
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
}

View file

@ -12,6 +12,7 @@ using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.rTorrent;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -35,9 +36,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
IRemotePathMappingService remotePathMappingService,
IDownloadSeedConfigProvider downloadSeedConfigProvider,
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;

View file

@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -29,9 +30,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
{
_proxy = proxy;

View file

@ -63,8 +63,8 @@ namespace NzbDrone.Core.Download
SetImportItem(trackedDownload);
// Only process tracked downloads that are still downloading
if (trackedDownload.State != TrackedDownloadState.Downloading)
// Only process tracked downloads that are still downloading or have been blocked for importing due to an issue with matching
if (trackedDownload.State != TrackedDownloadState.Downloading && trackedDownload.State != TrackedDownloadState.ImportBlocked)
{
return;
}
@ -93,7 +93,9 @@ namespace NzbDrone.Core.Download
if (artist == null)
{
trackedDownload.Warn("Artist name mismatch, automatic import is not possible.");
trackedDownload.Warn("Artist name mismatch, automatic import is not possible. Check the download troubleshooting entry on the wiki for common causes.");
SetStateToImportBlocked(trackedDownload);
return;
}
}
@ -113,6 +115,8 @@ namespace NzbDrone.Core.Download
if (trackedDownload.RemoteAlbum == null)
{
trackedDownload.Warn("Unable to parse download, automatic import is not possible.");
SetStateToImportBlocked(trackedDownload);
return;
}
@ -149,14 +153,11 @@ namespace NzbDrone.Core.Download
var statusMessages = new List<TrackedDownloadStatusMessage>
{
new TrackedDownloadStatusMessage("One or more albums expected in this release were not imported or missing", new List<string>())
new TrackedDownloadStatusMessage("One or more tracks expected in this release were not imported or missing from the release", new List<string>())
};
if (importResults.Any(c => c.Result != ImportResultType.Imported))
{
// Mark as failed to prevent further attempts at processing
trackedDownload.State = TrackedDownloadState.ImportFailed;
statusMessages.AddRange(
importResults
.Where(v => v.Result != ImportResultType.Imported && v.ImportDecision.Item != null)
@ -165,22 +166,34 @@ namespace NzbDrone.Core.Download
new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path),
v.Errors)));
// Mark as failed to prevent further attempts at processing
trackedDownload.State = TrackedDownloadState.ImportFailed;
if (statusMessages.Any())
{
trackedDownload.Warn(statusMessages.ToArray());
}
// Publish event to notify Album was imported incompelte
// Publish event to notify album was imported incomplete
_eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload));
return;
}
if (statusMessages.Any())
{
trackedDownload.Warn(statusMessages.ToArray());
SetStateToImportBlocked(trackedDownload);
}
}
public bool VerifyImport(TrackedDownload trackedDownload, List<ImportResult> importResults)
{
var allTracksImported = importResults.All(c => c.Result == ImportResultType.Imported) ||
importResults.Count(c => c.Result == ImportResultType.Imported) >=
Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount)));
var allTracksImported =
(importResults.Any() && importResults.All(c => c.Result == ImportResultType.Imported)) ||
importResults.Where(c => c.Result == ImportResultType.Imported)
.SelectMany(c => c.ImportDecision.Item.Tracks)
.Count() >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount)));
if (allTracksImported)
{
@ -191,6 +204,10 @@ namespace NzbDrone.Core.Download
return true;
}
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
.OrderByDescending(h => h.Date)
.ToList();
// Double check if all episodes were imported by checking the history if at least one
// file was imported. This will allow the decision engine to reject already imported
// episode files and still mark the download complete when all files are imported.
@ -199,19 +216,14 @@ namespace NzbDrone.Core.Download
// and an episode is removed, but later comes back with a different ID then Sonarr will treat it as incomplete.
// Since imports should be relatively fast and these types of data changes are infrequent this should be quite
// safe, but commenting for future benefit.
var atLeastOneEpisodeImported = importResults.Any(c => c.Result == ImportResultType.Imported);
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
.OrderByDescending(h => h.Date)
.ToList();
var atLeastOneTrackImported = importResults.Any(c => c.Result == ImportResultType.Imported);
var allTracksImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
if (allTracksImportedInHistory)
{
// Log different error messages depending on the circumstances, but treat both as fully imported, because that's the reality.
// The second message shouldn't be logged in most cases, but continued reporting would indicate an ongoing issue.
if (atLeastOneEpisodeImported)
if (atLeastOneTrackImported)
{
_logger.Debug("All albums were imported in history for {0}", trackedDownload.DownloadItem.Title);
}
@ -233,10 +245,15 @@ namespace NzbDrone.Core.Download
return true;
}
_logger.Debug("Not all albums have been imported for {0}", trackedDownload.DownloadItem.Title);
_logger.Debug("Not all albums have been imported for the release '{0}'", trackedDownload.DownloadItem.Title);
return false;
}
private void SetStateToImportBlocked(TrackedDownload trackedDownload)
{
trackedDownload.State = TrackedDownloadState.ImportBlocked;
}
private void SetImportItem(TrackedDownload trackedDownload)
{
trackedDownload.ImportItem = _provideImportItemService.ProvideImportItem(trackedDownload.DownloadItem, trackedDownload.ImportItem);

View file

@ -8,6 +8,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@ -23,6 +24,7 @@ namespace NzbDrone.Core.Download
protected readonly IConfigService _configService;
protected readonly IDiskProvider _diskProvider;
protected readonly IRemotePathMappingService _remotePathMappingService;
protected readonly ILocalizationService _localizationService;
protected readonly Logger _logger;
protected ResiliencePipeline<HttpResponse> RetryStrategy => new ResiliencePipelineBuilder<HttpResponse>()
@ -77,11 +79,13 @@ namespace NzbDrone.Core.Download
protected DownloadClientBase(IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
Logger logger)
{
_configService = configService;
_diskProvider = diskProvider;
_remotePathMappingService = remotePathMappingService;
_localizationService = localizationService;
_logger = logger;
}

View file

@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download
public interface IFailedDownloadService
{
void MarkAsFailed(int historyId, bool skipRedownload = false);
void MarkAsFailed(string downloadId, bool skipRedownload = false);
void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false);
void Check(TrackedDownload trackedDownload);
void ProcessFailed(TrackedDownload trackedDownload);
}
@ -20,15 +20,12 @@ namespace NzbDrone.Core.Download
public class FailedDownloadService : IFailedDownloadService
{
private readonly IHistoryService _historyService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IEventAggregator _eventAggregator;
public FailedDownloadService(IHistoryService historyService,
ITrackedDownloadService trackedDownloadService,
IEventAggregator eventAggregator)
{
_historyService = historyService;
_trackedDownloadService = trackedDownloadService;
_eventAggregator = eventAggregator;
}
@ -37,9 +34,10 @@ namespace NzbDrone.Core.Download
var history = _historyService.Get(historyId);
var downloadId = history.DownloadId;
if (downloadId.IsNullOrWhiteSpace())
{
PublishDownloadFailedEvent(new List<EntityHistory> { history }, "Manually marked as failed", skipRedownload: skipRedownload);
PublishDownloadFailedEvent(history, new List<int> { history.AlbumId }, "Manually marked as failed", skipRedownload: skipRedownload);
return;
}
@ -53,28 +51,26 @@ namespace NzbDrone.Core.Download
}
// Add any other history items for the download ID then filter out any duplicate history items.
grabbedHistory.AddRange(_historyService.Find(downloadId, EntityHistoryEventType.Grabbed));
grabbedHistory.AddRange(GetGrabbedHistory(downloadId));
grabbedHistory = grabbedHistory.DistinctBy(h => h.Id).ToList();
PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
PublishDownloadFailedEvent(history, GetAlbumIds(grabbedHistory), "Manually marked as failed");
}
public void MarkAsFailed(string downloadId, bool skipRedownload = false)
public void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false)
{
var history = _historyService.Find(downloadId, EntityHistoryEventType.Grabbed);
var history = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
if (history.Any())
{
var trackedDownload = _trackedDownloadService.Find(downloadId);
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
PublishDownloadFailedEvent(history.First(), GetAlbumIds(history), "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
}
}
public void Check(TrackedDownload trackedDownload)
{
// Only process tracked downloads that are still downloading
if (trackedDownload.State != TrackedDownloadState.Downloading)
// Only process tracked downloads that are still downloading or import is blocked (if they fail after attempting to be processed)
if (trackedDownload.State != TrackedDownloadState.Downloading && trackedDownload.State != TrackedDownloadState.ImportBlocked)
{
return;
}
@ -82,9 +78,7 @@ namespace NzbDrone.Core.Download
if (trackedDownload.DownloadItem.IsEncrypted ||
trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
{
var grabbedItems = _historyService
.Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)
.ToList();
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
if (grabbedItems.Empty())
{
@ -103,9 +97,7 @@ namespace NzbDrone.Core.Download
return;
}
var grabbedItems = _historyService
.Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)
.ToList();
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
if (grabbedItems.Empty())
{
@ -124,18 +116,17 @@ namespace NzbDrone.Core.Download
}
trackedDownload.State = TrackedDownloadState.DownloadFailed;
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
PublishDownloadFailedEvent(grabbedItems.First(), GetAlbumIds(grabbedItems), failure, trackedDownload);
}
private void PublishDownloadFailedEvent(List<EntityHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
private void PublishDownloadFailedEvent(EntityHistory historyItem, List<int> albumIds, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{
var historyItem = historyItems.Last();
Enum.TryParse(historyItem.Data.GetValueOrDefault(EntityHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
var downloadFailedEvent = new DownloadFailedEvent
{
ArtistId = historyItem.ArtistId,
AlbumIds = historyItems.Select(h => h.AlbumId).Distinct().ToList(),
AlbumIds = albumIds,
Quality = historyItem.Quality,
SourceTitle = historyItem.SourceTitle,
DownloadClient = historyItem.Data.GetValueOrDefault(EntityHistory.DOWNLOAD_CLIENT),
@ -144,10 +135,23 @@ namespace NzbDrone.Core.Download
Data = historyItem.Data,
TrackedDownload = trackedDownload,
SkipRedownload = skipRedownload,
ReleaseSource = releaseSource
ReleaseSource = releaseSource,
};
_eventAggregator.PublishEvent(downloadFailedEvent);
}
private List<int> GetAlbumIds(List<EntityHistory> historyItems)
{
return historyItems.Select(h => h.AlbumId).Distinct().ToList();
}
private List<EntityHistory> GetGrabbedHistory(string downloadId)
{
// Sort by date so items are always in the same order
return _historyService.Find(downloadId, EntityHistoryEventType.Grabbed)
.OrderByDescending(h => h.Date)
.ToList();
}
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.IO;
using System.Linq;
using System.Xml;
@ -15,39 +16,53 @@ namespace NzbDrone.Core.Download
{
public void Validate(string filename, byte[] fileContent)
{
var reader = new StreamReader(new MemoryStream(fileContent));
using (var xmlTextReader = XmlReader.Create(reader, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true }))
try
{
var xDoc = XDocument.Load(xmlTextReader);
var nzb = xDoc.Root;
var reader = new StreamReader(new MemoryStream(fileContent));
if (nzb == null)
using (var xmlTextReader = XmlReader.Create(reader,
new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true }))
{
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
}
var xDoc = XDocument.Load(xmlTextReader);
var nzb = xDoc.Root;
// nZEDb has an bug in their error reporting code spitting out invalid http status codes
if (nzb.Name.LocalName.Equals("error") &&
nzb.TryGetAttributeValue("code", out var code) &&
nzb.TryGetAttributeValue("description", out var description))
{
throw new InvalidNzbException("Invalid NZB: Contains indexer error: {0} - {1}", code, description);
}
if (nzb == null)
{
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
}
if (!nzb.Name.LocalName.Equals("nzb"))
{
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
}
// nZEDb has an bug in their error reporting code spitting out invalid http status codes
if (nzb.Name.LocalName.Equals("error") &&
nzb.TryGetAttributeValue("code", out var code) &&
nzb.TryGetAttributeValue("description", out var description))
{
throw new InvalidNzbException("Invalid NZB: Contains indexer error: {0} - {1}", code, description);
}
var ns = nzb.Name.Namespace;
var files = nzb.Elements(ns + "file").ToList();
if (!nzb.Name.LocalName.Equals("nzb"))
{
throw new InvalidNzbException(
"Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
}
if (files.Empty())
{
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
var ns = nzb.Name.Namespace;
var files = nzb.Elements(ns + "file").ToList();
if (files.Empty())
{
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
}
}
}
catch (InvalidNzbException)
{
// Throw the original exception
throw;
}
catch (Exception ex)
{
throw new InvalidNzbException("Invalid NZB: Unable to parse [{0}]", ex, filename);
}
}
}
}

View file

@ -10,6 +10,7 @@ using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -30,9 +31,10 @@ namespace NzbDrone.Core.Download
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(configService, diskProvider, remotePathMappingService, logger)
: base(configService, diskProvider, remotePathMappingService, localizationService, logger)
{
_httpClient = httpClient;
_blocklistService = blocklistService;
@ -170,7 +172,7 @@ namespace NzbDrone.Core.Download
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
{
_logger.Error(ex, "Downloading torrent file for album '{0}' failed since it no longer exists ({1})", remoteAlbum.Release.Title, torrentUrl);
throw new ReleaseUnavailableException(remoteAlbum.Release, "Downloading torrent failed", ex);

View file

@ -115,7 +115,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
var trackedDownload = _trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition, downloadItem);
if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
if (trackedDownload is { State: TrackedDownloadState.Downloading or TrackedDownloadState.ImportBlocked })
{
_failedDownloadService.Check(trackedDownload);
_completedDownloadService.Check(trackedDownload);

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