mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-05 13:21:25 -05:00
Compare commits
44 Commits
v5.18.2.96
...
v5.19.3.97
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36c66deb4b | ||
|
|
edec432244 | ||
|
|
554e15d438 | ||
|
|
553645a07c | ||
|
|
7de7e83c5b | ||
|
|
b7a46bedb0 | ||
|
|
0925769377 | ||
|
|
72244362fe | ||
|
|
c6526c34e9 | ||
|
|
efa2913dbc | ||
|
|
35c22a4ffa | ||
|
|
66d96e21da | ||
|
|
36d4e9e6cd | ||
|
|
7189d7b15c | ||
|
|
6e80113987 | ||
|
|
bb8a0dda63 | ||
|
|
525ed65687 | ||
|
|
3fbccc6af3 | ||
|
|
8e10eecfac | ||
|
|
a3b1512552 | ||
|
|
d375b5ffbe | ||
|
|
884abc0368 | ||
|
|
f8da7aae03 | ||
|
|
c165118d4d | ||
|
|
b3dd571a92 | ||
|
|
dd900eb739 | ||
|
|
66aae0c91c | ||
|
|
d888a0a2b3 | ||
|
|
cb5416a18c | ||
|
|
7977e0be05 | ||
|
|
cd836fef38 | ||
|
|
b0bfbe767c | ||
|
|
528b93dabe | ||
|
|
1edcbee5e1 | ||
|
|
8853dced9f | ||
|
|
c7aa1bae5e | ||
|
|
405ae77070 | ||
|
|
6236bc9b4f | ||
|
|
743c977e5b | ||
|
|
c0e5646f07 | ||
|
|
10094b4e66 | ||
|
|
d923406f08 | ||
|
|
69a9c72286 | ||
|
|
55b9477a01 |
@@ -87,4 +87,4 @@ This project is also supported by DigitalOcean
|
||||
### License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2024
|
||||
* Copyright 2010-2025
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.18.2'
|
||||
majorVersion: '5.19.3'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
15
docs.sh
15
docs.sh
@@ -1,13 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
FRAMEWORK="net6.0"
|
||||
PLATFORM=$1
|
||||
ARCHITECTURE="${2:-x64}"
|
||||
|
||||
if [ "$PLATFORM" = "Windows" ]; then
|
||||
RUNTIME="win-x64"
|
||||
RUNTIME="win-$ARCHITECTURE"
|
||||
elif [ "$PLATFORM" = "Linux" ]; then
|
||||
RUNTIME="linux-x64"
|
||||
RUNTIME="linux-$ARCHITECTURE"
|
||||
elif [ "$PLATFORM" = "Mac" ]; then
|
||||
RUNTIME="osx-x64"
|
||||
RUNTIME="osx-$ARCHITECTURE"
|
||||
else
|
||||
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
|
||||
echo "Platform must be provided as first argument: Windows, Linux or Mac"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -35,7 +40,7 @@ dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p
|
||||
dotnet new tool-manifest
|
||||
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
|
||||
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/$application" v3 &
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v3 &
|
||||
|
||||
sleep 45
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import TmdbRating from 'Components/TmdbRating';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import TraktRating from 'Components/TraktRating';
|
||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import { icons, kinds, sizes, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
@@ -753,11 +753,15 @@ class MovieDetails extends Component {
|
||||
<InteractiveImportModal
|
||||
isOpen={isInteractiveImportModalOpen}
|
||||
movieId={id}
|
||||
modalTitle={translate('ManageFiles')}
|
||||
title={title}
|
||||
folder={path}
|
||||
initialSortKey="relativePath"
|
||||
initialSortDirection={sortDirections.ASCENDING}
|
||||
showMovie={false}
|
||||
allowMovieChange={false}
|
||||
showFilterExistingFiles={true}
|
||||
showDelete={true}
|
||||
showImportMode={false}
|
||||
modalTitle={translate('ManageFiles')}
|
||||
onModalClose={this.onInteractiveImportModalClose}
|
||||
/>
|
||||
|
||||
|
||||
@@ -92,6 +92,19 @@ function MovieDetailsLinks(props: MovieDetailsLinksProps) {
|
||||
MDBList
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://www.blu-ray.com/search/?quicksearch=1&quicksearch_keyword=${imdbId}§ion=theatrical`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Blu-ray
|
||||
</Label>
|
||||
</Link>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import MediaInfoProps from 'typings/MediaInfo';
|
||||
import formatBitrate from 'Utilities/Number/formatBitrate';
|
||||
import getEntries from 'Utilities/Object/getEntries';
|
||||
|
||||
function MediaInfo(props: MediaInfoProps) {
|
||||
@@ -16,9 +17,19 @@ function MediaInfo(props: MediaInfoProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionListItem key={key} title={title} data={props[key]} />
|
||||
);
|
||||
if (key === 'audioBitrate' || key === 'videoBitrate') {
|
||||
return (
|
||||
<DescriptionListItem
|
||||
key={key}
|
||||
title={title}
|
||||
data={
|
||||
<span title={value.toString()}>{formatBitrate(value)}</span>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <DescriptionListItem key={key} title={title} data={value} />;
|
||||
})}
|
||||
</DescriptionList>
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
display: flex;
|
||||
color: var(--helpTextColor);
|
||||
|
||||
.icon {
|
||||
margin-top: 3px;
|
||||
.identifier {
|
||||
margin-top: 8px;
|
||||
margin-right: 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
interface CssExports {
|
||||
'footNote': string;
|
||||
'groups': string;
|
||||
'icon': string;
|
||||
'identifier': string;
|
||||
'namingSelect': string;
|
||||
'namingSelectContainer': string;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useCallback, useState } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
@@ -10,7 +9,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { icons, sizes } from 'Helpers/Props';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import NamingConfig from 'typings/Settings/NamingConfig';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import NamingOption from './NamingOption';
|
||||
@@ -88,32 +87,32 @@ const fileNameTokens = [
|
||||
];
|
||||
|
||||
const movieTokens = [
|
||||
{ token: '{Movie Title}', example: "Movie's Title", footNote: true },
|
||||
{ token: '{Movie Title:DE}', example: 'Titel des Films', footNote: true },
|
||||
{ token: '{Movie CleanTitle}', example: 'Movies Title', footNote: true },
|
||||
{ token: '{Movie Title}', example: "Movie's Title", footNotes: '1' },
|
||||
{ token: '{Movie Title:DE}', example: 'Titel des Films', footNotes: '1' },
|
||||
{ token: '{Movie CleanTitle}', example: 'Movies Title', footNotes: '1' },
|
||||
{
|
||||
token: '{Movie CleanTitle:DE}',
|
||||
example: 'Titel des Films',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie TitleThe}', example: "Movie's Title, The", footNote: true },
|
||||
{ token: '{Movie TitleThe}', example: "Movie's Title, The", footNotes: '1' },
|
||||
{
|
||||
token: '{Movie CleanTitleThe}',
|
||||
example: 'Movies Title, The',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας', footNote: true },
|
||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας', footNotes: '1' },
|
||||
{
|
||||
token: '{Movie CleanOriginalTitle}',
|
||||
example: 'Τίτλος ταινίας',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
|
||||
{ token: '{Movie TitleFirstCharacter:DE}', example: 'T' },
|
||||
{
|
||||
token: '{Movie Collection}',
|
||||
example: 'The Movie Collection',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie Certification}', example: 'R' },
|
||||
{ token: '{Release Year}', example: '2009' },
|
||||
@@ -131,12 +130,21 @@ const qualityTokens = [
|
||||
|
||||
const mediaInfoTokens = [
|
||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]', footNote: true },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]', footNotes: '1' },
|
||||
|
||||
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
|
||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]', footNote: true },
|
||||
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]', footNote: true },
|
||||
{
|
||||
token: '{MediaInfo AudioLanguages}',
|
||||
example: '[EN+DE]',
|
||||
footNotes: '1,2',
|
||||
},
|
||||
{
|
||||
token: '{MediaInfo AudioLanguagesAll}',
|
||||
example: '[EN]',
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]', footNotes: '1' },
|
||||
|
||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
|
||||
@@ -146,11 +154,11 @@ const mediaInfoTokens = [
|
||||
];
|
||||
|
||||
const releaseGroupTokens = [
|
||||
{ token: '{Release Group}', example: 'Rls Grp', footNote: true },
|
||||
{ token: '{Release Group}', example: 'Rls Grp', footNotes: '1' },
|
||||
];
|
||||
|
||||
const editionTokens = [
|
||||
{ token: '{Edition Tags}', example: 'IMAX', footNote: true },
|
||||
{ token: '{Edition Tags}', example: 'IMAX', footNotes: '1' },
|
||||
];
|
||||
|
||||
const customFormatTokens = [
|
||||
@@ -287,13 +295,13 @@ function NamingModal(props: NamingModalProps) {
|
||||
|
||||
<FieldSet legend={translate('Movie')}>
|
||||
<div className={styles.groups}>
|
||||
{movieTokens.map(({ token, example, footNote }) => {
|
||||
{movieTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -303,7 +311,7 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('MovieFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
@@ -346,13 +354,13 @@ function NamingModal(props: NamingModalProps) {
|
||||
|
||||
<FieldSet legend={translate('MediaInfo')}>
|
||||
<div className={styles.groups}>
|
||||
{mediaInfoTokens.map(({ token, example, footNote }) => {
|
||||
{mediaInfoTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -362,20 +370,25 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('MediaInfoFootNote')} />
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<sup className={styles.identifier}>2</sup>
|
||||
<InlineMarkdown data={translate('MediaInfoFootNote2')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('ReleaseGroup')}>
|
||||
<div className={styles.groups}>
|
||||
{releaseGroupTokens.map(({ token, example, footNote }) => {
|
||||
{releaseGroupTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -385,20 +398,20 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('ReleaseGroupFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('Edition')}>
|
||||
<div className={styles.groups}>
|
||||
{editionTokens.map(({ token, example, footNote }) => {
|
||||
{editionTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -408,7 +421,7 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('EditionFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
padding: 6px;
|
||||
background-color: var(--popoverBodyBackgroundColor);
|
||||
|
||||
.footNote {
|
||||
.footNotes {
|
||||
padding: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'example': string;
|
||||
'footNote': string;
|
||||
'footNotes': string;
|
||||
'isFullFilename': string;
|
||||
'large': string;
|
||||
'lower': string;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useCallback } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { Size } from 'Helpers/Props/sizes';
|
||||
import TokenCase from './TokenCase';
|
||||
import TokenSeparator from './TokenSeparator';
|
||||
@@ -14,7 +12,7 @@ interface NamingOptionProps {
|
||||
example: string;
|
||||
tokenCase: TokenCase;
|
||||
isFullFilename?: boolean;
|
||||
footNote?: boolean;
|
||||
footNotes?: string;
|
||||
size?: Extract<Size, keyof typeof styles>;
|
||||
onPress: ({
|
||||
isFullFilename,
|
||||
@@ -32,7 +30,7 @@ function NamingOption(props: NamingOptionProps) {
|
||||
example,
|
||||
tokenCase,
|
||||
isFullFilename = false,
|
||||
footNote = false,
|
||||
footNotes,
|
||||
size = 'small',
|
||||
onPress,
|
||||
} = props;
|
||||
@@ -66,8 +64,10 @@ function NamingOption(props: NamingOptionProps) {
|
||||
<div className={styles.example}>
|
||||
{example.replace(/ /g, tokenSeparator)}
|
||||
|
||||
{footNote ? (
|
||||
<Icon className={styles.footNote} name={icons.FOOTNOTE} />
|
||||
{footNotes ? (
|
||||
<div className={styles.footNotes}>
|
||||
<sup>{footNotes}</sup>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Alert from 'Components/Alert';
|
||||
@@ -13,6 +13,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import {
|
||||
saveMetadata,
|
||||
@@ -41,6 +42,8 @@ function EditMetadataModalContent({
|
||||
(state: AppState) => state.settings.metadata
|
||||
);
|
||||
|
||||
const wasSaving = usePrevious(isSaving);
|
||||
|
||||
const { settings, ...otherSettings } = useMemo(() => {
|
||||
const item = items.find((item) => item.id === id)!;
|
||||
|
||||
@@ -69,6 +72,12 @@ function EditMetadataModalContent({
|
||||
dispatch(saveMetadata({ id }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
onModalClose();
|
||||
}
|
||||
}, [isSaving, wasSaving, saveError, onModalClose]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
|
||||
@@ -210,6 +210,12 @@ export const defaultState = {
|
||||
name: 'rejectionCount',
|
||||
label: () => translate('RejectionCount'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'movieRequested',
|
||||
label: () => translate('MovieRequested'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
}
|
||||
],
|
||||
selectedFilterKey: 'all'
|
||||
|
||||
19
frontend/src/Utilities/Number/formatBitrate.ts
Normal file
19
frontend/src/Utilities/Number/formatBitrate.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { filesize } from 'filesize';
|
||||
|
||||
function formatBitrate(input: string | number) {
|
||||
const size = Number(input);
|
||||
|
||||
if (isNaN(size)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { value, symbol } = filesize(size, {
|
||||
base: 10,
|
||||
round: 1,
|
||||
output: 'object',
|
||||
});
|
||||
|
||||
return `${value} ${symbol}/s`;
|
||||
}
|
||||
|
||||
export default formatBitrate;
|
||||
@@ -17,37 +17,6 @@ namespace NzbDrone.Common.Disk
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
|
||||
private readonly HashSet<string> _setToRemove = new HashSet<string>
|
||||
{
|
||||
// Windows
|
||||
"boot",
|
||||
"bootmgr",
|
||||
"cache",
|
||||
"msocache",
|
||||
"recovery",
|
||||
"$recycle.bin",
|
||||
"recycler",
|
||||
"system volume information",
|
||||
"temporary internet files",
|
||||
"windows",
|
||||
|
||||
// OS X
|
||||
".fseventd",
|
||||
".spotlight",
|
||||
".trashes",
|
||||
".vol",
|
||||
"cachedmessages",
|
||||
"caches",
|
||||
"trash",
|
||||
|
||||
// QNAP
|
||||
".@__thumb",
|
||||
|
||||
// Synology
|
||||
"@eadir",
|
||||
"#recycle"
|
||||
};
|
||||
|
||||
public FileSystemLookupService(IDiskProvider diskProvider, IRuntimeInfo runtimeInfo)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
@@ -158,7 +127,7 @@ namespace NzbDrone.Common.Disk
|
||||
})
|
||||
.ToList();
|
||||
|
||||
directories.RemoveAll(d => _setToRemove.Contains(d.Name.ToLowerInvariant()));
|
||||
directories.RemoveAll(d => SpecialFolders.IsSpecialFolder(d.Name));
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
47
src/NzbDrone.Common/Disk/SpecialFolders.cs
Normal file
47
src/NzbDrone.Common/Disk/SpecialFolders.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Disk;
|
||||
|
||||
public static class SpecialFolders
|
||||
{
|
||||
private static readonly HashSet<string> _specialFolders = new HashSet<string>
|
||||
{
|
||||
// Windows
|
||||
"boot",
|
||||
"bootmgr",
|
||||
"cache",
|
||||
"msocache",
|
||||
"recovery",
|
||||
"$recycle.bin",
|
||||
"recycler",
|
||||
"system volume information",
|
||||
"temporary internet files",
|
||||
"windows",
|
||||
|
||||
// OS X
|
||||
".fseventd",
|
||||
".spotlight",
|
||||
".trashes",
|
||||
".vol",
|
||||
"cachedmessages",
|
||||
"caches",
|
||||
"trash",
|
||||
|
||||
// QNAP
|
||||
".@__thumb",
|
||||
|
||||
// Synology
|
||||
"@eadir",
|
||||
"#recycle"
|
||||
};
|
||||
|
||||
public static bool IsSpecialFolder(string folder)
|
||||
{
|
||||
if (folder == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _specialFolders.Contains(folder.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
@@ -54,10 +54,8 @@ namespace NzbDrone.Common.Extensions
|
||||
foreach (var item in src)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
if (!result.ContainsKey(key))
|
||||
{
|
||||
result[key] = item;
|
||||
}
|
||||
|
||||
result.TryAdd(key, item);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -69,10 +67,9 @@ namespace NzbDrone.Common.Extensions
|
||||
foreach (var item in src)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
if (!result.ContainsKey(key))
|
||||
{
|
||||
result[key] = valueSelector(item);
|
||||
}
|
||||
var value = valueSelector(item);
|
||||
|
||||
result.TryAdd(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation;
|
||||
|
||||
public class CleansingClefLogLayout : CompactJsonLayout
|
||||
{
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
base.RenderFormattedMessage(logEvent, target);
|
||||
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
var result = CleanseLogMessage.Cleanse(target.ToString());
|
||||
target.Clear();
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Layouts;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation;
|
||||
|
||||
public class CleansingConsoleLogLayout : SimpleLayout
|
||||
{
|
||||
public CleansingConsoleLogLayout(string format)
|
||||
: base(format)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
base.RenderFormattedMessage(logEvent, target);
|
||||
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
var result = CleanseLogMessage.Cleanse(target.ToString());
|
||||
target.Clear();
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using NLog.Targets;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public class NzbDroneFileTarget : FileTarget
|
||||
public class CleansingFileTarget : FileTarget
|
||||
{
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
@@ -3,7 +3,6 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NLog.Targets;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -13,9 +12,11 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public static class NzbDroneLogger
|
||||
{
|
||||
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
public const string ConsoleLogLayout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
public static CompactJsonLayout ClefLogLayout = new CompactJsonLayout();
|
||||
private const string FileLogLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
private const string ConsoleFormat = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
private static readonly CleansingConsoleLogLayout CleansingConsoleLayout = new (ConsoleFormat);
|
||||
private static readonly CleansingClefLogLayout ClefLogLayout = new ();
|
||||
|
||||
private static bool _isConfigured;
|
||||
|
||||
@@ -119,11 +120,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
? formatEnumValue
|
||||
: ConsoleLogFormat.Standard;
|
||||
|
||||
coloredConsoleTarget.Layout = logFormat switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => ClefLogLayout,
|
||||
_ => ConsoleLogLayout
|
||||
};
|
||||
ConfigureConsoleLayout(coloredConsoleTarget, logFormat);
|
||||
|
||||
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
|
||||
|
||||
@@ -140,7 +137,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
|
||||
{
|
||||
var fileTarget = new NzbDroneFileTarget();
|
||||
var fileTarget = new CleansingFileTarget();
|
||||
|
||||
fileTarget.Name = name;
|
||||
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName);
|
||||
@@ -153,7 +150,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
fileTarget.MaxArchiveFiles = maxArchiveFiles;
|
||||
fileTarget.EnableFileDelete = true;
|
||||
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
|
||||
fileTarget.Layout = FILE_LOG_LAYOUT;
|
||||
fileTarget.Layout = FileLogLayout;
|
||||
|
||||
var loggingRule = new LoggingRule("*", minLogLevel, fileTarget);
|
||||
|
||||
@@ -172,7 +169,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
fileTarget.ConcurrentWrites = false;
|
||||
fileTarget.ConcurrentWriteAttemptDelay = 50;
|
||||
fileTarget.ConcurrentWriteAttempts = 100;
|
||||
fileTarget.Layout = FILE_LOG_LAYOUT;
|
||||
fileTarget.Layout = FileLogLayout;
|
||||
|
||||
var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
|
||||
|
||||
@@ -217,6 +214,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
return GetLogger(obj.GetType());
|
||||
}
|
||||
|
||||
public static void ConfigureConsoleLayout(ColoredConsoleTarget target, ConsoleLogFormat format)
|
||||
{
|
||||
target.Layout = format switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
|
||||
_ => NzbDroneLogger.CleansingConsoleLayout
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConsoleLogFormat
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.2" />
|
||||
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.15" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.9" />
|
||||
<PackageReference Include="Sentry" Version="4.0.2" />
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Migration;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class stevenlu_update_urlFixture : MigrationTest<stevenlu_update_url>
|
||||
{
|
||||
[Test]
|
||||
public void should_update_stevenlu_url()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("ImportLists").Row(new
|
||||
{
|
||||
Enabled = true,
|
||||
EnableAuto = true,
|
||||
Name = "StevenLu List",
|
||||
QualityProfileId = 1,
|
||||
MinimumAvailability = 1,
|
||||
RootFolderPath = "/movies",
|
||||
Monitor = 0,
|
||||
SearchOnAdd = true,
|
||||
Tags = "[]",
|
||||
Implementation = "StevenLuImport",
|
||||
ConfigContract = "StevenLuSettings",
|
||||
Settings = new StevenLuSettings241
|
||||
{
|
||||
Link = "https://s3.amazonaws.com/popular-movies/movies.json"
|
||||
}.ToJson()
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<ImportListDefinition241>("SELECT \"Id\", \"Settings\" FROM \"ImportLists\"");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Settings.Link.Should().Be("https://popular-movies-data.stevenlu.com/movies.json");
|
||||
}
|
||||
}
|
||||
|
||||
public class ImportListDefinition241 : ModelBase
|
||||
{
|
||||
public StevenLuSettings241 Settings { get; set; }
|
||||
}
|
||||
|
||||
public class StevenLuSettings241
|
||||
{
|
||||
public string Link { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -20,32 +20,32 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
private List<ReleaseInfo> _reports;
|
||||
private RemoteMovie _remoteEpisode;
|
||||
|
||||
private Mock<IDecisionEngineSpecification> _pass1;
|
||||
private Mock<IDecisionEngineSpecification> _pass2;
|
||||
private Mock<IDecisionEngineSpecification> _pass3;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _pass1;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _pass2;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _pass3;
|
||||
|
||||
private Mock<IDecisionEngineSpecification> _fail1;
|
||||
private Mock<IDecisionEngineSpecification> _fail2;
|
||||
private Mock<IDecisionEngineSpecification> _fail3;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _fail1;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _fail2;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _fail3;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_pass1 = new Mock<IDecisionEngineSpecification>();
|
||||
_pass2 = new Mock<IDecisionEngineSpecification>();
|
||||
_pass3 = new Mock<IDecisionEngineSpecification>();
|
||||
_pass1 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_pass2 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_pass3 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
|
||||
_fail1 = new Mock<IDecisionEngineSpecification>();
|
||||
_fail2 = new Mock<IDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IDecisionEngineSpecification>();
|
||||
_fail1 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_fail2 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Accept);
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Accept);
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Accept);
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Accept);
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Accept);
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Accept);
|
||||
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Reject("fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Reject("fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Reject("fail3"));
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail3"));
|
||||
|
||||
_reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "Trolls.2016.720p.WEB-DL.DD5.1.H264-FGT" } };
|
||||
_remoteEpisode = new RemoteMovie
|
||||
@@ -58,9 +58,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
.Setup(c => c.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>())).Returns(_remoteEpisode);
|
||||
}
|
||||
|
||||
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
|
||||
private void GivenSpecifications(params Mock<IDownloadDecisionEngineSpecification>[] mocks)
|
||||
{
|
||||
Mocker.SetConstant<IEnumerable<IDecisionEngineSpecification>>(mocks.Select(c => c.Object));
|
||||
Mocker.SetConstant<IEnumerable<IDownloadDecisionEngineSpecification>>(mocks.Select(c => c.Object));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -4,7 +4,6 @@ using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.History;
|
||||
@@ -107,11 +106,11 @@ namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new Rejection("Rejected!")), "Test Failure"),
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure"),
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1999.mkv" }, new Rejection("Rejected!")), "Test Failure")
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1999.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure")
|
||||
});
|
||||
|
||||
Subject.Import(_trackedDownload);
|
||||
@@ -131,11 +130,11 @@ namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new Rejection("Rejected!")), "Test Failure"),
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure"),
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new Rejection("Rejected!")), "Test Failure")
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure")
|
||||
});
|
||||
|
||||
_trackedDownload.RemoteMovie.Movie = new Movie();
|
||||
|
||||
@@ -189,8 +189,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
{
|
||||
var decisions = new List<DownloadDecision>();
|
||||
RemoteMovie remoteMovie = null;
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!")));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!")));
|
||||
|
||||
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
|
||||
}
|
||||
@@ -201,7 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var remoteMovie = GetRemoteMovie(new QualityModel(Quality.HDTV720p));
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Never());
|
||||
@@ -214,7 +214,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(removeMovie));
|
||||
decisions.Add(new DownloadDecision(removeMovie, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(removeMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Never());
|
||||
@@ -226,8 +226,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var remoteEpisode = GetRemoteMovie(new QualityModel(Quality.HDTV720p));
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Once());
|
||||
|
||||
@@ -95,5 +95,22 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
|
||||
VerifySingleItem(DownloadItemStatus.Completed);
|
||||
}
|
||||
|
||||
[TestCase("@eaDir")]
|
||||
[TestCase(".@__thumb")]
|
||||
public void GetItems_should_not_include_special_subfolders(string folderName)
|
||||
{
|
||||
GivenCompletedItem();
|
||||
|
||||
var targetDir = Path.Combine(_completedDownloadFolder, folderName);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.GetDirectories(_completedDownloadFolder))
|
||||
.Returns(new[] { targetDir });
|
||||
|
||||
var items = Subject.GetItems(_completedDownloadFolder, TimeSpan.FromMilliseconds(50)).ToList();
|
||||
|
||||
items.Count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_remoteMovie.ParsedMovieInfo = _parsedMovieInfo;
|
||||
_remoteMovie.Release = _release;
|
||||
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
|
||||
|
||||
_heldReleases = new List<PendingRelease>();
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_remoteMovie.ParsedMovieInfo = _parsedMovieInfo;
|
||||
_remoteMovie.Release = _release;
|
||||
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
|
||||
|
||||
_heldReleases = new List<PendingRelease>();
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_remoteMovie.ParsedMovieInfo = _parsedMovieInfo;
|
||||
_remoteMovie.Release = _release;
|
||||
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
|
||||
|
||||
Mocker.GetMock<IPendingReleaseRepository>()
|
||||
.Setup(s => s.All())
|
||||
|
||||
@@ -76,6 +76,19 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
Subject.Check().ShouldBeWarning(wikiFragment: "downloads-in-root-folder");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_if_downloading_inside_root_folder()
|
||||
{
|
||||
var rootFolderPath = "c:\\Test".AsOsAgnostic();
|
||||
var downloadRootPath = "c:\\Test\\Downloads".AsOsAgnostic();
|
||||
|
||||
GivenRootFolder(rootFolderPath);
|
||||
|
||||
_clientStatus.OutputRootFolders = new List<OsPath> { new (downloadRootPath) };
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_if_not_downloading_to_root_folder()
|
||||
{
|
||||
@@ -87,7 +100,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("DownloadClientExceptions")]
|
||||
[TestCaseSource(nameof(DownloadClientExceptions))]
|
||||
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
|
||||
{
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
|
||||
@@ -7,7 +7,6 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
@@ -46,9 +45,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
.With(s => s.Path = @"C:\Test\TV\30 Rock".AsOsAgnostic())
|
||||
.Build();
|
||||
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
|
||||
|
||||
_approvedDecisions.Add(new ImportDecision(
|
||||
new LocalMovie
|
||||
|
||||
@@ -4,7 +4,6 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MovieImport;
|
||||
@@ -49,13 +48,13 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
_fail2 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IImportDecisionEngineSpecification>();
|
||||
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
|
||||
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail3"));
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail3"));
|
||||
|
||||
_movie = Builder<Movie>.CreateNew()
|
||||
.With(e => e.Path = @"C:\Test\Movie".AsOsAgnostic())
|
||||
|
||||
@@ -54,6 +54,17 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Specifications
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) part1.mkv",
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) part2.mkv"
|
||||
})]
|
||||
|
||||
[TestCase(new object[]
|
||||
{
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) pt1.mkv",
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) pt2.mkv"
|
||||
})]
|
||||
[TestCase(new object[]
|
||||
{
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) P1.mkv",
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) P2.mkv"
|
||||
})]
|
||||
[TestCase(new object[]
|
||||
{
|
||||
@"C:\Test\Downloaded\blah blah - cd 1.mvk",
|
||||
|
||||
@@ -52,6 +52,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title : Other Title 2010 x264.720p.Blu-ray Rip HD.VOSTFR.VFF. ONLY")]
|
||||
[TestCase("Movie Title 2019 HEVC.2160p.Blu-ray 4K.VOSTFR.VFF. JATO")]
|
||||
[TestCase("Movie.Title.1956.MULTi.VF.Bluray.1080p.REMUX.AC3.x264")]
|
||||
[TestCase("Movie.Title.2016.ENG-ITA-FRE.AAC.1080p.WebDL.x264")]
|
||||
[TestCase("Movie Title 2016 (BDrip 1080p ENG-ITA-FRE) Multisub x264")]
|
||||
public void should_parse_language_french(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
@@ -87,7 +89,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.German.1080p.XviD-LOL")]
|
||||
[TestCase("Movie.Title.2016.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP")]
|
||||
[TestCase("Movie Title 2016 - Kampfhaehne - mkv - by Videomann")]
|
||||
[TestCase("Movie.Title.2016.Ger.Dub.AAC.1080p.WebDL.x264-TKP21")]
|
||||
[TestCase("Movie.Title.2016.Ger.AAC.1080p.WebDL.x264-TKP21")]
|
||||
[TestCase("Movie.Title.2016.Hun/Ger/Ita.AAC.1080p.WebDL.x264-TKP21")]
|
||||
[TestCase("Movie.Title.2016.1080p.10Bit.HEVC.WEBRip.HIN-ENG-GER.DD5.1.H.265")]
|
||||
[TestCase("Movie.Title.2016.HU-IT-DE.AAC.1080p.WebDL.x264")]
|
||||
public void should_parse_language_german(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
@@ -96,6 +104,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.Italian.1080p.XviD-LOL")]
|
||||
[TestCase("Movie.Title.2016.ENG-FRE-ITA.AAC.1080p.WebDL.x264")]
|
||||
[TestCase("Movie Title 2016 (BDrip 1080p ENG-FRE-ITA) Multisub x264")]
|
||||
public void should_parse_language_italian(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(241)]
|
||||
public class stevenlu_update_url : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(FixStevenLuListsLink);
|
||||
}
|
||||
|
||||
private void FixStevenLuListsLink(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var updated = new List<object>();
|
||||
|
||||
using (var getStevenLuListCmd = conn.CreateCommand())
|
||||
{
|
||||
getStevenLuListCmd.Transaction = tran;
|
||||
getStevenLuListCmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"ImportLists\" WHERE \"ConfigContract\" = 'StevenLuSettings'";
|
||||
|
||||
using var reader = getStevenLuListCmd.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = Json.Deserialize<JObject>(reader.GetString(1));
|
||||
|
||||
var link = settings.Value<string>("link");
|
||||
|
||||
if (link.IsNotNullOrWhiteSpace() && link.StartsWith("https://s3.amazonaws.com/popular-movies"))
|
||||
{
|
||||
settings["link"] = "https://popular-movies-data.stevenlu.com/movies.json";
|
||||
}
|
||||
|
||||
updated.Add(new
|
||||
{
|
||||
Id = id,
|
||||
Settings = settings.ToJson()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE \"ImportLists\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
|
||||
conn.Execute(updateSql, updated, transaction: tran);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class Decision
|
||||
{
|
||||
public bool Accepted { get; private set; }
|
||||
public string Reason { get; private set; }
|
||||
|
||||
private static readonly Decision AcceptDecision = new Decision { Accepted = true };
|
||||
private Decision()
|
||||
{
|
||||
}
|
||||
|
||||
public static Decision Accept()
|
||||
{
|
||||
return AcceptDecision;
|
||||
}
|
||||
|
||||
public static Decision Reject(string reason, params object[] args)
|
||||
{
|
||||
return Reject(string.Format(reason, args));
|
||||
}
|
||||
|
||||
public static Decision Reject(string reason)
|
||||
{
|
||||
return new Decision
|
||||
{
|
||||
Accepted = false,
|
||||
Reason = reason
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public RemoteMovie RemoteMovie { get; private set; }
|
||||
|
||||
public IEnumerable<Rejection> Rejections { get; private set; }
|
||||
public IEnumerable<DownloadRejection> Rejections { get; private set; }
|
||||
|
||||
public bool Approved => !Rejections.Any();
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadDecision(RemoteMovie movie, params Rejection[] rejections)
|
||||
public DownloadDecision(RemoteMovie movie, params DownloadRejection[] rejections)
|
||||
{
|
||||
RemoteMovie = movie;
|
||||
Rejections = rejections.ToList();
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return Math.Round(Math.Log10(age)) * -1;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
public class DownloadDecisionMaker : IMakeDownloadDecision
|
||||
{
|
||||
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
|
||||
private readonly IEnumerable<IDownloadDecisionEngineSpecification> _specifications;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ICustomFormatCalculationService _formatCalculator;
|
||||
private readonly IRemoteMovieAggregationService _aggregationService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
|
||||
public DownloadDecisionMaker(IEnumerable<IDownloadDecisionEngineSpecification> specifications,
|
||||
IParsingService parsingService,
|
||||
IConfigService configService,
|
||||
ICustomFormatCalculationService formatCalculator,
|
||||
@@ -85,9 +85,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
if (remoteMovie.Movie == null)
|
||||
{
|
||||
var reason = "Unknown Movie";
|
||||
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection(reason));
|
||||
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnknownMovie, "Unknown Movie. Unable to identify correct movie using release name."));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -123,7 +121,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
Languages = parsedMovieInfo.Languages
|
||||
};
|
||||
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Unable to parse release"));
|
||||
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnableToParse, "Unable to parse release"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +130,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
_logger.Error(e, "Couldn't process release.");
|
||||
|
||||
var remoteMovie = new RemoteMovie { Release = report };
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection("Unexpected error processing release"));
|
||||
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Error, "Unexpected error processing release"));
|
||||
}
|
||||
|
||||
reportNumber++;
|
||||
@@ -175,7 +173,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
private DownloadDecision GetDecisionForReport(RemoteMovie remoteMovie, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var reasons = Array.Empty<Rejection>();
|
||||
var reasons = Array.Empty<DownloadRejection>();
|
||||
|
||||
foreach (var specifications in _specifications.GroupBy(v => v.Priority).OrderBy(v => v.Key))
|
||||
{
|
||||
@@ -192,7 +190,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
return new DownloadDecision(remoteMovie, reasons.ToArray());
|
||||
}
|
||||
|
||||
private Rejection EvaluateSpec(IDecisionEngineSpecification spec, RemoteMovie remoteMovie, SearchCriteriaBase searchCriteriaBase = null)
|
||||
private DownloadRejection EvaluateSpec(IDownloadDecisionEngineSpecification spec, RemoteMovie remoteMovie, SearchCriteriaBase searchCriteriaBase = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -200,7 +198,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
if (!result.Accepted)
|
||||
{
|
||||
return new Rejection(result.Reason, spec.Type);
|
||||
return new DownloadRejection(result.Reason, result.Message, spec.Type);
|
||||
}
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
@@ -212,7 +210,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
e.Data.Add("report", remoteMovie.Release.ToJson());
|
||||
e.Data.Add("parsed", remoteMovie.ParsedMovieInfo.ToJson());
|
||||
_logger.Error(e, "Couldn't evaluate decision on {0}, with spec: {1}", remoteMovie.Release.Title, spec.GetType().Name);
|
||||
return new Rejection($"{spec.GetType().Name}: {e.Message}");
|
||||
return new DownloadRejection(DownloadRejectionReason.DecisionError, $"{spec.GetType().Name}: {e.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
9
src/NzbDrone.Core/DecisionEngine/DownloadRejection.cs
Normal file
9
src/NzbDrone.Core/DecisionEngine/DownloadRejection.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Core.DecisionEngine;
|
||||
|
||||
public class DownloadRejection : Rejection<DownloadRejectionReason>
|
||||
{
|
||||
public DownloadRejection(DownloadRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
|
||||
: base(reason, message, type)
|
||||
{
|
||||
}
|
||||
}
|
||||
67
src/NzbDrone.Core/DecisionEngine/DownloadRejectionReason.cs
Normal file
67
src/NzbDrone.Core/DecisionEngine/DownloadRejectionReason.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
namespace NzbDrone.Core.DecisionEngine;
|
||||
|
||||
public enum DownloadRejectionReason
|
||||
{
|
||||
Unknown,
|
||||
UnknownMovie,
|
||||
UnableToParse,
|
||||
Error,
|
||||
DecisionError,
|
||||
Availability,
|
||||
MinimumAgeDelay,
|
||||
MovieNotMonitored,
|
||||
HistoryRecentCutoffMet,
|
||||
HistoryCdhDisabledCutoffMet,
|
||||
HistoryHigherPreference,
|
||||
HistoryHigherRevision,
|
||||
HistoryCutoffMet,
|
||||
HistoryCustomFormatCutoffMet,
|
||||
HistoryCustomFormatScore,
|
||||
HistoryCustomFormatScoreIncrement,
|
||||
HistoryUpgradesNotAllowed,
|
||||
NoMatchingTag,
|
||||
PropersDisabled,
|
||||
ProperForOldFile,
|
||||
WrongMovie,
|
||||
UnknownRuntime,
|
||||
BelowMinimumSize,
|
||||
AboveMaximumSize,
|
||||
AlreadyImportedSameHash,
|
||||
AlreadyImportedSameName,
|
||||
IndexerDisabled,
|
||||
Blocklisted,
|
||||
CustomFormatMinimumScore,
|
||||
MinimumFreeSpace,
|
||||
HardcodeSubtitles,
|
||||
WantedLanguage,
|
||||
MaximumSizeExceeded,
|
||||
MinimumAge,
|
||||
MaximumAge,
|
||||
Sample,
|
||||
ProtocolDisabled,
|
||||
QualityNotWanted,
|
||||
QualityUpgradesDisabled,
|
||||
QueueHigherPreference,
|
||||
QueueHigherRevision,
|
||||
QueueCutoffMet,
|
||||
QueueCustomFormatCutoffMet,
|
||||
QueueCustomFormatScore,
|
||||
QueueCustomFormatScoreIncrement,
|
||||
QueueUpgradesNotAllowed,
|
||||
QueuePropersDisabled,
|
||||
Raw,
|
||||
MustContainMissing,
|
||||
MustNotContainPresent,
|
||||
RepackDisabled,
|
||||
RepackUnknownReleaseGroup,
|
||||
RepackReleaseGroupDoesNotMatch,
|
||||
RequiredFlags,
|
||||
MinimumSeeders,
|
||||
DiskHigherPreference,
|
||||
DiskHigherRevision,
|
||||
DiskCutoffMet,
|
||||
DiskCustomFormatCutoffMet,
|
||||
DiskCustomFormatScore,
|
||||
DiskCustomFormatScoreIncrement,
|
||||
DiskUpgradesNotAllowed
|
||||
}
|
||||
34
src/NzbDrone.Core/DecisionEngine/DownloadSpecDecision.cs
Normal file
34
src/NzbDrone.Core/DecisionEngine/DownloadSpecDecision.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class DownloadSpecDecision
|
||||
{
|
||||
public bool Accepted { get; private set; }
|
||||
public DownloadRejectionReason Reason { get; set; }
|
||||
public string Message { get; private set; }
|
||||
|
||||
private static readonly DownloadSpecDecision AcceptDownloadSpecDecision = new () { Accepted = true };
|
||||
private DownloadSpecDecision()
|
||||
{
|
||||
}
|
||||
|
||||
public static DownloadSpecDecision Accept()
|
||||
{
|
||||
return AcceptDownloadSpecDecision;
|
||||
}
|
||||
|
||||
public static DownloadSpecDecision Reject(DownloadRejectionReason reason, string message, params object[] args)
|
||||
{
|
||||
return Reject(reason, string.Format(message, args));
|
||||
}
|
||||
|
||||
public static DownloadSpecDecision Reject(DownloadRejectionReason reason, string message)
|
||||
{
|
||||
return new DownloadSpecDecision
|
||||
{
|
||||
Accepted = false,
|
||||
Reason = reason,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class Rejection
|
||||
public class Rejection<TRejectionReason>
|
||||
{
|
||||
public string Reason { get; set; }
|
||||
public TRejectionReason Reason { get; set; }
|
||||
public string Message { get; set; }
|
||||
public RejectionType Type { get; set; }
|
||||
|
||||
public Rejection(string reason, RejectionType type = RejectionType.Permanent)
|
||||
public Rejection(TRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
|
||||
{
|
||||
Reason = reason;
|
||||
Message = message;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}] {1}", Type, Reason);
|
||||
return string.Format("[{0}] {1}", Type, Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AcceptableSizeSpecification : IDecisionEngineSpecification
|
||||
public class AcceptableSizeSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IQualityDefinitionService _qualityDefinitionService;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Beginning size check for: {0}", subject);
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (subject.Release.Size == 0)
|
||||
{
|
||||
_logger.Debug("Release has unknown size, skipping size check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var qualityDefinition = _qualityDefinitionService.Get(quality);
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var runtimeMessage = subject.Movie.Title;
|
||||
|
||||
_logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage);
|
||||
return Decision.Reject("{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.BelowMinimumSize, "{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
else if (subject.Movie.MovieMetadata.Value.Runtime == 0)
|
||||
{
|
||||
_logger.Debug("Movie runtime is 0, unable to validate size until it is available, rejecting");
|
||||
return Decision.Reject("Movie runtime is 0, unable to validate size until it is available");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.UnknownRuntime, "Movie runtime is 0, unable to validate size until it is available");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -77,12 +77,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (subject.Release.Size > maxSize)
|
||||
{
|
||||
_logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} for {3}), rejecting", subject, subject.Release.Size, maxSize, subject.Movie.Title);
|
||||
return Decision.Reject("{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), subject.Movie.Title);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.AboveMaximumSize, "{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), subject.Movie.Title);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Item: {0}, meets size constraints", subject);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AlreadyImportedSpecification : IDecisionEngineSpecification
|
||||
public class AlreadyImportedSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -27,14 +27,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||
|
||||
if (!cdhEnabled)
|
||||
{
|
||||
_logger.Debug("Skipping already imported check because CDH is disabled");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var movie = subject.Movie;
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!movie.HasFile)
|
||||
{
|
||||
_logger.Debug("Skipping already imported check for movie without file");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var historyForMovie = _historyService.GetByMovieId(movie.Id, null);
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (lastGrabbed == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var imported = historyForMovie.FirstOrDefault(h =>
|
||||
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (imported == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
// This is really only a guard against redownloading the same release over
|
||||
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
// match skip this check.
|
||||
if (lastGrabbed.Quality.Equals(imported.Quality))
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var release = subject.Release;
|
||||
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (torrentInfo?.InfoHash != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId)
|
||||
{
|
||||
_logger.Debug("Has same torrent hash as a grabbed and imported release");
|
||||
return Decision.Reject("Has same torrent hash as a grabbed and imported release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.AlreadyImportedSameHash, "Has same torrent hash as a grabbed and imported release");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,11 +91,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_logger.Debug("Has same release name as a grabbed and imported release");
|
||||
return Decision.Reject("Has same release name as a grabbed and imported release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.AlreadyImportedSameName, "Has same release name as a grabbed and imported release");
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class BlockedIndexerSpecification : IDecisionEngineSpecification
|
||||
public class BlockedIndexerSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
private readonly Logger _logger;
|
||||
@@ -27,15 +27,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var status = _blockedIndexerCache.Find(subject.Release.IndexerId.ToString());
|
||||
if (status != null)
|
||||
{
|
||||
return Decision.Reject($"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.IndexerDisabled, $"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
private IDictionary<string, IndexerStatus> FetchBlockedIndexer()
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class BlocklistSpecification : IDecisionEngineSpecification
|
||||
public class BlocklistSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IBlocklistService _blocklistService;
|
||||
private readonly Logger _logger;
|
||||
@@ -19,15 +19,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_blocklistService.Blocklisted(subject.Movie.Id, subject.Release))
|
||||
{
|
||||
_logger.Debug("{0} is blocklisted, rejecting.", subject.Release.Title);
|
||||
return Decision.Reject("Release is blocklisted");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Blocklisted, "Release is blocklisted");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class CustomFormatAllowedbyProfileSpecification : IDecisionEngineSpecification
|
||||
public class CustomFormatAllowedbyProfileSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -17,19 +17,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var minScore = subject.Movie.QualityProfile.MinFormatScore;
|
||||
var score = subject.CustomFormatScore;
|
||||
|
||||
if (score < minScore)
|
||||
{
|
||||
return Decision.Reject("Custom Formats {0} have score {1} below Movie's profile minimum {2}", subject.CustomFormats.ConcatToString(), score, minScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.CustomFormatMinimumScore, "Custom Formats {0} have score {1} below Movie's profile minimum {2}", subject.CustomFormats.ConcatToString(), score, minScore);
|
||||
}
|
||||
|
||||
_logger.Trace("Custom Format Score of {0} [{1}] above Movie's profile minimum {2}", score, subject.CustomFormats.ConcatToString(), minScore);
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class FreeSpaceSpecification : IDecisionEngineSpecification
|
||||
public class FreeSpaceSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
@@ -21,15 +21,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public SpecificationPriority Priority => SpecificationPriority.Disk;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_configService.SkipFreeSpaceCheckWhenImporting)
|
||||
{
|
||||
_logger.Debug("Skipping free space check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var size = subject.Release.Size;
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
_logger.Debug("Unable to get available space for {0}. Skipping", path);
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var minimumSpace = _configService.MinimumFreeSpaceWhenImporting.Megabytes();
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var message = "Importing after download will exceed available disk space";
|
||||
|
||||
_logger.Debug(message);
|
||||
return Decision.Reject(message);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumFreeSpace, message);
|
||||
}
|
||||
|
||||
if (remainingSpace < minimumSpace)
|
||||
@@ -68,10 +68,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var message = $"Not enough free space ({minimumSpace.SizeSuffix()}) to import after download: {remainingSpace.SizeSuffix()}. (Settings: Media Management: Minimum Free Space)";
|
||||
|
||||
_logger.Debug(message);
|
||||
return Decision.Reject(message);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumFreeSpace, message);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class HardcodeSubsSpecification : IDecisionEngineSpecification
|
||||
public class HardcodeSubsSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -21,13 +21,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var hardcodeSubs = subject.ParsedMovieInfo.HardcodedSubs;
|
||||
|
||||
if (_configService.AllowHardcodedSubs || hardcodeSubs.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var whitelisted = _configService.WhitelistedHardcodedSubs.Split(',');
|
||||
@@ -35,12 +35,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (whitelisted != null && whitelisted.Any(t => (hardcodeSubs.ToLower().Contains(t.ToLower()) && t.IsNotNullOrWhiteSpace())))
|
||||
{
|
||||
_logger.Debug("Release hardcode subs ({0}) are in allowed values ({1})", hardcodeSubs, whitelisted);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Hardcode subs found: {0}", hardcodeSubs);
|
||||
return Decision.Reject("Hardcode subs found: {0}", hardcodeSubs);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HardcodeSubtitles, "Hardcode subs found: {0}", hardcodeSubs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public interface IDecisionEngineSpecification
|
||||
public interface IDownloadDecisionEngineSpecification
|
||||
{
|
||||
RejectionType Type { get; }
|
||||
|
||||
SpecificationPriority Priority { get; }
|
||||
|
||||
Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria);
|
||||
DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class LanguageSpecification : IDecisionEngineSpecification
|
||||
public class LanguageSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var wantedLanguage = subject.Movie.QualityProfile.Language;
|
||||
|
||||
if (wantedLanguage == Language.Any)
|
||||
{
|
||||
_logger.Debug("Profile allows any language, accepting release.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var originalLanguage = subject.Movie.MovieMetadata.Value.OriginalLanguage;
|
||||
@@ -34,10 +34,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!subject.Languages.Contains(originalLanguage))
|
||||
{
|
||||
_logger.Debug("Original Language({0}) is wanted, but found {1}", originalLanguage, subject.Languages.ToExtendedString());
|
||||
return Decision.Reject("Original Language ({0}) is wanted, but found {1}", originalLanguage, subject.Languages.ToExtendedString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.WantedLanguage, "Original Language ({0}) is wanted, but found {1}", originalLanguage, subject.Languages.ToExtendedString());
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedMovieInfo.Languages.ToExtendedString());
|
||||
@@ -45,10 +45,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!subject.Languages.Contains(wantedLanguage))
|
||||
{
|
||||
_logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.Languages.ToExtendedString(), wantedLanguage);
|
||||
return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.Languages.ToExtendedString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.WantedLanguage, "{0} is wanted, but found {1}", wantedLanguage, subject.Languages.ToExtendedString());
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class MaximumSizeSpecification : IDecisionEngineSpecification
|
||||
public class MaximumSizeSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var size = subject.Release.Size;
|
||||
var maximumSize = _configService.MaximumSize.Megabytes();
|
||||
@@ -28,13 +28,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (maximumSize == 0)
|
||||
{
|
||||
_logger.Debug("Maximum size is not set.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
_logger.Debug("Release has unknown size, skipping size check.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if release meets maximum size requirements. {0}", size.SizeSuffix());
|
||||
@@ -44,10 +44,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var message = $"{size.SizeSuffix()} is too big, maximum size is {maximumSize.SizeSuffix()} (Settings->Indexers->Maximum Size)";
|
||||
|
||||
_logger.Debug(message);
|
||||
return Decision.Reject(message);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MaximumSizeExceeded, message);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class MinimumAgeSpecification : IDecisionEngineSpecification
|
||||
public class MinimumAgeSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,12 +20,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
|
||||
{
|
||||
_logger.Debug("Not checking minimum age requirement for non-usenet report");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var age = subject.Release.AgeMinutes;
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (minimumAge == 0)
|
||||
{
|
||||
_logger.Debug("Minimum age is not set.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if report meets minimum age requirements. {0}", ageRounded);
|
||||
@@ -43,12 +43,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (age < minimumAge)
|
||||
{
|
||||
_logger.Debug("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAge, "Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
}
|
||||
|
||||
_logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", ageRounded, minimumAge);
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class NotSampleSpecification : IDecisionEngineSpecification
|
||||
public class NotSampleSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -17,15 +17,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes())
|
||||
{
|
||||
_logger.Debug("Sample release, rejecting.");
|
||||
return Decision.Reject("Sample");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Sample, "Sample");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Profiles.Delay;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class ProtocolSpecification : IDecisionEngineSpecification
|
||||
public class ProtocolSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IDelayProfileService _delayProfileService;
|
||||
private readonly Logger _logger;
|
||||
@@ -21,23 +21,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(subject.Movie.Tags);
|
||||
|
||||
if (subject.Release.DownloadProtocol == DownloadProtocol.Usenet && !delayProfile.EnableUsenet)
|
||||
{
|
||||
_logger.Debug("[{0}] Usenet is not enabled for this movie", subject.Release.Title);
|
||||
return Decision.Reject("Usenet is not enabled for this movie");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProtocolDisabled, "Usenet is not enabled for this movie");
|
||||
}
|
||||
|
||||
if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent && !delayProfile.EnableTorrent)
|
||||
{
|
||||
_logger.Debug("[{0}] Torrent is not enabled for this movie", subject.Release.Title);
|
||||
return Decision.Reject("Torrent is not enabled for this movie");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProtocolDisabled, "Torrent is not enabled for this movie");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class QualityAllowedByProfileSpecification : IDecisionEngineSpecification
|
||||
public class QualityAllowedByProfileSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedMovieInfo.Quality);
|
||||
|
||||
@@ -27,10 +27,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!qualityOrGroup.Allowed)
|
||||
{
|
||||
_logger.Debug("Quality {0} rejected by Movie's quality profile", subject.ParsedMovieInfo.Quality);
|
||||
return Decision.Reject("{0} is not wanted in profile", subject.ParsedMovieInfo.Quality.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QualityNotWanted, "{0} is not wanted in profile", subject.ParsedMovieInfo.Quality.Quality);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using NzbDrone.Core.Queue;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class QueueSpecification : IDecisionEngineSpecification
|
||||
public class QueueSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IQueueService _queueService;
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var queue = _queueService.GetQueue();
|
||||
var matchingMovies = queue.Where(q => q.RemoteMovie?.Movie != null &&
|
||||
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
queuedItemCustomFormats,
|
||||
subject.ParsedMovieInfo.Quality))
|
||||
{
|
||||
return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCutoffMet, "Quality for release in queue already meets cutoff: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
@@ -80,25 +80,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
switch (upgradeableRejectReason)
|
||||
{
|
||||
case UpgradeableRejectReason.BetterQuality:
|
||||
return Decision.Reject("Release in queue is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueHigherPreference, "Release in queue is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
|
||||
case UpgradeableRejectReason.BetterRevision:
|
||||
return Decision.Reject("Release in queue is of equal or higher revision: {0}", remoteMovie.ParsedMovieInfo.Quality.Revision);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueHigherRevision, "Release in queue is of equal or higher revision: {0}", remoteMovie.ParsedMovieInfo.Quality.Revision);
|
||||
|
||||
case UpgradeableRejectReason.QualityCutoff:
|
||||
return Decision.Reject("Release in queue meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCutoffMet, "Release in queue meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatCutoff:
|
||||
return Decision.Reject("Release in queue meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatCutoffMet, "Release in queue meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatScore:
|
||||
return Decision.Reject("Release in queue has an equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats));
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatScore, "Release in queue has an equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats));
|
||||
|
||||
case UpgradeableRejectReason.MinCustomFormatScore:
|
||||
return Decision.Reject("Release in queue has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatScoreIncrement, "Release in queue has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.UpgradesNotAllowed:
|
||||
return Decision.Reject("Release in queue and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueUpgradesNotAllowed, "Release in queue and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
}
|
||||
|
||||
if (_upgradableSpecification.IsRevisionUpgrade(remoteMovie.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality))
|
||||
@@ -106,12 +106,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueuePropersDisabled, "Proper downloading is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RawDiskSpecification : IDecisionEngineSpecification
|
||||
public class RawDiskSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private static readonly Regex[] DiscRegex = new[]
|
||||
{
|
||||
@@ -29,11 +29,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
foreach (var regex in DiscRegex)
|
||||
@@ -41,28 +41,28 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (regex.IsMatch(subject.Release.Title))
|
||||
{
|
||||
_logger.Debug("Release contains raw Bluray/DVD, rejecting.");
|
||||
return Decision.Reject("Raw Bluray/DVD release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw Bluray/DVD release");
|
||||
}
|
||||
}
|
||||
|
||||
if (subject.Release.Container.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (_dvdContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw DVD, rejecting.");
|
||||
return Decision.Reject("Raw DVD release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw DVD release");
|
||||
}
|
||||
|
||||
if (_blurayContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw Bluray, rejecting.");
|
||||
return Decision.Reject("Raw Bluray release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw Bluray release");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Profiles.Releases;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
|
||||
public class ReleaseRestrictionsSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IReleaseProfileService _releaseProfileService;
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if release meets restrictions: {0}", subject);
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var terms = string.Join(", ", requiredTerms);
|
||||
_logger.Debug("[{0}] does not contain one of the required terms: {1}", title, terms);
|
||||
return Decision.Reject("Does not contain one of the required terms: {0}", terms);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MustContainMissing, "Does not contain one of the required terms: {0}", terms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var terms = string.Join(", ", foundTerms);
|
||||
_logger.Debug("[{0}] contains these ignored terms: {1}", title, terms);
|
||||
return Decision.Reject("Contains these ignored terms: {0}", terms);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MustNotContainPresent, "Contains these ignored terms: {0}", terms);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("[{0}] No restrictions apply, allowing", subject);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
private List<string> ContainsAny(List<string> terms, string title)
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RepackSpecification : IDecisionEngineSpecification
|
||||
public class RepackSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -24,19 +24,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
|
||||
if (!subject.ParsedMovieInfo.Quality.Revision.IsRepack)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
|
||||
{
|
||||
_logger.Debug("Repacks are not preferred, skipping check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Movie.MovieFileId != 0)
|
||||
@@ -51,17 +51,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of repacks is disabled");
|
||||
return Decision.Reject("Repack downloading is disabled");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackDisabled, "Repack downloading is disabled");
|
||||
}
|
||||
|
||||
if (fileReleaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Reject("Unable to determine release group for the existing file");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackUnknownReleaseGroup, "Unable to determine release group for the existing file");
|
||||
}
|
||||
|
||||
if (releaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Reject("Unable to determine release group for this release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackUnknownReleaseGroup, "Unable to determine release group for this release");
|
||||
}
|
||||
|
||||
if (!fileReleaseGroup.Equals(releaseGroup, StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -70,7 +70,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
"Release is a repack for a different release group. Release Group: {0}. File release group: {1}",
|
||||
releaseGroup,
|
||||
fileReleaseGroup);
|
||||
return Decision.Reject(
|
||||
return DownloadSpecDecision.Reject(
|
||||
DownloadRejectionReason.RepackReleaseGroupDoesNotMatch,
|
||||
"Release is a repack for a different release group. Release Group: {0}. File release group: {1}",
|
||||
releaseGroup,
|
||||
fileReleaseGroup);
|
||||
@@ -78,7 +79,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RequiredIndexerFlagsSpecification : IDecisionEngineSpecification
|
||||
public class RequiredIndexerFlagsSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly Logger _logger;
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = subject.Release;
|
||||
|
||||
@@ -37,34 +37,35 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (torrentInfo == null || indexerSettings == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (indexerSettings is ITorrentIndexerSettings torrentIndexerSettings)
|
||||
{
|
||||
var requiredFlags = torrentIndexerSettings.RequiredFlags;
|
||||
var requiredFlag = (IndexerFlags)0;
|
||||
|
||||
if (requiredFlags == null || !requiredFlags.Any())
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var requiredFlag = (IndexerFlags)0;
|
||||
|
||||
foreach (var flag in requiredFlags)
|
||||
{
|
||||
if (torrentInfo.IndexerFlags.HasFlag((IndexerFlags)flag))
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
requiredFlag |= (IndexerFlags)flag;
|
||||
}
|
||||
|
||||
_logger.Debug("None of the required indexer flags {0} where found. Found flags: {1}", requiredFlag, torrentInfo.IndexerFlags);
|
||||
return Decision.Reject("None of the required indexer flags {0} where found. Found flags: {1}", requiredFlag, torrentInfo.IndexerFlags);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RequiredFlags, "None of the required indexer flags {0} where found. Found flags: {1}", requiredFlag, torrentInfo.IndexerFlags);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RetentionSpecification : IDecisionEngineSpecification
|
||||
public class RetentionSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -19,12 +19,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
|
||||
{
|
||||
_logger.Debug("Not checking retention requirement for non-usenet report");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var age = subject.Release.Age;
|
||||
@@ -34,10 +34,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (retention > 0 && age > retention)
|
||||
{
|
||||
_logger.Debug("Report age: {0} rejected by user's retention limit", age);
|
||||
return Decision.Reject("Older than configured retention");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MaximumAge, "Older than configured retention");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class AvailabilitySpecification : IDecisionEngineSpecification
|
||||
public class AvailabilitySpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -19,22 +19,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria is { UserInvokedSearch: true })
|
||||
{
|
||||
_logger.Debug("Skipping availability check during search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var availabilityDelay = _configService.AvailabilityDelay;
|
||||
|
||||
if (!subject.Movie.IsAvailable(availabilityDelay))
|
||||
{
|
||||
return Decision.Reject("Movie {0} will only be considered available {1} days after {2}", subject.Movie, availabilityDelay, subject.Movie.MinimumAvailability.ToString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Availability, "Movie {0} will only be considered available {1} days after {2}", subject.Movie, availabilityDelay, subject.Movie.MinimumAvailability.ToString());
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class DelaySpecification : IDecisionEngineSpecification
|
||||
public class DelaySpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IPendingReleaseService _pendingReleaseService;
|
||||
private readonly IUpgradableSpecification _qualityUpgradableSpecification;
|
||||
@@ -32,12 +32,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null && searchCriteria.UserInvokedSearch)
|
||||
{
|
||||
_logger.Debug("Ignoring delay for user invoked search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var profile = subject.Movie.QualityProfile;
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (delay == 0)
|
||||
{
|
||||
_logger.Debug("Delay Profile does not require a waiting period before download for {0}.", subject.Release.DownloadProtocol);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Delay Profile requires a waiting period of {0} minutes for {1}", delay, subject.Release.DownloadProtocol);
|
||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (revisionUpgrade)
|
||||
{
|
||||
_logger.Debug("New quality is a better revision for existing quality, skipping delay");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (isBestInProfile && isPreferredProtocol)
|
||||
{
|
||||
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (score >= minimum && isPreferredProtocol)
|
||||
{
|
||||
_logger.Debug("Custom format score ({0}) meets minimum ({1}) for preferred protocol, will not delay", score, minimum);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,16 +109,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (oldest != null && oldest.Release.AgeMinutes > delay)
|
||||
{
|
||||
_logger.Debug("Oldest pending release {0} has been delayed for {1}, longer than the set delay of {2}. Release will be accepted", oldest.Release.Title, oldest.Release.AgeMinutes, delay);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Release.AgeMinutes < delay)
|
||||
{
|
||||
_logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol);
|
||||
return Decision.Reject("Waiting for better quality release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAgeDelay, "Waiting for better quality release");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class HistorySpecification : IDecisionEngineSpecification
|
||||
public class HistorySpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
@@ -33,12 +33,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
_logger.Debug("Skipping history check during search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
if (!recent && cdhEnabled)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var customFormats = _formatService.ParseCustomFormat(mostRecent, subject.Movie);
|
||||
@@ -77,10 +77,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
if (recent)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryRecentCutoffMet, "Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCdhDisabledCutoffMet, "CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
var rejectionSubject = recent ? "Recent" : "CDH is disabled and";
|
||||
@@ -88,32 +88,32 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
switch (upgradeableRejectReason)
|
||||
{
|
||||
case UpgradeableRejectReason.None:
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
|
||||
case UpgradeableRejectReason.BetterQuality:
|
||||
return Decision.Reject("{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryHigherPreference, "{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality);
|
||||
|
||||
case UpgradeableRejectReason.BetterRevision:
|
||||
return Decision.Reject("{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryHigherRevision, "{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision);
|
||||
|
||||
case UpgradeableRejectReason.QualityCutoff:
|
||||
return Decision.Reject("{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCutoffMet, "{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatCutoff:
|
||||
return Decision.Reject("{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatCutoffMet, "{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatScore:
|
||||
return Decision.Reject("{0} grab event in history has an equal or higher Custom Format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScore, "{0} grab event in history has an equal or higher Custom Format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
|
||||
case UpgradeableRejectReason.MinCustomFormatScore:
|
||||
return Decision.Reject("{0} grab event in history has Custom Format score within Custom Format score increment: {1}", rejectionSubject, qualityProfile.MinUpgradeFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScoreIncrement, "{0} grab event in history has Custom Format score within Custom Format score increment: {1}", rejectionSubject, qualityProfile.MinUpgradeFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.UpgradesNotAllowed:
|
||||
return Decision.Reject("{0} grab event in history and Quality Profile '{1}' does not allow upgrades", rejectionSubject, qualityProfile.Name);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryUpgradesNotAllowed, "{0} grab event in history and Quality Profile '{1}' does not allow upgrades", rejectionSubject, qualityProfile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class IndexerTagSpecification : IDecisionEngineSpecification
|
||||
public class IndexerTagSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
@@ -22,11 +22,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release == null || subject.Movie?.Tags == null || subject.Release.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
// If indexer has tags, check that at least one of them is present on the series
|
||||
@@ -47,10 +47,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
_logger.Debug("Indexer {0} has tags. None of these are present on movie {1}. Rejecting", subject.Release.Indexer, subject.Movie);
|
||||
|
||||
return Decision.Reject("Movie tags do not match any of the indexer tags");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.NoMatchingTag, "Movie tags do not match any of the indexer tags");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class MonitoredMovieSpecification : IDecisionEngineSpecification
|
||||
public class MonitoredMovieSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -16,23 +16,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
if (searchCriteria.UserInvokedSearch)
|
||||
{
|
||||
_logger.Debug("Skipping monitored check during search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
|
||||
if (!subject.Movie.Monitored)
|
||||
{
|
||||
return Decision.Reject("Movie is not monitored");
|
||||
_logger.Debug("{0} is present in the DB but not tracked. Rejecting", subject.Movie);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MovieNotMonitored, "Movie is not monitored");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class ProperSpecification : IDecisionEngineSpecification
|
||||
public class ProperSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -23,11 +23,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
@@ -35,12 +35,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
|
||||
{
|
||||
_logger.Debug("Propers are not preferred, skipping check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Movie.MovieFile == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var file = subject.Movie.MovieFile;
|
||||
@@ -50,17 +50,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.PropersDisabled, "Proper downloading is disabled");
|
||||
}
|
||||
|
||||
if (file.DateAdded < DateTime.Today.AddDays(-7))
|
||||
{
|
||||
_logger.Debug("Proper for old file, rejecting: {0}", subject);
|
||||
return Decision.Reject("Proper for old file");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProperForOldFile, "Proper for old file");
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class MovieSpecification : IDecisionEngineSpecification
|
||||
public class MovieSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if movie matches searched movie");
|
||||
@@ -28,10 +28,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
if (subject.Movie.Id != searchCriteria.Movie.Id)
|
||||
{
|
||||
_logger.Debug("Movie {0} does not match {1}", subject.Movie, searchCriteria.Movie);
|
||||
return Decision.Reject("Wrong movie");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongMovie, "Wrong movie");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class TorrentSeedingSpecification : IDecisionEngineSpecification
|
||||
public class TorrentSeedingSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,13 +20,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = subject.Release as TorrentInfo;
|
||||
|
||||
if (torrentInfo == null || torrentInfo.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
||||
@@ -49,11 +49,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (torrentInfo.Seeders.HasValue && torrentInfo.Seeders.Value < minimumSeeders)
|
||||
{
|
||||
_logger.Debug("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
return Decision.Reject("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumSeeders, "Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeAllowedSpecification : IDecisionEngineSpecification
|
||||
public class UpgradeAllowedSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly ICustomFormatCalculationService _formatService;
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var qualityProfile = subject.Movie.QualityProfile;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (file == null)
|
||||
{
|
||||
_logger.Debug("File is no longer available, skipping this file.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
file.Movie = subject.Movie;
|
||||
@@ -50,11 +50,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
_logger.Debug("Upgrading is not allowed by the quality profile");
|
||||
|
||||
return Decision.Reject("Existing file and the Quality profile does not allow upgrades");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QualityUpgradesDisabled, "Existing file and the Quality profile does not allow upgrades");
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeDiskSpecification : IDecisionEngineSpecification
|
||||
public class UpgradeDiskSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly ICustomFormatCalculationService _formatService;
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var qualityProfile = subject.Movie.QualityProfile;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (file == null)
|
||||
{
|
||||
_logger.Debug("File is no longer available, skipping this file.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
file.Movie = subject.Movie;
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var cutoff = qualityProfile.UpgradeAllowed ? qualityProfile.Cutoff : qualityProfile.FirststAllowedQuality().Id;
|
||||
var qualityCutoff = qualityProfile.Items[qualityProfile.GetIndex(cutoff).Index];
|
||||
|
||||
return Decision.Reject("Existing file meets cutoff: {0} [{1}]", qualityCutoff, customFormats.ConcatToString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCutoffMet, "Existing file meets cutoff: {0} [{1}]", qualityCutoff, customFormats.ConcatToString());
|
||||
}
|
||||
|
||||
var upgradeableRejectReason = _upgradableSpecification.IsUpgradable(qualityProfile,
|
||||
@@ -64,31 +64,31 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
switch (upgradeableRejectReason)
|
||||
{
|
||||
case UpgradeableRejectReason.None:
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
|
||||
case UpgradeableRejectReason.BetterQuality:
|
||||
return Decision.Reject("Existing file on disk is of equal or higher preference: {0}", file.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskHigherPreference, "Existing file on disk is of equal or higher preference: {0}", file.Quality);
|
||||
|
||||
case UpgradeableRejectReason.BetterRevision:
|
||||
return Decision.Reject("Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskHigherRevision, "Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision);
|
||||
|
||||
case UpgradeableRejectReason.QualityCutoff:
|
||||
return Decision.Reject("Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCutoffMet, "Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatCutoff:
|
||||
return Decision.Reject("Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatCutoffMet, "Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatScore:
|
||||
return Decision.Reject("Existing file on disk has a equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatScore, "Existing file on disk has a equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
|
||||
case UpgradeableRejectReason.MinCustomFormatScore:
|
||||
return Decision.Reject("Existing file on disk has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatScoreIncrement, "Existing file on disk has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.UpgradesNotAllowed:
|
||||
return Decision.Reject("Existing file on disk and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskUpgradesNotAllowed, "Existing file on disk and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,13 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
{
|
||||
foreach (var folder in _diskScanService.FilterPaths(watchFolder, _diskProvider.GetDirectories(watchFolder)))
|
||||
{
|
||||
var title = FileNameBuilder.CleanFileName(Path.GetFileName(folder));
|
||||
var folderName = Path.GetFileName(folder);
|
||||
var title = FileNameBuilder.CleanFileName(folderName);
|
||||
|
||||
if (SpecialFolders.IsSpecialFolder(folderName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var newWatchItem = new WatchFolderItem
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
@@ -104,6 +105,8 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
|
||||
public override string Name => "uTorrent";
|
||||
|
||||
public override ProviderMessage Message => new (_localizationService.GetLocalizedString("DownloadClientUTorrentProviderMessage"), ProviderMessageType.Warning);
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
|
||||
public class TrackedDownloadService : ITrackedDownloadService,
|
||||
IHandle<MovieAddedEvent>,
|
||||
IHandle<MovieEditedEvent>,
|
||||
IHandle<MoviesBulkEditedEvent>,
|
||||
IHandle<MoviesDeletedEvent>
|
||||
{
|
||||
private readonly IParsingService _parsingService;
|
||||
@@ -272,6 +274,38 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(MovieEditedEvent message)
|
||||
{
|
||||
var cachedItems = _cache.Values
|
||||
.Where(t =>
|
||||
t.RemoteMovie?.Movie != null &&
|
||||
(t.RemoteMovie.Movie.Id == message.Movie?.Id || t.RemoteMovie.Movie.TmdbId == message.Movie?.TmdbId))
|
||||
.ToList();
|
||||
|
||||
if (cachedItems.Any())
|
||||
{
|
||||
cachedItems.ForEach(UpdateCachedItem);
|
||||
|
||||
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(MoviesBulkEditedEvent message)
|
||||
{
|
||||
var cachedItems = _cache.Values
|
||||
.Where(t =>
|
||||
t.RemoteMovie?.Movie != null &&
|
||||
message.Movies.Any(m => m.Id == t.RemoteMovie.Movie.Id || m.TmdbId == t.RemoteMovie.Movie.TmdbId))
|
||||
.ToList();
|
||||
|
||||
if (cachedItems.Any())
|
||||
{
|
||||
cachedItems.ForEach(UpdateCachedItem);
|
||||
|
||||
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(MoviesDeletedEvent message)
|
||||
{
|
||||
var cachedItems = _cache.Values
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
try
|
||||
{
|
||||
var status = client.GetStatus();
|
||||
var folders = status.OutputRootFolders.Where(folder => rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)));
|
||||
var folders = rootFolders.Where(r => status.OutputRootFolders.Any(folder => r.Path.PathEquals(folder.FullPath) || r.Path.IsParentPath(folder.FullPath)));
|
||||
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
_localizationService.GetLocalizedString("DownloadClientRootFolderHealthCheckMessage", new Dictionary<string, object>
|
||||
{
|
||||
{ "downloadClientName", client.Definition.Name },
|
||||
{ "rootFolderPath", folder.FullPath }
|
||||
{ "rootFolderPath", folder.Path }
|
||||
}),
|
||||
"#downloads-in-root-folder");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class CleanupDuplicateMovieTranslations : IHousekeepingTask
|
||||
{
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public CleanupDuplicateMovieTranslations(IMainDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
using var mapper = _database.OpenConnection();
|
||||
|
||||
mapper.Execute(@"DELETE FROM ""MovieTranslations""
|
||||
WHERE ""Id"" IN (
|
||||
SELECT MAX(""Id"") FROM ""MovieTranslations""
|
||||
GROUP BY ""MovieMetadataId"", ""Language""
|
||||
HAVING COUNT(""Id"") > 1
|
||||
)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,33 @@
|
||||
using System.Runtime.Serialization;
|
||||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
{
|
||||
public enum TraktPopularListType
|
||||
{
|
||||
[EnumMember(Value = "Trending Movies")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTrendingMovies")]
|
||||
Trending = 0,
|
||||
[EnumMember(Value = "Popular Movies")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypePopularMovies")]
|
||||
Popular = 1,
|
||||
[EnumMember(Value = "Top Anticipated Movies")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTopAnticipatedMovies")]
|
||||
Anticipated = 2,
|
||||
[EnumMember(Value = "Top Box Office Movies")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTopBoxOfficeMovies")]
|
||||
BoxOffice = 3,
|
||||
|
||||
[EnumMember(Value = "Top Watched Movies By Week")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByWeek")]
|
||||
TopWatchedByWeek = 4,
|
||||
[EnumMember(Value = "Top Watched Movies By Month")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByMonth")]
|
||||
TopWatchedByMonth = 5,
|
||||
[EnumMember(Value = "Top Watched Movies By Year")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByYear")]
|
||||
TopWatchedByYear = 6,
|
||||
[EnumMember(Value = "Top Watched Movies Of All Time")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeTopWatchedMoviesOfAllTime")]
|
||||
TopWatchedByAllTime = 7,
|
||||
[EnumMember(Value = "Recommended Movies By Week")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeRecommendedMoviesByWeek")]
|
||||
RecommendedByWeek = 8,
|
||||
[EnumMember(Value = "Recommended Movies By Month")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeRecommendedMoviesByMonth")]
|
||||
RecommendedByMonth = 9,
|
||||
[EnumMember(Value = "Recommended Movies By Year")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeRecommendedMoviesByYear")]
|
||||
RecommendedByYear = 10,
|
||||
[EnumMember(Value = "Recommended Movies Of All Time")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsPopularListTypeRecommendedMoviesOfAllTime")]
|
||||
RecommendedByAllTime = 11
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,21 +45,24 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
Years = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktPopularListType), HelpText = "Type of list you're seeking to import from")]
|
||||
[FieldDefinition(1, Label = "ImportListsTraktSettingsListType", Type = FieldType.Select, SelectOptions = typeof(TraktPopularListType), HelpText = "ImportListsTraktSettingsListTypeHelpText")]
|
||||
public int TraktListType { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Rating", HelpText = "Filter movies by rating range (0-100)")]
|
||||
[FieldDefinition(2, Label = "ImportListsTraktSettingsRating", HelpText = "ImportListsTraktSettingsRatingMovieHelpText")]
|
||||
public string Rating { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
|
||||
[FieldDefinition(3, Label = "ImportListsTraktSettingsCertification", HelpText = "ImportListsTraktSettingsCertificationMovieHelpText")]
|
||||
public string Certification { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
|
||||
[FieldDefinition(4, Label = "ImportListsTraktSettingsGenres", HelpText = "ImportListsTraktSettingsGenresMovieHelpText")]
|
||||
public string Genres { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Years", HelpText = "Filter movies by year or year range")]
|
||||
[FieldDefinition(5, Label = "ImportListsTraktSettingsYears", HelpText = "ImportListsTraktSettingsYearsMovieHelpText")]
|
||||
public string Years { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "ImportListsTraktSettingsAdditionalParameters", HelpText = "ImportListsTraktSettingsAdditionalParametersHelpText", Advanced = true)]
|
||||
public string TraktAdditionalParameters { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
}
|
||||
|
||||
public string Link => "https://api.trakt.tv";
|
||||
public virtual string Scope => "";
|
||||
|
||||
[FieldDefinition(0, Label = "Access Token", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AccessToken { get; set; }
|
||||
@@ -60,13 +59,10 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
[FieldDefinition(0, Label = "Auth User", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AuthUser { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Limit", HelpText = "Limit the number of movies to get")]
|
||||
[FieldDefinition(98, Label = "ImportListsTraktSettingsLimit", HelpText = "ImportListsTraktSettingsLimitMovieHelpText")]
|
||||
public int Limit { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Additional Parameters", HelpText = "Additional Trakt API parameters", Advanced = true)]
|
||||
public string TraktAdditionalParameters { get; set; }
|
||||
|
||||
[FieldDefinition(99, Label = "Authenticate with Trakt", Type = FieldType.OAuth)]
|
||||
[FieldDefinition(99, Label = "ImportListsTraktSettingsAuthenticateWithTrakt", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -25,10 +25,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
|
||||
public override IImportListRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new TraktUserRequestGenerator(_traktProxy)
|
||||
{
|
||||
Settings = Settings
|
||||
};
|
||||
return new TraktUserRequestGenerator(_traktProxy, Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System.Runtime.Serialization;
|
||||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
{
|
||||
public enum TraktUserListType
|
||||
{
|
||||
[EnumMember(Value = "User Watch List")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsUserListTypeWatch")]
|
||||
UserWatchList = 0,
|
||||
[EnumMember(Value = "User Watched List")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsUserListTypeWatched")]
|
||||
UserWatchedList = 1,
|
||||
[EnumMember(Value = "User Collection List")]
|
||||
[FieldOption(Label = "ImportListsTraktSettingsUserListTypeCollection")]
|
||||
UserCollectionList = 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Notifications.Trakt;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
@@ -8,11 +8,12 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
public class TraktUserRequestGenerator : IImportListRequestGenerator
|
||||
{
|
||||
private readonly ITraktProxy _traktProxy;
|
||||
public TraktUserSettings Settings { get; set; }
|
||||
private readonly TraktUserSettings _settings;
|
||||
|
||||
public TraktUserRequestGenerator(ITraktProxy traktProxy)
|
||||
public TraktUserRequestGenerator(ITraktProxy traktProxy, TraktUserSettings settings)
|
||||
{
|
||||
_traktProxy = traktProxy;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public virtual ImportListPageableRequestChain GetMovies()
|
||||
@@ -26,25 +27,39 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
|
||||
private IEnumerable<ImportListRequest> GetMoviesRequest()
|
||||
{
|
||||
var link = string.Empty;
|
||||
var userName = Settings.Username.IsNotNullOrWhiteSpace() ? Settings.Username.Trim() : Settings.AuthUser.Trim();
|
||||
var requestBuilder = new HttpRequestBuilder(_settings.Link.Trim());
|
||||
|
||||
switch (Settings.TraktListType)
|
||||
switch (_settings.TraktListType)
|
||||
{
|
||||
case (int)TraktUserListType.UserWatchList:
|
||||
link += $"users/{userName}/watchlist/movies?limit={Settings.Limit}";
|
||||
var watchSorting = _settings.TraktWatchSorting switch
|
||||
{
|
||||
(int)TraktUserWatchSorting.Added => "added",
|
||||
(int)TraktUserWatchSorting.Title => "title",
|
||||
(int)TraktUserWatchSorting.Released => "released",
|
||||
_ => "rank"
|
||||
};
|
||||
|
||||
requestBuilder
|
||||
.Resource("/users/{userName}/watchlist/movies/{sorting}")
|
||||
.SetSegment("sorting", watchSorting);
|
||||
break;
|
||||
case (int)TraktUserListType.UserWatchedList:
|
||||
link += $"users/{userName}/watched/movies?limit={Settings.Limit}";
|
||||
requestBuilder.Resource("/users/{userName}/watched/movies");
|
||||
break;
|
||||
case (int)TraktUserListType.UserCollectionList:
|
||||
link += $"users/{userName}/collection/movies?limit={Settings.Limit}";
|
||||
requestBuilder.Resource("/users/{userName}/collection/movies");
|
||||
break;
|
||||
}
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
var userName = _settings.Username.IsNotNullOrWhiteSpace() ? _settings.Username.Trim() : _settings.AuthUser.Trim();
|
||||
|
||||
yield return request;
|
||||
requestBuilder
|
||||
.SetSegment("userName", userName)
|
||||
.WithRateLimit(4)
|
||||
.AddQueryParam("limit", _settings.Limit.ToString());
|
||||
|
||||
yield return new ImportListRequest(_traktProxy.BuildRequest(requestBuilder.Build(), _settings.AccessToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,16 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
public TraktUserSettings()
|
||||
{
|
||||
TraktListType = (int)TraktUserListType.UserWatchList;
|
||||
TraktWatchSorting = (int)TraktUserWatchSorting.Rank;
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktUserListType), HelpText = "Type of list you're seeking to import from")]
|
||||
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktUserListType), HelpText = "ImportListsTraktSettingsListTypeHelpText")]
|
||||
public int TraktListType { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "Username for the List to import from (empty to use Auth User)")]
|
||||
[FieldDefinition(2, Label = "ImportListsTraktSettingsWatchListSorting", Type = FieldType.Select, SelectOptions = typeof(TraktUserWatchSorting), HelpText = "ImportListsTraktSettingsWatchListSortingHelpText")]
|
||||
public int TraktWatchSorting { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Username", HelpText = "ImportListsTraktSettingsUserListUsernameHelpText")]
|
||||
public string Username { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
@@ -33,4 +37,12 @@ namespace NzbDrone.Core.ImportLists.Trakt.User
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public enum TraktUserWatchSorting
|
||||
{
|
||||
Rank = 0,
|
||||
Added = 1,
|
||||
Title = 2,
|
||||
Released = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/download.php")
|
||||
.AddQueryParam("id", torrentId)
|
||||
.AddQueryParam("passkey", _settings.Passkey);
|
||||
.AddQueryParam("passkey", _settings.Passkey.Trim());
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Instrumentation
|
||||
|
||||
private void ReconfigureFile()
|
||||
{
|
||||
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
|
||||
foreach (var target in LogManager.Configuration.AllTargets.OfType<CleansingFileTarget>())
|
||||
{
|
||||
target.MaxArchiveFiles = _configFileProvider.LogRotate;
|
||||
target.ArchiveAboveSize = _configFileProvider.LogSizeLimit.Megabytes();
|
||||
@@ -120,11 +120,7 @@ namespace NzbDrone.Core.Instrumentation
|
||||
{
|
||||
var format = _configFileProvider.ConsoleLogFormat;
|
||||
|
||||
consoleTarget.Layout = format switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
|
||||
_ => NzbDroneLogger.ConsoleLogLayout
|
||||
};
|
||||
NzbDroneLogger.ConfigureConsoleLayout(consoleTarget, format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1083,5 +1083,9 @@
|
||||
"AutoTaggingSpecificationStatus": "الحالة",
|
||||
"CustomFormatsSpecificationLanguage": "لغة",
|
||||
"CustomFormatsSpecificationMaximumSize": "أكبر مقاس",
|
||||
"CustomFormatsSpecificationSource": "مصدر"
|
||||
"CustomFormatsSpecificationSource": "مصدر",
|
||||
"Mixed": "ثابت",
|
||||
"ImportListsTraktSettingsCertification": "شهادة",
|
||||
"ImportListsTraktSettingsGenres": "الأنواع",
|
||||
"ImportListsTraktSettingsRating": "التقييمات"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"AuthenticationMethodHelpText": "Изисквайте потребителско име и парола за достъп до {appName}",
|
||||
"AuthForm": "Формуляри (Страница за вход)",
|
||||
"AuthenticationMethodHelpText": "Изисква потребителско име и парола за достъп до {appName}",
|
||||
"AuthForm": "Форма (Страница за вход)",
|
||||
"MovieNaming": "Именуване на филми",
|
||||
"OpenBrowserOnStart": "Отворете браузъра при стартиране",
|
||||
"OnHealthIssue": "По здравен въпрос",
|
||||
@@ -730,7 +730,7 @@
|
||||
"ChmodFolderHelpText": "Осмично, приложено по време на импортиране / преименуване към медийни папки и файлове (без битове за изпълнение)",
|
||||
"ChmodFolderHelpTextWarning": "Това работи само ако потребителят, работещ с {appName}, е собственик на файла. По-добре е да се уверите, че клиентът за изтегляне правилно задава разрешенията.",
|
||||
"ChownGroupHelpText": "Име на група или gid. Използвайте gid за отдалечени файлови системи.",
|
||||
"AuthBasic": "Основно (изскачащ прозорец на браузъра)",
|
||||
"AuthBasic": "Базово (изскачащ прозорец на браузъра)",
|
||||
"Authentication": "Удостоверяване",
|
||||
"Announced": "Обявен",
|
||||
"ApplyTags": "Прилагане на тагове",
|
||||
@@ -1191,5 +1191,12 @@
|
||||
"DownloadClientFloodSettingsRemovalInfo": "{appName} ще се справи с автоматичното премахване на торенти въз основа на текущите критерии за сийд в Настройки -> Индексатори",
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Незадължително локация за изтеглянията, оставете празно, за да използвате локацията по подразбиране на Aria2",
|
||||
"BlocklistReleaseHelpText": "Блокира изтеглянето на тази версия от {appName} чрез RSS или автоматично търсене",
|
||||
"Disposition": "Разпореждане"
|
||||
"Disposition": "Разпореждане",
|
||||
"Delay": "Забавяне",
|
||||
"Label": "Етикет",
|
||||
"DownloadClientSettingsAddPaused": "Добави на пауза",
|
||||
"Theme": "Тема",
|
||||
"ImportListsTraktSettingsCertification": "Сертифициране",
|
||||
"ImportListsTraktSettingsGenres": "Жанрове",
|
||||
"ImportListsTraktSettingsRating": "Оценки"
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
"Genres": "Gèneres",
|
||||
"DownloadClientOptionsLoadError": "No es poden carregar les opcions del client de baixada",
|
||||
"ImportList": "Llista",
|
||||
"MappedNetworkDrivesWindowsService": "Les unitats de xarxa assignades no estan disponibles quan s'executen com a servei de Windows. Si us plau, consulteu les PMF per a obtenir més informació",
|
||||
"MappedNetworkDrivesWindowsService": "Les unitats de xarxa assignades no estan disponibles quan s'executen com a servei de Windows. Si us plau, consulteu les [PMF]{url} per a obtenir més informació.",
|
||||
"MissingMonitoredAndConsideredAvailable": "Absent (Monitorada)",
|
||||
"MissingNotMonitored": "Absent (No monitorada)",
|
||||
"Mode": "Mode",
|
||||
@@ -311,7 +311,7 @@
|
||||
"ICalIncludeUnmonitoredMoviesHelpText": "Inclou pel·lícules no monitorades al canal iCal",
|
||||
"UpdateAll": "Actualitzar-ho tot",
|
||||
"UpdateAutomaticallyHelpText": "Baixeu i instal·leu les actualitzacions automàticament. Encara podreu instal·lar des de Sistema: Actualitzacions",
|
||||
"UpdateAvailableHealthCheckMessage": "Nova actualització disponible",
|
||||
"UpdateAvailableHealthCheckMessage": "Nova actualització disponible: {version}",
|
||||
"Week": "Setmana",
|
||||
"WeekColumnHeader": "Capçalera de la columna de la setmana",
|
||||
"Weeks": "Setmanes",
|
||||
@@ -429,7 +429,7 @@
|
||||
"DeleteEmptyFolders": "Suprimeix les carpetes buides",
|
||||
"DeleteEmptyFoldersHelpText": "Suprimeix les carpetes de pel·lícules buides durant l'exploració del disc i quan s'esborren els fitxers de pel·lícules",
|
||||
"DeleteFile": "Esborrar Arxiu",
|
||||
"DeleteMovieFiles": "Suprimeix {0} fitxers de pel·lícula",
|
||||
"DeleteMovieFiles": "Suprimeix {movieFileCount} fitxers de pel·lícula",
|
||||
"DeleteSelectedMovieFiles": "Suprimeix els fitxers de pel·lícules seleccionats",
|
||||
"DeleteIndexer": "Suprimeix l'indexador",
|
||||
"DeleteIndexerMessageText": "Esteu segur que voleu suprimir l'indexador '{name}'?",
|
||||
@@ -1400,5 +1400,34 @@
|
||||
"CustomFormatsSpecificationMaximumYear": "Any màxim",
|
||||
"CustomFormatsSpecificationMinimumYear": "Any mínim",
|
||||
"CustomFormatsSpecificationResolution": "Resolució",
|
||||
"CustomFormatsSpecificationSource": "Font"
|
||||
"CustomFormatsSpecificationSource": "Font",
|
||||
"Mixed": "Combinat",
|
||||
"CountCustomFormatsSelected": "{count} format(s) personalitzat(s) seleccionat(s)",
|
||||
"BlocklistFilterHasNoItems": "El filtre de la llista de bloqueig seleccionat no conté elements",
|
||||
"CountVotes": "{votes} vots",
|
||||
"NotificationsKodiSettingsDisplayTime": "Temps de visualització",
|
||||
"TestParsing": "Prova anàlisi",
|
||||
"SubtitleLanguages": "Idiomes de subtítols",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Etiquetes addicionals",
|
||||
"DownloadClientFreeboxSettingsAppId": "Identificador de l'aplicació",
|
||||
"Install": "Instal·la",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "URL de l'API",
|
||||
"AnnouncedMovieAvailabilityDescription": "Les pel·lícules es consideren disponibles tan bon punt s'afegeixen a {appName}.",
|
||||
"NotificationsEmailSettingsBccAddress": "Adreça(es) BCC",
|
||||
"NotificationsKodiSettingsGuiNotification": "Notificació d'interfície gràfica",
|
||||
"MetadataKometaDeprecatedSetting": "Obsolet",
|
||||
"NotificationsMailgunSettingsSenderDomain": "Domini del remitent",
|
||||
"PasswordConfirmation": "Confirmeu la contrasenya",
|
||||
"UnknownEventTooltip": "Esdeveniment desconegut",
|
||||
"UpdateFiltered": "Actualitza filtrats",
|
||||
"Warning": "Advertència",
|
||||
"Disposition": "Disposició",
|
||||
"DownloadClientFloodSettingsAddPaused": "Afegeix pausats",
|
||||
"NotificationsAppriseSettingsTags": "Etiquetes d'Apprise",
|
||||
"NotificationsSettingsWebhookHeaders": "Capçaleres",
|
||||
"PreviouslyInstalled": "Instal·lat anteriorment",
|
||||
"DownloadClientSettingsAddPaused": "Afegeix pausats",
|
||||
"ImportListsTraktSettingsCertification": "Certificació",
|
||||
"ImportListsTraktSettingsRating": "Valoració",
|
||||
"ImportListsTraktSettingsGenres": "Gèneres"
|
||||
}
|
||||
|
||||
@@ -1263,5 +1263,48 @@
|
||||
"UsenetBlackholeNzbFolder": "Složka Nzb",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Další štítky",
|
||||
"ManualImportSetReleaseGroup": "Ruční import - nastavte skupinu vydání",
|
||||
"NotificationTriggersHelpText": "Vyber, které události mají vyvolat toto upozornění"
|
||||
"NotificationTriggersHelpText": "Vyber, které události mají vyvolat toto upozornění",
|
||||
"CustomFormatsSpecificationMinimumSizeHelpText": "Vydání musí být větší než tato velikost",
|
||||
"BlocklistAndSearchHint": "Začne hledat náhradu po blokaci",
|
||||
"CustomFormatsSpecificationExceptLanguage": "Vyjma jazyka",
|
||||
"CustomFormatsSpecificationExceptLanguageHelpText": "Odpovídá, pokud je přítomen jiný jazyk než vybraný",
|
||||
"DoNotBlocklist": "Nepřidávat do Seznamu blokování",
|
||||
"DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName} nemohl(a) přidat etiketu k {clientName}.",
|
||||
"Mixed": "Pevný",
|
||||
"BlocklistOnly": "Pouze seznam blokování",
|
||||
"BlocklistFilterHasNoItems": "Vybraný filtr blokování neobsahuje žádné položky",
|
||||
"CountCustomFormatsSelected": "{count} vybraný vlastní formát(y)",
|
||||
"BlocklistAndSearchMultipleHint": "Začne vyhledávat náhrady po blokaci",
|
||||
"DayOfWeekAt": "{day} v {time}",
|
||||
"ChangeCategoryMultipleHint": "Změní stahování do kategorie „Post-Import“ z aplikace Download Client",
|
||||
"DoNotBlocklistHint": "Odstraň bez přidání do seznamu blokování",
|
||||
"CutoffUnmetLoadError": "Chybné načítání nesplněných položek",
|
||||
"CutoffUnmetNoItems": "Žádné neodpovídající nesplněné položky",
|
||||
"ClickToChangeIndexerFlags": "Kliknutím změníte značky indexeru",
|
||||
"RecycleBinUnableToWriteHealthCheck": "Nelze zapisovat do nakonfigurované složky koše: {path}. Ujistěte se, že tato cesta existuje a že do ní může zapisovat uživatel se spuštěnou {appName}",
|
||||
"AutoTaggingSpecificationMaximumYear": "Maximální Rok",
|
||||
"AutoTaggingSpecificationMinimumYear": "Minimální Rok",
|
||||
"ChangeCategoryHint": "Změní stahování do kategorie „Post-Import“ z aplikace Download Client",
|
||||
"CustomFormatsSpecificationMaximumSizeHelpText": "Vydání musí odpovídat nebo být menší než tato velikost",
|
||||
"CustomFormatsSpecificationMaximumYear": "Maximální Rok",
|
||||
"CustomFormatsSpecificationMinimumYear": "Minimální Rok",
|
||||
"CustomFormatsSpecificationMinimumSize": "Minimální velikost",
|
||||
"DownloadClientDelugeSettingsDirectory": "Adresář stahování",
|
||||
"DownloadClientDelugeValidationLabelPluginFailure": "Konfigurace etikety selhala",
|
||||
"CountVotes": "{votes} hlasy",
|
||||
"CustomFormatsSpecificationRegularExpression": "Běžný výraz",
|
||||
"AutoTaggingSpecificationGenre": "Žánr(y)",
|
||||
"DeleteSelected": "Smazat vybrané",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexery s neplatnými klienty pro stahování: {indexerNames}.",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Dodatečné parametry",
|
||||
"ImportListsTraktSettingsRating": "Hodnocení",
|
||||
"ImportListsTraktSettingsCertification": "Osvědčení",
|
||||
"ImportListsTraktSettingsGenres": "Žánry",
|
||||
"DownloadClientDelugeValidationLabelPluginInactive": "Plugin pro štítky není aktivován",
|
||||
"DownloadClientDelugeTorrentStateError": "Deluge hlásí chybu",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Adresář kam přesunout po dokončení",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Nepovinné - umístění stahovaných souborů, pokud ponecháte prázné, použije se výchozí umístění Deluge",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Nepovinné - umístění kam přesunout dokončená stahování, pokud ponecháte prázné, použije se výchozí umístění Deluge",
|
||||
"DownloadClientDelugeValidationLabelPluginInactiveDetail": "Pro použití kategorií je nutné mít v {clientName} aktivovaný plugin pro štítky.",
|
||||
"DownloadClientDownloadStationProviderMessage": "Pokud je ve vašem účtu DSM zapnuto dvoufázové ověření, {appName} se nebude moci připojit k Download station"
|
||||
}
|
||||
|
||||
@@ -1105,5 +1105,9 @@
|
||||
"CustomFormatsSpecificationLanguage": "Sprog",
|
||||
"CustomFormatsSpecificationMaximumSize": "Maksimal størrelse",
|
||||
"CustomFormatsSpecificationResolution": "Opløsning",
|
||||
"CustomFormatsSpecificationSource": "Kilde"
|
||||
"CustomFormatsSpecificationSource": "Kilde",
|
||||
"Mixed": "Fast",
|
||||
"ImportListsTraktSettingsCertification": "Certifikation",
|
||||
"ImportListsTraktSettingsGenres": "Genrer",
|
||||
"ImportListsTraktSettingsRating": "Bedømmelse"
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
"Week": "Woche",
|
||||
"ICalLink": "iCal Link",
|
||||
"Language": "Sprache",
|
||||
"MediaManagementSettingsSummary": "Einstellungen für Bennenung und Dateiverwaltung",
|
||||
"MediaManagementSettingsSummary": "Einstellungen für Benennung und Dateiverwaltung",
|
||||
"Year": "Jahr",
|
||||
"Wanted": "Gesucht",
|
||||
"UpdateSelected": "Auswahl aktualisieren",
|
||||
@@ -166,7 +166,7 @@
|
||||
"QualitySettingsSummary": "Qualitätsgrößen und Namensgebung",
|
||||
"Protocol": "Protokoll",
|
||||
"Progress": "Fortschritt",
|
||||
"ProfilesSettingsSummary": "Profile für Qualität, Sprache und Verzögerung",
|
||||
"ProfilesSettingsSummary": "Qualitäts-, Sprach-, Verzögerungs- und Release-Profile",
|
||||
"PhysicalRelease": "VÖ Disc",
|
||||
"OutputPath": "Ausgabe-Pfad",
|
||||
"MovieTitle": "Filmtitel",
|
||||
@@ -457,7 +457,7 @@
|
||||
"RemoveFilter": "Filter entfernen",
|
||||
"RemoveFromQueue": "Aus der Warteschlange entfernen",
|
||||
"RenameMovies": "Filme umbenennen",
|
||||
"RenameMoviesHelpText": "Wenn das umbennen deaktiviert ist, wird der vorhandene Dateiname benutzt",
|
||||
"RenameMoviesHelpText": "Wenn das Umbenennen deaktiviert ist, wird {appName} den vorhandenen Dateiname verwenden.",
|
||||
"Reorder": "Neu anordnen",
|
||||
"ReplaceIllegalCharacters": "Illegale Zeichen ersetzen",
|
||||
"ReplaceIllegalCharactersHelpText": "Ersetze illegale Zeichen. Wenn nicht ausgewählt, werden sie stattdessen von {appName} entfernt",
|
||||
@@ -533,7 +533,7 @@
|
||||
"RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
|
||||
"AnalyseVideoFilesHelpText": "Videoinformationen wie Auflösung, Laufzeit und Codec aus Datien erkennen. Dazu ist es erforderlich, dass {appName} Teile der Datei liest, was zu hoher Festplatten- oder Netzwerkaktivität während der Scans führen kann.",
|
||||
"AddImportListExclusion": "Listenausschluss hinzufügen",
|
||||
"RequiredHelpText": "Diese {0} Bedingungen müsen zutreffen damit das eigene Format zutrifft. Ansonsten reicht ein einzelner {1} Treffer.",
|
||||
"RequiredHelpText": "Diese {implementationName} Bedingung muss übereinstimmen, damit das benutzerdefinierte Format angewendet wird. Andernfalls reicht eine einzelne {implementationName} Übereinstimmung aus.",
|
||||
"AllowHardcodedSubsHelpText": "Filme mit hartcodierten Untertiteln werden auch automatisch heruntergeladen",
|
||||
"ICalFeedHelpText": "Kopiere diese URL in deine(n) Client(s) oder klicke zum Abonnieren, wenn dein Browser webcal unterstützt",
|
||||
"NoMinimumForAnyRuntime": "Kein Minimum für beliebige Laufzeit",
|
||||
@@ -543,7 +543,7 @@
|
||||
"ApiKey": "API-Schlüssel",
|
||||
"ImportedTo": "Importiert nach",
|
||||
"Permissions": "Berechtigungen",
|
||||
"ImportExtraFilesMovieHelpText": "Importiere zutreffende Extra Dateien (Untertitel, nfo, etc.) nach dem Importieren einer Filmdatei",
|
||||
"ImportExtraFilesMovieHelpText": "Passende zusätzliche Dateien (Untertitel, NFO, etc.) nach dem Import eines Filmdateiformats importieren",
|
||||
"PreferIndexerFlags": "Bevorzugte Indexer Flags",
|
||||
"CopyUsingHardlinksMovieHelpText": "Hardlinks erlauben es {appName}, Torrents zu importieren die derzeit geseeded werden, ohne dabei weiteren Speicherplatz zu belegen oder die Datei vollständig zu kopieren. Hardlinks funktionieren nur, wenn sich die Quelle und das Ziel auf dem selben Volume befinden",
|
||||
"SearchForMovie": "Film suchen",
|
||||
@@ -609,7 +609,7 @@
|
||||
"IncludeRadarrRecommendations": "{appName} Empfehlungen einbeziehen",
|
||||
"ImportFailed": "Import fehlgeschlagen: {0}",
|
||||
"HiddenClickToShow": "Versteckt, zum Anzeigen anklicken",
|
||||
"GrabReleaseMessageText": "Das Release konnte keinem Film zugeordnet werden. Ein automatischer Import wird nicht möglich sein. Trotzdem '{0}' erfassen?",
|
||||
"GrabReleaseMessageText": "{appName} konnte nicht bestimmen, für welchen Film dieser Release ist. {appName} könnte diesen Release möglicherweise nicht automatisch importieren. Möchtest du '{0}' grabben?",
|
||||
"GoToInterp": "Zu {0} gehen",
|
||||
"ExistingTag": "Vorhandener Tag",
|
||||
"ExcludeMovie": "Film ausschließen",
|
||||
@@ -766,7 +766,7 @@
|
||||
"WhatsNew": "Was gibt's Neues?",
|
||||
"Weeks": "Wochen",
|
||||
"UsenetDisabled": "Usenet deaktiviert",
|
||||
"UsenetDelayTime": "Usenet Verzögerung: {0}",
|
||||
"UsenetDelayTime": "Usenet-Verzögerung: {usenetDelay}",
|
||||
"Uppercase": "Großgeschrieben",
|
||||
"UpgradeUntil": "Upgrade bis zur Qualität",
|
||||
"UpgradeUntilCustomFormatScore": "Upgrade bis zur benutzerdefinierten Formatbewertung",
|
||||
@@ -780,7 +780,7 @@
|
||||
"Trakt": "Trakt",
|
||||
"Trailer": "Trailer",
|
||||
"TorrentsDisabled": "Torrents deaktiviert",
|
||||
"TorrentDelayTime": "Torrent Verzögerung: {0}",
|
||||
"TorrentDelayTime": "Torrent-Verzögerung: {torrentDelay}",
|
||||
"TMDb": "TMDb",
|
||||
"ThisCannotBeCancelled": "Nach dem Start kann dies nicht mehr abgebrochen werden ohne alle Indexer zu deaktivieren.",
|
||||
"TheLogLevelDefault": "Die Protokollebene ist standardmäßig auf „Info“ eingestellt und kann unter „Allgemeine Einstellungen“ (/settings/general) geändert werden.",
|
||||
@@ -876,7 +876,7 @@
|
||||
"HttpHttps": "HTTP(S)",
|
||||
"Hours": "Stunden",
|
||||
"HomePage": "Hauptseite",
|
||||
"FolderMoveRenameWarning": "Dies wird auch den Filmordner nach dem Filmordnerformat aus den Einstellungen umbennen.",
|
||||
"FolderMoveRenameWarning": "Dies wird auch den Filmordner nach dem Filmordnerformat aus den Einstellungen umbenennen.",
|
||||
"FeatureRequests": "Feature Anfragen",
|
||||
"FailedToLoadMovieFromAPI": "Film konnte nicht über die API geladen werden",
|
||||
"ExternalUpdater": "{appName} ist so konfiguriert, dass es einen externen Aktualisierungsmechanismus verwendet",
|
||||
@@ -895,7 +895,7 @@
|
||||
"DockerUpdater": "aktualisiere den Docker Container um das Update zu erhalten",
|
||||
"Discord": "Discord",
|
||||
"DeleteMovieFolderConfirmation": "Der Filmordner und dessen Inhalt wird gelöscht.",
|
||||
"DeleteSelectedMovie": "Lösche ausgewählte Film/e",
|
||||
"DeleteSelectedMovie": "Lösche ausgewählten Film",
|
||||
"DeleteMovieFolder": "Filmordner löschen",
|
||||
"DeleteMovieFolderHelpText": "Lösche den Filmordner mitsamt Inhalt",
|
||||
"DeleteHeader": "Löschen - {0}",
|
||||
@@ -937,7 +937,7 @@
|
||||
"SqliteVersionCheckUpgradeRequiredMessage": "Die derzeit installierte SQLite-Version {0} wird nicht mehr unterstützt. Bitte aktualisieren Sie SQLite auf mindestens Version {1}.",
|
||||
"ShowCinemaRelease": "Erscheinungsdatum des Kinos anzeigen",
|
||||
"ShowReleaseDate": "Veröffentlichungsdatum anzeigen",
|
||||
"ShowReleaseDateHelpText": "Erscheinungsdatum unter Poster anzeigen",
|
||||
"ShowReleaseDateHelpText": "Veröffentlichungsdatum basierend auf der minimalen Verfügbarkeit unter dem Poster anzeigen",
|
||||
"OnMovieDelete": "Beim Löschen des Films",
|
||||
"OnMovieFileDelete": "Bei Filmdatei löschen",
|
||||
"OnMovieFileDeleteForUpgrade": "Bei Filmdatei Zum Upgrade löschen",
|
||||
@@ -1805,5 +1805,108 @@
|
||||
"CustomFormatsSpecificationMaximumSizeHelpText": "Das Release muss kleiner oder gleich Groß sein",
|
||||
"CustomFormatsSpecificationMaximumYear": "Höchstjahr",
|
||||
"CustomFormatsSpecificationMinimumSize": "Mindestgröße",
|
||||
"CustomFormatsSpecificationMinimumSizeHelpText": "Das Release muss größer als diese Größe sein"
|
||||
"CustomFormatsSpecificationMinimumSizeHelpText": "Das Release muss größer als diese Größe sein",
|
||||
"AnnouncedMovieAvailabilityDescription": "Filme werden als verfügbar erachtet sobald Sie zu {appName} hinzugefügt werden.",
|
||||
"AutoTaggingSpecificationMaximumRuntime": "Maximale Laufzeit",
|
||||
"AutoTaggingSpecificationMinimumRuntime": "Minimale Laufzeit",
|
||||
"DeleteMovieFolderMovieCount": "{movieFileCount} Film Dateien, total {size}",
|
||||
"DownloadFailedMovieTooltip": "Der Film Download ist fehlgeschlagen",
|
||||
"DownloadIgnoredMovieTooltip": "Film Download wurde ignoriert",
|
||||
"MetadataKometaDeprecatedSetting": "Veraltet",
|
||||
"NotificationsTelegramSettingsIncludeInstanceName": "Instanzname im Titel einfügen",
|
||||
"NotificationsTelegramSettingsIncludeInstanceNameHelpText": "Optional den Instanznamen in die Benachrichtigung einfügen",
|
||||
"Mixed": "Gemischt",
|
||||
"ReleasePush": "Veröffentlichung-Push",
|
||||
"ReleaseSource": "Veröffentlichungsquelle",
|
||||
"UserInvokedSearch": "Benutzerinitiierte Suche",
|
||||
"MetadataXmbcSettingsMovieMetadataHelpText": ".nfo mit vollständigen Film-Metadaten",
|
||||
"IncludeTrendingMoviesHelpText": "Trendige Filme auf TMDb einbeziehen",
|
||||
"ExistsInLibrary": "Existiert in der Bibliothek",
|
||||
"InvalidMovieInfoLanguageLanguage": "Die Sprache für Film-Informationen ist auf einen ungültigen Wert eingestellt, korrigiere ihn und speichere deine Einstellungen",
|
||||
"CustomFormatsSpecificationQualityModifier": "Qualitätsmodifikator",
|
||||
"Disposition": "Disposition",
|
||||
"EditionFootNote": "Steuere optional die Kürzung auf eine maximale Anzahl von Bytes, einschließlich Auslassungspunkten (`...`). Das Kürzen vom Ende (z. B. `{Edition Tags:30}`) oder vom Anfang (z. B. `{Edition Tags:-30}`) wird beide unterstützt.",
|
||||
"InCinemasMovieAvailabilityDescription": "Filme gelten als verfügbar, sobald sie in den Kinos erscheinen.",
|
||||
"IncludePopular": "Beliebte einbeziehen",
|
||||
"IncludePopularMoviesHelpText": "Beliebte Filme auf TMDb einbeziehen",
|
||||
"IncludeTrending": "Trendige Filme einbeziehen",
|
||||
"MetadataKometaDeprecated": "Kometa-Dateien werden nicht mehr erstellt, die Unterstützung wird in v6 vollständig entfernt",
|
||||
"MetadataXmbcSettingsMovieMetadataLanguageHelpText": "Die ausgewählte Sprache, falls verfügbar, in .nfo einbeziehen",
|
||||
"MetadataXmbcSettingsMovieMetadataNfoHelpText": "Metadaten in movie.nfo statt der standardmäßigen <movie-filename>.nfo schreiben",
|
||||
"MetadataXmbcSettingsMovieMetadataUrlHelpText": "TMDb- und IMDb-Film-URLs in .nfo einbeziehen",
|
||||
"MovieDownloaded": "Film heruntergeladen",
|
||||
"MovieFootNote": "Optional die Kürzung auf eine maximale Anzahl von Bytes einschließlich der Auslassungspunkte (`...`) steuern. Eine Kürzung vom Ende (z. B. `{Movie Title:30}`) oder vom Anfang (z. B. `{Movie Title:-30}`) wird beides unterstützt.",
|
||||
"MovieGrabbedTooltip": "Film von {indexer} abgerufen und an {downloadClient} gesendet",
|
||||
"MovieIsNotAvailable": "Film ist nicht verfügbar",
|
||||
"MovieIsPopular": "Film ist beliebt auf TMDb",
|
||||
"MovieIsTrending": "Film ist trendig auf TMDb",
|
||||
"MovieMissingFromDisk": "Film fehlt auf der Festplatte",
|
||||
"NewNonExcluded": "Neu, nicht ausgeschlossen",
|
||||
"NoMovieReleaseDatesAvailable": "Keine Veröffentlichungstermine auf [TMDb]({url}) für diesen Film verfügbar.",
|
||||
"NotificationsTelegramSettingsMetadataLinksMovieHelpText": "Füge Links zu den Film-Metadaten hinzu, wenn Benachrichtigungen gesendet werden",
|
||||
"ShowTraktRating": "Trakt-Bewertung anzeigen",
|
||||
"ShowTraktRatingPosterHelpText": "Trakt-Bewertung unter dem Poster anzeigen",
|
||||
"TraktRating": "Trakt-Bewertung",
|
||||
"TraktVotes": "Trakt-Stimmen",
|
||||
"NotificationsGotifySettingsMetadataLinksMovieHelpText": "Füge Links zu den Film-Metadaten hinzu, wenn Benachrichtigungen gesendet werden",
|
||||
"OnExcludedList": "Auf der Ausgeschlossenen Liste",
|
||||
"OrganizeNamingPattern": "Namensmuster: `{standardMovieFormat}`",
|
||||
"Popular": "Beliebt",
|
||||
"ReleasedMovieAvailabilityDescription": "Filme gelten als verfügbar, sobald die Blu-Ray- oder Streaming-Version veröffentlicht wird.",
|
||||
"SearchMoviesOnAdd": "Filme bei der Hinzufügung suchen",
|
||||
"Trending": "Trendig",
|
||||
"NotificationsSettingsUpdateMapPathsFromMovieHelpText": "{appName} Pfad, der verwendet wird, um Film-Pfade zu ändern, wenn {serviceName} den Bibliothekspfad anders sieht als {appName} (erfordert 'Bibliothek aktualisieren')",
|
||||
"NotificationsSettingsUpdateMapPathsToMovieHelpText": "{serviceName} Pfad, der verwendet wird, um Film-Pfade zu ändern, wenn {serviceName} den Bibliothekspfad anders sieht als {appName} (erfordert 'Bibliothek aktualisieren')",
|
||||
"MetadataSettingsMovieImages": "Film-Bilder",
|
||||
"MetadataSettingsMovieMetadata": "Film-Metadaten",
|
||||
"MetadataSettingsMovieMetadataCollectionName": "Name der Filmsammlung",
|
||||
"MetadataSettingsMovieMetadataLanguage": "Sprache der Film-Metadaten",
|
||||
"MetadataSettingsMovieMetadataNfo": "Verwende movie.nfo",
|
||||
"MetadataSettingsMovieMetadataUrl": "URL der Film-Metadaten",
|
||||
"MetadataXmbcSettingsMovieMetadataCollectionNameHelpText": "Sammlungsname in .nfo einbeziehen",
|
||||
"InteractiveSearchResultsFailedErrorMessage": "Die Suche ist fehlgeschlagen, da {message}. Versuche die Filminfos zu aktualisieren und überprüfe, ob die notwendigen Informationen vorhanden sind, bevor du erneut suchst.",
|
||||
"WhySearchesCouldBeFailing": "Klicke hier, um herauszufinden, warum die Suchen fehlschlagen könnten",
|
||||
"PopularityIndex": "Aktueller Beliebtheits-Index",
|
||||
"SearchMoviesConfirmationMessageText": "Bist du sicher, dass du eine Suche für {count} Film(e) durchführen möchtest?",
|
||||
"InteractiveImportNoMovie": "Film muss für jede ausgewählte Datei gewählt werden",
|
||||
"InteractiveImportNoLanguage": "Die Sprache muss für jede ausgewählte Datei gewählt werden",
|
||||
"InteractiveSearchModalHeaderTitle": "Interaktive Suche – {title}",
|
||||
"MovieImportedTooltip": "Film erfolgreich heruntergeladen und vom Download-Client abgeholt",
|
||||
"FailedToUpdateSettings": "Fehler beim Aktualisieren der Einstellungen",
|
||||
"ShowRottenTomatoesRating": "Tomato-Bewertung anzeigen",
|
||||
"MatchedToMovie": "Zu Film zugeordnet",
|
||||
"ShowTmdbRating": "TMDb-Bewertung anzeigen",
|
||||
"Popularity": "Beliebtheit",
|
||||
"MovieFileMissingTooltip": "Filmdatei fehlt",
|
||||
"MovieFolderImportedTooltip": "Film aus dem Filmordner importiert",
|
||||
"MovieImported": "Film importiert",
|
||||
"NoExtraFilesToManage": "Keine zusätzlichen Dateien zu verwalten.",
|
||||
"NotificationsPlexValidationNoMovieLibraryFound": "Mindestens eine Filmsammlung ist erforderlich",
|
||||
"MovieFileRenamedTooltip": "Filmdatei umbenannt",
|
||||
"ManageFiles": "Dateien verwalten",
|
||||
"ShowImdbRating": "IMDb-Bewertung anzeigen",
|
||||
"ShowImdbRatingHelpText": "IMDb-Bewertung unter dem Poster anzeigen",
|
||||
"ShowTmdbRatingHelpText": "TMDb-Bewertung unter dem Poster anzeigen",
|
||||
"MovieFileRenamed": "Filmdatei umbenannt",
|
||||
"NoMovieFilesToManage": "Keine Filmdateien zu verwalten.",
|
||||
"NotificationsGotifySettingIncludeMoviePosterHelpText": "Film-Poster in Nachricht einbeziehen",
|
||||
"NotificationsGotifySettingIncludeMoviePoster": "Film-Poster einbeziehen",
|
||||
"OverrideGrabNoMovie": "Ein Film muss ausgewählt werden",
|
||||
"ShowRottenTomatoesRatingHelpText": "Tomato-Bewertung unter dem Poster anzeigen",
|
||||
"NotificationsTagsMovieHelpText": "Benachrichtigungen nur für Filme mit mindestens einem übereinstimmenden Tag senden",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Zusätzliche Parameter",
|
||||
"ImportListsTraktSettingsAdditionalParametersHelpText": "Zusätzliche Trakt-API-Parameter",
|
||||
"ImportListsTraktSettingsGenres": "Genres",
|
||||
"ImportListsTraktSettingsListType": "Listentyp",
|
||||
"ImportListsTraktSettingsListTypeHelpText": "Typ der Liste, von der du importieren möchtest",
|
||||
"ImportListsTraktSettingsRating": "Bewertung",
|
||||
"ImportListsTraktSettingsYears": "Jahre",
|
||||
"ImportListsTraktSettingsAuthenticateWithTrakt": "Mit Trakt authentifizieren",
|
||||
"ImportListsTraktSettingsCertification": "Zertifizierung",
|
||||
"ImportListsTraktSettingsLimit": "Limit",
|
||||
"ImportListsTraktSettingsUserListTypeCollection": "Benutzer-Sammlungs-Liste",
|
||||
"ImportListsTraktSettingsUserListTypeWatch": "Benutzer-Watch-Liste",
|
||||
"ImportListsTraktSettingsUserListTypeWatched": "Benutzer-Gesehene-Liste",
|
||||
"ImportListsTraktSettingsWatchListSorting": "Sortierung der Gesehenen Liste",
|
||||
"ImportListsTraktSettingsUserListUsernameHelpText": "Benutzername für die Liste, von der du importieren möchtest (leer lassen, um den Authentifizierten Benutzer zu verwenden)"
|
||||
}
|
||||
|
||||
@@ -1241,5 +1241,9 @@
|
||||
"AutoTaggingSpecificationStatus": "Κατάσταση",
|
||||
"CustomFormatsSpecificationLanguage": "Γλώσσα",
|
||||
"CustomFormatsSpecificationMaximumSize": "Μέγιστο μέγεθος",
|
||||
"CustomFormatsSpecificationSource": "Πηγή"
|
||||
"CustomFormatsSpecificationSource": "Πηγή",
|
||||
"Mixed": "Σταθερός",
|
||||
"ImportListsTraktSettingsCertification": "Πιστοποιητικό",
|
||||
"ImportListsTraktSettingsGenres": "Είδη",
|
||||
"ImportListsTraktSettingsRating": "Ακροαματικότητα"
|
||||
}
|
||||
|
||||
@@ -537,6 +537,7 @@
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {downloadClientNames}",
|
||||
"DownloadClientTransmissionSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Transmission location",
|
||||
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Adds a prefix to the {clientName} rpc url, eg {url}, defaults to '{defaultUrl}'",
|
||||
"DownloadClientUTorrentProviderMessage": "Because uTorrent is known for cryptoware, malware and ads we'd suggest switching to a better client like qBittorrent, Deluge or ruTorrent.",
|
||||
"DownloadClientUTorrentTorrentStateError": "uTorrent is reporting an error",
|
||||
"DownloadClientUnavailable": "Download Client Unavailable",
|
||||
"DownloadClientValidationApiKeyIncorrect": "API Key Incorrect",
|
||||
@@ -767,6 +768,39 @@
|
||||
"ImportLists": "Import Lists",
|
||||
"ImportListsLoadError": "Unable to load Import Lists",
|
||||
"ImportListsSettingsSummary": "Import from another {appName} instance or Trakt lists and manage list exclusions",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Additional Parameters",
|
||||
"ImportListsTraktSettingsAdditionalParametersHelpText": "Additional Trakt API parameters",
|
||||
"ImportListsTraktSettingsAuthenticateWithTrakt": "Authenticate with Trakt",
|
||||
"ImportListsTraktSettingsCertification": "Certification",
|
||||
"ImportListsTraktSettingsCertificationMovieHelpText": "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17) (Comma Separated)",
|
||||
"ImportListsTraktSettingsGenres": "Genres",
|
||||
"ImportListsTraktSettingsGenresMovieHelpText": "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists",
|
||||
"ImportListsTraktSettingsLimit": "Limit",
|
||||
"ImportListsTraktSettingsLimitMovieHelpText": "Limit the number of movies to get",
|
||||
"ImportListsTraktSettingsListType": "List Type",
|
||||
"ImportListsTraktSettingsListTypeHelpText": "Type of list you're seeking to import from",
|
||||
"ImportListsTraktSettingsPopularListTypePopularMovies": "Popular Movies",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByMonth": "Recommended Movies By Month",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByWeek": "Recommended Movies By Week",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByYear": "Recommended Movies By Year",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesOfAllTime": "Recommended Movies By All Time",
|
||||
"ImportListsTraktSettingsPopularListTypeTopAnticipatedMovies": "Top Anticipated Movies",
|
||||
"ImportListsTraktSettingsPopularListTypeTopBoxOfficeMovies": "Top Box Office Movies",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByMonth": "Top Watched Movies By Month",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByWeek": "Top Watched Movies By Week",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByYear": "Top Watched Movies By Year",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesOfAllTime": "Top Watched Movies Of All Time",
|
||||
"ImportListsTraktSettingsPopularListTypeTrendingMovies": "Trending Movies",
|
||||
"ImportListsTraktSettingsRating": "Rating",
|
||||
"ImportListsTraktSettingsRatingMovieHelpText": "Filter movies by rating range (0-100)",
|
||||
"ImportListsTraktSettingsUserListTypeCollection": "User Collection List",
|
||||
"ImportListsTraktSettingsUserListTypeWatch": "User Watch List",
|
||||
"ImportListsTraktSettingsUserListTypeWatched": "User Watched List",
|
||||
"ImportListsTraktSettingsUserListUsernameHelpText": "Username for the List to import from (leave empty to use Auth User)",
|
||||
"ImportListsTraktSettingsWatchListSorting": "Watch List Sorting",
|
||||
"ImportListsTraktSettingsWatchListSortingHelpText": "If List Type is Watch, select the order to sort the list",
|
||||
"ImportListsTraktSettingsYears": "Years",
|
||||
"ImportListsTraktSettingsYearsMovieHelpText": "Filter movies by year or year range",
|
||||
"ImportMechanismHealthCheckMessage": "Enable Completed Download Handling",
|
||||
"ImportMovies": "Import Movies",
|
||||
"ImportNotForDownloads": "Do not use for importing downloads from your download client, this is only for existing organized libraries, not unsorted files.",
|
||||
@@ -929,6 +963,7 @@
|
||||
"Mechanism": "Mechanism",
|
||||
"MediaInfo": "Media Info",
|
||||
"MediaInfoFootNote": "MediaInfo Full/AudioLanguages/SubtitleLanguages support a `:EN+DE` suffix allowing you to filter the languages included in the filename. Use `-DE` to exclude specific languages. Appending `+` (eg `:EN+`) will output `[EN]`/`[EN+--]`/`[--]` depending on excluded languages. For example `{MediaInfo Full:EN+DE}`.",
|
||||
"MediaInfoFootNote2": "MediaInfo AudioLanguages excludes English if it is the only language. Use MediaInfo AudioLanguagesAll to include English-only",
|
||||
"MediaManagement": "Media Management",
|
||||
"MediaManagementSettings": "Media Management Settings",
|
||||
"MediaManagementSettingsLoadError": "Unable to load Media Management settings",
|
||||
@@ -1044,6 +1079,7 @@
|
||||
"MovieMissingFromDisk": "Movie missing from disk",
|
||||
"MovieNaming": "Movie Naming",
|
||||
"MovieOnly": "Movie Only",
|
||||
"MovieRequested": "Movie Requested",
|
||||
"MovieSearchResultsLoadError": "Unable to load results for this movie search. Try again later",
|
||||
"MovieTitle": "Movie Title",
|
||||
"MovieTitleToExcludeHelpText": "The title of the movie to exclude (can be anything meaningful)",
|
||||
|
||||
@@ -1889,5 +1889,42 @@
|
||||
"CustomFormatsSpecificationMaximumYear": "Año máximo",
|
||||
"AutoTaggingSpecificationMaximumRuntime": "Tiempo de ejecución máximo",
|
||||
"AutoTaggingSpecificationMinimumRuntime": "Tiempo de ejecución mínimo",
|
||||
"CustomFormatsSpecificationQualityModifier": "Modificador de calidad"
|
||||
"CustomFormatsSpecificationQualityModifier": "Modificador de calidad",
|
||||
"Mixed": "Mezclado",
|
||||
"MediaInfoFootNote2": "MediaInfo AudioLanguages excluye el inglés si es el único idioma. Usa MediaInfo AudioLanguagesAll para incluir solo el inglés",
|
||||
"UserInvokedSearch": "Búsqueda invocada por usuario",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Parámetros adicionales",
|
||||
"ImportListsTraktSettingsAdditionalParametersHelpText": "Parámetros adicionales de la API de Trakt",
|
||||
"ImportListsTraktSettingsAuthenticateWithTrakt": "Autenticar con Trakt",
|
||||
"ImportListsTraktSettingsCertification": "Certificación",
|
||||
"ImportListsTraktSettingsListType": "Tipo de lista",
|
||||
"ImportListsTraktSettingsRating": "Valoración",
|
||||
"ImportListsTraktSettingsYears": "Años",
|
||||
"ImportListsTraktSettingsGenres": "Géneros",
|
||||
"ImportListsTraktSettingsLimit": "Limitar",
|
||||
"ImportListsTraktSettingsListTypeHelpText": "Tipo de lista de la que deseas importar",
|
||||
"ImportListsTraktSettingsCertificationMovieHelpText": "Filtrar películas por certificación (NR,G,PG-13,R,NC-17) (Separados por comas)",
|
||||
"ImportListsTraktSettingsGenresMovieHelpText": "Filtrar películas por etiquetas de género de Trakt (separadas por coma) solo para listas populares",
|
||||
"ImportListsTraktSettingsLimitMovieHelpText": "Limitar el número de películas a obtener",
|
||||
"ImportListsTraktSettingsPopularListTypePopularMovies": "Películas populares",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByMonth": "Películas recomendadas por mes",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByWeek": "Películas recomendadas por semana",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByYear": "Películas recomendadas por año",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesOfAllTime": "Películas recomendadas de todos los tiempos",
|
||||
"ImportListsTraktSettingsPopularListTypeTopAnticipatedMovies": "Películas más esperadas",
|
||||
"ImportListsTraktSettingsPopularListTypeTopBoxOfficeMovies": "Películas más taquilleras",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByMonth": "Películas más vistas por mes",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByWeek": "Películas más vistas por semana",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByYear": "Películas más vistas por año",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesOfAllTime": "Películas más vistas de todos los tiempos",
|
||||
"ImportListsTraktSettingsPopularListTypeTrendingMovies": "Películas en tendencia",
|
||||
"ImportListsTraktSettingsRatingMovieHelpText": "Filtrar películas por rango de calificación (0-100)",
|
||||
"ImportListsTraktSettingsYearsMovieHelpText": "Filtras películas por año o rango de años",
|
||||
"MovieRequested": "Películas solicitadas",
|
||||
"ReleasePush": "Lanzamiento",
|
||||
"ImportListsTraktSettingsUserListTypeCollection": "Lista de colecciones de usuario",
|
||||
"ImportListsTraktSettingsUserListTypeWatch": "Lista de seguimiento de usuario",
|
||||
"ImportListsTraktSettingsUserListTypeWatched": "Lista de vistos de usuario",
|
||||
"ImportListsTraktSettingsUserListUsernameHelpText": "Usuario para la lista de la que importar (dejar vacío para usar Autenticación de usuario)",
|
||||
"ImportListsTraktSettingsWatchListSorting": "Ordenar la lista de vistos"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
{
|
||||
"About": "درباره"
|
||||
"About": "درباره",
|
||||
"DotNetVersion": ".NET",
|
||||
"Add": "افزودن",
|
||||
"AddAutoTag": "افزودن برچسب خودکار",
|
||||
"AddCondition": "افزودن شرط",
|
||||
"AddConnectionImplementation": "افزودن پیوند - {implementationName}",
|
||||
"AddDownloadClientImplementation": "افزودن کلاینت دانلود - {implementationName}",
|
||||
"AddImportList": "افزودن لیست واردات",
|
||||
"Docker": "Docker",
|
||||
"Usenet": "Usenet",
|
||||
"AddConditionError": "افزودن شرط جدید ناموفق بود، لطفا مجددا تلاش کنید.",
|
||||
"AddAutoTagError": "افزودن برچسب خودکار جدید ناموفق بود، لطفا مجددا تلاش کنید.",
|
||||
"AddDelayProfile": "افزودن نمایه تاخیر",
|
||||
"AddDelayProfileError": "افزودن نمایه تاخیر جدید ناموفق بود، لطفا مجددا تلاش کنید.",
|
||||
"AddImportListExclusion": "افزودن لیست واردات مستثنی",
|
||||
"ApiKey": "کلید API",
|
||||
"Torrents": "تورنت ها",
|
||||
"AddConditionImplementation": "افزودن شرط - {implementationName}",
|
||||
"AddConnection": "افزودن پیوند",
|
||||
"Actions": "اقدامات",
|
||||
"Activity": "فعالیت"
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"Branch": "Haara",
|
||||
"BuiltIn": "Sisäänrakennettu",
|
||||
"CalendarOptions": "Kalenterin asetukset",
|
||||
"Certification": "Varmennus",
|
||||
"Certification": "Ikäluokitus",
|
||||
"ChangeFileDate": "Muuta tiedoston päiväys",
|
||||
"AddIndexer": "Lisää hakupalvelu",
|
||||
"CheckForFinishedDownloadsInterval": "Valmistuneiden latausten takistusväli",
|
||||
@@ -258,7 +258,7 @@
|
||||
"Failed": "Epäonnistui",
|
||||
"InCinemas": "Teatterijulkaisu",
|
||||
"IncludeCustomFormatWhenRenaming": "Sisällytä mukautetut muodot uudelleennimettäessä",
|
||||
"IndexerFlags": "Tietolähteen liput",
|
||||
"IndexerFlags": "Hakupalvelun liput",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Hakupalvelut eivät ole käytettävissä yli kuusi tuntia kestäneiden virheiden vuoksi: {indexerNames}.",
|
||||
"IndexerStatusCheckAllClientMessage": "Hakupalvelut eivät ole virheiden vuoksi käytettävissä.",
|
||||
"Level": "Taso",
|
||||
@@ -294,7 +294,7 @@
|
||||
"LastExecution": "Edellinen suoritus",
|
||||
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
|
||||
"ImportListExclusions": "Listojen poikkeussäännöt",
|
||||
"Indexer": "Tietolähde",
|
||||
"Indexer": "Hakupalvelu",
|
||||
"MovieIsMonitored": "Elokuvaa valvotaan",
|
||||
"Updates": "Päivitykset",
|
||||
"AddImportListExclusion": "Lisää tuontilistapoikkeus",
|
||||
@@ -322,8 +322,8 @@
|
||||
"MovieTitleToExcludeHelpText": "Ohitettavan elokuvan nimi (voi olla miktä tahansa merkityksellistä).",
|
||||
"MovieYear": "Elokuvan vuosi",
|
||||
"TestAllClients": "Koesta latauspalvelut",
|
||||
"TestAllIndexers": "Tietolähteiden testaus",
|
||||
"TestAllLists": "Kaikkien listojen testaus",
|
||||
"TestAllIndexers": "Koesta pavelut",
|
||||
"TestAllLists": "Koesta listat",
|
||||
"AddRemotePathMapping": "Lisää etäsijainnin kohdistus",
|
||||
"Apply": "Käytä",
|
||||
"Analytics": "Analytiikka",
|
||||
@@ -473,8 +473,8 @@
|
||||
"DeleteMovieFiles": "Poista {movieFileCount} elokuvatiedostoa",
|
||||
"DeleteHeader": "Poistetaan – {0}",
|
||||
"DeleteImportListExclusion": "Poista tuontilistapoikkeus",
|
||||
"DeleteIndexer": "Poista tietolähde",
|
||||
"DeleteIndexerMessageText": "Haluatko varmasti poistaa tietolähteen '{name}'?",
|
||||
"DeleteIndexer": "Poista hakupalvelu",
|
||||
"DeleteIndexerMessageText": "Haluatko varmasti poistaa hakupalvelun \"{name}\"?",
|
||||
"DeleteMovieFolderHelpText": "Poista elokuvakansio ja sen sisältö",
|
||||
"DeleteNotification": "Poista ilmoituspalvelu",
|
||||
"DeleteNotificationMessageText": "Haluatko varmasti poistaa ilmoituspalvelun \"{name}\"?",
|
||||
@@ -593,14 +593,14 @@
|
||||
"IncludeRadarrRecommendations": "Sisällytä {appName}-suositukset",
|
||||
"IncludeRecommendationsHelpText": "Sisällytä {appName}in suosittelemat elokuvat etsintänäkymään",
|
||||
"ImportMovies": "Tuo elokuvia",
|
||||
"IndexerPriority": "Tietolähteiden painotus",
|
||||
"IndexerPriority": "Hakupalveluiden painotus",
|
||||
"IndexerPriorityHelpText": "Hakupalvelun painotus, 1– 50 (korkein-alin). Oletusarvo on 25. Käytetään muutoin tasaveroisten julkaisujen kaappauspäätökseen. {appName} käyttää edelleen kaikkia käytössä olevia hakupalveluita RSS-synkronointiin ja hakuun.",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "RSS-syötteitä tukevat tietolähteet eivät ole hiljattaisten tietolähdevirheiden vuoksi tilapaisesti käytettävissä.",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "RSS-syötteitä tukevat hakupalvelut eivät ole tilapäisesti käytettävissä hiljattaisten palveluvirheiden vuoksi.",
|
||||
"IndexerRssHealthCheckNoIndexers": "RSS-synkronoinnille ei ole määritetty hakupalveluita, eikä {appName} tämän vuoksi kaappaa uusia julkaisuja automaattisesti.",
|
||||
"Indexers": "Tietolähteet",
|
||||
"Indexers": "Hakupalvelut",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Automaattihaulle ei ole määritetty hakupalveluita, eikä {appName}in automaattihaku tämän vuoksi löydä tuloksia.",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Hakua tukevat hakupalvelut eivät ole hiljattaisten hakupalveluvirheiden vuoksi tilapäisesti käytettävissä.",
|
||||
"IndexerSettings": "Tietolähdeasetukset",
|
||||
"IndexerSettings": "Hakupalveluasetukset",
|
||||
"IndexerStatusCheckSingleClientMessage": "Hakupalvelut eivät ole virheiden vuoksi käytettävissä: {indexerNames}.",
|
||||
"InstallLatest": "Asenna uusin",
|
||||
"InteractiveImport": "Manuaalinen tuonti",
|
||||
@@ -876,8 +876,8 @@
|
||||
"Unlimited": "Rajoittamaton",
|
||||
"DownloadClientOptionsLoadError": "Latauspalveluasetusten lataus epäonnistui",
|
||||
"GeneralSettingsLoadError": "Virhe ladattaessa yleisasetuksia.",
|
||||
"IndexerOptionsLoadError": "Tietolähdeasetusten lataus epäonnistui",
|
||||
"IndexersLoadError": "Tietolähteiden lataus epäonnistui",
|
||||
"IndexerOptionsLoadError": "Virhe ladattaessa hakupalveluasetuksia.",
|
||||
"IndexersLoadError": "Virhe ladattaessa hakupalveluita.",
|
||||
"ImportListExclusionsLoadError": "Tuontilistapoikkeusten lataus epäonnistui",
|
||||
"ListOptionsLoadError": "Virhe ladattaessa lista-asetuksia.",
|
||||
"ImportListsLoadError": "Tuontilistojen lataus epäonnistui",
|
||||
@@ -993,7 +993,7 @@
|
||||
"DiscordUrlInSlackNotification": "Olet määrittänyt Discordin Slack-ilmoituksena. Määritä se Discord-ilmoituksena parempaa toiminnallisuutta varten. Koskee seuraavia: {0}.",
|
||||
"ManualImportSetReleaseGroup": "Manuaalinen tuonti– Aseta julkaisuryhmä",
|
||||
"AnnouncedMovieDescription": "Elokuva on julkistettu",
|
||||
"IndexerDownloadClientHelpText": "Määritä tämän tietolähteen kanssa käytettävä latauspalvelu.",
|
||||
"IndexerDownloadClientHelpText": "Määritä tämän hakupalvelun kanssa käytettävä latauspalvelu.",
|
||||
"SelectLanguages": "Valitse kielet",
|
||||
"SelectReleaseGroup": "Aseta julkaisuryhmä",
|
||||
"SetReleaseGroup": "Aseta julkaisuryhmä",
|
||||
@@ -1128,7 +1128,7 @@
|
||||
"ManageDownloadClients": "Palveluiden hallinta",
|
||||
"ManageIndexers": "Palveluiden hallinta",
|
||||
"MovieImportedTooltip": "Elokuva ladattiin ja poimittiin latauspalvelulta.",
|
||||
"NoIndexersFound": "Tietolähteitä ei löytynyt",
|
||||
"NoIndexersFound": "Hakupalveluita ei löytynyt",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Ilmoituspalvelut eivät ole ongelmien vuoksi käytettävissä: {notificationNames}.",
|
||||
"OrganizeNothingToRename": "Valmis! Toiminto on suoritettu, eikä uudelleennimettäviä tiedostoja ole.",
|
||||
"PackageVersionInfo": "{packageVersion} julkaisijalta {packageAuthor}",
|
||||
@@ -1155,17 +1155,17 @@
|
||||
"RemoveQueueItem": "Poistetaan – {sourceTitle}",
|
||||
"TablePageSizeMaximum": "Sivukohtainen kohdemäärä ei voi olla suurempi kuin {maximumValue}.",
|
||||
"TablePageSizeMinimum": "Sivukohtaisen kohdemäärän on oltava vähintään {minimumValue}.",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Tunnisteiden käyttö valituille latauspalveluille",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Tunnisteiden käyttö valituille tuontilistoille",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Tunnisteiden käyttö valituille latauspalveluille:",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Tunnisteiden käyttö valituille tuontilistoille:",
|
||||
"SelectFolderModalTitle": "{modalTitle} – Valitse kansio",
|
||||
"OverrideGrabModalTitle": "Ohitetaan ja kaapataan – {title}",
|
||||
"OverrideAndAddToDownloadQueue": "Ohita ja lisää latausjonoon",
|
||||
"TestParsing": "Testaa jäsennystä",
|
||||
"TestParsing": "Koesta jäsennys",
|
||||
"DeleteRootFolder": "Poista juurikansio",
|
||||
"DeleteRootFolderMessageText": "Haluatko varmasti poistaa juurikansion \"{path}\"?",
|
||||
"DisabledForLocalAddresses": "Ei käytössä paikallisissa osoitteissa",
|
||||
"NoDownloadClientsFound": "Latauspalveluita ei löytynyt",
|
||||
"ManageFiles": "Tiedostojen hallinta",
|
||||
"ManageFiles": "Hallitse tiedostoja",
|
||||
"OrganizeLoadError": "Virhe ladattaessa esikatseluita",
|
||||
"OrganizeModalHeader": "Järjestele ja uudelleennimeä",
|
||||
"RemoveQueueItemConfirmation": "Haluatko varmasti poistaa kohteen \"{sourceTitle}\" jonosta?",
|
||||
@@ -1185,7 +1185,7 @@
|
||||
"RemoveFailedDownloads": "Poista epäonnistuneet lataukset",
|
||||
"UpdaterLogFiles": "Päivittäjän lokitiedostot",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Latauspalvelu {downloadClientName} on määritetty poistamaan valmistuneet lataukset, jonka seuraksena ne saatetaan poistaa ennen kuin {appName} ehtii tuoda niitä.",
|
||||
"DownloadClientsLoadError": "Latauspalveluiden lataus epäonnistui",
|
||||
"DownloadClientsLoadError": "Virhe ladattaessa latauspalveluita.",
|
||||
"RemoveSelectedBlocklistMessageText": "Haluatko varmasti poistaa valitut kohteet estolistalta?",
|
||||
"TableOptionsButton": "Taulukon asetuspainike",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Syötä uusi käyttäjätunnus",
|
||||
@@ -1195,7 +1195,7 @@
|
||||
"EditAutoTag": "Muokkaa automaattimerkintää",
|
||||
"EditConditionImplementation": "Muokataan ehtoa – {implementationName}",
|
||||
"EditImportListImplementation": "Muokataan tuontilistaa – {implementationName}",
|
||||
"EditIndexerImplementation": "Muokataan tietolähdettä – {implementationName}",
|
||||
"EditIndexerImplementation": "Muokataan hakupalvelua – {implementationName}",
|
||||
"PendingDownloadClientUnavailable": "Odottaa – Latauspalvelu ei ole käytettävissä",
|
||||
"ApplyChanges": "Toteuta muutokset",
|
||||
"AddRootFolderError": "Virhe lisättäessä juurikansiota.",
|
||||
@@ -1466,7 +1466,7 @@
|
||||
"Rejections": "Hylkäykset",
|
||||
"CutoffNotMet": "Katkaisutasoa ei ole saavutettu",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashes": "Hylkää estetyt torrent-hajautusarvot kaapattaessa",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Jos torrent on estetty hajautusarvon perusteella sitä ei välttämättä hylätä oikein joidenkin tietolähteiden RSS-syötteestä tai hausta. Tämän käyttöönotto mahdollistaa tällaisten torrentien hylkäämisen kaappauksen jälkeen, kuitenkin ennen kuin niitä välitetään latauspalvelulle.",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Jos torrent on estetty hajautusarvon perusteella sitä ei välttämättä hylätä oikein joidenkin hakupalveluiden RSS-syötteestä tai hausta. Tämän käyttöönotto mahdollistaa tällaisten torrentien hylkäämisen kaappauksen jälkeen, kuitenkin ennen kuin niitä välitetään latauspalvelulle.",
|
||||
"MovieFileMissingTooltip": "Elokuvatiedosto puuttuu",
|
||||
"NotificationsAppriseSettingsServerUrlHelpText": "Apprise-palvelimen URL-osoite. SIsällytä myös http(s):// ja portti (tarvittaessa).",
|
||||
"NotificationsAppriseSettingsServerUrl": "Apprise-palvelimen URL",
|
||||
@@ -1727,7 +1727,7 @@
|
||||
"UrlBaseHelpText": "Käänteisen välityspalvelimen tukea varten. Oletusarvo on tyhjä.",
|
||||
"IndexerSettingsMultiLanguageRelease": "Useat kielet",
|
||||
"IndexerSettingsSeedRatio": "Jakosuhde",
|
||||
"IndexerSettingsSeedRatioHelpText": "Suhde, joka torrentin tulee saavuttaa ennen sen pysäytystä. Käytä latauspalvelun oletusta jättämällä tyhjäksi. Suhteen tulisi olla ainakin 1.0 ja noudattaa tietolähteen sääntöjä.",
|
||||
"IndexerSettingsSeedRatioHelpText": "Suhde, joka torrentin tulee saavuttaa ennen sen pysäytystä. Käytä latauspalvelun oletusta jättämällä tyhjäksi. Suhteen tulisi olla ainakin 1.0 ja noudattaa hakupalvelun sääntöjä.",
|
||||
"IndexerSettingsSeedTime": "Jakoaika",
|
||||
"IndexerSettingsSeedTimeHelpText": "Aika, joka torrentia tulee jakaa ennen sen pysäytystä. Käytä latauspalvelun oletusta jättämällä tyhjäksi.",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Vaihtoehtoinen sijainti, johon valmistuneet lataukset siirretään. Käytä Delugen oletusta jättämällä tyhjäksi.",
|
||||
@@ -1892,5 +1892,40 @@
|
||||
"CustomFormatsSpecificationQualityModifier": "Laatumuuttuja",
|
||||
"ReleaseSource": "Julkaisulähde",
|
||||
"UserInvokedSearch": "Käyttäjä herätti haun",
|
||||
"ReleasePush": "Julkaisun työntö"
|
||||
"ReleasePush": "Julkaisun työntö",
|
||||
"Mixed": "Sekoitettu",
|
||||
"MediaInfoFootNote2": "MediaInfo AudioLanguages ei huomioi englantia sen ollessa ainoa kieli. MediaInfo AudioLanguagesAll sisällyttää vain englanninkielen sisältävät kohteet.",
|
||||
"MovieRequested": "Elokuvaa pyydetty",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Muut parametrit",
|
||||
"ImportListsTraktSettingsAdditionalParametersHelpText": "Muut Trakt-rajapinnan parametrit.",
|
||||
"ImportListsTraktSettingsAuthenticateWithTrakt": "Tunnistaudu Traktilla",
|
||||
"ImportListsTraktSettingsCertification": "Ikäluokitus",
|
||||
"ImportListsTraktSettingsGenres": "Lajityypit",
|
||||
"ImportListsTraktSettingsLimit": "Rajoitus",
|
||||
"ImportListsTraktSettingsListType": "Listan tyyppi",
|
||||
"ImportListsTraktSettingsListTypeHelpText": "Tuotavan listan tyyppi.",
|
||||
"ImportListsTraktSettingsRating": "Arvio",
|
||||
"ImportListsTraktSettingsYears": "Vuodet",
|
||||
"ImportListsTraktSettingsCertificationMovieHelpText": "Suodata elokuvia ikäluokitusten perusteella (NR,G,PG,PG-13,R,NC-17) (pilkuin eroteltuna).",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesOfAllTime": "Kaikkien aikojen elokuvasuositukset",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByMonth": "Kuukausikohtaiset elokuvasuositukset",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByWeek": "Viikkokohtaiset elokuvasuositukset",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByYear": "Vuosikohtaiset elokuvasuositukset",
|
||||
"ImportListsTraktSettingsPopularListTypePopularMovies": "Suositut elokuvat",
|
||||
"ImportListsTraktSettingsLimitMovieHelpText": "Rajoita noudettavien elokuvien määrää.",
|
||||
"ImportListsTraktSettingsPopularListTypeTopAnticipatedMovies": "Odotetuimmat elokuvat",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByWeek": "Viikottain katsotuimmat elokuvat",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByMonth": "Kuukausittain katsotuimmat elokuvat",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByYear": "Vuosittain katsotuimmat elokuvat",
|
||||
"ImportListsTraktSettingsPopularListTypeTopBoxOfficeMovies": "Teattereissa katsotuimmat elokuvat",
|
||||
"ImportListsTraktSettingsYearsMovieHelpText": "Suodata elokuvia vuoden tai vuosivälin perusteella.",
|
||||
"ImportListsTraktSettingsRatingMovieHelpText": "Suodata elokuvia arvioden perusteella (alue 0–100).",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesOfAllTime": "Kaikkien aikojen katsotuimmat elokuvat",
|
||||
"ImportListsTraktSettingsPopularListTypeTrendingMovies": "Trendaavat elokuvat",
|
||||
"ImportListsTraktSettingsGenresMovieHelpText": "Suodata elokuvia Trakt-lajityyppien slug-arvojen perusteella (pilkuin eroteltuna). Koskee vain suosituimpia listoja.",
|
||||
"ImportListsTraktSettingsUserListTypeCollection": "Käyttäjän kokoelmat",
|
||||
"ImportListsTraktSettingsUserListTypeWatch": "Käyttäjän katselulista",
|
||||
"ImportListsTraktSettingsUserListTypeWatched": "Käyttäjän katseltujen lista",
|
||||
"ImportListsTraktSettingsUserListUsernameHelpText": "Listan tuontiin käytettävä käyttäjänimi. Käytä tunnistautunutta käyttäjää jättämällä tyhjäksi.",
|
||||
"ImportListsTraktSettingsWatchListSorting": "Katselulistan järjestys"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user