1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-05 13:21:25 -05:00

Compare commits

...

44 Commits

Author SHA1 Message Date
Bogdan
36c66deb4b Recommend against using uTorrent 2025-02-25 12:50:11 +02:00
Weblate
edec432244 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2025-02-24 19:28:00 +02:00
Bogdan
554e15d438 New: Watch list sorting and rate limit for Trakt Import Lists 2025-02-23 14:59:38 +02:00
Bogdan
553645a07c Bump version to 5.19.2 2025-02-23 12:14:28 +02:00
Bogdan
7de7e83c5b New: Add Blu-ray link to movie details 2025-02-23 00:03:48 +02:00
Bogdan
b7a46bedb0 Fixed: Avoid checking for free space if other specifications fail first 2025-02-22 21:33:38 +02:00
Weblate
0925769377 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Al3xPdx007 <constantin.pdx@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Pablo <pablo@pabloarraiz.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: pelnoph <pierre.regnier.1984@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2025-02-22 18:34:51 +02:00
Servarr
72244362fe Automated API Docs update 2025-02-22 18:33:37 +02:00
Mark McDowall
c6526c34e9 Cleanse console log messages
(cherry picked from commit 609e964794e17343f63e1ecff3fef323e3d284ff)
2025-02-19 15:44:15 +02:00
Bogdan
efa2913dbc Translate Trakt popular list types 2025-02-19 15:28:05 +02:00
Mark McDowall
35c22a4ffa Fixed: Only show Additional Parameters on Trakt Popular list
(cherry picked from commit b122ee967009d53432f3d1dd196132487f3999e1)
2025-02-19 15:15:17 +02:00
Stevie Robinson
66d96e21da Fixed: Fallback to Instance Name for Discord notifications
(cherry picked from commit b99e06acc0a3ecae2857d9225b35424c82c67a2b)
2025-02-19 14:52:30 +02:00
Bogdan
36d4e9e6cd New: Movie Requested filter for interactive search 2025-02-18 04:42:10 +02:00
Weblate
7189d7b15c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: haru4a <haru4as95@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2025-02-16 23:04:54 +02:00
Servarr
6e80113987 Automated API Docs update 2025-02-16 23:03:09 +02:00
Bogdan
bb8a0dda63 Fixed: Processing existing movie files via Manage Files 2025-02-16 21:36:10 +02:00
Bogdan
525ed65687 Fix download links for FileList when passkey contains spaces 2025-02-16 12:19:17 +02:00
Bogdan
3fbccc6af3 Bump version to 5.19.2 2025-02-16 12:19:04 +02:00
Bogdan
8e10eecfac Fixed: Close Metadata settings modal on saving 2025-02-15 13:34:21 +02:00
Bogdan
a3b1512552 Fixed: Parsing some titles with FRE as French and ITA as Italian 2025-02-13 17:31:39 +02:00
epmt7w3ugk
d375b5ffbe Fixed: Parse GER/DE releases as German language
Fix parsing for German language to correctly detect "GER" and "DE"
Update test for GER/DE language parsing.
2025-02-10 17:17:43 +02:00
Bogdan
884abc0368 Bump version to 5.19.1 2025-02-09 17:50:50 +02:00
Weblate
f8da7aae03 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Gionatan Spedicato <natanoig444@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2025-02-07 19:14:05 -06:00
Connor Gallopo
c165118d4d Update README.md
Update Copyright Date
2025-02-07 14:01:42 -06:00
Robin Dadswell
b3dd571a92 New: Migrated StevenLu URL to new URL 2025-02-06 03:41:33 +02:00
Bogdan
dd900eb739 Building docs on ARM
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
(cherry picked from commit 147e732c9ca7a4c289d4f6386f1277650e11f15b)
2025-02-06 00:43:53 +02:00
Bogdan
66aae0c91c Fixed: Reject multi-part files with P1, P2, etc. 2025-02-04 03:40:06 +02:00
Servarr
d888a0a2b3 Automated API Docs update 2025-02-03 21:31:13 +02:00
Bogdan
cb5416a18c Improve message for unknown movie rejection in release searching 2025-02-03 18:06:48 +02:00
Bogdan
7977e0be05 Add reason enum to decision engine rejections
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-02-03 17:46:13 +02:00
Bogdan
cd836fef38 Bump version to 5.19.0 2025-02-03 14:12:21 +02:00
Mark McDowall
b0bfbe767c Add MediaInfo AudioLanguagesAll and update styling
(cherry picked from commit 572b8620c9693f6824ae0919d6a37ba69c7590b1)
2025-02-02 15:16:41 +02:00
Bogdan
528b93dabe Fixed: Format bitrate for primary streams in media info
Co-authored-by: Mark McDowall <markus.mcd5@gmail.com>
2025-02-02 13:58:44 +02:00
Bogdan
1edcbee5e1 Bump version to 5.18.4 2025-02-02 12:49:50 +02:00
Bogdan
8853dced9f Fixed: Health warning for downloading inside root folders
(cherry picked from commit 1e9fd02e9d2bf57247adcac5728e2a0d2b084b86)
2025-02-01 23:36:10 +02:00
Mark McDowall
c7aa1bae5e Fixed: Ignore special folders inside Blackhole watch folders
(cherry picked from commit e79dd6f8e689617b1fd9f96c639ac300669112c5)
2025-02-01 23:35:45 +02:00
Bogdan
405ae77070 New: Prefer newer Usenet releases
(cherry picked from commit 6a439f03273b376feda713ef04a6912fc3af9d0a)
2025-02-01 23:35:31 +02:00
Weblate
6236bc9b4f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translation: Servarr/Radarr
2025-01-31 23:30:40 +02:00
Bogdan
743c977e5b New: Refresh cache for tracked queue on movies update 2025-01-31 23:29:41 +02:00
Bogdan
c0e5646f07 Bump Polly and NLog.Layouts.ClefJsonLayout 2025-01-26 15:42:43 +02:00
Weblate
10094b4e66 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Craze <christian.strey@gmail.com>
Co-authored-by: Dimitar \"Topper\" Maznekov <d.maznekov@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: ηg <jonas.konrath@icloud.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fa/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2025-01-26 15:00:33 +02:00
Bogdan
d923406f08 Bump version to 5.18.3 2025-01-26 14:48:34 +02:00
Bogdan
69a9c72286 Fixed: Loading movies with duplicated translations 2025-01-26 14:47:13 +02:00
Bogdan
55b9477a01 Fixed: Cleanup duplicated movie translations 2025-01-26 14:47:13 +02:00
162 changed files with 1948 additions and 777 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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}
/>

