mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-14 15:44:20 -04:00
Compare commits
44 Commits
v0.3.3.217
...
v0.3.6.223
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44e6de2e23 | ||
|
|
b209d047fa | ||
|
|
fd5ab27df6 | ||
|
|
4a89befd79 | ||
|
|
1a30293c33 | ||
|
|
f5c2a6bf51 | ||
|
|
f3d90fdaf1 | ||
|
|
04c5671a0a | ||
|
|
22cc88c5e7 | ||
|
|
ca0c95a2d2 | ||
|
|
419f790d66 | ||
|
|
9fe08429bc | ||
|
|
71f4a88ab3 | ||
|
|
30b283eda3 | ||
|
|
e23d0bbfa1 | ||
|
|
765a2aa01b | ||
|
|
64895c3210 | ||
|
|
03ab84a814 | ||
|
|
b77e5b14e1 | ||
|
|
75efbd45e1 | ||
|
|
00cac507ad | ||
|
|
c4850505b0 | ||
|
|
75213c86a1 | ||
|
|
b8c3a42643 | ||
|
|
8acb034aa6 | ||
|
|
889d32552b | ||
|
|
adc5f4db97 | ||
|
|
9d08050f96 | ||
|
|
f8cffbb4cf | ||
|
|
14aeb66142 | ||
|
|
37e8e11e31 | ||
|
|
bdb2f14936 | ||
|
|
a97af657be | ||
|
|
301127e6dc | ||
|
|
1f95bcae4e | ||
|
|
29118cda45 | ||
|
|
09beaa939d | ||
|
|
2107624f1c | ||
|
|
c1c2076e5c | ||
|
|
c31a797bd8 | ||
|
|
ebb2b4eca3 | ||
|
|
3ec5d9b9fe | ||
|
|
1ad84a7c44 | ||
|
|
9d67c18254 |
@@ -9,13 +9,13 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.3.3'
|
||||
majorVersion: '0.3.6'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.408'
|
||||
dotnetVersion: '6.0.413'
|
||||
nodeVersion: '16.X'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
|
||||
@@ -4,14 +4,14 @@ module.exports = {
|
||||
plugins: [
|
||||
// Stage 1
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
['@babel/plugin-proposal-optional-chaining', { loose }],
|
||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
||||
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
|
||||
|
||||
// Stage 2
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
'@babel/plugin-transform-export-namespace-from',
|
||||
|
||||
// Stage 3
|
||||
['@babel/plugin-proposal-class-properties', { loose }],
|
||||
['@babel/plugin-transform-class-properties', { loose }],
|
||||
'@babel/plugin-syntax-dynamic-import'
|
||||
],
|
||||
env: {
|
||||
|
||||
@@ -16,7 +16,7 @@ import AuthorIndex from './AuthorIndex';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
||||
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
||||
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
||||
@@ -24,17 +24,17 @@ function createMapStateToProps() {
|
||||
(
|
||||
author,
|
||||
isRefreshingAuthor,
|
||||
isRssSyncExecuting,
|
||||
isOrganizingAuthor,
|
||||
isRetaggingAuthor,
|
||||
isRssSyncExecuting,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...author,
|
||||
isRefreshingAuthor,
|
||||
isRssSyncExecuting,
|
||||
isOrganizingAuthor,
|
||||
isRetaggingAuthor,
|
||||
isRssSyncExecuting,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import BookIndex from './BookIndex';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createBookClientSideCollectionItemsSelector('bookIndex'),
|
||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
||||
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
||||
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||
createCommandExecutingSelector(commandNames.BULK_REFRESH_BOOK),
|
||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
||||
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.version {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.image {
|
||||
height: 250px;
|
||||
|
||||
@@ -6,6 +6,7 @@ interface CssExports {
|
||||
'image': string;
|
||||
'imageContainer': string;
|
||||
'message': string;
|
||||
'version': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import styles from './ErrorBoundaryError.css';
|
||||
|
||||
function ErrorBoundaryError(props) {
|
||||
const {
|
||||
className,
|
||||
messageClassName,
|
||||
detailsClassName,
|
||||
message,
|
||||
error,
|
||||
info
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={messageClassName}>
|
||||
{message}
|
||||
</div>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<details className={detailsClassName}>
|
||||
{
|
||||
error &&
|
||||
<div>
|
||||
{error.toString()}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={styles.info}>
|
||||
{info.componentStack}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ErrorBoundaryError.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
messageClassName: PropTypes.string.isRequired,
|
||||
detailsClassName: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
error: PropTypes.object.isRequired,
|
||||
info: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
ErrorBoundaryError.defaultProps = {
|
||||
className: styles.container,
|
||||
messageClassName: styles.message,
|
||||
detailsClassName: styles.details,
|
||||
message: 'There was an error loading this content'
|
||||
};
|
||||
|
||||
export default ErrorBoundaryError;
|
||||
77
frontend/src/Components/Error/ErrorBoundaryError.tsx
Normal file
77
frontend/src/Components/Error/ErrorBoundaryError.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import StackTrace from 'stacktrace-js';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ErrorBoundaryError.css';
|
||||
|
||||
interface ErrorBoundaryErrorProps {
|
||||
className: string;
|
||||
messageClassName: string;
|
||||
detailsClassName: string;
|
||||
message: string;
|
||||
error: Error;
|
||||
info: {
|
||||
componentStack: string;
|
||||
};
|
||||
}
|
||||
|
||||
function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
|
||||
const {
|
||||
className = styles.container,
|
||||
messageClassName = styles.message,
|
||||
detailsClassName = styles.details,
|
||||
message = translate('ErrorLoadingContent'),
|
||||
error,
|
||||
info,
|
||||
} = props;
|
||||
|
||||
const [detailedError, setDetailedError] = useState<
|
||||
StackTrace.StackFrame[] | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
StackTrace.fromError(error).then((de) => {
|
||||
setDetailedError(de);
|
||||
});
|
||||
} else {
|
||||
setDetailedError(null);
|
||||
}
|
||||
}, [error, setDetailedError]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={messageClassName}>{message}</div>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<details className={detailsClassName}>
|
||||
{error ? <div>{error.message}</div> : null}
|
||||
|
||||
{detailedError ? (
|
||||
detailedError.map((d, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
{` at ${d.functionName} (${d.fileName}:${d.lineNumber}:${d.columnNumber})`}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div>{info.componentStack}</div>
|
||||
)}
|
||||
|
||||
{
|
||||
<div className={styles.version}>
|
||||
Version: {window.Readarr.version}
|
||||
</div>
|
||||
}
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorBoundaryError;
|
||||
@@ -9,6 +9,10 @@
|
||||
&:hover {
|
||||
background-color: var(--inputHoverBackgroundColor);
|
||||
}
|
||||
|
||||
&.isDisabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.optionCheck {
|
||||
|
||||
35
package.json
35
package.json
@@ -30,7 +30,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@microsoft/signalr": "6.0.16",
|
||||
"@microsoft/signalr": "6.0.21",
|
||||
"@sentry/browser": "7.51.2",
|
||||
"@sentry/integrations": "7.51.2",
|
||||
"@types/node": "18.16.16",
|
||||
@@ -83,30 +83,27 @@
|
||||
"redux-localstorage": "0.4.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"reselect": "4.1.8",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.22.9",
|
||||
"@babel/eslint-parser": "7.22.9",
|
||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||
"@babel/core": "7.22.11",
|
||||
"@babel/eslint-parser": "7.22.11",
|
||||
"@babel/plugin-proposal-export-default-from": "7.22.5",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.22.9",
|
||||
"@babel/preset-env": "7.22.15",
|
||||
"@babel/preset-react": "7.22.5",
|
||||
"@babel/preset-typescript": "7.22.5",
|
||||
"@babel/preset-typescript": "7.22.11",
|
||||
"@types/lodash": "4.14.197",
|
||||
"@types/redux-actions": "2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.0.0",
|
||||
"@typescript-eslint/parser": "6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.5.0",
|
||||
"@typescript-eslint/parser": "6.5.0",
|
||||
"autoprefixer": "10.4.14",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.31.1",
|
||||
"css-loader": "6.7.3",
|
||||
"core-js": "3.32.1",
|
||||
"css-loader": "6.8.1",
|
||||
"css-modules-typescript-loader": "4.0.1",
|
||||
"eslint": "8.44.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
@@ -120,9 +117,9 @@
|
||||
"file-loader": "6.2.0",
|
||||
"filemanager-webpack-plugin": "8.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "8.0.0",
|
||||
"html-webpack-plugin": "5.5.1",
|
||||
"html-webpack-plugin": "5.5.3",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "2.7.5",
|
||||
"mini-css-extract-plugin": "2.7.6",
|
||||
"postcss": "8.4.23",
|
||||
"postcss-color-function": "4.1.0",
|
||||
"postcss-loader": "7.3.0",
|
||||
@@ -135,14 +132,14 @@
|
||||
"rimraf": "4.4.1",
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "3.3.2",
|
||||
"stylelint": "15.10.1",
|
||||
"style-loader": "3.3.3",
|
||||
"stylelint": "15.10.3",
|
||||
"stylelint-order": "6.0.3",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"ts-loader": "9.4.3",
|
||||
"ts-loader": "9.4.4",
|
||||
"typescript-plugin-css-modules": "5.0.1",
|
||||
"url-loader": "4.1.1",
|
||||
"webpack": "5.88.1",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-livereload-plugin": "3.0.2",
|
||||
"worker-loader": "3.0.8"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
||||
<PackageVersion Include="Dapper" Version="2.0.123" />
|
||||
<PackageVersion Include="DryIoc.dll" Version="5.4.0" />
|
||||
<PackageVersion Include="DryIoc.dll" Version="5.4.1" />
|
||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||
@@ -16,7 +16,7 @@
|
||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||
<PackageVersion Include="Mailkit" Version="3.6.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.16" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.21" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
@@ -43,7 +43,7 @@
|
||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||
<PackageVersion Include="Sentry" Version="3.31.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||
@@ -57,10 +57,10 @@
|
||||
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="6.0.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="6.0.8" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NLog;
|
||||
@@ -114,21 +115,21 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_simple_get()
|
||||
public async Task should_execute_simple_get()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
var response = await Subject.ExecuteAsync(request);
|
||||
|
||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_https_get()
|
||||
public async Task should_execute_https_get()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
var response = await Subject.ExecuteAsync(request);
|
||||
|
||||
response.Content.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
@@ -140,47 +141,47 @@ namespace NzbDrone.Common.Test.Http
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
|
||||
var request = new HttpRequest($"https://expired.badssl.com");
|
||||
|
||||
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
|
||||
Assert.ThrowsAsync<HttpRequestException>(async () => await Subject.ExecuteAsync(request));
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void bad_ssl_should_pass_if_remote_validation_disabled()
|
||||
public async Task bad_ssl_should_pass_if_remote_validation_disabled()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
|
||||
|
||||
var request = new HttpRequest($"https://expired.badssl.com");
|
||||
|
||||
Subject.Execute(request);
|
||||
await Subject.ExecuteAsync(request);
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_typed_get()
|
||||
public async Task should_execute_typed_get()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get?test=1");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Url.EndsWith("/get?test=1");
|
||||
response.Resource.Args.Should().Contain("test", "1");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_simple_post()
|
||||
public async Task should_execute_simple_post()
|
||||
{
|
||||
var message = "{ my: 1 }";
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/post");
|
||||
request.SetContent(message);
|
||||
|
||||
var response = Subject.Post<HttpBinResource>(request);
|
||||
var response = await Subject.PostAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Data.Should().Be(message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_post_with_content_type()
|
||||
public async Task should_execute_post_with_content_type()
|
||||
{
|
||||
var message = "{ my: 1 }";
|
||||
|
||||
@@ -188,17 +189,16 @@ namespace NzbDrone.Common.Test.Http
|
||||
request.SetContent(message);
|
||||
request.Headers.ContentType = "application/json";
|
||||
|
||||
var response = Subject.Post<HttpBinResource>(request);
|
||||
var response = await Subject.PostAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Data.Should().Be(message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_execute_get_using_gzip()
|
||||
public async Task should_execute_get_using_gzip()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip");
|
||||
|
||||
@@ -208,11 +208,10 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
[Test]
|
||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
||||
public void should_execute_get_using_brotli()
|
||||
public async Task should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
|
||||
|
||||
@@ -230,7 +229,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{statusCode}");
|
||||
|
||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
var exception = Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||
|
||||
((int)exception.Response.StatusCode).Should().Be(statusCode);
|
||||
|
||||
@@ -243,7 +242,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
|
||||
|
||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
@@ -253,7 +252,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||
|
||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
var exception = Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
@@ -264,28 +263,28 @@ namespace NzbDrone.Common.Test.Http
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
|
||||
request.LogHttpError = false;
|
||||
|
||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
Assert.ThrowsAsync<HttpException>(async () => await Subject.GetAsync<HttpBinResource>(request));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_follow_redirects_when_not_in_production()
|
||||
public async Task should_not_follow_redirects_when_not_in_production()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||
|
||||
Subject.Get(request);
|
||||
await Subject.GetAsync(request);
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_follow_redirects()
|
||||
public async Task should_follow_redirects()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
var response = await Subject.GetAsync(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
@@ -293,12 +292,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_follow_redirects()
|
||||
public async Task should_not_follow_redirects()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/1");
|
||||
request.AllowAutoRedirect = false;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
var response = await Subject.GetAsync(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Found);
|
||||
|
||||
@@ -306,14 +305,14 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_follow_redirects_to_https()
|
||||
public async Task should_follow_redirects_to_https()
|
||||
{
|
||||
var request = new HttpRequestBuilder($"https://{_httpBinHost}/redirect-to")
|
||||
.AddQueryParam("url", $"https://readarr.com/")
|
||||
.Build();
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
var response = await Subject.GetAsync(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
response.Content.Should().Contain("Readarr");
|
||||
@@ -327,17 +326,17 @@ namespace NzbDrone.Common.Test.Http
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/redirect/6");
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
Assert.Throws<WebException>(() => Subject.Get(request));
|
||||
Assert.ThrowsAsync<WebException>(async () => await Subject.GetAsync(request));
|
||||
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_send_user_agent()
|
||||
public async Task should_send_user_agent()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().ContainKey("User-Agent");
|
||||
|
||||
@@ -347,24 +346,24 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
|
||||
public void should_send_headers(string header, string value)
|
||||
public async Task should_send_headers(string header, string value)
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
request.Headers.Add(header, value);
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers[header].ToString().Should().Be(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_download_file()
|
||||
public async Task should_download_file()
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
var url = "https://readarr.com/img/slider/artistdetails.png";
|
||||
|
||||
Subject.DownloadFile(url, file);
|
||||
await Subject.DownloadFileAsync(url, file);
|
||||
|
||||
var fileInfo = new FileInfo(file);
|
||||
fileInfo.Exists.Should().BeTrue();
|
||||
@@ -372,7 +371,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_download_file_with_redirect()
|
||||
public async Task should_download_file_with_redirect()
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
@@ -380,7 +379,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
.AddQueryParam("url", $"https://readarr.com/img/slider/artistdetails.png")
|
||||
.Build();
|
||||
|
||||
Subject.DownloadFile(request.Url.FullUri, file);
|
||||
await Subject.DownloadFileAsync(request.Url.FullUri, file);
|
||||
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
|
||||
@@ -394,7 +393,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
|
||||
Assert.ThrowsAsync<HttpException>(async () => await Subject.DownloadFileAsync("https://download.sonarr.tv/wrongpath", file));
|
||||
|
||||
File.Exists(file).Should().BeFalse();
|
||||
|
||||
@@ -402,7 +401,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_write_redirect_content_to_stream()
|
||||
public async Task should_not_write_redirect_content_to_stream()
|
||||
{
|
||||
var file = GetTempFilePath();
|
||||
|
||||
@@ -412,7 +411,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
request.AllowAutoRedirect = false;
|
||||
request.ResponseStream = fileStream;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
var response = await Subject.GetAsync(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Moved);
|
||||
}
|
||||
@@ -427,12 +426,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_send_cookie()
|
||||
public async Task should_send_cookie()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
request.Cookies["my"] = "cookie";
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||
|
||||
@@ -461,13 +460,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_preserve_cookie_during_session()
|
||||
public async Task should_preserve_cookie_during_session()
|
||||
{
|
||||
GivenOldCookie();
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost2}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||
|
||||
@@ -477,30 +476,30 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_send_cookie_to_other_host()
|
||||
public async Task should_not_send_cookie_to_other_host()
|
||||
{
|
||||
GivenOldCookie();
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_store_request_cookie()
|
||||
public async Task should_not_store_request_cookie()
|
||||
{
|
||||
var requestGet = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
requestGet.Cookies.Add("my", "cookie");
|
||||
requestGet.AllowAutoRedirect = false;
|
||||
requestGet.StoreRequestCookie = false;
|
||||
requestGet.StoreResponseCookie = false;
|
||||
var responseGet = Subject.Get<HttpBinResource>(requestGet);
|
||||
var responseGet = await Subject.GetAsync<HttpBinResource>(requestGet);
|
||||
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.AllowAutoRedirect = false;
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||
|
||||
@@ -508,18 +507,18 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_store_request_cookie()
|
||||
public async Task should_store_request_cookie()
|
||||
{
|
||||
var requestGet = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
requestGet.Cookies.Add("my", "cookie");
|
||||
requestGet.AllowAutoRedirect = false;
|
||||
requestGet.StoreRequestCookie.Should().BeTrue();
|
||||
requestGet.StoreResponseCookie = false;
|
||||
var responseGet = Subject.Get<HttpBinResource>(requestGet);
|
||||
var responseGet = await Subject.GetAsync<HttpBinResource>(requestGet);
|
||||
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.AllowAutoRedirect = false;
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -527,7 +526,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_request_cookie()
|
||||
public async Task should_delete_request_cookie()
|
||||
{
|
||||
var requestDelete = new HttpRequest($"https://{_httpBinHost}/cookies/delete?my");
|
||||
requestDelete.Cookies.Add("my", "cookie");
|
||||
@@ -536,13 +535,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestDelete.StoreResponseCookie = false;
|
||||
|
||||
// Delete and redirect since that's the only way to check the internal temporary cookie container
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestDelete);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestDelete);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_clear_request_cookie()
|
||||
public async Task should_clear_request_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestSet.Cookies.Add("my", "cookie");
|
||||
@@ -550,7 +549,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestSet.StoreRequestCookie = true;
|
||||
requestSet.StoreResponseCookie = false;
|
||||
|
||||
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
||||
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
||||
|
||||
var requestClear = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestClear.Cookies.Add("my", null);
|
||||
@@ -558,24 +557,24 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestClear.StoreRequestCookie = true;
|
||||
requestClear.StoreResponseCookie = false;
|
||||
|
||||
var responseClear = Subject.Get<HttpCookieResource>(requestClear);
|
||||
var responseClear = await Subject.GetAsync<HttpCookieResource>(requestClear);
|
||||
|
||||
responseClear.Resource.Cookies.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_store_response_cookie()
|
||||
public async Task should_not_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreRequestCookie = false;
|
||||
requestSet.StoreResponseCookie.Should().BeFalse();
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
var responseSet = await Subject.GetAsync(requestSet);
|
||||
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||
|
||||
@@ -583,18 +582,18 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_store_response_cookie()
|
||||
public async Task should_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreRequestCookie = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
var responseSet = await Subject.GetAsync(requestSet);
|
||||
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -602,13 +601,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_temp_store_response_cookie()
|
||||
public async Task should_temp_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = true;
|
||||
requestSet.StoreRequestCookie = false;
|
||||
requestSet.StoreResponseCookie.Should().BeFalse();
|
||||
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
||||
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
||||
|
||||
// Set and redirect since that's the only way to check the internal temporary cookie container
|
||||
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
@@ -617,7 +616,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_overwrite_response_cookie()
|
||||
public async Task should_overwrite_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.Cookies.Add("my", "oldcookie");
|
||||
@@ -625,11 +624,11 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestSet.StoreRequestCookie = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
var responseSet = await Subject.GetAsync(requestSet);
|
||||
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -637,7 +636,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_overwrite_temp_response_cookie()
|
||||
public async Task should_overwrite_temp_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest($"https://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.Cookies.Add("my", "oldcookie");
|
||||
@@ -645,13 +644,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestSet.StoreRequestCookie = true;
|
||||
requestSet.StoreResponseCookie = false;
|
||||
|
||||
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
|
||||
var responseSet = await Subject.GetAsync<HttpCookieResource>(requestSet);
|
||||
|
||||
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
|
||||
|
||||
@@ -659,14 +658,14 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_delete_response_cookie()
|
||||
public async Task should_not_delete_response_cookie()
|
||||
{
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.Cookies.Add("my", "cookie");
|
||||
requestCookies.AllowAutoRedirect = false;
|
||||
requestCookies.StoreRequestCookie = true;
|
||||
requestCookies.StoreResponseCookie = false;
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -675,13 +674,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestDelete.StoreRequestCookie = false;
|
||||
requestDelete.StoreResponseCookie = false;
|
||||
|
||||
var responseDelete = Subject.Get(requestDelete);
|
||||
var responseDelete = await Subject.GetAsync(requestDelete);
|
||||
|
||||
requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.StoreRequestCookie = false;
|
||||
requestCookies.StoreResponseCookie = false;
|
||||
|
||||
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -689,14 +688,14 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_response_cookie()
|
||||
public async Task should_delete_response_cookie()
|
||||
{
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.Cookies.Add("my", "cookie");
|
||||
requestCookies.AllowAutoRedirect = false;
|
||||
requestCookies.StoreRequestCookie = true;
|
||||
requestCookies.StoreResponseCookie = false;
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -705,13 +704,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestDelete.StoreRequestCookie = false;
|
||||
requestDelete.StoreResponseCookie = true;
|
||||
|
||||
var responseDelete = Subject.Get(requestDelete);
|
||||
var responseDelete = await Subject.GetAsync(requestDelete);
|
||||
|
||||
requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.StoreRequestCookie = false;
|
||||
requestCookies.StoreResponseCookie = false;
|
||||
|
||||
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().BeEmpty();
|
||||
|
||||
@@ -719,14 +718,14 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_temp_response_cookie()
|
||||
public async Task should_delete_temp_response_cookie()
|
||||
{
|
||||
var requestCookies = new HttpRequest($"https://{_httpBinHost}/cookies");
|
||||
requestCookies.Cookies.Add("my", "cookie");
|
||||
requestCookies.AllowAutoRedirect = false;
|
||||
requestCookies.StoreRequestCookie = true;
|
||||
requestCookies.StoreResponseCookie = false;
|
||||
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
|
||||
var responseCookies = await Subject.GetAsync<HttpCookieResource>(requestCookies);
|
||||
|
||||
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
|
||||
|
||||
@@ -734,7 +733,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestDelete.AllowAutoRedirect = true;
|
||||
requestDelete.StoreRequestCookie = false;
|
||||
requestDelete.StoreResponseCookie = false;
|
||||
var responseDelete = Subject.Get<HttpCookieResource>(requestDelete);
|
||||
var responseDelete = await Subject.GetAsync<HttpCookieResource>(requestDelete);
|
||||
|
||||
responseDelete.Resource.Cookies.Should().BeEmpty();
|
||||
|
||||
@@ -752,13 +751,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/status/429");
|
||||
|
||||
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
|
||||
Assert.ThrowsAsync<TooManyRequestsException>(async () => await Subject.GetAsync(request));
|
||||
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_call_interceptor()
|
||||
public async Task should_call_interceptor()
|
||||
{
|
||||
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(new[] { Mocker.GetMock<IHttpRequestInterceptor>().Object });
|
||||
|
||||
@@ -772,7 +771,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
Subject.Get(request);
|
||||
await Subject.GetAsync(request);
|
||||
|
||||
Mocker.GetMock<IHttpRequestInterceptor>()
|
||||
.Verify(v => v.PreRequest(It.IsAny<HttpRequest>()), Times.Once());
|
||||
@@ -783,7 +782,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
[TestCase("en-US")]
|
||||
[TestCase("es-ES")]
|
||||
public void should_parse_malformed_cloudflare_cookie(string culture)
|
||||
public async Task should_parse_malformed_cloudflare_cookie(string culture)
|
||||
{
|
||||
var origCulture = Thread.CurrentThread.CurrentCulture;
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
|
||||
@@ -799,11 +798,11 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
var responseSet = await Subject.GetAsync(requestSet);
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().ContainKey("Cookie");
|
||||
|
||||
@@ -821,7 +820,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[TestCase("lang_code=en; expires=Wed, 23-Dec-2026 18:09:14 GMT; Max-Age=31536000; path=/; domain=.abc.com")]
|
||||
public void should_reject_malformed_domain_cookie(string malformedCookie)
|
||||
public async Task should_reject_malformed_domain_cookie(string malformedCookie)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -831,11 +830,11 @@ namespace NzbDrone.Common.Test.Http
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
var responseSet = await Subject.GetAsync(requestSet);
|
||||
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
var response = await Subject.GetAsync<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Headers.Should().NotContainKey("Cookie");
|
||||
|
||||
@@ -847,12 +846,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_correctly_use_basic_auth_with_basic_network_credential()
|
||||
public async Task should_correctly_use_basic_auth()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/basic-auth/username/password");
|
||||
request.Credentials = new BasicNetworkCredential("username", "password");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
var response = await Subject.ExecuteAsync(request);
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
if (text.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
public interface IHttpDispatcher
|
||||
{
|
||||
HttpResponse GetResponse(HttpRequest request, CookieContainer cookies);
|
||||
Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,13 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
|
||||
}
|
||||
|
||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
||||
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
|
||||
{
|
||||
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url);
|
||||
var requestMessage = new HttpRequestMessage(request.Method, (Uri)request.Url)
|
||||
{
|
||||
Version = HttpVersion.Version20,
|
||||
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
|
||||
};
|
||||
requestMessage.Headers.UserAgent.ParseAdd(_userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent));
|
||||
requestMessage.Headers.ConnectionClose = !request.ConnectionKeepAlive;
|
||||
|
||||
@@ -99,7 +103,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
|
||||
var httpClient = GetClient(request.Url);
|
||||
|
||||
using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||||
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
||||
{
|
||||
byte[] data = null;
|
||||
|
||||
@@ -107,11 +111,11 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
|
||||
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
|
||||
data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -123,7 +127,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
|
||||
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
|
||||
|
||||
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
|
||||
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode, responseMessage.Version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +164,8 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
|
||||
var client = new System.Net.Http.HttpClient(handler)
|
||||
{
|
||||
DefaultRequestVersion = HttpVersion.Version20,
|
||||
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
|
||||
Timeout = Timeout.InfiniteTimeSpan
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -25,6 +26,16 @@ namespace NzbDrone.Common.Http
|
||||
HttpResponse Post(HttpRequest request);
|
||||
HttpResponse<T> Post<T>(HttpRequest request)
|
||||
where T : new();
|
||||
|
||||
Task<HttpResponse> ExecuteAsync(HttpRequest request);
|
||||
Task DownloadFileAsync(string url, string fileName, string userAgent = null);
|
||||
Task<HttpResponse> GetAsync(HttpRequest request);
|
||||
Task<HttpResponse<T>> GetAsync<T>(HttpRequest request)
|
||||
where T : new();
|
||||
Task<HttpResponse> HeadAsync(HttpRequest request);
|
||||
Task<HttpResponse> PostAsync(HttpRequest request);
|
||||
Task<HttpResponse<T>> PostAsync<T>(HttpRequest request)
|
||||
where T : new();
|
||||
}
|
||||
|
||||
public class HttpClient : IHttpClient
|
||||
@@ -52,11 +63,11 @@ namespace NzbDrone.Common.Http
|
||||
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
|
||||
}
|
||||
|
||||
public HttpResponse Execute(HttpRequest request)
|
||||
public virtual async Task<HttpResponse> ExecuteAsync(HttpRequest request)
|
||||
{
|
||||
var cookieContainer = InitializeRequestCookies(request);
|
||||
|
||||
var response = ExecuteRequest(request, cookieContainer);
|
||||
var response = await ExecuteRequestAsync(request, cookieContainer);
|
||||
|
||||
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
||||
{
|
||||
@@ -82,7 +93,7 @@ namespace NzbDrone.Common.Http
|
||||
request.ContentSummary = null;
|
||||
}
|
||||
|
||||
response = ExecuteRequest(request, cookieContainer);
|
||||
response = await ExecuteRequestAsync(request, cookieContainer);
|
||||
}
|
||||
while (response.HasHttpRedirect);
|
||||
}
|
||||
@@ -112,6 +123,11 @@ namespace NzbDrone.Common.Http
|
||||
return response;
|
||||
}
|
||||
|
||||
public HttpResponse Execute(HttpRequest request)
|
||||
{
|
||||
return ExecuteAsync(request).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
||||
{
|
||||
return statusCode switch
|
||||
@@ -122,7 +138,7 @@ namespace NzbDrone.Common.Http
|
||||
};
|
||||
}
|
||||
|
||||
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
|
||||
private async Task<HttpResponse> ExecuteRequestAsync(HttpRequest request, CookieContainer cookieContainer)
|
||||
{
|
||||
foreach (var interceptor in _requestInterceptors)
|
||||
{
|
||||
@@ -131,14 +147,14 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
if (request.RateLimit != TimeSpan.Zero)
|
||||
{
|
||||
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
||||
await _rateLimitService.WaitAndPulseAsync(request.Url.Host, request.RateLimitKey, request.RateLimit);
|
||||
}
|
||||
|
||||
_logger.Trace(request);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
var response = _httpDispatcher.GetResponse(request, cookieContainer);
|
||||
var response = await _httpDispatcher.GetResponseAsync(request, cookieContainer);
|
||||
|
||||
HandleResponseCookies(response, cookieContainer);
|
||||
|
||||
@@ -246,7 +262,7 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadFile(string url, string fileName, string userAgent = null)
|
||||
public async Task DownloadFileAsync(string url, string fileName, string userAgent = null)
|
||||
{
|
||||
var fileNamePart = fileName + ".part";
|
||||
|
||||
@@ -261,12 +277,13 @@ namespace NzbDrone.Common.Http
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||
await using (var fileStream = new FileStream(fileNamePart, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
var request = new HttpRequest(url);
|
||||
request.AllowAutoRedirect = true;
|
||||
request.ResponseStream = fileStream;
|
||||
var response = Get(request);
|
||||
request.RequestTimeout = TimeSpan.FromSeconds(300);
|
||||
var response = await GetAsync(request);
|
||||
|
||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||
{
|
||||
@@ -293,38 +310,71 @@ namespace NzbDrone.Common.Http
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponse Get(HttpRequest request)
|
||||
public void DownloadFile(string url, string fileName, string userAgent = null)
|
||||
{
|
||||
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development#the-thread-pool-hack
|
||||
Task.Run(() => DownloadFileAsync(url, fileName, userAgent)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Task<HttpResponse> GetAsync(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
return Execute(request);
|
||||
return ExecuteAsync(request);
|
||||
}
|
||||
|
||||
public HttpResponse Get(HttpRequest request)
|
||||
{
|
||||
return Task.Run(() => GetAsync(request)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<HttpResponse<T>> GetAsync<T>(HttpRequest request)
|
||||
where T : new()
|
||||
{
|
||||
var response = await GetAsync(request);
|
||||
CheckResponseContentType(response);
|
||||
return new HttpResponse<T>(response);
|
||||
}
|
||||
|
||||
public HttpResponse<T> Get<T>(HttpRequest request)
|
||||
where T : new()
|
||||
{
|
||||
var response = Get(request);
|
||||
CheckResponseContentType(response);
|
||||
return new HttpResponse<T>(response);
|
||||
return Task.Run(() => GetAsync<T>(request)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Task<HttpResponse> HeadAsync(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Head;
|
||||
return ExecuteAsync(request);
|
||||
}
|
||||
|
||||
public HttpResponse Head(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Head;
|
||||
return Execute(request);
|
||||
return Task.Run(() => HeadAsync(request)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Task<HttpResponse> PostAsync(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Post;
|
||||
return ExecuteAsync(request);
|
||||
}
|
||||
|
||||
public HttpResponse Post(HttpRequest request)
|
||||
{
|
||||
request.Method = HttpMethod.Post;
|
||||
return Execute(request);
|
||||
return Task.Run(() => PostAsync(request)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<HttpResponse<T>> PostAsync<T>(HttpRequest request)
|
||||
where T : new()
|
||||
{
|
||||
var response = await PostAsync(request);
|
||||
CheckResponseContentType(response);
|
||||
return new HttpResponse<T>(response);
|
||||
}
|
||||
|
||||
public HttpResponse<T> Post<T>(HttpRequest request)
|
||||
where T : new()
|
||||
{
|
||||
var response = Post(request);
|
||||
CheckResponseContentType(response);
|
||||
return new HttpResponse<T>(response);
|
||||
return Task.Run(() => PostAsync<T>(request)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void CheckResponseContentType(HttpResponse response)
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace NzbDrone.Common.Http
|
||||
Method = HttpMethod.Get;
|
||||
Url = new HttpUri(url);
|
||||
Headers = new HttpHeader();
|
||||
ConnectionKeepAlive = true;
|
||||
AllowAutoRedirect = true;
|
||||
StoreRequestCookie = true;
|
||||
LogHttpError = true;
|
||||
|
||||
@@ -9,28 +9,31 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class HttpResponse
|
||||
{
|
||||
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
||||
private static readonly Regex RegexSetCookie = new ("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
||||
|
||||
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK, Version version = null)
|
||||
{
|
||||
Request = request;
|
||||
Headers = headers;
|
||||
ResponseData = binaryData;
|
||||
StatusCode = statusCode;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public HttpResponse(HttpRequest request, HttpHeader headers, string content, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||
public HttpResponse(HttpRequest request, HttpHeader headers, string content, HttpStatusCode statusCode = HttpStatusCode.OK, Version version = null)
|
||||
{
|
||||
Request = request;
|
||||
Headers = headers;
|
||||
ResponseData = Headers.GetEncodingFromContentType().GetBytes(content);
|
||||
_content = content;
|
||||
StatusCode = statusCode;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public HttpRequest Request { get; private set; }
|
||||
public HttpHeader Headers { get; private set; }
|
||||
public HttpStatusCode StatusCode { get; private set; }
|
||||
public Version Version { get; private set; }
|
||||
public byte[] ResponseData { get; private set; }
|
||||
|
||||
private string _content;
|
||||
@@ -84,7 +87,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var result = string.Format("Res: [{0}] {1}: {2}.{3} ({4} bytes)", Request.Method, Request.Url, (int)StatusCode, StatusCode, ResponseData?.Length ?? 0);
|
||||
var result = $"Res: HTTP/{Version} [{Request.Method}] {Request.Url}: {(int)StatusCode}.{StatusCode} ({ResponseData?.Length ?? 0} bytes)";
|
||||
|
||||
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
@@ -99,7 +102,7 @@ namespace NzbDrone.Common.Http
|
||||
where T : new()
|
||||
{
|
||||
public HttpResponse(HttpResponse response)
|
||||
: base(response.Request, response.Headers, response.ResponseData, response.StatusCode)
|
||||
: base(response.Request, response.Headers, response.ResponseData, response.StatusCode, response.Version)
|
||||
{
|
||||
Resource = Json.Deserialize<T>(response.Content);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -10,6 +11,8 @@ namespace NzbDrone.Common.TPL
|
||||
{
|
||||
void WaitAndPulse(string key, TimeSpan interval);
|
||||
void WaitAndPulse(string key, string subKey, TimeSpan interval);
|
||||
Task WaitAndPulseAsync(string key, TimeSpan interval);
|
||||
Task WaitAndPulseAsync(string key, string subKey, TimeSpan interval);
|
||||
}
|
||||
|
||||
public class RateLimitService : IRateLimitService
|
||||
@@ -28,7 +31,34 @@ namespace NzbDrone.Common.TPL
|
||||
WaitAndPulse(key, null, interval);
|
||||
}
|
||||
|
||||
public async Task WaitAndPulseAsync(string key, TimeSpan interval)
|
||||
{
|
||||
await WaitAndPulseAsync(key, null, interval);
|
||||
}
|
||||
|
||||
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
|
||||
{
|
||||
var delay = GetDelay(key, subKey, interval);
|
||||
|
||||
if (delay.TotalSeconds > 0.0)
|
||||
{
|
||||
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||
System.Threading.Thread.Sleep(delay);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WaitAndPulseAsync(string key, string subKey, TimeSpan interval)
|
||||
{
|
||||
var delay = GetDelay(key, subKey, interval);
|
||||
|
||||
if (delay.TotalSeconds > 0.0)
|
||||
{
|
||||
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan GetDelay(string key, string subKey, TimeSpan interval)
|
||||
{
|
||||
var waitUntil = DateTime.UtcNow.Add(interval);
|
||||
|
||||
@@ -59,13 +89,7 @@ namespace NzbDrone.Common.TPL
|
||||
|
||||
waitUntil -= interval;
|
||||
|
||||
var delay = waitUntil - DateTime.UtcNow;
|
||||
|
||||
if (delay.TotalSeconds > 0.0)
|
||||
{
|
||||
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
|
||||
System.Threading.Thread.Sleep(delay);
|
||||
}
|
||||
return waitUntil - DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -33,8 +34,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
||||
};
|
||||
|
||||
Mocker
|
||||
.GetMock<IIndexerRepository>()
|
||||
.GetMock<IIndexerFactory>()
|
||||
.Setup(m => m.Get(It.IsAny<int>()))
|
||||
.Throws(new ModelNotFoundException(typeof(IndexerDefinition), -1));
|
||||
|
||||
Mocker
|
||||
.GetMock<IIndexerFactory>()
|
||||
.Setup(m => m.Get(1))
|
||||
.Returns(_fakeIndexerDefinition);
|
||||
|
||||
_specification = Mocker.Resolve<IndexerTagSpecification>();
|
||||
@@ -106,5 +112,25 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
|
||||
|
||||
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void release_without_indexerid_should_return_true()
|
||||
{
|
||||
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||
_fakeAuthor.Tags = new HashSet<int> { 123, 789 };
|
||||
_fakeRelease.IndexerId = 0;
|
||||
|
||||
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void release_with_invalid_indexerid_should_return_true()
|
||||
{
|
||||
_fakeIndexerDefinition.Tags = new HashSet<int> { 456 };
|
||||
_fakeAuthor.Tags = new HashSet<int> { 123, 789 };
|
||||
_fakeRelease.IndexerId = 2;
|
||||
|
||||
_specification.IsSatisfiedBy(_parseResultMulti, new BookSearchCriteria { MonitoredBooksOnly = true }).Accepted.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_download_report_if_book_was_not_already_downloaded()
|
||||
public async Task should_download_report_if_book_was_not_already_downloaded()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -66,12 +67,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteBook));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_only_download_book_once()
|
||||
public async Task should_only_download_book_once()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -80,12 +81,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook));
|
||||
decisions.Add(new DownloadDecision(remoteBook));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_download_if_any_book_was_already_downloaded()
|
||||
public async Task should_not_download_if_any_book_was_already_downloaded()
|
||||
{
|
||||
var remoteBook1 = GetRemoteBook(
|
||||
new List<Book> { GetBook(1) },
|
||||
@@ -99,12 +100,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook1));
|
||||
decisions.Add(new DownloadDecision(remoteBook2));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_downloaded_reports()
|
||||
public async Task should_return_downloaded_reports()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -112,11 +113,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteBook));
|
||||
|
||||
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(1);
|
||||
var result = await Subject.ProcessDecisions(decisions);
|
||||
|
||||
result.Grabbed.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_all_downloaded_reports()
|
||||
public async Task should_return_all_downloaded_reports()
|
||||
{
|
||||
var remoteBook1 = GetRemoteBook(
|
||||
new List<Book> { GetBook(1) },
|
||||
@@ -130,11 +133,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook1));
|
||||
decisions.Add(new DownloadDecision(remoteBook2));
|
||||
|
||||
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
|
||||
var result = await Subject.ProcessDecisions(decisions);
|
||||
|
||||
result.Grabbed.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_only_return_downloaded_reports()
|
||||
public async Task should_only_return_downloaded_reports()
|
||||
{
|
||||
var remoteBook1 = GetRemoteBook(
|
||||
new List<Book> { GetBook(1) },
|
||||
@@ -153,11 +158,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook2));
|
||||
decisions.Add(new DownloadDecision(remoteBook3));
|
||||
|
||||
Subject.ProcessDecisions(decisions).Grabbed.Should().HaveCount(2);
|
||||
var result = await Subject.ProcessDecisions(decisions);
|
||||
|
||||
result.Grabbed.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_add_to_downloaded_list_when_download_fails()
|
||||
public async Task should_not_add_to_downloaded_list_when_download_fails()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -166,7 +173,11 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook));
|
||||
|
||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteBook>())).Throws(new Exception());
|
||||
Subject.ProcessDecisions(decisions).Grabbed.Should().BeEmpty();
|
||||
|
||||
var result = await Subject.ProcessDecisions(decisions);
|
||||
|
||||
result.Grabbed.Should().BeEmpty();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
@@ -181,7 +192,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_grab_if_pending()
|
||||
public async Task should_not_grab_if_pending()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -189,12 +200,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_add_to_pending_if_book_was_grabbed()
|
||||
public async Task should_not_add_to_pending_if_book_was_grabbed()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -203,12 +214,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook));
|
||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_to_pending_even_if_already_added_to_pending()
|
||||
public async Task should_add_to_pending_even_if_already_added_to_pending()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -217,12 +228,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteBook, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_to_failed_if_already_failed_for_that_protocol()
|
||||
public async Task should_add_to_failed_if_already_failed_for_that_protocol()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -234,12 +245,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteBook>()))
|
||||
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteBook>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_add_to_failed_if_failed_for_a_different_protocol()
|
||||
public async Task should_not_add_to_failed_if_failed_for_a_different_protocol()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3), DownloadProtocol.Usenet);
|
||||
@@ -252,13 +263,13 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)))
|
||||
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)), Times.Once());
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteBook>(r => r.Release.DownloadProtocol == DownloadProtocol.Torrent)), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_to_rejected_if_release_unavailable_on_indexer()
|
||||
public async Task should_add_to_rejected_if_release_unavailable_on_indexer()
|
||||
{
|
||||
var books = new List<Book> { GetBook(1) };
|
||||
var remoteBook = GetRemoteBook(books, new QualityModel(Quality.MP3));
|
||||
@@ -270,7 +281,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
.Setup(s => s.DownloadReport(It.IsAny<RemoteBook>()))
|
||||
.Throws(new ReleaseUnavailableException(remoteBook.Release, "That 404 Error is not just a Quirk"));
|
||||
|
||||
var result = Subject.ProcessDecisions(decisions);
|
||||
var result = await Subject.ProcessDecisions(decisions);
|
||||
|
||||
result.Grabbed.Should().BeEmpty();
|
||||
result.Rejected.Should().NotBeEmpty();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -69,7 +70,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
protected void GivenFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
||||
.Throws(new WebException());
|
||||
}
|
||||
|
||||
@@ -147,19 +148,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_download_file_if_it_doesnt_exist()
|
||||
public async Task Download_should_download_file_if_it_doesnt_exist()
|
||||
{
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_save_magnet_if_enabled()
|
||||
public async Task Download_should_save_magnet_if_enabled()
|
||||
{
|
||||
GivenMagnetFilePath();
|
||||
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
||||
@@ -167,16 +168,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = null;
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_save_magnet_using_specified_extension()
|
||||
public async Task Download_should_save_magnet_using_specified_extension()
|
||||
{
|
||||
var magnetFileExtension = ".url";
|
||||
GivenMagnetFilePath(magnetFileExtension);
|
||||
@@ -187,12 +188,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = null;
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -202,31 +203,31 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = null;
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Never());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Never());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_prefer_torrent_over_magnet()
|
||||
public async Task Download_should_prefer_torrent_over_magnet()
|
||||
{
|
||||
Subject.Definition.Settings.As<TorrentBlackholeSettings>().SaveMagnetFiles = true;
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_magnetFilePath), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_replace_illegal_characters_in_title()
|
||||
public async Task Download_should_replace_illegal_characters_in_title()
|
||||
{
|
||||
var illegalTitle = "Radiohead - Scotch Mist [2008/FLAC/Lossless]";
|
||||
var expectedFilename = Path.Combine(_blackholeFolder, "Radiohead - Scotch Mist [2008+FLAC+Lossless]" + Path.GetExtension(_filePath));
|
||||
@@ -234,11 +235,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.Title = illegalTitle;
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -247,7 +248,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = null;
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -317,11 +318,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_hash()
|
||||
public async Task should_return_null_hash()
|
||||
{
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer()).Should().BeNull();
|
||||
var result = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
result.Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -119,19 +120,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_download_file_if_it_doesnt_exist()
|
||||
public async Task Download_should_download_file_if_it_doesnt_exist()
|
||||
{
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_replace_illegal_characters_in_title()
|
||||
public async Task Download_should_replace_illegal_characters_in_title()
|
||||
{
|
||||
var illegalTitle = "Radiohead - Scotch Mist [2008/FLAC/Lossless]";
|
||||
var expectedFilename = Path.Combine(_blackholeFolder, "Radiohead - Scotch Mist [2008+FLAC+Lossless]" + Path.GetExtension(_filePath));
|
||||
@@ -139,11 +140,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.Title = illegalTitle;
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.GetAsync(It.Is<HttpRequest>(v => v.Url.FullUri == _downloadUrl)), Times.Once());
|
||||
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -200,26 +201,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NLog;
|
||||
@@ -36,8 +37,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
.Returns(() => CreateRemoteBook());
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
|
||||
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), Array.Empty<byte>())));
|
||||
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal(It.IsAny<string>(), It.IsAny<OsPath>()))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -385,7 +386,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_TvDirectory_should_force_directory()
|
||||
public async Task Download_with_TvDirectory_should_force_directory()
|
||||
{
|
||||
GivenSerialNumber();
|
||||
GivenTvDirectory();
|
||||
@@ -393,7 +394,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -402,7 +403,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_force_directory()
|
||||
public async Task Download_with_category_should_force_directory()
|
||||
{
|
||||
GivenSerialNumber();
|
||||
GivenMusicCategory();
|
||||
@@ -410,7 +411,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -419,14 +420,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenSerialNumber();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -505,7 +506,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
.Setup(s => s.GetSerialNumber(_settings))
|
||||
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||
|
||||
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync(Is.InstanceOf<Exception>(), async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -262,7 +263,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_TvDirectory_should_force_directory()
|
||||
public async Task Download_with_TvDirectory_should_force_directory()
|
||||
{
|
||||
GivenSerialNumber();
|
||||
GivenTvDirectory();
|
||||
@@ -270,7 +271,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -279,7 +280,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_force_directory()
|
||||
public async Task Download_with_category_should_force_directory()
|
||||
{
|
||||
GivenSerialNumber();
|
||||
GivenMusicCategory();
|
||||
@@ -287,7 +288,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -296,14 +297,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenSerialNumber();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -382,7 +383,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests
|
||||
.Setup(s => s.GetSerialNumber(_settings))
|
||||
.Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException"));
|
||||
|
||||
Assert.Throws(Is.InstanceOf<Exception>(), () => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync(Is.InstanceOf<Exception>(), async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
|
||||
Mocker.GetMock<IDownloadStationTaskProxy>()
|
||||
.Verify(v => v.AddTaskFromUrl(It.IsAny<string>(), null, _settings), Times.Never());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -103,8 +104,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
|
||||
.Setup(s => s.GetAsync(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), new byte[1000])));
|
||||
|
||||
Mocker.GetMock<IHadoukenProxy>()
|
||||
.Setup(s => s.AddTorrentUri(It.IsAny<HadoukenSettings>(), It.IsAny<string>()))
|
||||
@@ -196,13 +197,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
@@ -277,7 +278,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_from_magnet_link_should_return_hash_uppercase()
|
||||
public async Task Download_from_magnet_link_should_return_hash_uppercase()
|
||||
{
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
@@ -286,13 +287,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
||||
Mocker.GetMock<IHadoukenProxy>()
|
||||
.Setup(v => v.AddTorrentUri(It.IsAny<HadoukenSettings>(), It.IsAny<string>()));
|
||||
|
||||
var result = Subject.Download(remoteBook, CreateIndexer());
|
||||
var result = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_from_torrent_file_should_return_hash_uppercase()
|
||||
public async Task Download_from_torrent_file_should_return_hash_uppercase()
|
||||
{
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
@@ -300,7 +301,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests
|
||||
.Setup(v => v.AddTorrentFile(It.IsAny<HadoukenSettings>(), It.IsAny<byte[]>()))
|
||||
.Returns("hash");
|
||||
|
||||
var result = Subject.Download(remoteBook, CreateIndexer());
|
||||
var result = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Assert.IsFalse(result.Any(c => char.IsLower(c)));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -200,13 +201,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
@@ -218,7 +219,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -339,13 +340,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
@@ -357,7 +358,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
Assert.Throws<DownloadClientRejectedReleaseException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync<DownloadClientRejectedReleaseException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NLog;
|
||||
@@ -65,15 +66,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
|
||||
private void WithFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<IHttpClient>().Setup(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
|
||||
Mocker.GetMock<IHttpClient>().Setup(c => c.DownloadFileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_download_file_if_it_doesnt_exist()
|
||||
public async Task should_download_file_if_it_doesnt_exist()
|
||||
{
|
||||
Subject.Download(_remoteBook, _indexer);
|
||||
await Subject.Download(_remoteBook, _indexer);
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath, null), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(_nzbUrl, _nzbPath, null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -81,7 +82,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
{
|
||||
WithFailedDownload();
|
||||
|
||||
Assert.Throws<WebException>(() => Subject.Download(_remoteBook, _indexer));
|
||||
Assert.ThrowsAsync<WebException>(async () => await Subject.Download(_remoteBook, _indexer));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -90,7 +91,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
_remoteBook.Release.Title = "Alien Ant Farm - Discography";
|
||||
_remoteBook.ParsedBookInfo.Discography = true;
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => Subject.Download(_remoteBook, _indexer));
|
||||
Assert.ThrowsAsync<NotSupportedException>(async () => await Subject.Download(_remoteBook, _indexer));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -100,15 +101,15 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_illegal_characters_in_title()
|
||||
public async Task should_replace_illegal_characters_in_title()
|
||||
{
|
||||
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
|
||||
var expectedFilename = Path.Combine(_pneumaticFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
|
||||
_remoteBook.Release.Title = illegalTitle;
|
||||
|
||||
Subject.Download(_remoteBook, _indexer);
|
||||
await Subject.Download(_remoteBook, _indexer);
|
||||
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), expectedFilename, null), Times.Once());
|
||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFileAsync(It.IsAny<string>(), expectedFilename, null), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -449,26 +450,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
@@ -483,7 +484,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR";
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -496,28 +497,28 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp://abc";
|
||||
|
||||
Assert.DoesNotThrow(() => Subject.Download(remoteBook, CreateIndexer()));
|
||||
Assert.DoesNotThrowAsync(async () => await Subject.Download(remoteBook, CreateIndexer()));
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Verify(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_set_top_priority()
|
||||
public async Task Download_should_set_top_priority()
|
||||
{
|
||||
GivenHighPriority();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Verify(v => v.MoveTorrentToTopInQueue(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_not_fail_if_top_priority_not_available()
|
||||
public async Task Download_should_not_fail_if_top_priority_not_available()
|
||||
{
|
||||
GivenHighPriority();
|
||||
GivenSuccessfulDownload();
|
||||
@@ -528,7 +529,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -555,27 +556,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_handle_http_redirect_to_magnet()
|
||||
public async Task Download_should_handle_http_redirect_to_magnet()
|
||||
{
|
||||
GivenRedirectToMagnet();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_handle_http_redirect_to_torrent()
|
||||
public async Task Download_should_handle_http_redirect_to_torrent()
|
||||
{
|
||||
GivenRedirectToTorrent();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
@@ -633,7 +634,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
.Returns(new QBittorrentTorrentProperties
|
||||
{
|
||||
Hash = "HASH",
|
||||
SeedingTime = seedingTime
|
||||
SeedingTime = seedingTime * 60
|
||||
});
|
||||
|
||||
return torrent;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -121,13 +122,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -300,27 +301,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
}
|
||||
|
||||
[TestCase("[ TOWN ]-[ http://www.town.ag ]-[ ANIME ]-[Usenet Provider >> http://www.ssl- <<] - [Commie] Aldnoah Zero 18 [234C8FC7]", "[ TOWN ]-[ http-++www.town.ag ]-[ ANIME ]-[Usenet Provider http-++www.ssl- ] - [Commie] Aldnoah Zero 18 [234C8FC7].nzb")]
|
||||
public void Download_should_use_clean_title(string title, string filename)
|
||||
public async Task Download_should_use_clean_title(string title, string filename)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.Title = title;
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), filename, It.IsAny<string>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
@@ -353,7 +354,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
||||
public async Task Download_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
||||
{
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()))
|
||||
@@ -366,7 +367,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
Subject.Download(remoteBook, CreateIndexer());
|
||||
await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
Mocker.GetMock<ISabnzbdProxy>()
|
||||
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()), Times.Once());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -55,26 +56,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_TvDirectory_should_force_directory()
|
||||
public async Task Download_with_TvDirectory_should_force_directory()
|
||||
{
|
||||
GivenTvDirectory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -83,14 +84,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_force_directory()
|
||||
public async Task Download_with_category_should_force_directory()
|
||||
{
|
||||
GivenMusicCategory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -99,7 +100,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_not_have_double_slashes()
|
||||
public async Task Download_with_category_should_not_have_double_slashes()
|
||||
{
|
||||
GivenMusicCategory();
|
||||
GivenSuccessfulDownload();
|
||||
@@ -108,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -117,13 +118,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -132,14 +133,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
||||
}
|
||||
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -228,13 +229,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
@@ -252,14 +253,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
|
||||
// Proxy.GetTorrents does not return original url. So item has to be found via magnet url.
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
@@ -350,27 +351,27 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_handle_http_redirect_to_magnet()
|
||||
public async Task Download_should_handle_http_redirect_to_magnet()
|
||||
{
|
||||
GivenRedirectToMagnet();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_handle_http_redirect_to_torrent()
|
||||
public async Task Download_should_handle_http_redirect_to_torrent()
|
||||
{
|
||||
GivenRedirectToTorrent();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -63,26 +64,26 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
public async Task Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_TvDirectory_should_force_directory()
|
||||
public async Task Download_with_TvDirectory_should_force_directory()
|
||||
{
|
||||
GivenTvDirectory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -91,14 +92,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_force_directory()
|
||||
public async Task Download_with_category_should_force_directory()
|
||||
{
|
||||
GivenMusicCategory();
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -107,7 +108,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_with_category_should_not_have_double_slashes()
|
||||
public async Task Download_with_category_should_not_have_double_slashes()
|
||||
{
|
||||
GivenMusicCategory();
|
||||
GivenSuccessfulDownload();
|
||||
@@ -116,7 +117,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -125,13 +126,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_without_TvDirectory_and_Category_should_use_default()
|
||||
public async Task Download_without_TvDirectory_and_Category_should_use_default()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
|
||||
@@ -140,14 +141,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
||||
}
|
||||
|
||||
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
|
||||
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
public async Task Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteBook = CreateRemoteBook();
|
||||
remoteBook.Release.DownloadUrl = magnetUrl;
|
||||
|
||||
var id = Subject.Download(remoteBook, CreateIndexer());
|
||||
var id = await Subject.Download(remoteBook, CreateIndexer());
|
||||
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -77,23 +78,23 @@ namespace NzbDrone.Core.Test.Download
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_report_should_publish_on_grab_event()
|
||||
public async Task Download_report_should_publish_on_grab_event()
|
||||
{
|
||||
var mock = WithUsenetClient();
|
||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()));
|
||||
|
||||
Subject.DownloadReport(_parseResult);
|
||||
await Subject.DownloadReport(_parseResult);
|
||||
|
||||
VerifyEventPublished<BookGrabbedEvent>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_report_should_grab_using_client()
|
||||
public async Task Download_report_should_grab_using_client()
|
||||
{
|
||||
var mock = WithUsenetClient();
|
||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()));
|
||||
|
||||
Subject.DownloadReport(_parseResult);
|
||||
await Subject.DownloadReport(_parseResult);
|
||||
|
||||
mock.Verify(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||
}
|
||||
@@ -105,7 +106,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()))
|
||||
.Throws(new WebException());
|
||||
|
||||
Assert.Throws<WebException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<WebException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
VerifyEventNotPublished<BookGrabbedEvent>();
|
||||
}
|
||||
@@ -120,7 +121,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
throw new ReleaseDownloadException(v.Release, "Error", new WebException());
|
||||
});
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
|
||||
@@ -140,7 +141,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
||||
});
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5.0)), Times.Once());
|
||||
@@ -160,7 +161,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
|
||||
});
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(),
|
||||
@@ -174,7 +175,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
mock.Setup(s => s.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()))
|
||||
.Throws(new DownloadClientException("Some Error"));
|
||||
|
||||
Assert.Throws<DownloadClientException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
|
||||
@@ -190,7 +191,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
throw new ReleaseUnavailableException(v.Release, "Error", new WebException());
|
||||
});
|
||||
|
||||
Assert.Throws<ReleaseUnavailableException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<ReleaseUnavailableException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
|
||||
@@ -199,14 +200,14 @@ namespace NzbDrone.Core.Test.Download
|
||||
[Test]
|
||||
public void should_not_attempt_download_if_client_isnt_configured()
|
||||
{
|
||||
Assert.Throws<DownloadClientUnavailableException>(() => Subject.DownloadReport(_parseResult));
|
||||
Assert.ThrowsAsync<DownloadClientUnavailableException>(async () => await Subject.DownloadReport(_parseResult));
|
||||
|
||||
Mocker.GetMock<IDownloadClient>().Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
||||
VerifyEventNotPublished<BookGrabbedEvent>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_attempt_download_even_if_client_is_disabled()
|
||||
public async Task should_attempt_download_even_if_client_is_disabled()
|
||||
{
|
||||
var mockUsenet = WithUsenetClient();
|
||||
|
||||
@@ -221,7 +222,7 @@ namespace NzbDrone.Core.Test.Download
|
||||
}
|
||||
});
|
||||
|
||||
Subject.DownloadReport(_parseResult);
|
||||
await Subject.DownloadReport(_parseResult);
|
||||
|
||||
Mocker.GetMock<IDownloadClientStatusService>().Verify(c => c.GetBlockedProviders(), Times.Never());
|
||||
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||
@@ -229,26 +230,26 @@ namespace NzbDrone.Core.Test.Download
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_send_download_to_correct_usenet_client()
|
||||
public async Task should_send_download_to_correct_usenet_client()
|
||||
{
|
||||
var mockTorrent = WithTorrentClient();
|
||||
var mockUsenet = WithUsenetClient();
|
||||
|
||||
Subject.DownloadReport(_parseResult);
|
||||
await Subject.DownloadReport(_parseResult);
|
||||
|
||||
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
||||
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_send_download_to_correct_torrent_client()
|
||||
public async Task should_send_download_to_correct_torrent_client()
|
||||
{
|
||||
var mockTorrent = WithTorrentClient();
|
||||
var mockUsenet = WithUsenetClient();
|
||||
|
||||
_parseResult.Release.DownloadProtocol = DownloadProtocol.Torrent;
|
||||
|
||||
Subject.DownloadReport(_parseResult);
|
||||
await Subject.DownloadReport(_parseResult);
|
||||
|
||||
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Once());
|
||||
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteBook>(), It.IsAny<IIndexer>()), Times.Never());
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class DownloadClientRemovesCompletedDownloadsCheckFixture : CoreTest<DownloadClientRemovesCompletedDownloadsCheck>
|
||||
{
|
||||
private DownloadClientInfo _clientStatus;
|
||||
private Mock<IDownloadClient> _downloadClient;
|
||||
|
||||
private static Exception[] DownloadClientExceptions =
|
||||
{
|
||||
new DownloadClientUnavailableException("error"),
|
||||
new DownloadClientAuthenticationException("error"),
|
||||
new DownloadClientException("error")
|
||||
};
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_clientStatus = new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = true,
|
||||
|
||||
// SortingMode = null,
|
||||
RemovesCompletedDownloads = true
|
||||
};
|
||||
|
||||
_downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||
_downloadClient.Setup(s => s.Definition)
|
||||
.Returns(new DownloadClientDefinition { Name = "Test" });
|
||||
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
.Returns(_clientStatus);
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(s => s.GetDownloadClients(It.IsAny<bool>()))
|
||||
.Returns(new IDownloadClient[] { _downloadClient.Object });
|
||||
|
||||
Mocker.GetMock<ILocalizationService>()
|
||||
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
|
||||
.Returns("Some Warning Message");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_if_removing_completed_downloads_is_enabled()
|
||||
{
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_if_remove_completed_downloads_is_not_enabled()
|
||||
{
|
||||
_clientStatus.RemovesCompletedDownloads = false;
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(DownloadClientExceptions))]
|
||||
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
|
||||
{
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
.Throws(ex);
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
|
||||
ExceptionVerification.ExpectedErrors(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Books;
|
||||
@@ -27,11 +28,11 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
|
||||
Mocker.GetMock<ISearchForReleases>()
|
||||
.Setup(s => s.AuthorSearch(_author.Id, false, true, false))
|
||||
.Returns(new List<DownloadDecision>());
|
||||
.Returns(Task.FromResult(new List<DownloadDecision>()));
|
||||
|
||||
Mocker.GetMock<IProcessDownloadDecisions>()
|
||||
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
||||
.Returns(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>(), new List<DownloadDecision>()));
|
||||
.Returns(Task.FromResult(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>(), new List<DownloadDecision>())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -60,13 +61,13 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
|
||||
_mockIndexer.Setup(v => v.Fetch(It.IsAny<BookSearchCriteria>()))
|
||||
.Callback<BookSearchCriteria>(s => result.Add(s))
|
||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
||||
.Returns(Task.FromResult<IList<Parser.Model.ReleaseInfo>>(new List<Parser.Model.ReleaseInfo>()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerTags_AuthorNoTags_IndexerNotIncluded()
|
||||
public async Task Tags_IndexerTags_AuthorNoTags_IndexerNotIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.BookSearch(_firstBook, false, true, false);
|
||||
await Subject.BookSearch(_firstBook, false, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||
|
||||
@@ -84,7 +85,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerNoTags_AuthorTags_IndexerIncluded()
|
||||
public async Task Tags_IndexerNoTags_AuthorTags_IndexerIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
@@ -102,7 +103,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.BookSearch(_firstBook, false, true, false);
|
||||
await Subject.BookSearch(_firstBook, false, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||
|
||||
@@ -110,7 +111,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerAndAuthorTagsMatch_IndexerIncluded()
|
||||
public async Task Tags_IndexerAndAuthorTagsMatch_IndexerIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
@@ -129,7 +130,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.BookSearch(_firstBook, false, true, false);
|
||||
await Subject.BookSearch(_firstBook, false, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||
|
||||
@@ -137,7 +138,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tags_IndexerAndAuthorTagsMismatch_IndexerNotIncluded()
|
||||
public async Task Tags_IndexerAndAuthorTagsMismatch_IndexerNotIncluded()
|
||||
{
|
||||
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition
|
||||
{
|
||||
@@ -156,7 +157,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.BookSearch(_firstBook, false, true, false);
|
||||
await Subject.BookSearch(_firstBook, false, true, false);
|
||||
|
||||
var criteria = allCriteria.OfType<BookSearchCriteria>().ToList();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -26,15 +27,15 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_FileList()
|
||||
public async Task should_parse_recent_feed_from_FileList()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/FileList/RecentFeed.json");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -30,24 +31,24 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_gazelle()
|
||||
public async Task should_parse_recent_feed_from_gazelle()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Gazelle/Gazelle.json");
|
||||
var indexFeed = ReadAllText(@"Files/Indexers/Gazelle/GazelleIndex.json");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=browse"))))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader { ContentType = "application/json" }, recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get && v.Url.FullUri.Contains("ajax.php?action=browse"))))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader { ContentType = "application/json" }, recentFeed)));
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("ajax.php?action=index"))))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), indexFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("ajax.php?action=index"))))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), indexFeed)));
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("login.php"))))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), indexFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post && v.Url.FullUri.Contains("login.php"))))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), indexFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(4);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -84,15 +85,15 @@ namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_IPTorrents()
|
||||
public async Task should_parse_recent_feed_from_IPTorrents()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/IPTorrents/IPTorrents.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -39,15 +40,15 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_newznab_nzb_su()
|
||||
public async Task should_parse_recent_feed_from_newznab_nzb_su()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Newznab/newznab_nzb_su.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(100);
|
||||
|
||||
@@ -83,7 +84,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_record_indexer_failure_if_caps_throw()
|
||||
public async Task should_record_indexer_failure_if_caps_throw()
|
||||
{
|
||||
var request = new HttpRequest("http://my.indexer.com");
|
||||
var response = new HttpResponse(request, new HttpHeader(), new byte[0], (HttpStatusCode)429);
|
||||
@@ -96,7 +97,9 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
_caps.MaxPageSize = 30;
|
||||
_caps.DefaultPageSize = 25;
|
||||
|
||||
Subject.FetchRecent().Should().BeEmpty();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().BeEmpty();
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5.0)), Times.Once());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -26,15 +27,15 @@ namespace NzbDrone.Core.Test.IndexerTests.NyaaTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_Nyaa()
|
||||
public async Task should_parse_recent_feed_from_Nyaa()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Nyaa/Nyaa.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(4);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
{
|
||||
var result = new List<ValidationFailure>();
|
||||
SetupNLog(); // Enable this to enable trace logging with nlog for debugging purposes
|
||||
Test(result);
|
||||
Test(result).GetAwaiter().GetResult();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -34,17 +35,21 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/" + rssXmlFile);
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.ExecuteAsync(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.IsAny<HttpRequest>()))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_ImmortalSeed()
|
||||
public async Task should_parse_recent_feed_from_ImmortalSeed()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/ImmortalSeed.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(50);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -66,11 +71,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_Ezrss()
|
||||
public async Task should_parse_recent_feed_from_Ezrss()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/Ezrss.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(3);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -92,13 +97,13 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_ShowRSS_info()
|
||||
public async Task should_parse_recent_feed_from_ShowRSS_info()
|
||||
{
|
||||
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
|
||||
|
||||
GivenRecentFeedResponse("TorrentRss/ShowRSS.info.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -120,13 +125,13 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_Doki()
|
||||
public async Task should_parse_recent_feed_from_Doki()
|
||||
{
|
||||
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
|
||||
|
||||
GivenRecentFeedResponse("TorrentRss/Doki.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -148,11 +153,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_ExtraTorrents()
|
||||
public async Task should_parse_recent_feed_from_ExtraTorrents()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/ExtraTorrents.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -174,11 +179,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_LimeTorrents()
|
||||
public async Task should_parse_recent_feed_from_LimeTorrents()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/LimeTorrents.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -200,11 +205,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_AnimeTosho_without_size()
|
||||
public async Task should_parse_recent_feed_from_AnimeTosho_without_size()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -226,11 +231,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_multi_enclosure_from_AnimeTosho()
|
||||
public async Task should_parse_multi_enclosure_from_AnimeTosho()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/AnimeTosho_NoSize.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.Last().Should().BeOfType<TorrentInfo>();
|
||||
@@ -243,11 +248,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_AlphaRatio()
|
||||
public async Task should_parse_recent_feed_from_AlphaRatio()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/AlphaRatio.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.Last().Should().BeOfType<TorrentInfo>();
|
||||
@@ -260,12 +265,12 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_EveolutionWorld_without_size()
|
||||
public async Task should_parse_recent_feed_from_EveolutionWorld_without_size()
|
||||
{
|
||||
Subject.Definition.Settings.As<TorrentRssIndexerSettings>().AllowZeroSize = true;
|
||||
GivenRecentFeedResponse("TorrentRss/EvolutionWorld.xml");
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
@@ -287,11 +292,13 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_record_indexer_failure_if_unsupported_feed()
|
||||
public async Task should_record_indexer_failure_if_unsupported_feed()
|
||||
{
|
||||
GivenRecentFeedResponse("TorrentRss/invalid/TorrentDay_NoPubDate.xml");
|
||||
|
||||
Subject.FetchRecent().Should().BeEmpty();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().BeEmpty();
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.Zero), Times.Once());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -26,15 +27,15 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentleechTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_Torrentleech()
|
||||
public async Task should_parse_recent_feed_from_Torrentleech()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torrentleech/Torrentleech.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@@ -44,15 +45,15 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_torznab_hdaccess_net()
|
||||
public async Task should_parse_recent_feed_from_torznab_hdaccess_net()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_hdaccess_net.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
|
||||
@@ -73,15 +74,15 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_torznab_tpb()
|
||||
public async Task should_parse_recent_feed_from_torznab_tpb()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
var releases = await Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(5);
|
||||
|
||||
@@ -140,8 +141,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
(Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl;
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
var result = new NzbDroneValidationResult(Subject.Test());
|
||||
result.IsValid.Should().BeTrue();
|
||||
@@ -155,8 +156,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
|
||||
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
|
||||
|
||||
(Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath;
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
{
|
||||
if (release.InfoHash.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return release.InfoHash.Equals(item.TorrentInfoHash);
|
||||
return release.InfoHash.Equals(item.TorrentInfoHash, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
return HasSameIndexer(item, release.Indexer);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -10,12 +11,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public class IndexerTagSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IIndexerRepository _indexerRepository;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
public IndexerTagSpecification(Logger logger, IIndexerRepository indexerRepository)
|
||||
public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_indexerRepository = indexerRepository;
|
||||
_indexerFactory = indexerFactory;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
@@ -23,8 +24,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteBook subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
// If indexer has tags, check that at least one of them is present on the author
|
||||
var indexerTags = _indexerRepository.Get(subject.Release.IndexerId).Tags;
|
||||
if (subject.Release == null || subject.Author?.Tags == null || subject.Release.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
try
|
||||
{
|
||||
indexer = _indexerFactory.Get(subject.Release.IndexerId);
|
||||
}
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
// If indexer has tags, check that at least one of them is present on the series
|
||||
var indexerTags = indexer.Tags;
|
||||
|
||||
if (indexerTags.Any() && indexerTags.Intersect(subject.Author.Tags).Empty())
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -32,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
|
||||
public override string Download(RemoteBook remoteBook, IIndexer indexer)
|
||||
public override async Task<string> Download(RemoteBook remoteBook, IIndexer indexer)
|
||||
{
|
||||
var url = remoteBook.Release.DownloadUrl;
|
||||
var title = remoteBook.Release.Title;
|
||||
@@ -48,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||
var nzbFile = Path.Combine(Settings.NzbFolder, title + ".nzb");
|
||||
|
||||
_logger.Debug("Downloading NZB from: {0} to: {1}", url, nzbFile);
|
||||
_httpClient.DownloadFile(url, nzbFile);
|
||||
await _httpClient.DownloadFileAsync(url, nzbFile);
|
||||
|
||||
_logger.Debug("NZB Download succeeded, saved to: {0}", nzbFile);
|
||||
|
||||
|
||||
@@ -389,7 +389,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
|
||||
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) }
|
||||
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) },
|
||||
RemovesCompletedDownloads = (config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -628,11 +629,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
if (torrent.SeedingTimeLimit >= 0)
|
||||
{
|
||||
seedingTimeLimit = torrent.SeedingTimeLimit;
|
||||
seedingTimeLimit = torrent.SeedingTimeLimit * 60;
|
||||
}
|
||||
else if (torrent.SeedingTimeLimit == -2 && config.MaxSeedingTimeEnabled)
|
||||
{
|
||||
seedingTimeLimit = config.MaxSeedingTime;
|
||||
seedingTimeLimit = config.MaxSeedingTime * 60;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
public float RatioLimit { get; set; } = -2;
|
||||
|
||||
[JsonProperty(PropertyName = "seeding_time")]
|
||||
public long? SeedingTime { get; set; } // Torrent seeding time (not provided by the list api)
|
||||
public long? SeedingTime { get; set; } // Torrent seeding time (in seconds, not provided by the list api)
|
||||
|
||||
[JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited)
|
||||
public long SeedingTimeLimit { get; set; } = -2;
|
||||
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
public string SavePath { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "seeding_time")]
|
||||
public long SeedingTime { get; set; } // Torrent seeding time
|
||||
public long SeedingTime { get; set; } // Torrent seeding time (in seconds)
|
||||
}
|
||||
|
||||
public class QBittorrentTorrentFile
|
||||
|
||||
@@ -263,6 +263,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
status.OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) };
|
||||
}
|
||||
|
||||
status.RemovesCompletedDownloads = config.Misc.history_retention != "0";
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
public string[] date_categories { get; set; }
|
||||
public bool enable_date_sorting { get; set; }
|
||||
public bool pre_check { get; set; }
|
||||
public string history_retention { get; set; }
|
||||
}
|
||||
|
||||
public class SabnzbdCategory
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Download
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract string Download(RemoteBook remoteBook, IIndexer indexer);
|
||||
public abstract Task<string> Download(RemoteBook remoteBook, IIndexer indexer);
|
||||
public abstract IEnumerable<DownloadClientItem> GetItems();
|
||||
|
||||
public virtual DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt)
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
|
||||
public bool IsLocalhost { get; set; }
|
||||
public bool RemovesCompletedDownloads { get; set; }
|
||||
public List<OsPath> OutputRootFolders { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -16,7 +17,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadService
|
||||
{
|
||||
void DownloadReport(RemoteBook remoteBook);
|
||||
Task DownloadReport(RemoteBook remoteBook);
|
||||
}
|
||||
|
||||
public class DownloadService : IDownloadService
|
||||
@@ -49,15 +50,23 @@ namespace NzbDrone.Core.Download
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void DownloadReport(RemoteBook remoteBook)
|
||||
public async Task DownloadReport(RemoteBook remoteBook)
|
||||
{
|
||||
var filterBlockedClients = remoteBook.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable;
|
||||
|
||||
var tags = remoteBook.Author?.Tags;
|
||||
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteBook.Release.DownloadProtocol, remoteBook.Release.IndexerId, filterBlockedClients, tags);
|
||||
|
||||
await DownloadReport(remoteBook, downloadClient);
|
||||
}
|
||||
|
||||
private async Task DownloadReport(RemoteBook remoteBook, IDownloadClient downloadClient)
|
||||
{
|
||||
Ensure.That(remoteBook.Author, () => remoteBook.Author).IsNotNull();
|
||||
Ensure.That(remoteBook.Books, () => remoteBook.Books).HasItems();
|
||||
|
||||
var downloadTitle = remoteBook.Release.Title;
|
||||
var filterBlockedClients = remoteBook.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable;
|
||||
var tags = remoteBook.Author?.Tags;
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteBook.Release.DownloadProtocol, remoteBook.Release.IndexerId, filterBlockedClients, tags);
|
||||
|
||||
if (downloadClient == null)
|
||||
{
|
||||
@@ -71,7 +80,7 @@ namespace NzbDrone.Core.Download
|
||||
if (remoteBook.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteBook.Release.DownloadUrl.StartsWith("magnet:"))
|
||||
{
|
||||
var url = new HttpUri(remoteBook.Release.DownloadUrl);
|
||||
_rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2));
|
||||
await _rateLimitService.WaitAndPulseAsync(url.Host, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
IIndexer indexer = null;
|
||||
@@ -84,7 +93,7 @@ namespace NzbDrone.Core.Download
|
||||
string downloadClientId;
|
||||
try
|
||||
{
|
||||
downloadClientId = downloadClient.Download(remoteBook, indexer);
|
||||
downloadClientId = await downloadClient.Download(remoteBook, indexer);
|
||||
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
|
||||
_indexerStatusService.RecordSuccess(remoteBook.Release.IndexerId);
|
||||
}
|
||||
@@ -100,8 +109,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
{
|
||||
var http429 = ex.InnerException as TooManyRequestsException;
|
||||
if (http429 != null)
|
||||
if (ex.InnerException is TooManyRequestsException http429)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(remoteBook.Release.IndexerId, http429.RetryAfter);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
@@ -8,7 +9,7 @@ namespace NzbDrone.Core.Download
|
||||
public interface IDownloadClient : IProvider
|
||||
{
|
||||
DownloadProtocol Protocol { get; }
|
||||
string Download(RemoteBook remoteBook, IIndexer indexer);
|
||||
Task<string> Download(RemoteBook remoteBook, IIndexer indexer);
|
||||
IEnumerable<DownloadClientItem> GetItems();
|
||||
DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt);
|
||||
void RemoveItem(DownloadClientItem item, bool deleteData);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
@@ -12,7 +13,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IProcessDownloadDecisions
|
||||
{
|
||||
ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions);
|
||||
Task<ProcessedDecisions> ProcessDecisions(List<DownloadDecision> decisions);
|
||||
}
|
||||
|
||||
public class ProcessDownloadDecisions : IProcessDownloadDecisions
|
||||
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Download
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions)
|
||||
public async Task<ProcessedDecisions> ProcessDecisions(List<DownloadDecision> decisions)
|
||||
{
|
||||
var qualifiedReports = GetQualifiedReports(decisions);
|
||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
|
||||
@@ -75,7 +76,7 @@ namespace NzbDrone.Core.Download
|
||||
try
|
||||
{
|
||||
_logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteBook.Release.Indexer, remoteBook.Release.IndexerPriority);
|
||||
_downloadService.DownloadReport(remoteBook);
|
||||
await _downloadService.DownloadReport(remoteBook);
|
||||
grabbed.Add(report);
|
||||
}
|
||||
catch (ReleaseUnavailableException)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using MonoTorrent;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -41,7 +42,7 @@ namespace NzbDrone.Core.Download
|
||||
protected abstract string AddFromMagnetLink(RemoteBook remoteBook, string hash, string magnetLink);
|
||||
protected abstract string AddFromTorrentFile(RemoteBook remoteBook, string hash, string filename, byte[] fileContent);
|
||||
|
||||
public override string Download(RemoteBook remoteBook, IIndexer indexer)
|
||||
public override async Task<string> Download(RemoteBook remoteBook, IIndexer indexer)
|
||||
{
|
||||
var torrentInfo = remoteBook.Release as TorrentInfo;
|
||||
|
||||
@@ -68,7 +69,7 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromWebUrl(remoteBook, indexer, torrentUrl);
|
||||
return await DownloadFromWebUrl(remoteBook, indexer, torrentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -114,14 +115,14 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return DownloadFromWebUrl(remoteBook, indexer, torrentUrl);
|
||||
return await DownloadFromWebUrl(remoteBook, indexer, torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DownloadFromWebUrl(RemoteBook remoteBook, IIndexer indexer, string torrentUrl)
|
||||
private async Task<string> DownloadFromWebUrl(RemoteBook remoteBook, IIndexer indexer, string torrentUrl)
|
||||
{
|
||||
byte[] torrentFile = null;
|
||||
|
||||
@@ -132,7 +133,7 @@ namespace NzbDrone.Core.Download
|
||||
request.Headers.Accept = "application/x-bittorrent";
|
||||
request.AllowAutoRedirect = false;
|
||||
|
||||
var response = _httpClient.Get(request);
|
||||
var response = await _httpClient.GetAsync(request);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.MovedPermanently ||
|
||||
response.StatusCode == HttpStatusCode.Found ||
|
||||
@@ -151,7 +152,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
request.Url += new HttpUri(locationHeader);
|
||||
|
||||
return DownloadFromWebUrl(remoteBook, indexer, request.Url.ToString());
|
||||
return await DownloadFromWebUrl(remoteBook, indexer, request.Url.ToString());
|
||||
}
|
||||
|
||||
throw new WebException("Remote website tried to redirect without providing a location.");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -34,7 +35,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
protected abstract string AddFromNzbFile(RemoteBook remoteBook, string filename, byte[] fileContent);
|
||||
|
||||
public override string Download(RemoteBook remoteBook, IIndexer indexer)
|
||||
public override async Task<string> Download(RemoteBook remoteBook, IIndexer indexer)
|
||||
{
|
||||
var url = remoteBook.Release.DownloadUrl;
|
||||
var filename = FileNameBuilder.CleanFileName(remoteBook.Release.Title) + ".nzb";
|
||||
@@ -46,7 +47,9 @@ namespace NzbDrone.Core.Download
|
||||
var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url);
|
||||
request.RateLimitKey = remoteBook?.Release?.IndexerId.ToString();
|
||||
|
||||
nzbData = _httpClient.Get(request).ResponseData;
|
||||
var response = await _httpClient.GetAsync(request);
|
||||
|
||||
nzbData = response.ResponseData;
|
||||
|
||||
_logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", remoteBook.Release.Title, nzbData.Length, url);
|
||||
}
|
||||
|
||||
@@ -346,6 +346,7 @@ namespace NzbDrone.Core.Extras.Metadata
|
||||
private void DownloadImage(Author author, ImageFileResult image)
|
||||
{
|
||||
var fullPath = Path.Combine(author.Path, image.RelativePath);
|
||||
var downloaded = true;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -353,12 +354,19 @@ namespace NzbDrone.Core.Extras.Metadata
|
||||
{
|
||||
_httpClient.DownloadFile(image.Url, fullPath);
|
||||
}
|
||||
else
|
||||
else if (_diskProvider.FileExists(image.Url))
|
||||
{
|
||||
_diskProvider.CopyFile(image.Url, fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
downloaded = false;
|
||||
}
|
||||
|
||||
_mediaFileAttributeService.SetFilePermissions(fullPath);
|
||||
if (downloaded)
|
||||
{
|
||||
_mediaFileAttributeService.SetFilePermissions(fullPath);
|
||||
}
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
|
||||
[CheckOn(typeof(ModelEvent<RootFolder>))]
|
||||
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
|
||||
|
||||
public class DownloadClientRemovesCompletedDownloadsCheck : HealthCheckBase, IProvideHealthCheck
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadClientRemovesCompletedDownloadsCheck(IProvideDownloadClient downloadClientProvider,
|
||||
Logger logger,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var clients = _downloadClientProvider.GetDownloadClients(true);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientName = client.Definition.Name;
|
||||
var status = client.GetStatus();
|
||||
|
||||
if (status.RemovesCompletedDownloads)
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("DownloadClientRemovesCompletedDownloadsHealthCheckMessage"), clientName, "Readarr"),
|
||||
"#download-client-removes-completed-downloads");
|
||||
}
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unknown error occurred in DownloadClientHistoryRetentionCheck HealthCheck");
|
||||
}
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class FixMultipleMonitoredEditions : IHousekeepingTask
|
||||
{
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public FixMultipleMonitoredEditions(IMainDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
using var mapper = _database.OpenConnection();
|
||||
|
||||
mapper.Execute(@"UPDATE ""Editions""
|
||||
SET ""Monitored"" = 0
|
||||
WHERE ""Id"" IN (
|
||||
SELECT MIN(""Id"")
|
||||
FROM ""Editions""
|
||||
WHERE ""Monitored"" = 1
|
||||
GROUP BY ""BookId""
|
||||
HAVING COUNT(""BookId"") > 1
|
||||
)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
public void Execute(AuthorSearchCommand message)
|
||||
{
|
||||
var decisions = _releaseSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false);
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
var decisions = _releaseSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false).GetAwaiter().GetResult();
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions).GetAwaiter().GetResult();
|
||||
|
||||
_logger.ProgressInfo("Author search completed. {0} reports downloaded.", processed.Grabbed.Count);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Queue;
|
||||
@@ -39,16 +39,15 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private void SearchForMissingBooks(List<Book> books, bool userInvokedSearch)
|
||||
private async Task SearchForMissingBooks(List<Book> books, bool userInvokedSearch)
|
||||
{
|
||||
_logger.ProgressInfo("Performing missing search for {0} books", books.Count);
|
||||
var downloadedCount = 0;
|
||||
|
||||
foreach (var book in books)
|
||||
{
|
||||
List<DownloadDecision> decisions;
|
||||
decisions = _releaseSearchService.BookSearch(book.Id, false, userInvokedSearch, false);
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
var decisions = await _releaseSearchService.BookSearch(book.Id, false, userInvokedSearch, false);
|
||||
var processed = await _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
|
||||
downloadedCount += processed.Grabbed.Count;
|
||||
}
|
||||
@@ -60,9 +59,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
foreach (var bookId in message.BookIds)
|
||||
{
|
||||
var decisions =
|
||||
_releaseSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false);
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
var decisions = _releaseSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false).GetAwaiter().GetResult();
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions).GetAwaiter().GetResult();
|
||||
|
||||
_logger.ProgressInfo("Book search completed. {0} reports downloaded.", processed.Grabbed.Count);
|
||||
}
|
||||
@@ -106,7 +104,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
var queue = _queueService.GetQueue().Where(q => q.Book != null).Select(q => q.Book.Id);
|
||||
var missing = books.Where(e => !queue.Contains(e.Id)).ToList();
|
||||
|
||||
SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual);
|
||||
SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void Execute(CutoffUnmetBookSearchCommand message)
|
||||
@@ -132,7 +130,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
var queue = _queueService.GetQueue().Where(q => q.Book != null).Select(q => q.Book.Id);
|
||||
var missing = books.Where(e => !queue.Contains(e.Id)).ToList();
|
||||
|
||||
SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual);
|
||||
SearchForMissingBooks(missing, message.Trigger == CommandTrigger.Manual).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Indexers;
|
||||
@@ -16,8 +15,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public interface ISearchForReleases
|
||||
{
|
||||
List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
|
||||
List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
|
||||
Task<List<DownloadDecision>> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
|
||||
Task<List<DownloadDecision>> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
|
||||
}
|
||||
|
||||
public class ReleaseSearchService : ISearchForReleases
|
||||
@@ -41,31 +40,31 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
public async Task<List<DownloadDecision>> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
var book = _bookService.GetBook(bookId);
|
||||
|
||||
var decisions = BookSearch(book, missingOnly, userInvokedSearch, interactiveSearch);
|
||||
var decisions = await BookSearch(book, missingOnly, userInvokedSearch, interactiveSearch);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
|
||||
return DeDupeDecisions(downloadDecisions);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
public async Task<List<DownloadDecision>> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
var author = _authorService.GetAuthor(authorId);
|
||||
|
||||
var decisions = AuthorSearch(author, missingOnly, userInvokedSearch, interactiveSearch);
|
||||
var decisions = await AuthorSearch(author, missingOnly, userInvokedSearch, interactiveSearch);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
|
||||
return DeDupeDecisions(downloadDecisions);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> AuthorSearch(Author author, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
public async Task<List<DownloadDecision>> AuthorSearch(Author author, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var searchSpec = Get<AuthorSearchCriteria>(author, userInvokedSearch, interactiveSearch);
|
||||
var books = _bookService.GetBooksByAuthor(author.Id);
|
||||
@@ -74,10 +73,10 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
searchSpec.Books = books;
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
return await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> BookSearch(Book book, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
public async Task<List<DownloadDecision>> BookSearch(Book book, bool missingOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var author = _authorService.GetAuthor(book.AuthorId);
|
||||
|
||||
@@ -91,7 +90,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
searchSpec.BookYear = book.ReleaseDate.Value.Year;
|
||||
}
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
return await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(Author author, List<Book> books, bool userInvokedSearch, bool interactiveSearch)
|
||||
@@ -118,7 +117,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
return spec;
|
||||
}
|
||||
|
||||
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
|
||||
private async Task<List<DownloadDecision>> Dispatch(Func<IIndexer, Task<IList<ReleaseInfo>>> searchAction, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
var indexers = criteriaBase.InteractiveSearch ?
|
||||
_indexerFactory.InteractiveSearchEnabled() :
|
||||
@@ -127,42 +126,33 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
// Filter indexers to untagged indexers and indexers with intersecting tags
|
||||
indexers = indexers.Where(i => i.Definition.Tags.Empty() || i.Definition.Tags.Intersect(criteriaBase.Author.Tags).Any()).ToList();
|
||||
|
||||
var reports = new List<ReleaseInfo>();
|
||||
|
||||
_logger.ProgressInfo("Searching indexers for {0}. {1} active indexers", criteriaBase, indexers.Count);
|
||||
|
||||
var taskList = new List<Task>();
|
||||
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
|
||||
var tasks = indexers.Select(indexer => DispatchIndexer(searchAction, indexer, criteriaBase));
|
||||
|
||||
foreach (var indexer in indexers)
|
||||
{
|
||||
var indexerLocal = indexer;
|
||||
var batch = await Task.WhenAll(tasks);
|
||||
|
||||
taskList.Add(taskFactory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var indexerReports = searchAction(indexerLocal);
|
||||
|
||||
lock (reports)
|
||||
{
|
||||
reports.AddRange(indexerReports);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Error while searching for {0}", criteriaBase);
|
||||
}
|
||||
}).LogExceptions());
|
||||
}
|
||||
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
var reports = batch.SelectMany(x => x).ToList();
|
||||
|
||||
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
|
||||
|
||||
return _makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList();
|
||||
}
|
||||
|
||||
private async Task<IList<ReleaseInfo>> DispatchIndexer(Func<IIndexer, Task<IList<ReleaseInfo>>> searchAction, IIndexer indexer, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await searchAction(indexer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Error while searching for {0}", criteriaBase);
|
||||
}
|
||||
|
||||
return Array.Empty<ReleaseInfo>();
|
||||
}
|
||||
|
||||
private List<DownloadDecision> DeDupeDecisions(List<DownloadDecision> decisions)
|
||||
{
|
||||
// De-dupe reports by guid so duplicate results aren't returned. Pick the one with the least rejections and higher indexer priority.
|
||||
|
||||
@@ -3,13 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IFetchAndParseRss
|
||||
{
|
||||
List<ReleaseInfo> Fetch();
|
||||
Task<List<ReleaseInfo>> Fetch();
|
||||
}
|
||||
|
||||
public class FetchAndParseRssService : IFetchAndParseRss
|
||||
@@ -23,54 +22,42 @@ namespace NzbDrone.Core.Indexers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<ReleaseInfo> Fetch()
|
||||
public async Task<List<ReleaseInfo>> Fetch()
|
||||
{
|
||||
var result = new List<ReleaseInfo>();
|
||||
|
||||
var indexers = _indexerFactory.RssEnabled();
|
||||
|
||||
if (!indexers.Any())
|
||||
{
|
||||
_logger.Warn("No available indexers. check your configuration.");
|
||||
return result;
|
||||
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
_logger.Debug("Available indexers {0}", indexers.Count);
|
||||
|
||||
var taskList = new List<Task>();
|
||||
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
|
||||
var tasks = indexers.Select(FetchIndexer);
|
||||
|
||||
foreach (var indexer in indexers)
|
||||
{
|
||||
var indexerLocal = indexer;
|
||||
var batch = await Task.WhenAll(tasks);
|
||||
|
||||
var task = taskFactory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var indexerReports = indexerLocal.FetchRecent();
|
||||
|
||||
lock (result)
|
||||
{
|
||||
_logger.Debug("Found {0} from {1}", indexerReports.Count, indexer.Name);
|
||||
|
||||
result.AddRange(indexerReports);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Error during RSS Sync");
|
||||
}
|
||||
}).LogExceptions();
|
||||
|
||||
taskList.Add(task);
|
||||
}
|
||||
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
var result = batch.SelectMany(x => x).ToList();
|
||||
|
||||
_logger.Debug("Found {0} reports", result.Count);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<IList<ReleaseInfo>> FetchIndexer(IIndexer indexer)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await indexer.FetchRecent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Error during RSS Sync");
|
||||
}
|
||||
|
||||
return Array.Empty<ReleaseInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(searchCriteria.AuthorQuery.Trim()), Uri.EscapeDataString(searchCriteria.BookQuery.Trim()))));
|
||||
var authorQuery = searchCriteria.AuthorQuery.Replace("+", " ").Trim();
|
||||
var bookQuery = searchCriteria.BookQuery.Replace("+", " ").Trim();
|
||||
|
||||
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(authorQuery), Uri.EscapeDataString(bookQuery))));
|
||||
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}+{1}", Uri.EscapeDataString(bookQuery), Uri.EscapeDataString(authorQuery))));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -32,7 +36,9 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}", Uri.EscapeDataString(searchCriteria.AuthorQuery.Trim()))));
|
||||
var authorQuery = searchCriteria.AuthorQuery.Replace("+", " ").Trim();
|
||||
|
||||
pageableRequests.Add(GetRequest("search-torrents", Settings.Categories, string.Format("&type=name&query={0}", Uri.EscapeDataString(authorQuery))));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
|
||||
Categories = new int[]
|
||||
{
|
||||
(int)FileListCategories.DOCS
|
||||
(int)FileListCategories.Docs
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
|
||||
public enum FileListCategories
|
||||
{
|
||||
[FieldOption]
|
||||
DOCS = 16,
|
||||
[FieldOption(Label = "Docs")]
|
||||
Docs = 16,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -68,11 +69,12 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
protected override async Task Test(List<ValidationFailure> failures)
|
||||
{
|
||||
// Remove previous cookies when testing incase user or pwd change
|
||||
_authCookieCache.Remove(Settings.BaseUrl.Trim().TrimEnd('/'));
|
||||
base.Test(failures);
|
||||
|
||||
await base.Test(failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -43,17 +44,14 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
||||
{
|
||||
Authenticate();
|
||||
Authenticate().GetAwaiter().GetResult();
|
||||
|
||||
var filter = "";
|
||||
if (searchParameters == null)
|
||||
{
|
||||
}
|
||||
|
||||
var request =
|
||||
new IndexerRequest(
|
||||
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/ajax.php?action=browse&searchstr={searchParameters}{filter}",
|
||||
HttpAccept.Json);
|
||||
var request = new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/ajax.php?action=browse&searchstr={searchParameters}{filter}", HttpAccept.Json);
|
||||
|
||||
var cookies = AuthCookieCache.Find(Settings.BaseUrl.Trim().TrimEnd('/'));
|
||||
foreach (var cookie in cookies)
|
||||
@@ -64,36 +62,36 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
yield return request;
|
||||
}
|
||||
|
||||
private GazelleAuthResponse GetIndex(Dictionary<string, string> cookies)
|
||||
private async Task<GazelleAuthResponse> GetIndex(Dictionary<string, string> cookies)
|
||||
{
|
||||
var indexRequestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
|
||||
{
|
||||
LogResponseContent = true
|
||||
LogResponseContent = true,
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
|
||||
indexRequestBuilder.SetCookies(cookies);
|
||||
indexRequestBuilder.Method = HttpMethod.Post;
|
||||
indexRequestBuilder.Resource("ajax.php?action=index");
|
||||
|
||||
var authIndexRequest = indexRequestBuilder
|
||||
.Accept(HttpAccept.Json)
|
||||
.Build();
|
||||
|
||||
var indexResponse = HttpClient.Execute(authIndexRequest);
|
||||
var indexResponse = await HttpClient.ExecuteAsync(authIndexRequest);
|
||||
|
||||
var result = Json.Deserialize<GazelleAuthResponse>(indexResponse.Content);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Authenticate()
|
||||
private async Task Authenticate()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
|
||||
{
|
||||
LogResponseContent = true
|
||||
LogResponseContent = true,
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
|
||||
requestBuilder.Method = HttpMethod.Post;
|
||||
requestBuilder.Resource("login.php");
|
||||
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
@@ -111,19 +109,20 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
.Accept(HttpAccept.Json)
|
||||
.Build();
|
||||
|
||||
var response = HttpClient.Execute(authLoginRequest);
|
||||
var response = await HttpClient.ExecuteAsync(authLoginRequest);
|
||||
|
||||
cookies = response.GetCookies();
|
||||
|
||||
AuthCookieCache.Set(authKey, cookies);
|
||||
}
|
||||
|
||||
var index = GetIndex(cookies);
|
||||
var index = await GetIndex(cookies);
|
||||
|
||||
if (index == null || index.Status.IsNullOrWhiteSpace() || index.Status != "success")
|
||||
{
|
||||
Logger.Debug("Gazelle authentication failed.");
|
||||
AuthCookieCache.Remove(authKey);
|
||||
|
||||
throw new Exception("Failed to authenticate with Gazelle.");
|
||||
}
|
||||
|
||||
|
||||
@@ -40,31 +40,31 @@ namespace NzbDrone.Core.Indexers
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> FetchRecent()
|
||||
public override Task<IList<ReleaseInfo>> FetchRecent()
|
||||
{
|
||||
if (!SupportsRss)
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
return Task.FromResult<IList<ReleaseInfo>>(Array.Empty<ReleaseInfo>());
|
||||
}
|
||||
|
||||
return FetchReleases(g => g.GetRecentRequests(), true);
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria)
|
||||
public override Task<IList<ReleaseInfo>> Fetch(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
if (!SupportsSearch)
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
return Task.FromResult<IList<ReleaseInfo>>(Array.Empty<ReleaseInfo>());
|
||||
}
|
||||
|
||||
return FetchReleases(g => g.GetSearchRequests(searchCriteria));
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> Fetch(AuthorSearchCriteria searchCriteria)
|
||||
public override Task<IList<ReleaseInfo>> Fetch(AuthorSearchCriteria searchCriteria)
|
||||
{
|
||||
if (!SupportsSearch)
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
return Task.FromResult<IList<ReleaseInfo>>(Array.Empty<ReleaseInfo>());
|
||||
}
|
||||
|
||||
return FetchReleases(g => g.GetSearchRequests(searchCriteria));
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Indexers
|
||||
return new HttpRequest(link);
|
||||
}
|
||||
|
||||
protected virtual IList<ReleaseInfo> FetchReleases(Func<IIndexerRequestGenerator, IndexerPageableRequestChain> pageableRequestChainSelector, bool isRecent = false)
|
||||
protected virtual async Task<IList<ReleaseInfo>> FetchReleases(Func<IIndexerRequestGenerator, IndexerPageableRequestChain> pageableRequestChainSelector, bool isRecent = false)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var url = string.Empty;
|
||||
@@ -107,7 +107,7 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
url = request.Url.FullUri;
|
||||
|
||||
var page = FetchPage(request, parser);
|
||||
var page = await FetchPage(request, parser);
|
||||
|
||||
pagedReleases.AddRange(page);
|
||||
|
||||
@@ -257,9 +257,17 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
protected virtual bool IsValidRelease(ReleaseInfo release)
|
||||
{
|
||||
if (release.Title.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Trace("Invalid Release: '{0}' from indexer: {1}. No title provided.", release.InfoUrl, Definition.Name);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (release.DownloadUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Trace("Invalid Release: '{0}' from indexer: {1}. No Download URL provided.", release.Title, release.Indexer);
|
||||
_logger.Trace("Invalid Release: '{0}' from indexer: {1}. No Download URL provided.", release.Title, Definition.Name);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -271,9 +279,9 @@ namespace NzbDrone.Core.Indexers
|
||||
return PageSize != 0 && page.Count >= PageSize;
|
||||
}
|
||||
|
||||
protected virtual IList<ReleaseInfo> FetchPage(IndexerRequest request, IParseIndexerResponse parser)
|
||||
protected virtual async Task<IList<ReleaseInfo>> FetchPage(IndexerRequest request, IParseIndexerResponse parser)
|
||||
{
|
||||
var response = FetchIndexerResponse(request);
|
||||
var response = await FetchIndexerResponse(request);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -287,7 +295,7 @@ namespace NzbDrone.Core.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IndexerResponse FetchIndexerResponse(IndexerRequest request)
|
||||
protected virtual async Task<IndexerResponse> FetchIndexerResponse(IndexerRequest request)
|
||||
{
|
||||
_logger.Debug("Downloading Feed " + request.HttpRequest.ToString(false));
|
||||
|
||||
@@ -298,15 +306,17 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
request.HttpRequest.RateLimitKey = Definition.Id.ToString();
|
||||
|
||||
return new IndexerResponse(request, _httpClient.Execute(request.HttpRequest));
|
||||
var response = await _httpClient.ExecuteAsync(request.HttpRequest);
|
||||
|
||||
return new IndexerResponse(request, response);
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
protected override async Task Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestConnection());
|
||||
failures.AddIfNotNull(await TestConnection());
|
||||
}
|
||||
|
||||
protected virtual ValidationFailure TestConnection()
|
||||
protected virtual async Task<ValidationFailure> TestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -319,7 +329,7 @@ namespace NzbDrone.Core.Indexers
|
||||
return new ValidationFailure(string.Empty, "No rss feed query available. This may be an issue with the indexer or your indexer category settings.");
|
||||
}
|
||||
|
||||
var releases = FetchPage(firstRequest, parser);
|
||||
var releases = await FetchPage(firstRequest, parser);
|
||||
|
||||
if (releases.Empty())
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -12,9 +13,9 @@ namespace NzbDrone.Core.Indexers
|
||||
bool SupportsSearch { get; }
|
||||
DownloadProtocol Protocol { get; }
|
||||
|
||||
IList<ReleaseInfo> FetchRecent();
|
||||
IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria);
|
||||
IList<ReleaseInfo> Fetch(AuthorSearchCriteria searchCriteria);
|
||||
Task<IList<ReleaseInfo>> FetchRecent();
|
||||
Task<IList<ReleaseInfo>> Fetch(BookSearchCriteria searchCriteria);
|
||||
Task<IList<ReleaseInfo>> Fetch(AuthorSearchCriteria searchCriteria);
|
||||
HttpRequest GetDownloadRequest(string link);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -66,16 +67,14 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
protected TSettings Settings => (TSettings)Definition.Settings;
|
||||
|
||||
public abstract IList<ReleaseInfo> FetchRecent();
|
||||
|
||||
public abstract IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria);
|
||||
public abstract IList<ReleaseInfo> Fetch(AuthorSearchCriteria searchCriteria);
|
||||
public abstract Task<IList<ReleaseInfo>> FetchRecent();
|
||||
public abstract Task<IList<ReleaseInfo>> Fetch(BookSearchCriteria searchCriteria);
|
||||
public abstract Task<IList<ReleaseInfo>> Fetch(AuthorSearchCriteria searchCriteria);
|
||||
public abstract HttpRequest GetDownloadRequest(string link);
|
||||
|
||||
protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases)
|
||||
{
|
||||
var result = releases.DistinctBy(v => v.Guid).ToList();
|
||||
var settings = Definition.Settings as IIndexerSettings;
|
||||
|
||||
result.ForEach(c =>
|
||||
{
|
||||
@@ -94,7 +93,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
try
|
||||
{
|
||||
Test(failures);
|
||||
Test(failures).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -105,7 +104,7 @@ namespace NzbDrone.Core.Indexers
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
protected abstract void Test(List<ValidationFailure> failures);
|
||||
protected abstract Task Test(List<ValidationFailure> failures);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -92,9 +93,10 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
protected override async Task Test(List<ValidationFailure> failures)
|
||||
{
|
||||
base.Test(failures);
|
||||
await base.Test(failures);
|
||||
|
||||
if (failures.HasErrors())
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,7 @@ using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
@@ -73,6 +74,13 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}", indexerSettings.BaseUrl);
|
||||
throw;
|
||||
}
|
||||
catch (ApiKeyException ex)
|
||||
{
|
||||
ex.WithData(response, 128 * 1024);
|
||||
_logger.Trace("Unexpected Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content);
|
||||
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}, invalid API key", indexerSettings.BaseUrl);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.WithData(response, 128 * 1024);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
@@ -39,16 +40,16 @@ namespace NzbDrone.Core.Indexers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private ProcessedDecisions Sync()
|
||||
private async Task<ProcessedDecisions> Sync()
|
||||
{
|
||||
_logger.ProgressInfo("Starting RSS Sync");
|
||||
|
||||
var rssReleases = _rssFetcherAndParser.Fetch();
|
||||
var rssReleases = await _rssFetcherAndParser.Fetch();
|
||||
var pendingReleases = _pendingReleaseService.GetPending();
|
||||
|
||||
var reports = rssReleases.Concat(pendingReleases).ToList();
|
||||
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
|
||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
var processed = await _processDownloadDecisions.ProcessDecisions(decisions);
|
||||
|
||||
var message = string.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count);
|
||||
|
||||
@@ -64,7 +65,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
public void Execute(RssSyncCommand message)
|
||||
{
|
||||
var processed = Sync();
|
||||
var processed = Sync().GetAwaiter().GetResult();
|
||||
var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList();
|
||||
|
||||
_eventAggregator.PublishEvent(new RssSyncCompleteEvent(processed));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -69,9 +70,10 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
protected override async Task Test(List<ValidationFailure> failures)
|
||||
{
|
||||
base.Test(failures);
|
||||
await base.Test(failures);
|
||||
|
||||
if (failures.HasErrors())
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -13,36 +13,36 @@
|
||||
"45MinutesFourtyFive": "90 minut: {0}",
|
||||
"60MinutesSixty": "60 minut: {0}",
|
||||
"APIKey": "Klíč API",
|
||||
"About": "O",
|
||||
"About": "O aplikaci",
|
||||
"AddListExclusion": "Přidat vyloučení seznamu",
|
||||
"AddingTag": "Přidávání značky",
|
||||
"AgeWhenGrabbed": "Stáří (při zachycení)",
|
||||
"AlreadyInYourLibrary": "Již ve vaší knihovně",
|
||||
"AddingTag": "Přidání značky",
|
||||
"AgeWhenGrabbed": "Stáří (kdy bylo získáno)",
|
||||
"AlreadyInYourLibrary": "Již máte ve své knihovně",
|
||||
"AlternateTitles": "Alternativní název",
|
||||
"Analytics": "Analytics",
|
||||
"Analytics": "Analýzy",
|
||||
"AnalyticsEnabledHelpText": "Odesílejte anonymní informace o použití a chybách na servery Radarru. To zahrnuje informace o vašem prohlížeči, které stránky Radarr WebUI používáte, hlášení chyb a také verzi operačního systému a běhového prostředí. Tyto informace použijeme k upřednostnění funkcí a oprav chyb.",
|
||||
"AppDataDirectory": "Adresář AppData",
|
||||
"ApplyTags": "Použít značky",
|
||||
"Authentication": "Ověření",
|
||||
"Authentication": "Ověřování",
|
||||
"AuthenticationMethodHelpText": "Vyžadovat uživatelské jméno a heslo pro přístup k Radarr",
|
||||
"AuthorClickToChangeBook": "Kliknutím změníte film",
|
||||
"AutoRedownloadFailedHelpText": "Automaticky vyhledejte a pokuste se stáhnout jiné vydání",
|
||||
"AutoRedownloadFailedHelpText": "Automatické vyhledání a pokus o stažení jiného vydání",
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Filmy odstraněné z disku jsou automaticky sledovány v Radarru",
|
||||
"Automatic": "Automatický",
|
||||
"BackupFolderHelpText": "Relativní cesty budou v adresáři AppData společnosti Radarr",
|
||||
"BackupNow": "Zálohovat hned",
|
||||
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchování budou automaticky vyčištěny",
|
||||
"BackupNow": "Ihned zálohovat",
|
||||
"BackupRetentionHelpText": "Automatické zálohy starší než doba uchovávání budou automaticky vyčištěny",
|
||||
"Backups": "Zálohy",
|
||||
"BindAddress": "Vazba adresy",
|
||||
"BindAddressHelpText": "Platná adresa IP4 nebo '*' pro všechna rozhraní",
|
||||
"BindAddress": "Vázat adresu",
|
||||
"BindAddressHelpText": "Platná IP adresa, localhost nebo '*' pro všechna rozhraní",
|
||||
"BindAddressHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
||||
"BookIsDownloading": "Film se stahuje",
|
||||
"BookIsDownloadingInterp": "Film se stahuje - {0}% {1}",
|
||||
"Branch": "Větev",
|
||||
"BypassProxyForLocalAddresses": "Obejít proxy pro místní adresy",
|
||||
"BypassProxyForLocalAddresses": "Obcházení proxy serveru pro místní adresy",
|
||||
"Calendar": "Kalendář",
|
||||
"CalendarWeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
|
||||
"Cancel": "zrušení",
|
||||
"Cancel": "Zrušit",
|
||||
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?",
|
||||
"CertificateValidation": "Ověření certifikátu",
|
||||
"CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
|
||||
@@ -53,33 +53,33 @@
|
||||
"ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.",
|
||||
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
|
||||
"ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako Radarr.",
|
||||
"Clear": "Průhledná",
|
||||
"ClickToChangeQuality": "Klepnutím změníte kvalitu",
|
||||
"Clear": "Vyčistit",
|
||||
"ClickToChangeQuality": "Kliknutím změníte kvalitu",
|
||||
"ClientPriority": "Priorita klienta",
|
||||
"CloneIndexer": "Klonovat indexátor",
|
||||
"CloneProfile": "Klonovat profil",
|
||||
"Close": "Zavřít",
|
||||
"Columns": "Sloupce",
|
||||
"CompletedDownloadHandling": "Zpracování stahování bylo dokončeno",
|
||||
"ConnectSettings": "Připojit nastavení",
|
||||
"ConnectSettings": "Nastavení připojení",
|
||||
"Connections": "Připojení",
|
||||
"CopyUsingHardlinksHelpText": "Hardlinks použijte, když se pokoušíte kopírovat soubory z torrentů, které se stále používají",
|
||||
"CopyUsingHardlinksHelpTextWarning": "Zámky souborů mohou občas zabránit přejmenování souborů, které se právě vysazují. Výsev můžete dočasně deaktivovat a použít funkci Radarr pro přejmenování.",
|
||||
"CreateEmptyAuthorFoldersHelpText": "Během skenování disku vytvářejte chybějící složky filmů",
|
||||
"CreateGroup": "Vytvořit skupinu",
|
||||
"CutoffHelpText": "Jakmile je této kvality dosaženo, Radarr již nebude stahovat filmy",
|
||||
"CutoffUnmet": "Cut-off Unmet",
|
||||
"CutoffUnmet": "Vynechat nevyhovující",
|
||||
"DBMigration": "Migrace databáze",
|
||||
"Dates": "Termíny",
|
||||
"DelayProfile": "Zpožděný profil",
|
||||
"DelayProfile": "Profil zpoždění",
|
||||
"DelayProfiles": "Profily zpoždění",
|
||||
"DelayingDownloadUntilInterp": "Zpoždění stahování do {0} o {1}",
|
||||
"Delete": "Vymazat",
|
||||
"DeleteBackup": "Odstranit zálohu",
|
||||
"DeleteBackupMessageText": "Opravdu chcete smazat zálohu „{0}“?",
|
||||
"DeleteDelayProfile": "Smazat profil zpoždění",
|
||||
"Delete": "Smazat",
|
||||
"DeleteBackup": "Odstranění zálohy",
|
||||
"DeleteBackupMessageText": "Opravdu chcete odstranit zálohu '{name}'?",
|
||||
"DeleteDelayProfile": "Odstranění profilu zpoždění",
|
||||
"DeleteDelayProfileMessageText": "Opravdu chcete smazat tento profil zpoždění?",
|
||||
"DeleteDownloadClient": "Odstranit staženého klienta",
|
||||
"DeleteDownloadClient": "Odstranění klienta pro stahování",
|
||||
"DeleteDownloadClientMessageText": "Opravdu chcete odstranit klienta pro stahování „{0}“?",
|
||||
"DeleteEmptyFolders": "Odstraňte prázdné složky",
|
||||
"DeleteEmptyFoldersHelpText": "Během skenování disku a při mazání filmových souborů odstraňte prázdné složky s filmy",
|
||||
@@ -455,12 +455,12 @@
|
||||
"ShowTitle": "Ukázat nadpis",
|
||||
"RemoveFromBlocklist": "Odebrat z černé listiny",
|
||||
"UnableToLoadBlocklist": "Nelze načíst černou listinu",
|
||||
"Component": "Součástka",
|
||||
"Component": "Komponenta",
|
||||
"Level": "Úroveň",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Pobočka {0} není platná větev vydání Radarr, nebudete dostávat aktualizace",
|
||||
"Time": "Čas",
|
||||
"Blocklist": "Černá listina",
|
||||
"BlocklistRelease": "Vydání černé listiny",
|
||||
"Blocklist": "Blocklist",
|
||||
"BlocklistRelease": "Blocklist pro vydání",
|
||||
"ShowUnknownAuthorItems": "Zobrazit neznámé položky filmu",
|
||||
"ThisCannotBeCancelled": "Toto nelze zrušit po spuštění bez restartování Radarru.",
|
||||
"UnselectAll": "Odznačit vše",
|
||||
@@ -485,7 +485,7 @@
|
||||
"SettingsRemotePathMappingRemotePath": "Vzdálená cesta",
|
||||
"UISettingsSummary": "Možnosti kalendáře, data a barev",
|
||||
"DownloadClientsSettingsSummary": "Stahování klientů, zpracování stahování a mapování vzdálených cest",
|
||||
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možné zabránit smazání AppData při aktualizaci",
|
||||
"AppDataLocationHealthCheckMessage": "Aktualizace nebude možná, aby se zabránilo odstranění AppData při aktualizaci",
|
||||
"FileWasDeletedByViaUI": "Soubor byl odstraněn prostřednictvím uživatelského rozhraní",
|
||||
"IndexersSettingsSummary": "Indexery a omezení vydání",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
|
||||
@@ -556,7 +556,7 @@
|
||||
"Conditions": "Podmínky",
|
||||
"CopyToClipboard": "Zkopírovat do schránky",
|
||||
"CustomFormat": "Vlastní formát",
|
||||
"DeleteCustomFormat": "Odstranit vlastní formát",
|
||||
"DeleteCustomFormat": "Odstranění vlastního formátu",
|
||||
"DeleteCustomFormatMessageText": "Opravdu chcete odstranit indexer „{0}“?",
|
||||
"ExportCustomFormat": "Exportovat vlastní formát",
|
||||
"Formats": "Formáty",
|
||||
@@ -573,14 +573,14 @@
|
||||
"HideAdvanced": "Skrýt pokročilé",
|
||||
"ShowAdvanced": "Zobrazit pokročilé",
|
||||
"ShownClickToHide": "Zobrazeno, kliknutím se skryjete",
|
||||
"ColonReplacement": "Výměna tlustého střeva",
|
||||
"ColonReplacement": "Nahrazení dvojtečky",
|
||||
"ReplaceWithDash": "Nahraďte Dash",
|
||||
"ReplaceWithSpaceDashSpace": "Nahraďte Space Dash Space",
|
||||
"ReplaceWithSpaceDash": "Nahraďte Space Dash",
|
||||
"DeleteRemotePathMapping": "Upravit vzdálené mapování cesty",
|
||||
"DeleteRemotePathMappingMessageText": "Opravdu chcete toto vzdálené mapování cesty odstranit?",
|
||||
"Negated": "Negovaný",
|
||||
"BlocklistReleases": "Vydání černé listiny",
|
||||
"BlocklistReleases": "Blocklist pro vydání",
|
||||
"DeleteConditionMessageText": "Opravdu chcete smazat značku „{0}“?",
|
||||
"RemoveSelectedItemBlocklistMessageText": "Opravdu chcete odebrat vybrané položky z černé listiny?",
|
||||
"RemoveSelectedItemQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
|
||||
@@ -597,12 +597,12 @@
|
||||
"NoChange": "Žádná změna",
|
||||
"RemovingTag": "Odebírání značky",
|
||||
"SetTags": "Nastavit značky",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidejte značky do existujícího seznamu značek",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Jak použít značky na vybrané filmy",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Jak použít značky na vybrané filmy",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít značky na vybrané filmy",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebere zadané značky",
|
||||
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit tagy zadanými tagy (pro vymazání všech tagů zadejte žádné tagy)",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidá značky k již existujícímu seznamu",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Jak použít značky na vybrané klienty pro stahování",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Jak použít značky na vybrané importní seznamy",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít značky na vybrané indexátory",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané značky",
|
||||
"ApplyTagsHelpTextReplace": "Nahradit: Nahradit značky zadanými značkami (zadáním žádné značky vymažete všechny značky)",
|
||||
"DeleteSelectedDownloadClients": "Odstranit staženého klienta",
|
||||
"DeleteSelectedIndexersMessageText": "Opravdu chcete odstranit indexer „{0}“?",
|
||||
"Yes": "Ano",
|
||||
@@ -617,19 +617,50 @@
|
||||
"Medium": "Střední",
|
||||
"NoResultsFound": "Nebyly nalezeny žádné výsledky",
|
||||
"SomeResultsAreHiddenByTheAppliedFilter": "Některé výsledky jsou použitým filtrem skryty",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Všechny výsledky jsou skryty použitým filtrem",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Všechny výsledky jsou schovány použitým filtrem",
|
||||
"Events": "Události",
|
||||
"FreeSpace": "Volný prostor",
|
||||
"System": "Systém",
|
||||
"TotalSpace": "Celkový prostor",
|
||||
"ConnectionLost": "Spojení ztraceno",
|
||||
"ConnectionLostReconnect": "Radarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.",
|
||||
"ConnectionLostToBackend": "Radarr ztratil spojení s back-endem a pro obnovení funkčnosti bude nutné jej znovu načíst.",
|
||||
"ConnectionLostReconnect": "{appName} se pokusí připojit automaticky, nebo můžete kliknout na tlačítko znovunačtení níže.",
|
||||
"ConnectionLostToBackend": "{appName} ztratila spojení s backendem a pro obnovení funkčnosti bude třeba ji znovu načíst.",
|
||||
"Large": "Velký",
|
||||
"LastDuration": "lastDuration",
|
||||
"Ui": "UI",
|
||||
"WhatsNew": "Co je nového?",
|
||||
"Activity": "Aktivita",
|
||||
"AddNew": "Přidat nový",
|
||||
"NextExecution": "Další spuštění"
|
||||
"AddNew": "Přidat nové",
|
||||
"NextExecution": "Další spuštění",
|
||||
"ClickToChangeReleaseGroup": "Kliknutím změníte skupinu vydání",
|
||||
"ApplicationURL": "URL aplikace",
|
||||
"ApplicationUrlHelpText": "Externí adresa URL této aplikace včetně http(s)://, portu a základní adresy URL",
|
||||
"Continuing": "Pokračující",
|
||||
"AutomaticUpdatesDisabledDocker": "Automatické aktualizace nejsou při použití aktualizačního mechanismu Docker přímo podporovány. Obraz kontejneru je nutné aktualizovat mimo {appName} nebo použít skript",
|
||||
"AppUpdated": "{appName} aktualizován",
|
||||
"ApplyChanges": "Použít změny",
|
||||
"AutoAdd": "Přidat automaticky",
|
||||
"AutomaticAdd": "Přidat automaticky",
|
||||
"ChownGroup": "Skupina chown",
|
||||
"CloneCondition": "Klonovat podmínku",
|
||||
"Clone": "Klonovat",
|
||||
"ApiKeyValidationHealthCheckMessage": "Aktualizujte svůj klíč API tak, aby měl alespoň {0} znaků. Můžete to provést prostřednictvím nastavení nebo konfiguračního souboru",
|
||||
"ChooseImportMethod": "Vyberte mód importu",
|
||||
"CatalogNumber": "katalogové číslo",
|
||||
"Publisher": "Vydavatel",
|
||||
"StatusEndedContinuing": "Pokračující",
|
||||
"MetadataProfiles": "profil metadat",
|
||||
"ReleaseProfiles": "profil vydání",
|
||||
"MetadataProfile": "profil metadat",
|
||||
"Label": "Etiketa",
|
||||
"Library": "Knihovna",
|
||||
"BypassIfAboveCustomFormatScore": "Obejít, pokud je vyšší než skóre vlastního formátu",
|
||||
"AppUpdatedVersion": "{appName} byla aktualizována na verzi `{version}`, abyste získali nejnovější změny, musíte znovu načíst {appName}.",
|
||||
"BypassIfAboveCustomFormatScoreHelpText": "Povolit obcházení, pokud má vydání vyšší skóre, než je nakonfigurované minimální skóre vlastního formátu",
|
||||
"BypassIfHighestQuality": "Obejít v případě nejvyšší kvality",
|
||||
"Theme": "Motiv",
|
||||
"MinimumCustomFormatScoreHelpText": "Minimální skóre vlastního formátu požadované pro obejití zpoždění preferovaného protokolu",
|
||||
"Series": "Seriál",
|
||||
"DeleteCondition": "Odstranit podmínku",
|
||||
"Database": "Databáze"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"20MinutesTwenty": "60 minutter: {0}",
|
||||
"45MinutesFourtyFive": "60 minutter: {0}",
|
||||
"20MinutesTwenty": "20 minutter: {0}",
|
||||
"45MinutesFourtyFive": "45 minutter: {0}",
|
||||
"60MinutesSixty": "60 minutter: {0}",
|
||||
"APIKey": "API-nøgle",
|
||||
"About": "Om",
|
||||
@@ -634,5 +634,6 @@
|
||||
"AddNew": "Tilføj Ny",
|
||||
"Large": "Stor",
|
||||
"Library": "Bibliotek",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Alle resultater skjules af det anvendte filter"
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Alle resultater skjules af det anvendte filter",
|
||||
"AddNewItem": "Tilføj Ny Genstand"
|
||||
}
|
||||
|
||||
@@ -993,5 +993,8 @@
|
||||
"AddNew": "Προσθήκη Νέων",
|
||||
"Backup": "Αντίγραφο Ασφαλείας",
|
||||
"NextExecution": "Επόμενη εκτέλεση",
|
||||
"Small": "Μικρό"
|
||||
"Small": "Μικρό",
|
||||
"AppUpdated": "{appName} Ενημερώθηκε",
|
||||
"AppUpdatedVersion": "ξαναφορτωθεί",
|
||||
"AutoAdd": "Προσθήκη"
|
||||
}
|
||||
|
||||
@@ -265,6 +265,7 @@
|
||||
"DownloadClientCheckDownloadingToRoot": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.",
|
||||
"DownloadClientCheckNoneAvailableMessage": "No download client is available",
|
||||
"DownloadClientCheckUnableToCommunicateMessage": "Unable to communicate with {0}.",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Download client {0} is set to remove completed downloads. This can result in downloads being removed from your client before {1} can import them.",
|
||||
"DownloadClientSettings": "Download Client Settings",
|
||||
"DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}",
|
||||
@@ -306,6 +307,7 @@
|
||||
"Ended": "Ended",
|
||||
"EndedAllBooksDownloaded": "Ended (All books downloaded)",
|
||||
"EntityName": "Entity Name",
|
||||
"ErrorLoadingContent": "There was an error loading this content",
|
||||
"ErrorLoadingContents": "Error loading contents",
|
||||
"ErrorLoadingPreviews": "Error loading previews",
|
||||
"Events": "Events",
|
||||
|
||||
@@ -478,7 +478,7 @@
|
||||
"AllowAuthorChangeClickToChangeAuthor": "Click para cambiar el autor",
|
||||
"AddedAuthorSettings": "Añadidas las opciones de Autor",
|
||||
"AddImportListExclusionHelpText": "Evitar que el libro se añada a Readarr mediante listas de importación o la actualización del autor",
|
||||
"AddMissing": "Añadir los que faltan",
|
||||
"AddMissing": "Añadir faltantes",
|
||||
"AllBooks": "Todos los libros",
|
||||
"AddNewItem": "Añadir nuevo item",
|
||||
"AllExpandedExpandAll": "Expandir todo",
|
||||
@@ -539,7 +539,7 @@
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Ningún indexer está disponible por errores durando más de 6 horas",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexers no disponible por errores durando más de 6 horas: {0}",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "Todos los indexers capaces de RSS están temporalmente desactivados debido a errores recientes con el indexer",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "No hay Indexers con Búsqueda Interactiva activada, Radarr no obtendrá ningún resultado en las búsquedas",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "No hay Indexers con Búsqueda Interactiva activada, Readarr no obtendrá ningún resultado en las búsquedas",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexers no disponibles debido a errores: {0}",
|
||||
"OnBookFileDeleteForUpgrade": "En archivo de película Eliminar para actualizar",
|
||||
"OnBookFileDeleteForUpgradeHelpText": "En archivo de película Eliminar para actualizar",
|
||||
@@ -728,5 +728,31 @@
|
||||
"Events": "Eventos",
|
||||
"Medium": "Medio",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Todos los resultados están ocultos por el filtro aplicado",
|
||||
"CatalogNumber": "número de catálogo"
|
||||
"CatalogNumber": "número de catálogo",
|
||||
"Authors": "Autores",
|
||||
"AuthorEditor": "Editor de Autor",
|
||||
"Author": "Autor",
|
||||
"BackupIntervalHelpText": "Intervalo para respaldar la base de datos y configuraciones de Readarr",
|
||||
"BookFileCounttotalBookCountBooksDownloadedInterp": "{0}/{1} libros descargados",
|
||||
"BookList": "Lista de libros",
|
||||
"BookFilesCountMessage": "No hay archivos de libros",
|
||||
"AllowFingerprintingHelpText": "Utilizar la huella digital para mejorar la precisión de la coincidencia de libros",
|
||||
"BookStudio": "Estudio de libros",
|
||||
"AutomaticUpdatesDisabledDocker": "Las actualizaciones automáticas no son compatibles directamente al usar el mecanismo de actualización de Docker. Deberás actualizar la imagen del contenedor fuera de {appName} o utilizar un script",
|
||||
"AuthorIndex": "Índice de autor",
|
||||
"AudioFileMetadata": "Escribir metadatos en archivos de audio",
|
||||
"BookIndex": "Índice de libro",
|
||||
"BookEditor": "Editor de Libro",
|
||||
"ASIN": "ASIN",
|
||||
"AnyEditionOkHelpText": "Readarr cambiará automáticamente a la edición que mejor coincida con los archivos descargados",
|
||||
"AppUpdated": "{appName} Actualizado",
|
||||
"AppUpdatedVersion": "{appName} ha sido actualizado a la versión `{version}`, para obtener los cambios más recientes, necesitaras recargar {appName}",
|
||||
"AuthorFolderFormat": "Formato de Carpeta de Autor",
|
||||
"AuthorNameHelpText": "El nombre del autor/libro a excluir (puede ser cualquier cosa significativa)",
|
||||
"BookMonitoring": "Monitoreo de libros",
|
||||
"AllowFingerprintingHelpTextWarning": "Esto requiere que Readarr lea partes del archivo, lo que ralentizará los escaneos y puede provocar una alta actividad en el disco o en la red.",
|
||||
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total: {2})",
|
||||
"Book": "Libro",
|
||||
"AutomaticallySwitchEdition": "Cambiar edición automáticamente",
|
||||
"BookNaming": "Nombrado de libros"
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
"SslCertPathHelpTextWarning": "Nécessite un redémarrage pour prendre effet",
|
||||
"UnableToLoadMetadataProfiles": "Impossible de charger les profils de délai",
|
||||
"AddingTag": "Ajouter un tag",
|
||||
"AgeWhenGrabbed": "Age (au moment du téléchargement)",
|
||||
"AlreadyInYourLibrary": "Déjà disponible dans votre librairie",
|
||||
"AlternateTitles": "Titre alternatif",
|
||||
"AgeWhenGrabbed": "Âge (au moment de la saisie)",
|
||||
"AlreadyInYourLibrary": "Déjà dans la bibliothèque",
|
||||
"AlternateTitles": "Titres alternatifs",
|
||||
"Analytics": "Statistiques",
|
||||
"AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs vers les serveurs de Readarr. Cela inclut des informations sur votre navigateur, quelle page de l'interface web Readarr vous utilisez, les rapports d'erreur ainsi que le SE et sa version. Nous utiliserons ces informations pour prioriser les nouvelles fonctionnalités et les corrections de bugs.",
|
||||
"AnalyticsEnabledHelpTextWarning": "Nécessite un redémarrage pour prendre effet",
|
||||
@@ -35,14 +35,14 @@
|
||||
"Authentication": "Authentification",
|
||||
"AuthenticationMethodHelpText": "Exiger un identifiant et un mot de passe pour accéder à Readarr",
|
||||
"AuthorClickToChangeBook": "Cliquer pour changer le livre",
|
||||
"AutoRedownloadFailedHelpText": "Chercher et essayer de télécharger une version différente automatiquement",
|
||||
"AutoRedownloadFailedHelpText": "Recherche automatique et tentative de téléchargement d'une version différente",
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Les livres qui sont effacés du disque dur sont automatiquement non-surveillés dans Readarr",
|
||||
"Automatic": "Automatique",
|
||||
"BackupFolderHelpText": "Les chemins correspondants seront sous le répertoire AppData de Readarr",
|
||||
"BackupNow": "Sauvegarder maintenant",
|
||||
"BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de conservation seront automatiquement effacées",
|
||||
"BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de rétention seront nettoyées automatiquement",
|
||||
"Backups": "Sauvegardes",
|
||||
"BindAddress": "Adresse d'attache",
|
||||
"BindAddress": "Adresse de liaison",
|
||||
"BindAddressHelpText": "Adresse IP valide, localhost ou '*' pour toutes les interfaces",
|
||||
"BookIsDownloading": "Le livre est en cours de téléchargement",
|
||||
"BookIsDownloadingInterp": "Le livre est en cours de téléchargement - {0}% {1}",
|
||||
@@ -52,16 +52,16 @@
|
||||
"Cancel": "Annuler",
|
||||
"CancelMessageText": "Êtes-vous sur de vouloir annuler cette tâche en attente ?",
|
||||
"CertificateValidation": "Validation du certificat",
|
||||
"CertificateValidationHelpText": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne changez rien si vous ne comprenez pas les risques.",
|
||||
"CertificateValidationHelpText": "Modifier le niveau de rigueur de la validation de la certification HTTPS. Ne pas modifier si vous ne maîtrisez pas les risques.",
|
||||
"ChangeFileDate": "Changer la date du fichier",
|
||||
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
|
||||
"ChmodFolder": "chmod Dossier",
|
||||
"ChmodFolderHelpText": "Nombre, appliqué durant l'import/renommage vers les dossiers et fichiers multimédias (sans exécuter les bits)",
|
||||
"ChmodFolderHelpText": "Octal, appliqué lors de l'importation/du renommage des dossiers et fichiers multimédias (sans bits d'exécution)",
|
||||
"ChmodFolderHelpTextWarning": "Fonctionne uniquement si l'utilisateur exécutant Readarr est le propriétaire du fichier. Il est recommandé de vérifier les permissions du client de téléchargement.",
|
||||
"ChownGroupHelpText": "Nom du Groupe ou GID. Utiliser le GID pour les systèmes de fichier distants.",
|
||||
"ChownGroupHelpText": "Nom du groupe ou gid. Utilisez gid pour les systèmes de fichiers distants.",
|
||||
"ChownGroupHelpTextWarning": "Fonctionne uniquement si l'utilisateur exécutant Readarr est le propriétaire du fichier. Il est recommandé de vérifier que le client de téléchargement utilise le même Groupe que Readarr.",
|
||||
"Clear": "Effacer",
|
||||
"ClickToChangeQuality": "Cliquer pour changer la qualité",
|
||||
"ClickToChangeQuality": "Cliquez pour changer la qualité",
|
||||
"ClientPriority": "Priorité du client",
|
||||
"CloneIndexer": "Dupliqué l'indexeur",
|
||||
"CloneProfile": "Dupliqué le profil",
|
||||
@@ -258,7 +258,7 @@
|
||||
"PublishedDate": "Date de publication",
|
||||
"Quality": "Qualité",
|
||||
"QualityDefinitions": "Définitions qualité",
|
||||
"QualityProfile": "Profil qualité",
|
||||
"QualityProfile": "Profil de qualité",
|
||||
"QualitySettings": "Paramètres Qualité",
|
||||
"Queue": "File d'attente",
|
||||
"RSSSync": "Synchro RSS",
|
||||
@@ -634,7 +634,7 @@
|
||||
"CalibreOutputFormat": "Format de sortie de Calibre",
|
||||
"CalibreOutputProfile": "Profil de sortie de Calibre",
|
||||
"CalibrePassword": "Mot de passe de Calibre",
|
||||
"ChownGroup": "Groupe chown",
|
||||
"ChownGroup": "chown Groupe",
|
||||
"CollapseMultipleBooks": "Regrouper plusieurs livres",
|
||||
"CollapseMultipleBooksHelpText": "Regrouper les livres qui sortent le même jour",
|
||||
"ConvertToFormat": "Convertir au format",
|
||||
@@ -667,7 +667,7 @@
|
||||
"ApplicationURL": "URL de l'application",
|
||||
"ImportListExclusions": "Supprimer les exclusions de liste d'imports",
|
||||
"ChooseImportMethod": "Choisir une méthode d'importation",
|
||||
"ClickToChangeReleaseGroup": "Cliquer pour changer le groupe de publication",
|
||||
"ClickToChangeReleaseGroup": "Cliquez pour changer de groupe de diffusion",
|
||||
"HardlinkCopyFiles": "Lier/copier les fichiers",
|
||||
"MoveFiles": "Déplacer les fichiers",
|
||||
"OnApplicationUpdate": "Lors de la mise à jour de l'app",
|
||||
@@ -722,7 +722,7 @@
|
||||
"ReplaceWithSpaceDashSpace": "Remplacer par Space Dash Space",
|
||||
"DeleteRemotePathMapping": "Éditer le chemin distant",
|
||||
"DeleteRemotePathMappingMessageText": "Êtes-vous sûr de vouloir effacer ce chemin ?",
|
||||
"BlocklistReleases": "Mettre cette release sur la liste noire",
|
||||
"BlocklistReleases": "Publications de la liste de blocage",
|
||||
"DeleteConditionMessageText": "Voulez-vous vraiment supprimer la liste '{0}' ?",
|
||||
"Negated": "Inversé",
|
||||
"RemoveSelectedItem": "Supprimer l'élément sélectionné",
|
||||
@@ -744,12 +744,12 @@
|
||||
"RedownloadFailed": "Échec du téléchargement",
|
||||
"RemoveCompleted": "Supprimer les complétés",
|
||||
"RemoveFailed": "Echec de la suppression",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Comment appliquer des tags au film sélectionné",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Comment appliquer des balises aux clients de téléchargement sélectionnés",
|
||||
"ApplyChanges": "Appliquer les modifications",
|
||||
"ApplyTagsHelpTextAdd": "Ajouter : Ajouter les tags à la liste de tags existantes",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Comment appliquer des tags au film sélectionné",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Comment appliquer des balises aux listes d'importation sélectionnées",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Comment appliquer des tags aux indexeurs sélectionnés",
|
||||
"ApplyTagsHelpTextReplace": "Remplacer : Remplace les tags par les tags renseignés (ne pas renseigner de tags pour effacer tous les tags)",
|
||||
"ApplyTagsHelpTextReplace": "Remplacer : Remplace les balises par les balises saisies (ne pas saisir de balises pour effacer toutes les balises)",
|
||||
"CountIndexersSelected": "{0} indexeur(s) sélectionné(s)",
|
||||
"DeleteSelectedDownloadClients": "Supprimer le client de téléchargement",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Voulez-vous vraiment supprimer l'indexeur '{0}' ?",
|
||||
@@ -773,7 +773,7 @@
|
||||
"RecentChanges": "Changements récents",
|
||||
"System": "Système",
|
||||
"WhatsNew": "Quoi de neuf ?",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Tous les résultats ont été dissimulés par le filtre actuellement appliqué",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Tous les résultats sont masqués par le filtre appliqué",
|
||||
"Location": "Emplacement",
|
||||
"NoResultsFound": "Aucun résultat trouvé",
|
||||
"Events": "Événements",
|
||||
@@ -790,5 +790,8 @@
|
||||
"Ui": "UI",
|
||||
"Activity": "Activité",
|
||||
"AddNew": "Ajouter un nouveau",
|
||||
"Backup": "Sauvegarde"
|
||||
"Backup": "Sauvegarde",
|
||||
"AutoAdd": "Ajout automatique",
|
||||
"AutomaticUpdatesDisabledDocker": "Les mises à jour automatiques ne sont pas directement prises en charge lors de l'utilisation du mécanisme de mise à jour de Docker. Vous devrez mettre à jour l'image du conteneur en dehors de {appName} ou utiliser un script",
|
||||
"AppUpdated": "{appName} Mise à jour"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"DeleteReleaseProfile": "지연 프로필 삭제",
|
||||
"DeleteIndexer": "인덱서 삭제",
|
||||
"HasPendingChangesSaveChanges": "변경 사항을 저장하다",
|
||||
"History": "역사",
|
||||
"History": "내역",
|
||||
"DownloadClients": "클라이언트 다운로드",
|
||||
"Host": "주최자",
|
||||
"GoToInterp": "{0}로 이동",
|
||||
@@ -408,7 +408,7 @@
|
||||
"UnableToLoadRemotePathMappings": "원격 경로 매핑을로드 할 수 없습니다.",
|
||||
"UnableToLoadRootFolders": "루트 폴더를로드 할 수 없습니다.",
|
||||
"UnableToLoadTags": "태그를로드 할 수 없습니다.",
|
||||
"UnableToLoadTheCalendar": "캘린더를로드 할 수 없습니다.",
|
||||
"UnableToLoadTheCalendar": "달력을 불러올 수 없습니다.",
|
||||
"UnableToLoadUISettings": "UI 설정을로드 할 수 없습니다.",
|
||||
"Ungroup": "그룹 해제",
|
||||
"Unmonitored": "모니터링되지 않음",
|
||||
@@ -608,7 +608,7 @@
|
||||
"FreeSpace": "여유 공간",
|
||||
"NextExecution": "다음 실행",
|
||||
"SomeResultsAreHiddenByTheAppliedFilter": "적용된 필터에 의해 모든 결과가 숨겨집니다.",
|
||||
"System": "체계",
|
||||
"System": "시스템",
|
||||
"Medium": "매질",
|
||||
"ConnectionLost": "연결이 끊어졌습니다.",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "적용된 필터에 의해 모든 결과가 숨겨집니다.",
|
||||
@@ -616,6 +616,6 @@
|
||||
"LastDuration": "lastDuration",
|
||||
"LastExecution": "마지막 실행",
|
||||
"Activity": "활동",
|
||||
"AddNew": "새로 추가",
|
||||
"AddNew": "새로 추가하기",
|
||||
"Backup": "백업"
|
||||
}
|
||||
|
||||
@@ -646,5 +646,7 @@
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Hoe tags toepassen op de geselecteerde download clients",
|
||||
"ApplyTagsHelpTextRemove": "Verwijderen: Verwijder de ingevoerde tags",
|
||||
"ApplyTagsHelpTextReplace": "Vervangen: Vervang de tags met de ingevoerde tags (vul geen tags in om alle tags te wissen)",
|
||||
"AutoAdd": "Automatisch Toevoegen"
|
||||
"AutoAdd": "Automatisch Toevoegen",
|
||||
"Activity": "Activiteit",
|
||||
"AddNew": "Voeg Nieuwe Toe"
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
"DeleteImportListMessageText": "Czy na pewno chcesz usunąć listę „{0}”?",
|
||||
"DeleteIndexer": "Usuń indeksator",
|
||||
"DeleteIndexerMessageText": "Czy na pewno chcesz usunąć indeksator „{0}”?",
|
||||
"DeleteMetadataProfileMessageText": "Czy na pewno chcesz usunąć profil metadanych '{0}'?",
|
||||
"DeleteMetadataProfileMessageText": "Czy na pewno usunąć informacje dodatkowe '{0name}'?",
|
||||
"DeleteNotification": "Usuń powiadomienie",
|
||||
"DeleteNotificationMessageText": "Czy na pewno chcesz usunąć powiadomienie „{0}”?",
|
||||
"DeleteQualityProfile": "Usuń profil jakości",
|
||||
|
||||
@@ -140,18 +140,18 @@
|
||||
"SearchAll": "Pesquisar todos",
|
||||
"Source": "Origem",
|
||||
"AddListExclusion": "Adicionar exclusão de lista",
|
||||
"AddingTag": "A adicionar etiqueta",
|
||||
"AgeWhenGrabbed": "Tempo de vida (quando capturado)",
|
||||
"AddingTag": "Adicionando etiqueta",
|
||||
"AgeWhenGrabbed": "Idade (quando capturada)",
|
||||
"AlreadyInYourLibrary": "Já está na sua biblioteca",
|
||||
"AlternateTitles": "Título alternativo",
|
||||
"Analytics": "Análises",
|
||||
"AlternateTitles": "Títulos Alternativos",
|
||||
"Analytics": "Análise",
|
||||
"AnalyticsEnabledHelpText": "Envia informações anônimas de uso e de erros aos servidores do Readarr. Isso inclui informações sobre seu browser, páginas utilizadas na WebUI do Readarr, relatórios de erros, bem como as versões do sistema operativo e da aplicação. Utilizaremos essas informações para priorizar funcionalidades e correções de bugs.",
|
||||
"AppDataDirectory": "Pasta AppData",
|
||||
"AppDataDirectory": "Diretório AppData",
|
||||
"ApplyTags": "Aplicar etiquetas",
|
||||
"Authentication": "Autenticação",
|
||||
"AuthenticationMethodHelpText": "Solicitar nome de utilizador e palavra-passe para acessar ao Readarr",
|
||||
"AuthorClickToChangeBook": "Clique para mudar o livro",
|
||||
"AutoRedownloadFailedHelpText": "Pesquisar e tentar transferir automaticamente uma versão diferente",
|
||||
"AutoRedownloadFailedHelpText": "Procurar automaticamente e tente baixar uma versão diferente",
|
||||
"AutoUnmonitorPreviouslyDownloadedBooksHelpText": "Livros eliminados do disco deixam automaticamente de ser monitorados no Readarr",
|
||||
"Automatic": "Automático",
|
||||
"BackupFolderHelpText": "Caminhos relativos estarão na pasta AppData do Readarr",
|
||||
@@ -690,7 +690,7 @@
|
||||
"IndexerJackettAll": "Indexadores que usam o ponto de extremidade não suportado do Jackett 'all (tudo)' : {0}",
|
||||
"Duration": "Duração",
|
||||
"Filters": "Filtros",
|
||||
"AppDataLocationHealthCheckMessage": "Não foi possivél actualizar para prevenir apagar a AppData durante a actualização",
|
||||
"AppDataLocationHealthCheckMessage": "Não foi possível atualizar para prevenir apagar a AppData durante a atualização",
|
||||
"FileWasDeletedByViaUI": "O ficheiro foi eliminado por meio da IU",
|
||||
"SizeLimit": "Tamanho Limite",
|
||||
"Started": "Começado",
|
||||
@@ -781,7 +781,7 @@
|
||||
"OnBookFileDelete": "Ao eliminar o ficheiro do filme",
|
||||
"InstanceName": "Nome da Instancia",
|
||||
"RestartRequiredHelpTextWarning": "Requer reinício para aplicar alterações",
|
||||
"AddList": "Adicionar lista",
|
||||
"AddList": "Adicionar Lista",
|
||||
"RenameFiles": "Renomear ficheiros",
|
||||
"Test": "Testar",
|
||||
"InstanceNameHelpText": "Nome da instância na aba e nome da aplicação para Syslog",
|
||||
@@ -789,7 +789,7 @@
|
||||
"AddedAuthorSettings": "Definições de Autor adicionadas",
|
||||
"ManualImportSelectEdition": "Importação manual - Seleciona o filme",
|
||||
"ApplicationUrlHelpText": "O URL desta aplicação externa, incluindo http(s)://, porta e URL base",
|
||||
"ApplicationURL": "URL da aplicação",
|
||||
"ApplicationURL": "URL do Aplicativo",
|
||||
"ChooseImportMethod": "Selecionar Modo de Importação",
|
||||
"HardlinkCopyFiles": "Realizar ligação fixa/copiar ficheiros",
|
||||
"MoveFiles": "Mover ficheiros",
|
||||
@@ -834,5 +834,16 @@
|
||||
"ReplaceWithDash": "Substituir por travessão",
|
||||
"ReplaceWithSpaceDash": "Substituir por espaço e travessão",
|
||||
"ReplaceWithSpaceDashSpace": "Substituir por espaço, travessão e espaço",
|
||||
"ApiKeyValidationHealthCheckMessage": "Por favor, actualize a sua API Key para ter no minimo {0} caracteres. Pode fazer através das definições ou do ficheiro de configuração"
|
||||
"ApiKeyValidationHealthCheckMessage": "Por favor, atualize a sua API Key para ter no mínimo {0} caracteres. Pode fazer através das definições ou do ficheiro de configuração",
|
||||
"AppUpdated": "{appName} Atualizado",
|
||||
"ApplyTagsHelpTextAdd": "Adicionar: agregar as etiquetas à lista existente de etiquetas",
|
||||
"ApplyTagsHelpTextRemove": "Remover: eliminar as etiquetas adicionadas",
|
||||
"ApplyTagsHelpTextReplace": "Substituir: mudar as etiquetas pelas adicionadas (deixe em branco para limpar todas as etiquetas)",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar etiquetas aos indexadores selecionados",
|
||||
"AutoAdd": "Adicionar automaticamente",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Todos os resultados foram ocultados pelo filtro aplicado",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Como aplicar etiquetas aos clientes de download selecionados",
|
||||
"Activity": "Atividade",
|
||||
"AddNew": "Adicionar Novo",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar etiquetas às listas de importação selecionadas"
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
"ProxyPasswordHelpText": "Você só precisa digitar um nome de usuário e senha se for necessário. Caso contrário, deixe-os em branco.",
|
||||
"SslCertPathHelpTextWarning": "Requer reinício para ter efeito",
|
||||
"UnableToLoadMetadataProfiles": "Não foi possível carregar os perfis de metadados",
|
||||
"AddListExclusion": "Adicionar exclusão à lista",
|
||||
"AddListExclusion": "Adicionar à Lista de Exclusão",
|
||||
"AddingTag": "Adicionar tag",
|
||||
"AlreadyInYourLibrary": "Já está na sua biblioteca",
|
||||
"AlternateTitles": "Títulos alternativos",
|
||||
"AlternateTitles": "Títulos Alternativos",
|
||||
"Analytics": "Analítica",
|
||||
"AnalyticsEnabledHelpText": "Envie informações anônimas de uso e erro para os servidores do Readarr. Isso inclui informações sobre seu navegador, quais páginas da interface Web do Readarr você usa, relatórios de erros, e a versão do sistema operacional e do tempo de execução. Usaremos essas informações para priorizar recursos e correções de bugs.",
|
||||
"AppDataDirectory": "Diretório AppData",
|
||||
@@ -57,7 +57,7 @@
|
||||
"Edit": "Editar",
|
||||
"Edition": "Edição",
|
||||
"Enable": "Habilitar",
|
||||
"GrabRelease": "Capturar Versão",
|
||||
"GrabRelease": "Obter Lançamento",
|
||||
"GrabReleaseMessageText": "O Readarr não conseguiu determinar a qual autor e livro esse lançamento está relacionado. O Readarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?",
|
||||
"GrabSelected": "Obter Selecionado",
|
||||
"Group": "Grupo",
|
||||
@@ -122,7 +122,7 @@
|
||||
"DeleteIndexerMessageText": "Tem certeza de que deseja excluir o indexador '{name}'?",
|
||||
"DeleteMetadataProfileMessageText": "Tem certeza de que deseja excluir o perfil de metadados '{name}'?",
|
||||
"DeleteNotification": "Excluir Notificação",
|
||||
"DeleteNotificationMessageText": "Tem certeza de que deseja excluir o perfil de metadados '{name}'?",
|
||||
"DeleteNotificationMessageText": "Tem certeza de que deseja excluir a notificação '{name}'?",
|
||||
"DeleteQualityProfile": "Excluir Perfil de Qualidade",
|
||||
"DeleteQualityProfileMessageText": "Tem certeza de que deseja excluir o perfil de qualidade '{name}'?",
|
||||
"DeleteReleaseProfile": "Excluir Perfil de Lançamento",
|
||||
@@ -133,7 +133,7 @@
|
||||
"DeleteTag": "Excluir tag",
|
||||
"DeleteTagMessageText": "Tem certeza de que deseja excluir a tag \"{0}\"?",
|
||||
"DestinationPath": "Caminho de Destino",
|
||||
"DetailedProgressBarHelpText": "Mostrar texto em barra de progresso",
|
||||
"DetailedProgressBarHelpText": "Mostrar texto na barra de progresso",
|
||||
"EnableAutomaticAdd": "Habilitar Adição Automática",
|
||||
"EnableAutomaticSearch": "Ativar a pesquisa automática",
|
||||
"EnableColorImpairedMode": "Habilitar Modo para Deficientes Visuais",
|
||||
@@ -145,7 +145,7 @@
|
||||
"EnableSSL": "Habilitar SSL",
|
||||
"EnableSslHelpText": " Requer a reinicialização com a execução como administrador para fazer efeito",
|
||||
"Ended": "Terminou",
|
||||
"ErrorLoadingContents": "Erro ao carregar conteúdo",
|
||||
"ErrorLoadingContents": "Erro ao carregar o conteúdo",
|
||||
"ErrorLoadingPreviews": "Erro ao carregar as visualizações",
|
||||
"Exception": "Exceção",
|
||||
"ExtraFileExtensionsHelpTexts1": "Lista separada por vírgulas de arquivos adicionais a importar (.nfo será importado como .nfo-orig)",
|
||||
@@ -202,7 +202,7 @@
|
||||
"Logging": "Registrando",
|
||||
"LongDateFormat": "Formato de Data Longa",
|
||||
"MIA": "Desaparecidos",
|
||||
"ManualImport": "Importação manual",
|
||||
"ManualImport": "Importação Manual",
|
||||
"MarkAsFailed": "Marcar como Falha",
|
||||
"MarkAsFailedMessageText": "Tem certeza que deseja marcar \"{0}\" como falhado?",
|
||||
"MaximumLimits": "Limites Máximos",
|
||||
@@ -239,7 +239,7 @@
|
||||
"OpenBrowserOnStart": "Abrir navegador ao iniciar",
|
||||
"Options": "Opções",
|
||||
"Original": "Original",
|
||||
"Overview": "Visão geral",
|
||||
"Overview": "Visão Geral",
|
||||
"PackageVersion": "Versão do pacote",
|
||||
"PageSize": "Tamanho da página",
|
||||
"PageSizeHelpText": "Quantidade de itens a exibir em cada página",
|
||||
@@ -249,8 +249,8 @@
|
||||
"Port": "Porta",
|
||||
"PortHelpTextWarning": "Requer reinício para ter efeito",
|
||||
"PortNumber": "Número da Porta",
|
||||
"PosterSize": "Tamanho do pôster",
|
||||
"PreviewRename": "Visualizar renomeação",
|
||||
"PosterSize": "Tamanho do Pôster",
|
||||
"PreviewRename": "Prévia da Renomeação",
|
||||
"Profiles": "Perfis",
|
||||
"Proper": "Proper",
|
||||
"PropersAndRepacks": "Propers e repacks",
|
||||
@@ -284,7 +284,7 @@
|
||||
"RefreshInformationAndScanDisk": "Atualizar as informações e verificar o disco",
|
||||
"ReleaseDate": "Data de lançamento",
|
||||
"ReleaseGroup": "Grupo de lançamento",
|
||||
"ReleaseRejected": "Versão rejeitada",
|
||||
"ReleaseRejected": "Lançamento Rejeitado",
|
||||
"ReleaseWillBeProcessedInterp": "A versão será processada {0}",
|
||||
"Reload": "Recarregar",
|
||||
"RemotePathMappings": "Mapeamentos de Caminho Remoto",
|
||||
@@ -327,9 +327,9 @@
|
||||
"Scheduled": "Programado",
|
||||
"ScriptPath": "Caminho do Script",
|
||||
"Search": "Pesquisar",
|
||||
"SearchAll": "Pesquisar tudo",
|
||||
"SearchAll": "Pesquisar Todos",
|
||||
"SearchForMissing": "Pesquisar por Ausentes",
|
||||
"SearchSelected": "Pesquisar selecionado(s)",
|
||||
"SearchSelected": "Pesquisar Selecionado",
|
||||
"Security": "Segurança",
|
||||
"SendAnonymousUsageData": "Enviar dados de uso anônimos",
|
||||
"SetPermissions": "Definir Permissões",
|
||||
@@ -338,17 +338,17 @@
|
||||
"Settings": "Configurações",
|
||||
"ShortDateFormat": "Formato de Data Curta",
|
||||
"ShowCutoffUnmetIconHelpText": "Mostrar ícone para arquivos quando o limite não foi atingindo",
|
||||
"ShowDateAdded": "Mostrar data de adição",
|
||||
"ShowMonitored": "Mostrar monitorado(s)",
|
||||
"ShowMonitoredHelpText": "Mostrar status de monitoramento sob o pôster",
|
||||
"ShowPath": "Mostrar caminho",
|
||||
"ShowQualityProfile": "Mostrar perfil de qualidade",
|
||||
"ShowQualityProfileHelpText": "Mostrar perfil de qualidade sob o pôster",
|
||||
"ShowDateAdded": "Mostrar Data de Adição",
|
||||
"ShowMonitored": "Mostrar Monitorados",
|
||||
"ShowMonitoredHelpText": "Mostrar status monitorado sob o pôster",
|
||||
"ShowPath": "Mostrar Caminho",
|
||||
"ShowQualityProfile": "Mostrar Perfil de Qualidade",
|
||||
"ShowQualityProfileHelpText": "Mostrar perfil de qualidade abaixo do pôster",
|
||||
"ShowRelativeDates": "Mostrar Datas Relativas",
|
||||
"ShowRelativeDatesHelpText": "Mostrar datas relativas (Hoje/Ontem/etc) ou absolutas",
|
||||
"ShowSearch": "Mostrar pesquisa",
|
||||
"ShowSearch": "Mostrar Pesquisa",
|
||||
"ShowSearchActionHelpText": "Mostrar botão de pesquisa ao passar o mouse",
|
||||
"ShowSizeOnDisk": "Mostrar tamanho no disco",
|
||||
"ShowSizeOnDisk": "Mostrar Tamanho no Disco",
|
||||
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Mostrado acima de cada coluna quando a semana é a exibição ativa",
|
||||
"Size": " Tamanho",
|
||||
"SkipFreeSpaceCheck": "Ignorar verificação de espaço livre",
|
||||
@@ -373,7 +373,7 @@
|
||||
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Será usado quando pesquisas automáticas forem realizadas pela interface ou pelo Readarr",
|
||||
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Será usado com a pesquisa interativa",
|
||||
"TagIsNotUsedAndCanBeDeleted": "A tag não é usada e pode ser excluída",
|
||||
"Tags": "Etiquetas",
|
||||
"Tags": "Tags",
|
||||
"Tasks": "Tarefas",
|
||||
"TestAll": "Testar tudo",
|
||||
"TestAllClients": "Testar todos os clientes",
|
||||
@@ -385,7 +385,7 @@
|
||||
"TorrentDelay": "Atraso do Torrent",
|
||||
"TorrentDelayHelpText": "Demora em minutos para esperar antes de pegar um torrent",
|
||||
"Torrents": "Torrents",
|
||||
"TotalFileSize": "Tamanho total do arquivo",
|
||||
"TotalFileSize": "Tamanho Total do Arquivo",
|
||||
"UILanguage": "Idioma da interface",
|
||||
"UILanguageHelpText": "Idioma que o Readarr usará para a interface",
|
||||
"UILanguageHelpTextWarning": "É necessário recarregar o navegador",
|
||||
@@ -454,7 +454,7 @@
|
||||
"TrackNumber": "Número da faixa",
|
||||
"TotalBookCountBooksTotalBookFileCountBooksWithFilesInterp": "{0} livro(s) no total. {1} livro(s) com arquivos.",
|
||||
"TheBooksFilesWillBeDeleted": "Os arquivos do livro serão excluídos.",
|
||||
"TagsHelpText": "Aplica-se a autores com pelo menos uma etiqueta correspondente. Deixe em branco para aplicar a todos os autores",
|
||||
"TagsHelpText": "Aplica-se a autores com pelo menos uma tag correspondente. Deixe em branco para aplicar a todos os autores",
|
||||
"StatusEndedDeceased": "Falecido",
|
||||
"StatusEndedContinuing": "Continuação",
|
||||
"SslCertPasswordHelpTextWarning": "Requer reinício para ter efeito",
|
||||
@@ -470,14 +470,14 @@
|
||||
"ShowLastBook": "Mostrar o último livro",
|
||||
"ShowBookCount": "Mostrar contagem de livros",
|
||||
"ShowBannersHelpText": "Mostrar banners em vez de nomes",
|
||||
"ShowBanners": "Mostrar banners",
|
||||
"ShowBanners": "Mostrar Banners",
|
||||
"ShouldSearchHelpText": "Indexadores de pesquisa para novos itens adicionados. Tenha cuidado ao usar com listas grandes.",
|
||||
"ShouldMonitorHelpText": "Monitorar novos autores e livros adicionados por esta lista",
|
||||
"SeriesNumber": "Número da série",
|
||||
"Series": "Séries",
|
||||
"SendMetadataToCalibre": "Enviar metadados para o Calibre",
|
||||
"SelectedCountAuthorsSelectedInterp": "{0} autor(es) selecionado(s)",
|
||||
"SearchMonitored": "Pesquisar monitorados",
|
||||
"SearchMonitored": "Pesquisa Monitorada",
|
||||
"SearchForNewItems": "Pesquisar novos itens",
|
||||
"SearchForMonitoredBooks": "Pesquisar livros monitorados",
|
||||
"SearchForAllMissingBooks": "Pesquisar todos os livros ausentes",
|
||||
@@ -488,7 +488,7 @@
|
||||
"RescanAfterRefreshHelpText": "Verificar novamente a pasta de autor após atualizar o autor",
|
||||
"ReplaceIllegalCharactersHelpText": "Substituir caracteres ilegais. Se desmarcada, o Readarr irá removê-los",
|
||||
"RenameBooks": "Renomear livros",
|
||||
"ReleaseProfiles": "Perfis de Lançamento",
|
||||
"ReleaseProfiles": "Perfis de Lançamentos",
|
||||
"RefreshInformation": "Atualizar informações",
|
||||
"RefreshAuthor": "Atualizar autor",
|
||||
"ReadarrSupportsMultipleListsForImportingBooksAndAuthorsIntoTheDatabase": "O Readarr oferece suporte a várias listas para importar livros e autores para o banco de dados.",
|
||||
@@ -604,7 +604,7 @@
|
||||
"ContinuingNoAdditionalBooksAreExpected": "Não espera-se mais livros",
|
||||
"ContinuingMoreBooksAreExpected": "Espera-se mais livros",
|
||||
"ContinuingAllBooksDownloaded": "Continuação (todos os livros baixados)",
|
||||
"Continuing": "Continuação",
|
||||
"Continuing": "Continuando",
|
||||
"ConsoleLogLevel": "Nível de log do console",
|
||||
"CollapseMultipleBooksHelpText": "Recolher vários livros lançados no mesmo dia",
|
||||
"CollapseMultipleBooks": "Recolher vários livros",
|
||||
@@ -675,7 +675,7 @@
|
||||
"ShowBookTitleHelpText": "Mostrar título do filme abaixo do pôster",
|
||||
"ShowReleaseDate": "Mostrar data de lançamento",
|
||||
"NotAvailable": "Indisponível",
|
||||
"ShowTitle": "Mostrar título",
|
||||
"ShowTitle": "Mostrar Título",
|
||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "A pasta do filme \"{0}\" e todo o seu conteúdo serão excluídos.",
|
||||
"BookMonitoring": "Monitoramento de Livro",
|
||||
"ExistingItems": "Itens Existentes",
|
||||
@@ -702,10 +702,10 @@
|
||||
"BookIndex": "Índice de Livro",
|
||||
"MassBookSearch": "Busca em Massa por Livro",
|
||||
"MassBookSearchWarning": "Tem certeza de que deseja executar pesquisa em massa para {0} livros?",
|
||||
"SelectAll": "Selecionar Todos",
|
||||
"SelectAll": "Selecionar Tudo",
|
||||
"SelectedCountBooksSelectedInterp": "{0} Livro(s) Selecionado(s)",
|
||||
"TheFollowingFilesWillBeDeleted": "Os seguintes arquivos irão ser excluídos:",
|
||||
"UnselectAll": "Deselecionar Todos",
|
||||
"UnselectAll": "Desmarcar Todos",
|
||||
"BookEditor": "Editor de Livro",
|
||||
"BookList": "Lista de Livros",
|
||||
"ThisCannotBeCancelled": "Isso não pode ser cancelado uma vez iniciado sem desabilitar todos os seus indexadores.",
|
||||
@@ -829,7 +829,7 @@
|
||||
"UISettingsSummary": "Opções de calendário, data e cores prejudicadas",
|
||||
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
|
||||
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta de translocação de aplicativo.",
|
||||
"TagsSettingsSummary": "Gerenciar etiquetas de autor, perfil, restrição e notificação",
|
||||
"TagsSettingsSummary": "Gerenciar tags de autor, perfil, restrição e notificação",
|
||||
"InstanceNameHelpText": "Nome da instância na aba e para o nome do aplicativo Syslog",
|
||||
"AddList": "Adicionar Lista",
|
||||
"DataExistingBooks": "Monitorar livros que você tem arquivos ou que não foram lançados ainda",
|
||||
@@ -839,7 +839,7 @@
|
||||
"EditList": "Editar Lista",
|
||||
"MonitorExistingBooks": "Monitorar Livros Existentes",
|
||||
"RenameFiles": "Renomear Arquivos",
|
||||
"Test": "Testar",
|
||||
"Test": "Teste",
|
||||
"WriteMetadataTags": "Salvar tags de metadados",
|
||||
"RestartRequiredHelpTextWarning": "Requer reinicialização para entrar em vigor",
|
||||
"InstanceName": "Nome da Instância",
|
||||
@@ -862,12 +862,12 @@
|
||||
"Theme": "Tema",
|
||||
"ThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, o tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por Theme.Park",
|
||||
"EnableRssHelpText": "Será usado quando o Readarr procurar periodicamente lançamentos via RSS Sync",
|
||||
"HardlinkCopyFiles": "Vínculo real/Copiar arquivos",
|
||||
"MoveFiles": "Mover arquivos",
|
||||
"HardlinkCopyFiles": "Hardlink/Copiar Arquivos",
|
||||
"MoveFiles": "Mover Arquivos",
|
||||
"OnApplicationUpdate": "Na Atualização do Aplicativo",
|
||||
"OnApplicationUpdateHelpText": "Ao atualizar o aplicativo",
|
||||
"ChooseImportMethod": "Escolha o método de importação",
|
||||
"ClickToChangeReleaseGroup": "Clique para alterar o grupo do lançamento",
|
||||
"ClickToChangeReleaseGroup": "Clique para alterar o grupo de lançamento",
|
||||
"BypassIfAboveCustomFormatScore": "Ignorar se estiver acima da pontuação do formato personalizado",
|
||||
"BypassIfAboveCustomFormatScoreHelpText": "Ativar ignorar quando a versão tiver uma pontuação maior que a pontuação mínima configurada do formato personalizado",
|
||||
"BypassIfHighestQuality": "Ignorar se a qualidade mais alta",
|
||||
@@ -880,7 +880,7 @@
|
||||
"DataFutureBooks": "Monitorar livros que ainda não foram lançados",
|
||||
"DeleteFormatMessageText": "Tem certeza de que deseja excluir a tag de formato '{0}'?",
|
||||
"IncludeCustomFormatWhenRenamingHelpText": "'Incluir em {Formatos Personalizados} formato de renomeação'",
|
||||
"IndexerTagsHelpText": "Use este indexador apenas para autores com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os autores.",
|
||||
"IndexerTagsHelpText": "Use este indexador apenas para autores com pelo menos uma tag correspondente. Deixe em branco para usar com todos os autores.",
|
||||
"MinFormatScoreHelpText": "Pontuação mínima de formato personalizado permitida para download",
|
||||
"RecycleBinUnableToWriteHealthCheck": "Não é possível gravar na pasta da lixeira configurada: {0}. Certifique-se de que este caminho exista e seja gravável pelo usuário executando o Readarr",
|
||||
"Clone": "Clonar",
|
||||
@@ -921,7 +921,7 @@
|
||||
"ReplaceWithSpaceDashSpace": "Substituir com Espaço, Traço e Espaço",
|
||||
"DeleteRemotePathMapping": "Excluir Mapeamento de Caminho Remoto",
|
||||
"BlocklistReleases": "Lançamentos na lista de bloqueio",
|
||||
"CloneCondition": "Condição de clone",
|
||||
"CloneCondition": "Clonar Condição",
|
||||
"DeleteConditionMessageText": "Tem certeza de que deseja excluir a condição '{name}'?",
|
||||
"DeleteRemotePathMappingMessageText": "Tem certeza de que deseja excluir este mapeamento de caminho remoto?",
|
||||
"DeleteCondition": "Excluir condição",
|
||||
@@ -955,18 +955,18 @@
|
||||
"RemoveFailedDownloads": "Remover downloads com falha",
|
||||
"RemoveDownloadsAlert": "As configurações de remoção foram movidas para as configurações individuais do cliente de download na tabela acima.",
|
||||
"RemoveFailed": "Falha na remoção",
|
||||
"SetTags": "Definir etiquetas",
|
||||
"SetTags": "Definir tags",
|
||||
"Yes": "Sim",
|
||||
"ApplyTagsHelpTextHowToApplyAuthors": "Como aplicar etiquetas aos autores selecionados",
|
||||
"ApplyTagsHelpTextHowToApplyAuthors": "Como aplicar tags aos autores selecionados",
|
||||
"ManageDownloadClients": "Gerenciar clientes de download",
|
||||
"ManageImportLists": "Gerenciar listas de importação",
|
||||
"RemoveCompleted": "Remoção Concluída",
|
||||
"AutoAdd": "Adicionar automaticamente",
|
||||
"AutomaticAdd": "Adição Automática",
|
||||
"ApplyChanges": "Aplicar Mudanças",
|
||||
"ApplyTagsHelpTextAdd": "Adicionar: adicione as etiquetas à lista existente de etiquetas",
|
||||
"ApplyTagsHelpTextRemove": "Remover: remove as etiquetas inseridas",
|
||||
"ApplyTagsHelpTextReplace": "Substituir: Substitua as etiquetas pelas etiquetas inseridas (não digite nenhuma etiqueta para limpar todas as etiquetas)",
|
||||
"ApplyTagsHelpTextAdd": "Adicionar: adicione as tags à lista existente de tags",
|
||||
"ApplyTagsHelpTextRemove": "Remover: Remove as tags inseridas",
|
||||
"ApplyTagsHelpTextReplace": "Substituir: Substitua as tags pelas tags inseridas (não digite nenhuma tag para limpar todas as tags)",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Como aplicar tags aos clientes de download selecionados",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar tags às listas de importação selecionadas",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar tags aos indexadores selecionados",
|
||||
@@ -977,11 +977,11 @@
|
||||
"DeleteSelectedImportLists": "Excluir lista(s) de importação",
|
||||
"DeleteSelectedImportListsMessageText": "Tem certeza de que deseja excluir {count} lista(s) de importação selecionada(s)?",
|
||||
"DeleteSelectedIndexers": "Excluir indexador(es)",
|
||||
"DownloadClientTagHelpText": "Use este cliente de download apenas para autores com pelo menos uma etiqueta correspondente. Deixe em branco para usar com todos os autores.",
|
||||
"ExistingTag": "Etiqueta existente",
|
||||
"DownloadClientTagHelpText": "Use este cliente de download apenas para autores com pelo menos uma tag correspondente. Deixe em branco para usar com todos os autores.",
|
||||
"ExistingTag": "Tag existente",
|
||||
"No": "Não",
|
||||
"RemoveCompletedDownloads": "Remover downloads concluídos",
|
||||
"RemovingTag": "Removendo etiqueta",
|
||||
"RemovingTag": "Removendo tag",
|
||||
"SkipRedownloadHelpText": "Impede Readarr de tentar baixar versões alternativas para os itens removidos",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexadores com clientes de download inválidos: {0}.",
|
||||
"Activity": "Atividade",
|
||||
@@ -1011,10 +1011,12 @@
|
||||
"AutomaticUpdatesDisabledDocker": "As atualizações automáticas não têm suporte direto ao usar o mecanismo de atualização do Docker. Você precisará atualizar a imagem do contêiner fora de {appName} ou usar um script",
|
||||
"WouldYouLikeToRestoreBackup": "Gostaria de restaurar o backup '{name}'?",
|
||||
"AppUpdated": "{appName} Atualizado",
|
||||
"AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`, para obter as alterações mais recentes, você precisará recarregar {appName}",
|
||||
"AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`. Para obter as alterações mais recentes, você precisará recarregar {appName}",
|
||||
"ConnectionLost": "Conexão Perdida",
|
||||
"ConnectionLostToBackend": "{appName} perdeu sua conexão com o backend e precisará ser recarregado para restaurar a funcionalidade.",
|
||||
"ConnectionLostToBackend": "{appName} perdeu a conexão com o backend e precisará ser recarregado para restaurar a funcionalidade.",
|
||||
"CountAuthorsSelected": "{selectedCount} autor(es) selecionado(s)",
|
||||
"RecentChanges": "Mudanças Recentes",
|
||||
"WhatsNew": "O quê há de novo?"
|
||||
"WhatsNew": "O que há de novo?",
|
||||
"RemotePathMappingsInfo": "Raramente são necessários mapeamentos de caminho remoto, se {app} e seu cliente de download estiverem no mesmo sistema, é melhor combinar seus caminhos. Para obter mais informações, consulte o [wiki]({wikiLink}).",
|
||||
"ErrorLoadingContent": "Ocorreu um erro ao carregar este conteúdo"
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"Cancel": "Anulează",
|
||||
"CancelMessageText": "Sigur doriți să anulați această sarcină în așteptare?",
|
||||
"CertificateValidation": "Validarea certificatului",
|
||||
"CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS",
|
||||
"CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS. Nu schimbați dacă nu înțelegeți riscurile.",
|
||||
"ChangeHasNotBeenSavedYet": "Modificarea nu a fost încă salvată",
|
||||
"ChmodFolder": "chmod Folder",
|
||||
"ChmodFolderHelpText": "Octal, aplicat în timpul importului / redenumirii în dosare și fișiere media (fără biți de executare)",
|
||||
@@ -149,11 +149,11 @@
|
||||
"DeleteImportListMessageText": "Sigur doriți să ștergeți lista „{0}”?",
|
||||
"DeleteIndexer": "Ștergeți Indexer",
|
||||
"DeleteIndexerMessageText": "Sigur doriți să ștergeți indexatorul „{0}”?",
|
||||
"DeleteMetadataProfileMessageText": "Sigur doriți să ștergeți profilul de calitate {0}",
|
||||
"DeleteMetadataProfileMessageText": "Sigur doriți să ștergeți profilul de metadate '{name}'?",
|
||||
"DeleteNotification": "Ștergeți notificarea",
|
||||
"DeleteNotificationMessageText": "Sigur doriți să ștergeți notificarea „{0}”?",
|
||||
"DeleteQualityProfile": "Ștergeți profilul de calitate",
|
||||
"DeleteQualityProfileMessageText": "Sigur doriți să ștergeți profilul de calitate {0}",
|
||||
"DeleteQualityProfileMessageText": "Sigur doriți să ștergeți profilul de calitate '{name}'?",
|
||||
"DeleteReleaseProfile": "Ștergeți profilul de întârziere",
|
||||
"DeleteReleaseProfileMessageText": "Sigur doriți să ștergeți acest profil de întârziere?",
|
||||
"DeleteSelectedBookFiles": "Ștergeți fișierele film selectate",
|
||||
@@ -617,7 +617,7 @@
|
||||
"Authentication": "Autentificare",
|
||||
"ResetQualityDefinitions": "Resetare definitii de calitate",
|
||||
"ChooseImportMethod": "Alege modul de import",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Aplicații indisponibile datorită erorilor: {0}",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Notificări indisponibile datorită erorilor: {0}",
|
||||
"Small": "Mic",
|
||||
"Ui": "Interfață Grafica",
|
||||
"Events": "Evenimente",
|
||||
|
||||
@@ -80,23 +80,23 @@
|
||||
"DelayingDownloadUntilInterp": "Приостановить скачивание до {0} в {1}",
|
||||
"Delete": "Удалить",
|
||||
"DeleteBackup": "Удалить резервную копию",
|
||||
"DeleteBackupMessageText": "Вы уверены, что хотите удалить резервную копию '{0}'?",
|
||||
"DeleteBackupMessageText": "Вы уверены, что хотите удалить резервную копию '{name}'?",
|
||||
"DeleteDelayProfile": "Удалить профиль задержки",
|
||||
"DeleteDelayProfileMessageText": "Вы уверены, что хотите удалить этот профиль задержки?",
|
||||
"DeleteDownloadClient": "Удалить программу для скачивания",
|
||||
"DeleteDownloadClientMessageText": "Вы уверены, что хотите удалить программу для скачивания '{0}'?",
|
||||
"DeleteDownloadClientMessageText": "Вы уверены, что хотите удалить клиент загрузки '{name}'?",
|
||||
"DeleteEmptyFolders": "Удалить пустые папки",
|
||||
"DeleteEmptyFoldersHelpText": "Удалять пустые папки во время сканирования диска, а так же после удаления файлов фильмов",
|
||||
"DeleteImportListExclusion": "Удалить лист исключения для импорта",
|
||||
"DeleteImportListExclusionMessageText": "Вы уверены, что хотите удалить этот лист исключений?",
|
||||
"DeleteImportListMessageText": "Вы уверены, что хотите удалить список '{0}'?",
|
||||
"DeleteImportListExclusionMessageText": "Вы уверены, что хотите удалить это исключение из списка импорта?",
|
||||
"DeleteImportListMessageText": "Вы уверены, что хотите удалить список '{name}'?",
|
||||
"DeleteIndexer": "Удалить индексер",
|
||||
"DeleteIndexerMessageText": "Вы уверены что хотите удалить индексер '{0}'?",
|
||||
"DeleteIndexerMessageText": "Вы уверены что хотите удалить индексатор '{name}'?",
|
||||
"DeleteMetadataProfileMessageText": "Вы действительно хотите удалить профиль качества {0}",
|
||||
"DeleteNotification": "Удалить уведомление",
|
||||
"DeleteNotificationMessageText": "Вы уверены, что хотите удалить уведомление '{0}'?",
|
||||
"DeleteNotificationMessageText": "Вы уверены, что хотите удалить уведомление '{name}'?",
|
||||
"DeleteQualityProfile": "Удалить качественный профиль",
|
||||
"DeleteQualityProfileMessageText": "Вы действительно хотите удалить профиль качества {0}",
|
||||
"DeleteQualityProfileMessageText": "Вы уверены, что хотите удалить профиль качества '{name}'?",
|
||||
"DeleteReleaseProfile": "Удалить профиль задержки",
|
||||
"DeleteReleaseProfileMessageText": "Вы уверены, что хотите удалить этот профиль задержки?",
|
||||
"DeleteSelectedBookFiles": "Удалить выбранные файлы фильма",
|
||||
@@ -613,10 +613,10 @@
|
||||
"CustomFormats": "Настраиваемое форматирование",
|
||||
"CutoffFormatScoreHelpText": "Radarr перестанет скачивать фильмы после достижения указанного количества очков",
|
||||
"DeleteCustomFormat": "Удалить пользовательский формат",
|
||||
"DeleteCustomFormatMessageText": "Вы уверены что хотите удалить индексер '{0}'?",
|
||||
"DeleteCustomFormatMessageText": "Вы уверены, что хотите удалить пользовательский формат '{name}'?",
|
||||
"MinFormatScoreHelpText": "Минимальная оценка пользовательского формата для скачивания",
|
||||
"NegateHelpText": "Если отмечено, то настроенный формат не будет применён при условии {0}.",
|
||||
"ResetDefinitionTitlesHelpText": "Сбросить заголовки определений, а также значения",
|
||||
"ResetDefinitionTitlesHelpText": "Сбросить названия определений, а также значения",
|
||||
"ResetDefinitions": "Сбросить определения",
|
||||
"ResetTitles": "Сбросить заголовки",
|
||||
"ImportListMissingRoot": "Отсутствует корневая папка для импортирования списка(ов): {0}",
|
||||
@@ -634,7 +634,7 @@
|
||||
"ApiKeyValidationHealthCheckMessage": "Пожалуйста, обновите свой ключ API, чтобы он был длиной не менее {0} символов. Вы можете сделать это через настройки или файл конфигурации",
|
||||
"BlocklistReleaseHelpText": "Запрещает Lidarr автоматически получать этот релиз повторно",
|
||||
"NoEventsFound": "Событий не найдено",
|
||||
"RemoveSelectedItemQueueMessageText": "Вы действительно хотите удалить {0} из очереди?",
|
||||
"RemoveSelectedItemQueueMessageText": "Вы уверены, что хотите удалить 1 элемент из очереди?",
|
||||
"RemoveSelectedItems": "Удалить выбранные элементы",
|
||||
"RemoveSelectedItemsQueueMessageText": "Вы действительно хотите удалить {0} из очереди?",
|
||||
"ResetQualityDefinitions": "Сбросить определения качества",
|
||||
@@ -647,26 +647,26 @@
|
||||
"SetTags": "Установить теги",
|
||||
"Yes": "Да",
|
||||
"ApplyTagsHelpTextHowToApplyAuthors": "Как добавить ярлыки к выбранным фильмам",
|
||||
"DeleteSelectedIndexersMessageText": "Вы уверены что хотите удалить индексер '{0}'?",
|
||||
"DeleteSelectedIndexersMessageText": "Вы уверены, что хотите удалить {count} выбранных индексатора?",
|
||||
"BlocklistReleases": "Релиз из черного списка",
|
||||
"DeleteRemotePathMapping": "Редактировать расположение подключенной папки",
|
||||
"DeleteRemotePathMapping": "Удалить сопоставление удаленного пути",
|
||||
"Negated": "Отрицательный",
|
||||
"RedownloadFailed": "Неудачное скачивание",
|
||||
"RemoveCompleted": "Удаление завершено",
|
||||
"RemoveDownloadsAlert": "Настройки удаления были перенесены в отдельные настройки клиента загрузки выше.",
|
||||
"RemoveFailed": "Удаление не удалось",
|
||||
"RemoveSelectedItem": "Удалить выбранный элемент",
|
||||
"ApplyChanges": "Применять изменения",
|
||||
"ApplyTagsHelpTextAdd": "Добавить: добавить ярлыки к существующему списку",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Как добавить ярлыки к выбранным фильмам",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Как добавить ярлыки к выбранным фильмам",
|
||||
"ApplyTagsHelpTextRemove": "Убрать: Убрать введенные тэги",
|
||||
"ApplyTagsHelpTextReplace": "Заменить: Изменить существующие тэги на введенные тэги (оставьте пустым чтобы очистить все тэги)",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Как добавить ярлыки к выбранным фильмам",
|
||||
"DeleteSelectedDownloadClients": "Удалить программу для скачивания",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Вы уверены что хотите удалить индексер '{0}'?",
|
||||
"DeleteSelectedImportListsMessageText": "Вы уверены что хотите удалить индексер '{0}'?",
|
||||
"DeleteSelectedIndexers": "Удалить индексер",
|
||||
"ApplyChanges": "Применить изменения",
|
||||
"ApplyTagsHelpTextAdd": "Добавить: Добавьте теги в существующий список тегов",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Как применить теги к выбранным спискам импорта",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Как применить теги к выбранным индексаторам",
|
||||
"ApplyTagsHelpTextRemove": "Удалить: удалить введенные теги",
|
||||
"ApplyTagsHelpTextReplace": "Заменить: заменить теги введенными тегами (оставьте поле пустым, чтобы удалить все теги)",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Как применить теги к выбранным клиентам загрузки",
|
||||
"DeleteSelectedDownloadClients": "Удалить клиент(ы) загрузки",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Вы уверены, что хотите удалить {count} выбранных клиента загрузки?",
|
||||
"DeleteSelectedImportListsMessageText": "Вы уверены, что хотите удалить {count} выбранных списков импорта?",
|
||||
"DeleteSelectedIndexers": "Удалить индексатор(ы)",
|
||||
"DownloadClientTagHelpText": "Используйте этот индексатор только для фильмов с хотя бы одним совпадающим тегом. Оставьте пустым, чтобы использовать для всех фильмов.",
|
||||
"EditSelectedImportLists": "Редактировать выбранные списки импорта",
|
||||
"ExistingTag": "Существующий тэг",
|
||||
@@ -678,7 +678,7 @@
|
||||
"Activity": "Активность",
|
||||
"AddNew": "Добавить",
|
||||
"Medium": "Средний",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Все листы недоступны из-за ошибок",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Все уведомления недоступны из-за сбоев",
|
||||
"TotalSpace": "Общее сводное место",
|
||||
"Ui": "Пользовательский интерфейс",
|
||||
"Backup": "Резервное копирование",
|
||||
@@ -687,7 +687,7 @@
|
||||
"LastWriteTime": "Последнее время записи",
|
||||
"Library": "Библиотека",
|
||||
"NoResultsFound": "Нет результатов",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Листы недоступны из-за ошибок: {0}",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Уведомления недоступны из-за сбоев: {0}",
|
||||
"System": "Система",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Все результаты скрыты фильтром",
|
||||
"ConnectionLostToBackend": "Radarr потерял связь с сервером и его необходимо перезагрузить, чтобы восстановить работоспособность.",
|
||||
@@ -696,5 +696,36 @@
|
||||
"SomeResultsAreHiddenByTheAppliedFilter": "Некоторые результаты скрыты примененным фильтром",
|
||||
"WhatsNew": "Что нового?",
|
||||
"NextExecution": "Следующее выполнение",
|
||||
"Small": "Маленький"
|
||||
"Small": "Маленький",
|
||||
"DeleteRootFolder": "Удалить корневую папку",
|
||||
"ListWillRefreshEveryInterp": "Список будет обновляться каждые {0}",
|
||||
"ListRefreshInterval": "Интервал обновления списка",
|
||||
"RemoveCompletedDownloads": "Удалить завершенные загрузки",
|
||||
"RemoveFailedDownloads": "Удаление неудачных загрузок",
|
||||
"RemoveSelectedItemBlocklistMessageText": "Вы уверены, что хотите удалить выбранные элементы из списка заблокированных?",
|
||||
"ManageClients": "Управление клиентами",
|
||||
"ManageDownloadClients": "Менеджер клиентов загрузки",
|
||||
"ManageImportLists": "Управление списками импорта",
|
||||
"ManageIndexers": "Управление индексаторами",
|
||||
"DeleteSelectedImportLists": "Удалить списки импорта",
|
||||
"ManageLists": "Управление листами",
|
||||
"NoDownloadClientsFound": "Клиенты для загрузки не найдены",
|
||||
"NoImportListsFound": "Списки импорта не найдены",
|
||||
"NoHistoryBlocklist": "Нет истории блокировок",
|
||||
"ThereWasAnErrorLoadingThisItem": "Произошла ошибка при загрузке этого элемента",
|
||||
"ThereWasAnErrorLoadingThisPage": "Произошла ошибка при загрузке этой страницы",
|
||||
"SkipRedownload": "Пропустить повторное скачивание",
|
||||
"DeleteImportList": "Удалить список импорта",
|
||||
"AutomaticUpdatesDisabledDocker": "Автоматические обновления напрямую не поддерживаются при использовании механизма обновления Docker. Вам нужно будет обновить образ контейнера за пределами {AppName} или использовать скрипт",
|
||||
"EditSelectedIndexers": "Редактировать выбранный индексатор",
|
||||
"CloneCondition": "Условие клонирования",
|
||||
"DeleteCondition": "Удалить условие",
|
||||
"Implementation": "Реализация",
|
||||
"NoIndexersFound": "Индексаторы не найдены",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Индексаторы с недопустимыми клиентами загрузки: {0}.",
|
||||
"AutomaticAdd": "Автоматическое добавление",
|
||||
"ResetQualityDefinitionsMessageText": "Вы уверены, что хотите сбросить определения качества?",
|
||||
"DeleteRemotePathMappingMessageText": "Вы уверены, что хотите удалить это сопоставление удаленного пути?",
|
||||
"RemotePathMappingsInfo": "Сопоставление удаленных путей требуется крайне редко, если {app} и клиент загрузки находятся в одной системе, то лучше согласовать пути. Более подробную информацию можно найти в [wiki]({wikiLink}).",
|
||||
"WouldYouLikeToRestoreBackup": "Желаете восстановить резервную копию {name} ?"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"ChmodFolder": "修改文件夹权限",
|
||||
"ChmodFolderHelpText": "八进制,当导入和重命名媒体文件夹和文件时应用",
|
||||
"ChmodFolderHelpText": "八进制,当导入和重命名媒体文件夹和文件时应用(不带执行位)",
|
||||
"History": "历史记录",
|
||||
"Host": "主机",
|
||||
"PortNumber": "端口号",
|
||||
"PosterSize": "海报尺寸",
|
||||
"AddingTag": "添加标签",
|
||||
"AgeWhenGrabbed": "发布时长",
|
||||
"AgeWhenGrabbed": "年龄(在被抓取后)",
|
||||
"AlreadyInYourLibrary": "已经在你的库中",
|
||||
"AlternateTitles": "替代名称",
|
||||
"AlternateTitles": "备选标题",
|
||||
"Analytics": "分析",
|
||||
"AnalyticsEnabledHelpText": "发送匿名使用信息和错误信息给我们,这些信息包括您的浏览器信息,使用的Web界面,错误报告,操作系统及运行版本。我们会根据这些信息来调整功能和修复问题的优先级。",
|
||||
"AnalyticsEnabledHelpTextWarning": "需要重启才能生效",
|
||||
@@ -44,19 +44,19 @@
|
||||
"Clear": "清除",
|
||||
"ClickToChangeQuality": "点击修改质量",
|
||||
"ClientPriority": "客户端优先级",
|
||||
"CloneIndexer": "复制索引",
|
||||
"CloneIndexer": "复制索引器",
|
||||
"CloneProfile": "复制配置",
|
||||
"Close": "关闭",
|
||||
"Columns": "列",
|
||||
"CompletedDownloadHandling": "完成下载处理",
|
||||
"ConnectSettings": "连接设置",
|
||||
"Connections": "连接",
|
||||
"CopyUsingHardlinksHelpText": "拷贝文件时torrents文件还在做种中则使用硬链接",
|
||||
"CopyUsingHardlinksHelpText": "硬链接允许Readarr导入播放种子到剧集文件夹,而无需占用额外的磁盘空间或复制文件的整个内容。只有当源和目标在同一卷上时,硬链接才会起作用",
|
||||
"CopyUsingHardlinksHelpTextWarning": "有时候,文件锁可能会阻止对正在做种的文件进行重命名。您可以暂时禁用做种功能,并使用Radarr的重命名功能作为解决方案。",
|
||||
"CreateEmptyAuthorFoldersHelpText": "硬盘扫描过程中创建缺失的电影目录",
|
||||
"CreateGroup": "创建组",
|
||||
"CutoffHelpText": "一旦质量满足则Radarr不会再下载影片",
|
||||
"CutoffUnmet": "终止未满足",
|
||||
"CutoffUnmet": "未达截止条件",
|
||||
"DBMigration": "数据库迁移版本",
|
||||
"Dates": "日期",
|
||||
"DelayProfile": "延时配置",
|
||||
@@ -64,24 +64,24 @@
|
||||
"DelayingDownloadUntilInterp": "延时下载直到 {1} 在 {0} 之前 Delaying download until {0} at {1}",
|
||||
"Delete": "删除",
|
||||
"DeleteBackup": "删除备份",
|
||||
"DeleteBackupMessageText": "您确定要删除备份 '{0}' 吗?",
|
||||
"DeleteBackupMessageText": "您确定要删除备份“{name}”吗?",
|
||||
"DeleteDelayProfile": "删除延迟配置",
|
||||
"DeleteDelayProfileMessageText": "是否确实要删除此延迟配置文件?",
|
||||
"DeleteDelayProfileMessageText": "你确定要删除此延迟配置吗?",
|
||||
"DeleteDownloadClient": "删除下载客户端",
|
||||
"DeleteDownloadClientMessageText": "您确定要删除下载客户端 '{0}' 吗?",
|
||||
"DeleteDownloadClientMessageText": "你确定要删除下载客户端 “{name}” 吗?",
|
||||
"DeleteEmptyFolders": "删除空目录",
|
||||
"DeleteEmptyFoldersHelpText": "磁盘扫描过程中删除被移除的电影空目录",
|
||||
"DeleteImportListExclusion": "删除导入排除列表",
|
||||
"DeleteImportListExclusionMessageText": "你确定要删除这个导入排除列表吗?",
|
||||
"DeleteImportListMessageText": "您确定要删除列表 '{0}'?",
|
||||
"DeleteIndexer": "删除索引",
|
||||
"DeleteIndexerMessageText": "您确定要删除索引 '{0}'吗?",
|
||||
"DeleteMetadataProfileMessageText": "确定要删除元数据配置吗‘{0}’?",
|
||||
"DeleteImportListMessageText": "您确定要删除列表 “{name}” 吗?",
|
||||
"DeleteIndexer": "删除索引器",
|
||||
"DeleteIndexerMessageText": "您确定要删除索引器 “{name}” 吗?",
|
||||
"DeleteMetadataProfileMessageText": "您确定要删除元数据配置文件“{name}”吗?",
|
||||
"DeleteNotification": "删除消息推送",
|
||||
"DeleteNotificationMessageText": "您确定要删除推送 '{0}' 吗?",
|
||||
"DeleteQualityProfile": "删除电影质量配置",
|
||||
"DeleteQualityProfileMessageText": "确定要删除歌曲质量配置“{0}”",
|
||||
"DeleteReleaseProfile": "删除延迟配置",
|
||||
"DeleteNotificationMessageText": "您确定要删除消息推送 “{name}” 吗?",
|
||||
"DeleteQualityProfile": "删除质量配置",
|
||||
"DeleteQualityProfileMessageText": "你确定要删除质量配置 “{name}” 吗?",
|
||||
"DeleteReleaseProfile": "删除发布组配置",
|
||||
"DeleteReleaseProfileMessageText": "您确定要删除这个发行配置?",
|
||||
"DeleteSelectedBookFiles": "删除选择的电影文件",
|
||||
"DeleteSelectedBookFilesMessageText": "您确定要删除选择的电影文件吗?",
|
||||
@@ -145,7 +145,7 @@
|
||||
"ICalFeed": "iCal订阅地址",
|
||||
"ICalHttpUrlHelpText": "将此URL复制到您的客户端,如果您的浏览器支持webcal,请直接点击右侧订阅按钮",
|
||||
"ICalLink": "iCal链接",
|
||||
"IconForCutoffUnmet": "未满足停止监控的图标",
|
||||
"IconForCutoffUnmet": "未达截止条件的图标",
|
||||
"IconTooltip": "计划中",
|
||||
"IgnoredAddresses": "已忽略地址",
|
||||
"IgnoredHelpText": "如版本包含一个或多个条件则丢弃(无视大小写)",
|
||||
@@ -161,7 +161,7 @@
|
||||
"IncludeUnmonitored": "包含未监控的",
|
||||
"Indexer": "索引器",
|
||||
"IndexerPriority": "索引器优先级",
|
||||
"IndexerSettings": "搜刮器设置",
|
||||
"IndexerSettings": "索引器设置",
|
||||
"Indexers": "索引器",
|
||||
"Interval": "间隔",
|
||||
"IsCutoffCutoff": "截止",
|
||||
@@ -204,11 +204,11 @@
|
||||
"NamingSettings": "命名设置",
|
||||
"New": "新的",
|
||||
"NoBackupsAreAvailable": "无备份可用",
|
||||
"NoHistory": "无历史记录",
|
||||
"NoHistory": "无历史记录。",
|
||||
"NoLeaveIt": "不,就这样",
|
||||
"NoLimitForAnyRuntime": "不限制任何歌曲时长",
|
||||
"NoLimitForAnyRuntime": "不限制任何运行环境",
|
||||
"NoLogFiles": "没有日志文件",
|
||||
"NoMinimumForAnyRuntime": "歌曲时间没有最小限制",
|
||||
"NoMinimumForAnyRuntime": "运行环境没有最小限制",
|
||||
"NoUpdatesAreAvailable": "无可用更新",
|
||||
"None": "无",
|
||||
"NotificationTriggers": "通知触发器",
|
||||
@@ -216,7 +216,7 @@
|
||||
"OnHealthIssueHelpText": "健康度异常",
|
||||
"OnRenameHelpText": "重命名中",
|
||||
"OnUpgradeHelpText": "升级中",
|
||||
"OpenBrowserOnStart": "打开浏览器时启动",
|
||||
"OpenBrowserOnStart": "启动时打开浏览器",
|
||||
"Options": "选项",
|
||||
"Original": "原始的",
|
||||
"Overview": "概述",
|
||||
@@ -251,7 +251,7 @@
|
||||
"ReadarrSupportsAnyIndexerThatUsesTheNewznabStandardAsWellAsOtherIndexersListedBelow": "Radarr支持任何使用Newznab标准的搜刮器,以及下面列出的其他搜刮器。",
|
||||
"ReadarrTags": "Radarr标签",
|
||||
"Real": "真的",
|
||||
"Reason": "季",
|
||||
"Reason": "原因",
|
||||
"RecycleBinCleanupDaysHelpText": "设置为0关闭自动清理",
|
||||
"RecycleBinCleanupDaysHelpTextWarning": "回收站中的文件在超出选择的天数后会被自动清理",
|
||||
"RecycleBinHelpText": "影片文件会被移动到回收站以替代永久删除",
|
||||
@@ -276,11 +276,11 @@
|
||||
"RemoveSelected": "移除已选",
|
||||
"RemoveTagExistingTag": "已有标签",
|
||||
"RemoveTagRemovingTag": "移除标签",
|
||||
"RemovedFromTaskQueue": "从任务队列中移除",
|
||||
"RemovedFromTaskQueue": "从任务队列中删除",
|
||||
"RenameBooksHelpText": "如果重命名未启用,Radarr会使用现有文件名",
|
||||
"Reorder": "重新排序Reorder",
|
||||
"ReplaceIllegalCharacters": "替换非法字符",
|
||||
"RequiredHelpText": "发布的歌曲必须至少包含一个这些项目(不区分大小写)",
|
||||
"RequiredHelpText": "这个{0}条件必须匹配自定义格式才能应用。否则,一个{0}匹配就足够了。",
|
||||
"RequiredPlaceHolder": "添加新限制",
|
||||
"RescanAfterRefreshHelpTextWarning": "当没有设置为“总是”时,Radarr将不会自动检测文件的更改",
|
||||
"RescanAuthorFolderAfterRefresh": "刷新后重新扫描作者文件夹",
|
||||
@@ -298,7 +298,7 @@
|
||||
"RetryingDownloadInterp": "尝试重新下载“{0}”在“{1}”",
|
||||
"RootFolder": "根目录",
|
||||
"RootFolders": "根目录",
|
||||
"RssSyncIntervalHelpText": "间隔时间以分钟为单位,设置为0则关闭该功能(会停止所有歌曲的自动抓取下载)",
|
||||
"RssSyncIntervalHelpText": "间隔时间以分钟为单位,设置为0则关闭该功能(会停止所有剧集的自动抓取下载)",
|
||||
"SSLCertPassword": "SSL证书密码",
|
||||
"SSLCertPath": "SSL证书路径",
|
||||
"SSLPort": "SSL端口",
|
||||
@@ -317,7 +317,7 @@
|
||||
"ShortDateFormat": "短日期格式",
|
||||
"ShowCutoffUnmetIconHelpText": "终止监控条件未满足前为文件显示图标",
|
||||
"ShowDateAdded": "显示添加日期",
|
||||
"ShowMonitored": "显示监控中的歌曲",
|
||||
"ShowMonitored": "显示监控中的",
|
||||
"ShowMonitoredHelpText": "在海报下显示监控状态",
|
||||
"ShowPath": "显示路径",
|
||||
"ShowQualityProfile": "显示媒体质量配置",
|
||||
@@ -333,7 +333,7 @@
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "当 Readarr 无法从您的作者根文件夹中检测到空闲空间时使用",
|
||||
"SorryThatAuthorCannotBeFound": "对不起,未找到影片。",
|
||||
"SorryThatBookCannotBeFound": "对不起,未找到影片。",
|
||||
"Source": "源",
|
||||
"Source": "来源",
|
||||
"SourcePath": "来源路径",
|
||||
"SslCertPasswordHelpText": "pfx文件密码",
|
||||
"SslCertPasswordHelpTextWarning": "重启生效",
|
||||
@@ -355,13 +355,13 @@
|
||||
"Tasks": "任务",
|
||||
"TestAll": "测试全部",
|
||||
"TestAllClients": "测试全部客户端",
|
||||
"TestAllIndexers": "测试全部搜刮器",
|
||||
"TestAllIndexers": "测试全部索引器",
|
||||
"TestAllLists": "测试全部列表",
|
||||
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "这将适用于所有搜刮器,请遵循他们所制定的规则",
|
||||
"TimeFormat": "时间格式",
|
||||
"Title": "标题",
|
||||
"TorrentDelay": "Torrent延时",
|
||||
"TorrentDelayHelpText": "延迟几分钟等待获取洪流",
|
||||
"TorrentDelayHelpText": "延迟几分钟等待获取torrent",
|
||||
"Torrents": "种子",
|
||||
"TotalFileSize": "总文件体积",
|
||||
"UILanguage": "UI界面语言",
|
||||
@@ -383,7 +383,7 @@
|
||||
"UnableToLoadDownloadClientOptions": "无法加载下载客户端选项",
|
||||
"UnableToLoadDownloadClients": "无法加载下载客户端",
|
||||
"UnableToLoadGeneralSettings": "无法加载通用设置",
|
||||
"UnableToLoadHistory": "无法加载历史记录",
|
||||
"UnableToLoadHistory": "无法加载历史记录。",
|
||||
"UnableToLoadImportListExclusions": "无法加载排除列表",
|
||||
"UnableToLoadIndexerOptions": "无法加载搜刮器选项",
|
||||
"UnableToLoadIndexers": "无法加载搜刮器",
|
||||
@@ -405,7 +405,7 @@
|
||||
"UnmonitoredHelpText": "在iCal订阅中包含未监控的电影",
|
||||
"UpdateAll": "全部更新",
|
||||
"UpdateAutomaticallyHelpText": "自动下载并安装更新。你还可以在“系统:更新”中安装",
|
||||
"UpdateMechanismHelpText": "使用 Radarr 的内置更新程序或脚本",
|
||||
"UpdateMechanismHelpText": "使用Readarr内置的更新程序或脚本",
|
||||
"UpdateScriptPathHelpText": "自定义脚本的路径,该脚本处理获取的更新包并处理更新过程的其余部分",
|
||||
"Updates": "更新",
|
||||
"UpgradeAllowedHelpText": "如关闭,则质量不做升级",
|
||||
@@ -435,13 +435,13 @@
|
||||
"SslPortHelpTextWarning": "重启生效",
|
||||
"SslCertPathHelpTextWarning": "重启生效",
|
||||
"ProxyUsernameHelpText": "如果需要,您只需要输入用户名和密码。否则就让它们为空。",
|
||||
"MaintenanceRelease": "维护版本:修复错误及其他改进,参见Github提交 查看更多详情",
|
||||
"MaintenanceRelease": "维护发布:bug修复和其他改进。更多细节请参见Github提交历史",
|
||||
"DeleteBookFileMessageText": "您确认您想删除吗?",
|
||||
"ApiKeyHelpTextWarning": "需重启以生效",
|
||||
"Actions": "动作",
|
||||
"AddMissing": "添加丢失项",
|
||||
"AddNewItem": "添加新项目",
|
||||
"DeleteRootFolderMessageText": "您确定要删除索引 '{0}'吗?",
|
||||
"DeleteRootFolderMessageText": "您确定要删除根文件夹“{name}”吗?",
|
||||
"Progress": "进度",
|
||||
"Publisher": "发布者",
|
||||
"Series": "节目",
|
||||
@@ -454,7 +454,7 @@
|
||||
"NotAvailable": "不可用",
|
||||
"NotMonitored": "未监控的",
|
||||
"OutputPath": "输出路径",
|
||||
"ReleaseTitle": "发布标题",
|
||||
"ReleaseTitle": "发行版标题",
|
||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "电影目录 '{0}' 及所有内容都会被删除。",
|
||||
"Today": "今天",
|
||||
"Tomorrow": "明天",
|
||||
@@ -509,7 +509,7 @@
|
||||
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "没有索引器开启自动搜索,Radarr不会提供任何自动搜索结果",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "由于最近的索引器错误,所有支持搜索的索引器暂时不可用",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,Radarr 不会提供任何手动搜索结果",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有启用交互式搜索的索引器,Readarr将不提供任何交互式搜索结果",
|
||||
"IndexersSettingsSummary": "搜刮器和发布版本限制",
|
||||
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
|
||||
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
|
||||
@@ -563,7 +563,7 @@
|
||||
"General": "通用",
|
||||
"HealthNoIssues": "您的设置没有问题",
|
||||
"ImportListStatusCheckSingleClientMessage": "列表因错误不可用:{0}",
|
||||
"IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低),默认25。当资源连接中断时寻找同等资源时使用,否则Radarr将依旧使用已启用的索引器进行RSS同步并搜索",
|
||||
"IndexerPriorityHelpText": "索引器优先级从1(最高)到50(最低)。默认值:25。在抓取版本时,Readarr将使用所有启用的索引器进行RSS同步和搜索。",
|
||||
"Lists": "列表",
|
||||
"MediaManagement": "媒体管理",
|
||||
"Metadata": "元数据",
|
||||
@@ -588,7 +588,7 @@
|
||||
"BackupIntervalHelpText": "备份 Lidarr 数据库和设置的时间间隔",
|
||||
"MetadataProfile": "元数据配置",
|
||||
"MetadataProfiles": "元数据配置",
|
||||
"InstanceName": "中文",
|
||||
"InstanceName": "应用名称",
|
||||
"InstanceNameHelpText": "选项卡及日志应用名称",
|
||||
"AddList": "添加列表",
|
||||
"BookList": "书籍清单",
|
||||
@@ -736,10 +736,10 @@
|
||||
"RefreshAuthor": "刷新作者",
|
||||
"RefreshBook": "刷新书籍",
|
||||
"RefreshInformation": "刷新信息",
|
||||
"ReleaseProfiles": "刷新配置文件",
|
||||
"ReleaseProfiles": "发行版概要",
|
||||
"RenameBooks": "重命名书籍",
|
||||
"RenameFiles": "重命名文件",
|
||||
"RestartRequiredHelpTextWarning": "需要重新启动才能生效",
|
||||
"RestartRequiredHelpTextWarning": "需重启以生效",
|
||||
"SearchForAllMissingBooks": "搜索所有丢失的书籍",
|
||||
"SendMetadataToCalibre": "发送元数据至Calibre",
|
||||
"SeriesNumber": "系列编号",
|
||||
@@ -780,10 +780,10 @@
|
||||
"ChownGroup": "修改组权限",
|
||||
"ContinuingAllBooksDownloaded": "仍在继续(所有书籍已下载)",
|
||||
"EnableProfile": "启用配置文件",
|
||||
"IfYouDontAddAnImportListExclusionAndTheAuthorHasAMetadataProfileOtherThanNoneThenThisBookMayBeReaddedDuringTheNextAuthorRefresh": "如果您不添加导入列表例外,并且作者元数据配置不是“无”,那这本书可能会在下次作者刷新是重新添加",
|
||||
"IfYouDontAddAnImportListExclusionAndTheAuthorHasAMetadataProfileOtherThanNoneThenThisBookMayBeReaddedDuringTheNextAuthorRefresh": "如果您不添加导入列表排除,并且作者有除“无”之外的元数据配置文件,那么这本书可能会在下一次作者刷新期间重新添加。",
|
||||
"IndexerIdHelpTextWarning": "使用带有首字母的特定索引器可能会导致复制版本被抓取",
|
||||
"MassBookSearchWarning": "您确定要对{0}本书进行批量书籍搜索吗?",
|
||||
"MediaManagementSettingsSummary": "命名、文件管理设置、根目录文件夹",
|
||||
"MediaManagementSettingsSummary": "命名,文件管理和根文件夹设置",
|
||||
"MetadataConsumers": "用户元数据",
|
||||
"MetadataProfileIdHelpText": "元数据配置文件列表项应添加",
|
||||
"MetadataSettingsSummary": "在导入书籍或刷新作者时创建元数据文件",
|
||||
@@ -817,7 +817,7 @@
|
||||
"HideBooks": "隐藏书籍",
|
||||
"HostHelpText": "Calibre内容服务器主机",
|
||||
"IgnoredMetaHelpText": "如果书籍包含一个/多个术语(不区分大小写)将被忽略",
|
||||
"ImportListExclusions": "导入列表例外",
|
||||
"ImportListExclusions": "导入排除列表",
|
||||
"ImportLists": "导入列表",
|
||||
"ISBN": "ISBN",
|
||||
"IsCalibreLibraryHelpText": "使用Calibre内容服务器操作库",
|
||||
@@ -831,7 +831,7 @@
|
||||
"IsShowingMonitoredUnmonitorSelected": "未监控选中的",
|
||||
"ItsEasyToAddANewAuthorOrBookJustStartTypingTheNameOfTheItemYouWantToAdd": "添加新作者或书籍很容易,只需开始输入要添加的条目的名称",
|
||||
"LatestBook": "最新书籍",
|
||||
"LibraryHelpText": "Calibre内容服务器库名 默认留空",
|
||||
"LibraryHelpText": "校准内容服务器库名称。默认为空。",
|
||||
"ListsSettingsSummary": "导入列表",
|
||||
"AddImportListExclusionHelpText": "通过导入列表或作者刷新 防止书籍添加到Readarr",
|
||||
"ManualDownload": "手动下载",
|
||||
@@ -856,8 +856,8 @@
|
||||
"CollapseMultipleBooksHelpText": "折叠在同日发行的多本书籍",
|
||||
"ASIN": "亚马逊标准识别码",
|
||||
"NoTagsHaveBeenAddedYet": "尚未添加标签,添加标签以链接具有延迟配置文件、限制或通知的作者。单击{0}以了解有关Readarr中标签的更多信息。",
|
||||
"BindAddressHelpText": "有效的 IPv4 地址、localhost、或以'*'代表所有接口",
|
||||
"ApplicationURL": "程序URL",
|
||||
"BindAddressHelpText": "有效的 IP 地址、localhost、或以'*'代表所有接口",
|
||||
"ApplicationURL": "应用程序 URL",
|
||||
"ApplicationUrlHelpText": "此应用的外部URL,包含 http(s)://、端口和基本URL",
|
||||
"ClickToChangeReleaseGroup": "点击修改发布组",
|
||||
"MoveFiles": "移动文件",
|
||||
@@ -865,7 +865,7 @@
|
||||
"OnApplicationUpdateHelpText": "程序更新时",
|
||||
"ChooseImportMethod": "选择导入模式",
|
||||
"HardlinkCopyFiles": "硬链接/复制文件",
|
||||
"ThemeHelpText": "更改程序界面主题,“自动”主题将根据您的操作系统主题来设置浅色或深色模式。灵感来自Theme.Park",
|
||||
"ThemeHelpText": "改变应用界面主题,选择“自动”主题会通过操作系统主题来自适应白天黑夜模式。(受Theme.Park启发)",
|
||||
"Theme": "主题",
|
||||
"EnableRssHelpText": "当Radarr定期通过RSS同步查找发布时使用",
|
||||
"BypassIfAboveCustomFormatScore": "若高于自定义格式分数则绕过",
|
||||
@@ -878,8 +878,8 @@
|
||||
"CustomFormats": "自定义命名格式",
|
||||
"CutoffFormatScoreHelpText": "一旦自定义格式分数满足则Radarr不会再下载影片",
|
||||
"DeleteCustomFormat": "删除自定义命名格式",
|
||||
"DeleteCustomFormatMessageText": "是否确实要删除条件“{0}”?",
|
||||
"DeleteFormatMessageText": "你确定要删除格式标签 “{0}” 吗?",
|
||||
"DeleteCustomFormatMessageText": "您确定要删除自定义格式“{name}”吗?",
|
||||
"DeleteFormatMessageText": "您确定要删除格式标签 '{0}' 吗?",
|
||||
"Formats": "格式",
|
||||
"ImportListMissingRoot": "在导入列表中缺少根目录文件夹",
|
||||
"ImportListMultipleMissingRoots": "导入列表中缺失多个根目录文件夹",
|
||||
@@ -901,7 +901,7 @@
|
||||
"DataFutureBooks": "监控尚未发布的书籍",
|
||||
"ExportCustomFormat": "已有自定义格式",
|
||||
"ResetDefinitionTitlesHelpText": "重置定义标题和值",
|
||||
"DeleteRemotePathMappingMessageText": "是否确实要删除此远程路径映射?",
|
||||
"DeleteRemotePathMappingMessageText": "你确定要删除此远程路径映射吗?",
|
||||
"ListRefreshInterval": "列表刷新间隔",
|
||||
"CloneCondition": "克隆条件",
|
||||
"DeleteCondition": "删除条件",
|
||||
@@ -911,9 +911,9 @@
|
||||
"ListWillRefreshEveryInterp": "列表将每 {0} 刷新一次",
|
||||
"ShowAdvanced": "显示高级",
|
||||
"ShownClickToHide": "显示,点击隐藏",
|
||||
"RemoveSelectedItemQueueMessageText": "您确定要从队列中删除 1 项吗?",
|
||||
"RemoveSelectedItemQueueMessageText": "您确定要从队列中删除一个项目吗?",
|
||||
"RemoveSelectedItemBlocklistMessageText": "您确定要从阻止列表中删除所选项目吗?",
|
||||
"RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除 {0} 个项目吗?",
|
||||
"RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除{0}个项目吗?",
|
||||
"ResetQualityDefinitionsMessageText": "您确定要重置质量定义吗?",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "如何将标签应用到已选择的下载客户端",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "如何将标签应用到已选择的导入列表",
|
||||
@@ -942,5 +942,81 @@
|
||||
"NoChange": "无修改",
|
||||
"Negated": "无效的",
|
||||
"No": "否",
|
||||
"ResetQualityDefinitions": "重置质量定义"
|
||||
"ResetQualityDefinitions": "重置质量定义",
|
||||
"AppUpdated": "{appName} 升级",
|
||||
"ConnectionLost": "连接丢失",
|
||||
"ConnectionLostReconnect": "{appName} 将会尝试自动连接,您也可以点击下方的重新加载。",
|
||||
"ConnectionLostToBackend": "{appName}失去了与后端的连接,需要重新加载以恢复功能。",
|
||||
"DeleteRemotePathMapping": "删除远程路径映射",
|
||||
"AutomaticUpdatesDisabledDocker": "不支持在使用 Docker 容器时直接升级。你需要升级 {appName} 容器镜像或使用脚本(script)",
|
||||
"ColonReplacement": "替换冒号",
|
||||
"Clone": "复制",
|
||||
"ErrorLoadingContent": "加载此内容时出错",
|
||||
"ReplaceWithSpaceDash": "替换为空格破折号",
|
||||
"ReplaceWithDash": "替换为破折号",
|
||||
"ReplaceWithSpaceDashSpace": "替换为空格破折号空格",
|
||||
"Large": "大",
|
||||
"IndexerDownloadClientHealthCheckMessage": "有无效下载客户端的索引器:{0}。",
|
||||
"RecentChanges": "最近修改",
|
||||
"RedownloadFailed": "重新下载失败",
|
||||
"SomeResultsAreHiddenByTheAppliedFilter": "部分结果已被过滤隐藏",
|
||||
"NoResultsFound": "无结果",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "由于故障,所有通知都不可用",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "由于失败导致通知不可用:{0}",
|
||||
"Medium": "中",
|
||||
"RemoveDownloadsAlert": "移除设置被移至上表中的单个下载客户端设置。",
|
||||
"Small": "小",
|
||||
"Ui": "UI",
|
||||
"SmartReplace": "智能替换",
|
||||
"WhatsNew": "什么是新的?",
|
||||
"BlocklistReleaseHelpText": "防止 Readarr 再次自动抓取这些文件",
|
||||
"AppUpdatedVersion": "{appName}已更新为版本` {version}`,为了获得最新的更改,您需要重新加载{appName}",
|
||||
"BypassIfAboveCustomFormatScoreHelpText": "当抓取发布版本的分数高于配置的最低自定义格式分数时跳过延时",
|
||||
"CountAuthorsSelected": "已选择{selectedCount}个作者",
|
||||
"CountDownloadClientsSelected": "{selectedCount}下载客户端已选中",
|
||||
"CountImportListsSelected": "{selectedCount}导入列表已选中",
|
||||
"DeleteSelectedDownloadClientsMessageText": "您确定要删除{count}选定的下载客户端吗?",
|
||||
"FreeSpace": "剩余空间",
|
||||
"Implementation": "执行",
|
||||
"LastDuration": "上一次用时",
|
||||
"NextExecution": "下一次执行",
|
||||
"System": "系统",
|
||||
"WouldYouLikeToRestoreBackup": "是否要还原备份“{name}”?",
|
||||
"NoImportListsFound": "未找到导入列表",
|
||||
"NoIndexersFound": "未找到索引器",
|
||||
"NoMissingItems": "没有缺失的项目",
|
||||
"SkipRedownloadHelpText": "阻止 Readarr 尝试下载已删除项目的替代版本",
|
||||
"ApplyTagsHelpTextHowToApplyAuthors": "如何将标签应用到选定的作者",
|
||||
"AutomaticAdd": "自动添加",
|
||||
"BypassIfHighestQualityHelpText": "当发布版本在质量配置文件中具有最高启用质量时,跳过延迟",
|
||||
"DeleteSelectedImportListsMessageText": "您确定要删除选定的{count}导入列表吗?",
|
||||
"MinimumCustomFormatScoreHelpText": "跳过首选协议延迟所需的最低自定义格式分数",
|
||||
"NoDownloadClientsFound": "找不到下载客户端",
|
||||
"NoCutoffUnmetItems": "没有未满足的截止项目",
|
||||
"RemoveCompletedDownloads": "删除已完成的下载",
|
||||
"RemoveFailedDownloads": "删除失败的下载",
|
||||
"TotalSpace": "总空间",
|
||||
"DashOrSpaceDashDependingOnName": "破折号或空格破折号取决于名字",
|
||||
"CountIndexersSelected": "{selectedCount}索引器已选中",
|
||||
"DeleteConditionMessageText": "您确定要删除条件“{name}”吗?",
|
||||
"DeleteFormat": "删除格式",
|
||||
"DownloadClientTagHelpText": "仅对具有至少一个匹配标签的作者使用此下载客户端。留空以供所有作者使用。",
|
||||
"DeleteSelectedIndexersMessageText": "您确定要删除{count}选定的索引器吗?",
|
||||
"Events": "事件",
|
||||
"Location": "位置",
|
||||
"ManageLists": "管理列表",
|
||||
"ManageClients": "管理客户端",
|
||||
"ManageIndexers": "管理索引器",
|
||||
"ManageImportLists": "管理导入列表",
|
||||
"ManageDownloadClients": "管理下载客户端",
|
||||
"RemotePathMappingsInfo": "很少需要远程路径映射,如果{app}和你的下载客户端在同一个系统上,最好匹配你的路径。有关更多信息,请参阅[wiki]({wikiink})。",
|
||||
"Bookshelf": "书架",
|
||||
"ApplyChanges": "应用更改",
|
||||
"Activity": "活动",
|
||||
"Backup": "备份",
|
||||
"AddNew": "添加新的",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "根据过滤条件所有结果已隐藏",
|
||||
"LastWriteTime": "最后写入时间",
|
||||
"LastExecution": "上一次执行",
|
||||
"Library": "库"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Memory;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace NzbDrone.Core.MediaCover
|
||||
@@ -23,9 +22,6 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
_enabled = true;
|
||||
|
||||
// More conservative memory allocation
|
||||
SixLabors.ImageSharp.Configuration.Default.MemoryAllocator = new SimpleGcMemoryAllocator();
|
||||
|
||||
// Thumbnails don't need super high quality
|
||||
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using LazyCache;
|
||||
using LazyCache.Providers;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -18,6 +19,7 @@ using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Http;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource.Goodreads;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace NzbDrone.Core.MetadataSource.BookInfo
|
||||
{
|
||||
@@ -78,7 +80,7 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
|
||||
|
||||
var httpResponse = _httpClient.Get<RecentUpdatesResource>(httpRequest);
|
||||
|
||||
if (httpResponse.Resource.Limited)
|
||||
if (httpResponse.Resource == null || httpResponse.Resource.Limited)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -469,17 +471,17 @@ namespace NzbDrone.Core.MetadataSource.BookInfo
|
||||
|
||||
private List<Book> MapSearchResult(List<int> ids)
|
||||
{
|
||||
HttpRequest httpRequest;
|
||||
HttpResponse<BulkBookResource> httpResponse;
|
||||
|
||||
while (true)
|
||||
{
|
||||
httpRequest = _requestBuilder.GetRequestBuilder().Create()
|
||||
.SetSegment("route", $"book/bulk")
|
||||
var httpRequest = _requestBuilder.GetRequestBuilder().Create()
|
||||
.SetSegment("route", "book/bulk")
|
||||
.SetHeader("Content-Type", "application/json")
|
||||
.Build();
|
||||
|
||||
httpRequest.SetContent(ids.ToJson());
|
||||
httpRequest.ContentSummary = ids.ToJson(Formatting.None);
|
||||
|
||||
httpRequest.AllowAutoRedirect = true;
|
||||
httpRequest.SuppressHttpError = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net.Http;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Notifications.Webhook;
|
||||
@@ -14,10 +15,12 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
{
|
||||
private const string URL = "https://notifiarr.com";
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NotifiarrProxy(IHttpClient httpClient)
|
||||
public NotifiarrProxy(IHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SendNotification(WebhookPayload payload, NotifiarrSettings settings)
|
||||
@@ -47,12 +50,17 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
switch ((int)responseCode)
|
||||
{
|
||||
case 401:
|
||||
_logger.Error("HTTP 401 - API key is invalid");
|
||||
throw new NotifiarrException("API key is invalid");
|
||||
case 400:
|
||||
throw new NotifiarrException("Unable to send notification. Ensure Readarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
// 400 responses shouldn't be treated as an actual error because it's a misconfiguration
|
||||
// between Readarr and Notifiarr for a specific event, but shouldn't stop all events.
|
||||
_logger.Error("HTTP 400 - Unable to send notification. Ensure Readarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
break;
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
_logger.Error("Unable to send notification. Service Unavailable");
|
||||
throw new NotifiarrException("Unable to send notification. Service Unavailable", ex);
|
||||
case 520:
|
||||
case 521:
|
||||
@@ -61,6 +69,7 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
case 524:
|
||||
throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex);
|
||||
default:
|
||||
_logger.Error(ex, "Unknown HTTP Error - Unable to send notification");
|
||||
throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace NzbDrone.Core.Notifications
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override List<NotificationDefinition> Active()
|
||||
{
|
||||
return base.Active().Where(c => c.Enable).ToList();
|
||||
}
|
||||
|
||||
public List<INotification> OnGrabEnabled(bool filterBlockedNotifications = true)
|
||||
{
|
||||
if (filterBlockedNotifications)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user