mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-18 21:55:12 -04:00
Compare commits
121 Commits
| 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_when_type_is_apparent = true:suggestion
|
||||||
csharp_style_var_elsewhere = true:suggestion
|
csharp_style_var_elsewhere = true:suggestion
|
||||||
|
|
||||||
|
# Using directive is unnecessary.
|
||||||
|
dotnet_diagnostic.IDE0005.severity = error
|
||||||
|
|
||||||
# Stylecop Rules
|
# Stylecop Rules
|
||||||
dotnet_diagnostic.SA0001.severity = none
|
dotnet_diagnostic.SA0001.severity = none
|
||||||
dotnet_diagnostic.SA1005.severity = none
|
dotnet_diagnostic.SA1005.severity = none
|
||||||
|
|||||||
@@ -13,4 +13,11 @@
|
|||||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||||
for bug reports and feature requests. However, this issue appears
|
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/)
|
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).
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
+1
-1
@@ -9,7 +9,7 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '1.4.0'
|
majorVersion: '1.5.1'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
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('|');
|
.join('|');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
|
||||||
parser: '@babel/eslint-parser',
|
parser: '@babel/eslint-parser',
|
||||||
|
|
||||||
env: {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-83
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"stylelint-order"
|
"stylelint-order"
|
||||||
],
|
],
|
||||||
"ignoreFiles": [
|
"ignoreFiles": [
|
||||||
"frontend/src/Styles/scaffolding.css",
|
"frontend/src/Styles/scaffolding.css",
|
||||||
"**/*.js"
|
"**/*.js"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"at-rule-empty-line-before": [
|
"at-rule-empty-line-before": [
|
||||||
"always",
|
"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": [
|
"at-rule-no-unknown": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
@@ -28,83 +25,36 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"at-rule-no-vendor-prefix": true,
|
"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-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-hex-length": "short",
|
||||||
"color-named": "never",
|
"color-named": "never",
|
||||||
"color-no-invalid-hex": true,
|
"color-no-invalid-hex": true,
|
||||||
"comment-whitespace-inside": "always",
|
"comment-whitespace-inside": "always",
|
||||||
"declaration-bang-space-after": "never",
|
|
||||||
"declaration-bang-space-before": "always",
|
|
||||||
"declaration-block-no-duplicate-properties": [
|
"declaration-block-no-duplicate-properties": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
"ignoreProperties": [
|
"ignoreProperties": [
|
||||||
"composes"
|
"composes"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"declaration-block-no-redundant-longhand-properties": true,
|
"declaration-block-no-redundant-longhand-properties": true,
|
||||||
"declaration-block-no-shorthand-property-overrides": 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-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",
|
"font-family-name-quotes": "always-unless-keyword",
|
||||||
"function-calc-no-unspaced-operator": true,
|
"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-linear-gradient-no-nonstandard-direction": true,
|
||||||
"function-name-case": "lower",
|
"function-name-case": "lower",
|
||||||
"function-parentheses-newline-inside": "never-multi-line",
|
|
||||||
"function-parentheses-space-inside": "never",
|
|
||||||
"function-url-quotes": "always",
|
"function-url-quotes": "always",
|
||||||
"function-url-scheme-disallowed-list": [
|
"function-url-scheme-disallowed-list": [
|
||||||
"data"
|
"data"
|
||||||
],
|
],
|
||||||
"function-whitespace-after": "always",
|
|
||||||
"indentation": 2,
|
|
||||||
"keyframe-declaration-no-important": true,
|
"keyframe-declaration-no-important": true,
|
||||||
"length-zero-no-unit": true,
|
"length-zero-no-unit": true,
|
||||||
"max-empty-lines": 1,
|
|
||||||
"max-line-length": [
|
|
||||||
100,
|
|
||||||
{
|
|
||||||
"ignore": [
|
|
||||||
"non-comments"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"max-nesting-depth": 2,
|
"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-name-no-vendor-prefix": true,
|
||||||
"media-feature-range-operator-space-after": "always",
|
|
||||||
"media-feature-range-operator-space-before": "always",
|
|
||||||
"no-empty-source": true,
|
"no-empty-source": true,
|
||||||
"no-eol-whitespace": true,
|
|
||||||
"no-extra-semicolons": true,
|
|
||||||
"no-invalid-double-slash-comments": 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": [
|
"order/order": [
|
||||||
"custom-properties",
|
"custom-properties",
|
||||||
"dollar-variables",
|
"dollar-variables",
|
||||||
@@ -132,6 +82,7 @@
|
|||||||
"right",
|
"right",
|
||||||
"bottom",
|
"bottom",
|
||||||
"left",
|
"left",
|
||||||
|
"inset",
|
||||||
"z-index",
|
"z-index",
|
||||||
"display",
|
"display",
|
||||||
"visibility",
|
"visibility",
|
||||||
@@ -343,54 +294,33 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"property-case": "lower",
|
|
||||||
"property-no-vendor-prefix": true,
|
"property-no-vendor-prefix": true,
|
||||||
"rule-empty-line-before": [
|
"rule-empty-line-before": [
|
||||||
"always",
|
"always",
|
||||||
{
|
{
|
||||||
"except": [
|
"except": [
|
||||||
"first-nested"
|
"first-nested"
|
||||||
],
|
],
|
||||||
"ignore": [
|
"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-attribute-quotes": "never",
|
||||||
"selector-class-pattern": "^[A-Za-z0-9]+$",
|
"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-attribute": 0,
|
||||||
"selector-max-class": 3,
|
"selector-max-class": 3,
|
||||||
"selector-max-compound-selectors": 3,
|
"selector-max-compound-selectors": 3,
|
||||||
"selector-max-empty-lines": 0,
|
|
||||||
"selector-max-id": 0,
|
"selector-max-id": 0,
|
||||||
"selector-max-universal": 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-colon-notation": "double",
|
||||||
"selector-pseudo-element-no-unknown": true,
|
"selector-pseudo-element-no-unknown": true,
|
||||||
"selector-type-case": "lower",
|
"selector-type-case": "lower",
|
||||||
"selector-type-no-unknown": true,
|
"selector-type-no-unknown": true,
|
||||||
"shorthand-property-no-redundant-values": true,
|
"shorthand-property-no-redundant-values": true,
|
||||||
"string-no-newline": true,
|
"string-no-newline": true,
|
||||||
"string-quotes": "single",
|
|
||||||
"time-min-milliseconds": 100,
|
"time-min-milliseconds": 100,
|
||||||
"unit-case": "lower",
|
|
||||||
"unit-no-unknown": true,
|
"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
|
"value-no-vendor-prefix": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"stylelint.vscode-stylelint",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ module.exports = (env) => {
|
|||||||
'node_modules'
|
'node_modules'
|
||||||
],
|
],
|
||||||
alias: {
|
alias: {
|
||||||
jquery: 'jquery/src/jquery'
|
jquery: 'jquery/dist/jquery.min'
|
||||||
},
|
},
|
||||||
fallback: {
|
fallback: {
|
||||||
buffer: false,
|
buffer: false,
|
||||||
@@ -251,18 +251,19 @@ module.exports = (env) => {
|
|||||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||||
|
|
||||||
config.optimization.minimizer = [
|
config.optimization = {
|
||||||
new TerserPlugin({
|
minimize: true,
|
||||||
cache: true,
|
minimizer: [
|
||||||
parallel: true,
|
new TerserPlugin({
|
||||||
sourceMap: true, // Must be set to true if using source-maps in production
|
terserOptions: {
|
||||||
terserOptions: {
|
sourceMap: true, // Must be set to true if using source-maps in production
|
||||||
mangle: false,
|
mangle: false,
|
||||||
keep_classnames: true,
|
keep_classnames: true,
|
||||||
keep_fnames: true
|
keep_fnames: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class DescriptionListItem extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<div>
|
||||||
<DescriptionListItemTitle
|
<DescriptionListItemTitle
|
||||||
className={titleClassName}
|
className={titleClassName}
|
||||||
>
|
>
|
||||||
@@ -29,7 +29,7 @@ class DescriptionListItem extends Component {
|
|||||||
>
|
>
|
||||||
{data}
|
{data}
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
</span>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import PathInput from 'Components/Form/PathInput';
|
import PathInput from 'Components/Form/PathInput';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
@@ -39,7 +38,7 @@ class FileBrowserModalContent extends Component {
|
|||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this._scrollerNode = null;
|
this._scrollerRef = React.createRef();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isFileBrowserModalOpen: false,
|
isFileBrowserModalOpen: false,
|
||||||
@@ -57,21 +56,10 @@ class FileBrowserModalContent extends Component {
|
|||||||
currentPath !== prevState.currentPath
|
currentPath !== prevState.currentPath
|
||||||
) {
|
) {
|
||||||
this.setState({ 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
|
// Listeners
|
||||||
|
|
||||||
@@ -145,7 +133,7 @@ class FileBrowserModalContent extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Scroller
|
<Scroller
|
||||||
ref={this.setScrollerRef}
|
ref={this._scrollerRef}
|
||||||
className={styles.scroller}
|
className={styles.scroller}
|
||||||
scrollDirection={scrollDirections.BOTH}
|
scrollDirection={scrollDirections.BOTH}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
.inputContainer {
|
.inputContainer {
|
||||||
top: -1px;
|
inset: -1px;
|
||||||
right: -1px;
|
|
||||||
bottom: -1px;
|
|
||||||
left: -1px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function IconButton(props) {
|
|||||||
className,
|
className,
|
||||||
isDisabled && styles.isDisabled
|
isDisabled && styles.isDisabled
|
||||||
)}
|
)}
|
||||||
|
aria-label="Table Options Button"
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const messages = [
|
|||||||
'Loading humorous message... Please Wait',
|
'Loading humorous message... Please Wait',
|
||||||
'I could\'ve been faster in Python',
|
'I could\'ve been faster in Python',
|
||||||
'Don\'t forget to rewind your tracks',
|
'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!',
|
'HELP! I\'m being held hostage and forced to write these stupid lines!',
|
||||||
'RE-calibrating the internet...',
|
'RE-calibrating the internet...',
|
||||||
'I\'ll be here all week',
|
'I\'ll be here all week',
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class PageHeader extends Component {
|
|||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/logo.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/logo.png`}
|
||||||
|
alt="Prowlarr Logo"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,6 +75,7 @@ class PageHeader extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
className={styles.donate}
|
className={styles.donate}
|
||||||
name={icons.HEART}
|
name={icons.HEART}
|
||||||
|
aria-label="Donate"
|
||||||
to="https://prowlarr.com/donate"
|
to="https://prowlarr.com/donate"
|
||||||
size={14}
|
size={14}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function PageHeaderActionsMenu(props) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Menu alignMenu={align.RIGHT}>
|
<Menu alignMenu={align.RIGHT}>
|
||||||
<MenuButton className={styles.menuButton}>
|
<MenuButton className={styles.menuButton} aria-label="Menu Button">
|
||||||
<Icon
|
<Icon
|
||||||
name={icons.INTERACTIVE}
|
name={icons.INTERACTIVE}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ function ProgressBar(props) {
|
|||||||
styles[kind],
|
styles[kind],
|
||||||
enableColorImpairedMode && 'colorImpaired'
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
)}
|
)}
|
||||||
aria-valuenow={progress}
|
role="meter"
|
||||||
|
aria-label={`Progress Bar at ${progress.toFixed(0)}%`}
|
||||||
|
aria-valuenow={progress.toFixed(0)}
|
||||||
aria-valuemin="0"
|
aria-valuemin="0"
|
||||||
aria-valuemax="100"
|
aria-valuemax="100"
|
||||||
style={{ width: progressPercent }}
|
style={{ width: progressPercent }}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function Logger(minimumLogLevel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger.prototype.cleanse = function(message) {
|
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)');
|
return message.replace(apikey, 'access_token=(removed)');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ class SignalRConnector extends Component {
|
|||||||
|
|
||||||
this.connection = new signalR.HubConnectionBuilder()
|
this.connection = new signalR.HubConnectionBuilder()
|
||||||
.configureLogging(new Logger(signalR.LogLevel.Information))
|
.configureLogging(new Logger(signalR.LogLevel.Information))
|
||||||
.withUrl(`${url}?access_token=${window.Prowlarr.apiKey}`)
|
.withUrl(`${url}?access_token=${encodeURIComponent(window.Prowlarr.apiKey)}`)
|
||||||
.withAutomaticReconnect({
|
.withAutomaticReconnect({
|
||||||
nextRetryDelayInMilliseconds: (retryContext) => {
|
nextRetryDelayInMilliseconds: (retryContext) => {
|
||||||
if (retryContext.elapsedMilliseconds > 180000) {
|
if (retryContext.elapsedMilliseconds > 180000) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
|
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||||
import HistoryRowParameter from './HistoryRowParameter';
|
import HistoryRowParameter from './HistoryRowParameter';
|
||||||
@@ -193,7 +194,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.season ?
|
data.season ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Season'
|
title={translate('Season')}
|
||||||
value={data.season}
|
value={data.season}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -202,7 +203,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.episode ?
|
data.episode ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Episode'
|
title={translate('Episode')}
|
||||||
value={data.episode}
|
value={data.episode}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -211,7 +212,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.artist ?
|
data.artist ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Artist'
|
title={translate('Artist')}
|
||||||
value={data.artist}
|
value={data.artist}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -220,7 +221,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.album ?
|
data.album ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Album'
|
title={translate('Album')}
|
||||||
value={data.album}
|
value={data.album}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -229,7 +230,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.label ?
|
data.label ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Label'
|
title={translate('Label')}
|
||||||
value={data.label}
|
value={data.label}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -238,7 +239,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.track ?
|
data.track ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Track'
|
title={translate('Track')}
|
||||||
value={data.track}
|
value={data.track}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -247,7 +248,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.year ?
|
data.year ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Year'
|
title={translate('Year')}
|
||||||
value={data.year}
|
value={data.year}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -256,7 +257,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.genre ?
|
data.genre ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Genre'
|
title={translate('Genre')}
|
||||||
value={data.genre}
|
value={data.genre}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -265,7 +266,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.author ?
|
data.author ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Author'
|
title={translate('Author')}
|
||||||
value={data.author}
|
value={data.author}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -274,7 +275,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.bookTitle ?
|
data.bookTitle ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Book'
|
title={translate('Book')}
|
||||||
value={data.bookTitle}
|
value={data.bookTitle}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -283,7 +284,7 @@ class HistoryRow extends Component {
|
|||||||
{
|
{
|
||||||
data.publisher ?
|
data.publisher ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
title='Publisher'
|
title={translate('Publisher')}
|
||||||
value={data.publisher}
|
value={data.publisher}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
@@ -351,6 +352,11 @@ class HistoryRow extends Component {
|
|||||||
`${data.elapsedTime}ms` :
|
`${data.elapsedTime}ms` :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
data.cached === '1' ?
|
||||||
|
' (cached)' :
|
||||||
|
null
|
||||||
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -376,14 +382,14 @@ class HistoryRow extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
name={icons.SEARCH}
|
name={icons.SEARCH}
|
||||||
onPress={this.onSearchPress}
|
onPress={this.onSearchPress}
|
||||||
title='Repeat Search'
|
title={translate('RepeatSearch')}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
<IconButton
|
<IconButton
|
||||||
name={icons.INFO}
|
name={icons.INFO}
|
||||||
onPress={this.onDetailsPress}
|
onPress={this.onDetailsPress}
|
||||||
title='History Details'
|
title={translate('HistoryDetails')}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -222,7 +222,11 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
|||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<IndexerIndexSelectModeButton
|
<IndexerIndexSelectModeButton
|
||||||
label={isSelectMode ? 'Stop Selecting' : 'Select Indexer'}
|
label={
|
||||||
|
isSelectMode
|
||||||
|
? translate('StopSelecting')
|
||||||
|
: translate('SelectIndexer')
|
||||||
|
}
|
||||||
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
|
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
|
||||||
isSelectMode={isSelectMode}
|
isSelectMode={isSelectMode}
|
||||||
overflowComponent={IndexerIndexSelectModeMenuItem}
|
overflowComponent={IndexerIndexSelectModeMenuItem}
|
||||||
@@ -230,7 +234,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<IndexerIndexSelectAllButton
|
<IndexerIndexSelectAllButton
|
||||||
label="SelectAll"
|
label={translate('SelectAll')}
|
||||||
isSelectMode={isSelectMode}
|
isSelectMode={isSelectMode}
|
||||||
overflowComponent={IndexerIndexSelectAllMenuItem}
|
overflowComponent={IndexerIndexSelectAllMenuItem}
|
||||||
/>
|
/>
|
||||||
@@ -245,7 +249,10 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
|||||||
optionsComponent={IndexerIndexTableOptions}
|
optionsComponent={IndexerIndexTableOptions}
|
||||||
onTableOptionChange={onTableOptionChange}
|
onTableOptionChange={onTableOptionChange}
|
||||||
>
|
>
|
||||||
<PageToolbarButton label="Options" iconName={icons.TABLE} />
|
<PageToolbarButton
|
||||||
|
label={translate('Options')}
|
||||||
|
iconName={icons.TABLE}
|
||||||
|
/>
|
||||||
</TableOptionsModalWrapper>
|
</TableOptionsModalWrapper>
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
@@ -276,7 +283,9 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
|||||||
>
|
>
|
||||||
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
|
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
{!isFetching && !!error ? <div>Unable to load indexers</div> : null}
|
{!isFetching && !!error ? (
|
||||||
|
<div>{translate('UnableToLoadIndexers')}</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{isLoaded ? (
|
{isLoaded ? (
|
||||||
<div className={styles.contentBodyContainer}>
|
<div className={styles.contentBodyContainer}>
|
||||||
@@ -295,7 +304,10 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!error && isPopulated && !items.length ? (
|
{!error && isPopulated && !items.length ? (
|
||||||
<NoIndexer totalItems={totalItems} />
|
<NoIndexer
|
||||||
|
totalItems={totalItems}
|
||||||
|
onAddIndexerPress={onAddIndexerPress}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
{isLoaded && !!jumpBarItems.order.length ? (
|
{isLoaded && !!jumpBarItems.order.length ? (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
|||||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
interface IndexerIndexSelectAllButtonProps {
|
interface IndexerIndexSelectAllButtonProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -32,7 +33,7 @@ function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {
|
|||||||
|
|
||||||
return isSelectMode ? (
|
return isSelectMode ? (
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={allSelected ? 'Unselect All' : 'Select All'}
|
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||||
iconName={icon}
|
iconName={icon}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
|||||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||||
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
interface IndexerIndexSelectAllMenuItemProps {
|
interface IndexerIndexSelectAllMenuItemProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -33,7 +34,7 @@ function IndexerIndexSelectAllMenuItem(
|
|||||||
|
|
||||||
return isSelectMode ? (
|
return isSelectMode ? (
|
||||||
<PageToolbarOverflowMenuItem
|
<PageToolbarOverflowMenuItem
|
||||||
label={allSelected ? 'Unselect All' : 'Select All'}
|
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||||
iconName={iconName}
|
iconName={iconName}
|
||||||
onPress={onPressWrapper}
|
onPress={onPressWrapper}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
|||||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
|
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './TagsModalContent.css';
|
import styles from './TagsModalContent.css';
|
||||||
|
|
||||||
interface TagsModalContentProps {
|
interface TagsModalContentProps {
|
||||||
@@ -70,7 +71,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Form>
|
<Form>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Tags</FormLabel>
|
<FormLabel>{translate('Tags')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TAG}
|
type={inputTypes.TAG}
|
||||||
@@ -81,7 +82,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Apply Tags</FormLabel>
|
<FormLabel>{translate('ApplyTags')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
@@ -89,17 +90,17 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
value={applyTags}
|
value={applyTags}
|
||||||
values={applyTagsOptions}
|
values={applyTagsOptions}
|
||||||
helpTexts={[
|
helpTexts={[
|
||||||
'How to apply tags to the selected series',
|
translate('ApplyTagsHelpTexts1'),
|
||||||
'Add: Add the tags the existing list of tags',
|
translate('ApplyTagsHelpTexts2'),
|
||||||
'Remove: Remove the entered tags',
|
translate('ApplyTagsHelpTexts3'),
|
||||||
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)',
|
translate('ApplyTagsHelpTexts4'),
|
||||||
]}
|
]}
|
||||||
onChange={onApplyTagsChange}
|
onChange={onApplyTagsChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Result</FormLabel>
|
<FormLabel>{translate('Result')}</FormLabel>
|
||||||
|
|
||||||
<div className={styles.result}>
|
<div className={styles.result}>
|
||||||
{indexerTags.map((id) => {
|
{indexerTags.map((id) => {
|
||||||
@@ -116,7 +117,11 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
title={removeTag ? 'Removing tag' : 'Existing tag'}
|
title={
|
||||||
|
removeTag
|
||||||
|
? translate('RemoveTagRemovingTag')
|
||||||
|
: translate('RemoveTagExistingTag')
|
||||||
|
}
|
||||||
kind={removeTag ? kinds.INVERSE : kinds.INFO}
|
kind={removeTag ? kinds.INVERSE : kinds.INFO}
|
||||||
size={sizes.LARGE}
|
size={sizes.LARGE}
|
||||||
>
|
>
|
||||||
@@ -140,7 +145,7 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
title={'Adding tag'}
|
title={translate('AddingTag')}
|
||||||
kind={kinds.SUCCESS}
|
kind={kinds.SUCCESS}
|
||||||
size={sizes.LARGE}
|
size={sizes.LARGE}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
|
|||||||
const vipExpiration =
|
const vipExpiration =
|
||||||
fields.find((field) => field.name === 'vipExpiration')?.value ?? '';
|
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 [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
|
||||||
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
|
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup';
|
|||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
import { inputTypes } from 'Helpers/Props';
|
import { inputTypes } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import selectTableOptions from './selectTableOptions';
|
import selectTableOptions from './selectTableOptions';
|
||||||
|
|
||||||
interface IndexerIndexTableOptionsProps {
|
interface IndexerIndexTableOptionsProps {
|
||||||
@@ -32,13 +33,13 @@ function IndexerIndexTableOptions(props: IndexerIndexTableOptionsProps) {
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Show Search</FormLabel>
|
<FormLabel>{translate('ShowSearch')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="showSearchAction"
|
name="showSearchAction"
|
||||||
value={showSearchAction}
|
value={showSearchAction}
|
||||||
helpText="Show search button on hover"
|
helpText={translate('ShowSearchHelpText')}
|
||||||
onChange={onTableOptionChangeWrapper}
|
onChange={onTableOptionChangeWrapper}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
|
|||||||
const enableKind = redirect ? kinds.INFO : kinds.SUCCESS;
|
const enableKind = redirect ? kinds.INFO : kinds.SUCCESS;
|
||||||
const enableIcon = redirect ? icons.REDIRECT : icons.CHECK;
|
const enableIcon = redirect ? icons.REDIRECT : icons.CHECK;
|
||||||
const enableTitle = redirect
|
const enableTitle = redirect
|
||||||
? 'Indexer is Enabled, Redirect is Enabled'
|
? translate('EnabledRedirected')
|
||||||
: 'Indexer is Enabled';
|
: translate('Enabled');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component className={className} {...otherProps}>
|
<Component className={className} {...otherProps}>
|
||||||
@@ -43,7 +43,7 @@ function IndexerStatusCell(props: IndexerStatusCellProps) {
|
|||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
kind={enabled ? enableKind : kinds.DEFAULT}
|
kind={enabled ? enableKind : kinds.DEFAULT}
|
||||||
name={enabled ? enableIcon : icons.BLOCKLIST}
|
name={enabled ? enableIcon : icons.BLOCKLIST}
|
||||||
title={enabled ? enableTitle : 'Indexer is Disabled'}
|
title={enabled ? enableTitle : translate('EnabledIndexerIsDisabled')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{status ? (
|
{status ? (
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
|||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
import { align, kinds } from 'Helpers/Props';
|
import { align, kinds } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import StatsFilterMenu from './StatsFilterMenu';
|
import StatsFilterMenu from './StatsFilterMenu';
|
||||||
import styles from './Stats.css';
|
import styles from './Stats.css';
|
||||||
|
|
||||||
@@ -188,53 +189,53 @@ function Stats(props) {
|
|||||||
<div className={styles.fullWidthChart}>
|
<div className={styles.fullWidthChart}>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={getAverageResponseTimeData(item.indexers)}
|
data={getAverageResponseTimeData(item.indexers)}
|
||||||
title='Average Response Times (ms)'
|
title={translate('AverageResponseTimesMs')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.fullWidthChart}>
|
<div className={styles.fullWidthChart}>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={getFailureRateData(item.indexers)}
|
data={getFailureRateData(item.indexers)}
|
||||||
title='Indexer Failure Rate'
|
title={translate('IndexerFailureRate')}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.halfWidthChart}>
|
<div className={styles.halfWidthChart}>
|
||||||
<StackedBarChart
|
<StackedBarChart
|
||||||
data={getTotalRequestsData(item.indexers)}
|
data={getTotalRequestsData(item.indexers)}
|
||||||
title='Total Indexer Queries'
|
title={translate('TotalIndexerQueries')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.halfWidthChart}>
|
<div className={styles.halfWidthChart}>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={getNumberGrabsData(item.indexers)}
|
data={getNumberGrabsData(item.indexers)}
|
||||||
title='Total Indexer Successful Grabs'
|
title={translate('TotalIndexerSuccessfulGrabs')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.halfWidthChart}>
|
<div className={styles.halfWidthChart}>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={getUserAgentQueryData(item.userAgents)}
|
data={getUserAgentQueryData(item.userAgents)}
|
||||||
title='Total User Agent Queries'
|
title={translate('TotalUserAgentQueries')}
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.halfWidthChart}>
|
<div className={styles.halfWidthChart}>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={getUserAgentGrabsData(item.userAgents)}
|
data={getUserAgentGrabsData(item.userAgents)}
|
||||||
title='Total User Agent Grabs'
|
title={translate('TotalUserAgentGrabs')}
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.halfWidthChart}>
|
<div className={styles.halfWidthChart}>
|
||||||
<DoughnutChart
|
<DoughnutChart
|
||||||
data={getHostQueryData(item.hosts)}
|
data={getHostQueryData(item.hosts)}
|
||||||
title='Total Host Queries'
|
title={translate('TotalHostQueries')}
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.halfWidthChart}>
|
<div className={styles.halfWidthChart}>
|
||||||
<DoughnutChart
|
<DoughnutChart
|
||||||
data={getHostGrabsData(item.hosts)}
|
data={getHostGrabsData(item.hosts)}
|
||||||
title='Total Host Grabs'
|
title={translate('TotalHostGrabs')}
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import Card from 'Components/Card';
|
import Card from 'Components/Card';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import TagList from 'Components/TagList';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EditApplicationModalConnector from './EditApplicationModalConnector';
|
import EditApplicationModalConnector from './EditApplicationModalConnector';
|
||||||
@@ -55,7 +56,9 @@ class Application extends Component {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
syncLevel
|
syncLevel,
|
||||||
|
tags,
|
||||||
|
tagList
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -92,6 +95,11 @@ class Application extends Component {
|
|||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<TagList
|
||||||
|
tags={tags}
|
||||||
|
tagList={tagList}
|
||||||
|
/>
|
||||||
|
|
||||||
<EditApplicationModalConnector
|
<EditApplicationModalConnector
|
||||||
id={id}
|
id={id}
|
||||||
isOpen={this.state.isEditApplicationModalOpen}
|
isOpen={this.state.isEditApplicationModalOpen}
|
||||||
@@ -117,6 +125,8 @@ Application.propTypes = {
|
|||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
syncLevel: PropTypes.string.isRequired,
|
syncLevel: PropTypes.string.isRequired,
|
||||||
|
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onConfirmDeleteApplication: PropTypes.func
|
onConfirmDeleteApplication: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class Applications extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
tagList,
|
||||||
onConfirmDeleteApplication,
|
onConfirmDeleteApplication,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -71,6 +72,7 @@ class Applications extends Component {
|
|||||||
<Application
|
<Application
|
||||||
key={item.id}
|
key={item.id}
|
||||||
{...item}
|
{...item}
|
||||||
|
tagList={tagList}
|
||||||
onConfirmDeleteApplication={onConfirmDeleteApplication}
|
onConfirmDeleteApplication={onConfirmDeleteApplication}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -109,6 +111,7 @@ Applications.propTypes = {
|
|||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onConfirmDeleteApplication: PropTypes.func.isRequired
|
onConfirmDeleteApplication: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { deleteApplication, fetchApplications } from 'Store/Actions/settingsActions';
|
import { deleteApplication, fetchApplications } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import Applications from './Applications';
|
import Applications from './Applications';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.applications', sortByName),
|
createSortedSectionSelector('settings.applications', sortByName),
|
||||||
(applications) => applications
|
createTagsSelector(),
|
||||||
|
(applications, tagList) => {
|
||||||
|
return {
|
||||||
|
...applications,
|
||||||
|
tagList
|
||||||
|
};
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ function HostSettings(props) {
|
|||||||
port,
|
port,
|
||||||
urlBase,
|
urlBase,
|
||||||
instanceName,
|
instanceName,
|
||||||
|
applicationUrl,
|
||||||
enableSsl,
|
enableSsl,
|
||||||
sslPort,
|
sslPort,
|
||||||
sslCertPath,
|
sslCertPath,
|
||||||
@@ -89,6 +90,21 @@ function HostSettings(props) {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>{translate('ApplicationURL')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="applicationUrl"
|
||||||
|
helpText={translate('ApplicationUrlHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...applicationUrl}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import Card from 'Components/Card';
|
import Card from 'Components/Card';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import TagList from 'Components/TagList';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EditNotificationModalConnector from './EditNotificationModalConnector';
|
import EditNotificationModalConnector from './EditNotificationModalConnector';
|
||||||
@@ -57,10 +58,14 @@ class Notification extends Component {
|
|||||||
name,
|
name,
|
||||||
onGrab,
|
onGrab,
|
||||||
onHealthIssue,
|
onHealthIssue,
|
||||||
|
onHealthRestored,
|
||||||
onApplicationUpdate,
|
onApplicationUpdate,
|
||||||
supportsOnGrab,
|
supportsOnGrab,
|
||||||
supportsOnHealthIssue,
|
supportsOnHealthIssue,
|
||||||
supportsOnApplicationUpdate
|
supportsOnHealthRestored,
|
||||||
|
supportsOnApplicationUpdate,
|
||||||
|
tags,
|
||||||
|
tagList
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -74,17 +79,27 @@ class Notification extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsOnGrab && onGrab &&
|
supportsOnGrab && onGrab ?
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
{translate('OnGrab')}
|
{translate('OnGrab')}
|
||||||
</Label>
|
</Label> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsOnHealthIssue && onHealthIssue &&
|
supportsOnHealthIssue && onHealthIssue ?
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
{translate('OnHealthIssue')}
|
{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
|
<Label
|
||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
@@ -106,6 +121,11 @@ class Notification extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<TagList
|
||||||
|
tags={tags}
|
||||||
|
tagList={tagList}
|
||||||
|
/>
|
||||||
|
|
||||||
<EditNotificationModalConnector
|
<EditNotificationModalConnector
|
||||||
id={id}
|
id={id}
|
||||||
isOpen={this.state.isEditNotificationModalOpen}
|
isOpen={this.state.isEditNotificationModalOpen}
|
||||||
@@ -132,10 +152,14 @@ Notification.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
onGrab: PropTypes.bool.isRequired,
|
onGrab: PropTypes.bool.isRequired,
|
||||||
onHealthIssue: PropTypes.bool.isRequired,
|
onHealthIssue: PropTypes.bool.isRequired,
|
||||||
|
onHealthRestored: PropTypes.bool.isRequired,
|
||||||
onApplicationUpdate: PropTypes.bool.isRequired,
|
onApplicationUpdate: PropTypes.bool.isRequired,
|
||||||
supportsOnGrab: PropTypes.bool.isRequired,
|
supportsOnGrab: PropTypes.bool.isRequired,
|
||||||
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
||||||
|
supportsOnHealthRestored: PropTypes.bool.isRequired,
|
||||||
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
||||||
|
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ function NotificationEventItems(props) {
|
|||||||
const {
|
const {
|
||||||
onGrab,
|
onGrab,
|
||||||
onHealthIssue,
|
onHealthIssue,
|
||||||
|
onHealthRestored,
|
||||||
onApplicationUpdate,
|
onApplicationUpdate,
|
||||||
supportsOnGrab,
|
supportsOnGrab,
|
||||||
includeManualGrabs,
|
includeManualGrabs,
|
||||||
supportsOnHealthIssue,
|
supportsOnHealthIssue,
|
||||||
|
supportsOnHealthRestored,
|
||||||
includeHealthWarnings,
|
includeHealthWarnings,
|
||||||
supportsOnApplicationUpdate
|
supportsOnApplicationUpdate
|
||||||
} = item;
|
} = item;
|
||||||
@@ -70,8 +72,19 @@ function NotificationEventItems(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class Notifications extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
tagList,
|
||||||
onConfirmDeleteNotification,
|
onConfirmDeleteNotification,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -71,6 +72,7 @@ class Notifications extends Component {
|
|||||||
<Notification
|
<Notification
|
||||||
key={item.id}
|
key={item.id}
|
||||||
{...item}
|
{...item}
|
||||||
|
tagList={tagList}
|
||||||
onConfirmDeleteNotification={onConfirmDeleteNotification}
|
onConfirmDeleteNotification={onConfirmDeleteNotification}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -109,6 +111,7 @@ Notifications.propTypes = {
|
|||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
|
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import Notifications from './Notifications';
|
import Notifications from './Notifications';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.notifications', sortByName),
|
createSortedSectionSelector('settings.notifications', sortByName),
|
||||||
(notifications) => notifications
|
createTagsSelector(),
|
||||||
|
(notifications, tagList) => {
|
||||||
|
return {
|
||||||
|
...notifications,
|
||||||
|
tagList
|
||||||
|
};
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
@define-mixin scrollbar {
|
@define-mixin scrollbar {
|
||||||
|
scrollbar-color: var(--scrollbarBackgroundColor) transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ module.exports = {
|
|||||||
|
|
||||||
defaultButtonTextColor: '#eee',
|
defaultButtonTextColor: '#eee',
|
||||||
defaultButtonBackgroundColor: '#333',
|
defaultButtonBackgroundColor: '#333',
|
||||||
defaultBorderColor: '#eaeaea',
|
defaultBorderColor: '#393f45',
|
||||||
defaultHoverBackgroundColor: '#444',
|
defaultHoverBackgroundColor: '#444',
|
||||||
defaultHoverBorderColor: '#d6d6d6',
|
defaultHoverBorderColor: '#5a6265',
|
||||||
|
|
||||||
primaryBackgroundColor: '#5d9cec',
|
primaryBackgroundColor: '#5d9cec',
|
||||||
primaryBorderColor: '#5899eb',
|
primaryBorderColor: '#5899eb',
|
||||||
|
|||||||
+28
-34
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config ./frontend/build/webpack.config.js",
|
"build": "webpack --config ./frontend/build/webpack.config.js",
|
||||||
"prebuild": "yarn clean",
|
"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",
|
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
|
||||||
"watch": "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/",
|
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
|
||||||
@@ -20,10 +20,7 @@
|
|||||||
"readmeFilename": "readme.md",
|
"readmeFilename": "readme.md",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">0.25%",
|
"defaults"
|
||||||
"not ie 11",
|
|
||||||
"not op_mini all",
|
|
||||||
"not chrome < 60"
|
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "6.4.0",
|
"@fortawesome/fontawesome-free": "6.4.0",
|
||||||
@@ -33,13 +30,12 @@
|
|||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@juggle/resize-observer": "3.4.0",
|
"@juggle/resize-observer": "3.4.0",
|
||||||
"@microsoft/signalr": "6.0.16",
|
"@microsoft/signalr": "6.0.16",
|
||||||
"@sentry/browser": "7.46.0",
|
"@sentry/browser": "7.51.2",
|
||||||
"@sentry/integrations": "7.46.0",
|
"@sentry/integrations": "7.51.2",
|
||||||
"@types/jest": "29.5.0",
|
|
||||||
"@types/node": "18.15.11",
|
"@types/node": "18.15.11",
|
||||||
"@types/react": "18.0.31",
|
"@types/react": "18.2.6",
|
||||||
"@types/react-dom": "18.0.11",
|
"@types/react-dom": "18.2.4",
|
||||||
"chart.js": "4.2.1",
|
"chart.js": "4.3.0",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"connected-react-router": "6.9.3",
|
"connected-react-router": "6.9.3",
|
||||||
@@ -48,7 +44,7 @@
|
|||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"https-browserify": "1.0.0",
|
"https-browserify": "1.0.0",
|
||||||
"jdu": "1.0.0",
|
"jdu": "1.0.0",
|
||||||
"jquery": "3.6.4",
|
"jquery": "3.7.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mobile-detect": "1.4.5",
|
"mobile-detect": "1.4.5",
|
||||||
"moment": "2.29.4",
|
"moment": "2.29.4",
|
||||||
@@ -86,36 +82,33 @@
|
|||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"reselect": "4.1.7",
|
"reselect": "4.1.7",
|
||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"typescript": "5.0.3"
|
"typescript": "5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.21.3",
|
"@babel/core": "7.21.8",
|
||||||
"@babel/eslint-parser": "7.21.3",
|
"@babel/eslint-parser": "7.21.8",
|
||||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
"@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-default-from": "7.18.10",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
"@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-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-optional-chaining": "7.21.0",
|
||||||
"@babel/plugin-proposal-throw-expressions": "7.18.6",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@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-react": "7.18.6",
|
||||||
"@babel/preset-typescript": "7.21.0",
|
"@babel/preset-typescript": "7.21.5",
|
||||||
"@types/react-window": "1.8.5",
|
"@types/react-window": "1.8.5",
|
||||||
"@typescript-eslint/eslint-plugin": "5.57.0",
|
"@types/webpack-livereload-plugin": "2.3.3",
|
||||||
"@typescript-eslint/parser": "5.57.0",
|
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||||
|
"@typescript-eslint/parser": "5.59.5",
|
||||||
"are-you-es5": "2.1.2",
|
"are-you-es5": "2.1.2",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"babel-loader": "9.1.2",
|
"babel-loader": "9.1.2",
|
||||||
"babel-plugin-inline-classnames": "2.0.1",
|
"babel-plugin-inline-classnames": "2.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
"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-loader": "6.7.3",
|
||||||
"css-modules-typescript-loader": "4.0.1",
|
"css-modules-typescript-loader": "4.0.1",
|
||||||
"eslint": "8.37.0",
|
"eslint": "8.40.0",
|
||||||
"eslint-config-prettier": "8.8.0",
|
"eslint-config-prettier": "8.8.0",
|
||||||
"eslint-plugin-filenames": "1.3.2",
|
"eslint-plugin-filenames": "1.3.2",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
@@ -126,29 +119,30 @@
|
|||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"filemanager-webpack-plugin": "8.0.0",
|
"filemanager-webpack-plugin": "8.0.0",
|
||||||
"fork-ts-checker-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",
|
"loader-utils": "^3.2.1",
|
||||||
"mini-css-extract-plugin": "2.7.5",
|
"mini-css-extract-plugin": "2.7.5",
|
||||||
"postcss": "8.4.21",
|
"postcss": "8.4.23",
|
||||||
"postcss-color-function": "4.1.0",
|
"postcss-color-function": "4.1.0",
|
||||||
"postcss-loader": "7.1.0",
|
"postcss-loader": "7.3.0",
|
||||||
"postcss-mixins": "9.0.4",
|
"postcss-mixins": "9.0.4",
|
||||||
"postcss-nested": "6.0.1",
|
"postcss-nested": "6.0.1",
|
||||||
"postcss-simple-vars": "7.0.1",
|
"postcss-simple-vars": "7.0.1",
|
||||||
"postcss-url": "10.1.3",
|
"postcss-url": "10.1.3",
|
||||||
"prettier": "2.8.7",
|
"prettier": "2.8.8",
|
||||||
"require-nocache": "1.0.0",
|
"require-nocache": "1.0.0",
|
||||||
"rimraf": "4.4.1",
|
"rimraf": "4.4.1",
|
||||||
"run-sequence": "2.2.1",
|
"run-sequence": "2.2.1",
|
||||||
"streamqueue": "1.1.2",
|
"streamqueue": "1.1.2",
|
||||||
"style-loader": "3.3.2",
|
"style-loader": "3.3.2",
|
||||||
"stylelint": "14.16.0",
|
"stylelint": "15.6.1",
|
||||||
"stylelint-order": "5.0.0",
|
"stylelint-order": "6.0.3",
|
||||||
|
"terser-webpack-plugin": "5.3.8",
|
||||||
"ts-loader": "9.4.2",
|
"ts-loader": "9.4.2",
|
||||||
"typescript-plugin-css-modules": "5.0.0",
|
"typescript-plugin-css-modules": "5.0.1",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"webpack": "5.77.0",
|
"webpack": "5.82.1",
|
||||||
"webpack-cli": "5.0.1",
|
"webpack-cli": "5.1.1",
|
||||||
"webpack-livereload-plugin": "3.0.2"
|
"webpack-livereload-plugin": "3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AnalysisLevel>6.0-all</AnalysisLevel>
|
<AnalysisLevel>6.0-all</AnalysisLevel>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
|
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||||
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<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>
|
<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('Prowlarr'))">true</ProwlarrProject>
|
||||||
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</ProwlarrProject>
|
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</ProwlarrProject>
|
||||||
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</ProwlarrProject>
|
<ProwlarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</ProwlarrProject>
|
||||||
|
|
||||||
<!-- A test project gets the test sdk packages automatically added -->
|
<!-- A test project gets the test sdk packages automatically added -->
|
||||||
<TestProject>false</TestProject>
|
<TestProject>false</TestProject>
|
||||||
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
|
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
|
||||||
@@ -50,7 +52,7 @@
|
|||||||
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Test projects need bindingRedirects -->
|
<!-- Test projects need bindingRedirects -->
|
||||||
<PropertyGroup Condition="'$(ProwlarrOutputType)'=='Test'">
|
<PropertyGroup Condition="'$(ProwlarrOutputType)'=='Test'">
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
@@ -63,7 +65,7 @@
|
|||||||
<Product>Prowlarr</Product>
|
<Product>Prowlarr</Product>
|
||||||
<Company>prowlarr.com</Company>
|
<Company>prowlarr.com</Company>
|
||||||
<Copyright>Copyright 2014-$([System.DateTime]::Now.ToString('yyyy')) prowlarr.com (GNU General Public v3)</Copyright>
|
<Copyright>Copyright 2014-$([System.DateTime]::Now.ToString('yyyy')) prowlarr.com (GNU General Public v3)</Copyright>
|
||||||
|
|
||||||
<!-- Should be replaced by CI -->
|
<!-- Should be replaced by CI -->
|
||||||
<AssemblyVersion>10.0.0.*</AssemblyVersion>
|
<AssemblyVersion>10.0.0.*</AssemblyVersion>
|
||||||
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
|
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
|
||||||
@@ -72,7 +74,7 @@
|
|||||||
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
|
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
|
||||||
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
|
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
|
||||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
|
||||||
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
|
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
|
||||||
|
|
||||||
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
|
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
|
||||||
@@ -94,7 +96,7 @@
|
|||||||
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
|
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Standard testing packages -->
|
<!-- Standard testing packages -->
|
||||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using NzbDrone.Common.EnvironmentInfo;
|
|||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Chrome;
|
using OpenQA.Selenium.Chrome;
|
||||||
using OpenQA.Selenium.Remote;
|
|
||||||
|
|
||||||
namespace NzbDrone.Automation.Test
|
namespace NzbDrone.Automation.Test
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -352,6 +351,26 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
.Verify(v => v.DeleteFile(_targetPath), Times.Once());
|
.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]
|
[Test]
|
||||||
public void should_log_error_if_rollback_partialmove_fails()
|
public void should_log_error_if_rollback_partialmove_fails()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
@@ -12,14 +13,14 @@ namespace NzbDrone.Common.Test.EnsureTest
|
|||||||
public void EnsureWindowsPath(string path)
|
public void EnsureWindowsPath(string path)
|
||||||
{
|
{
|
||||||
WindowsOnly();
|
WindowsOnly();
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(@"/var/user/file with, comma.mkv")]
|
[TestCase(@"/var/user/file with, comma.mkv")]
|
||||||
public void EnsureLinuxPath(string path)
|
public void EnsureLinuxPath(string path)
|
||||||
{
|
{
|
||||||
PosixOnly();
|
PosixOnly();
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
using System.Globalization;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
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(@"\\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)
|
public void Clean_Path_Windows(string dirty, string clean)
|
||||||
{
|
{
|
||||||
WindowsOnly();
|
WindowsOnly();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Cache
|
namespace NzbDrone.Common.Cache
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
private void CheckFolderExists(string path)
|
private void CheckFolderExists(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (!FolderExists(path))
|
if (!FolderExists(path))
|
||||||
{
|
{
|
||||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
private void CheckFileExists(string path)
|
private void CheckFileExists(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (!FileExists(path))
|
if (!FileExists(path))
|
||||||
{
|
{
|
||||||
@@ -93,19 +93,19 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public bool FolderExists(string path)
|
public bool FolderExists(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
return Directory.Exists(path);
|
return Directory.Exists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
return FileExists(path, PathStringComparison);
|
return FileExists(path, PathStringComparison);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FileExists(string path, StringComparison stringComparison)
|
public bool FileExists(string path, StringComparison stringComparison)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
switch (stringComparison)
|
switch (stringComparison)
|
||||||
{
|
{
|
||||||
@@ -125,7 +125,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public bool FolderWritable(string path)
|
public bool FolderWritable(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -144,35 +144,35 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public bool FolderEmpty(string path)
|
public bool FolderEmpty(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return Directory.EnumerateFileSystemEntries(path).Empty();
|
return Directory.EnumerateFileSystemEntries(path).Empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetDirectories(string path)
|
public string[] GetDirectories(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return Directory.GetDirectories(path);
|
return Directory.GetDirectories(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetFiles(string path, SearchOption searchOption)
|
public string[] GetFiles(string path, SearchOption searchOption)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return Directory.GetFiles(path, "*.*", searchOption);
|
return Directory.GetFiles(path, "*.*", searchOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetFolderSize(string path)
|
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);
|
return GetFiles(path, SearchOption.AllDirectories).Sum(e => new FileInfo(e).Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetFileSize(string path)
|
public long GetFileSize(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (!FileExists(path))
|
if (!FileExists(path))
|
||||||
{
|
{
|
||||||
@@ -185,13 +185,13 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void CreateFolder(string path)
|
public void CreateFolder(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(string path)
|
public void DeleteFile(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Logger.Trace("Deleting file: {0}", path);
|
Logger.Trace("Deleting file: {0}", path);
|
||||||
|
|
||||||
RemoveReadOnly(path);
|
RemoveReadOnly(path);
|
||||||
@@ -201,8 +201,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void CloneFile(string source, string destination, bool overwrite = false)
|
public void CloneFile(string source, string destination, bool overwrite = false)
|
||||||
{
|
{
|
||||||
Ensure.That(source, () => source).IsValidPath();
|
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(destination, () => destination).IsValidPath();
|
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (source.PathEquals(destination))
|
if (source.PathEquals(destination))
|
||||||
{
|
{
|
||||||
@@ -219,8 +219,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void CopyFile(string source, string destination, bool overwrite = false)
|
public void CopyFile(string source, string destination, bool overwrite = false)
|
||||||
{
|
{
|
||||||
Ensure.That(source, () => source).IsValidPath();
|
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(destination, () => destination).IsValidPath();
|
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (source.PathEquals(destination))
|
if (source.PathEquals(destination))
|
||||||
{
|
{
|
||||||
@@ -237,8 +237,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void MoveFile(string source, string destination, bool overwrite = false)
|
public void MoveFile(string source, string destination, bool overwrite = false)
|
||||||
{
|
{
|
||||||
Ensure.That(source, () => source).IsValidPath();
|
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(destination, () => destination).IsValidPath();
|
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (source.PathEquals(destination))
|
if (source.PathEquals(destination))
|
||||||
{
|
{
|
||||||
@@ -256,14 +256,19 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void MoveFolder(string source, string destination, bool overwrite = false)
|
public void MoveFolder(string source, string destination, bool overwrite = false)
|
||||||
{
|
{
|
||||||
Ensure.That(source, () => source).IsValidPath();
|
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(destination, () => destination).IsValidPath();
|
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
Directory.Move(source, destination);
|
Directory.Move(source, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void MoveFileInternal(string source, string destination)
|
protected virtual void MoveFileInternal(string source, string destination)
|
||||||
{
|
{
|
||||||
|
if (File.Exists(destination))
|
||||||
|
{
|
||||||
|
throw new FileAlreadyExistsException("File already exists", destination);
|
||||||
|
}
|
||||||
|
|
||||||
File.Move(source, destination);
|
File.Move(source, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +286,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void DeleteFolder(string path, bool recursive)
|
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);
|
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
||||||
Array.ForEach(files, RemoveReadOnly);
|
Array.ForEach(files, RemoveReadOnly);
|
||||||
@@ -291,14 +296,14 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public string ReadAllText(string filePath)
|
public string ReadAllText(string filePath)
|
||||||
{
|
{
|
||||||
Ensure.That(filePath, () => filePath).IsValidPath();
|
Ensure.That(filePath, () => filePath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return File.ReadAllText(filePath);
|
return File.ReadAllText(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteAllText(string filename, string contents)
|
public void WriteAllText(string filename, string contents)
|
||||||
{
|
{
|
||||||
Ensure.That(filename, () => filename).IsValidPath();
|
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
|
||||||
RemoveReadOnly(filename);
|
RemoveReadOnly(filename);
|
||||||
|
|
||||||
// File.WriteAllText is broken on net core when writing to some CIFS mounts
|
// 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)
|
public void FolderSetLastWriteTime(string path, DateTime dateTime)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (dateTime.Before(DateTimeExtensions.Epoch))
|
if (dateTime.Before(DateTimeExtensions.Epoch))
|
||||||
{
|
{
|
||||||
@@ -326,7 +331,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void FileSetLastWriteTime(string path, DateTime dateTime)
|
public void FileSetLastWriteTime(string path, DateTime dateTime)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
if (dateTime.Before(DateTimeExtensions.Epoch))
|
if (dateTime.Before(DateTimeExtensions.Epoch))
|
||||||
{
|
{
|
||||||
@@ -353,14 +358,14 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public virtual string GetPathRoot(string path)
|
public virtual string GetPathRoot(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return Path.GetPathRoot(path);
|
return Path.GetPathRoot(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetParentFolder(string 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));
|
var parent = Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
|
||||||
|
|
||||||
@@ -407,7 +412,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public void EmptyFolder(string path)
|
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))
|
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
|
||||||
{
|
{
|
||||||
@@ -496,7 +501,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public List<DirectoryInfo> GetDirectoryInfos(string path)
|
public List<DirectoryInfo> GetDirectoryInfos(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
var di = new DirectoryInfo(path);
|
var di = new DirectoryInfo(path);
|
||||||
|
|
||||||
@@ -505,14 +510,14 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public FileInfo GetFileInfo(string path)
|
public FileInfo GetFileInfo(string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
return new FileInfo(path);
|
return new FileInfo(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
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);
|
var di = new DirectoryInfo(path);
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
|
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
|
||||||
{
|
{
|
||||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
sourcePath = ResolveRealParentPath(sourcePath);
|
sourcePath = ResolveRealParentPath(sourcePath);
|
||||||
targetPath = ResolveRealParentPath(targetPath);
|
targetPath = ResolveRealParentPath(targetPath);
|
||||||
@@ -140,8 +140,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
{
|
{
|
||||||
var filesCopied = 0;
|
var filesCopied = 0;
|
||||||
|
|
||||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
sourcePath = ResolveRealParentPath(sourcePath);
|
sourcePath = ResolveRealParentPath(sourcePath);
|
||||||
targetPath = ResolveRealParentPath(targetPath);
|
targetPath = ResolveRealParentPath(targetPath);
|
||||||
@@ -255,8 +255,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
|
|
||||||
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
|
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
|
||||||
{
|
{
|
||||||
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
|
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
Ensure.That(targetPath, () => targetPath).IsValidPath();
|
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
|
||||||
|
|
||||||
sourcePath = ResolveRealParentPath(sourcePath);
|
sourcePath = ResolveRealParentPath(sourcePath);
|
||||||
targetPath = ResolveRealParentPath(targetPath);
|
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));
|
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;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
if (
|
||||||
allowFoldersWithoutTrailingSlashes &&
|
allowFoldersWithoutTrailingSlashes &&
|
||||||
query.IsPathValid() &&
|
query.IsPathValid(PathValidationType.CurrentOs) &&
|
||||||
_diskProvider.FolderExists(query))
|
_diskProvider.FolderExists(query))
|
||||||
{
|
{
|
||||||
return GetResult(query, includeFiles);
|
return GetResult(query, includeFiles);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Security.Principal;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Disk
|
namespace NzbDrone.Common.Disk
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Disk
|
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()
|
private int GetFileNameIndex()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NzbDrone.Common.Disk
|
||||||
|
{
|
||||||
|
public enum PathValidationType
|
||||||
|
{
|
||||||
|
CurrentOs,
|
||||||
|
AnyOs
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnsureThat.Resources;
|
using NzbDrone.Common.EnsureThat.Resources;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
@@ -111,14 +112,14 @@ namespace NzbDrone.Common.EnsureThat
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DebuggerStepThrough]
|
[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))
|
if (string.IsNullOrWhiteSpace(param.Value))
|
||||||
{
|
{
|
||||||
throw ExceptionFactory.CreateForParamValidation(param.Name, ExceptionMessages.EnsureExtensions_IsNotNullOrWhiteSpace);
|
throw ExceptionFactory.CreateForParamValidation(param.Name, ExceptionMessages.EnsureExtensions_IsNotNullOrWhiteSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.Value.IsPathValid())
|
if (param.Value.IsPathValid(validationType))
|
||||||
{
|
{
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Security.Principal;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Exceptions;
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ namespace NzbDrone.Common.Extensions
|
|||||||
public static string CleanFilePath(this string path)
|
public static string CleanFilePath(this string path)
|
||||||
{
|
{
|
||||||
Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
|
Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
|
||||||
Ensure.That(path, () => path).IsValidPath();
|
Ensure.That(path, () => path).IsValidPath(PathValidationType.AnyOs);
|
||||||
|
|
||||||
var info = new FileInfo(path.Trim());
|
var info = new FileInfo(path.Trim());
|
||||||
|
|
||||||
// UNC
|
// UNC
|
||||||
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
|
if (!info.FullName.Contains('/') && info.FullName.StartsWith(@"\\"))
|
||||||
{
|
{
|
||||||
return info.FullName.TrimEnd('/', '\\', ' ');
|
return info.FullName.TrimEnd('/', '\\', ' ');
|
||||||
}
|
}
|
||||||
@@ -136,24 +136,24 @@ namespace NzbDrone.Common.Extensions
|
|||||||
|
|
||||||
private static readonly Regex WindowsPathWithDriveRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled);
|
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))
|
if (path.ContainsInvalidPathChars() || string.IsNullOrWhiteSpace(path))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (validationType == PathValidationType.AnyOs)
|
||||||
|
{
|
||||||
|
return IsPathValidForWindows(path) || IsPathValidForNonWindows(path);
|
||||||
|
}
|
||||||
|
|
||||||
if (OsInfo.IsNotWindows)
|
if (OsInfo.IsNotWindows)
|
||||||
{
|
{
|
||||||
return path.StartsWith(Path.DirectorySeparatorChar.ToString());
|
return IsPathValidForNonWindows(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path))
|
return IsPathValidForWindows(path);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ContainsInvalidPathChars(this string text)
|
public static bool ContainsInvalidPathChars(this string text)
|
||||||
@@ -337,5 +337,15 @@ namespace NzbDrone.Common.Extensions
|
|||||||
{
|
{
|
||||||
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
|
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}";
|
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)
|
public static string CalculateMd5(string s)
|
||||||
{
|
{
|
||||||
// Use input string to calculate MD5 hash
|
// Use input string to calculate MD5 hash
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Net.Http;
|
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
@@ -9,7 +8,6 @@ using System.Net.Sockets;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Http.Proxy;
|
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;
|
var headers = request.Content?.Headers;
|
||||||
if (headers == null)
|
if (headers == null)
|
||||||
|
|||||||
@@ -322,11 +322,12 @@ namespace NzbDrone.Common.Http
|
|||||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||||
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
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);
|
var request = new HttpRequest(url);
|
||||||
request.AllowAutoRedirect = true;
|
request.AllowAutoRedirect = true;
|
||||||
request.ResponseStream = fileStream;
|
request.ResponseStream = fileStream;
|
||||||
|
request.RequestTimeout = TimeSpan.FromSeconds(300);
|
||||||
var response = await GetAsync(request);
|
var response = await GetAsync(request);
|
||||||
|
|
||||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
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.Collections.Specialized;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Fluent;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation.Extensions
|
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation
|
namespace NzbDrone.Common.Instrumentation
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sentry;
|
using Sentry;
|
||||||
using Sentry.Protocol;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation.Sentry
|
namespace NzbDrone.Common.Instrumentation.Sentry
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using Npgsql;
|
|||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using Sentry;
|
using Sentry;
|
||||||
using Sentry.Protocol;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation.Sentry
|
namespace NzbDrone.Common.Instrumentation.Sentry
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
|||||||
@@ -131,14 +131,7 @@ namespace NzbDrone.Common.Processes
|
|||||||
var key = environmentVariable.Key.ToString();
|
var key = environmentVariable.Key.ToString();
|
||||||
var value = environmentVariable.Value?.ToString();
|
var value = environmentVariable.Value?.ToString();
|
||||||
|
|
||||||
if (startInfo.EnvironmentVariables.ContainsKey(key))
|
startInfo.EnvironmentVariables[key] = value;
|
||||||
{
|
|
||||||
startInfo.EnvironmentVariables[key] = value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
startInfo.EnvironmentVariables.Add(key, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -320,7 +313,7 @@ namespace NzbDrone.Common.Processes
|
|||||||
processInfo = new ProcessInfo();
|
processInfo = new ProcessInfo();
|
||||||
processInfo.Id = process.Id;
|
processInfo.Id = process.Id;
|
||||||
processInfo.Name = process.ProcessName;
|
processInfo.Name = process.ProcessName;
|
||||||
processInfo.StartPath = GetExeFileName(process);
|
processInfo.StartPath = process.MainModule.FileName;
|
||||||
|
|
||||||
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
||||||
{
|
{
|
||||||
@@ -335,16 +328,6 @@ namespace NzbDrone.Common.Processes
|
|||||||
return processInfo;
|
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)
|
private List<Process> GetProcessesByName(string name)
|
||||||
{
|
{
|
||||||
//TODO: move this to an OS specific class
|
//TODO: move this to an OS specific class
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Cloud;
|
using NzbDrone.Common.Cloud;
|
||||||
@@ -10,7 +9,6 @@ using NzbDrone.Common.Http.Proxy;
|
|||||||
using NzbDrone.Common.TPL;
|
using NzbDrone.Common.TPL;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Http;
|
using NzbDrone.Core.Http;
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Security;
|
using NzbDrone.Core.Security;
|
||||||
using NzbDrone.Test.Common;
|
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 NUnit.Framework;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|||||||
+291
@@ -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.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using NzbDrone.Core.Indexers;
|
|||||||
using NzbDrone.Core.Indexers.Newznab;
|
using NzbDrone.Core.Indexers.Newznab;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.IndexerTests
|
namespace NzbDrone.Core.Test.IndexerTests
|
||||||
{
|
{
|
||||||
@@ -31,13 +32,15 @@ namespace NzbDrone.Core.Test.IndexerTests
|
|||||||
Mocker.SetConstant<IIndexerRepository>(repo);
|
Mocker.SetConstant<IIndexerRepository>(repo);
|
||||||
|
|
||||||
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
|
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
|
||||||
existingIndexers.ConfigContract = typeof(NewznabSettings).Name;
|
existingIndexers.ConfigContract = nameof(NewznabSettings);
|
||||||
|
|
||||||
repo.Insert(existingIndexers);
|
repo.Insert(existingIndexers);
|
||||||
|
|
||||||
Subject.Handle(new ApplicationStartedEvent());
|
Subject.Handle(new ApplicationStartedEvent());
|
||||||
|
|
||||||
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
|
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
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.Title.Should().Be("Red Hot Chili Peppers - Californication [1999] [Album] [US / Reissue 2020] [FLAC 24bit Lossless] [Vinyl]");
|
||||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
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.InfoUrl.Should().Be("https://redacted.ch/torrents.php?id=16720&torrentid=3892313");
|
||||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
Subject.Definition = new IndexerDefinition()
|
Subject.Definition = new IndexerDefinition
|
||||||
{
|
{
|
||||||
Name = "SecretCinema",
|
Name = "SecretCinema",
|
||||||
Settings = new GazelleSettings { Username = "somekey", Password = "somekey" }
|
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.Title.Should().Be("Singin' in the Rain (1952) 2160p");
|
||||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
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.InfoUrl.Should().Be("https://secret-cinema.pw/torrents.php?id=2497&torrentid=45068");
|
||||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -210,10 +210,6 @@ namespace NzbDrone.Core.Test.Messaging.Commands
|
|||||||
|
|
||||||
public class CommandB : Command
|
public class CommandB : Command
|
||||||
{
|
{
|
||||||
public CommandB()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string CompletionMessage => null;
|
public override string CompletionMessage => null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentAssertions;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
TestLogger.Info("OnHealthIssue was called");
|
TestLogger.Info("OnHealthIssue was called");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnHealthRestored(Core.HealthCheck.HealthCheck healthCheck)
|
||||||
|
{
|
||||||
|
TestLogger.Info("OnHealthRestored was called");
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
||||||
{
|
{
|
||||||
TestLogger.Info("OnApplicationUpdate was called");
|
TestLogger.Info("OnApplicationUpdate was called");
|
||||||
@@ -79,6 +84,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
var notification = new TestNotificationWithAllEvents();
|
var notification = new TestNotificationWithAllEvents();
|
||||||
|
|
||||||
notification.SupportsOnHealthIssue.Should().BeTrue();
|
notification.SupportsOnHealthIssue.Should().BeTrue();
|
||||||
|
notification.SupportsOnHealthRestored.Should().BeTrue();
|
||||||
notification.SupportsOnApplicationUpdate.Should().BeTrue();
|
notification.SupportsOnApplicationUpdate.Should().BeTrue();
|
||||||
notification.SupportsOnGrab.Should().BeTrue();
|
notification.SupportsOnGrab.Should().BeTrue();
|
||||||
}
|
}
|
||||||
@@ -89,6 +95,7 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||||||
var notification = new TestNotificationWithNoEvents();
|
var notification = new TestNotificationWithNoEvents();
|
||||||
|
|
||||||
notification.SupportsOnHealthIssue.Should().BeFalse();
|
notification.SupportsOnHealthIssue.Should().BeFalse();
|
||||||
|
notification.SupportsOnHealthRestored.Should().BeFalse();
|
||||||
notification.SupportsOnApplicationUpdate.Should().BeFalse();
|
notification.SupportsOnApplicationUpdate.Should().BeFalse();
|
||||||
notification.SupportsOnGrab.Should().BeFalse();
|
notification.SupportsOnGrab.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,5 +63,22 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
{
|
{
|
||||||
ParseUtil.GetLongFromString(original).Should().Be(parsedInt);
|
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())
|
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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
{
|
{
|
||||||
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_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);
|
return new ValidationResult(failures);
|
||||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
public override List<AppIndexerMap> GetIndexerMappings()
|
public override List<AppIndexerMap> GetIndexerMappings()
|
||||||
{
|
{
|
||||||
var indexers = _lidarrV1Proxy.GetIndexers(Settings)
|
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>();
|
var mappings = new List<AppIndexerMap>();
|
||||||
|
|
||||||
@@ -174,7 +173,13 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
{
|
{
|
||||||
var cacheKey = $"{Settings.BaseUrl}";
|
var cacheKey = $"{Settings.BaseUrl}";
|
||||||
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
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 newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
|
|
||||||
public class LidarrV1Proxy : ILidarrV1Proxy
|
public class LidarrV1Proxy : ILidarrV1Proxy
|
||||||
{
|
{
|
||||||
|
private static Version MinimumApplicationVersion => new (1, 0, 2, 0);
|
||||||
|
|
||||||
private const string AppApiRoute = "/api/v1";
|
private const string AppApiRoute = "/api/v1";
|
||||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
|
|
||||||
try
|
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)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
{
|
{
|
||||||
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_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);
|
return new ValidationResult(failures);
|
||||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
public override List<AppIndexerMap> GetIndexerMappings()
|
public override List<AppIndexerMap> GetIndexerMappings()
|
||||||
{
|
{
|
||||||
var indexers = _radarrV3Proxy.GetIndexers(Settings)
|
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>();
|
var mappings = new List<AppIndexerMap>();
|
||||||
|
|
||||||
@@ -174,7 +173,13 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
{
|
{
|
||||||
var cacheKey = $"{Settings.BaseUrl}";
|
var cacheKey = $"{Settings.BaseUrl}";
|
||||||
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
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 newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||||
|
|||||||
@@ -23,8 +23,12 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
|
|
||||||
public class RadarrV3Proxy : IRadarrV3Proxy
|
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 AppApiRoute = "/api/v3";
|
||||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
@@ -102,7 +106,29 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
|
|
||||||
try
|
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)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
{
|
{
|
||||||
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_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);
|
return new ValidationResult(failures);
|
||||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
public override List<AppIndexerMap> GetIndexerMappings()
|
public override List<AppIndexerMap> GetIndexerMappings()
|
||||||
{
|
{
|
||||||
var indexers = _sonarrV3Proxy.GetIndexers(Settings)
|
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>();
|
var mappings = new List<AppIndexerMap>();
|
||||||
|
|
||||||
@@ -176,7 +175,13 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
{
|
{
|
||||||
var cacheKey = $"{Settings.BaseUrl}";
|
var cacheKey = $"{Settings.BaseUrl}";
|
||||||
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
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 newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
|
|
||||||
public class SonarrV3Proxy : ISonarrV3Proxy
|
public class SonarrV3Proxy : ISonarrV3Proxy
|
||||||
{
|
{
|
||||||
|
private static Version MinimumApplicationVersion => new (3, 0, 5, 0);
|
||||||
|
|
||||||
private const string AppApiRoute = "/api/v3";
|
private const string AppApiRoute = "/api/v3";
|
||||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
|
|
||||||
try
|
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)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ namespace NzbDrone.Core.Configuration
|
|||||||
public CertificateValidationType CertificateValidation =>
|
public CertificateValidationType CertificateValidation =>
|
||||||
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
|
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
|
||||||
|
|
||||||
|
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
|
||||||
|
|
||||||
private string GetValue(string key)
|
private string GetValue(string key)
|
||||||
{
|
{
|
||||||
return GetValue(key, string.Empty);
|
return GetValue(key, string.Empty);
|
||||||
|
|||||||
@@ -55,5 +55,6 @@ namespace NzbDrone.Core.Configuration
|
|||||||
bool LogIndexerResponse { get; set; }
|
bool LogIndexerResponse { get; set; }
|
||||||
|
|
||||||
CertificateValidationType CertificateValidation { get; }
|
CertificateValidationType CertificateValidation { get; }
|
||||||
|
string ApplicationUrl { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Data;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Converters
|
namespace NzbDrone.Core.Datastore.Converters
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using NzbDrone.Common.Disk;
|
|||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Exceptions;
|
using NzbDrone.Common.Exceptions;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using FluentMigrator;
|
using FluentMigrator;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Dapper;
|
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