1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-25 17:35:35 -04:00

Compare commits

...

89 Commits

Author SHA1 Message Date
Qstick
2bca1a71a2 Bump version 3.2.2 2021-06-03 07:34:31 -04:00
Qstick
4f009bb81d Fixed: Use normal URL for Trakt Oauth per new docs 2021-06-03 07:33:27 -04:00
Robin Dadswell
bdc7733faf Fixed: Clarify delay profile bypass only applies to preferred protocol 2021-05-27 07:48:55 -04:00
Qstick
7f4be53db0 Bump version 3.2.1 2021-05-27 07:48:24 -04:00
Weblate
27d998d6f2 Added translation using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]
Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1104 of 1104 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1104 of 1104 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2021-05-26 23:41:04 -04:00
Evan J
5eb593f453 Update login.html
(cherry picked from commit e8f58eb9be583639909c0ac9b3dc3b40db8c7a53)
2021-05-24 20:40:35 -04:00
Servarr
4652db0583 Translations update from Weblate (#6348)
* Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 99.7% (1101 of 1104 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/

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

Currently translated at 99.8% (1102 of 1104 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/

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

Currently translated at 99.7% (1101 of 1104 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/

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

Currently translated at 99.7% (1101 of 1104 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/

Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
2021-05-24 00:41:35 -04:00
Qstick
35d43480bf Fixed: Errors in queue after Movie deleted
Fixes #5899
Fixes RADARR-17A
2021-05-23 23:14:56 -04:00
Qstick
3e7c136a7f Fixed: ArgumentException on Import List Root Folder Check 2021-05-23 21:36:07 -04:00
Qstick
05f9f6b413 Fixed: Check movie null before trying to use it 2021-05-23 21:26:01 -04:00
Robin Dadswell
67f6eb544a New: Indexer Categories no longer Advanced option 2021-05-23 21:19:53 -04:00
Robin Dadswell
3c11e934a8 Fixed: Parsing service error for multi language releases when movie not mapped 2021-05-23 16:41:33 -04:00
Robin Dadswell
627a39b8fc Fix: Root Folder Downloads check giving errors when RuTorrent is used 2021-05-23 16:41:33 -04:00
Qstick
95c7b96dff Fixed: Health MovieEditor link goes nowhere 2021-05-20 18:47:24 -04:00
Robin Dadswell
dadd59fc3a Fix ESLint (#6335)
(cherry picked from commit c3837c9f7b50534e3eafe1d9fbaf360fee4588b7)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2021-05-19 23:36:50 +01:00
Daniel Martin Gonzalez
e67d3d3666 New: Add missing properties to Connection Webhook (#6333) 2021-05-18 19:41:13 +01:00
Qstick
f4718243ed Fixed: NZBGet Settings reference Sabnzbd 2021-05-17 22:15:10 -04:00
Robin Dadswell
fcec787eb6 Show User Agent in System->Tasks for externally triggered commands (#6327)
* Show User Agent in System->Tasks for externally triggered commands

(cherry picked from commit fe8f319f7bfdadb7218b6313ada6cae1d2a35ad8)

* Translations

Co-authored-by: Taloth Saldono <Taloth@users.noreply.github.com>
2021-05-17 21:45:23 -04:00
Qstick
5fe8f65d64 Fixed: Filter Unknown from Profile Language Select 2021-05-15 18:11:06 -04:00
JohnBednarczyk
c2a21cd238 Added custom languages to the top 2021-05-15 18:11:06 -04:00
Daniel Martin Gonzalez
a31ca4e80b New: Telegram Notification - Add Upgrade Movie Support 2021-05-15 18:10:30 -04:00
Michael Higgins
db14ac4605 New: Update scenename from API (#6132)
* update scenename from API

* Update MovieFileListResource.cs

* Update MovieFileModule.cs

null checks similar to Sonarr pull request

* Update MovieFileModule.cs

add blank line

* Update MovieFileModule.cs

include isSceneTitle check

* Update MovieFileModule.cs

fix namespace

* Update MovieFileModule.cs
2021-05-15 18:08:04 -04:00
Robin Dadswell
5f229b78be Fixed: Multi language release being rejected when indexer profile is setup for original and any other language (#6311) 2021-05-15 16:52:15 +01:00
ta264
543f2e7ddc Don't run build for weblate PRs
(cherry picked from commit 62221c2a7fc959dfe47af7bc98622d81eba3419b)
2021-05-14 06:59:03 -04:00
Servarr
d6b7ab6260 Translations update from Weblate (#6295)
* Added translation using Weblate (Chinese (Traditional)) [skip ci]

* Translated using Weblate (Danish) [skip ci]

Currently translated at 97.0% (1068 of 1101 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/

* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1101 of 1101 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/

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

Currently translated at 100.0% (1101 of 1101 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/

* Deleted translation using Weblate (Chinese (Traditional))  [skip ci]

* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1102 of 1102 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/

Co-authored-by: ted09080037 <ted09080037@gmail.com>
Co-authored-by: lechuck <theghostpirate@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Qstick <qstick@gmail.com>
2021-05-12 20:50:14 -04:00
Robin Dadswell
d7ab9292fb Fixed: restoring scroll position when going back to index page (#6308)
(cherry picked from commit 1bc52d0138c7bcb94ffce31ec05f675387612a62)

Co-authored-by: ta264 <ta264@users.noreply.github.com>
2021-05-12 12:36:20 +01:00
Robin Dadswell
4300d8d8c6 New: TMDb Keyword List support (#6297) 2021-05-12 12:36:01 +01:00
Qstick
446b2ffff9 New: Letterboxd Links for Movies 2021-05-11 23:59:44 -04:00
Mark McDowall
695720b552 New: Add rel="noreferrer" to all external links
(cherry picked from commit c722e9112496062313d09df16b169873f910f2a1)
2021-05-10 23:45:06 -04:00
Qstick
c47934c5ca Fixed: Alignment of Certification and Runtime on Add Movie 2021-05-10 23:41:21 -04:00
bakerboy448
9938737cd7 fix minimumAcailability typo and truncate version to 3 [skip ci] 2021-05-10 22:59:03 -04:00
bakerboy448
58326f05e0 remove legacy swagger doc [skip ci] 2021-05-10 22:59:03 -04:00
Robin Dadswell
04ad5ec9c0 Fixed: Interactive Search Release Rejected tooltip no longer covers download button 2021-05-10 19:06:46 -04:00
crbrz
2c008384dd Fixed: Links to search results not always working on interactive search
Co-authored-by: Cristi <cristi@emailaddressmanager.com>
2021-05-09 18:54:54 +01:00
Servarr
a9b605c872 Translations update from Weblate (#6276)
* Translated using Weblate (Dutch) [skip ci]

Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/

* Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1101 of 1101 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/

Co-authored-by: ProjectHydra31 <kay@timmerman.io>
Co-authored-by: reloxx <reloxx@interia.pl>
2021-05-09 10:28:34 -04:00
Robin Dadswell
cd9b469823 fixups 2021-05-09 08:15:10 -04:00
Mark McDowall
df4bfa501c Fixed files that were using incorrect imports
(cherry picked from commit a3bb2f1c32fc1e0c49d0d1fe24c04940453f5431)
2021-05-09 08:15:10 -04:00
Mark McDowall
194a1e5154 Updated create-react-class package
(cherry picked from commit 8acc0f77b677e4713d0f3d6f059b948a3aaca450)
2021-05-09 08:15:10 -04:00
Mark McDowall
e53b2bb83c Update react-autosuggest, react-focus-lock, react-lazyload react-slider and react-tabs packages
(cherry picked from commit 2964d0bb6da089be863199514c1e7ed4ca2832f7)
2021-05-09 08:15:10 -04:00
Qstick
10772c09ef Update microsoft/signalr package
(cherry picked from commit afe1d695d0071fa321d655425f5badc4d7a1f6c2)
2021-05-09 08:15:10 -04:00
Qstick
f9ed15409a Update connected-react-router
(cherry picked from commit d64980ce6b1eeeffc8c7b0a938b70c18bdda5067)
2021-05-09 08:15:10 -04:00
Qstick
f75ab93458 Updated react-dnd and added touch support
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
(cherry picked from commit 0b85b506e226cf1bf1f2d28eb7ed441108cc154d)

to be squashed
2021-05-09 08:15:10 -04:00
Qstick
7755a8bd3b Updated redux and react-redux packages
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
(cherry picked from commit 699da04eb3a0db3fa3eb89f5f61dcf90749e9df2)
2021-05-09 08:15:10 -04:00
Qstick
017c7998be Update react and react-dom packages
(cherry picked from commit e3968f90b23cdb9a875b474d159787e84d480bf7)
2021-05-09 08:15:10 -04:00
Qstick
f40ddfef10 Update jquery, filesize, qs packages
(cherry picked from commit c60858aed5c3607e0df192caf02e6a400c76e172)
2021-05-09 08:15:10 -04:00
Qstick
80049909eb Update clipboard, lodash, mobile-detect
(cherry picked from commit 9a30173e8defb49a8a24159c35f4982b4f791e02)
2021-05-09 08:15:10 -04:00
Qstick
fc22264f89 Update classnames
(cherry picked from commit 8f2e73045ac682c2e9abb3a1f3894a4ca16eb650)
2021-05-09 08:15:10 -04:00
Qstick
aba2e10b5c Update sentry packages
(cherry picked from commit adb01c2f8685de2abaf5ebf37000f3765e52b086)
2021-05-09 08:15:10 -04:00
Qstick
4b6874d551 Update fontawesome packages
(cherry picked from commit ef0c9472d94ff72ec186684c8c773af7ceaa8073)
2021-05-09 08:15:10 -04:00
Qstick
58934a30ce Update postcss packages
(cherry picked from commit 408089957ab8a9dc6a21b485853b9d86a9ace59f)
2021-05-09 08:15:10 -04:00
Qstick
33a960f325 Update core-js package
(cherry picked from commit f383e57e7de33c6a071083e8282ea69238ef8987)

to be sqaushed
2021-05-09 08:15:10 -04:00
Qstick
8560ff43fe Update lint packages
(cherry picked from commit 871a6272322e3fad58f70f410e1aa21feb4ff9ad)
2021-05-09 08:15:10 -04:00
Qstick
c88a47b275 Upgrade babel packages
(cherry picked from commit 1af730ffb82c14693911942718e2c171123f6ccb)
2021-05-09 08:15:10 -04:00
Qstick
67fe9101d9 Update to webpack 5, remove gulp
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
(cherry picked from commit 4ef2174226a0210f756f180dded8567d659589e2)
2021-05-09 08:15:10 -04:00
Robin Dadswell
af99c78352 New: Allow user to choose whether delay profile should apply to release of the highest enabled quality (#6218)
(cherry picked from commit d668e923af83ab7f1589d947fec9f40a3919e0b8)

Co-authored-by: Taloth Saldono <Taloth@users.noreply.github.com>
2021-05-09 00:45:04 +01:00
Robin Dadswell
df3253f55c Improved logic for root folder downloading health check (#6287) 2021-05-09 00:43:56 +01:00
Robin Dadswell
b9abc1be11 Add missing On Delete Notifications to Mailgun notifications
(cherry picked from commit a824fa44d21328d9c8bbb1ccfd6d247d4ff5c888)
2021-05-08 18:13:51 +01:00
Skyler Mäntysaari
5c0ee04271 New: Mailgun connection
(cherry picked from commit 55752a6c6213c1d83d347ba0f6870aa6c1cc0770)
2021-05-08 18:13:51 +01:00
Qstick
5e2cd3798b Move support bot to Github Action [skip ci]
[common]
2021-05-07 18:48:35 -04:00
Qstick
83041b1d37 Delete todobot config [skip ci] 2021-05-07 18:40:14 -04:00
Robin Dadswell
9f6c48191b New: Errors for Mono Health checks on X86 and BSD (except FreeBSD) based Operating Systems 2021-05-07 23:38:10 +01:00
Robin Dadswell
5696fa2efe Fixed: Wiki links on all health checks 2021-05-07 23:38:10 +01:00
Robin Dadswell
d38311b717 New: Localised all health check strings 2021-05-07 23:38:10 +01:00
Robin Dadswell
aa522066ee New: Mono health check moved from warning to error 2021-05-07 23:38:10 +01:00
Robin Dadswell
d2ba70c4d7 Fixed: Added wiki fragments to indexer health checks 2021-05-07 23:38:10 +01:00
Robin Dadswell
ca2e62492d Fixed: Import Lists messages can now show in UI 2021-05-07 17:08:19 +01:00
bakerboy448
02bcb4d865 Fixed: Incorrectly identifying YTS.LT as Lithuanian Language
New: Detect YTS.LT as a Release Group
2021-05-07 12:04:15 -04:00
ta264
36962f176f Always access config file via provider to utilise lock 2021-05-07 11:52:03 +01:00
ta264
0a2afe692f Fail build on BSD integration test fails 2021-05-07 11:52:03 +01:00
ta264
5140ee8f2e Don't start integration tests too soon
(cherry picked from commit b284f40716269dc4f641323b8293f40dfda8a424)
2021-05-07 11:52:03 +01:00
Servarr
e47ceae0c5 Translations update from Weblate (#6272)
* Translated using Weblate (French) [skip ci]

Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/

* Translated using Weblate (Italian) [skip ci]

Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/

* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/

* Translated using Weblate (Turkish) [skip ci]

Currently translated at 99.2% (1070 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/

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

Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/

Co-authored-by: Florian <sephrat.flo@gmail.com>
Co-authored-by: memnos <fabrassi@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: batuhankrmn <batuhankrmn3@gmail.com>
Co-authored-by: Ivo Capanema <ivoliveirabh@hotmail.com>
2021-05-06 07:49:42 -04:00
Qstick
906f8c1049 Bump version 3.2.0 2021-05-04 07:40:13 -04:00
bakerboy448
27e871656e New: DiscordNotifier is now Notifiarr (#6263)
[common]
2021-05-03 22:14:06 +01:00
bakerboy448
1ff147c541 fix bot discord link [skip ci] 2021-05-02 21:43:21 +01:00
Mark McDowall
7028bfb082 Fixed broken tests
(cherry picked from commit 16156192c5f1abd929bcd825186b9f153507a46e)
2021-05-02 21:42:34 +01:00
Mark McDowall
efdb9c20d4 Fixed: Removing completed download from SABnzbd
Closes #4445

(cherry picked from commit c669be317fffa252d59851e9a8ca9e56032a01fb)
2021-05-02 21:42:34 +01:00
Qstick
9bf872c9fa Delete reaction.yml
[skip ci]
2021-05-01 10:10:45 -04:00
Csaba
feaeda9186 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
2021-04-30 18:54:18 -04:00
Mark McDowall
e9cf9d05f5 Fixed: Refresh queue count when navigating Activity: Queue
(cherry picked from commit 0a2b109a3fe101e260b623d0768240ef8b7a47ae)
2021-04-29 22:47:32 -04:00
nitsua
a1211f54de Move the warning icona and download icon next to the release so it is never out of viewport 2021-04-29 22:46:51 -04:00
Qstick
c7b5e6204c Bump Version 3.1.1 2021-04-29 18:53:40 -04:00
ta264
788404ee27 Utilisation of amended coverlet package to fix randomly failing tests 2021-04-27 14:40:49 +01:00
bakerboy448
8ef4429b9e New: ISO 8601 Date in log files
Closes #6224
2021-04-25 10:36:14 -04:00
Robin Dadswell
7c5fc1e4b0 New: Adds SSL option to Kodi connections (#6219) 2021-04-24 18:43:26 +01:00
Robin Dadswell
c66f7abea5 Fixed: Parsing RSS with null values (#6220) 2021-04-24 18:43:06 +01:00
Robin Dadswell
ce6f52552a Fixed: Incorrectly grabbing revision downgrades (#6194) 2021-04-24 18:14:27 +01:00
Will Segatto
7807e2e13a Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/pt_BR/
2021-04-23 14:57:01 +00:00
Will Segatto
d5aa73fe8f Translated using Weblate (Portuguese) [skip ci]
Currently translated at 100.0% (1078 of 1078 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/radarr/radarr/pt/
2021-04-23 14:57:00 +00:00
bakerboy448
cb5bf86d8e Update indexer category help text
closes #6209

(cherrypicked from sonarr 8d2d9078ff8f6daf50aef2dded3f96dae93252cc)
2021-04-22 20:22:47 -04:00
183 changed files with 4464 additions and 7097 deletions

2
.github/config.yml vendored
View File

@@ -1,2 +0,0 @@
todo:
keyword: "TODO"

View File

@@ -1 +0,0 @@

13
.github/support.yml vendored
View File

@@ -1,13 +0,0 @@
# Configuration for support-requests - https://github.com/dessant/support-requests
# Label used to mark issues as support requests
supportLabel: 'Type: Support'
# Comment to post on issues marked as support requests. Add a link
# to a support page, or set to `false` to disable
supportComment: >
We use the issue tracker exclusively for bug reports and feature requests.
However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr)
# Whether to close issues marked as support requests
close: true
# Whether to lock issues marked as support requests
lock: false

21
.github/workflows/support.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: 'Support requests'
on:
issues:
types: [labeled, unlabeled, reopened]
jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
issue-comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord)
or [Subreddit](https://reddit.com/r/radarr)
close-issue: true
lock-issue: false

View File

@@ -7,7 +7,7 @@ variables:
outputFolder: './_output'
artifactsFolder: './_artifacts'
testsFolder: './_tests'
majorVersion: '3.1.0'
majorVersion: '3.2.2'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
@@ -23,7 +23,12 @@ trigger:
- master
pr:
- develop
branches:
include:
- develop
paths:
exclude:
- src/NzbDrone.Core/Localization/Core
stages:
- stage: Setup
@@ -651,7 +656,7 @@ stages:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'FreeBSD Integration Tests'
failTaskOnFailedTests: false
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_Docker

View File

@@ -87,11 +87,11 @@ YarnInstall()
ProgressEnd 'yarn install'
}
RunGulp()
RunWebpack()
{
ProgressStart 'Running gulp'
yarn run build --production
ProgressEnd 'Running gulp'
ProgressStart 'Running webpack'
yarn run build --env production
ProgressEnd 'Running webpack'
}
PackageFiles()
@@ -350,7 +350,7 @@ fi
if [ "$FRONTEND" = "YES" ];
then
YarnInstall
RunGulp
RunWebpack
fi
if [ "$LINT" = "YES" ];

View File

@@ -6,8 +6,10 @@ const dirs = fs
.map((dirent) => dirent.name)
.join('|');
const frontendFolder = __dirname;
module.exports = {
parser: 'babel-eslint',
parser: '@babel/eslint-parser',
env: {
browser: true,
@@ -25,6 +27,9 @@ module.exports = {
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
babelOptions: {
configFile: `${frontendFolder}/babel.config.js`
},
ecmaFeatures: {
modules: true,
impliedStrict: true
@@ -271,7 +276,7 @@ module.exports = {
// ImportSort
'simple-import-sort/sort': 'error',
'simple-import-sort/imports': 'error',
'import/newline-after-import': 'error',
// React
@@ -309,7 +314,7 @@ module.exports = {
{
files: ['*.js'],
rules: {
'simple-import-sort/sort': [
'simple-import-sort/imports': [
'error',
{
groups: [

View File

@@ -0,0 +1,269 @@
const path = require('path');
const webpack = require('webpack');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env) => {
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = !!env.production;
const isProfiling = isProduction && !!env.profile;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const config = {
mode: isProduction ? 'production' : 'development',
devtool: 'source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
},
fallback: {
buffer: false,
http: false,
https: false,
url: false,
util: false,
net: false
}
},
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
},
performance: {
hints: false
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html',
publicPath: '/'
}),
new CopyPlugin({
patterns: [
// HTML
{
from: 'frontend/src/*.html',
to: path.join(distFolder, '[name][ext]'),
globOptions: {
ignore: ['**/index.html']
}
},
// Fonts
{
from: 'frontend/src/Content/Fonts/*.*',
to: path.join(distFolder, 'Content/Fonts', '[name][ext]')
},
// Icon Images
{
from: 'frontend/src/Content/Images/Icons/*.*',
to: path.join(distFolder, 'Content/Images/Icons', '[name][ext]')
},
// Images
{
from: 'frontend/src/Content/Images/*.*',
to: path.join(distFolder, 'Content/Images', '[name][ext]')
},
// Robots
{
from: 'frontend/src/Content/robots.txt',
to: path.join(distFolder, 'Content', '[name][ext]')
}
]
}),
new LiveReloadPlugin()
],
resolveLoader: {
modules: [
'node_modules',
'frontend/build/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
return config;
};

View File

@@ -1,18 +0,0 @@
const gulp = require('gulp');
require('./clean');
require('./copy');
require('./webpack');
gulp.task('build',
gulp.series('clean',
gulp.parallel(
'webpack',
'copyHtml',
'copyFonts',
'copyImages',
'copyRobots'
)
)
);

View File

@@ -1,8 +0,0 @@
const gulp = require('gulp');
const del = require('del');
const paths = require('./helpers/paths');
gulp.task('clean', () => {
return del([paths.dest.root]);
});

View File

@@ -1,42 +0,0 @@
const path = require('path');
const gulp = require('gulp');
const print = require('gulp-print').default;
const cache = require('gulp-cached');
const livereload = require('gulp-livereload');
const paths = require('./helpers/paths.js');
gulp.task('copyHtml', () => {
return gulp.src(paths.src.html, { base: paths.src.root })
.pipe(cache('copyHtml'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyFonts', () => {
return gulp.src(
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyFonts'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyImages', () => {
return gulp.src(
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyImages'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyRobots', () => {
return gulp.src(paths.src.robots, { base: paths.src.root })
.pipe(cache('copyRobots'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});

View File

@@ -1,5 +0,0 @@
require('./build.js');
require('./clean.js');
require('./copy.js');
require('./watch.js');
require('./webpack.js');

View File

@@ -1,6 +0,0 @@
const colors = require('ansi-colors');
module.exports = function errorHandler(error) {
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
this.emit('end');
};

View File

@@ -1,24 +0,0 @@
const root = './frontend/src';
const paths = {
src: {
root,
html: `${root}/*.html`,
scripts: `${root}/**/*.js`,
content: `${root}/Content/`,
fonts: `${root}/Content/Fonts/`,
images: `${root}/Content/Images/`,
robots: `${root}/Content/robots.txt`,
exclude: {
libs: `!${root}/JsLibraries/**`
}
},
dest: {
root: './_output/UI/',
content: './_output/UI/Content/',
fonts: './_output/UI/Content/Fonts/',
images: './_output/UI/Content/Images/'
}
};
module.exports = paths;

View File

@@ -1,19 +0,0 @@
const gulp = require('gulp');
const livereload = require('gulp-livereload');
const gulpWatch = require('gulp-watch');
const paths = require('./helpers/paths.js');
require('./copy.js');
require('./webpack.js');
function watch() {
livereload.listen({ start: true });
gulp.task('webpackWatch')();
gulpWatch(paths.src.html, gulp.series('copyHtml'));
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
gulpWatch(paths.src.robots, gulp.series('copyRobots'));
}
gulp.task('watch', gulp.series('build', watch));

View File

@@ -1,275 +0,0 @@
const gulp = require('gulp');
const webpackStream = require('webpack-stream');
const livereload = require('gulp-livereload');
const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
const TerserPlugin = require('terser-webpack-plugin');
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1;
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const cssVarsFiles = [
'../src/Styles/Variables/colors',
'../src/Styles/Variables/dimensions',
'../src/Styles/Variables/fonts',
'../src/Styles/Variables/animations',
'../src/Styles/Variables/zIndexes'
].map(require.resolve);
// Override the way HtmlWebpackPlugin injects the scripts
// TODO: Find a better way to get these paths without
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
const head = assetTags.headTags.map((v) => {
const href = v.attributes.href
.replace('\\', '/')
.replace('%5C', '/');
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
});
const body = assetTags.bodyTags.map((v) => {
v.attributes = { src: `/${v.attributes.src}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
});
return html
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
};
const plugins = [
new webpack.IgnorePlugin({
resourceRegExp: /(fetch-cookie|node-fetch|tough-cookie)/
}),
new OptimizeCssAssetsPlugin({}),
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: path.join('Content', 'styles.css')
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html'
})
];
const config = {
mode: isProduction ? 'production' : 'development',
devtool: '#source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
}
},
output: {
path: distFolder,
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
},
plugins,
resolveLoader: {
modules: [
'node_modules',
'frontend/gulp/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
config: {
ctx: {
cssVarsFiles
},
path: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
gulp.task('webpack', () => {
return webpackStream(config)
.pipe(gulp.dest('_output/UI'));
});
gulp.task('webpackWatch', () => {
config.watch = true;
return webpackStream(config, webpack)
.on('error', errorHandler)
.pipe(gulp.dest('_output/UI'))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);
});

View File

@@ -1,23 +1,32 @@
const reload = require('require-nocache')(module);
module.exports = (ctx, configPath, options) => {
const config = {
plugins: {
'postcss-mixins': {
mixinsDir: [
'frontend/src/Styles/Mixins'
]
},
'postcss-simple-vars': {
variables: () =>
ctx.options.cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
},
'postcss-color-function': {},
'postcss-nested': {}
}
};
const cssVarsFiles = [
'./src/Styles/Variables/colors',
'./src/Styles/Variables/dimensions',
'./src/Styles/Variables/fonts',
'./src/Styles/Variables/animations',
'./src/Styles/Variables/zIndexes'
].map(require.resolve);
return config;
};
const mixinsFiles = [
'frontend/src/Styles/Mixins/cover.css',
'frontend/src/Styles/Mixins/linkOverlay.css',
'frontend/src/Styles/Mixins/scroller.css',
'frontend/src/Styles/Mixins/truncate.css'
];
module.exports = {
plugins: [
['postcss-mixins', {
mixinsFiles
}],
['postcss-simple-vars', {
variables: () =>
cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
}],
'postcss-color-function',
'postcss-nested'
]
};

View File

@@ -43,6 +43,7 @@ class QueueConnector extends Component {
const {
useCurrentPage,
fetchQueue,
fetchQueueStatus,
gotoQueueFirstPage
} = this.props;
@@ -53,6 +54,8 @@ class QueueConnector extends Component {
} else {
gotoQueueFirstPage();
}
fetchQueueStatus();
}
componentDidUpdate(prevProps) {
@@ -152,6 +155,7 @@ QueueConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchQueue: PropTypes.func.isRequired,
fetchQueueStatus: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired,
gotoQueueNextPage: PropTypes.func.isRequired,

View File

@@ -92,6 +92,19 @@
position: relative;
}
.certification {
margin-left: 2px;
padding: 0 5px;
border: 1px solid;
border-radius: 5px;
font-size: 16px;
}
.runtime {
margin-left: 8px;
font-size: 16px;
}
.statusContainer {
margin-right: 22px;
font-weight: bold;
@@ -103,10 +116,3 @@
overflow: hidden;
}
}
.certification {
margin-right: 5px;
padding: 0 5px;
border: 1px solid;
border-radius: 5px;
}

View File

@@ -174,7 +174,7 @@ class AddNewMovieSearchResult extends Component {
{
!!runtime &&
<span>
<span className={styles.runtime}>
{formatRuntime(runtime, movieRuntimeFormat)}
</span>
}

View File

@@ -16,7 +16,7 @@ function createTagListSelector() {
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
filterType !== filterTypes.EQUAL &&
filterType !== filterBuilderTypes.NOT_EQUAL ||
filterType !== filterTypes.NOT_EQUAL ||
!selectedFilterBuilderProp.optionsSelector
) {
return [];

View File

@@ -479,6 +479,7 @@ class EnhancedSelectInput extends Component {
<OptionComponent
key={v.key}
id={v.key}
dividerAfter={v.dividerAfter}
depth={depth}
isSelected={isSelectedItem(index, this.props)}
isDisabled={parentSelected}
@@ -539,6 +540,7 @@ class EnhancedSelectInput extends Component {
<OptionComponent
key={v.key}
id={v.key}
dividerAfter={v.dividerAfter}
depth={depth}
isSelected={isSelectedItem(index, this.props)}
isMultiSelect={isMultiSelect}

View File

@@ -13,6 +13,7 @@ import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
@@ -72,6 +73,9 @@ function getComponent(type) {
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInputConnector;
case inputTypes.SELECT:
return EnhancedSelectInput;

View File

@@ -21,3 +21,8 @@
color: $darkGray;
font-size: $smallFontSize;
}
.divider {
border: none;
border-bottom: 1px solid $lightGray;
}

View File

@@ -12,37 +12,46 @@ function HintedSelectInputOption(props) {
depth,
isSelected,
isDisabled,
dividerAfter,
isMultiSelect,
isMobile,
...otherProps
} = props;
return (
<EnhancedSelectInputOption
id={id}
depth={depth}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
isMultiSelect={isMultiSelect}
isMobile={isMobile}
{...otherProps}
>
<div className={classNames(
styles.optionText,
isMobile && styles.isMobile
)}
<div>
<EnhancedSelectInputOption
id={id}
depth={depth}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
isMultiSelect={isMultiSelect}
isMobile={isMobile}
{...otherProps}
>
<div>{value}</div>
<div className={classNames(
styles.optionText,
isMobile && styles.isMobile
)}
>
<div>{value}</div>
{
hint != null &&
<div className={styles.hintText}>
{hint}
</div>
}
</div>
</EnhancedSelectInputOption>
{
hint != null &&
<div className={styles.hintText}>
{hint}
</div>
}
</div>
</EnhancedSelectInputOption>
{
dividerAfter ?
<div className={styles.divider} /> :
null
}
</div>
);
}
@@ -50,15 +59,18 @@ HintedSelectInputOption.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.string.isRequired,
hint: PropTypes.node,
name: PropTypes.string,
depth: PropTypes.number,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
dividerAfter: PropTypes.bool.isRequired,
isMultiSelect: PropTypes.bool.isRequired,
isMobile: PropTypes.bool.isRequired
};
HintedSelectInputOption.defaultProps = {
isDisabled: false,
dividerAfter: false,
isHidden: false,
isMultiSelect: false
};

View File

@@ -0,0 +1,52 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state, { values }) => values,
( languages ) => {
const minId = languages.reduce((min, v) => (v.key < 1 ? v.key : min), languages[0].key);
const values = languages.map(({ key, value }) => {
return {
key,
value,
dividerAfter: minId < 1 ? key === minId : false
};
});
return {
values
};
}
);
}
class LanguageSelectInputConnector extends Component {
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.props.onChange}
/>
);
}
}
LanguageSelectInputConnector.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
onChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps)(LanguageSelectInputConnector);

View File

@@ -49,6 +49,7 @@ function getSelectValues(selectOptions) {
result.push({
key: option.value,
value: option.name,
dividerAfter: option.dividerAfter,
hint: option.hint
});

View File

@@ -38,11 +38,12 @@ class Link extends Component {
const linkProps = { target };
let el = component;
if (to) {
if (to && typeof to === 'string') {
if ((/\w+?:\/\//).test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
@@ -52,6 +53,18 @@ class Link extends Component {
linkProps.to = `${window.Radarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
} else if (to && typeof to === 'object') {
el = RouterLink;
linkProps.target = target;
if (to.pathname.startsWith(`${window.Radarr.urlBase}/`)) {
linkProps.to = to;
} else {
const pathname = `${window.Radarr.urlBase}/${to.pathname.replace(/^\//, '')}`;
linkProps.to = {
...to,
pathname
};
}
}
if (el === 'button' || el === 'input') {
@@ -82,7 +95,7 @@ class Link extends Component {
Link.propTypes = {
className: PropTypes.string,
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
to: PropTypes.string,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
target: PropTypes.string,
isDisabled: PropTypes.bool,
noRouter: PropTypes.bool,

View File

@@ -53,7 +53,13 @@ class PageHeader extends Component {
return (
<div className={styles.header}>
<div className={styles.logoContainer}>
<Link to={'/'}>
<Link
className={styles.logoLink}
to={{
pathname: '/',
state: { restoreScrollPosition: true }
}}
>
<img
className={isSmallScreen ? styles.logo : styles.logoFull}
src={isSmallScreen ? `${window.Radarr.urlBase}/Content/Images/logo.png` : `${window.Radarr.urlBase}/Content/Images/logo-full.png`}

View File

@@ -1,8 +1,8 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd-multi-backend';
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -128,7 +128,7 @@ class TableOptionsModal extends Component {
const isDraggingDown = isDragging && dropIndex > dragIndex;
return (
<DndProvider backend={HTML5Backend}>
<DndProvider options={HTML5toTouch}>
<Modal
isOpen={isOpen}
onModalClose={onModalClose}

View File

@@ -39,7 +39,8 @@ class VirtualTable extends Component {
super(props, context);
this.state = {
width: 0
width: 0,
scrollRestored: false
};
this._grid = null;
@@ -48,11 +49,13 @@ class VirtualTable extends Component {
componentDidUpdate(prevProps, prevState) {
const {
items,
scrollIndex
scrollIndex,
scrollTop
} = this.props;
const {
width
width,
scrollRestored
} = this.state;
if (this._grid &&
@@ -68,6 +71,11 @@ class VirtualTable extends Component {
columnIndex: 0
});
}
if (this._grid && scrollTop !== undefined && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
}
//
@@ -96,6 +104,7 @@ class VirtualTable extends Component {
items,
scroller,
focusScroller,
scrollTop: ignored,
header,
headerHeight,
rowRenderer,
@@ -180,6 +189,7 @@ VirtualTable.propTypes = {
className: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
scrollIndex: PropTypes.number,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,
focusScroller: PropTypes.bool.isRequired,
header: PropTypes.node.isRequired,

View File

@@ -8,7 +8,7 @@ function withScrollPosition(WrappedComponent, scrollPositionKey) {
history
} = props;
const scrollTop = history.action === 'POP' ?
const scrollTop = history.action === 'POP' || (history.location.state && history.location.state.restoreScrollPosition) ?
scrollPositions[scrollPositionKey] :
0;

View File

@@ -184,6 +184,7 @@ export const PAGE_LAST = fasFastForward;
export const PARENT = fasLevelUpAlt;
export const PAUSED = fasPause;
export const PENDING = farClock;
export const PLAY = fasPlay;
export const PROFILE = fasUser;
export const POSTER = fasTh;
export const QUEUED = fasCloud;

View File

@@ -3,6 +3,7 @@ export const AVAILABILITY_SELECT = 'availabilitySelect';
export const CAPTCHA = 'captcha';
export const CHECK = 'check';
export const DEVICE = 'device';
export const KEY_VALUE_LIST = 'keyValueList';
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
@@ -11,6 +12,7 @@ export const PATH = 'path';
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
@@ -26,6 +28,7 @@ export const all = [
CAPTCHA,
CHECK,
DEVICE,
KEY_VALUE_LIST,
MOVIE_MONITORED_SELECT,
NUMBER,
OAUTH,
@@ -34,6 +37,7 @@ export const all = [
QUALITY_PROFILE_SELECT,
ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT,
SELECT,
DYNAMIC_SELECT,
TAG,

View File

@@ -1,5 +1,6 @@
export const DANGER = 'danger';
export const DEFAULT = 'default';
export const DELETE = 'delete';
export const DISABLED = 'disabled';
export const INFO = 'info';
export const INVERSE = 'inverse';
@@ -13,6 +14,7 @@ export const QUEUE = 'queue';
export const all = [
DANGER,
DEFAULT,
DELETE,
DISABLED,
INFO,
INVERSE,

View File

@@ -22,6 +22,20 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'releaseWeight',
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, { name: icons.DANGER }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'title',
label: translate('Title'),
@@ -85,20 +99,6 @@ const columns = [
label: React.createElement(Icon, { name: icons.FLAG }),
isSortable: true,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, { name: icons.DANGER }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'releaseWeight',
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
}
];

View File

@@ -145,6 +145,46 @@ class InteractiveSearchRow extends Component {
{formatAge(age, ageHours, ageMinutes)}
</TableRowCell>
<TableRowCell className={styles.download}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
/>
</TableRowCell>
<TableRowCell className={styles.rejected}>
{
!!rejections.length &&
<Popover
anchor={
<Icon
name={icons.DANGER}
kind={kinds.DANGER}
/>
}
title={translate('ReleaseRejected')}
body={
<ul>
{
rejections.map((rejection, index) => {
return (
<li key={index}>
{rejection}
</li>
);
})
}
</ul>
}
position={tooltipPositions.BOTTOM}
/>
}
</TableRowCell>
<TableRowCell className={styles.title}>
<Link
to={infoUrl}
@@ -252,51 +292,11 @@ class InteractiveSearchRow extends Component {
}
</ul>
}
position={tooltipPositions.LEFT}
position={tooltipPositions.BOTTOM}
/>
}
</TableRowCell>
<TableRowCell className={styles.rejected}>
{
!!rejections.length &&
<Popover
anchor={
<Icon
name={icons.DANGER}
kind={kinds.DANGER}
/>
}
title={translate('ReleaseRejected')}
body={
<ul>
{
rejections.map((rejection, index) => {
return (
<li key={index}>
{rejection}
</li>
);
})
}
</ul>
}
position={tooltipPositions.LEFT}
/>
}
</TableRowCell>
<TableRowCell className={styles.download}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
/>
</TableRowCell>
<ConfirmModal
isOpen={this.state.isConfirmGrabModalOpen}
kind={kinds.WARNING}

View File

@@ -41,6 +41,19 @@ function MovieDetailsLinks(props) {
</Label>
</Link>
<Link
className={styles.link}
to={`https://letterboxd.com/tmdb/${tmdbId}`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{translate('Letterboxd')}
</Label>
</Link>
{
!!imdbId &&
<Link
@@ -61,7 +74,7 @@ function MovieDetailsLinks(props) {
!!imdbId &&
<Link
className={styles.link}
to={` https://moviechat.org/${imdbId}/`}
to={`https://moviechat.org/${imdbId}/`}
>
<Label
className={styles.linkLabel}
@@ -77,7 +90,7 @@ function MovieDetailsLinks(props) {
!!youTubeTrailerId &&
<Link
className={styles.link}
to={` https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
to={`https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
>
<Label
className={styles.linkLabel}

View File

@@ -60,7 +60,8 @@ class MovieIndexOverviews extends Component {
columnCount: 1,
posterWidth: 162,
posterHeight: 238,
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
scrollRestored: false
};
this._grid = null;
@@ -72,13 +73,15 @@ class MovieIndexOverviews extends Component {
sortKey,
overviewOptions,
jumpToCharacter,
scrollTop,
isMovieEditorActive,
isSmallScreen
} = this.props;
const {
width,
rowHeight
rowHeight,
scrollRestored
} = this.state;
if (prevProps.sortKey !== sortKey ||
@@ -97,6 +100,11 @@ class MovieIndexOverviews extends Component {
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
@@ -262,6 +270,7 @@ MovieIndexOverviews.propTypes = {
sortKey: PropTypes.string,
overviewOptions: PropTypes.object.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number.isRequired,
scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -104,7 +104,8 @@ class MovieIndexPosters extends Component {
columnCount: 1,
posterWidth: 162,
posterHeight: 238,
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
scrollRestored: false
};
this._isInitialized = false;
@@ -119,14 +120,16 @@ class MovieIndexPosters extends Component {
posterOptions,
jumpToCharacter,
isSmallScreen,
isMovieEditorActive
isMovieEditorActive,
scrollTop
} = this.props;
const {
width,
columnWidth,
columnCount,
rowHeight
rowHeight,
scrollRestored
} = this.state;
if (prevProps.sortKey !== sortKey ||
@@ -145,6 +148,11 @@ class MovieIndexPosters extends Component {
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
@@ -157,6 +165,10 @@ class MovieIndexPosters extends Component {
});
}
}
if (this._grid && scrollTop !== 0) {
this._grid.scrollToPosition({ scrollTop });
}
}
//
@@ -332,6 +344,7 @@ MovieIndexPosters.propTypes = {
sortKey: PropTypes.string,
posterOptions: PropTypes.object.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number.isRequired,
scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -87,6 +87,7 @@ class MovieIndexTable extends Component {
isSmallScreen,
onSortPress,
scroller,
scrollTop,
allSelected,
allUnselected,
onSelectAllChange,
@@ -100,6 +101,7 @@ class MovieIndexTable extends Component {
items={items}
scrollIndex={this.state.scrollIndex}
isSmallScreen={isSmallScreen}
scrollTop={scrollTop}
scroller={scroller}
rowHeight={38}
overscanRowCount={2}
@@ -130,6 +132,7 @@ MovieIndexTable.propTypes = {
sortDirection: PropTypes.oneOf(sortDirections.all),
jumpToCharacter: PropTypes.string,
isSmallScreen: PropTypes.bool.isRequired,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,
onSortPress: PropTypes.func.isRequired,
allSelected: PropTypes.bool.isRequired,

View File

@@ -155,7 +155,7 @@ class FileEditModalContent extends Component {
<FormLabel>{translate('Languages')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
type={inputTypes.LANGUAGE_SELECT}
name="languageIds"
value={languageIds}
values={languageOptions}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -47,7 +48,8 @@ function EditImportListModalContent(props) {
rootFolderPath,
searchOnAdd,
tags,
fields
fields,
message
} = item;
return (
@@ -74,6 +76,15 @@ function EditImportListModalContent(props) {
<Form
{...otherProps}
>
{
!!message &&
<Alert
className={styles.message}
kind={message.value.type}
>
{message.value.message}
</Alert>
}
<FormGroup>
<FormLabel>{translate('Name')}</FormLabel>

View File

@@ -40,6 +40,7 @@ function EditDelayProfileModalContent(props) {
enableTorrent,
usenetDelay,
torrentDelay,
bypassIfHighestQuality,
tags
} = item;
@@ -110,6 +111,20 @@ function EditDelayProfileModalContent(props) {
</FormGroup>
}
{
<FormGroup>
<FormLabel>{translate('BypassDelayIfHighestQuality')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="bypassIfHighestQuality"
{...bypassIfHighestQuality}
helpText={translate('BypassDelayIfHighestQualityHelpText')}
onChange={onInputChange}
/>
</FormGroup>
}
{
id === 1 ?
<Alert>

View File

@@ -254,7 +254,7 @@ class EditQualityProfileModalContent extends Component {
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
type={inputTypes.LANGUAGE_SELECT}
name="language"
values={languages}
value={languageId}

View File

@@ -96,12 +96,13 @@ function createLanguagesSelector() {
(state) => state.settings.languages,
(languages) => {
const items = languages.items;
const filterItems = ['Unknown'];
if (!items) {
return [];
}
const newItems = items.map((item) => {
const newItems = items.filter((lang) => !filterItems.includes(lang.name)).map((item) => {
return {
key: item.id,
value: item.name

View File

@@ -104,7 +104,7 @@ class QualityProfile extends Component {
return (
<Label
key={item.quality.id}
kind={isCutoff ? kinds.INFO : kinds.default}
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
title={isCutoff ? translate('UpgradeUntilThisQualityIsMetOrExceeded') : null}
>
{item.quality.name}
@@ -120,7 +120,7 @@ class QualityProfile extends Component {
className={styles.tooltipLabel}
anchor={
<Label
kind={isCutoff ? kinds.INFO : kinds.default}
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
title={isCutoff ? translate('Cutoff') : null}
>
{item.name}
@@ -133,7 +133,7 @@ class QualityProfile extends Component {
return (
<Label
key={groupItem.quality.id}
kind={isCutoff ? kinds.INFO : kinds.default}
kind={isCutoff ? kinds.INFO : kinds.DEFAULT}
title={isCutoff ? translate('Cutoff') : null}
>
{groupItem.quality.name}

View File

@@ -200,7 +200,7 @@ class UISettings extends Component {
<FormGroup>
<FormLabel>{translate('MovieInfoLanguage')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
type={inputTypes.LANGUAGE_SELECT}
name="movieInfoLanguage"
values={languages}
helpText={translate('MovieInfoLanguageHelpText')}
@@ -213,7 +213,7 @@ class UISettings extends Component {
<FormGroup>
<FormLabel>{translate('UILanguage')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
type={inputTypes.LANGUAGE_SELECT}
name="uiLanguage"
values={uiLanguages}
helpText={translate('UILanguageHelpText')}

View File

@@ -6,7 +6,7 @@
}
@define-mixin scrollbarTrack {
&&::-webkit-scrollbar-track {
&::-webkit-scrollbar-track {
background-color: transparent;
}
}

View File

@@ -42,7 +42,7 @@ function getInternalLink(source) {
<IconButton
name={icons.PLAY}
title={translate('MovieEditor')}
to="/movieeditor"
to="/"
/>
);
case 'UpdateCheck':

View File

@@ -10,6 +10,15 @@
width: 100%;
}
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}
.queued,
.started,
.ended {

View File

@@ -41,7 +41,7 @@ function getStatusIconProps(status, message) {
case 'failed':
return {
name: icons.FATAL,
kind: kinds.ERROR,
kind: kinds.DANGER,
title: `${title}: ${message}`
};
@@ -157,6 +157,7 @@ class QueuedTaskRow extends Component {
status,
duration,
message,
clientUserAgent,
longDateFormat,
timeFormat,
onCancelPress
@@ -192,7 +193,18 @@ class QueuedTaskRow extends Component {
</span>
</TableRowCell>
<TableRowCell>{commandName}</TableRowCell>
<TableRowCell>
<span className={styles.commandName}>
{commandName}
</span>
{
clientUserAgent ?
<span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
{translate('from')}: {clientUserAgent}
</span> :
null
}
</TableRowCell>
<TableRowCell
className={styles.queued}
@@ -256,6 +268,7 @@ QueuedTaskRow.propTypes = {
status: PropTypes.string.isRequired,
duration: PropTypes.string,
message: PropTypes.string,
clientUserAgent: PropTypes.string,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,

View File

@@ -226,6 +226,7 @@
required
title="User name is required"
autoFocus="true"
autoCapitalize="false"
/>
</div>

View File

@@ -3,10 +3,11 @@
"version": "1.0.0",
"description": "Radarr is a PVR for Usenet and BitTorrent users",
"scripts": {
"build": "gulp build",
"start": "gulp watch",
"watch": "gulp watch",
"clean": "git clean -fXd",
"build": "webpack --config ./frontend/build/webpack.config.js",
"prebuild": "yarn clean",
"clean": "rimraf ./_output/UI && rimraf \"**/*.js.map\"",
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "esprint check",
"lint-fix": "esprint check --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
@@ -16,123 +17,120 @@
"author": "Team Radarr",
"license": "GPL-3.0",
"readmeFilename": "readme.md",
"dependencies": {
"@babel/core": "7.11.6",
"@babel/plugin-proposal-class-properties": "7.10.4",
"@babel/plugin-proposal-decorators": "7.10.5",
"@babel/plugin-proposal-export-default-from": "7.10.4",
"@babel/plugin-proposal-export-namespace-from": "7.10.4",
"@babel/plugin-proposal-function-sent": "7.10.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.10.4",
"@babel/plugin-proposal-numeric-separator": "7.10.4",
"@babel/plugin-proposal-optional-chaining": "7.11.0",
"@babel/plugin-proposal-throw-expressions": "7.10.4",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.11.5",
"@babel/preset-react": "7.10.4",
"@fortawesome/fontawesome-free": "5.15.0",
"@fortawesome/fontawesome-svg-core": "1.2.31",
"@fortawesome/free-regular-svg-icons": "5.15.0",
"@fortawesome/free-solid-svg-icons": "5.15.0",
"@fortawesome/react-fontawesome": "0.1.11",
"@microsoft/signalr": "5.0.5",
"@sentry/browser": "5.29.2",
"@sentry/integrations": "5.29.2",
"ansi-colors": "4.1.1",
"autoprefixer": "9.7.5",
"babel-eslint": "10.1.0",
"babel-loader": "8.1.0",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"classnames": "2.2.6",
"clipboard": "2.0.6",
"connected-react-router": "6.8.0",
"core-js": "3.6.5",
"css-loader": "3.4.2",
"del": "6.0.0",
"element-class": "0.2.2",
"eslint": "7.10.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.22.0",
"eslint-plugin-json": "2.1.2",
"eslint-plugin-react": "7.21.3",
"eslint-plugin-simple-import-sort": "5.0.3",
"esprint": "0.7.0",
"file-loader": "6.1.0",
"filesize": "6.1.0",
"fuse.js": "6.4.1",
"gulp": "4.0.2",
"gulp-cached": "1.1.1",
"gulp-concat": "2.6.1",
"gulp-livereload": "4.0.2",
"gulp-postcss": "8.0.0",
"gulp-print": "5.0.2",
"gulp-sourcemaps": "2.6.5",
"gulp-watch": "5.0.1",
"gulp-wrap": "0.15.0",
"history": "4.10.1",
"html-webpack-plugin": "4.5.0",
"jdu": "1.0.0",
"jquery": "3.5.1",
"loader-utils": "^2.0.0",
"lodash": "4.17.20",
"mini-css-extract-plugin": "0.9.0",
"mobile-detect": "1.4.4",
"moment": "2.29.0",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"optimize-css-assets-webpack-plugin": "5.0.3",
"postcss-color-function": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-mixins": "6.2.3",
"postcss-nested": "4.2.1",
"postcss-simple-vars": "5.0.2",
"postcss-url": "8.0.0",
"prop-types": "15.7.2",
"qs": "6.9.4",
"react": "16.13.1",
"react-addons-shallow-compare": "15.6.2",
"react-async-script": "1.2.0",
"react-autosuggest": "10.0.2",
"react-custom-scrollbars": "4.2.1",
"react-dnd": "11.1.3",
"react-dnd-html5-backend": "11.1.3",
"react-document-title": "2.0.3",
"react-dom": "16.13.1",
"react-focus-lock": "2.4.1",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.0.0",
"react-measure": "1.4.7",
"react-popper": "1.3.7",
"react-redux": "7.2.1",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-slider": "1.0.11",
"react-tabs": "3.1.1",
"react-text-truncate": "0.16.0",
"react-virtualized": "9.21.1",
"redux": "4.0.5",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0",
"require-nocache": "1.0.0",
"reselect": "4.0.0",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "1.2.1",
"stylelint": "13.7.2",
"stylelint-order": "4.1.0",
"url-loader": "4.1.0",
"webpack": "4.44.2",
"webpack-stream": "6.1.0",
"worker-loader": "3.0.3"
},
"main": "index.js",
"browserslist": [
">0.25%",
"not ie 11",
"not op_mini all",
"not chrome < 60"
]
],
"dependencies": {
"@fortawesome/fontawesome-free": "5.15.3",
"@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "5.0.5",
"@sentry/browser": "6.3.1",
"@sentry/integrations": "6.3.1",
"classnames": "2.3.1",
"clipboard": "2.0.8",
"connected-react-router": "6.9.1",
"element-class": "0.2.2",
"filesize": "6.3.0",
"fuse.js": "6.4.6",
"history": "4.10.1",
"https-browserify": "1.0.0",
"jdu": "1.0.0",
"jquery": "3.6.0",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.1",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.7.2",
"qs": "6.10.1",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
"react-autosuggest": "10.1.0",
"react-custom-scrollbars": "4.2.1",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0",
"react-dnd-multi-backend": "6.0.2",
"react-dnd-touch-backend": "14.0.0",
"react-document-title": "2.0.3",
"react-dom": "17.0.2",
"react-focus-lock": "2.5.0",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",
"react-measure": "1.4.7",
"react-popper": "1.3.7",
"react-redux": "7.2.4",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-slider": "1.1.4",
"react-tabs": "3.2.2",
"react-text-truncate": "0.16.0",
"react-virtualized": "9.21.1",
"redux": "4.1.0",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0",
"reselect": "4.0.0"
},
"devDependencies": {
"@babel/core": "7.13.16",
"@babel/plugin-proposal-class-properties": "7.13.0",
"@babel/plugin-proposal-decorators": "7.13.15",
"@babel/plugin-proposal-export-default-from": "7.12.13",
"@babel/plugin-proposal-export-namespace-from": "7.12.13",
"@babel/plugin-proposal-function-sent": "7.12.13",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.13.8",
"@babel/plugin-proposal-numeric-separator": "7.12.13",
"@babel/plugin-proposal-optional-chaining": "7.13.12",
"@babel/plugin-proposal-throw-expressions": "7.12.13",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.13.15",
"@babel/preset-react": "7.13.13",
"@babel/eslint-parser": "7.13.14",
"autoprefixer": "10.2.5",
"babel-loader": "8.2.2",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"copy-webpack-plugin": "8.1.1",
"core-js": "3.11.0",
"css-loader": "5.2.4",
"eslint": "7.25.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-json": "3.0.0",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "2.0.0",
"file-loader": "6.2.0",
"html-webpack-plugin": "5.3.1",
"loader-utils": "^2.0.0",
"mini-css-extract-plugin": "1.5.0",
"postcss": "8.2.12",
"postcss-color-function": "4.1.0",
"postcss-loader": "5.2.0",
"postcss-mixins": "7.0.3",
"postcss-nested": "5.0.5",
"postcss-simple-vars": "6.0.3",
"postcss-url": "10.1.3",
"require-nocache": "1.0.0",
"rimraf": "3.0.2",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "2.0.0",
"url-loader": "4.1.1",
"webpack": "5.35.1",
"webpack-cli": "4.6.0",
"webpack-livereload-plugin": "3.0.1",
"worker-loader": "3.0.8",
"stylelint": "13.13.0",
"stylelint-order": "4.1.0"
}
}

View File

@@ -97,7 +97,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net5.0'">
<PackageReference Include="coverlet.collector" Version="1.2.1" PrivateAssets="all" />
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
</ItemGroup>
<!-- Allow building net framework using mono -->

View File

@@ -7,5 +7,6 @@
<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="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using NzbDrone.Common.Http;
using NzbDrone.Core.Messaging.Commands;
using Radarr.Http.REST;
@@ -21,6 +22,8 @@ namespace NzbDrone.Api.Commands
public string Exception { get; set; }
public CommandTrigger Trigger { get; set; }
public string ClientUserAgent { get; set; }
[JsonIgnore]
public string CompletionMessage { get; set; }
@@ -125,6 +128,8 @@ namespace NzbDrone.Api.Commands
Exception = model.Exception,
Trigger = model.Trigger,
ClientUserAgent = UserAgentParser.SimplifyUserAgent(model.Body.ClientUserAgent),
CompletionMessage = model.Body.CompletionMessage,
LastExecutionTime = model.Body.LastExecutionTime
};

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Api.Profiles.Delay
public DownloadProtocol PreferredProtocol { get; set; }
public int UsenetDelay { get; set; }
public int TorrentDelay { get; set; }
public bool BypassIfHighestQuality { get; set; }
public int Order { get; set; }
public HashSet<int> Tags { get; set; }
}
@@ -35,6 +36,7 @@ namespace NzbDrone.Api.Profiles.Delay
PreferredProtocol = model.PreferredProtocol,
UsenetDelay = model.UsenetDelay,
TorrentDelay = model.TorrentDelay,
BypassIfHighestQuality = model.BypassIfHighestQuality,
Order = model.Order,
Tags = new HashSet<int>(model.Tags)
};
@@ -56,6 +58,7 @@ namespace NzbDrone.Api.Profiles.Delay
PreferredProtocol = resource.PreferredProtocol,
UsenetDelay = resource.UsenetDelay,
TorrentDelay = resource.TorrentDelay,
BypassIfHighestQuality = resource.BypassIfHighestQuality,
Order = resource.Order,
Tags = new HashSet<int>(resource.Tags)
};

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Api.Queue
throw new BadRequestException();
}
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
downloadClient.RemoveItem(trackedDownload.DownloadItem, true);
if (blacklist)
{

View File

@@ -1,428 +0,0 @@
{
"openapi": "3.0.0",
"info": {
"title": "Radarr",
"description": "Movie Automation",
"contact": {
"url": "https://radarr.video"
},
"version": "0.2.0"
},
"servers": [
{
"url": "{protocol}://{hostPath}/api",
"variables": {
"protocol": {
"enum": [
"https",
"http"
],
"default": "https"
},
"hostPath": {
"default": "localhost:7878",
"description": "Your Radarr Server URL"
}
}
}
],
"paths": {
"/movie": {
"get": {
"tags": [
"Movie"
],
"summary": "Get all movies",
"description": "Returns all movies",
"operationId": "getMovie",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Movie"
}
}
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Movie not found"
}
},
"security": [
{
"api_key": []
}
]
},
"post": {
"tags": [
"Movie"
],
"summary": "Add new movie",
"requestBody": {
"description": "Movie object that needs to be added",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Movie"
}
}
}
},
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"type": "integer"
}
}
}
},
"405": {
"description": "Validation exception"
}
},
"security": [
{
"api_key": []
}
]
},
"put": {
"tags": [
"Movie"
],
"summary": "Edit existing movie",
"requestBody": {
"description": "Movie object that needs to be edited",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Movie"
}
}
}
},
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Movie"
}
}
}
},
"404": {
"description": "Movie not found"
},
"405": {
"description": "Validation exception"
}
},
"security": [
{
"api_key": []
}
]
}
},
"/movie/{movieId}": {
"get": {
"tags": [
"Movie"
],
"summary": "Get movie by ID",
"description": "Returns a single movie",
"operationId": "getMovieById",
"parameters": [
{
"name": "movieId",
"in": "path",
"description": "ID of movie to return",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Movie"
}
}
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Movie not found"
}
},
"security": [
{
"api_key": []
}
]
},
"delete": {
"tags": [
"Movie"
],
"summary": "Deletes a Movie",
"description": "",
"operationId": "deleteMovie",
"parameters": [
{
"name": "movieId",
"in": "path",
"description": "Movie id to delete",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "addImportExclusion",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "deleteFiles",
"in": "query",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Movie not found"
}
},
"security": [
{
"api_key": []
}
]
}
}
},
"components": {
"schemas": {
"Movie": {
"type": "object",
"required": [
"title"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"title": {
"type": "string",
"example": "Dark Phoenix"
},
"sortTitle": {
"type": "string",
"example": "dark phoenix"
},
"sizeOnDisk": {
"type": "number"
},
"overview": {
"type": "string"
},
"inCinemas": {
"type": "string"
},
"physicalRelease": {
"type": "string"
},
"images": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Image"
}
},
"website": {
"type": "string",
"example": "http://darkphoenix.com"
},
"year": {
"type": "integer"
},
"hasFile": {
"type": "boolean"
},
"youTubeTrailerId": {
"type": "string"
},
"studio": {
"type": "string"
},
"path": {
"type": "string"
},
"qualityProfileId": {
"type": "integer"
},
"monitored": {
"type": "boolean"
},
"minimumAcailability": {
"type": "string",
"enum": [
"announced",
"inCinema",
"released"
]
},
"isAvailable": {
"type": "boolean"
},
"folderName": {
"type": "string"
},
"runtime": {
"type": "integer"
},
"cleanTitle": {
"type": "string"
},
"imdbId": {
"type": "string"
},
"tmdbId": {
"type": "integer"
},
"titleSlug": {
"type": "integer"
},
"certification": {
"type": "string"
},
"genres": {
"type": "array",
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"items": {
"type": "integer"
}
},
"added": {
"type": "string"
},
"ratings": {
"$ref": "#/components/schemas/Rating"
},
"collection": {
"$ref": "#/components/schemas/Collection"
},
"status": {
"type": "string",
"description": "movie status",
"enum": [
"deleted",
"tba",
"announced",
"inCinema",
"released"
]
}
},
"xml": {
"name": "Movie"
}
},
"Image": {
"type": "object",
"properties": {
"coverType": {
"type": "string",
"enum": [
"poster",
"fanart"
]
},
"url": {
"type": "string"
}
},
"xml": {
"name": "Image"
}
},
"Collection": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"tmdbId": {
"type": "integer"
},
"images": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Image"
}
}
},
"xml": {
"name": "Collection"
}
},
"Rating": {
"type": "object",
"properties": {
"votes": {
"type": "integer"
},
"value": {
"type": "integer"
}
},
"xml": {
"name": "Rating"
}
}
},
"securitySchemes": {
"api_key": {
"type": "apiKey",
"in": "query",
"name": "apiKey"
}
}
},
"externalDocs": {
"description": "GitHub",
"url": "https://github.com/Radarr/Radarr"
}
}

View File

@@ -61,7 +61,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"OutputPath=/home/mySecret/Downloads")]
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("https://discordnotifier.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NzbDrone.Common.Http
{
public static class UserAgentParser
{
public static string SimplifyUserAgent(string userAgent)
{
if (userAgent == null || userAgent.StartsWith("Mozilla/5.0"))
{
return null;
}
return userAgent;
}
}
}

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
{
public static class NzbDroneLogger
{
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private static bool _isConfigured;

View File

@@ -104,10 +104,22 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
}
[Test]
public void should_be_true_when_quality_is_last_allowed_in_profile()
public void should_be_false_when_quality_is_last_allowed_in_profile_and_bypass_disabled()
{
_remoteMovie.ParsedMovieInfo.Quality = new QualityModel(Quality.Bluray720p);
_remoteMovie.Release.PublishDate = DateTime.UtcNow;
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
}
[Test]
public void should_be_true_when_quality_is_last_allowed_in_profile_and_bypass_enabled()
{
_delayProfile.BypassIfHighestQuality = true;
_remoteMovie.ParsedMovieInfo.Quality = new QualityModel(Quality.Bluray720p);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
}

View File

@@ -87,5 +87,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p);
_upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
}
[Test]
public void should_not_be_upgradable_if_revision_downgrade_if_propers_are_preferred()
{
_firstFile.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2));
_parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p);
_upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -25,6 +26,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
protected string _blackholeFolder;
protected string _filePath;
protected string _magnetFilePath;
protected DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
@@ -34,6 +36,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
_filePath = (@"c:\blackhole\torrent\" + _title + ".torrent").AsOsAgnostic();
_magnetFilePath = Path.ChangeExtension(_filePath, ".magnet");
_downloadClientItem = Builder<DownloadClientItem>
.CreateNew()
.With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
.With(d => d.OutputPath = new OsPath(Path.Combine(_completedDownloadFolder, _title)))
.Build();
Mocker.SetConstant<IScanWatchFolder>(Mocker.Resolve<ScanWatchFolder>());
Subject.Definition = new DownloadClientDefinition();
@@ -246,7 +254,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Setup(c => c.FileExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Once());
@@ -261,7 +269,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Once());
@@ -270,7 +278,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test]
public void RemoveItem_should_ignore_if_unknown_item()
{
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());
@@ -284,7 +292,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{
GivenCompletedItem();
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", false));
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(_downloadClientItem, false));
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -21,6 +22,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
protected string _completedDownloadFolder;
protected string _blackholeFolder;
protected string _filePath;
protected DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
@@ -29,6 +31,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
_blackholeFolder = @"c:\blackhole\nzb".AsOsAgnostic();
_filePath = (@"c:\blackhole\nzb\" + _title + ".nzb").AsOsAgnostic();
_downloadClientItem = Builder<DownloadClientItem>
.CreateNew()
.With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
.With(d => d.OutputPath = new OsPath(Path.Combine(_completedDownloadFolder, _title)))
.Build();
Mocker.SetConstant<IScanWatchFolder>(Mocker.Resolve<ScanWatchFolder>());
Subject.Definition = new DownloadClientDefinition();
@@ -143,7 +151,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Setup(c => c.FileExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Once());
@@ -158,7 +166,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
.Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Once());
@@ -167,7 +175,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
[Test]
public void RemoveItem_should_ignore_if_unknown_item()
{
Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());
@@ -181,7 +189,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{
GivenCompletedItem();
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem("_Droned.1998.1080p.WEB-DL-DRONE_0", false));
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(_downloadClientItem, false));
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -20,6 +21,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
private NzbgetHistoryItem _failed;
private NzbgetHistoryItem _completed;
private Dictionary<string, string> _configItems;
private DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
@@ -74,6 +76,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
MarkStatus = "NONE"
};
_downloadClientItem = Builder<DownloadClientItem>
.CreateNew()
.With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
.With(d => d.OutputPath = new OsPath("/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()))
.Build();
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetGlobalStatus(It.IsAny<NzbgetSettings>()))
.Returns(new NzbgetGlobalStatus
@@ -155,7 +163,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
.Setup(v => v.FolderExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("id", true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Once());

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Net;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
@@ -21,6 +22,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
private string _strmFolder;
private string _nzbPath;
private RemoteMovie _remoteMovie;
private DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
@@ -37,6 +39,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
_remoteMovie.ParsedMovieInfo = new ParsedMovieInfo();
_downloadClientItem = Builder<DownloadClientItem>
.CreateNew().With(d => d.DownloadId = "_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0")
.Build();
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new PneumaticSettings
{
@@ -69,7 +75,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
[Test]
public void should_throw_item_is_removed()
{
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem("", true));
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(_downloadClientItem, true));
}
[Test]

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
private SabnzbdHistory _completed;
private SabnzbdConfig _config;
private SabnzbdFullStatus _fullStatus;
private DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
@@ -99,6 +101,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
}
};
_downloadClientItem = Builder<DownloadClientItem>
.CreateNew()
.With(d => d.Status = DownloadItemStatus.Completed)
.With(d => d.DownloadId = _completed.Items.First().Id)
.Build();
Mocker.GetMock<ISabnzbdProxy>()
.Setup(v => v.GetVersion(It.IsAny<SabnzbdSettings>()))
.Returns("1.2.3");
@@ -582,6 +590,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
public void should_remove_output_path_folder_when_deleting_a_completed_item_and_delete_data_is_true()
{
var path = @"C:\Test\Series.Title.S01E01".AsOsAgnostic();
_downloadClientItem.OutputPath = new OsPath(path);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(path))
@@ -592,7 +601,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
GivenQueue(null);
GivenHistory(_completed);
Subject.RemoveItem(_completed.Items.First().Id, true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(path, true), Times.Once);
@@ -605,6 +614,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
public void should_remove_output_path_file_when_deleting_a_completed_item_and_delete_data_is_true()
{
var path = @"C:\Test\Series.Title.S01E01.mkv".AsOsAgnostic();
_downloadClientItem.OutputPath = new OsPath(path);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(path))
@@ -619,7 +629,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
GivenQueue(null);
GivenHistory(_completed);
Subject.RemoveItem(_completed.Items.First().Id, true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(path, true), Times.Never);
@@ -646,7 +656,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
GivenQueue(null);
GivenHistory(_completed);
Subject.RemoveItem(_completed.Items.First().Id, true);
Subject.RemoveItem(_downloadClientItem, true);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(path, true), Times.Never);
@@ -673,7 +683,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
GivenQueue(null);
GivenHistory(_completed);
Subject.RemoveItem(_completed.Items.First().Id, false);
Subject.RemoveItem(_downloadClientItem, false);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.FolderExists(path), Times.Never);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -7,6 +8,7 @@ using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@@ -83,5 +85,123 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
trackedDownload.RemoteMovie.Movie.Should().NotBeNull();
trackedDownload.RemoteMovie.Movie.Id.Should().Be(3);
}
[Test]
public void should_unmap_tracked_download_if_movie_deleted()
{
GivenDownloadHistory();
var remoteMovie = new RemoteMovie
{
Movie = new Movie() { Id = 3 },
ParsedMovieInfo = new ParsedMovieInfo()
{
MovieTitle = "A Movie",
Year = 1998
}
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
.Returns(new MappingResult { RemoteMovie = remoteMovie });
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<MovieHistory>());
ParseMovieTitle();
var client = new DownloadClientDefinition()
{
Id = 1,
Protocol = DownloadProtocol.Torrent
};
var item = new DownloadClientItem()
{
Title = "A Movie 1998",
DownloadId = "12345",
DownloadClientInfo = new DownloadClientItemClientInfo
{
Id = 1,
Type = "Blackhole",
Name = "Blackhole Client",
Protocol = DownloadProtocol.Torrent
}
};
Subject.TrackDownload(client, item);
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
.Returns(new MappingResult { MappingResultType = MappingResultType.Unknown });
Subject.Handle(new MoviesDeletedEvent(new List<Movie> { remoteMovie.Movie }, false, false));
var trackedDownloads = Subject.GetTrackedDownloads();
trackedDownloads.Should().HaveCount(1);
trackedDownloads.First().RemoteMovie.Should().BeNull();
}
[Test]
public void should_not_throw_when_processing_deleted_movie()
{
GivenDownloadHistory();
var remoteMovie = new RemoteMovie
{
Movie = new Movie() { Id = 3 },
ParsedMovieInfo = new ParsedMovieInfo()
{
MovieTitle = "A Movie",
Year = 1998
}
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
.Returns(new MappingResult { MappingResultType = MappingResultType.Unknown });
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<MovieHistory>());
ParseMovieTitle();
var client = new DownloadClientDefinition()
{
Id = 1,
Protocol = DownloadProtocol.Torrent
};
var item = new DownloadClientItem()
{
Title = "A Movie 1998",
DownloadId = "12345",
DownloadClientInfo = new DownloadClientItemClientInfo
{
Id = 1,
Type = "Blackhole",
Name = "Blackhole Client",
Protocol = DownloadProtocol.Torrent
}
};
Subject.TrackDownload(client, item);
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), null))
.Returns(new MappingResult { MappingResultType = MappingResultType.Unknown });
Subject.Handle(new MoviesDeletedEvent(new List<Movie> { remoteMovie.Movie }, false, false));
var trackedDownloads = Subject.GetTrackedDownloads();
trackedDownloads.Should().HaveCount(1);
trackedDownloads.First().RemoteMovie.Should().BeNull();
}
}
}

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Processes;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
@@ -9,6 +11,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[TestFixture]
public class MonoNotNetCoreCheckFixture : CoreTest<MonoNotNetCoreCheck>
{
[SetUp]
public void setup()
{
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
}
[Test]
[Platform(Exclude = "Mono")]
public void should_return_ok_if_net_core()
@@ -18,14 +28,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
[Platform("Mono")]
public void should_log_warning_if_mono()
public void should_log_error_if_mono()
{
Subject.Check().ShouldBeWarning();
Subject.Check().ShouldBeError();
}
[Test]
[Platform("Mono")]
public void should_return_ok_if_otherbsd()
public void should_return_error_if_otherbsd()
{
Mocker.GetMock<IProcessProvider>()
.Setup(x => x.StartAndCapture("uname", null, null))
@@ -36,12 +46,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
new ProcessOutputLine(ProcessOutputLevel.Standard, "OpenBSD")
}
});
Subject.Check().ShouldBeOk();
Subject.Check().ShouldBeError();
}
[Test]
[Platform("Mono")]
public void should_log_warning_if_freebsd()
public void should_log_error_if_freebsd()
{
Mocker.GetMock<IProcessProvider>()
.Setup(x => x.StartAndCapture("uname", null, null))
@@ -52,7 +62,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
new ProcessOutputLine(ProcessOutputLevel.Standard, "FreeBSD")
}
});
Subject.Check().ShouldBeWarning();
Subject.Check().ShouldBeError();
}
}
}

View File

@@ -10,6 +10,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Parser.Model;
@@ -84,6 +85,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Ensure.That(path, () => path).IsValidPath();
return false;
});
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
}
private void GivenFolderExists(string folder)

View File

@@ -10,6 +10,9 @@ namespace NzbDrone.Core.Test.Languages
{
public static object[] FromIntCases =
{
new object[] { -2, Language.Original },
new object[] { -1, Language.Any },
new object[] { 0, Language.Unknown },
new object[] { 1, Language.English },
new object[] { 2, Language.French },
new object[] { 3, Language.Spanish },
@@ -45,6 +48,9 @@ namespace NzbDrone.Core.Test.Languages
public static object[] ToIntCases =
{
new object[] { Language.Original, -2 },
new object[] { Language.Any, -1 },
new object[] { Language.Unknown, 0 },
new object[] { Language.English, 1 },
new object[] { Language.French, 2 },
new object[] { Language.Spanish, 3 },

View File

@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("The Danish Movie 2015")]
[TestCase("Movie.Title.2018.2160p.WEBRip.x265.10bit.HDR.DD5.1-GASMASK")]
[TestCase("Movie.Title.2010.720p.BluRay.x264.-[YTS.LT]")]
public void should_parse_language_unknown(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);

View File

@@ -27,6 +27,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
private ParsedMovieInfo _translationTitleInfo;
private ParsedMovieInfo _umlautInfo;
private ParsedMovieInfo _umlautAltInfo;
private ParsedMovieInfo _multiLanguageInfo;
private ParsedMovieInfo _multiLanguageWithOriginalInfo;
private MovieSearchCriteria _movieSearchCriteria;
[SetUp]
@@ -97,6 +99,18 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Year = _movie.Year
};
_multiLanguageInfo = new ParsedMovieInfo
{
MovieTitle = _movie.Title,
Languages = new List<Language> { Language.Original, Language.French }
};
_multiLanguageWithOriginalInfo = new ParsedMovieInfo
{
MovieTitle = _movie.Title,
Languages = new List<Language> { Language.Original, Language.French, Language.English }
};
_movieSearchCriteria = new MovieSearchCriteria
{
Movie = _movie
@@ -180,5 +194,20 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Subject.Map(_umlautInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
Subject.Map(_umlautAltInfo, "", _movieSearchCriteria).Movie.Should().Be(_movieSearchCriteria.Movie);
}
[Test]
public void should_convert_original()
{
Subject.Map(_multiLanguageInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.English);
Subject.Map(_multiLanguageInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.French);
}
[Test]
public void should_remove_original_as_already_exists()
{
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.English);
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().Contain(Language.French);
Subject.Map(_multiLanguageWithOriginalInfo, "", _movieSearchCriteria).RemoteMovie.ParsedMovieInfo.Languages.Should().NotContain(Language.Original);
}
}
}

View File

@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("SomeMovie.1080p.BluRay.DTS.x264.-FTW-HS.mkv", "FTW-HS")]
[TestCase("SomeMovie.1080p.BluRay.DTS.x264.-VH-PROD.mkv", "VH-PROD")]
[TestCase("Some.Dead.Movie.2006.1080p.BluRay.DTS.x264.D-Z0N3", "D-Z0N3")]
[TestCase("Movie.Title.2010.720p.BluRay.x264.-[YTS.LT]", "YTS.LT")]
//[TestCase("", "")]

View File

@@ -6,5 +6,6 @@ namespace NzbDrone.Core.Annotations
public string Name { get; set; }
public int Order { get; set; }
public string Hint { get; set; }
public bool DividerAfter { get; set; }
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Linq;
using System.Xml.Linq;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
@@ -24,12 +24,17 @@ namespace NzbDrone.Core.Authentication
private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider;
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
public UserService(IUserRepository repo,
IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
{
_repo = repo;
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
_configFileProvider = configFileProvider;
}
public User Add(string username, string password)
@@ -105,14 +110,7 @@ namespace NzbDrone.Core.Authentication
return;
}
var configFile = _appFolderInfo.GetConfigPath();
if (!_diskProvider.FileExists(configFile))
{
return;
}
var xDoc = XDocument.Load(configFile);
var xDoc = _configFileProvider.LoadConfigFile();
var config = xDoc.Descendants("Config").Single();
var usernameElement = config.Descendants("Username").FirstOrDefault();
var passwordElement = config.Descendants("Password").FirstOrDefault();

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Core.Configuration
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
IExecute<ResetApiKeyCommand>
{
XDocument LoadConfigFile();
Dictionary<string, object> GetConfigDictionary();
void SaveConfigDictionary(Dictionary<string, object> configValues);
@@ -310,7 +311,7 @@ namespace NzbDrone.Core.Configuration
SaveConfigFile(xDoc);
}
private XDocument LoadConfigFile()
public XDocument LoadConfigFile()
{
try
{

View File

@@ -0,0 +1,17 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(194)]
public class add_bypass_to_delay_profile : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("DelayProfiles").AddColumn("BypassIfHighestQuality").AsBoolean().WithDefaultValue(false);
// Set to true for existing Delay Profiles to keep behavior the same.
Execute.Sql("UPDATE DelayProfiles SET BypassIfHighestQuality = 1;");
}
}
}

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(195)]
public class update_notifiarr : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.Sql("UPDATE Notifications SET Implementation = Replace(Implementation, 'DiscordNotifier', 'Notifiarr'),ConfigContract = Replace(ConfigContract, 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE Implementation = 'DiscordNotifier';");
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Linq;
using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.Pending;
@@ -78,13 +77,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
}
// If quality meets or exceeds the best allowed quality in the profile accept it immediately
var bestQualityInProfile = profile.LastAllowedQuality();
var isBestInProfile = comparer.Compare(subject.ParsedMovieInfo.Quality.Quality, bestQualityInProfile) >= 0;
if (isBestInProfile && isPreferredProtocol)
if (delayProfile.BypassIfHighestQuality)
{
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay.");
return Decision.Accept();
var bestQualityInProfile = profile.LastAllowedQuality();
var isBestInProfile = comparer.Compare(subject.ParsedMovieInfo.Quality.Quality, bestQualityInProfile) >= 0;
if (isBestInProfile && isPreferredProtocol)
{
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay.");
return Decision.Accept();
}
}
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Movie.Id);

View File

@@ -32,6 +32,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
var qualityComparer = new QualityModelComparer(profile);
var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality);
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
if (qualityCompare > 0)
{
@@ -45,10 +46,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
var qualityRevisionCompare = newQuality?.Revision.CompareTo(currentQuality.Revision);
// Accept unless the user doesn't want to prefer propers, optionally they can
// use preferred words to prefer propers/repacks over non-propers/repacks.
if (_configService.DownloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
newQuality?.Revision.CompareTo(currentQuality.Revision) > 0)
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
qualityRevisionCompare > 0)
{
return true;
}
@@ -56,6 +59,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
var currentFormatScore = profile.CalculateCustomFormatScore(currentCustomFormats);
var newFormatScore = profile.CalculateCustomFormatScore(newCustomFormats);
// Reject unless the user does not prefer propers/repacks and it's a revision downgrade.
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
qualityRevisionCompare < 0)
{
_logger.Debug("Existing item has a better quality revision, skipping");
return false;
}
if (newFormatScore <= currentFormatScore)
{
_logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping",

View File

@@ -106,14 +106,14 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
if (!deleteData)
{
throw new NotSupportedException("Blackhole cannot remove DownloadItem without deleting the data as well, ignoring.");
}
DeleteItemData(downloadId);
DeleteItemData(item);
}
public override DownloadClientInfo GetStatus()

View File

@@ -78,14 +78,14 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
if (!deleteData)
{
throw new NotSupportedException("Blackhole cannot remove DownloadItem without deleting the data as well, ignoring.");
}
DeleteItemData(downloadId);
DeleteItemData(item);
}
public override DownloadClientInfo GetStatus()

View File

@@ -190,9 +190,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
return items;
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
_proxy.RemoveTorrent(item.DownloadId.ToLower(), deleteData, Settings);
}
public override DownloadClientInfo GetStatus()

View File

@@ -134,15 +134,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
if (deleteData)
{
DeleteItemData(downloadId);
DeleteItemData(item);
}
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId);
_dsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings);
_logger.Debug("{0} removed correctly", item.DownloadId);
}
protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTask torrent, string serialNumber)

View File

@@ -158,15 +158,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
if (deleteData)
{
DeleteItemData(downloadId);
DeleteItemData(item);
}
_dsTaskProxy.RemoveTask(ParseDownloadId(downloadId), Settings);
_logger.Debug("{0} removed correctly", downloadId);
_dsTaskProxy.RemoveTask(ParseDownloadId(item.DownloadId), Settings);
_logger.Debug("{0} removed correctly", item.DownloadId);
}
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContent)

View File

@@ -201,9 +201,10 @@ namespace NzbDrone.Core.Download.Clients.Flood
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
_proxy.DeleteTorrent(downloadId, deleteData, Settings);
_proxy.DeleteTorrent(item.DownloadId, deleteData, Settings);
_proxy.DeleteTorrent(item.DownloadId, deleteData, Settings);
}
public override DownloadClientInfo GetStatus()

View File

@@ -109,15 +109,15 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
return items;
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
if (deleteData)
{
_proxy.RemoveTorrentAndData(Settings, downloadId);
_proxy.RemoveTorrentAndData(Settings, item.DownloadId);
}
else
{
_proxy.RemoveTorrent(Settings, downloadId);
_proxy.RemoveTorrent(Settings, item.DownloadId);
}
}

View File

@@ -121,19 +121,19 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
return queueItems;
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
// Try to find the download by numerical ID, otherwise try by AddUUID
int id;
if (int.TryParse(downloadId, out id))
if (int.TryParse(item.DownloadId, out id))
{
_proxy.Remove(id, deleteData, Settings);
}
else
{
var queue = _proxy.GetQueue(30, Settings);
var queueItem = queue.FirstOrDefault(c => c.AddUUID == downloadId);
var queueItem = queue.FirstOrDefault(c => c.AddUUID == item.DownloadId);
if (queueItem != null)
{

View File

@@ -195,14 +195,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
return GetQueue().Concat(GetHistory()).Where(downloadClientItem => downloadClientItem.Category == Settings.MovieCategory);
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
if (deleteData)
{
DeleteItemData(downloadId);
DeleteItemData(item);
}
_proxy.RemoveItem(downloadId, Settings);
_proxy.RemoveItem(item.DownloadId, Settings);
}
public override DownloadClientInfo GetStatus()

View File

@@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Sabnzbd")]
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to NZBGet")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the nzbget url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]

View File

@@ -98,7 +98,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
throw new NotSupportedException();
}

View File

@@ -155,7 +155,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{
_logger.Warn(ex, "Failed to set the torrent seed criteria for {0}.", hash);
}
}
}
if (moveToTop)
{
@@ -320,9 +320,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return queueItems;
}
public override void RemoveItem(string hash, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
Proxy.RemoveTorrent(hash.ToLower(), deleteData, Settings);
Proxy.RemoveTorrent(item.DownloadId.ToLower(), deleteData, Settings);
}
public override DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt)

View File

@@ -194,47 +194,22 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
var historyItem = GetHistory().SingleOrDefault(v => v.DownloadId == downloadId);
var queueClientItem = GetQueue().SingleOrDefault(v => v.DownloadId == item.DownloadId);
if (historyItem == null)
if (queueClientItem == null)
{
_proxy.RemoveFrom("queue", downloadId, deleteData, Settings);
if (deleteData && item.Status == DownloadItemStatus.Completed)
{
DeleteItemData(item);
}
_proxy.RemoveFrom("history", item.DownloadId, deleteData, Settings);
}
else
{
_proxy.RemoveFrom("history", downloadId, deleteData, Settings);
// Completed items in SAB's history do not remove the files from the file system when deleted, even if instructed to.
// If the output path is valid delete the file(s), otherwise warn that they cannot be deleted.
if (deleteData && historyItem.Status == DownloadItemStatus.Completed)
{
if (ValidatePath(historyItem))
{
var outputPath = historyItem.OutputPath;
try
{
if (_diskProvider.FolderExists(outputPath.FullPath))
{
_diskProvider.DeleteFolder(outputPath.FullPath.ToString(), true);
}
else if (_diskProvider.FileExists(outputPath.FullPath))
{
_diskProvider.DeleteFile(outputPath.FullPath.ToString());
}
}
catch (Exception)
{
_logger.Error("Unable to delete output path: '{0}'. Delete file(s) manually", outputPath.FullPath);
}
}
else
{
_logger.Warn("Invalid path '{0}'. Delete file(s) manually");
}
}
_proxy.RemoveFrom("queue", item.DownloadId, deleteData, Settings);
}
}

View File

@@ -159,9 +159,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return false;
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
_proxy.RemoveTorrent(item.DownloadId.ToLower(), deleteData, Settings);
}
public override DownloadClientInfo GetStatus()

View File

@@ -26,9 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Vuze
{
}
public override void RemoveItem(string downloadId, bool deleteData)
public override void RemoveItem(DownloadClientItem item, bool deleteData)
{
_proxy.RemoveTorrent(downloadId, deleteData, Settings);
_proxy.RemoveTorrent(item.DownloadId, deleteData, Settings);
}
protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent)

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