1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-07 13:40:38 -05:00

Compare commits

..

109 Commits

Author SHA1 Message Date
RobinDadswell
89110c2cc8 version bump to 6.1.1 2025-12-29 16:32:16 +00:00
TRaSH
a126835028 New: Parse Group GiLG 2025-12-25 11:54:17 +00:00
Erik Frantz
4c00729183 Fix: (#11303) collection API error when using Movie CollectionThe (#11304)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-12-10 20:56:11 +01:00
Robin Dadswell
b59ff0a3b1 Skip proxy tests on MacOsX 2025-11-27 10:14:47 +00:00
plz12345
b9c2563c9b Chore: Remove Readarr donation logo 2025-11-27 09:00:49 +00:00
Stevie Robinson
949922b9a1 New: add TTL setting for pushover notifications
(cherry picked from commit 317cdf15582746bd4e713d6b99e17a21dcb8abeb)
2025-11-19 08:10:22 +01:00
Robin Dadswell
1b9662d588 chore: updated build images 2025-11-14 23:14:05 +00:00
Robin Dadswell
005c870f69 bump to 6.1.0 2025-11-14 23:04:59 +00:00
Mark McDowall
90cd8df1ae Add private IPv6 networks
(cherry picked from commit 52972e7efcce800560cbbaa64f5f76aaef6cbe77)
2025-11-09 10:20:36 +00:00
Mark McDowall
7d8444c435 Set known networks to RFC 1918 ranges during startup
(cherry picked from commit d10107739b9ed6a50165e5dd1dfae15c7e8aea56)
2025-10-30 09:37:21 -05:00
Polgonite
1883ae52ac Fixed: qBittorrent /login API success check 2025-10-29 17:59:53 -05:00
bakerboy448
47d4ebbeac Bump to 6.0.4 2025-10-26 12:55:36 -05:00
Bogdan
ef9836d71d Fixed: Movie status on Wanted pages
(cherry picked from commit c9c8d4ad400f9e7066063236da180cf85ff63031)
2025-10-25 12:51:40 -05:00
Bogdan
955ee2f29b Switch to FluentMigrator.Runner.Core to avoid extranous platform runners
(cherry picked from commit f93100d9fd1deb2982dbd154dd05032b17099774)
2025-10-25 12:51:40 -05:00
Bogdan
abf3fc4557 Remove redundant code in selecting with click on poster
(cherry picked from commit b116f63a1d95a23a6f3684e6b60ead60c2584f0f)
2025-10-25 12:51:40 -05:00
bakerboy448
1e72cc6b5a Bump to 6.0.3 2025-10-05 18:37:06 -05:00
Bogdan
24639a7016 Pin System.Drawing.Common to 8.0.20
(cherry picked from commit b9a79c4225b85230b4fdec702621c7b0f41c6ae1)
2025-10-05 18:36:38 -05:00
bakerboy448
e52547fa37 chore: sync CONTRIBUTING.md from Servarr/wiki (#11207)
[skip-ci]
2025-10-04 15:23:05 -05:00
bakerboy448
ff6a69701f Bump to 6.0.2 2025-09-28 21:57:01 -05:00
Collin Heist
f6afbfa684 Fixed: Prevent modals from overflowing screen width
(cherry picked from commit 6c581b7e3c5c74db350d7ba2aad04f2df77c7671)
2025-09-28 21:57:01 -05:00
Stevie Robinson
b1b33e0dbf New: Switch theme automatically on system change
(cherry picked from commit 4904e85887b8455483e509b83abaa2c6517d45a0)
2025-09-28 21:57:01 -05:00
Bogdan
cf465899b4 New: Retry SQLite writes for database is locked errors
(cherry picked from commit 2e1289b9248a70ce50bde52a66d3a589f3dcb8f5)
2025-09-28 21:57:01 -05:00
Mark McDowall
e63691935d Upgrade MonoTorrent to 3.0.2
(cherry picked from commit b0224c1c5cf00c8959c67d9e61f3e932800060c4)
2025-09-28 21:57:01 -05:00
Bogdan
1bae9499e4 Bump System.Data.SQLite to official 2.0.2
(cherry picked from commit 89ed33e1ac10a8b50082e47c604501a848ece3ea)
2025-09-28 21:57:01 -05:00
Bogdan
c991a8927d Bump FluentMigrator to official 6.2.0
(cherry picked from commit 82299cfc04732371e6709612cdb7d8d4eaf6ead3)
2025-09-28 21:57:01 -05:00
Bogdan
3c75250c08 Bump postcss to 8.5.6
(cherry picked from commit f84650b6c04f01fd498f50403c83a2103cb75140)
2025-09-28 21:57:01 -05:00
Bogdan
1e06fc5b43 Switch HttpProxySettingsProviderFixture to test cases
(cherry picked from commit 4e8fe6e81b1ac3f53135ad2e2b95d7aae811b87e)
2025-09-28 21:57:01 -05:00
Bogdan
52307038af Bump Moq to 4.18.4
(cherry picked from commit 90bb9b513cb1ac874bfa9a0a7b12b7fa7b8ede5a)
2025-09-28 21:57:01 -05:00
Bogdan
0297dba7f9 Attempt to remove pid file only if config folder exists
(cherry picked from commit df4a56662f6870a194179ffc29c98dc64af9e07c)
2025-09-28 21:57:01 -05:00
Bogdan
554a54b009 Improve error tracing in migrate app data folder
(cherry picked from commit 5ef6145db350df36507b3efaf1f8f412c1a13779)
2025-09-28 21:57:01 -05:00
nuxen
64b2a10b3f Fixed: RlsGrp parser exeption for 126811 2025-09-27 19:39:59 -05:00
Bogdan
97c226c23c Fix code coverage on CI
Signed-off-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2025-09-26 12:40:19 -05:00
Bogdan
9959c658be Avoid rewriting file names in builds
Signed-off-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2025-09-26 12:40:19 -05:00
Bogdan
eaeb668eb5 Bump coverlet.collector to official 6.0.4
Signed-off-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2025-09-26 12:40:19 -05:00
bakerboy448
bb6713f1d2 Fixed: Improve IMDb list logging 2025-09-23 17:59:37 -05:00
bakerboy448
9906b95893 New: Prioritize Exact Exception Release Group Matches over non-Exact Exception Groups 2025-09-23 17:47:36 -05:00
bakerboy448
8c94581cb6 Fixed: Treat TAoE and QxR as release groups
(cherry picked from commit b00229e53c7a4bcb8684fd0aa4f66650c64a9a20)

Co-Authored-By: Mark McDowall <mark@mcdowall.ca>
2025-09-23 17:47:36 -05:00
bakerboy448
6bdbc9c600 align parsing with upstream
Separate release group parsing logic into dedicated classes and update references throughout codebase.

(cherry picked from commit b00229e53c7a4bcb8684fd0aa4f66650c64a9a20)

Co-Authored-By: Mark McDowall <mark@mcdowall.ca>
2025-09-23 17:47:36 -05:00
Erik Frantz
f28691e48d New: Add MovieCollectionThe Naming Token (#11145) 2025-09-23 16:39:25 -05:00
Weblate
e7bddaeedd Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jeremi Florczyk <j.m.florczyk@gmail.com>
Co-authored-by: Storm <storm47rus@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2025-09-23 08:04:14 -05:00
bakerboy448
94ced8cff9 Bump to 6.0.1 2025-09-21 14:54:50 -05:00
bakerboy448
3429fe0696 Fixed: Fix Indexer Flag color
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-09-14 15:34:38 -05:00
Weblate
100e121afc Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2025-09-13 15:32:39 -05:00
Servarr
24be516fdb Automated API Docs update 2025-09-11 12:59:19 -05:00
bakerboy448
f49c35563d Fix syntax 2025-09-10 07:47:39 -05:00
Bogdan
6e23750705 Fix clearing pending changes for First Run
`TypeError: can't access property "section", a is undefined`
2025-09-10 07:28:03 -05:00
bogdan
30fc50e049 Bump MailKit and Microsoft.Data.SqlClient 2025-09-09 16:56:04 -05:00
Mark McDowall
8000abc2be Change authentication to Forms if set to Basic
(cherry picked from commit dfb6fdfbeb7ce85b287b41fed80f2511727353e5)
2025-09-09 16:56:04 -05:00
Bogdan
62a05e2765 Fixed: Validation for tags label 2025-09-09 16:56:04 -05:00
Bogdan
f04bff8e91 Fixed: Removed support for movie file tokens in Movie Folder Format 2025-09-09 16:56:04 -05:00
Bogdan
84593502a3 New: Validation for movie file tokens in Movie Folder Format 2025-09-09 16:56:04 -05:00
Mark McDowall
d478b404df New: Remove Basic Auth
(cherry picked from commit 0f9e063e2146812f6e963363eee70a524612f354)
2025-09-09 16:56:04 -05:00
bakerboy448
80a9fa68de New: Default wanted language for quality profiles changed to Original 2025-09-09 16:56:04 -05:00
Bogdan
8eb9fc71b8 Bump Swashbuckle to 8.1.4 2025-09-09 16:56:04 -05:00
Bogdan
6b1567ddae Bump version to 6.0.0 2025-09-09 16:56:04 -05:00
Bogdan
265e931451 New: Support removed for linux-x86 2025-09-09 16:56:04 -05:00
Bogdan
2a886fb26a New: Migrate appdata folder for .NET 8 on OSX 2025-09-09 16:56:04 -05:00
Bogdan
2235823af3 New: Bump to .NET 8
Co-authored-by: Qstick <qstick@gmail.com>
2025-09-09 16:56:04 -05:00
Weblate
f99162b8ee Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: BoreasMun <duchpl665@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: MJ <likadion@gmail.com>
Co-authored-by: Pazuzu6666 <fukscam978@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: myrad2267 <37258280+myrad2267@users.noreply.github.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2025-09-09 16:55:49 -05:00
bakerboy448
a00ee08750 Bump to 5.28.1 2025-09-07 00:31:28 -05:00
Michon van Dooren
54cbbe05d9 New: (NFO Metadata) Include the TMDB Collection ID (#11164) 2025-09-06 05:58:07 -05:00
Mark McDowall
57f602eb02 New: Changing icon during import to blue 2025-09-03 17:02:22 -05:00
bakerboy448
e841c9b764 Bump to 5.28.0 2025-09-03 07:07:57 -05:00
bakerboy448
81bbaf8946 Fixed: Add missing translation keys 2025-09-01 11:29:59 -05:00
bakerboy448
8b4288fa18 New: UI Note that Filters are for movie properties only
Co-authored-by: PearsonFlyer <john@theediguy.com>

Closes #11200
2025-09-01 11:29:59 -05:00
Weblate
9aa3061e8e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Xoores <servarr-35466@xoores.cz>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2025-08-30 12:09:23 -05:00
Weblate
308c58f729 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ReDFiRe <wwsoft@abv.bg>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: rtme <pps.kmg@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2025-08-21 21:39:34 +01:00
grapexy
d38492188a New: Georgian language support (#11209) 2025-08-17 22:11:49 -05:00
bakerboy448
50e75e1362 Bump to 5.27.5 2025-08-17 14:44:46 -04:00
bakerboy448
f36845c251 Fixed: Parse UHDBDRip as BluRay quality 2025-08-16 10:35:25 -05:00
bakerboy448
110a338fb6 Fixed: TMDb List Paging (#11201) 2025-08-16 09:20:34 -05:00
Mark McDowall
3fcbaf9259 New: Move auth success logging to debug
Closes #7978

(cherry picked from commit 9ebe043bd94d036fe2ab45f3bb3f882cda48e211)
2025-08-12 12:20:36 -05:00
Luigi
576eff1890 New: Select with poster click in movie selection (#11187) 2025-08-12 11:49:58 -05:00
jkhsjdhjs
b0284bda07 Fixed: Parse HDDVDRip as BluRay 2025-08-11 18:53:36 -05:00
Bwaffles
c78666009d New: Add Year sorting to Discover page 2025-08-11 18:52:56 -05:00
Mark McDowall
b51d1beaaa Don't log debug messages for API key validation
(cherry picked from commit 78ca30d1f81361a2dabaddd0036b764859b858af)
2025-08-11 18:32:42 -05:00
Weblate
4d22bf1ceb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ArLab1 <arnaud.laberge@hotmail.com>
Co-authored-by: Ethan Francois <ethanfrancois0@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: adri1m64 <adrien.melle@laposte.net>
Co-authored-by: deamok <deamok@gmail.com>
Co-authored-by: dtorner <dtorner@gmail.com>
Co-authored-by: scdani <csonkadancsi@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2025-08-11 18:31:56 -05:00
bakerboy448
f9562b9b76 Bump version to 5.27.4 2025-08-03 01:10:22 -05:00
bakerboy448
6851c26328 Bump SixLabors.ImageSharp to 3.1.11 2025-07-31 21:52:59 -05:00
bakerboy448
e29be26fc9 Fixed: Prevent using Original names with other movie file tokens (#11175)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-07-25 08:16:18 -05:00
bakerboy448
f6bd2f52d5 Bump version to 5.27.3 2025-07-23 08:51:28 -05:00
Denis Gheorghescu
8bef9b4da7 New:(Pushcut) Improved Notification Details (#10897) 2025-07-19 10:43:18 -04:00
Mark McDowall
787c387036 Return error if Manual Import called without items
(cherry picked from commit 4bdb0408f1bafa38b777a41babb1a775f99a94c1)
2025-07-09 16:19:57 -05:00
bakerboy448
0525256115 Bump version to 5.27.2 2025-07-08 18:37:58 -05:00
bakerboy448
5767e181b7 New: Improve Reject for Unknown Movie Messaging (#11063) 2025-07-08 18:25:51 -05:00
Mark McDowall
1cf3ef5dff New: Improve stored UI settings for multiple instances under the same host
Closes #10671
Fixes #11146

(cherry picked from commit 6677fd11168de6dbf78d03bfedf67b89dfe1df53)
2025-07-08 18:07:52 -05:00
Weblate
b6bad2398c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ACHN SYPS <achn.syps@gmail.com>
Co-authored-by: EdiTurn <yyxstter@gmail.com>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: josef <josef.holzapfel@proton.me>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2025-07-08 18:06:46 -05:00
nuxen
16308e4b1c Fixed: xvid not always detected correctly (#11138) 2025-07-07 20:00:13 -05:00
bakerboy448
bd7465fae4 Fixed: Allow Discover Exclusions of Movies without Year (Year 0)
Fixes #11135
2025-06-28 09:07:26 -05:00
Weblate
c0d70485c3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: HanaO00 <lwin24452@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2025-06-19 18:20:22 -05:00
Bogdan
c743383912 Fixed: Deleting tags from UI
Fixes #11131
2025-06-16 20:34:00 +03:00
Servarr
d93c1d7808 Automated API Docs update 2025-06-16 20:22:31 +03:00
nuxen
0e2e7e4259 New: Support for multiple movieIds in Rename API endpoint 2025-06-16 19:09:19 +03:00
Bogdan
e6b27512c9 Bump version to 5.27.1 2025-06-15 09:20:29 +03:00
Bogdan
dae5e86b2c Fixed: Skip title searches for Newznab/Torznab indexers when movie year is missing
Prevents useless text searches of `Movie Title 0` when year is missing.

Fixes #10569
2025-06-14 13:20:11 +03:00
Bogdan
71f032d175 Bump Polly to 8.6.0 2025-06-11 23:13:54 +03:00
Bogdan
5a6db29dbd Bump version to 5.27.0 2025-06-11 23:11:20 +03:00
Weblate
2dac2dd35b 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: Qqqqqquexx <946921515@qq.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_Hans/
Translation: Servarr/Radarr
2025-06-11 15:01:35 +03:00
Stevie Robinson
b829638a77 Fixed: Include network drive types in Disk Space
(cherry picked from commit 9ffcd141a515e99604881a4ef383dadafef31eeb)
2025-06-10 15:31:34 +03:00
Mark McDowall
b6b7f13839 Prevent should refresh movie metadata from failing
Fixed: Prevent error checking if movie metadata should be refreshed from failing refresh movies task
(cherry picked from commit 3eed84c67938fed308e562e69cf7bcd727063803)
2025-06-10 15:31:34 +03:00
Mark McDowall
a9ad197b75 New: Update wording when removing a root folder
(cherry picked from commit 51c17fd3122f7b96a4155593d465ba32870d0c91)
2025-06-10 15:31:34 +03:00
Mark McDowall
1b28116a7e Fixed: Escape backticks in discord notifications
(cherry picked from commit 70c74fc1769f2094a14faaa103c49d744534be9f)
2025-06-10 15:31:34 +03:00
Bogdan
5870c88e1c Fix fullscreen automation screenshots 2025-06-09 22:05:09 +03:00
Servarr
0629832bd0 Automated API Docs update 2025-06-09 15:22:52 +03:00
Bogdan
430897c710 Fixed: Hide separators when page toolbar shows all buttons on small screens
Fixes #11124
2025-06-09 15:11:45 +03:00
Bogdan
9c42246eef Bump SixLabors.ImageSharp to 3.1.9 2025-06-08 11:32:36 +03:00
Weblate
489a86b253 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2025-06-08 10:42:02 +03:00
Robert Dailey
9c8d3b679d Add 'qualitydefinition/limits' endpoint to get size limitations
(cherry picked from commit 24f03fc1e96eba215f96312c791cf167f10499c7)
2025-06-08 10:41:37 +03:00
Bogdan
b2e51d1613 Bump version to 5.26.2 2025-06-08 10:30:06 +03:00
199 changed files with 4403 additions and 883 deletions

View File

@@ -2,7 +2,7 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "Radarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,

9
.gitignore vendored
View File

@@ -165,15 +165,12 @@ Thumbs.db
/tools/Addins/*
packages.config.md5sum
# Common IntelliJ Platform excludes
# Ignore Rider projects completely for now
.idea/
# ignore node_modules symlink
node_modules
node_modules.nosync
# API doc generation
.config/
# Ignore Jetbrains IntelliJ Workspace Directories
.idea/

2
.vscode/launch.json vendored
View File

@@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Radarr",
"program": "${workspaceFolder}/_output/net8.0/Radarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console

View File

@@ -1,13 +1,186 @@
# How to Contribute
We're always looking for people to help make Radarr even better, there are a number of ways to contribute.
This file has been moved to the wiki for the latest details please see the [contributing wiki page](https://wiki.servarr.com/radarr/contributing).
# Documentation
## Documentation
Setup guides, [FAQ](/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better.
Setup guides, [FAQ](https://wiki.servarr.com/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better.
# Development
## Development
Radarr is written in C# (backend) and JS (frontend). The backend is built on the .NET6 (and _soon_ .NET8) framework, while the frontend utilizes Reactjs.
See the [Wiki Page](https://wiki.servarr.com/radarr/contributing)
## Tools required
- Visual Studio 2022 or higher is recommended (<https://www.visualstudio.com/vs/>). The community version is free and works (<https://www.visualstudio.com/downloads/>).
> VS 2022 V17.0 or higher is recommended as it includes the .NET6 SDK
{.is-info}
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
- [Git](https://git-scm.com/downloads)
- The [Node.js](https://nodejs.org/) runtime is required. The following versions are supported:
- **20** (any minor or patch version within this)
{.grid-list}
> The Application will **NOT** run on older versions such as `18.x`, `16.x` or any version below 20.0! Due to a dependency issue, it will also not run on `21.x` and is untested on other verisons.
{.is-warning}
- [Yarn](https://yarnpkg.com/getting-started/install) is required to build the frontend
- Yarn is included with **Node 20**+ by default. Enable it with `corepack enable`
- For other Node versions, install it with `npm i -g corepack`
## Getting started
1. Fork Radarr
1. Clone the repository into your development machine. [*info*](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
> Be sure to run lint `yarn lint --fix` on your code for any front end changes before committing.
For css changes `yarn stylelint-windows --fix` {.is-info}
### Building the frontend
- Navigate to the cloned directory
- Install the required Node Packages
```bash
yarn install
```
- Start webpack to monitor your development environment for any changes that need post processing using:
```bash
yarn start
```
### Building the Backend
The backend solution is most easily built and ran in Visual Studio or Rider, however if the only priority is working on the frontend UI it can be built easily from command line as well when the correct SDK is installed.
#### Visual Studio
> Ensure startup project is set to `Radarr.Console` and framework to `net6.0`
{.is-info}
1. First `Build` the solution in Visual Studio, this will ensure all projects are correctly built and dependencies restored
1. Next `Debug/Run` the project in Visual Studio to start Radarr
1. Open <http://localhost:7878>
#### Command line
1. Clean solution
```shell
dotnet clean src/Radarr.sln -c Debug
```
1. Restore and Build debug configuration for the correct platform (Posix or Windows)
```shell
dotnet msbuild -restore src/Radarr.sln -p:Configuration=Debug -p:Platform=Posix -t:PublishAllRids
```
1. Run the produced executable from `/_output`
## Contributing Code
- If you're adding a new, already requested feature, please comment on [GitHub Issues](https://github.com/Radarr/Radarr/issues) so work is not duplicated (If you want to add something not already on there, please talk to us first)
- Rebase from Radarr's develop branch, do not merge
- Make meaningful commits, or squash them
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
- Reach out to us on the discord if you have any questions
- Add tests (unit/integration)
- Commit with \*nix line endings for consistency (We checkout Windows and commit \*nix)
- One feature/bug fix per pull request to keep things clean and easy to understand
- Use 4 spaces instead of tabs, this is the default for VS 2022 and WebStorm
## Pull Requesting
- Only make pull requests to `develop`, never `master`, if you make a PR to `master` we will comment on it and close it
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
- `new-feature` (Good)
- `fix-bug` (Good)
- `patch` (Bad)
- `develop` (Bad)
- Commits should be wrote as `New:` or `Fixed:` for changes that would not be considered a `maintenance release`
## Unit Testing
Radarr utilizes nunit for its unit, integration, and automation test suite.
### Running Tests
Tests can be run easily from within VS using the included nunit3testadapter nuget package or from the command line using the included bash script `test.sh`.
From VS simply navigate to Test Explorer and run or debug the tests you'd like to examine.
Tests can be run all at once or one at a time in VS.
From command line the `test.sh` script accepts 3 parameters
```bash
test.sh <PLATFORM> <TYPE> <COVERAGE>
```
### Writing Tests
While not always fun, we encourage writing unit tests for any backend code changes. This will ensure the change is functioning as you intended and that future changes dont break the expected behavior.
> We currently require 80% coverage on new code when submitting a PR
{.is-info}
If you have any questions about any of this, please let us know.
# Translation
Radarr uses a self hosted open access [Weblate](https://translate.servarr.com) instance to manage its json translation files. These files are stored in the repo at `src/NzbDrone.Core/Localization`
## Contributing to an Existing Translation
Weblate handles synchronization and translation of strings for all languages other than English. Editing of translated strings and translating existing strings for supported languages should be performed there for the Radarr project.
The English translation, `en.json`, serves as the source for all other translations and is managed on GitHub repo.
## Adding a Language
Adding translations to Radarr requires two steps
- Adding the Language to weblate
- Adding the Language to Radarr codebase
## Adding Translation Strings in Code
The English translation, `src/NzbDrone.Core/Localization/en.json`, serves as the source for all other translations and is managed on GitHub repo. When adding a new string to either the UI or backend a key must also be added to `en.json` along with the default value in English. This key may then be consumed as follows:
> PRs for translation of log messages will not be accepted
{.is-warning}
### Backend Strings
Backend strings may be added utilizing the Localization Service `GetLocalizedString` method
```dotnet
private readonly ILocalizationService _localizationService;
public IndexerCheck(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
var translated = _localizationService.GetLocalizedString("IndexerHealthCheckNoIndexers")
```
### Frontend Strings
New strings can be added to the frontend by importing the translate function and using a key specified from `en.json`
```js
import translate from 'Utilities/String/translate';
<div>
{translate('UnableToAddANewIndexerPleaseTryAgain')}
</div>
```

View File

@@ -9,18 +9,18 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.26.1'
majorVersion: '6.1.1'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.427'
dotnetVersion: '8.0.405'
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-22.04'
macImage: 'macOS-13'
windowsImage: 'windows-2025'
linuxImage: 'ubuntu-24.04'
macImage: 'macOS-15'
trigger:
branches:
@@ -106,7 +106,7 @@ stages:
echo "Extra platforms already enabled"
else
echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS"
fi
displayName: Enable Extra Platform Support
- bash: ./build.sh --backend --enable-extra-platforms
@@ -122,27 +122,23 @@ stages:
artifact: '$(osName)Backend'
displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/win-x64/publish'
- publish: '$(testsFolder)/net8.0/win-x64/publish'
artifact: win-x64-tests
displayName: Publish win-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
- publish: '$(testsFolder)/net8.0/linux-x64/publish'
artifact: linux-x64-tests
displayName: Publish linux-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
artifact: linux-x86-tests
displayName: Publish linux-x86 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
- publish: '$(testsFolder)/net8.0/linux-musl-x64/publish'
artifact: linux-musl-x64-tests
displayName: Publish linux-musl-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
- publish: '$(testsFolder)/net8.0/freebsd-x64/publish'
artifact: freebsd-x64-tests
displayName: Publish freebsd-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
- publish: '$(testsFolder)/net8.0/osx-x64/publish'
artifact: osx-x64-tests
displayName: Publish osx-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@@ -189,7 +185,7 @@ stages:
artifact: '$(osName)Frontend'
displayName: Publish Frontend
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- stage: Installer
dependsOn:
- Build_Backend
@@ -260,21 +256,21 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
rootFolderOrFile: $(artifactsFolder)/win-x64/net8.0
- task: ArchiveFiles@2
displayName: Create win-x86 zip
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
rootFolderOrFile: $(artifactsFolder)/win-x86/net8.0
- task: ArchiveFiles@2
displayName: Create osx-x64 app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net8.0
- task: ArchiveFiles@2
displayName: Create osx-x64 tar
inputs:
@@ -282,14 +278,14 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
rootFolderOrFile: $(artifactsFolder)/osx-x64/net8.0
- task: ArchiveFiles@2
displayName: Create osx-arm64 app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net8.0
- task: ArchiveFiles@2
displayName: Create osx-arm64 tar
inputs:
@@ -297,7 +293,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-x64 tar
inputs:
@@ -305,7 +301,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
rootFolderOrFile: $(artifactsFolder)/linux-x64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-musl-x64 tar
inputs:
@@ -313,15 +309,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
- task: ArchiveFiles@2
displayName: Create linux-x86 tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x86.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-arm tar
inputs:
@@ -329,7 +317,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
rootFolderOrFile: $(artifactsFolder)/linux-arm/net8.0
- task: ArchiveFiles@2
displayName: Create linux-musl-arm tar
inputs:
@@ -337,7 +325,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net8.0
- task: ArchiveFiles@2
displayName: Create linux-arm64 tar
inputs:
@@ -345,7 +333,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-musl-arm64 tar
inputs:
@@ -353,7 +341,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net8.0
- task: ArchiveFiles@2
displayName: Create freebsd-x64 tar
inputs:
@@ -361,7 +349,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net8.0
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages'
displayName: Publish Packages
@@ -392,7 +380,7 @@ stages:
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
SENTRY_ORG: $(sentryOrg)
SENTRY_URL: $(sentryUrl)
- stage: Unit_Test
displayName: Unit Tests
dependsOn: Build_Backend
@@ -493,29 +481,19 @@ stages:
testName: 'Musl Net Core'
artifactName: linux-musl-x64-tests
containerImage: ghcr.io/servarr/testimages:alpine
linux-x86:
testName: 'linux-x86'
artifactName: linux-x86-tests
containerImage: ghcr.io/servarr/testimages:linux-x86
pool:
vmImage: ${{ variables.linuxImage }}
container: $[ variables['containerImage'] ]
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .NET'
inputs:
version: $(dotnetVersion)
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
- bash: |
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
displayName: 'Install .NET'
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
@@ -559,7 +537,7 @@ stages:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -611,12 +589,12 @@ stages:
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -699,7 +677,7 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -721,7 +699,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -776,7 +754,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -840,7 +818,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -926,29 +904,18 @@ stages:
artifactName: linux-musl-x64-tests
containerImage: ghcr.io/servarr/testimages:alpine
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz'
linux-x86:
testName: 'linux-x86'
artifactName: linux-x86-tests
containerImage: ghcr.io/servarr/testimages:linux-x86
pattern: 'Radarr.*.linux-core-x86.tar.gz'
pool:
vmImage: ${{ variables.linuxImage }}
container: $[ variables['containerImage'] ]
timeoutInMinutes: 15
steps:
- task: UseDotNet@2
displayName: 'Install .NET'
inputs:
version: $(dotnetVersion)
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
- bash: |
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
displayName: 'Install .NET'
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
@@ -965,7 +932,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -988,7 +955,7 @@ stages:
- stage: Automation
displayName: Automation
dependsOn: Packages
jobs:
- job: Automation
strategy:
@@ -1014,7 +981,7 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -1036,7 +1003,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -1161,7 +1128,7 @@ stages:
- checkout: self
submodules: true
persistCredentials: true
fetchDepth: 1
fetchDepth: 1
- bash: ./docs.sh Windows
displayName: Create openapi.json
- bash: |
@@ -1227,22 +1194,23 @@ stages:
extraProperties: |
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.cobertura.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: |
./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
./build.sh --backend -f net8.0 -r win-x64
TEST_DIR=_tests/net8.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@3
condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results
- task: reportgenerator@5.3.11
- task: reportgenerator@5
displayName: Generate Coverage Report
inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml'
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
publishCodeCoverageResults: true
sourcedirs: src
- stage: Report_Out
dependsOn:
@@ -1274,4 +1242,3 @@ stages:
DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId)

View File

@@ -33,14 +33,14 @@ EnableExtraPlatformsInSDK()
echo "Extra platforms already enabled"
else
echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS"
fi
}
EnableExtraPlatforms()
{
if grep -qv freebsd-x64 src/Directory.Build.props; then
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
fi
}
@@ -79,9 +79,9 @@ Build()
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
dotnet msbuild -restore $slnFile -p:SelfContained=True -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
else
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
dotnet msbuild -restore $slnFile -p:SelfContained=True -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
fi
ProgressEnd 'Build'
@@ -137,7 +137,7 @@ PackageLinux()
echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net6.0" ]; then
if [ "$framework" = "net8.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi
@@ -165,7 +165,7 @@ PackageMacOS()
echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net6.0" ]; then
if [ "$framework" = "net8.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi
@@ -377,15 +377,14 @@ then
Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
PackageTests "net6.0" "win-x64"
PackageTests "net6.0" "win-x86"
PackageTests "net6.0" "linux-x64"
PackageTests "net6.0" "linux-musl-x64"
PackageTests "net6.0" "osx-x64"
PackageTests "net8.0" "win-x64"
PackageTests "net8.0" "win-x86"
PackageTests "net8.0" "linux-x64"
PackageTests "net8.0" "linux-musl-x64"
PackageTests "net8.0" "osx-x64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then
PackageTests "net6.0" "freebsd-x64"
PackageTests "net6.0" "linux-x86"
PackageTests "net8.0" "freebsd-x64"
fi
else
PackageTests "$FRAMEWORK" "$RID"
@@ -413,20 +412,19 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
Package "net6.0" "win-x64"
Package "net6.0" "win-x86"
Package "net6.0" "linux-x64"
Package "net6.0" "linux-musl-x64"
Package "net6.0" "linux-arm64"
Package "net6.0" "linux-musl-arm64"
Package "net6.0" "linux-arm"
Package "net6.0" "linux-musl-arm"
Package "net6.0" "osx-x64"
Package "net6.0" "osx-arm64"
Package "net8.0" "win-x64"
Package "net8.0" "win-x86"
Package "net8.0" "linux-x64"
Package "net8.0" "linux-musl-x64"
Package "net8.0" "linux-arm64"
Package "net8.0" "linux-musl-arm64"
Package "net8.0" "linux-arm"
Package "net8.0" "linux-musl-arm"
Package "net8.0" "osx-x64"
Package "net8.0" "osx-arm64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then
Package "net6.0" "freebsd-x64"
Package "net6.0" "linux-x86"
Package "net8.0" "freebsd-x64"
fi
else
Package "$FRAMEWORK" "$RID"
@@ -436,7 +434,7 @@ fi
if [ "$INSTALLER" = "YES" ];
then
InstallInno
BuildInstaller "net6.0" "win-x64"
BuildInstaller "net6.0" "win-x86"
BuildInstaller "net8.0" "win-x64"
BuildInstaller "net8.0" "win-x86"
RemoveInno
fi

View File

@@ -1,7 +1,7 @@
#!/bin/bash
set -e
FRAMEWORK="net6.0"
FRAMEWORK="net8.0"
PLATFORM=$1
ARCHITECTURE="${2:-x64}"
@@ -38,7 +38,7 @@ dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
dotnet tool install --version 8.1.4 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v3 &

View File

@@ -26,7 +26,7 @@ function BlocklistDetailsModal(props: BlocklistDetailsModalProps) {
return (
<Modal isOpen={isOpen} onModalClose={onModalClose}>
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Details</ModalHeader>
<ModalHeader>{translate('Details')}</ModalHeader>
<ModalBody>
<DescriptionList>

View File

@@ -304,7 +304,7 @@ function Queue() {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh"
label={translate('Refresh')}
iconName={icons.REFRESH}
isSpinning={isRefreshing}
onPress={handleRefreshPress}

View File

@@ -90,7 +90,7 @@ function QueueStatus(props: QueueStatusProps) {
if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`;
iconKind = kinds.PURPLE;
iconKind = kinds.PRIMARY;
}
if (trackedDownloadState === 'failedPending') {

View File

@@ -56,6 +56,8 @@ function CustomFiltersModalContent(props) {
{translate('AddCustomFilter')}
</Button>
</div>
<br />
{translate('FilterMoviePropertiesOnlyNotFileWarning')}
</ModalBody>
<ModalFooter>

View File

@@ -26,6 +26,10 @@
color: var(--warningColor);
}
.primary {
color: var(--primaryColor);
}
.purple {
color: var(--purple);
}

View File

@@ -6,6 +6,7 @@ interface CssExports {
'disabled': string;
'info': string;
'pink': string;
'primary': string;
'purple': string;
'success': string;
'warning': string;

View File

@@ -19,6 +19,7 @@
.modal {
position: relative;
display: flex;
max-width: 90%;
max-height: 90%;
border-radius: 6px;
opacity: 1;

View File

@@ -80,8 +80,12 @@ function PageToolbarSection({
if (buttonCount - 1 === maxButtons) {
const overflowItems: PageToolbarButtonProps[] = [];
const buttonsWithoutSeparators = validChildren.filter(
(child) => Object.keys(child.props).length > 0
);
return {
buttons: validChildren,
buttons: buttonsWithoutSeparators,
buttonCount,
overflowItems,
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -47,6 +47,15 @@ function DiscoverMovieSortMenu(props) {
{translate('Studio')}
</SortMenuItem>
<SortMenuItem
name="year"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Year')}
</SortMenuItem>
<SortMenuItem
name="inCinemas"
sortKey={sortKey}

View File

@@ -36,7 +36,8 @@
.imdbRating,
.rottenTomatoesRating,
.traktRating,
.runtime {
.runtime,
.year {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 90px;

View File

@@ -22,6 +22,7 @@ interface CssExports {
'studio': string;
'tmdbRating': string;
'traktRating': string;
'year': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -61,7 +61,8 @@
.imdbRating,
.rottenTomatoesRating,
.traktRating,
.runtime {
.runtime,
.year {
composes: cell;
flex: 0 0 90px;

View File

@@ -28,6 +28,7 @@ interface CssExports {
'studio': string;
'tmdbRating': string;
'traktRating': string;
'year': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -167,6 +167,14 @@ class DiscoverMovieRow extends Component {
);
}
if (name === 'year') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>
{year}
</VirtualTableRowCell>
);
}
if (name === 'collection') {
return (
<VirtualTableRowCell

View File

@@ -54,7 +54,7 @@ export default function AuthenticationRequiredModalContent() {
dispatch(fetchGeneralSettings());
return () => {
dispatch(clearPendingChanges());
dispatch(clearPendingChanges({ section: `settings.${SECTION}` }));
};
}, [dispatch]);

View File

@@ -0,0 +1,56 @@
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import themes from 'Styles/Themes';
function createThemeSelector() {
return createSelector(
(state: AppState) => state.settings.ui.item.theme || window.Radarr.theme,
(theme) => theme
);
}
const useTheme = () => {
const selectedTheme = useSelector(createThemeSelector());
const [resolvedTheme, setResolvedTheme] = useState(selectedTheme);
useEffect(() => {
if (selectedTheme !== 'auto') {
setResolvedTheme(selectedTheme);
return;
}
const applySystemTheme = () => {
setResolvedTheme(
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
);
};
applySystemTheme();
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', applySystemTheme);
return () => {
window
.matchMedia('(prefers-color-scheme: dark)')
.removeEventListener('change', applySystemTheme);
};
}, [selectedTheme]);
return resolvedTheme;
};
export default useTheme;
export const useThemeColor = (color: string) => {
const theme = useTheme();
const themeVariables = themes[theme];
// @ts-expect-error - themeVariables is a string indexable type
return themeVariables[color];
};

View File

@@ -284,7 +284,7 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
/>
<MovieIndexSelectAllButton
label="SelectAll"
label={translate('SelectAll')}
isSelectMode={isSelectMode}
overflowComponent={MovieIndexSelectAllMenuItem}
/>

View File

@@ -67,6 +67,7 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
monitored,
status,
path,
titleSlug,
overview,
statistics = {} as Statistics,
images,
@@ -141,7 +142,9 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
<div className={styles.content}>
<div className={styles.poster}>
<div className={styles.posterContainer}>
{isSelectMode ? <MovieIndexPosterSelect movieId={movieId} /> : null}
{isSelectMode ? (
<MovieIndexPosterSelect movieId={movieId} titleSlug={titleSlug} />
) : null}
{status === 'deleted' ? (
<div className={styles.deleted} title={translate('Deleted')} />

View File

@@ -69,6 +69,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
monitored,
status,
images,
titleSlug,
tmdbId,
imdbId,
youTubeTrailerId,
@@ -141,7 +142,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
setIsDeleteMovieModalOpen(false);
}, [setIsDeleteMovieModalOpen]);
const link = `/movie/${tmdbId}`;
const link = `/movie/${titleSlug}`;
const elementStyle = {
width: `${posterWidth}px`,
@@ -151,7 +152,9 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
return (
<div className={styles.content}>
<div className={styles.posterContainer} title={title}>
{isSelectMode ? <MovieIndexPosterSelect movieId={movieId} /> : null}
{isSelectMode ? (
<MovieIndexPosterSelect movieId={movieId} titleSlug={titleSlug} />
) : null}
<Label className={styles.controls}>
<SpinnerIconButton

View File

@@ -3,8 +3,8 @@
top: 0;
left: 0;
z-index: 3;
width: 36px;
height: 36px;
width: 100%;
height: 100%;
}
.checkContainer {

View File

@@ -7,15 +7,23 @@ import styles from './MovieIndexPosterSelect.css';
interface MovieIndexPosterSelectProps {
movieId: number;
titleSlug: string;
}
function MovieIndexPosterSelect(props: MovieIndexPosterSelectProps) {
const { movieId } = props;
function MovieIndexPosterSelect({
movieId,
titleSlug,
}: MovieIndexPosterSelectProps) {
const [selectState, selectDispatch] = useSelect();
const isSelected = selectState.selectedState[movieId];
const onSelectPress = useCallback(
(event: SyntheticEvent<HTMLElement, PointerEvent>) => {
if (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey) {
window.open(`${window.Radarr.urlBase}/movie/${titleSlug}`, '_blank');
return;
}
const shiftKey = event.nativeEvent.shiftKey;
selectDispatch({
@@ -25,7 +33,7 @@ function MovieIndexPosterSelect(props: MovieIndexPosterSelectProps) {
shiftKey,
});
},
[movieId, isSelected, selectDispatch]
[movieId, titleSlug, isSelected, selectDispatch]
);
return (

View File

@@ -161,7 +161,7 @@ class MovieFileEditorRow extends Component {
>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
anchor={<Icon name={icons.FLAG} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}

View File

@@ -82,9 +82,9 @@ function RootFolderRow(props: RootFolderRowProps) {
<ConfirmModal
isOpen={isDeleteModalOpen}
kind={kinds.DANGER}
title={translate('DeleteRootFolder')}
message={translate('DeleteRootFolderMessageText', { path })}
confirmLabel={translate('Delete')}
title={translate('RemoveRootFolder')}
message={translate('RemoveRootFolderMoviesMessageText', { path })}
confirmLabel={translate('Remove')}
onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose}
/>

View File

@@ -30,7 +30,9 @@ export const authenticationMethodOptions = [
key: 'basic',
get value() {
return translate('AuthBasic');
}
},
isDisabled: true,
isHidden: true
},
{
key: 'forms',

View File

@@ -100,7 +100,7 @@ function ImportList({
<TagList tags={tags} tagList={tagList} />
<div className={styles.enabled}>
<Label kind={kinds.DEFAULT} title="List Refresh Interval">
<Label kind={kinds.DEFAULT} title={translate('ListRefreshInterval')}>
{`${translate('Refresh')}: ${formatShortTimeSpan(
minRefreshInterval
)}`}

View File

@@ -114,6 +114,16 @@ const movieTokens = [
example: 'The Movie Collection',
footNotes: '1',
},
{
token: '{Movie CollectionThe}',
example: "Movie's Collection, The",
footNotes: '1',
},
{
token: '{Movie CleanCollectionThe}',
example: 'Movies Collection, The',
footNotes: '1',
},
{ token: '{Movie Certification}', example: 'R' },
{ token: '{Release Year}', example: '2009' },
];

View File

@@ -54,11 +54,11 @@ function Tag({ id, label }: TagProps) {
setIsDeleteTagModalOpen(true);
}, []);
const handleConfirmDeleteTag = useCallback(() => {
const handleDeleteTagModalClose = useCallback(() => {
setIsDeleteTagModalOpen(false);
}, []);
const handleDeleteTagModalClose = useCallback(() => {
const handleConfirmDeleteTag = useCallback(() => {
dispatch(deleteTag({ id }));
}, [id, dispatch]);

View File

@@ -133,6 +133,12 @@ export const defaultState = {
isSortable: true,
isVisible: true
},
{
name: 'year',
label: () => translate('Year'),
isSortable: true,
isVisible: false
},
{
name: 'inCinemas',
label: () => translate('InCinemas'),

View File

@@ -96,14 +96,22 @@ function merge(initialState, persistedState) {
return computedState;
}
const KEY = 'radarr';
const config = {
slicer,
serialize,
merge,
key: 'radarr'
key: window.Radarr.instanceName.toLowerCase().replace(/ /g, '_') || KEY
};
export default function createPersistState() {
// Migrate existing local storage value to new key if it does not already exist.
// Leave old value as-is in case there are multiple instances using the same key.
if (config.key !== KEY && localStorage.getItem(KEY) && !localStorage.getItem(config.key)) {
localStorage.setItem(config.key, localStorage.getItem(KEY));
}
// Migrate existing local storage before proceeding
const persistedState = JSON.parse(localStorage.getItem(config.key));
migrate(persistedState);

View File

@@ -23,14 +23,6 @@ function Donations() {
/>
</Link>
</div>
<div className={styles.logoContainer} title="Readarr">
<Link to="https://readarr.com/donate">
<img
className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
/>
</Link>
</div>
<div className={styles.logoContainer} title="Prowlarr">
<Link to="https://prowlarr.com/donate">
<img

View File

@@ -18,9 +18,19 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import TablePager from 'Components/Table/TablePager';
import usePaging from 'Components/Table/usePaging';
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
import usePrevious from 'Helpers/Hooks/usePrevious';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { align, icons, kinds } from 'Helpers/Props';
import Movie from 'Movie/Movie';
import { executeCommand } from 'Store/Actions/commandActions';
import {
clearMovieFiles,
fetchMovieFiles,
} from 'Store/Actions/movieFileActions';
import {
clearQueueDetails,
fetchQueueDetails,
} from 'Store/Actions/queueActions';
import {
batchToggleCutoffUnmetMovies,
clearCutoffUnmet,
@@ -35,6 +45,8 @@ import { CheckInputChanged } from 'typings/inputs';
import { SelectStateInputProps } from 'typings/props';
import { TableOptionsChangePayload } from 'typings/Table';
import getFilterValue from 'Utilities/Filter/getFilterValue';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import {
registerPagePopulator,
unregisterPagePopulator,
@@ -108,6 +120,8 @@ function CutoffUnmet() {
const isSearchingForMovies =
isSearchingForAllMovies || isSearchingForSelectedMovies;
const previousItems = usePrevious(items);
const handleSelectAllChange = useCallback(
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
@@ -204,6 +218,8 @@ function CutoffUnmet() {
return () => {
dispatch(clearCutoffUnmet());
dispatch(clearQueueDetails());
dispatch(clearMovieFiles());
};
}, [requestCurrentPage, dispatch]);
@@ -223,6 +239,21 @@ function CutoffUnmet() {
};
}, [dispatch]);
useEffect(() => {
if (!previousItems || hasDifferentItems(items, previousItems)) {
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
const movieFileIds = selectUniqueIds<Movie, number>(items, 'movieFileId');
if (movieIds.length) {
dispatch(fetchQueueDetails({ movieIds }));
}
if (movieFileIds.length) {
dispatch(fetchMovieFiles({ movieFileIds }));
}
}
}, [items, previousItems, dispatch]);
return (
<PageContent title={translate('CutoffUnmet')}>
<PageToolbar>

View File

@@ -18,10 +18,16 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import TablePager from 'Components/Table/TablePager';
import usePaging from 'Components/Table/usePaging';
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
import usePrevious from 'Helpers/Hooks/usePrevious';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { align, icons, kinds } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import Movie from 'Movie/Movie';
import { executeCommand } from 'Store/Actions/commandActions';
import {
clearQueueDetails,
fetchQueueDetails,
} from 'Store/Actions/queueActions';
import {
batchToggleMissingMovies,
clearMissing,
@@ -36,6 +42,8 @@ import { CheckInputChanged } from 'typings/inputs';
import { SelectStateInputProps } from 'typings/props';
import { TableOptionsChangePayload } from 'typings/Table';
import getFilterValue from 'Utilities/Filter/getFilterValue';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import {
registerPagePopulator,
unregisterPagePopulator,
@@ -112,6 +120,8 @@ function Missing() {
const isSearchingForMovies =
isSearchingForAllMovies || isSearchingForSelectedMovies;
const previousItems = usePrevious(items);
const handleSelectAllChange = useCallback(
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
@@ -216,6 +226,7 @@ function Missing() {
return () => {
dispatch(clearMissing());
dispatch(clearQueueDetails());
};
}, [requestCurrentPage, dispatch]);
@@ -235,6 +246,16 @@ function Missing() {
};
}, [dispatch]);
useEffect(() => {
if (!previousItems || hasDifferentItems(items, previousItems)) {
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
if (movieIds.length) {
dispatch(fetchQueueDetails({ movieIds }));
}
}
}, [items, previousItems, dispatch]);
return (
<PageContent title={translate('Missing')}>
<PageToolbar>

5
global.json Normal file
View File

@@ -0,0 +1,5 @@
{
"sdk": {
"version": "8.0.405"
}
}

View File

@@ -29,7 +29,7 @@
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/react-fontawesome": "0.2.2",
"@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.25",
"@microsoft/signalr": "8.0.7",
"@sentry/browser": "7.119.1",
"@sentry/integrations": "7.119.1",
"@tanstack/react-query": "5.74.3",
@@ -131,7 +131,7 @@
"html-webpack-plugin": "5.6.0",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.9.1",
"postcss": "8.4.47",
"postcss": "8.5.6",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",

View File

@@ -84,7 +84,7 @@
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
<PathMap>$(MSBuildThisFileDirectory)=./</PathMap>
</PropertyGroup>
<!-- Set the AssemblyConfiguration attribute for projects -->
@@ -99,13 +99,6 @@
<RootNamespace Condition="'$(RadarrProject)'=='true'">$(MSBuildProjectName.Replace('Radarr','NzbDrone'))</RootNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(TestProject)'!='true'">
<!-- Annotates .NET assemblies with repository information including SHA -->
<!-- Sentry uses this to link directly to GitHub at the exact version/file/line -->
<!-- This is built-in on .NET 8 and can be removed once the project is updated -->
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<!-- Sentry specific configuration: Only in Release mode -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- https://docs.sentry.io/platforms/dotnet/configuration/msbuild/ -->
@@ -130,14 +123,11 @@
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
</ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'">
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.1.20" />
</ItemGroup>
<PropertyGroup Condition="'$(RadarrProject)'=='true' and '$(EnableAnalyzers)'=='false'">

View File

@@ -5,9 +5,6 @@
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBuilder" Version="6.1.0" />
@@ -8,6 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Radarr.Core.csproj" />
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
<ProjectReference Include="..\Radarr.Api.V3\Radarr.Api.V3.csproj" />
<ProjectReference Include="..\Radarr.Http\Radarr.Http.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,198 @@
using FluentValidation.TestHelper;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
using Radarr.Api.V3.Qualities;
namespace NzbDrone.Api.Test.v3.Qualities;
[Parallelizable(ParallelScope.All)]
public class QualityDefinitionResourceValidatorTests
{
private readonly QualityDefinitionResourceValidator _validator = new ();
[Test]
public void Validate_fails_when_min_size_is_below_min_limit()
{
var resource = new QualityDefinitionResource
{
MinSize = QualityDefinitionLimits.Min - 1,
PreferredSize = null,
MaxSize = null
};
var result = _validator.TestValidate(resource);
result.ShouldHaveValidationErrorFor(r => r.MinSize)
.WithErrorCode("GreaterThanOrEqualTo");
}
[Test]
public void Validate_fails_when_min_size_is_above_preferred_size_and_below_limit()
{
var resource = new QualityDefinitionResource
{
MinSize = 10,
PreferredSize = 5,
MaxSize = null
};
var result = _validator.TestValidate(resource);
result.ShouldHaveValidationErrorFor(r => r.MinSize)
.WithErrorCode("LessThanOrEqualTo");
result.ShouldHaveValidationErrorFor(r => r.PreferredSize)
.WithErrorCode("GreaterThanOrEqualTo");
}
[Test]
public void Validate_passes_when_min_size_is_within_limits()
{
var resource = new QualityDefinitionResource
{
MinSize = QualityDefinitionLimits.Min,
PreferredSize = null,
MaxSize = null
};
var result = _validator.TestValidate(resource);
result.ShouldNotHaveAnyValidationErrors();
}
[Test]
public void Validate_fails_when_max_size_is_below_preferred_size_and_above_limit()
{
var resource = new QualityDefinitionResource
{
MinSize = null,
PreferredSize = 10,
MaxSize = 5
};
var result = _validator.TestValidate(resource);
result.ShouldHaveValidationErrorFor(r => r.MaxSize)
.WithErrorCode("GreaterThanOrEqualTo");
result.ShouldHaveValidationErrorFor(r => r.PreferredSize)
.WithErrorCode("LessThanOrEqualTo");
}
[Test]
public void Validate_fails_when_max_size_exceeds_max_limit()
{
var resource = new QualityDefinitionResource
{
MinSize = null,
PreferredSize = null,
MaxSize = QualityDefinitionLimits.Max + 1
};
var result = _validator.TestValidate(resource);
result.ShouldHaveValidationErrorFor(r => r.MaxSize)
.WithErrorCode("LessThanOrEqualTo");
}
[Test]
public void Validate_passes_when_max_size_is_within_limits()
{
var resource = new QualityDefinitionResource
{
MinSize = null,
PreferredSize = null,
MaxSize = QualityDefinitionLimits.Max
};
var result = _validator.TestValidate(resource);
result.ShouldNotHaveAnyValidationErrors();
}
[Test]
public void Validate_fails_when_preferred_size_is_below_min_size_and_above_max_size()
{
var resource = new QualityDefinitionResource
{
MinSize = 10,
PreferredSize = 7,
MaxSize = 5
};
var result = _validator.TestValidate(resource);
result.ShouldHaveValidationErrorFor(r => r.PreferredSize)
.WithErrorCode("GreaterThanOrEqualTo");
result.ShouldHaveValidationErrorFor(r => r.MaxSize)
.WithErrorCode("GreaterThanOrEqualTo");
}
[Test]
public void Validate_passes_when_preferred_size_is_null_and_other_sizes_are_valid()
{
var resource = new QualityDefinitionResource
{
MinSize = 5,
PreferredSize = null,
MaxSize = 10
};
var result = _validator.TestValidate(resource);
result.ShouldNotHaveAnyValidationErrors();
}
[Test]
public void Validate_passes_when_preferred_size_equals_limits()
{
var resource = new QualityDefinitionResource
{
MinSize = 5,
PreferredSize = 5,
MaxSize = 10
};
var result = _validator.TestValidate(resource);
result.ShouldNotHaveAnyValidationErrors();
}
[Test]
public void Validate_fails_when_all_sizes_are_provided_and_invalid()
{
var resource = new QualityDefinitionResource
{
MinSize = 15,
PreferredSize = 10,
MaxSize = 5
};
var result = _validator.TestValidate(resource);
result.ShouldHaveValidationErrorFor(r => r.MinSize)
.WithErrorCode("LessThanOrEqualTo");
result.ShouldHaveValidationErrorFor(r => r.MaxSize)
.WithErrorCode("GreaterThanOrEqualTo");
result.ShouldHaveValidationErrorFor(r => r.PreferredSize)
.WithErrorCode("GreaterThanOrEqualTo");
}
[Test]
public void Validate_passes_when_preferred_size_is_valid_within_limits()
{
var resource = new QualityDefinitionResource
{
MinSize = 5,
PreferredSize = 7,
MaxSize = 10
};
var result = _validator.TestValidate(resource);
result.ShouldNotHaveAnyValidationErrors();
}
}

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:7878";
driver.Navigate().GoToUrl("http://localhost:7878");
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

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

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" />

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Common.Test.EnvironmentInfo
[Test]
public void should_return_version()
{
BuildInfo.Version.Major.Should().BeOneOf(5, 10);
BuildInfo.Version.Major.Should().BeOneOf(6, 10);
}
[Test]

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" />

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Runtime.Serialization;
namespace NzbDrone.Common.Disk
{
@@ -24,10 +23,5 @@ namespace NzbDrone.Common.Disk
: base(message, innerException)
{
}
protected DestinationAlreadyExistsException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View File

@@ -75,6 +75,17 @@ namespace NzbDrone.Common.EnvironmentInfo
{
try
{
if (OsInfo.IsOsx)
{
var userAppDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify), ".config", "Radarr");
if (_diskProvider.FolderExists(userAppDataFolder) && !_diskProvider.FileExists(_appFolderInfo.GetConfigPath()))
{
_diskTransferService.MirrorFolder(userAppDataFolder, _appFolderInfo.AppDataFolder);
_diskProvider.DeleteFolder(userAppDataFolder, true);
}
}
var oldDbFile = Path.Combine(_appFolderInfo.AppDataFolder, "nzbdrone.db");
if (_startupContext.Args.ContainsKey(StartupContext.APPDATA))
@@ -111,7 +122,7 @@ namespace NzbDrone.Common.EnvironmentInfo
catch (Exception ex)
{
_logger.Debug(ex, ex.Message);
throw new RadarrStartupException("Unable to migrate DB from nzbdrone.db to {0}. Migrate manually", _appFolderInfo.GetDatabase());
throw new RadarrStartupException(ex, "Unable to migrate DB from nzbdrone.db to {0}. Migrate manually", _appFolderInfo.GetDatabase());
}
}
@@ -188,7 +199,7 @@ namespace NzbDrone.Common.EnvironmentInfo
private void RemovePidFile()
{
if (OsInfo.IsNotWindows)
if (OsInfo.IsNotWindows && _diskProvider.FolderExists(_appFolderInfo.AppDataFolder))
{
_diskProvider.DeleteFile(Path.Combine(_appFolderInfo.AppDataFolder, "radarr.pid"));
}

View File

@@ -202,6 +202,7 @@ namespace NzbDrone.Common.Instrumentation
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
c.ForLogger("Radarr.Http.Authentication.ApiKeyAuthenticationHandler").WriteToNil(LogLevel.Info);
});
}

View File

@@ -1,28 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="IPAddressRange" Version="6.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.4.0" />
<PackageReference Include="Npgsql" Version="7.0.10" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.Text.Json" Version="6.0.10" />
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.2" />
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="EnsureThat\Resources\ExceptionMessages.Designer.cs">

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
<ApplicationIcon>..\NzbDrone.Host\Radarr.ico</ApplicationIcon>
</PropertyGroup>

View File

@@ -9,7 +9,7 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class collectionsFixture : MigrationTest<collections>
public class collectionsFixture : MigrationTest<add_collections>
{
[Test]
public void should_add_collection_from_movie_and_link_back_to_movie()

View File

@@ -8,6 +8,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Http
{
[TestFixture]
[Platform(Exclude = "MacOsX")]
public class HttpProxySettingsProviderFixture : TestBase<HttpProxySettingsProvider>
{
private HttpProxySettings GetProxySettings()
@@ -15,24 +16,24 @@ namespace NzbDrone.Core.Test.Http
return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com,172.16.0.0/12", true, null, null);
}
[Test]
public void should_bypass_proxy()
[TestCase("http://eu.httpbin.org/get")]
[TestCase("http://google.com/get")]
[TestCase("http://localhost:8654/get")]
[TestCase("http://172.21.0.1:8989/api/v3/indexer/schema")]
public void should_bypass_proxy(string url)
{
var settings = GetProxySettings();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://eu.httpbin.org/get")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://google.com/get")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://localhost:8654/get")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.21.0.1:8989/api/v3/indexer/schema")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri(url)).Should().BeTrue();
}
[Test]
public void should_not_bypass_proxy()
[TestCase("http://bing.com/get")]
[TestCase("http://172.3.0.1:8989/api/v3/indexer/schema")]
public void should_not_bypass_proxy(string url)
{
var settings = GetProxySettings();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://bing.com/get")).Should().BeFalse();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.3.0.1:8989/api/v3/indexer/schema")).Should().BeFalse();
Subject.ShouldProxyBeBypassed(settings, new HttpUri(url)).Should().BeFalse();
}
}
}

View File

@@ -68,7 +68,8 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 53, Language.Tagalog },
new object[] { 54, Language.Urdu },
new object[] { 55, Language.Romansh },
new object[] { 56, Language.Mongolian }
new object[] { 56, Language.Mongolian },
new object[] { 57, Language.Georgian }
};
public static object[] ToIntCases =
@@ -131,7 +132,8 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Tagalog, 53 },
new object[] { Language.Urdu, 54 },
new object[] { Language.Romansh, 55 },
new object[] { Language.Mongolian, 56 }
new object[] { Language.Mongolian, 56 },
new object[] { Language.Georgian, 57 }
};
[Test]

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class CleanCollectionTheFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private MovieFile _movieFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.With(e => e.Title = "Movie Title")
.Build();
_movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" };
_namingConfig = NamingConfig.Default;
_namingConfig.RenameMovies = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
Mocker.GetMock<ICustomFormatService>()
.Setup(v => v.All())
.Returns(new List<CustomFormat>());
}
[TestCase("The Badger's Collection", "Badgers Collection, The")]
[TestCase("@ The Movies Collection", "@ The Movies Collection")] // This doesn't seem right; see: FileNameBuilder.ScenifyRemoveChars, looks like it has the "at sign" in the regex
[TestCase("A Stupid/Idiotic Collection", "Stupid Idiotic Collection, A")]
[TestCase("An Astounding & Amazing Collection", "Astounding and Amazing Collection, An")]
[TestCase("The Amazing Animal-Hero's Collection (2001)", "Amazing Animal-Heros Collection, The 2001")]
[TestCase("A Different Movië (AU)", "Different Movie, A AU")]
[TestCase("The Repairër (ZH) (2015)", "Repairer, The ZH 2015")]
[TestCase("The Eighth Sensë 2 (Thai)", "Eighth Sense 2, The Thai")]
[TestCase("The Astonishing Jæg (Latin America)", "Astonishing Jaeg, The Latin America")]
[TestCase("The Hampster Pack (B&F)", "Hampster Pack, The BandF")]
[TestCase("The Gásm: I (Almost) Got Away With It (1900)", "Gasm I Almost Got Away With It, The 1900")]
[TestCase(null, "")]
public void should_get_expected_title_back(string collection, string expected)
{
SetCollectionName(_movie, collection);
_namingConfig.StandardMovieFormat = "{Movie CleanCollectionThe}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(expected);
}
[TestCase("A")]
[TestCase("Anne")]
[TestCase("Theodore")]
[TestCase("3%")]
public void should_not_change_title(string collection)
{
SetCollectionName(_movie, collection);
_namingConfig.StandardMovieFormat = "{Movie CleanCollectionThe}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(collection);
}
private void SetCollectionName(Movie movie, string collectionName)
{
var metadata = new MovieMetadata()
{
CollectionTitle = collectionName,
};
movie.MovieMetadata = new Core.Datastore.LazyLoaded<MovieMetadata>(metadata);
movie.MovieMetadata.Value.CollectionTitle = collectionName;
}
}
}

View File

@@ -53,6 +53,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("The Amazing Race (Latin America)", "Amazing Race, The Latin America")]
[TestCase("The Rat Pack (A&E)", "Rat Pack, The AandE")]
[TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax I Almost Got Away With It, The 2016")]
[TestCase(null, "")]
public void should_get_expected_title_back(string title, string expected)
{
_movie.Title = title;

View File

@@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class CollectionTheFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private MovieFile _movieFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.With(e => e.Title = "Movie Title")
.Build();
_movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" };
_namingConfig = NamingConfig.Default;
_namingConfig.RenameMovies = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
Mocker.GetMock<ICustomFormatService>()
.Setup(v => v.All())
.Returns(new List<CustomFormat>());
}
[TestCase("The Badger Collection", "Badger Collection, The")]
[TestCase("The Mover Collection", "Mover Collection, The")]
[TestCase("A Stupid Collection", "Stupid Collection, A")]
[TestCase("An Astounding Collection", "Astounding Collection, An")]
[TestCase("The Amazing Animal-Hero Collection (2001)", "Amazing Animal-Hero Collection, The (2001)")]
[TestCase("A Different Movie (AU)", "Different Movie, A (AU)")]
[TestCase("The Repairer (ZH) (2015)", "Repairer, The (ZH) (2015)")]
[TestCase("The Eighth Sense 2 (Thai)", "Eighth Sense 2, The (Thai)")]
[TestCase("The Astonishing Jog (Latin America)", "Astonishing Jog, The (Latin America)")]
[TestCase("The Hampster Pack (B&F)", "Hampster Pack, The (B&F)")]
[TestCase("The Gasm: I (Almost) Got Away With It (1900)", "Gasm - I (Almost) Got Away With It, The (1900)")]
public void should_get_expected_title_back(string collection, string expected)
{
SetCollectionName(_movie, collection);
_namingConfig.StandardMovieFormat = "{Movie CollectionThe}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(expected);
}
[TestCase("A")]
[TestCase("Anne")]
[TestCase("Theodore")]
[TestCase("3%")]
public void should_not_change_title(string collection)
{
SetCollectionName(_movie, collection);
_namingConfig.StandardMovieFormat = "{Movie CollectionThe}";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be(collection);
}
private void SetCollectionName(Movie movie, string collectionName)
{
var metadata = new MovieMetadata()
{
CollectionTitle = collectionName,
};
movie.MovieMetadata = new Core.Datastore.LazyLoaded<MovieMetadata>(metadata);
movie.MovieMetadata.Value.CollectionTitle = collectionName;
}
}
}

View File

@@ -407,6 +407,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("nor", "NO")]
[TestCase("khk", "MN")]
[TestCase("mvf", "MN")]
[TestCase("geo", "KA")]
[TestCase("kat", "KA")]
public void should_format_languagecodes_properly(string language, string code)
{
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";

View File

@@ -1,3 +1,4 @@
using System.IO;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Movies;
@@ -32,5 +33,30 @@ namespace NzbDrone.Core.Test.OrganizerTests
Subject.GetMovieFolder(movie).Should().Be(expected);
}
[TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")]
[TestCase("A Decade's Worth of Changes", "The First Year", 1980, "{Movie CleanCollectionThe}/{Movie TitleThe} ({Release Year})", "Decades Worth of Changes, A", "First Year, The (1980)")]
[TestCase(null, "Just a Movie", 1999, "{Movie Title} ({Release Year})", null, "Just a Movie (1999)")]
[TestCase(null, "Collectionless Slop", 1949, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", null, "Collectionless Slop (1949)")]
public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle)
{
_namingConfig.MovieFolderFormat = format;
var movie = new Movie
{
MovieMetadata = new MovieMetadata
{
CollectionTitle = collectionTitle,
Title = movieTitle,
Year = year,
},
};
var result = Subject.GetMovieFolder(movie);
var expected = !string.IsNullOrWhiteSpace(expectedCollection)
? Path.Combine(expectedCollection, expectedTitle)
: expectedTitle;
result.Should().Be(expected);
}
}
}

View File

@@ -119,5 +119,15 @@ namespace NzbDrone.Core.Test.ParserTests
var result = IsoLanguages.Find(isoCode);
result.Language.Should().Be(Language.Bengali);
}
[TestCase("ka")]
[TestCase("geo")]
[TestCase("kat")]
[TestCase("ka-GE")]
public void should_return_georgian(string isoCode)
{
var result = IsoLanguages.Find(isoCode);
result.Language.Should().Be(Language.Georgian);
}
}
}

View File

@@ -521,6 +521,16 @@ namespace NzbDrone.Core.Test.ParserTests
result.Should().Contain(Language.Mongolian);
}
[TestCase("Movie.Title.1994.Georgian.WEB-DL.h264")]
[TestCase("Movie.Title.2016.Geo.WEB-DL.h264")]
[TestCase("Movie.Title.2016.KA.WEB-DL.h264")]
[TestCase("Movie.Title.2016.RU-KA.WEB-DL.h264")]
public void should_parse_language_georgian(string postTitle)
{
var result = LanguageParser.ParseLanguages(postTitle);
result.Should().Contain(Language.Georgian);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -294,6 +294,11 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Name.2016.German.DTS.DL.1080p.UHDBD.x265-TDO.mkv", false)]
[TestCase("Movie.Name.2021.1080p.BDLight.x265-AVCDVD", false)]
[TestCase("Movie.Title.2012.German.DL.1080p.UHD2BD.x264-QfG", false)]
[TestCase("Movie.Title.2005.1080p.HDDVDRip.x264", false)]
[TestCase("Movie.Title.2019.German.DL.1080p.HDR.UHDBDRip.AV1-GROUP", false)]
[TestCase("Movie.Title.2014.German.OPUS.DL.1080p.UHDBDRiP.HDR.AV1-GROUP", false)]
[TestCase("Movie.Title.1999.German.DL.1080p.HDR.UHDBDRip.AV1-GROUP", false)]
[TestCase("Movie.Title.1993.Uncut.German.DL.1080p.HDR.UHDBDRip.h265-GROUP", false)]
public void should_parse_bluray1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, QualitySource.BLURAY, proper, Resolution.R1080p);
@@ -312,6 +317,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Name.2020.German.UHDBD.2160p.HDR10.HEVC.EAC3.DL-pmHD.mkv", false)]
[TestCase("Movie.Title.2014.2160p.UHD.BluRay.X265-IAMABLE.mkv", false)]
[TestCase("Movie.Title.2014.2160p.BDRip.AAC.7.1.HDR10.x265.10bit-Markll", false)]
[TestCase("Movie.Title.1956.German.DL.2160p.HDR.UHDBDRip.h266-GROUP", false)]
[TestCase("Movie.Title.2021.4K.HDR.2160P.UHDBDRip.HEVC-10bit.GROUP", false)]
public void should_parse_bluray2160p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, QualitySource.BLURAY, proper, Resolution.R2160p);

View File

@@ -55,14 +55,14 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", null)]
[TestCase("Movie Name (2017) [2160p REMUX] [HEVC DV HYBRID HDR10+ Dolby TrueHD Atmos 7 1 24-bit Audio English] [Data Lass]", null)]
[TestCase("Movie Name (2017) [2160p REMUX] [HEVC DV HYBRID HDR10+ Dolby TrueHD Atmos 7 1 24-bit Audio English]-DataLass", "DataLass")]
[TestCase("Movie Name (2017) (Showtime) (1080p.BD.DD5.1.x265-TheSickle[TAoE])", "TheSickle")]
[TestCase("Movie Name (2017) (Showtime) (1080p.BD.DD5.1.x265-TheSickle[TAoE])", "TAoE")]
public void should_parse_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Movie Name (2020) [2160p x265 10bit S82 Joy]", "Joy")]
[TestCase("Movie Name (2003) (2160p BluRay X265 HEVC 10bit HDR AAC 7.1 Tigole) [QxR]", "Tigole")]
[TestCase("Movie Name (2003) (2160p BluRay X265 HEVC 10bit HDR AAC 7.1 Tigole) [QxR]", "QxR")]
[TestCase("Ode To Joy (2009) (2160p BluRay x265 10bit HDR Joy)", "Joy")]
[TestCase("Movie Name (2001) 1080p NF WEB-DL DDP2.0 x264-E.N.D", "E.N.D")]
[TestCase("Movie Name (2020) [1080p] [WEBRip] [5.1] [YTS.MX]", "YTS.MX")]
@@ -108,8 +108,11 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("A Movie in the Name (1964) (1080p BluRay x265 r00t)", "r00t")]
[TestCase("Movie Title (2022) (2160p ATV WEB-DL Hybrid H265 DV HDR DDP Atmos 5.1 English - HONE)", "HONE")]
[TestCase("Movie Title (2009) (2160p PMTP WEB-DL Hybrid H265 DV HDR10+ DDP Atmos 5.1 English - HONE)", "HONE")]
[TestCase("Movie Title (2022) (1080p PCOK WEB-DL H265 DV HDR DDP Atmos 5.1 English - GiLG)", "GiLG")]
[TestCase("Movie Title (2022) Extended (2160p PCOK WEB-DL H265 DV HDR DDP Atmos 5.1 English - GiLG)", "GiLG")]
[TestCase("Why.Cant.You.Use.Normal.Characters.2021.2160p.UHD.HDR10+.BluRay.TrueHD.Atmos.7.1.x265-ZØNEHD", "ZØNEHD")]
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole)", "Tigole")]
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", null)]
[TestCase("Movie.Title.2005.2160p.UHD.BluRay.TrueHD 7.1.Atmos.x265 - HQMUX", "HQMUX")]
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG (Blue Lock, Multi-Subs)", "VARYG")]
[TestCase("Movie Title (2023) (1080p BluRay x265 SDR AAC 2.0 English Vyndros)", "Vyndros")]
@@ -117,8 +120,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Title (2011) [BluRay] [1080p] [YTS.MX] [YIFY]", "YIFY")]
[TestCase("Movie Title (2014) [BluRay] [1080p] [YIFY] [YTS]", "YTS")]
[TestCase("Movie Title (2018) [BluRay] [1080p] [YIFY] [YTS.LT]", "YTS.LT")]
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 RZeroX) QxR", "RZeroX")]
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 Garshasp) QxR", "Garshasp")]
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 RZeroX) QxR", "QxR")]
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 Garshasp) QxR", "QxR")]
[TestCase("Movie Title 2024 mUHD 10Bits DoVi HDR10 2160p BluRay DD 5 1 x265 - TMd", "TMd")]
[TestCase("Movie Title 2024 mUHD 10Bits DoVi HDR10 2160p BluRay DD 5 1 x265 TMd", "TMd")]
[TestCase("Movie Title (2024) 2160p WEB-DL ESP DD+ 5.1 ING DD+ 5.1 Atmos DV HDR H.265-Eml HDTeam", "Eml HDTeam")]
@@ -126,15 +129,16 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Title (2022) BDFull 1080p DTS-HD MA 5.1 AVC LMain", "LMain")]
[TestCase("Movie Title (2024) (1080p BluRay x265 SDR DDP 5.1 English - DarQ)", "DarQ")]
[TestCase("Movie Title (2024) (1080p BluRay x265 SDR DDP 5.1 English -BEN THE MEN", "BEN THE MEN")]
[TestCase("Movie Title 2024 2160p WEB-DL DoVi HDR10+ H265 DDP 5.1 Atmos-126811", "126811")]
public void should_parse_exception_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase(@"C:\Test\Doctor.Series.2005.s01e01.internal.bdrip.x264-archivist.mkv", "archivist")]
public void should_not_include_extension_in_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Some.Movie.S02E04.720p.WEBRip.x264-SKGTV English", "SKGTV")]
@@ -143,7 +147,7 @@ namespace NzbDrone.Core.Test.ParserTests
public void should_not_include_language_in_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Some.Movie.2019.1080p.BDRip.X264.AC3-EVO-RP", "EVO")]
@@ -173,7 +177,7 @@ namespace NzbDrone.Core.Test.ParserTests
public void should_not_include_bad_suffix_in_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("[FFF] Invaders of the Movies!! - S01E11 - Someday, With Movies", "FFF")]
@@ -184,13 +188,13 @@ namespace NzbDrone.Core.Test.ParserTests
public void should_parse_anime_release_groups(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
[TestCase("Terrible.Anime.Title.2020.DBOX.480p.x264-iKaos [v3] [6AFFEF6B]")]
public void should_not_parse_anime_hash_as_release_group(string title)
{
Parser.Parser.ParseReleaseGroup(title).Should().BeNull();
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().BeNull();
}
}
}

View File

@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Title Future 2023 DVDRip XviD RUNNER[www.allstate.net]", null)]
public void should_not_parse_url_in_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
}
}
}

View File

@@ -1,11 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.151" />
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />

View File

@@ -1,8 +1,11 @@
using System;
namespace NzbDrone.Core.Authentication
{
public enum AuthenticationType
{
None = 0,
[Obsolete("Use Forms authentication instead")]
Basic = 1,
Forms = 2,
External = 3

View File

@@ -206,13 +206,24 @@ namespace NzbDrone.Core.Configuration
if (enabled)
{
SetValue("AuthenticationMethod", AuthenticationType.Basic);
return AuthenticationType.Basic;
SetValue("AuthenticationMethod", AuthenticationType.Forms);
return AuthenticationType.Forms;
}
return Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
var value = Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
? enumValue
: GetValueEnum("AuthenticationMethod", AuthenticationType.None);
#pragma warning disable CS0618 // Type or member is obsolete
if (value == AuthenticationType.Basic)
#pragma warning restore CS0618 // Type or member is obsolete
{
SetValue("AuthenticationMethod", AuthenticationType.Forms);
return AuthenticationType.Forms;
}
return value;
}
}
@@ -386,6 +397,12 @@ namespace NzbDrone.Core.Configuration
{
SetValue("EnableSsl", false);
}
#pragma warning disable CS0618 // Type or member is obsolete
if (AuthenticationMethod == AuthenticationType.Basic)
#pragma warning restore CS0618 // Type or member is obsolete
{
SetValue("AuthenticationMethod", AuthenticationType.Forms);
}
}
private void DeleteOldValues()

View File

@@ -1,13 +1,18 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Dapper;
using NLog;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Events;
using Polly;
using Polly.Retry;
namespace NzbDrone.Core.Datastore
{
@@ -40,12 +45,31 @@ namespace NzbDrone.Core.Datastore
public class BasicRepository<TModel> : IBasicRepository<TModel>
where TModel : ModelBase, new()
{
private static readonly ILogger Logger = NzbDroneLogger.GetLogger(typeof(BasicRepository<TModel>));
private readonly IEventAggregator _eventAggregator;
private readonly PropertyInfo _keyProperty;
private readonly List<PropertyInfo> _properties;
private readonly string _updateSql;
private readonly string _insertSql;
private static ResiliencePipeline RetryStrategy => new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<SQLiteException>(ex => ex.ResultCode == SQLiteErrorCode.Busy),
Delay = TimeSpan.FromMilliseconds(100),
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
OnRetry = args =>
{
Logger.Warn(args.Outcome.Exception, "Failed writing to database. Retry #{0}", args.AttemptNumber);
return default;
}
})
.Build();
protected readonly IDatabase _database;
protected readonly string _table;
@@ -186,7 +210,9 @@ namespace NzbDrone.Core.Datastore
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
{
SqlBuilderExtensions.LogQuery(_insertSql, model);
var multi = connection.QueryMultiple(_insertSql, model, transaction);
var multi = RetryStrategy.Execute(static (state, _) => state.connection.QueryMultiple(state._insertSql, state.model, state.transaction), (connection, _insertSql, model, transaction));
var multiRead = multi.Read();
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
_keyProperty.SetValue(model, id);
@@ -381,7 +407,7 @@ namespace NzbDrone.Core.Datastore
SqlBuilderExtensions.LogQuery(sql, model);
connection.Execute(sql, model, transaction: transaction);
RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.model, transaction: state.transaction), (connection, sql, model, transaction));
}
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList<TModel> models, List<PropertyInfo> propertiesToUpdate)
@@ -393,7 +419,7 @@ namespace NzbDrone.Core.Datastore
SqlBuilderExtensions.LogQuery(sql, model);
}
connection.Execute(sql, models, transaction: transaction);
RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.models, transaction: state.transaction), (connection, sql, models, transaction));
}
protected virtual SqlBuilder PagedBuilder() => Builder();

View File

@@ -7,7 +7,7 @@ using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Core.Datastore.Migration
{
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
public class DatabaseEngineVersionCheck : FluentMigrator.Migration
public class DatabaseEngineVersionCheck : ForwardOnlyMigration
{
protected readonly Logger _logger;
@@ -22,11 +22,6 @@ namespace NzbDrone.Core.Datastore.Migration
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
}
public override void Down()
{
// No-op
}
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
{
using (var versionCmd = conn.CreateCommand())

View File

@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Datastore.Migration
if (!Schema.Table("ImportExclusions").Exists())
{
Create.TableForModel("ImportExclusions")
.WithColumn("TmdbId").AsInt64().NotNullable().Unique().PrimaryKey()
.WithColumn("TmdbId").AsInt64().NotNullable().Unique()
.WithColumn("MovieTitle").AsString().Nullable()
.WithColumn("MovieYear").AsInt64().Nullable().WithDefaultValue(0);
}

View File

@@ -12,6 +12,7 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Core.Datastore.Migration
@@ -809,7 +810,7 @@ namespace NzbDrone.Core.Datastore.Migration
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
{
sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
sceneName = sceneName.IsNotNullOrWhiteSpace() ? FileExtensions.RemoveFileExtension(sceneName) : string.Empty;
foreach (var token in tokens)
{

View File

@@ -12,7 +12,7 @@ using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(208)]
public class collections : NzbDroneMigrationBase
public class add_collections : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{

View File

@@ -6,7 +6,6 @@ using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Extensions.Logging;
@@ -20,13 +19,10 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
public class MigrationController : IMigrationController
{
private readonly Logger _logger;
private readonly ILoggerProvider _migrationLoggerProvider;
public MigrationController(Logger logger,
ILoggerProvider migrationLoggerProvider)
public MigrationController(Logger logger)
{
_logger = logger;
_migrationLoggerProvider = migrationLoggerProvider;
}
public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
@@ -35,16 +31,13 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
_logger.Info("*** Migrating {0} ***", connectionString);
ServiceProvider serviceProvider;
var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres";
serviceProvider = new ServiceCollection()
var serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddNLog())
.AddFluentMigratorCore()
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
.ConfigureRunner(
builder => builder
.ConfigureRunner(builder => builder
.AddPostgres()
.AddNzbDroneSQLite()
.WithGlobalConnectionString(connectionString)

View File

@@ -4,9 +4,14 @@ using FluentMigrator.Builders.Create;
using FluentMigrator.Builders.Create.Table;
using FluentMigrator.Runner;
using FluentMigrator.Runner.BatchParser;
using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Generators.SQLite;
using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors;
using FluentMigrator.Runner.Processors.SQLite;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace NzbDrone.Core.Datastore.Migration.Framework
{
@@ -26,23 +31,40 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
return command;
}
public static void AddParameter(this System.Data.IDbCommand command, object value)
public static void AddParameter(this IDbCommand command, object value)
{
var parameter = command.CreateParameter();
parameter.Value = value;
command.Parameters.Add(parameter);
}
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder)
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder, bool binaryGuid = false, bool useStrictTables = false)
{
builder.Services
.AddTransient<SQLiteBatchParser>()
.AddScoped<SQLiteDbFactory>()
.AddScoped<NzbDroneSQLiteProcessor>()
.AddScoped<NzbDroneSQLiteProcessor>(sp =>
{
var factory = sp.GetService<SQLiteDbFactory>();
var logger = sp.GetService<ILogger<NzbDroneSQLiteProcessor>>();
var options = sp.GetService<IOptionsSnapshot<ProcessorOptions>>();
var connectionStringAccessor = sp.GetService<IConnectionStringAccessor>();
var sqliteQuoter = new SQLiteQuoter(false);
return new NzbDroneSQLiteProcessor(factory, sp.GetService<SQLiteGenerator>(), logger, options, connectionStringAccessor, sp, sqliteQuoter);
})
.AddScoped<ISQLiteTypeMap>(_ => new NzbDroneSQLiteTypeMap(useStrictTables))
.AddScoped<IMigrationProcessor>(sp => sp.GetRequiredService<NzbDroneSQLiteProcessor>())
.AddScoped<SQLiteQuoter>()
.AddScoped<SQLiteGenerator>()
.AddScoped(
sp =>
{
var typeMap = sp.GetRequiredService<ISQLiteTypeMap>();
return new SQLiteGenerator(
new SQLiteQuoter(binaryGuid),
typeMap,
new OptionsWrapper<GeneratorOptions>(new GeneratorOptions()));
})
.AddScoped<IMigrationGenerator>(sp => sp.GetRequiredService<SQLiteGenerator>());
return builder;
}
}

View File

@@ -15,6 +15,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
{
public class NzbDroneSQLiteProcessor : SQLiteProcessor
{
private readonly SQLiteQuoter _quoter;
public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
SQLiteGenerator generator,
ILogger<NzbDroneSQLiteProcessor> logger,
@@ -24,6 +26,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
SQLiteQuoter quoter)
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
{
_quoter = quoter;
}
public override void Process(AlterColumnExpression expression)
@@ -35,7 +38,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
if (columnIndex == -1)
{
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName));
throw new ApplicationException($"Column {expression.Column.Name} does not exist on table {expression.TableName}.");
}
columnDefinitions[columnIndex] = expression.Column;
@@ -45,6 +48,28 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
ProcessAlterTable(tableDefinition);
}
public override void Process(AlterDefaultConstraintExpression expression)
{
var tableDefinition = GetTableSchema(expression.TableName);
var columnDefinitions = tableDefinition.Columns.ToList();
var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.ColumnName);
if (columnIndex == -1)
{
throw new ApplicationException($"Column {expression.ColumnName} does not exist on table {expression.TableName}.");
}
var changedColumn = columnDefinitions[columnIndex];
changedColumn.DefaultValue = expression.DefaultValue;
columnDefinitions[columnIndex] = changedColumn;
tableDefinition.Columns = columnDefinitions;
ProcessAlterTable(tableDefinition);
}
public override void Process(DeleteColumnExpression expression)
{
var tableDefinition = GetTableSchema(expression.TableName);
@@ -62,7 +87,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
if (columnsToRemove.Any())
{
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName));
throw new ApplicationException($"Column {columnsToRemove.First()} does not exist on table {expression.TableName}.");
}
ProcessAlterTable(tableDefinition);
@@ -78,12 +103,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
if (columnIndex == -1)
{
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, expression.TableName));
throw new ApplicationException($"Column {expression.OldName} does not exist on table {expression.TableName}.");
}
if (columnDefinitions.Any(c => c.Name == expression.NewName))
{
throw new ApplicationException(string.Format("Column {0} already exists on table {1}.", expression.NewName, expression.TableName));
throw new ApplicationException($"Column {expression.NewName} already exists on table {expression.TableName}.");
}
oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone();
@@ -128,21 +153,20 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
}
// What is the cleanest way to do this? Add function to Generator?
var quoter = new SQLiteQuoter();
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => quoter.QuoteColumnName(c.Name)));
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => quoter.QuoteColumnName(c.Name)));
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => _quoter.QuoteColumnName(c.Name)));
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => _quoter.QuoteColumnName(c.Name)));
Process(new CreateTableExpression() { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
Process(new CreateTableExpression { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
Process(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName)));
Process($"INSERT INTO {_quoter.QuoteTableName(tempTableName)} ({columnsToInsert}) SELECT {columnsToFetch} FROM {_quoter.QuoteTableName(tableName)}");
Process(new DeleteTableExpression() { TableName = tableName });
Process(new DeleteTableExpression { TableName = tableName });
Process(new RenameTableExpression() { OldName = tempTableName, NewName = tableName });
Process(new RenameTableExpression { OldName = tempTableName, NewName = tableName });
foreach (var index in tableDefinition.Indexes)
{
Process(new CreateIndexExpression() { Index = index });
Process(new CreateIndexExpression { Index = index });
}
}
}

View File

@@ -0,0 +1,76 @@
using System.Data;
using FluentMigrator.Runner.Generators.Base;
using FluentMigrator.Runner.Generators.SQLite;
namespace NzbDrone.Core.Datastore.Migration.Framework;
// Based on https://github.com/fluentmigrator/fluentmigrator/blob/v6.2.0/src/FluentMigrator.Runner.SQLite/Generators/SQLite/SQLiteTypeMap.cs
public sealed class NzbDroneSQLiteTypeMap : TypeMapBase, ISQLiteTypeMap
{
public bool UseStrictTables { get; }
public NzbDroneSQLiteTypeMap(bool useStrictTables = false)
{
UseStrictTables = useStrictTables;
SetupTypeMaps();
}
// Must be kept in sync with upstream
protected override void SetupTypeMaps()
{
SetTypeMap(DbType.Binary, "BLOB");
SetTypeMap(DbType.Byte, "INTEGER");
SetTypeMap(DbType.Int16, "INTEGER");
SetTypeMap(DbType.Int32, "INTEGER");
SetTypeMap(DbType.Int64, "INTEGER");
SetTypeMap(DbType.SByte, "INTEGER");
SetTypeMap(DbType.UInt16, "INTEGER");
SetTypeMap(DbType.UInt32, "INTEGER");
SetTypeMap(DbType.UInt64, "INTEGER");
if (!UseStrictTables)
{
SetTypeMap(DbType.Currency, "NUMERIC");
SetTypeMap(DbType.Decimal, "NUMERIC");
SetTypeMap(DbType.Double, "NUMERIC");
SetTypeMap(DbType.Single, "NUMERIC");
SetTypeMap(DbType.VarNumeric, "NUMERIC");
SetTypeMap(DbType.Date, "DATETIME");
SetTypeMap(DbType.DateTime, "DATETIME");
SetTypeMap(DbType.DateTime2, "DATETIME");
SetTypeMap(DbType.Time, "DATETIME");
SetTypeMap(DbType.Guid, "UNIQUEIDENTIFIER");
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
SetTypeMap(DbType.DateTimeOffset, "DATETIME");
}
else
{
SetTypeMap(DbType.Currency, "TEXT");
SetTypeMap(DbType.Decimal, "TEXT");
SetTypeMap(DbType.Double, "REAL");
SetTypeMap(DbType.Single, "REAL");
SetTypeMap(DbType.VarNumeric, "TEXT");
SetTypeMap(DbType.Date, "TEXT");
SetTypeMap(DbType.DateTime, "TEXT");
SetTypeMap(DbType.DateTime2, "TEXT");
SetTypeMap(DbType.Time, "TEXT");
SetTypeMap(DbType.Guid, "TEXT");
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
SetTypeMap(DbType.DateTimeOffset, "TEXT");
}
SetTypeMap(DbType.AnsiString, "TEXT");
SetTypeMap(DbType.String, "TEXT");
SetTypeMap(DbType.AnsiStringFixedLength, "TEXT");
SetTypeMap(DbType.StringFixedLength, "TEXT");
SetTypeMap(DbType.Boolean, "INTEGER");
}
public override string GetTypeMap(DbType type, int? size, int? precision)
{
return base.GetTypeMap(type, size: null, precision: null);
}
}

View File

@@ -85,7 +85,7 @@ namespace NzbDrone.Core.DecisionEngine
if (remoteMovie.Movie == null)
{
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnknownMovie, "Unknown Movie. Unable to identify correct movie using release name."));
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnknownMovie, pushedRelease ? "Unknown Movie. Unable to match to existing movie in Library using release title." : "Unknown Movie. Unable to match to correct movie using release title."));
}
else
{

View File

@@ -39,7 +39,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;
}
@@ -61,7 +64,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

@@ -424,8 +424,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
catch (HttpException ex)
{
_logger.Debug("qbitTorrent authentication failed.");
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
_logger.Debug(ex, "qbitTorrent authentication failed.");
if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
{
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
}
@@ -437,7 +437,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
}
if (response.Content != "Ok.")
if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.")
{
// returns "Fails." on bad login
_logger.Debug("qbitTorrent authentication failed.");

View File

@@ -221,7 +221,7 @@ namespace NzbDrone.Core.Download
try
{
hash = MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
hash = MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
}
catch (FormatException ex)
{

View File

@@ -280,6 +280,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{
var setElement = new XElement("set");
setElement.SetAttributeValue("tmdbcolid", movie.MovieMetadata.Value.CollectionTmdbId);
setElement.Add(new XElement("name", movie.MovieMetadata.Value.CollectionTitle));
setElement.Add(new XElement("overview"));

View File

@@ -30,7 +30,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
return new HealthCheck(
GetType(),
HealthCheckResult.Warning,
HealthCheckResult.Error,
_localizationService.GetLocalizedString(
"NamingConfigMovieFolderFormatDeprecatedHealthCheckMessage", new Dictionary<string, object>
{

View File

@@ -30,6 +30,8 @@ namespace NzbDrone.Core.ImportLists
public virtual int PageSize => 0;
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
protected virtual bool UsePreGeneratedPages => false;
public abstract IImportListRequestGenerator GetRequestGenerator();
public abstract IParseImportListResponse GetParser();
@@ -79,7 +81,7 @@ namespace NzbDrone.Core.ImportLists
break;
}
if (!IsFullPage(page))
if (!UsePreGeneratedPages && !IsFullPage(page))
{
break;
}
@@ -210,7 +212,26 @@ namespace NzbDrone.Core.ImportLists
{
var parser = GetParser();
var generator = GetRequestGenerator();
var releases = FetchPage(generator.GetMovies().GetAllTiers().First().First(), parser);
var pageableRequests = generator.GetMovies();
var allTiers = pageableRequests.GetAllTiers();
if (!allTiers.Any())
{
return new NzbDroneValidationFailure(string.Empty,
"No pages were returned from your import list, please check your settings and the log for details.")
{ IsWarning = true };
}
var firstTier = allTiers.First();
if (!firstTier.Any())
{
return new NzbDroneValidationFailure(string.Empty,
"No data could be retrieved from your import list, please check your settings.")
{ IsWarning = true };
}
var firstRequest = firstTier.First();
var releases = FetchPage(firstRequest, parser);
if (releases.Empty())
{

View File

@@ -76,7 +76,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
public override IParseImportListResponse GetParser()
{
return new IMDbListParser(Settings);
return new IMDbListParser(Settings, _logger);
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.ImportLists.ImportListMovies;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
@@ -11,10 +12,12 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
public class IMDbListParser : RadarrList2Parser
{
private readonly IMDbListSettings _settings;
private readonly Logger _logger;
public IMDbListParser(IMDbListSettings settings)
public IMDbListParser(IMDbListSettings settings, Logger logger)
{
_settings = settings;
_logger = logger;
}
public override IList<ImportListMovie> ParseResponse(ImportListResponse importListResponse)
@@ -25,6 +28,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
if (!PreProcess(importResponse))
{
_logger.Debug("IMDb List {0}: Found {1} movies", _settings.ListId, movies.Count);
return movies;
}
@@ -34,20 +38,19 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
var rows = importResponse.Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
movies = rows.Skip(1).SelectList(m => m.Split(',')).Where(m => m.Length > 5).SelectList(i => new ImportListMovie { ImdbId = i[1], Title = i[5] });
return movies;
}
else
{
var jsonResponse = JsonConvert.DeserializeObject<List<MovieResource>>(importResponse.Content);
if (jsonResponse == null)
if (jsonResponse != null)
{
return movies;
movies = jsonResponse.SelectList(m => new ImportListMovie { TmdbId = m.TmdbId });
}
return jsonResponse.SelectList(m => new ImportListMovie { TmdbId = m.TmdbId });
}
_logger.Debug("IMDb List {0}: Found {1} movies", _settings.ListId, movies.Count);
return movies;
}
}
}

View File

@@ -9,16 +9,22 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
protected override HttpRequest GetHttpRequest()
{
Logger.Info("IMDb List {0}: Importing movies", Settings.ListId);
// Use IMDb list Export for user lists to bypass RadarrAPI caching
if (Settings.ListId.StartsWith("ls", StringComparison.OrdinalIgnoreCase))
{
throw new Exception("IMDb lists of the form 'ls12345678' are no longer supported. Feel free to remove this list after you review your Clean Library Level.");
}
return RequestBuilder.Create()
var request = RequestBuilder.Create()
.SetSegment("route", $"list/imdb/{Settings.ListId}")
.Accept(HttpAccept.Json)
.Build();
Logger.Trace("IMDb List {0}: Request URL: {1}", Settings.ListId, request.Url);
return request;
}
}
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.List
private IEnumerable<ImportListRequest> GetMoviesRequest()
{
Logger.Info("Importing TMDb movies from list: {0}", Settings.ListId);
Logger.Info("TMDb List {0}: Importing movies", Settings.ListId);
var requestBuilder = RequestBuilder.Create()
.SetSegment("api", "4")
@@ -32,19 +32,25 @@ namespace NzbDrone.Core.ImportLists.TMDb.List
.SetSegment("id", Settings.ListId)
.SetSegment("secondaryRoute", "");
Logger.Debug("Getting total pages that TMDb List: {0} consists of", Settings.ListId);
Logger.Trace("TMDb List {0}: Getting total pages", Settings.ListId);
var jsonResponse = JsonConvert.DeserializeObject<MovieSearchResource>(HttpClient.Execute(requestBuilder.Build()).Content);
MaxPages = jsonResponse.TotalPages;
if (jsonResponse.TotalPages > 1)
{
Logger.Debug("TMDb List {0}: processing {1} pages", Settings.ListId, MaxPages);
}
for (var pageNumber = 1; pageNumber <= MaxPages; pageNumber++)
{
requestBuilder.AddQueryParam("page", pageNumber, true);
var request = requestBuilder.Build();
Logger.Debug("Importing TMDb movies from: {0}", request.Url);
Logger.Debug("TMDb List {0}: Processing page {1} of {2}", Settings.ListId, pageNumber, MaxPages);
Logger.Trace("TMDb List {0}: Request URL: {1}", Settings.ListId, request.Url);
yield return new ImportListRequest(request);
}

View File

@@ -119,7 +119,8 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
var request = requestBuilder.Build();
Logger.Debug("Importing TMDb movies from: {0}", request.Url);
Logger.Debug("TMDb Popular: Processing page {0} of {1}", pageNumber, MaxPages);
Logger.Trace("TMDb Popular: Request URL: {0}", request.Url);
yield return new ImportListRequest(request);
}

View File

@@ -14,6 +14,7 @@ namespace NzbDrone.Core.ImportLists.TMDb
public override ImportListType ListType => ImportListType.TMDB;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12);
public override int PageSize => 20;
protected override bool UsePreGeneratedPages => true;
public readonly ISearchForNewMovie _skyhookProxy;
public readonly IHttpRequestBuilderFactory _requestBuilder;

View File

@@ -75,6 +75,8 @@ namespace NzbDrone.Core.ImportLists.TMDb
[FieldOption(Hint = "Raeto-Romance")]
rm,
[FieldOption(Hint = "Mongolian")]
mn
mn,
[FieldOption(Hint = "Georgian")]
ka
}
}

View File

@@ -52,17 +52,25 @@ namespace NzbDrone.Core.ImportLists.TMDb.User
requestBuilder.Method = HttpMethod.Get;
Logger.Trace("TMDb User {0}: Getting total pages", (TMDbUserListType)Settings.ListType);
var jsonResponse = JsonConvert.DeserializeObject<MovieSearchResource>(HttpClient.Execute(requestBuilder.Build()).Content);
MaxPages = jsonResponse.TotalPages;
if (jsonResponse.TotalPages > 1)
{
Logger.Debug("TMDb User {0}: processing {1} pages", (TMDbUserListType)Settings.ListType, MaxPages);
}
for (var pageNumber = 1; pageNumber <= MaxPages; pageNumber++)
{
requestBuilder.AddQueryParam("page", pageNumber, true);
var request = requestBuilder.Build();
Logger.Debug("Importing TMDb movies from: {0}", request.Url);
Logger.Debug("TMDb User {0}: Processing page {1} of {2}", (TMDbUserListType)Settings.ListType, pageNumber, MaxPages);
Logger.Trace("TMDb User {0}: Request URL: {1}", (TMDbUserListType)Settings.ListType, request.Url);
yield return new ImportListRequest(request);
}

View File

@@ -152,17 +152,18 @@ namespace NzbDrone.Core.Indexers.Newznab
}
}
if (SupportsSearch)
if (SupportsSearch && searchCriteria.Movie.Year > 0)
{
chain.AddTier();
var queryTitles = TextSearchEngine == "raw" ? searchCriteria.SceneTitles : searchCriteria.CleanSceneTitles;
foreach (var queryTitle in queryTitles)
{
var searchQuery = queryTitle;
if (!Settings.RemoveYear)
{
searchQuery = $"{searchQuery} {searchCriteria.Movie.Year}";
searchQuery += $" {searchCriteria.Movie.Year}";
}
chain.Add(GetPagedRequests(MaxPages,

View File

@@ -82,7 +82,7 @@ namespace NzbDrone.Core.Indexers
{
try
{
return MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
return MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
}
catch
{

View File

@@ -127,6 +127,7 @@ namespace NzbDrone.Core.Languages
public static Language Urdu => new Language(54, "Urdu");
public static Language Romansh => new Language(55, "Romansh");
public static Language Mongolian => new Language(56, "Mongolian");
public static Language Georgian => new Language(57, "Georgian");
public static Language Any => new Language(-1, "Any");
public static Language Original => new Language(-2, "Original");
@@ -193,6 +194,7 @@ namespace NzbDrone.Core.Languages
Urdu,
Romansh,
Mongolian,
Georgian,
Any,
Original
};

View File

@@ -1203,5 +1203,20 @@
"Tba": "TBA",
"IndexerHDBitsSettingsCategories": "Категории",
"IndexerHDBitsSettingsMediums": "Среден",
"IndexerSettingsCategories": "Категории"
"IndexerSettingsCategories": "Категории",
"ReleaseProfile": "Профил за издания",
"CinemaRelease": "Пуснат по кината",
"BlocklistedAt": "Блокиран на {date}",
"Complete": "Завърши",
"DeleteSelected": "Изтрийте избраните",
"CollectionShowDetailsHelpText": "Покажи статуса и свойствата на колекцията",
"AutoTaggingSpecificationStudio": "Студио(я)",
"Completed": "Завършено",
"DelayMinutes": "{delay} Минути",
"Category": "Категория",
"AutoTaggingSpecificationKeyword": "Ключова(и) дума(и)",
"ChangeCategory": "Промени категорията",
"DefaultNotFoundMessage": "Трябва да сте се изгубили, няма какво да видите тук.",
"ClearBlocklist": "Изчисти списъка с блокирани",
"CountMissingMoviesFromLibrary": "Липсващи филми от библеотеката : {count}"
}

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