mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-05 13:40:08 -05:00
Compare commits
121 Commits
v1.4.0.323
...
v1.5.1.342
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3ddf2f9cd | ||
|
|
d9ce9eb0b2 | ||
|
|
29ab1801db | ||
|
|
19ff73dad0 | ||
|
|
c455f1a113 | ||
|
|
b8793d8783 | ||
|
|
ce34940287 | ||
|
|
dcb19a66b0 | ||
|
|
b3bc92e60e | ||
|
|
1b17d38564 | ||
|
|
d8c7361205 | ||
|
|
7a0dd0bc0d | ||
|
|
c02bfb5930 | ||
|
|
d0fbb1f49a | ||
|
|
aafdefe2f0 | ||
|
|
96234c0fe1 | ||
|
|
8b5648d7bd | ||
|
|
1fc79f9e9b | ||
|
|
ec40761757 | ||
|
|
0a8e4eb092 | ||
|
|
ade961fad5 | ||
|
|
81b1c0e445 | ||
|
|
0fe54ed36a | ||
|
|
337828ff9c | ||
|
|
fb34294d2e | ||
|
|
931e3cf42d | ||
|
|
051930455e | ||
|
|
eba5413250 | ||
|
|
cc2f50544b | ||
|
|
450c6d7af5 | ||
|
|
bdc0178e44 | ||
|
|
aa9705846e | ||
|
|
7559a87bc8 | ||
|
|
6a7fe30171 | ||
|
|
2b0f4e18e7 | ||
|
|
4a5a986220 | ||
|
|
38ae17a99f | ||
|
|
9a72da2803 | ||
|
|
3bba76caab | ||
|
|
47ceabc834 | ||
|
|
48bb3196dd | ||
|
|
4c4ebdf17c | ||
|
|
b5706a0d55 | ||
|
|
d946ef4a9e | ||
|
|
48ec5bbaa1 | ||
|
|
2bcdae44c7 | ||
|
|
541b8b4f7f | ||
|
|
8dd79c38d5 | ||
|
|
615b85fffe | ||
|
|
ceab19caf9 | ||
|
|
3d61719a2c | ||
|
|
befb354913 | ||
|
|
10bbaee55d | ||
|
|
131550b92d | ||
|
|
5f83da9725 | ||
|
|
1ca8ff5012 | ||
|
|
061a0c0da8 | ||
|
|
4cc2706ee5 | ||
|
|
32691832a5 | ||
|
|
48977de3b8 | ||
|
|
34fbb3e135 | ||
|
|
d38f2614d3 | ||
|
|
ecc5439464 | ||
|
|
795274e7e1 | ||
|
|
eb96fbe956 | ||
|
|
2f1fb396a5 | ||
|
|
20c085a979 | ||
|
|
4990e537eb | ||
|
|
f8111ac7ba | ||
|
|
cb1fd39cb3 | ||
|
|
5e9094b54c | ||
|
|
746d84cf83 | ||
|
|
bbe3241b83 | ||
|
|
a86aa4c5d3 | ||
|
|
a753f721d1 | ||
|
|
202836110e | ||
|
|
1a5e41d831 | ||
|
|
e1d0e2c799 | ||
|
|
92e7a38bd0 | ||
|
|
008f238dda | ||
|
|
40125046fa | ||
|
|
1fd188fe7a | ||
|
|
5e5699fbbe | ||
|
|
d61275e6db | ||
|
|
dca3e939f0 | ||
|
|
26ac66c0e1 | ||
|
|
649b301444 | ||
|
|
78ed2a1af0 | ||
|
|
a4854b7b5f | ||
|
|
97edf495bd | ||
|
|
d10bdf4676 | ||
|
|
03647143e3 | ||
|
|
8090dc9983 | ||
|
|
5bc1f345c0 | ||
|
|
4ef01f5640 | ||
|
|
f13d5c5a14 | ||
|
|
dc8773cf79 | ||
|
|
cad774e250 | ||
|
|
b28eee578a | ||
|
|
5b8c7d0b79 | ||
|
|
8bdc7a6db7 | ||
|
|
cb189b8f61 | ||
|
|
24468db376 | ||
|
|
9b10cea556 | ||
|
|
d8fb71d501 | ||
|
|
fc39a11ece | ||
|
|
40dc4de47d | ||
|
|
a0e2f3324c | ||
|
|
1bcc3b426e | ||
|
|
66f5fd2a26 | ||
|
|
b5e5701791 | ||
|
|
1a9b202afe | ||
|
|
309f42bac5 | ||
|
|
ed330ea657 | ||
|
|
fc6a31ea78 | ||
|
|
25ba9195cf | ||
|
|
681f06e321 | ||
|
|
af7fb442d2 | ||
|
|
2061b9142f | ||
|
|
b97f6f8ddf | ||
|
|
f31c0bb1de |
@@ -40,6 +40,9 @@ csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Using directive is unnecessary.
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
|
||||
# Stylecop Rules
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
dotnet_diagnostic.SA1005.severity = none
|
||||
|
||||
9
.github/label-actions.yml
vendored
9
.github/label-actions.yml
vendored
@@ -13,4 +13,11 @@
|
||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||
for bug reports and feature requests. However, this issue appears
|
||||
to be a indexer request. Please use our Indexer request [site](https://requests.prowlarr.com/)
|
||||
close: true
|
||||
close: true
|
||||
|
||||
'Status: Logs Needed':
|
||||
comment: >
|
||||
:wave: @{issue-author}, in order to help you further we'll need to see logs.
|
||||
You'll need to enable trace logging and replicate the problem that you encountered.
|
||||
Guidance on how to enable trace logging can be found in
|
||||
our [troubleshooting guide](https://wiki.servarr.com/prowlarr/troubleshooting#logging-and-log-files).
|
||||
19
.github/labeler.yml
vendored
Normal file
19
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
'Area: API':
|
||||
- src/Prowlarr.Api.V1/**/*
|
||||
|
||||
'Area: Db-migration':
|
||||
- src/NzbDrone.Core/Datastore/Migration/*
|
||||
|
||||
'Area: Download Clients':
|
||||
- src/NzbDrone.Core/Download/Clients/**/*
|
||||
|
||||
'Area: Indexer':
|
||||
- src/NzbDrone.Core/Indexers/**/*
|
||||
|
||||
'Area: Notifications':
|
||||
- src/NzbDrone.Core/Notifications/**/*
|
||||
|
||||
'Area: UI':
|
||||
- frontend/**/*
|
||||
- package.json
|
||||
- yarn.lock
|
||||
12
.github/workflows/labeler.yml
vendored
Normal file
12
.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.4.0'
|
||||
majorVersion: '1.5.1'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"remove-empty-rulesets": true,
|
||||
"always-semicolon": true,
|
||||
"color-case": "lower",
|
||||
"block-indent": " ",
|
||||
"color-shorthand": false,
|
||||
"element-case": "lower",
|
||||
"eof-newline": true,
|
||||
"leading-zero": true,
|
||||
"quotes": "double",
|
||||
"sort-order-fallback": "abc",
|
||||
"space-before-colon": "",
|
||||
"space-after-colon": " ",
|
||||
"space-before-combinator": " ",
|
||||
"space-after-combinator": " ",
|
||||
"space-between-declarations": "\n",
|
||||
"space-before-opening-brace": " ",
|
||||
"space-after-opening-brace": "\n",
|
||||
"space-after-selector-delimiter": " ",
|
||||
"space-before-selector-delimiter": "",
|
||||
"space-before-closing-brace": "\n",
|
||||
"strip-spaces": true,
|
||||
"tab-size": true,
|
||||
"unitless-zero": false
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
{
|
||||
"indent": {
|
||||
"value": " ",
|
||||
"FunctionExpression": 1,
|
||||
"ArrayExpression": 1,
|
||||
"ObjectExpression": 1
|
||||
},
|
||||
"lineBreak": {
|
||||
"value": "\n",
|
||||
|
||||
"before": {
|
||||
"ArrayPatternClosing": 0,
|
||||
"ArrayPatternComma": 0,
|
||||
"ArrayPatternOpening": 0,
|
||||
"ArrowFunctionExpressionArrow": 0,
|
||||
"ArrowFunctionExpressionClosingBrace": ">=1",
|
||||
"ArrowFunctionExpressionOpeningBrace": 0,
|
||||
"AssignmentExpression": ">=1",
|
||||
"AssignmentOperator": 0,
|
||||
"BlockStatement": 0,
|
||||
"BreakKeyword": ">=1",
|
||||
"CallExpression": -1,
|
||||
"CallExpressionClosingParentheses": -1,
|
||||
"CallExpressionOpeningParentheses": 0,
|
||||
"CatchClosingBrace": ">=1",
|
||||
"CatchKeyword": 0,
|
||||
"CatchOpeningBrace": 0,
|
||||
"ClassDeclaration": ">=1",
|
||||
"ClassDeclarationClosingBrace": ">=1",
|
||||
"ClassDeclarationOpeningBrace": 0,
|
||||
"ConditionalExpression": ">=1",
|
||||
"DeleteOperator": ">=1",
|
||||
"DoWhileStatement": ">=1",
|
||||
"DoWhileStatementClosingBrace": ">=1",
|
||||
"DoWhileStatementOpeningBrace": 0,
|
||||
"ElseIfStatement": 0,
|
||||
"ElseIfStatementClosingBrace": ">=1",
|
||||
"ElseIfStatementOpeningBrace": 0,
|
||||
"ElseStatement": 0,
|
||||
"ElseStatementClosingBrace": ">=1",
|
||||
"ElseStatementOpeningBrace": 0,
|
||||
"EmptyStatement": -1,
|
||||
"EndOfFile": -1,
|
||||
"FinallyClosingBrace": ">=1",
|
||||
"FinallyKeyword": -1,
|
||||
"FinallyOpeningBrace": 0,
|
||||
"ForInStatement": ">=1",
|
||||
"ForInStatementClosingBrace": ">=1",
|
||||
"ForInStatementExpressionClosing": 0,
|
||||
"ForInStatementExpressionOpening": 0,
|
||||
"ForInStatementOpeningBrace": 0,
|
||||
"ForStatement": ">=1",
|
||||
"ForStatementClosingBrace": ">=1",
|
||||
"ForStatementExpressionClosing": "<2",
|
||||
"ForStatementExpressionOpening": 0,
|
||||
"ForStatementOpeningBrace": 0,
|
||||
"FunctionDeclaration": ">=1",
|
||||
"FunctionDeclarationClosingBrace": ">=1",
|
||||
"FunctionDeclarationOpeningBrace": 0,
|
||||
"FunctionExpression": 0,
|
||||
"FunctionExpressionClosingBrace": 1,
|
||||
"FunctionExpressionOpeningBrace":0,
|
||||
"IIFEClosingParentheses": 0,
|
||||
"IfStatement": ">=1",
|
||||
"IfStatementClosingBrace": ">=1",
|
||||
"IfStatementOpeningBrace": 0,
|
||||
"LogicalExpression": -1,
|
||||
"MemberExpressionClosing": 0,
|
||||
"MemberExpressionOpening": 0,
|
||||
"MemberExpressionPeriod": -1,
|
||||
"MethodDefinition": ">=1",
|
||||
"ObjectExpressionClosingBrace": "<=1",
|
||||
"ObjectPatternClosingBrace": 0,
|
||||
"ObjectPatternComma": 0,
|
||||
"ObjectPatternOpeningBrace": 0,
|
||||
"ParameterDefault": 0,
|
||||
"Property": "<=2",
|
||||
"PropertyValue": 0,
|
||||
"ReturnStatement": -1,
|
||||
"SwitchClosingBrace": ">=1",
|
||||
"SwitchOpeningBrace": 0,
|
||||
"ThisExpression": -1,
|
||||
"ThrowStatement": ">=1",
|
||||
"TryClosingBrace": ">=1",
|
||||
"TryKeyword": -1,
|
||||
"TryOpeningBrace": 0,
|
||||
"VariableDeclaration": ">=1",
|
||||
"VariableDeclarationSemiColon": 0,
|
||||
"VariableDeclarationWithoutInit": ">=1",
|
||||
"VariableName": ">=1",
|
||||
"VariableValue": 0,
|
||||
"WhileStatement": ">=1",
|
||||
"WhileStatementClosingBrace": ">=1",
|
||||
"WhileStatementOpeningBrace": 0
|
||||
},
|
||||
|
||||
"after": {
|
||||
"ArrayPatternClosing": 0,
|
||||
"ArrayPatternComma": 0,
|
||||
"ArrayPatternOpening": 0,
|
||||
"ArrowFunctionExpressionArrow": 0,
|
||||
"ArrowFunctionExpressionClosingBrace": -1,
|
||||
"ArrowFunctionExpressionOpeningBrace": ">=1",
|
||||
"AssignmentExpression": ">=1",
|
||||
"AssignmentOperator": 0,
|
||||
"BlockStatement": 0,
|
||||
"BreakKeyword": -1,
|
||||
"CallExpression": -1,
|
||||
"CallExpressionClosingParentheses": -1,
|
||||
"CallExpressionOpeningParentheses": -1,
|
||||
"CatchClosingBrace": ">=0",
|
||||
"CatchKeyword": 0,
|
||||
"CatchOpeningBrace": ">=1",
|
||||
"ClassDeclaration": ">=1",
|
||||
"ClassDeclarationClosingBrace": ">=1",
|
||||
"ClassDeclarationOpeningBrace": ">=1",
|
||||
"ConditionalExpression": ">=1",
|
||||
"DeleteOperator": ">=1",
|
||||
"DoWhileStatement": ">=1",
|
||||
"DoWhileStatementClosingBrace": 0,
|
||||
"DoWhileStatementOpeningBrace": ">=1",
|
||||
"ElseIfStatement": ">=1",
|
||||
"ElseIfStatementClosingBrace": ">=1",
|
||||
"ElseIfStatementOpeningBrace": ">=1",
|
||||
"ElseStatement": ">=1",
|
||||
"ElseStatementClosingBrace": ">=1",
|
||||
"ElseStatementOpeningBrace": ">=1",
|
||||
"EmptyStatement": -1,
|
||||
"FinallyClosingBrace": ">=1",
|
||||
"FinallyKeyword": -1,
|
||||
"FinallyOpeningBrace": ">=1",
|
||||
"ForInStatement": ">=1",
|
||||
"ForInStatementClosingBrace": ">=1",
|
||||
"ForInStatementExpressionClosing": -1,
|
||||
"ForInStatementExpressionOpening": "<2",
|
||||
"ForInStatementOpeningBrace": ">=1",
|
||||
"ForStatement": ">=1",
|
||||
"ForStatementClosingBrace": ">=1",
|
||||
"ForStatementExpressionClosing": -1,
|
||||
"ForStatementExpressionOpening": "<2",
|
||||
"ForStatementOpeningBrace": ">=1",
|
||||
"FunctionDeclaration": ">=1",
|
||||
"FunctionDeclarationClosingBrace": ">=1",
|
||||
"FunctionDeclarationOpeningBrace": ">=1",
|
||||
"FunctionExpression": 0,
|
||||
"FunctionExpressionClosingBrace": -1,
|
||||
"FunctionExpressionOpeningBrace": 1,
|
||||
"IIFEOpeningParentheses": 0,
|
||||
"IfStatement": ">=1",
|
||||
"IfStatementClosingBrace": ">=1",
|
||||
"IfStatementOpeningBrace": ">=1",
|
||||
"LogicalExpression": -1,
|
||||
"MemberExpressionClosing": 0,
|
||||
"MemberExpressionOpening": 0,
|
||||
"MemberExpressionPeriod": 0,
|
||||
"MethodDefinition": ">=1",
|
||||
"ObjectExpressionOpeningBrace": "<=1",
|
||||
"ObjectPatternClosingBrace": 0,
|
||||
"ObjectPatternComma": 0,
|
||||
"ObjectPatternOpeningBrace": 0,
|
||||
"ParameterDefault": 0,
|
||||
"Property": -1,
|
||||
"PropertyName": 0,
|
||||
"ReturnStatement": -1,
|
||||
"SwitchCaseColon": ">=1",
|
||||
"SwitchClosingBrace": ">=1",
|
||||
"SwitchOpeningBrace": ">=1",
|
||||
"ThisExpression": 0,
|
||||
"ThrowStatement": ">=1",
|
||||
"TryClosingBrace": 0,
|
||||
"TryKeyword": -1,
|
||||
"TryOpeningBrace": ">=1",
|
||||
"VariableDeclaration": ">=1",
|
||||
"VariableDeclarationSemiColon": ">=1",
|
||||
"VariableValue": -1,
|
||||
"WhileStatement": ">=1",
|
||||
"WhileStatementClosingBrace": ">=1",
|
||||
"WhileStatementOpeningBrace": ">=1"
|
||||
}
|
||||
},
|
||||
"whiteSpace": {
|
||||
"value": " ",
|
||||
"removeTrailing": 1,
|
||||
"before": {
|
||||
"ArgumentComma": 0,
|
||||
"ArgumentList": 0,
|
||||
"ArgumentListArrayExpression": 0,
|
||||
"ArgumentListFunctionExpression": 1,
|
||||
"ArgumentListObjectExpression": 0,
|
||||
"ArrayExpressionClosing": 0,
|
||||
"ArrayExpressionComma": 0,
|
||||
"ArrayExpressionOpening": 1,
|
||||
"AssignmentOperator": 1,
|
||||
"BinaryExpression": 0,
|
||||
"BinaryExpressionOperator": 1,
|
||||
"BlockComment": 1,
|
||||
"CallExpression": 1,
|
||||
"CatchClosingBrace": 1,
|
||||
"CatchKeyword": 1,
|
||||
"CatchOpeningBrace": 1,
|
||||
"CatchParameterList": 0,
|
||||
"CommaOperator": 0,
|
||||
"ConditionalExpressionAlternate": 1,
|
||||
"ConditionalExpressionConsequent": 1,
|
||||
"DoWhileStatementClosingBrace": 1,
|
||||
"DoWhileStatementConditional": 1,
|
||||
"DoWhileStatementOpeningBrace": 1,
|
||||
"ElseIfStatementClosingBrace": 1,
|
||||
"ElseIfStatementOpeningBrace": 1,
|
||||
"ElseStatementClosingBrace": 1,
|
||||
"ElseStatementOpeningBrace": 1,
|
||||
"EmptyStatement": 0,
|
||||
"ExpressionClosingParentheses": 0,
|
||||
"FinallyClosingBrace": 1,
|
||||
"FinallyKeyword": -1,
|
||||
"FinallyOpeningBrace": 1,
|
||||
"ForInStatement": 1,
|
||||
"ForInStatementClosingBrace": 1,
|
||||
"ForInStatementExpressionClosing": 0,
|
||||
"ForInStatementExpressionOpening": 1,
|
||||
"ForInStatementOpeningBrace": 1,
|
||||
"ForStatement": 1,
|
||||
"ForStatementClosingBrace": 1,
|
||||
"ForStatementExpressionClosing": 0,
|
||||
"ForStatementExpressionOpening": 1,
|
||||
"ForStatementOpeningBrace": 1,
|
||||
"ForStatementSemicolon": 0,
|
||||
"FunctionDeclarationClosingBrace": 1,
|
||||
"FunctionDeclarationOpeningBrace": 1,
|
||||
"FunctionExpressionClosingBrace": 1,
|
||||
"FunctionExpressionOpeningBrace": 1,
|
||||
"IfStatementClosingBrace": 1,
|
||||
"IfStatementConditionalClosing": 0,
|
||||
"IfStatementConditionalOpening": 1,
|
||||
"IfStatementOpeningBrace": 1,
|
||||
"LineComment": 1,
|
||||
"LogicalExpressionOperator": 1,
|
||||
"MemberExpressionClosing": 0,
|
||||
"ObjectExpressionClosingBrace": 1,
|
||||
"ParameterComma": 0,
|
||||
"ParameterList": 0,
|
||||
"Property": 1,
|
||||
"PropertyName": 1,
|
||||
"PropertyValue": 1,
|
||||
"SwitchDiscriminantClosing": 0,
|
||||
"SwitchDiscriminantOpening": 1,
|
||||
"ThrowKeyword": 1,
|
||||
"TryClosingBrace": 1,
|
||||
"TryKeyword": -1,
|
||||
"TryOpeningBrace": 1,
|
||||
"UnaryExpressionOperator": 0,
|
||||
"VariableName": 1,
|
||||
"VariableValue": 1,
|
||||
"WhileStatementClosingBrace": 1,
|
||||
"WhileStatementConditionalClosing": 0,
|
||||
"WhileStatementConditionalOpening": 1,
|
||||
"WhileStatementOpeningBrace": 1
|
||||
},
|
||||
"after": {
|
||||
"ArgumentComma": 1,
|
||||
"ArgumentList": 0,
|
||||
"ArgumentListArrayExpression": 1,
|
||||
"ArgumentListFunctionExpression": 1,
|
||||
"ArgumentListObjectExpression": 0,
|
||||
"ArrayExpressionClosing": 0,
|
||||
"ArrayExpressionComma": 1,
|
||||
"ArrayExpressionOpening": 0,
|
||||
"AssignmentOperator": 1,
|
||||
"BinaryExpression": 0,
|
||||
"BinaryExpressionOperator": 1,
|
||||
"BlockComment": 1,
|
||||
"CallExpression": 0,
|
||||
"CatchClosingBrace": 1,
|
||||
"CatchKeyword": 1,
|
||||
"CatchOpeningBrace": 1,
|
||||
"CatchParameterList": 0,
|
||||
"CommaOperator": 1,
|
||||
"ConditionalExpressionConsequent": 1,
|
||||
"ConditionalExpressionTest": 1,
|
||||
"DoWhileStatementBody": 1,
|
||||
"DoWhileStatementClosingBrace": 1,
|
||||
"DoWhileStatementOpeningBrace": 1,
|
||||
"ElseIfStatementClosingBrace": 1,
|
||||
"ElseIfStatementOpeningBrace": 1,
|
||||
"ElseStatementClosingBrace": 1,
|
||||
"ElseStatementOpeningBrace": 1,
|
||||
"EmptyStatement": 0,
|
||||
"ExpressionOpeningParentheses": 0,
|
||||
"FinallyClosingBrace": 1,
|
||||
"FinallyKeyword": -1,
|
||||
"FinallyOpeningBrace": 1,
|
||||
"ForInStatement": 1,
|
||||
"ForInStatementClosingBrace": 1,
|
||||
"ForInStatementExpressionClosing": 1,
|
||||
"ForInStatementExpressionOpening": 0,
|
||||
"ForInStatementOpeningBrace": 1,
|
||||
"ForStatement": 1,
|
||||
"ForStatementClosingBrace": 1,
|
||||
"ForStatementExpressionClosing": 1,
|
||||
"ForStatementExpressionOpening": 0,
|
||||
"ForStatementOpeningBrace": 1,
|
||||
"ForStatementSemicolon": 1,
|
||||
"FunctionDeclarationClosingBrace": 0,
|
||||
"FunctionDeclarationOpeningBrace": 0,
|
||||
"FunctionExpressionClosingBrace": 0,
|
||||
"FunctionExpressionOpeningBrace": 0,
|
||||
"FunctionName": 0,
|
||||
"FunctionReservedWord": 0,
|
||||
"IfStatementClosingBrace": 1,
|
||||
"IfStatementConditionalClosing": 0,
|
||||
"IfStatementConditionalOpening": 0,
|
||||
"IfStatementOpeningBrace": 1,
|
||||
"LogicalExpressionOperator": 1,
|
||||
"MemberExpressionOpening": 0,
|
||||
"ObjectExpressionClosingBrace": 0,
|
||||
"ObjectExpressionOpeningBrace": 1,
|
||||
"ParameterComma": 1,
|
||||
"ParameterList": 0,
|
||||
"PropertyName": 0,
|
||||
"PropertyValue": 0,
|
||||
"SwitchDiscriminantClosing": 1,
|
||||
"SwitchDiscriminantOpening": 0,
|
||||
"ThrowKeyword": 1,
|
||||
"TryClosingBrace": 1,
|
||||
"TryKeyword": -1,
|
||||
"TryOpeningBrace": 1,
|
||||
"UnaryExpressionOperator": 0,
|
||||
"VariableName": 1,
|
||||
"WhileStatementClosingBrace": 1,
|
||||
"WhileStatementConditionalClosing": 1,
|
||||
"WhileStatementConditionalOpening": 0,
|
||||
"WhileStatementOpeningBrace": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ const dirs = fs
|
||||
.join('|');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
parser: '@babel/eslint-parser',
|
||||
|
||||
env: {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"js": {
|
||||
"indent_size": 2,
|
||||
"indent_char": " ",
|
||||
"indent_level": 2,
|
||||
"indent_with_tabs": false,
|
||||
"preserve_newlines": true,
|
||||
"brace_style": "collapse",
|
||||
"max_preserve_newlines": 2,
|
||||
"jslint_happy": true
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"plugins": [
|
||||
"stylelint-order"
|
||||
],
|
||||
"ignoreFiles": [
|
||||
"frontend/src/Styles/scaffolding.css",
|
||||
"**/*.js"
|
||||
],
|
||||
"rules": {
|
||||
"plugins": [
|
||||
"stylelint-order"
|
||||
],
|
||||
"ignoreFiles": [
|
||||
"frontend/src/Styles/scaffolding.css",
|
||||
"**/*.js"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
@@ -15,9 +15,6 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"at-rule-name-case": "lower",
|
||||
"at-rule-name-newline-after": "always-multi-line",
|
||||
"at-rule-name-space-after": "always",
|
||||
"at-rule-no-unknown": [
|
||||
true,
|
||||
{
|
||||
@@ -28,83 +25,36 @@
|
||||
}
|
||||
],
|
||||
"at-rule-no-vendor-prefix": true,
|
||||
"at-rule-semicolon-newline-after": "always",
|
||||
"at-rule-semicolon-space-before": "never",
|
||||
"block-closing-brace-empty-line-before": "never",
|
||||
"block-closing-brace-newline-after": "always",
|
||||
"block-closing-brace-newline-before": "always",
|
||||
"block-closing-brace-space-after": "always-single-line",
|
||||
"block-closing-brace-space-before": "always-single-line",
|
||||
"block-no-empty": true,
|
||||
"block-opening-brace-newline-after": "always",
|
||||
"block-opening-brace-newline-before": "never-single-line",
|
||||
"block-opening-brace-space-after": "always-single-line",
|
||||
"block-opening-brace-space-before": "always",
|
||||
"color-hex-case": "lower",
|
||||
"color-hex-length": "short",
|
||||
"color-named": "never",
|
||||
"color-no-invalid-hex": true,
|
||||
"comment-whitespace-inside": "always",
|
||||
"declaration-bang-space-after": "never",
|
||||
"declaration-bang-space-before": "always",
|
||||
"declaration-block-no-duplicate-properties": [
|
||||
true,
|
||||
{
|
||||
"ignoreProperties": [
|
||||
"composes"
|
||||
"composes"
|
||||
]
|
||||
}
|
||||
],
|
||||
"declaration-block-no-redundant-longhand-properties": true,
|
||||
"declaration-block-no-shorthand-property-overrides": true,
|
||||
"declaration-block-semicolon-newline-after": "always",
|
||||
"declaration-block-semicolon-newline-before": "never-multi-line",
|
||||
"declaration-block-semicolon-space-before": "never",
|
||||
"declaration-block-single-line-max-declarations": 1,
|
||||
"declaration-block-trailing-semicolon": "always",
|
||||
"declaration-colon-space-after": "always",
|
||||
"declaration-colon-space-before": "never",
|
||||
"font-family-name-quotes": "always-unless-keyword",
|
||||
"function-calc-no-unspaced-operator": true,
|
||||
"function-comma-newline-after": "never-multi-line",
|
||||
"function-comma-newline-before": "never-multi-line",
|
||||
"function-comma-space-after": "always",
|
||||
"function-comma-space-before": "never",
|
||||
"function-linear-gradient-no-nonstandard-direction": true,
|
||||
"function-name-case": "lower",
|
||||
"function-parentheses-newline-inside": "never-multi-line",
|
||||
"function-parentheses-space-inside": "never",
|
||||
"function-url-quotes": "always",
|
||||
"function-url-scheme-disallowed-list": [
|
||||
"data"
|
||||
],
|
||||
"function-whitespace-after": "always",
|
||||
"indentation": 2,
|
||||
"keyframe-declaration-no-important": true,
|
||||
"length-zero-no-unit": true,
|
||||
"max-empty-lines": 1,
|
||||
"max-line-length": [
|
||||
100,
|
||||
{
|
||||
"ignore": [
|
||||
"non-comments"
|
||||
]
|
||||
}
|
||||
],
|
||||
"max-nesting-depth": 2,
|
||||
"media-feature-colon-space-after": "always",
|
||||
"media-feature-colon-space-before": "never",
|
||||
"media-feature-name-case": "lower",
|
||||
"media-feature-name-no-vendor-prefix": true,
|
||||
"media-feature-range-operator-space-after": "always",
|
||||
"media-feature-range-operator-space-before": "always",
|
||||
"no-empty-source": true,
|
||||
"no-eol-whitespace": true,
|
||||
"no-extra-semicolons": true,
|
||||
"no-invalid-double-slash-comments": true,
|
||||
"no-missing-end-of-source-newline": true,
|
||||
"number-leading-zero": "always",
|
||||
"number-no-trailing-zeros": true,
|
||||
"order/order": [
|
||||
"custom-properties",
|
||||
"dollar-variables",
|
||||
@@ -132,6 +82,7 @@
|
||||
"right",
|
||||
"bottom",
|
||||
"left",
|
||||
"inset",
|
||||
"z-index",
|
||||
"display",
|
||||
"visibility",
|
||||
@@ -343,54 +294,33 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"property-case": "lower",
|
||||
"property-no-vendor-prefix": true,
|
||||
"rule-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
"except": [
|
||||
"first-nested"
|
||||
"first-nested"
|
||||
],
|
||||
"ignore": [
|
||||
"after-comment"
|
||||
"after-comment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selector-attribute-brackets-space-inside": "never",
|
||||
"selector-attribute-operator-space-after": "never",
|
||||
"selector-attribute-operator-space-before": "never",
|
||||
"selector-attribute-quotes": "never",
|
||||
"selector-class-pattern": "^[A-Za-z0-9]+$",
|
||||
"selector-combinator-space-after": "always",
|
||||
"selector-combinator-space-before": "always",
|
||||
"selector-descendant-combinator-no-non-space": true,
|
||||
"selector-list-comma-newline-after": "always",
|
||||
"selector-list-comma-newline-before": "never-multi-line",
|
||||
"selector-list-comma-space-before": "never",
|
||||
"selector-max-attribute": 0,
|
||||
"selector-max-class": 3,
|
||||
"selector-max-compound-selectors": 3,
|
||||
"selector-max-empty-lines": 0,
|
||||
"selector-max-id": 0,
|
||||
"selector-max-universal": 0,
|
||||
"selector-pseudo-class-case": "lower",
|
||||
"selector-pseudo-class-parentheses-space-inside": "never",
|
||||
"selector-pseudo-element-case": "lower",
|
||||
"selector-pseudo-element-colon-notation": "double",
|
||||
"selector-pseudo-element-no-unknown": true,
|
||||
"selector-type-case": "lower",
|
||||
"selector-type-no-unknown": true,
|
||||
"shorthand-property-no-redundant-values": true,
|
||||
"string-no-newline": true,
|
||||
"string-quotes": "single",
|
||||
"time-min-milliseconds": 100,
|
||||
"unit-case": "lower",
|
||||
"unit-no-unknown": true,
|
||||
"value-list-comma-newline-after": "never-multi-line",
|
||||
"value-list-comma-newline-before": "never-multi-line",
|
||||
"value-list-comma-space-after": "always",
|
||||
"value-list-comma-space-before": "never",
|
||||
"value-list-max-empty-lines": 0,
|
||||
"value-no-vendor-prefix": true
|
||||
}
|
||||
}
|
||||
}
|
||||
7
frontend/.vscode/extensions.json
vendored
Normal file
7
frontend/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"stylelint.vscode-stylelint",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
@@ -50,7 +50,7 @@ module.exports = (env) => {
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery'
|
||||
jquery: 'jquery/dist/jquery.min'
|
||||
},
|
||||
fallback: {
|
||||
buffer: false,
|
||||
@@ -251,18 +251,19 @@ module.exports = (env) => {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
config.optimization = {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
|
||||
@@ -17,7 +17,7 @@ class DescriptionListItem extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<div>
|
||||
<DescriptionListItemTitle
|
||||
className={titleClassName}
|
||||
>
|
||||
@@ -29,7 +29,7 @@ class DescriptionListItem extends Component {
|
||||
>
|
||||
{data}
|
||||
</DescriptionListItemDescription>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Alert from 'Components/Alert';
|
||||
import PathInput from 'Components/Form/PathInput';
|
||||
import Button from 'Components/Link/Button';
|
||||
@@ -39,7 +38,7 @@ class FileBrowserModalContent extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._scrollerNode = null;
|
||||
this._scrollerRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
isFileBrowserModalOpen: false,
|
||||
@@ -57,21 +56,10 @@ class FileBrowserModalContent extends Component {
|
||||
currentPath !== prevState.currentPath
|
||||
) {
|
||||
this.setState({ currentPath });
|
||||
this._scrollerNode.scrollTop = 0;
|
||||
this._scrollerRef.current.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
setScrollerRef = (ref) => {
|
||||
if (ref) {
|
||||
this._scrollerNode = ReactDOM.findDOMNode(ref);
|
||||
} else {
|
||||
this._scrollerNode = null;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
@@ -145,7 +133,7 @@ class FileBrowserModalContent extends Component {
|
||||
/>
|
||||
|
||||
<Scroller
|
||||
ref={this.setScrollerRef}
|
||||
ref={this._scrollerRef}
|
||||
className={styles.scroller}
|
||||
scrollDirection={scrollDirections.BOTH}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
.inputContainer {
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
left: -1px;
|
||||
inset: -1px;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -23,6 +23,7 @@ function IconButton(props) {
|
||||
className,
|
||||
isDisabled && styles.isDisabled
|
||||
)}
|
||||
aria-label="Table Options Button"
|
||||
isDisabled={isDisabled}
|
||||
{...otherProps}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,7 @@ const messages = [
|
||||
'Loading humorous message... Please Wait',
|
||||
'I could\'ve been faster in Python',
|
||||
'Don\'t forget to rewind your tracks',
|
||||
'Congratulations! you are the 1000th visitor.',
|
||||
'Congratulations! You are the 1000th visitor.',
|
||||
'HELP! I\'m being held hostage and forced to write these stupid lines!',
|
||||
'RE-calibrating the internet...',
|
||||
'I\'ll be here all week',
|
||||
|
||||
@@ -56,6 +56,7 @@ class PageHeader extends Component {
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Prowlarr.urlBase}/Content/Images/logo.png`}
|
||||
alt="Prowlarr Logo"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -74,6 +75,7 @@ class PageHeader extends Component {
|
||||
<IconButton
|
||||
className={styles.donate}
|
||||
name={icons.HEART}
|
||||
aria-label="Donate"
|
||||
to="https://prowlarr.com/donate"
|
||||
size={14}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,7 @@ function PageHeaderActionsMenu(props) {
|
||||
return (
|
||||
<div>
|
||||
<Menu alignMenu={align.RIGHT}>
|
||||
<MenuButton className={styles.menuButton}>
|
||||
<MenuButton className={styles.menuButton} aria-label="Menu Button">
|
||||
<Icon
|
||||
name={icons.INTERACTIVE}
|
||||
/>
|
||||
|
||||
@@ -56,7 +56,9 @@ function ProgressBar(props) {
|
||||
styles[kind],
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
aria-valuenow={progress}
|
||||
role="meter"
|
||||
aria-label={`Progress Bar at ${progress.toFixed(0)}%`}
|
||||
aria-valuenow={progress.toFixed(0)}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
style={{ width: progressPercent }}
|
||||
|
||||
@@ -54,7 +54,7 @@ function Logger(minimumLogLevel) {
|
||||
}
|
||||
|
||||
Logger.prototype.cleanse = function(message) {
|
||||
const apikey = new RegExp(`access_token=${window.Prowlarr.apiKey}`, 'g');
|
||||
const apikey = new RegExp(`access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`, 'g');
|
||||
return message.replace(apikey, 'access_token=(removed)');
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ class SignalRConnector extends Component {
|
||||
|
||||
this.connection = new signalR.HubConnectionBuilder()
|
||||
.configureLogging(new Logger(signalR.LogLevel.Information))
|
||||
.withUrl(`${url}?access_token=${window.Prowlarr.apiKey}`)
|
||||
.withUrl(`${url}?access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`)
|
||||
.withAutomaticReconnect({
|
||||
nextRetryDelayInMilliseconds: (retryContext) => {
|
||||
if (retryContext.elapsedMilliseconds > 180000) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||
import HistoryRowParameter from './HistoryRowParameter';
|
||||
@@ -193,7 +194,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.season ?
|
||||
<HistoryRowParameter
|
||||
title='Season'
|
||||
title={translate('Season')}
|
||||
value={data.season}
|
||||
/> :
|
||||
null
|
||||
@@ -202,7 +203,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.episode ?
|
||||
<HistoryRowParameter
|
||||
title='Episode'
|
||||
title={translate('Episode')}
|
||||
value={data.episode}
|
||||
/> :
|
||||
null
|
||||
@@ -211,7 +212,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.artist ?
|
||||
<HistoryRowParameter
|
||||
title='Artist'
|
||||
title={translate('Artist')}
|
||||
value={data.artist}
|
||||
/> :
|
||||
null
|
||||
@@ -220,7 +221,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.album ?
|
||||
<HistoryRowParameter
|
||||
title='Album'
|
||||
title={translate('Album')}
|
||||
value={data.album}
|
||||
/> :
|
||||
null
|
||||
@@ -229,7 +230,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.label ?
|
||||
<HistoryRowParameter
|
||||
title='Label'
|
||||
title={translate('Label')}
|
||||
value={data.label}
|
||||
/> :
|
||||
null
|
||||
@@ -238,7 +239,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.track ?
|
||||
<HistoryRowParameter
|
||||
title='Track'
|
||||
title={translate('Track')}
|
||||
value={data.track}
|
||||
/> :
|
||||
null
|
||||
@@ -247,7 +248,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.year ?
|
||||
<HistoryRowParameter
|
||||
title='Year'
|
||||
title={translate('Year')}
|
||||
value={data.year}
|
||||
/> :
|
||||
null
|
||||
@@ -256,7 +257,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.genre ?
|
||||
<HistoryRowParameter
|
||||
title='Genre'
|
||||
title={translate('Genre')}
|
||||
value={data.genre}
|
||||
/> :
|
||||
null
|
||||
@@ -265,7 +266,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.author ?
|
||||
<HistoryRowParameter
|
||||
title='Author'
|
||||
title={translate('Author')}
|
||||
value={data.author}
|
||||
/> :
|
||||
null
|
||||
@@ -274,7 +275,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.bookTitle ?
|
||||
<HistoryRowParameter
|
||||
title='Book'
|
||||
title={translate('Book')}
|
||||
value={data.bookTitle}
|
||||
/> :
|
||||
null
|
||||
@@ -283,7 +284,7 @@ class HistoryRow extends Component {
|
||||
{
|
||||
data.publisher ?
|
||||
<HistoryRowParameter
|
||||
title='Publisher'
|
||||
title={translate('Publisher')}
|
||||
value={data.publisher}
|
||||
/> :
|
||||
null
|
||||
@@ -351,6 +352,11 @@ class HistoryRow extends Component {
|
||||
`${data.elapsedTime}ms` :
|
||||
null
|
||||
}
|
||||
{
|
||||
data.cached === '1' ?
|
||||
' (cached)' :
|
||||
null
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -376,14 +382,14 @@ class HistoryRow extends Component {
|
||||
<IconButton
|
||||
name={icons.SEARCH}
|
||||
onPress={this.onSearchPress}
|
||||
title='Repeat Search'
|
||||
title={translate('RepeatSearch')}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
<IconButton
|
||||
name={icons.INFO}
|
||||
onPress={this.onDetailsPress}
|
||||
title='History Details'
|
||||
title={translate('HistoryDetails')}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
|
||||
@@ -222,7 +222,11 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<IndexerIndexSelectModeButton
|
||||
label={isSelectMode ? 'Stop Selecting' : 'Select Indexer'}
|
||||
label={
|
||||
isSelectMode
|
||||
? translate('StopSelecting')
|
||||
: translate('SelectIndexer')
|
||||
}
|
||||
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
|
||||
isSelectMode={isSelectMode}
|
||||
overflowComponent={IndexerIndexSelectModeMenuItem}
|
||||
@@ -230,7 +234,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
||||
/>
|
||||
|
||||
<IndexerIndexSelectAllButton
|
||||
label="SelectAll"
|
||||
label={translate('SelectAll')}
|
||||
isSelectMode={isSelectMode}
|
||||
overflowComponent={IndexerIndexSelectAllMenuItem}
|
||||
/>
|
||||
@@ -245,7 +249,10 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
||||
optionsComponent={IndexerIndexTableOptions}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<PageToolbarButton label="Options" iconName={icons.TABLE} />
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
@@ -276,7 +283,9 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
||||
>
|
||||
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
|
||||
|
||||
{!isFetching && !!error ? <div>Unable to load indexers</div> : null}
|
||||
{!isFetching && !!error ? (
|
||||
<div>{translate('UnableToLoadIndexers')}</div>
|
||||
) : null}
|
||||
|
||||
{isLoaded ? (
|
||||
<div className={styles.contentBodyContainer}>
|
||||
@@ -295,7 +304,10 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
||||
) : null}
|
||||
|
||||
{!error && isPopulated && !items.length ? (
|
||||
<NoIndexer totalItems={totalItems} />
|
||||
<NoIndexer
|
||||
totalItems={totalItems}
|
||||
onAddIndexerPress={onAddIndexerPress}
|
||||
/>
|
||||
) : null}
|
||||
</PageContentBody>
|
||||
{isLoaded && !!jumpBarItems.order.length ? (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
interface IndexerIndexSelectAllButtonProps {
|
||||
label: string;
|
||||
@@ -32,7 +33,7 @@ function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {
|
||||
|
||||
return isSelectMode ? (
|
||||
<PageToolbarButton
|
||||
label={allSelected ? 'Unselect All' : 'Select All'}
|
||||
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||
iconName={icon}
|
||||
onPress={onPress}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
interface IndexerIndexSelectAllMenuItemProps {
|
||||
label: string;
|
||||
@@ -33,7 +34,7 @@ function IndexerIndexSelectAllMenuItem(
|
||||
|
||||
return isSelectMode ? (
|
||||
<PageToolbarOverflowMenuItem
|
||||
label={allSelected ? 'Unselect All' : 'Select All'}
|
||||
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||
iconName={iconName}
|
||||
onPress={onPressWrapper}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
|
||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './TagsModalContent.css';
|
||||
|
||||
interface TagsModalContentProps {
|
||||
@@ -70,7 +71,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
@@ -81,7 +82,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Apply Tags</FormLabel>
|
||||
<FormLabel>{translate('ApplyTags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
@@ -89,17 +90,17 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
value={applyTags}
|
||||
values={applyTagsOptions}
|
||||
helpTexts={[
|
||||
'How to apply tags to the selected series',
|
||||
'Add: Add the tags the existing list of tags',
|
||||
'Remove: Remove the entered tags',
|
||||
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)',
|
||||
translate('ApplyTagsHelpTexts1'),
|
||||
translate('ApplyTagsHelpTexts2'),
|
||||
translate('ApplyTagsHelpTexts3'),
|
||||
translate('ApplyTagsHelpTexts4'),
|
||||
]}
|
||||
onChange={onApplyTagsChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Result</FormLabel>
|
||||
<FormLabel>{translate('Result')}</FormLabel>
|
||||
|
||||
<div className={styles.result}>
|
||||
{indexerTags.map((id) => {
|
||||
@@ -116,7 +117,11 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
return (
|
||||
<Label
|
||||
key={tag.id}
|
||||
title={removeTag ? 'Removing tag' : 'Existing tag'}
|
||||
title={
|
||||
removeTag
|
||||
? translate('RemoveTagRemovingTag')
|
||||
: translate('RemoveTagExistingTag')
|
||||
}
|
||||
kind={removeTag ? kinds.INVERSE : kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
@@ -140,7 +145,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
return (
|
||||
<Label
|
||||
key={tag.id}
|
||||
title={'Adding tag'}
|
||||
title={translate('AddingTag')}
|
||||
kind={kinds.SUCCESS}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
|
||||
@@ -55,7 +55,11 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
|
||||
const vipExpiration =
|
||||
fields.find((field) => field.name === 'vipExpiration')?.value ?? '';
|
||||
|
||||
const rssUrl = `${window.location.origin}${window.Prowlarr.urlBase}/${id}/api?t=search&extended=1&apikey=${window.Prowlarr.apiKey}`;
|
||||
const rssUrl = `${window.location.origin}${
|
||||
window.Prowlarr.urlBase
|
||||
}/${id}/api?apikey=${encodeURIComponent(
|
||||
window.Prowlarr.apiKey
|
||||
)}&extended=1&t=search`;
|
||||
|
||||
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
|
||||
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
|
||||
|
||||
@@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import selectTableOptions from './selectTableOptions';
|
||||
|
||||
interface IndexerIndexTableOptionsProps {
|
||||
@@ -32,13 +33,13 @@ function IndexerIndexTableOptions(props: IndexerIndexTableOptionsProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
<FormGroup>
|
||||
<FormLabel>Show Search</FormLabel>
|
||||
<FormLabel>{translate('ShowSearch')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showSearchAction"
|
||||
value={showSearchAction}
|
||||
helpText="Show search button on hover"
|
||||
helpText={translate('ShowSearchHelpText')}
|
||||
onChange={onTableOptionChangeWrapper}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -33,8 +33,8 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
|
||||
const enableKind = redirect ? kinds.INFO : kinds.SUCCESS;
|
||||
const enableIcon = redirect ? icons.REDIRECT : icons.CHECK;
|
||||
const enableTitle = redirect
|
||||
? 'Indexer is Enabled, Redirect is Enabled'
|
||||
: 'Indexer is Enabled';
|
||||
? translate('EnabledRedirected')
|
||||
: translate('Enabled');
|
||||
|
||||
return (
|
||||
<Component className={className} {...otherProps}>
|
||||
@@ -43,7 +43,7 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
|
||||
className={styles.statusIcon}
|
||||
kind={enabled ? enableKind : kinds.DEFAULT}
|
||||
name={enabled ? enableIcon : icons.BLOCKLIST}
|
||||
title={enabled ? enableTitle : 'Indexer is Disabled'}
|
||||
title={enabled ? enableTitle : translate('EnabledIndexerIsDisabled')}
|
||||
/>
|
||||
}
|
||||
{status ? (
|
||||
|
||||
@@ -10,6 +10,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import { align, kinds } from 'Helpers/Props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import StatsFilterMenu from './StatsFilterMenu';
|
||||
import styles from './Stats.css';
|
||||
|
||||
@@ -188,53 +189,53 @@ function Stats(props) {
|
||||
<div className={styles.fullWidthChart}>
|
||||
<BarChart
|
||||
data={getAverageResponseTimeData(item.indexers)}
|
||||
title='Average Response Times (ms)'
|
||||
title={translate('AverageResponseTimesMs')}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.fullWidthChart}>
|
||||
<BarChart
|
||||
data={getFailureRateData(item.indexers)}
|
||||
title='Indexer Failure Rate'
|
||||
title={translate('IndexerFailureRate')}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<StackedBarChart
|
||||
data={getTotalRequestsData(item.indexers)}
|
||||
title='Total Indexer Queries'
|
||||
title={translate('TotalIndexerQueries')}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<BarChart
|
||||
data={getNumberGrabsData(item.indexers)}
|
||||
title='Total Indexer Successful Grabs'
|
||||
title={translate('TotalIndexerSuccessfulGrabs')}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<BarChart
|
||||
data={getUserAgentQueryData(item.userAgents)}
|
||||
title='Total User Agent Queries'
|
||||
title={translate('TotalUserAgentQueries')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<BarChart
|
||||
data={getUserAgentGrabsData(item.userAgents)}
|
||||
title='Total User Agent Grabs'
|
||||
title={translate('TotalUserAgentGrabs')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<DoughnutChart
|
||||
data={getHostQueryData(item.hosts)}
|
||||
title='Total Host Queries'
|
||||
title={translate('TotalHostQueries')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<DoughnutChart
|
||||
data={getHostGrabsData(item.hosts)}
|
||||
title='Total Host Grabs'
|
||||
title={translate('TotalHostGrabs')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagList from 'Components/TagList';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditApplicationModalConnector from './EditApplicationModalConnector';
|
||||
@@ -55,7 +56,9 @@ class Application extends Component {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
syncLevel
|
||||
syncLevel,
|
||||
tags,
|
||||
tagList
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -92,6 +95,11 @@ class Application extends Component {
|
||||
</Label>
|
||||
}
|
||||
|
||||
<TagList
|
||||
tags={tags}
|
||||
tagList={tagList}
|
||||
/>
|
||||
|
||||
<EditApplicationModalConnector
|
||||
id={id}
|
||||
isOpen={this.state.isEditApplicationModalOpen}
|
||||
@@ -117,6 +125,8 @@ Application.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
syncLevel: PropTypes.string.isRequired,
|
||||
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onConfirmDeleteApplication: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ class Applications extends Component {
|
||||
render() {
|
||||
const {
|
||||
items,
|
||||
tagList,
|
||||
onConfirmDeleteApplication,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -71,6 +72,7 @@ class Applications extends Component {
|
||||
<Application
|
||||
key={item.id}
|
||||
{...item}
|
||||
tagList={tagList}
|
||||
onConfirmDeleteApplication={onConfirmDeleteApplication}
|
||||
/>
|
||||
);
|
||||
@@ -109,6 +111,7 @@ Applications.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onConfirmDeleteApplication: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteApplication, fetchApplications } from 'Store/Actions/settingsActions';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import Applications from './Applications';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createSortedSectionSelector('settings.applications', sortByName),
|
||||
(applications) => applications
|
||||
createTagsSelector(),
|
||||
(applications, tagList) => {
|
||||
return {
|
||||
...applications,
|
||||
tagList
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ function HostSettings(props) {
|
||||
port,
|
||||
urlBase,
|
||||
instanceName,
|
||||
applicationUrl,
|
||||
enableSsl,
|
||||
sslPort,
|
||||
sslCertPath,
|
||||
@@ -89,6 +90,21 @@ function HostSettings(props) {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('ApplicationURL')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="applicationUrl"
|
||||
helpText={translate('ApplicationUrlHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...applicationUrl}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagList from 'Components/TagList';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditNotificationModalConnector from './EditNotificationModalConnector';
|
||||
@@ -57,10 +58,14 @@ class Notification extends Component {
|
||||
name,
|
||||
onGrab,
|
||||
onHealthIssue,
|
||||
onHealthRestored,
|
||||
onApplicationUpdate,
|
||||
supportsOnGrab,
|
||||
supportsOnHealthIssue,
|
||||
supportsOnApplicationUpdate
|
||||
supportsOnHealthRestored,
|
||||
supportsOnApplicationUpdate,
|
||||
tags,
|
||||
tagList
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -74,17 +79,27 @@ class Notification extends Component {
|
||||
</div>
|
||||
|
||||
{
|
||||
supportsOnGrab && onGrab &&
|
||||
supportsOnGrab && onGrab ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnGrab')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnHealthIssue && onHealthIssue &&
|
||||
supportsOnHealthIssue && onHealthIssue ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnHealthIssue')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnHealthRestored && onHealthRestored ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnHealthRestored')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
@@ -96,7 +111,7 @@ class Notification extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!onGrab && !onHealthIssue && !onApplicationUpdate ?
|
||||
!onGrab && !onHealthIssue && !onHealthRestored && !onApplicationUpdate ?
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
@@ -106,6 +121,11 @@ class Notification extends Component {
|
||||
null
|
||||
}
|
||||
|
||||
<TagList
|
||||
tags={tags}
|
||||
tagList={tagList}
|
||||
/>
|
||||
|
||||
<EditNotificationModalConnector
|
||||
id={id}
|
||||
isOpen={this.state.isEditNotificationModalOpen}
|
||||
@@ -132,10 +152,14 @@ Notification.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onGrab: PropTypes.bool.isRequired,
|
||||
onHealthIssue: PropTypes.bool.isRequired,
|
||||
onHealthRestored: PropTypes.bool.isRequired,
|
||||
onApplicationUpdate: PropTypes.bool.isRequired,
|
||||
supportsOnGrab: PropTypes.bool.isRequired,
|
||||
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
||||
supportsOnHealthRestored: PropTypes.bool.isRequired,
|
||||
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
||||
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ function NotificationEventItems(props) {
|
||||
const {
|
||||
onGrab,
|
||||
onHealthIssue,
|
||||
onHealthRestored,
|
||||
onApplicationUpdate,
|
||||
supportsOnGrab,
|
||||
includeManualGrabs,
|
||||
supportsOnHealthIssue,
|
||||
supportsOnHealthRestored,
|
||||
includeHealthWarnings,
|
||||
supportsOnApplicationUpdate
|
||||
} = item;
|
||||
@@ -70,8 +72,19 @@ function NotificationEventItems(props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onHealthRestored"
|
||||
helpText={translate('OnHealthRestoredHelpText')}
|
||||
isDisabled={!supportsOnHealthRestored.value}
|
||||
{...onHealthRestored}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
onHealthIssue.value &&
|
||||
(onHealthIssue.value || onHealthRestored.value) &&
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
|
||||
@@ -49,6 +49,7 @@ class Notifications extends Component {
|
||||
render() {
|
||||
const {
|
||||
items,
|
||||
tagList,
|
||||
onConfirmDeleteNotification,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -71,6 +72,7 @@ class Notifications extends Component {
|
||||
<Notification
|
||||
key={item.id}
|
||||
{...item}
|
||||
tagList={tagList}
|
||||
onConfirmDeleteNotification={onConfirmDeleteNotification}
|
||||
/>
|
||||
);
|
||||
@@ -109,6 +111,7 @@ Notifications.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import Notifications from './Notifications';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createSortedSectionSelector('settings.notifications', sortByName),
|
||||
(notifications) => notifications
|
||||
createTagsSelector(),
|
||||
(notifications, tagList) => {
|
||||
return {
|
||||
...notifications,
|
||||
tagList
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
@define-mixin scrollbar {
|
||||
scrollbar-color: var(--scrollbarBackgroundColor) transparent;
|
||||
scrollbar-width: thin;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@@ -74,9 +74,9 @@ module.exports = {
|
||||
|
||||
defaultButtonTextColor: '#eee',
|
||||
defaultButtonBackgroundColor: '#333',
|
||||
defaultBorderColor: '#eaeaea',
|
||||
defaultBorderColor: '#393f45',
|
||||
defaultHoverBackgroundColor: '#444',
|
||||
defaultHoverBorderColor: '#d6d6d6',
|
||||
defaultHoverBorderColor: '#5a6265',
|
||||
|
||||
primaryBackgroundColor: '#5d9cec',
|
||||
primaryBorderColor: '#5899eb',
|
||||
|
||||
62
package.json
62
package.json
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"build": "webpack --config ./frontend/build/webpack.config.js",
|
||||
"prebuild": "yarn clean",
|
||||
"clean": "rimraf ./_output/UI && rimraf -g \"**/*.js.map\"",
|
||||
"clean": "rimraf ./_output/UI && rimraf --glob \"**/*.js.map\"",
|
||||
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
|
||||
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
|
||||
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
|
||||
@@ -20,10 +20,7 @@
|
||||
"readmeFilename": "readme.md",
|
||||
"main": "index.js",
|
||||
"browserslist": [
|
||||
">0.25%",
|
||||
"not ie 11",
|
||||
"not op_mini all",
|
||||
"not chrome < 60"
|
||||
"defaults"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "6.4.0",
|
||||
@@ -33,13 +30,12 @@
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@juggle/resize-observer": "3.4.0",
|
||||
"@microsoft/signalr": "6.0.16",
|
||||
"@sentry/browser": "7.46.0",
|
||||
"@sentry/integrations": "7.46.0",
|
||||
"@types/jest": "29.5.0",
|
||||
"@sentry/browser": "7.51.2",
|
||||
"@sentry/integrations": "7.51.2",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/react": "18.0.31",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"chart.js": "4.2.1",
|
||||
"@types/react": "18.2.6",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"chart.js": "4.3.0",
|
||||
"classnames": "2.3.2",
|
||||
"clipboard": "2.0.11",
|
||||
"connected-react-router": "6.9.3",
|
||||
@@ -48,7 +44,7 @@
|
||||
"history": "4.10.1",
|
||||
"https-browserify": "1.0.0",
|
||||
"jdu": "1.0.0",
|
||||
"jquery": "3.6.4",
|
||||
"jquery": "3.7.0",
|
||||
"lodash": "4.17.21",
|
||||
"mobile-detect": "1.4.5",
|
||||
"moment": "2.29.4",
|
||||
@@ -86,36 +82,33 @@
|
||||
"redux-thunk": "2.4.2",
|
||||
"reselect": "4.1.7",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"typescript": "5.0.3"
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.21.3",
|
||||
"@babel/eslint-parser": "7.21.3",
|
||||
"@babel/core": "7.21.8",
|
||||
"@babel/eslint-parser": "7.21.8",
|
||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "7.21.0",
|
||||
"@babel/plugin-proposal-export-default-from": "7.18.10",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
||||
"@babel/plugin-proposal-function-sent": "7.18.6",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.18.6",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@babel/preset-env": "7.21.5",
|
||||
"@babel/preset-react": "7.18.6",
|
||||
"@babel/preset-typescript": "7.21.0",
|
||||
"@babel/preset-typescript": "7.21.5",
|
||||
"@types/react-window": "1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "5.57.0",
|
||||
"@typescript-eslint/parser": "5.57.0",
|
||||
"@types/webpack-livereload-plugin": "2.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||
"@typescript-eslint/parser": "5.59.5",
|
||||
"are-you-es5": "2.1.2",
|
||||
"autoprefixer": "10.4.14",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.29.1",
|
||||
"core-js": "3.30.2",
|
||||
"css-loader": "6.7.3",
|
||||
"css-modules-typescript-loader": "4.0.1",
|
||||
"eslint": "8.37.0",
|
||||
"eslint": "8.40.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-plugin-filenames": "1.3.2",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
@@ -126,29 +119,30 @@
|
||||
"file-loader": "6.2.0",
|
||||
"filemanager-webpack-plugin": "8.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "8.0.0",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"html-webpack-plugin": "5.5.1",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "2.7.5",
|
||||
"postcss": "8.4.21",
|
||||
"postcss": "8.4.23",
|
||||
"postcss-color-function": "4.1.0",
|
||||
"postcss-loader": "7.1.0",
|
||||
"postcss-loader": "7.3.0",
|
||||
"postcss-mixins": "9.0.4",
|
||||
"postcss-nested": "6.0.1",
|
||||
"postcss-simple-vars": "7.0.1",
|
||||
"postcss-url": "10.1.3",
|
||||
"prettier": "2.8.7",
|
||||
"prettier": "2.8.8",
|
||||
"require-nocache": "1.0.0",
|
||||
"rimraf": "4.4.1",
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "3.3.2",
|
||||
"stylelint": "14.16.0",
|
||||
"stylelint-order": "5.0.0",
|
||||
"stylelint": "15.6.1",
|
||||
"stylelint-order": "6.0.3",
|
||||
"terser-webpack-plugin": "5.3.8",
|
||||
"ts-loader": "9.4.2",
|
||||
"typescript-plugin-css-modules": "5.0.0",
|
||||
"typescript-plugin-css-modules": "5.0.1",
|
||||
"url-loader": "4.1.1",
|
||||
"webpack": "5.77.0",
|
||||
"webpack-cli": "5.0.1",
|
||||
"webpack": "5.82.1",
|
||||
"webpack-cli": "5.1.1",
|
||||
"webpack-livereload-plugin": "3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<PropertyGroup>
|
||||
<AnalysisLevel>6.0-all</AnalysisLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;osx-arm64;linux-x64;linux-musl-x64;linux-musl-arm;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
|
||||
@@ -24,7 +26,7 @@
|
||||
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('Prowlarr'))">true</ProwlarrProject>
|
||||
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</ProwlarrProject>
|
||||
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</ProwlarrProject>
|
||||
|
||||
|
||||
<!-- A test project gets the test sdk packages automatically added -->
|
||||
<TestProject>false</TestProject>
|
||||
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
|
||||
@@ -50,7 +52,7 @@
|
||||
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<!-- Test projects need bindingRedirects -->
|
||||
<PropertyGroup Condition="'$(ProwlarrOutputType)'=='Test'">
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
@@ -63,7 +65,7 @@
|
||||
<Product>Prowlarr</Product>
|
||||
<Company>prowlarr.com</Company>
|
||||
<Copyright>Copyright 2014-$([System.DateTime]::Now.ToString('yyyy')) prowlarr.com (GNU General Public v3)</Copyright>
|
||||
|
||||
|
||||
<!-- Should be replaced by CI -->
|
||||
<AssemblyVersion>10.0.0.*</AssemblyVersion>
|
||||
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
|
||||
@@ -72,7 +74,7 @@
|
||||
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
|
||||
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
|
||||
|
||||
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
|
||||
|
||||
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
|
||||
@@ -94,7 +96,7 @@
|
||||
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
|
||||
@@ -11,7 +11,6 @@ using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using OpenQA.Selenium.Remote;
|
||||
|
||||
namespace NzbDrone.Automation.Test
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -352,6 +351,26 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
.Verify(v => v.DeleteFile(_targetPath), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_rollback_move_on_partial_if_destination_already_exists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
|
||||
.Callback(() =>
|
||||
{
|
||||
WithExistingFile(_targetPath, true, 900);
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
|
||||
.Throws(new FileAlreadyExistsException("File already exists", _targetPath));
|
||||
|
||||
Assert.Throws<FileAlreadyExistsException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFile(_targetPath), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_if_rollback_partialmove_fails()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -12,14 +13,14 @@ namespace NzbDrone.Common.Test.EnsureTest
|
||||
public void EnsureWindowsPath(string path)
|
||||
{
|
||||
WindowsOnly();
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
}
|
||||
|
||||
[TestCase(@"/var/user/file with, comma.mkv")]
|
||||
public void EnsureLinuxPath(string path)
|
||||
{
|
||||
PosixOnly();
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace NzbDrone.Common.Test
|
||||
[TestCase(@"\\Testserver\Test\file.ext", @"\\Testserver\Test\file.ext")]
|
||||
[TestCase(@"\\Testserver\Test\file.ext\\", @"\\Testserver\Test\file.ext")]
|
||||
[TestCase(@"\\Testserver\Test\file.ext \\", @"\\Testserver\Test\file.ext")]
|
||||
[TestCase(@"//CAPITAL//lower// ", @"\\CAPITAL\lower")]
|
||||
public void Clean_Path_Windows(string dirty, string clean)
|
||||
{
|
||||
WindowsOnly();
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Cache
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
private void CheckFolderExists(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (!FolderExists(path))
|
||||
{
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
private void CheckFileExists(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (!FileExists(path))
|
||||
{
|
||||
@@ -93,19 +93,19 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public bool FolderExists(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
return FileExists(path, PathStringComparison);
|
||||
}
|
||||
|
||||
public bool FileExists(string path, StringComparison stringComparison)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
switch (stringComparison)
|
||||
{
|
||||
@@ -125,7 +125,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public bool FolderWritable(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -144,35 +144,35 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public bool FolderEmpty(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return Directory.EnumerateFileSystemEntries(path).Empty();
|
||||
}
|
||||
|
||||
public string[] GetDirectories(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return Directory.GetDirectories(path);
|
||||
}
|
||||
|
||||
public string[] GetFiles(string path, SearchOption searchOption)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return Directory.GetFiles(path, "*.*", searchOption);
|
||||
}
|
||||
|
||||
public long GetFolderSize(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return GetFiles(path, SearchOption.AllDirectories).Sum(e => new FileInfo(e).Length);
|
||||
}
|
||||
|
||||
public long GetFileSize(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (!FileExists(path))
|
||||
{
|
||||
@@ -185,13 +185,13 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void CreateFolder(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
Logger.Trace("Deleting file: {0}", path);
|
||||
|
||||
RemoveReadOnly(path);
|
||||
@@ -201,8 +201,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void CloneFile(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (source.PathEquals(destination))
|
||||
{
|
||||
@@ -219,8 +219,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void CopyFile(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (source.PathEquals(destination))
|
||||
{
|
||||
@@ -237,8 +237,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void MoveFile(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (source.PathEquals(destination))
|
||||
{
|
||||
@@ -256,14 +256,19 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void MoveFolder(string source, string destination, bool overwrite = false)
|
||||
{
|
||||
Ensure.That(source, () => source).IsValidPath();
|
||||
Ensure.That(destination, () => destination).IsValidPath();
|
||||
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
Directory.Move(source, destination);
|
||||
}
|
||||
|
||||
protected virtual void MoveFileInternal(string source, string destination)
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
throw new FileAlreadyExistsException("File already exists", destination);
|
||||
}
|
||||
|
||||
File.Move(source, destination);
|
||||
}
|
||||
|
||||
@@ -281,7 +286,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void DeleteFolder(string path, bool recursive)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
||||
Array.ForEach(files, RemoveReadOnly);
|
||||
@@ -291,14 +296,14 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public string ReadAllText(string filePath)
|
||||
{
|
||||
Ensure.That(filePath, () => filePath).IsValidPath();
|
||||
Ensure.That(filePath, () => filePath).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return File.ReadAllText(filePath);
|
||||
}
|
||||
|
||||
public void WriteAllText(string filename, string contents)
|
||||
{
|
||||
Ensure.That(filename, () => filename).IsValidPath();
|
||||
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
|
||||
RemoveReadOnly(filename);
|
||||
|
||||
// File.WriteAllText is broken on net core when writing to some CIFS mounts
|
||||
@@ -314,7 +319,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void FolderSetLastWriteTime(string path, DateTime dateTime)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (dateTime.Before(DateTimeExtensions.Epoch))
|
||||
{
|
||||
@@ -326,7 +331,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void FileSetLastWriteTime(string path, DateTime dateTime)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
if (dateTime.Before(DateTimeExtensions.Epoch))
|
||||
{
|
||||
@@ -353,14 +358,14 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public virtual string GetPathRoot(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return Path.GetPathRoot(path);
|
||||
}
|
||||
|
||||
public string GetParentFolder(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
var parent = Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
|
||||
|
||||
@@ -407,7 +412,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public void EmptyFolder(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
@@ -496,7 +501,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public List<DirectoryInfo> GetDirectoryInfos(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
var di = new DirectoryInfo(path);
|
||||
|
||||
@@ -505,14 +510,14 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public FileInfo GetFileInfo(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
return new FileInfo(path);
|
||||
}
|
||||
|
||||
public List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
var di = new DirectoryInfo(path);
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
|
||||
{
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
sourcePath = ResolveRealParentPath(sourcePath);
|
||||
targetPath = ResolveRealParentPath(targetPath);
|
||||
@@ -140,8 +140,8 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
var filesCopied = 0;
|
||||
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
sourcePath = ResolveRealParentPath(sourcePath);
|
||||
targetPath = ResolveRealParentPath(targetPath);
|
||||
@@ -255,8 +255,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
|
||||
{
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
|
||||
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
|
||||
|
||||
sourcePath = ResolveRealParentPath(sourcePath);
|
||||
targetPath = ResolveRealParentPath(targetPath);
|
||||
@@ -500,9 +500,13 @@ namespace NzbDrone.Common.Disk
|
||||
throw new IOException(string.Format("File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
RollbackPartialMove(sourcePath, targetPath);
|
||||
if (ex is not FileAlreadyExistsException)
|
||||
{
|
||||
RollbackPartialMove(sourcePath, targetPath);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
15
src/NzbDrone.Common/Disk/FileAlreadyExistsException.cs
Normal file
15
src/NzbDrone.Common/Disk/FileAlreadyExistsException.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public class FileAlreadyExistsException : Exception
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
|
||||
public FileAlreadyExistsException(string message, string filename)
|
||||
: base(message)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (
|
||||
allowFoldersWithoutTrailingSlashes &&
|
||||
query.IsPathValid() &&
|
||||
query.IsPathValid(PathValidationType.CurrentOs) &&
|
||||
_diskProvider.FolderExists(query))
|
||||
{
|
||||
return GetResult(query, includeFiles);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
@@ -162,7 +161,7 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid => _path.IsPathValid();
|
||||
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
|
||||
|
||||
private int GetFileNameIndex()
|
||||
{
|
||||
|
||||
8
src/NzbDrone.Common/Disk/PathValidationType.cs
Normal file
8
src/NzbDrone.Common/Disk/PathValidationType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public enum PathValidationType
|
||||
{
|
||||
CurrentOs,
|
||||
AnyOs
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnsureThat.Resources;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -111,14 +112,14 @@ namespace NzbDrone.Common.EnsureThat
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static Param<string> IsValidPath(this Param<string> param)
|
||||
public static Param<string> IsValidPath(this Param<string> param, PathValidationType validationType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(param.Value))
|
||||
{
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, ExceptionMessages.EnsureExtensions_IsNotNullOrWhiteSpace);
|
||||
}
|
||||
|
||||
if (param.Value.IsPathValid())
|
||||
if (param.Value.IsPathValid(validationType))
|
||||
{
|
||||
return param;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
@@ -29,12 +29,12 @@ namespace NzbDrone.Common.Extensions
|
||||
public static string CleanFilePath(this string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
Ensure.That(path, () => path).IsValidPath(PathValidationType.AnyOs);
|
||||
|
||||
var info = new FileInfo(path.Trim());
|
||||
|
||||
// UNC
|
||||
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
|
||||
if (!info.FullName.Contains('/') && info.FullName.StartsWith(@"\\"))
|
||||
{
|
||||
return info.FullName.TrimEnd('/', '\\', ' ');
|
||||
}
|
||||
@@ -136,24 +136,24 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
private static readonly Regex WindowsPathWithDriveRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled);
|
||||
|
||||
public static bool IsPathValid(this string path)
|
||||
public static bool IsPathValid(this string path, PathValidationType validationType)
|
||||
{
|
||||
if (path.ContainsInvalidPathChars() || string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (validationType == PathValidationType.AnyOs)
|
||||
{
|
||||
return IsPathValidForWindows(path) || IsPathValidForNonWindows(path);
|
||||
}
|
||||
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
return path.StartsWith(Path.DirectorySeparatorChar.ToString());
|
||||
return IsPathValidForNonWindows(path);
|
||||
}
|
||||
|
||||
if (path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return IsPathValidForWindows(path);
|
||||
}
|
||||
|
||||
public static bool ContainsInvalidPathChars(this string text)
|
||||
@@ -337,5 +337,15 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
|
||||
}
|
||||
|
||||
private static bool IsPathValidForWindows(string path)
|
||||
{
|
||||
return path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path);
|
||||
}
|
||||
|
||||
private static bool IsPathValidForNonWindows(string path)
|
||||
{
|
||||
return path.StartsWith("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ namespace NzbDrone.Common
|
||||
return $"{mCrc:x8}";
|
||||
}
|
||||
|
||||
public static string ComputeSha256Hash(string rawData)
|
||||
{
|
||||
using var sha256Hash = SHA256.Create();
|
||||
var hashBytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
|
||||
|
||||
return Convert.ToHexString(hashBytes);
|
||||
}
|
||||
|
||||
public static string CalculateMd5(string s)
|
||||
{
|
||||
// Use input string to calculate MD5 hash
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
@@ -9,7 +8,6 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
@@ -247,7 +245,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
|
||||
private void AddContentHeader(HttpRequestMessage request, string header, string value)
|
||||
private static void AddContentHeader(HttpRequestMessage request, string header, string value)
|
||||
{
|
||||
var headers = request.Content?.Headers;
|
||||
if (headers == null)
|
||||
|
||||
@@ -322,11 +322,12 @@ namespace NzbDrone.Common.Http
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
var request = new HttpRequest(url);
|
||||
request.AllowAutoRedirect = true;
|
||||
request.ResponseStream = fileStream;
|
||||
request.RequestTimeout = TimeSpan.FromSeconds(300);
|
||||
var response = await GetAsync(request);
|
||||
|
||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Targets;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Sentry;
|
||||
using Sentry.Protocol;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
{
|
||||
|
||||
@@ -12,7 +12,6 @@ using Npgsql;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using Sentry;
|
||||
using Sentry.Protocol;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
|
||||
@@ -131,14 +131,7 @@ namespace NzbDrone.Common.Processes
|
||||
var key = environmentVariable.Key.ToString();
|
||||
var value = environmentVariable.Value?.ToString();
|
||||
|
||||
if (startInfo.EnvironmentVariables.ContainsKey(key))
|
||||
{
|
||||
startInfo.EnvironmentVariables[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo.EnvironmentVariables.Add(key, value);
|
||||
}
|
||||
startInfo.EnvironmentVariables[key] = value;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -320,7 +313,7 @@ namespace NzbDrone.Common.Processes
|
||||
processInfo = new ProcessInfo();
|
||||
processInfo.Id = process.Id;
|
||||
processInfo.Name = process.ProcessName;
|
||||
processInfo.StartPath = GetExeFileName(process);
|
||||
processInfo.StartPath = process.MainModule.FileName;
|
||||
|
||||
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
||||
{
|
||||
@@ -335,16 +328,6 @@ namespace NzbDrone.Common.Processes
|
||||
return processInfo;
|
||||
}
|
||||
|
||||
private static string GetExeFileName(Process process)
|
||||
{
|
||||
if (process.MainModule.FileName != "mono.exe")
|
||||
{
|
||||
return process.MainModule.FileName;
|
||||
}
|
||||
|
||||
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
|
||||
}
|
||||
|
||||
private List<Process> GetProcessesByName(string name)
|
||||
{
|
||||
//TODO: move this to an OS specific class
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using NLog;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Cloud;
|
||||
@@ -10,7 +9,6 @@ using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Http;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class ReleaseSearchServiceFixture : CoreTest<ReleaseSearchService>
|
||||
{
|
||||
private Mock<IIndexer> _mockIndexer;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_mockIndexer = Mocker.GetMock<IIndexer>();
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = 1 });
|
||||
_mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(s => s.Enabled(It.IsAny<bool>()))
|
||||
.Returns(new List<IIndexer> { _mockIndexer.Object });
|
||||
}
|
||||
|
||||
private List<SearchCriteriaBase> WatchForSearchCriteria()
|
||||
{
|
||||
var result = new List<SearchCriteriaBase>();
|
||||
|
||||
_mockIndexer.Setup(v => v.Fetch(It.IsAny<MovieSearchCriteria>()))
|
||||
.Callback<MovieSearchCriteria>(s => result.Add(s))
|
||||
.Returns(Task.FromResult(new IndexerPageableQueryResult()));
|
||||
|
||||
_mockIndexer.Setup(v => v.Fetch(It.IsAny<TvSearchCriteria>()))
|
||||
.Callback<TvSearchCriteria>(s => result.Add(s))
|
||||
.Returns(Task.FromResult(new IndexerPageableQueryResult()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[TestCase("tt0183790", "0183790")]
|
||||
[TestCase("0183790", "0183790")]
|
||||
[TestCase("183790", "0183790")]
|
||||
[TestCase("tt10001870", "10001870")]
|
||||
[TestCase("10001870", "10001870")]
|
||||
public void should_normalize_imdbid_movie_search_criteria(string input, string expected)
|
||||
{
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
var request = new NewznabRequest
|
||||
{
|
||||
t = "movie",
|
||||
imdbid = input
|
||||
};
|
||||
|
||||
Subject.Search(request, new List<int> { 1 }, false);
|
||||
|
||||
var criteria = allCriteria.OfType<MovieSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].ImdbId.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("tt0183790", "0183790")]
|
||||
[TestCase("0183790", "0183790")]
|
||||
[TestCase("183790", "0183790")]
|
||||
[TestCase("tt10001870", "10001870")]
|
||||
[TestCase("10001870", "10001870")]
|
||||
public void should_normalize_imdbid_tv_search_criteria(string input, string expected)
|
||||
{
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
var request = new NewznabRequest
|
||||
{
|
||||
t = "tvsearch",
|
||||
imdbid = input
|
||||
};
|
||||
|
||||
Subject.Search(request, new List<int> { 1 }, false);
|
||||
|
||||
var criteria = allCriteria.OfType<TvSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].ImdbId.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.BroadcastheNet;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
|
||||
{
|
||||
public class BroadcastheNetRequestGeneratorFixture : CoreTest<BroadcastheNetRequestGenerator>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Settings = new BroadcastheNetSettings
|
||||
{
|
||||
BaseUrl = "https://api.broadcasthe.net/",
|
||||
ApiKey = "abc"
|
||||
};
|
||||
|
||||
Subject.Capabilities = new IndexerCapabilities
|
||||
{
|
||||
LimitsDefault = 100,
|
||||
LimitsMax = 1000,
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.TvdbId, TvSearchParam.RId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_have_empty_parameters_if_rss_search()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id }
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().BeNull();
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().BeNullOrWhiteSpace();
|
||||
query.Category.Should().BeNullOrWhiteSpace();
|
||||
query.Name.Should().BeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_tvdbid_season_if_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
TvdbId = 371980,
|
||||
Season = 1
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(2);
|
||||
|
||||
var firstPage = results.GetAllTiers().First().First();
|
||||
var firstQuery = ParseTorrentQueryFromRequest(firstPage.HttpRequest);
|
||||
|
||||
firstQuery.Tvdb.Should().Be("371980");
|
||||
firstQuery.Tvrage.Should().BeNull();
|
||||
firstQuery.Search.Should().BeNull();
|
||||
firstQuery.Category.Should().Be("Season");
|
||||
firstQuery.Name.Should().Be("Season 1%");
|
||||
|
||||
var secondPage = results.GetAllTiers().Skip(1).First().First();
|
||||
var secondQuery = ParseTorrentQueryFromRequest(secondPage.HttpRequest);
|
||||
|
||||
secondQuery.Tvdb.Should().Be("371980");
|
||||
secondQuery.Tvrage.Should().BeNull();
|
||||
secondQuery.Search.Should().BeNull();
|
||||
secondQuery.Category.Should().Be("Episode");
|
||||
secondQuery.Name.Should().Be("S01E%");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_tvdbid_season_episode_if_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
TvdbId = 371980,
|
||||
Season = 1,
|
||||
Episode = "3"
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().Be("371980");
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().BeNull();
|
||||
query.Category.Should().Be("Episode");
|
||||
query.Name.Should().Be("S01E03");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_tvdbid_daily_episode_if_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
TvdbId = 289574,
|
||||
Season = 2023,
|
||||
Episode = "01/03"
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().Be("289574");
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().BeNull();
|
||||
query.Category.Should().Be("Episode");
|
||||
query.Name.Should().Be("2023.01.03");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_prefer_search_by_tvdbid_if_rid_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
TvdbId = 371980,
|
||||
RId = 12345
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().Be("371980");
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().BeNull();
|
||||
query.Category.Should().BeNull();
|
||||
query.Name.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_term_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
SearchTerm = "Malcolm in the Middle"
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().BeNull();
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().Be("Malcolm%in%the%Middle");
|
||||
query.Category.Should().BeNull();
|
||||
query.Name.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_term_season_if_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
SearchTerm = "Malcolm in the Middle",
|
||||
Season = 2
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(2);
|
||||
|
||||
var firstPage = results.GetAllTiers().First().First();
|
||||
var firstQuery = ParseTorrentQueryFromRequest(firstPage.HttpRequest);
|
||||
|
||||
firstQuery.Tvdb.Should().BeNull();
|
||||
firstQuery.Tvrage.Should().BeNull();
|
||||
firstQuery.Search.Should().Be("Malcolm%in%the%Middle");
|
||||
firstQuery.Category.Should().Be("Season");
|
||||
firstQuery.Name.Should().Be("Season 2%");
|
||||
|
||||
var secondPage = results.GetAllTiers().Skip(1).First().First();
|
||||
var secondQuery = ParseTorrentQueryFromRequest(secondPage.HttpRequest);
|
||||
|
||||
secondQuery.Tvdb.Should().BeNull();
|
||||
secondQuery.Tvrage.Should().BeNull();
|
||||
secondQuery.Search.Should().Be("Malcolm%in%the%Middle");
|
||||
secondQuery.Category.Should().Be("Episode");
|
||||
secondQuery.Name.Should().Be("S02E%");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_term_season_episode_if_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
SearchTerm = "Malcolm in the Middle",
|
||||
Season = 2,
|
||||
Episode = "3"
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().BeNull();
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().Be("Malcolm%in%the%Middle");
|
||||
query.Category.Should().Be("Episode");
|
||||
query.Name.Should().Be("S02E03");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_search_by_term_daily_episode_if_supported()
|
||||
{
|
||||
var tvSearchCriteria = new TvSearchCriteria
|
||||
{
|
||||
Categories = new[] { NewznabStandardCategory.TV.Id, NewznabStandardCategory.TVHD.Id },
|
||||
SearchTerm = "The Late Show with Stephen Colbert",
|
||||
Season = 2023,
|
||||
Episode = "01/03"
|
||||
};
|
||||
|
||||
var results = Subject.GetSearchRequests(tvSearchCriteria);
|
||||
|
||||
results.Tiers.Should().Be(1);
|
||||
results.GetAllTiers().Should().HaveCount(1);
|
||||
|
||||
var page = results.GetAllTiers().First().First();
|
||||
var query = ParseTorrentQueryFromRequest(page.HttpRequest);
|
||||
|
||||
query.Tvdb.Should().BeNull();
|
||||
query.Tvrage.Should().BeNull();
|
||||
query.Search.Should().Be("The%Late%Show%with%Stephen%Colbert");
|
||||
query.Category.Should().Be("Episode");
|
||||
query.Name.Should().Be("2023.01.03");
|
||||
}
|
||||
|
||||
private static BroadcastheNetTorrentQuery ParseTorrentQueryFromRequest(HttpRequest httpRequest)
|
||||
{
|
||||
var encoding = HttpHeader.GetEncodingFromContentType(httpRequest.Headers.ContentType);
|
||||
var body = encoding.GetString(httpRequest.ContentData);
|
||||
|
||||
var rpcBody = JsonConvert.DeserializeObject<Dictionary<string, object>>(body);
|
||||
|
||||
return JsonConvert.DeserializeObject<BroadcastheNetTorrentQuery>(((JArray)rpcBody["params"])[1].ToJson());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests
|
||||
{
|
||||
@@ -31,13 +32,15 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
Mocker.SetConstant<IIndexerRepository>(repo);
|
||||
|
||||
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
|
||||
existingIndexers.ConfigContract = typeof(NewznabSettings).Name;
|
||||
existingIndexers.ConfigContract = nameof(NewznabSettings);
|
||||
|
||||
repo.Insert(existingIndexers);
|
||||
|
||||
Subject.Handle(new ApplicationStartedEvent());
|
||||
|
||||
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RedactedTests
|
||||
|
||||
torrentInfo.Title.Should().Be("Red Hot Chili Peppers - Californication [1999] [Album] [US / Reissue 2020] [FLAC 24bit Lossless] [Vinyl]");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313&usetoken=0");
|
||||
torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313");
|
||||
torrentInfo.InfoUrl.Should().Be("https://redacted.ch/torrents.php?id=16720&torrentid=3892313");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
Subject.Definition = new IndexerDefinition
|
||||
{
|
||||
Name = "SecretCinema",
|
||||
Settings = new GazelleSettings { Username = "somekey", Password = "somekey" }
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
|
||||
|
||||
torrentInfo.Title.Should().Be("Singin' in the Rain (1952) 2160p");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://secret-cinema.pw/torrents.php?action=download&useToken=0&id=45068");
|
||||
torrentInfo.DownloadUrl.Should().Be("https://secret-cinema.pw/torrents.php?action=download&id=45068");
|
||||
torrentInfo.InfoUrl.Should().Be("https://secret-cinema.pw/torrents.php?id=2497&torrentid=45068");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -210,10 +210,6 @@ namespace NzbDrone.Core.Test.Messaging.Commands
|
||||
|
||||
public class CommandB : Command
|
||||
{
|
||||
public CommandB()
|
||||
{
|
||||
}
|
||||
|
||||
public override string CompletionMessage => null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
TestLogger.Info("OnHealthIssue was called");
|
||||
}
|
||||
|
||||
public override void OnHealthRestored(Core.HealthCheck.HealthCheck healthCheck)
|
||||
{
|
||||
TestLogger.Info("OnHealthRestored was called");
|
||||
}
|
||||
|
||||
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
||||
{
|
||||
TestLogger.Info("OnApplicationUpdate was called");
|
||||
@@ -79,6 +84,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
var notification = new TestNotificationWithAllEvents();
|
||||
|
||||
notification.SupportsOnHealthIssue.Should().BeTrue();
|
||||
notification.SupportsOnHealthRestored.Should().BeTrue();
|
||||
notification.SupportsOnApplicationUpdate.Should().BeTrue();
|
||||
notification.SupportsOnGrab.Should().BeTrue();
|
||||
}
|
||||
@@ -89,6 +95,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||
var notification = new TestNotificationWithNoEvents();
|
||||
|
||||
notification.SupportsOnHealthIssue.Should().BeFalse();
|
||||
notification.SupportsOnHealthRestored.Should().BeFalse();
|
||||
notification.SupportsOnApplicationUpdate.Should().BeFalse();
|
||||
notification.SupportsOnGrab.Should().BeFalse();
|
||||
}
|
||||
|
||||
@@ -63,5 +63,22 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
ParseUtil.GetLongFromString(original).Should().Be(parsedInt);
|
||||
}
|
||||
|
||||
[TestCase("tt0183790", "tt0183790")]
|
||||
[TestCase("0183790", "tt0183790")]
|
||||
[TestCase("183790", "tt0183790")]
|
||||
[TestCase("tt10001870", "tt10001870")]
|
||||
[TestCase("10001870", "tt10001870")]
|
||||
[TestCase("tt", null)]
|
||||
[TestCase("tt0", null)]
|
||||
[TestCase("abc", null)]
|
||||
[TestCase("abc0", null)]
|
||||
[TestCase("0", null)]
|
||||
[TestCase("", null)]
|
||||
[TestCase(null, null)]
|
||||
public void should_parse_full_imdb_id_from_string(string input, string expected)
|
||||
{
|
||||
ParseUtil.GetFullImdbId(input).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,11 +210,11 @@ namespace NzbDrone.Core.Applications
|
||||
|
||||
if (intersectingTags.Any())
|
||||
{
|
||||
_logger.Debug("Application {0} and indexer {1} [{2}] have {3} intersecting tags.", app.Name, indexer.Name, indexer.Id, intersectingTags.Length);
|
||||
_logger.Debug("Application {0} and indexer {1} [{2}] have {3} intersecting (matching) tags.", app.Name, indexer.Name, indexer.Id, intersectingTags.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.Info("Application {0} does not have any intersecting tags with {1} [{2}]. Indexer will not be handled.", app.Name, indexer.Name, indexer.Id);
|
||||
_logger.Debug("Application {0} does not have any intersecting (matching) tags with {1} [{2}]. Indexer will neither be synced to nor removed from the application.", app.Name, indexer.Name, indexer.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
public override List<AppIndexerMap> GetIndexerMappings()
|
||||
{
|
||||
var indexers = _lidarrV1Proxy.GetIndexers(Settings)
|
||||
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||
.Where(i => i.Implementation is "Newznab" or "Torznab");
|
||||
|
||||
var mappings = new List<AppIndexerMap>();
|
||||
|
||||
@@ -174,7 +173,13 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "earlyReleaseLimit", "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||
|
||||
@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
public class LidarrV1Proxy : ILidarrV1Proxy
|
||||
{
|
||||
private static Version MinimumApplicationVersion => new (1, 0, 2, 0);
|
||||
|
||||
private const string AppApiRoute = "/api/v1";
|
||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
try
|
||||
{
|
||||
Execute<LidarrIndexer>(request);
|
||||
var applicationVersion = _httpClient.Post<LidarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
|
||||
|
||||
if (applicationVersion == null)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version");
|
||||
}
|
||||
|
||||
if (new Version(applicationVersion) < MinimumApplicationVersion)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
public override List<AppIndexerMap> GetIndexerMappings()
|
||||
{
|
||||
var indexers = _radarrV3Proxy.GetIndexers(Settings)
|
||||
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||
.Where(i => i.Implementation is "Newznab" or "Torznab");
|
||||
|
||||
var mappings = new List<AppIndexerMap>();
|
||||
|
||||
@@ -174,7 +173,13 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "multiLanguages", "removeYear", "requiredFlags", "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||
|
||||
@@ -23,8 +23,12 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
public class RadarrV3Proxy : IRadarrV3Proxy
|
||||
{
|
||||
private static Version MinimumApplicationV4Version => new (4, 0, 4, 0);
|
||||
private static Version MinimumApplicationV3Version => new (3, 1, 1, 0);
|
||||
|
||||
private const string AppApiRoute = "/api/v3";
|
||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -102,7 +106,29 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
try
|
||||
{
|
||||
Execute<RadarrIndexer>(request);
|
||||
var applicationVersion = _httpClient.Post<RadarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
|
||||
|
||||
if (applicationVersion == null)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Failed to fetch Radarr version");
|
||||
}
|
||||
|
||||
var version = new Version(applicationVersion);
|
||||
|
||||
if (version.Major == 3)
|
||||
{
|
||||
if (version < MinimumApplicationV3Version)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (version < MinimumApplicationV4Version)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
public override List<AppIndexerMap> GetIndexerMappings()
|
||||
{
|
||||
var indexers = _sonarrV3Proxy.GetIndexers(Settings)
|
||||
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||
.Where(i => i.Implementation is "Newznab" or "Torznab");
|
||||
|
||||
var mappings = new List<AppIndexerMap>();
|
||||
|
||||
@@ -176,7 +175,13 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "animeStandardFormatSearch", "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||
|
||||
@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
public class SonarrV3Proxy : ISonarrV3Proxy
|
||||
{
|
||||
private static Version MinimumApplicationVersion => new (3, 0, 5, 0);
|
||||
|
||||
private const string AppApiRoute = "/api/v3";
|
||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
try
|
||||
{
|
||||
Execute<SonarrIndexer>(request);
|
||||
var applicationVersion = _httpClient.Post<SonarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
|
||||
|
||||
if (applicationVersion == null)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version");
|
||||
}
|
||||
|
||||
if (new Version(applicationVersion) < MinimumApplicationVersion)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
||||
@@ -182,6 +182,8 @@ namespace NzbDrone.Core.Configuration
|
||||
public CertificateValidationType CertificateValidation =>
|
||||
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
|
||||
|
||||
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, string.Empty);
|
||||
|
||||
@@ -55,5 +55,6 @@ namespace NzbDrone.Core.Configuration
|
||||
bool LogIndexerResponse { get; set; }
|
||||
|
||||
CertificateValidationType CertificateValidation { get; }
|
||||
string ApplicationUrl { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using FluentMigrator;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user