View File

@@ -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}&section=theatrical`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
Blu-ray
</Label>
</Link>
</>
) : null}

View File

@@ -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>
);

View File

@@ -21,8 +21,8 @@
display: flex;
color: var(--helpTextColor);
.icon {
margin-top: 3px;
.identifier {
margin-top: 8px;
margin-right: 5px;
padding: 2px;
}

View File

@@ -3,7 +3,7 @@
interface CssExports {
'footNote': string;
'groups': string;
'icon': string;
'identifier': string;
'namingSelect': string;
'namingSelectContainer': string;
}

View File

@@ -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>

View File

@@ -40,7 +40,7 @@
padding: 6px;
background-color: var(--popoverBodyBackgroundColor);
.footNote {
.footNotes {
padding: 2px;
color: #aaa;
}

View File

@@ -2,7 +2,7 @@
// Please do not change this file!
interface CssExports {
'example': string;
'footNote': string;
'footNotes': string;
'isFullFilename': string;
'large': string;
'lower': string;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'

View 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;

View File

@@ -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;
}

View 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());
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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" />

View File

@@ -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; }
}
}

View File

@@ -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]

View File

@@ -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();

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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>();

View File

@@ -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>();

View File

@@ -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())

View File

@@ -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())

View File

@@ -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

View File

@@ -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())

View File

@@ -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",

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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();

View File

@@ -159,7 +159,7 @@ namespace NzbDrone.Core.DecisionEngine
return 10;
}
return 1;
return Math.Round(Math.Log10(age)) * -1;
});
}

View File

@@ -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;

View 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)
{
}
}

View 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
}

View 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
};
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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()

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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)

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
{

View File

@@ -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();

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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
)");
}
}
}

View File

@@ -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
}
}

View File

@@ -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));

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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));
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -1083,5 +1083,9 @@
"AutoTaggingSpecificationStatus": "الحالة",
"CustomFormatsSpecificationLanguage": "لغة",
"CustomFormatsSpecificationMaximumSize": "أكبر مقاس",
"CustomFormatsSpecificationSource": "مصدر"
"CustomFormatsSpecificationSource": "مصدر",
"Mixed": "ثابت",
"ImportListsTraktSettingsCertification": "شهادة",
"ImportListsTraktSettingsGenres": "الأنواع",
"ImportListsTraktSettingsRating": "التقييمات"
}

View File

@@ -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": "Оценки"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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)"
}

View File

@@ -1241,5 +1241,9 @@
"AutoTaggingSpecificationStatus": "Κατάσταση",
"CustomFormatsSpecificationLanguage": "Γλώσσα",
"CustomFormatsSpecificationMaximumSize": "Μέγιστο μέγεθος",
"CustomFormatsSpecificationSource": "Πηγή"
"CustomFormatsSpecificationSource": "Πηγή",
"Mixed": "Σταθερός",
"ImportListsTraktSettingsCertification": "Πιστοποιητικό",
"ImportListsTraktSettingsGenres": "Είδη",
"ImportListsTraktSettingsRating": "Ακροαματικότητα"
}

View File

@@ -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)",

View File

@@ -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"
}

View File

@@ -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": "فعالیت"
}

View File

@@ -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äsennys",
"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 0100).",
"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