mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-29 18:15:37 -04:00
Compare commits
61 Commits
v5.17.2.95
...
v5.19.2.97
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7de7e83c5b | ||
|
|
b7a46bedb0 | ||
|
|
0925769377 | ||
|
|
72244362fe | ||
|
|
c6526c34e9 | ||
|
|
efa2913dbc | ||
|
|
35c22a4ffa | ||
|
|
66d96e21da | ||
|
|
36d4e9e6cd | ||
|
|
7189d7b15c | ||
|
|
6e80113987 | ||
|
|
bb8a0dda63 | ||
|
|
525ed65687 | ||
|
|
3fbccc6af3 | ||
|
|
8e10eecfac | ||
|
|
a3b1512552 | ||
|
|
d375b5ffbe | ||
|
|
884abc0368 | ||
|
|
f8da7aae03 | ||
|
|
c165118d4d | ||
|
|
b3dd571a92 | ||
|
|
dd900eb739 | ||
|
|
66aae0c91c | ||
|
|
d888a0a2b3 | ||
|
|
cb5416a18c | ||
|
|
7977e0be05 | ||
|
|
cd836fef38 | ||
|
|
b0bfbe767c | ||
|
|
528b93dabe | ||
|
|
1edcbee5e1 | ||
|
|
8853dced9f | ||
|
|
c7aa1bae5e | ||
|
|
405ae77070 | ||
|
|
6236bc9b4f | ||
|
|
743c977e5b | ||
|
|
c0e5646f07 | ||
|
|
10094b4e66 | ||
|
|
d923406f08 | ||
|
|
69a9c72286 | ||
|
|
55b9477a01 | ||
|
|
6b81f92137 | ||
|
|
3ceda1bcda | ||
|
|
f1f1921517 | ||
|
|
af0c96538a | ||
|
|
3d52f45b6a | ||
|
|
d4715f119d | ||
|
|
d58135bf17 | ||
|
|
b452c10da3 | ||
|
|
f6b364725d | ||
|
|
99f6be3f3d | ||
|
|
c2ac49a873 | ||
|
|
0e24a3e8bc | ||
|
|
18032cc83b | ||
|
|
927eb38945 | ||
|
|
5fac348613 | ||
|
|
7ba9603449 | ||
|
|
e36de8ab8d | ||
|
|
f8704a1655 | ||
|
|
f507d5154e | ||
|
|
5f03e7142a | ||
|
|
c0ebbee7c9 |
@@ -87,4 +87,4 @@ This project is also supported by DigitalOcean
|
||||
### License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2024
|
||||
* Copyright 2010-2025
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.17.2'
|
||||
majorVersion: '5.19.2'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
@@ -1116,20 +1116,20 @@ stages:
|
||||
vmImage: ${{ variables.windowsImage }}
|
||||
steps:
|
||||
- checkout: self # Need history for Sonar analysis
|
||||
- task: SonarCloudPrepare@2
|
||||
- task: SonarCloudPrepare@3
|
||||
env:
|
||||
SONAR_SCANNER_OPTS: ''
|
||||
inputs:
|
||||
SonarCloud: 'SonarCloud'
|
||||
organization: 'radarr'
|
||||
scannerMode: 'CLI'
|
||||
scannerMode: 'cli'
|
||||
configMode: 'manual'
|
||||
cliProjectKey: 'Radarr_Radarr.UI'
|
||||
cliProjectName: 'RadarrUI'
|
||||
cliProjectVersion: '$(radarrVersion)'
|
||||
cliSources: './frontend'
|
||||
- task: SonarCloudAnalyze@2
|
||||
|
||||
- task: SonarCloudAnalyze@3
|
||||
|
||||
- job: Api_Docs
|
||||
displayName: API Docs
|
||||
dependsOn: Prepare
|
||||
@@ -1205,12 +1205,12 @@ stages:
|
||||
submodules: true
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
- task: SonarCloudPrepare@2
|
||||
- task: SonarCloudPrepare@3
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
inputs:
|
||||
SonarCloud: 'SonarCloud'
|
||||
organization: 'radarr'
|
||||
scannerMode: 'MSBuild'
|
||||
scannerMode: 'dotnet'
|
||||
projectKey: 'Radarr_Radarr'
|
||||
projectName: 'Radarr'
|
||||
projectVersion: '$(radarrVersion)'
|
||||
@@ -1223,7 +1223,7 @@ stages:
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@2
|
||||
- task: SonarCloudAnalyze@3
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
displayName: Publish SonarCloud Results
|
||||
- task: reportgenerator@5.3.11
|
||||
|
||||
15
docs.sh
15
docs.sh
@@ -1,13 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
FRAMEWORK="net6.0"
|
||||
PLATFORM=$1
|
||||
ARCHITECTURE="${2:-x64}"
|
||||
|
||||
if [ "$PLATFORM" = "Windows" ]; then
|
||||
RUNTIME="win-x64"
|
||||
RUNTIME="win-$ARCHITECTURE"
|
||||
elif [ "$PLATFORM" = "Linux" ]; then
|
||||
RUNTIME="linux-x64"
|
||||
RUNTIME="linux-$ARCHITECTURE"
|
||||
elif [ "$PLATFORM" = "Mac" ]; then
|
||||
RUNTIME="osx-x64"
|
||||
RUNTIME="osx-$ARCHITECTURE"
|
||||
else
|
||||
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
|
||||
echo "Platform must be provided as first argument: Windows, Linux or Mac"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -35,7 +40,7 @@ dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p
|
||||
dotnet new tool-manifest
|
||||
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
|
||||
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/$application" v3 &
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v3 &
|
||||
|
||||
sleep 45
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ function HistoryDetails(props: HistoryDetailsProps) {
|
||||
indexer,
|
||||
releaseGroup,
|
||||
movieMatchType,
|
||||
releaseSource,
|
||||
customFormatScore,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
@@ -53,6 +54,31 @@ function HistoryDetails(props: HistoryDetailsProps) {
|
||||
|
||||
const downloadClientNameInfo = downloadClientName ?? downloadClient;
|
||||
|
||||
let releaseSourceMessage = '';
|
||||
|
||||
switch (releaseSource) {
|
||||
case 'Unknown':
|
||||
releaseSourceMessage = translate('Unknown');
|
||||
break;
|
||||
case 'Rss':
|
||||
releaseSourceMessage = translate('Rss');
|
||||
break;
|
||||
case 'Search':
|
||||
releaseSourceMessage = translate('Search');
|
||||
break;
|
||||
case 'UserInvokedSearch':
|
||||
releaseSourceMessage = translate('UserInvokedSearch');
|
||||
break;
|
||||
case 'InteractiveSearch':
|
||||
releaseSourceMessage = translate('InteractiveSearch');
|
||||
break;
|
||||
case 'ReleasePush':
|
||||
releaseSourceMessage = translate('ReleasePush');
|
||||
break;
|
||||
default:
|
||||
releaseSourceMessage = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
@@ -88,6 +114,14 @@ function HistoryDetails(props: HistoryDetailsProps) {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{releaseSource ? (
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ReleaseSource')}
|
||||
data={releaseSourceMessage}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{nzbInfoUrl ? (
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
|
||||
@@ -81,7 +81,6 @@ ImportMovieRow.propTypes = {
|
||||
selectedMovie: PropTypes.object,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
queued: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired
|
||||
|
||||
@@ -131,7 +131,7 @@ class ImportMovieSelectMovie extends Component {
|
||||
id={this._buttonId}
|
||||
>
|
||||
<Link
|
||||
ref={ref}
|
||||
// ref={ref}
|
||||
className={styles.button}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
@@ -255,7 +255,7 @@ class ImportMovieSelectMovie extends Component {
|
||||
items.map((item) => {
|
||||
return (
|
||||
<ImportMovieSearchResultConnector
|
||||
key={item.tvdbId}
|
||||
key={item.tmdbId}
|
||||
tmdbId={item.tmdbId}
|
||||
title={item.title}
|
||||
year={item.year}
|
||||
|
||||
@@ -15,7 +15,7 @@ function TraktRating(props: TraktRatingProps) {
|
||||
const { ratings, iconSize = 14, hideIcon = false } = props;
|
||||
|
||||
const traktImage =
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiAgICAgdmlld0JveD0iMCAwIDE0NC44IDE0NC44IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxNDQuOCAxNDQuOCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+ICAgIDxwYXRoIGZpbGw9IiNFRDIyMjQiIGQ9Ik0yOS41LDExMS44YzEwLjYsMTEuNiwyNS45LDE4LjgsNDIuOSwxOC44YzguNywwLDE2LjktMS45LDI0LjMtNS4zTDU2LjMsODVMMjkuNSwxMTEuOHoiLz4gICAgPHBhdGggZmlsbD0iI0VEMjIyNCIgZD0iTTU2LjEsNjAuNkwyNS41LDkxLjFMMjEuNCw4N2wzMi4yLTMyLjJoMGwzNy42LTM3LjZjLTUuOS0yLTEyLjItMy4xLTE4LjgtMy4xYy0zMi4yLDAtNTguMywyNi4xLTU4LjMsNTguMyAgICAgICBjMCwxMy4xLDQuMywyNS4yLDExLjcsMzVsMzAuNS0zMC41bDIuMSwybDQzLjcsNDMuN2MwLjktMC41LDEuNy0xLDIuNS0xLjZMNTYuMyw3Mi43TDI3LDEwMmwtNC4xLTQuMWwzMy40LTMzLjRsMi4xLDJsNTEsNTAuOSAgICAgICBjMC44LTAuNiwxLjUtMS4zLDIuMi0xLjlsLTU1LTU1TDU2LjEsNjAuNnoiLz4gICAgPHBhdGggZmlsbD0iI0VEMUMyNCIgZD0iTTExNS43LDExMS40YzkuMy0xMC4zLDE1LTI0LDE1LTM5YzAtMjMuNC0xMy44LTQzLjUtMzMuNi01Mi44TDYwLjQsNTYuMkwxMTUuNywxMTEuNHogTTc0LjUsNjYuOGwtNC4xLTQuMSAgICAgICBsMjguOS0yOC45bDQuMSw0LjFMNzQuNSw2Ni44eiBNMTAxLjksMjcuMUw2OC42LDYwLjRsLTQuMS00LjFMOTcuOCwyM0wxMDEuOSwyNy4xeiIvPiAgICA8Zz4gICAgICAgPGc+ICAgICAgICAgIDxwYXRoIGZpbGw9IiNFRDIyMjQiIGQ9Ik03Mi40LDE0NC44QzMyLjUsMTQ0LjgsMCwxMTIuMywwLDcyLjRDMCwzMi41LDMyLjUsMCw3Mi40LDBzNzIuNCwzMi41LDcyLjQsNzIuNCAgICAgICAgICAgICBDMTQ0LjgsMTEyLjMsMTEyLjMsMTQ0LjgsNzIuNCwxNDQuOHogTTcyLjQsNy4zQzM2LjUsNy4zLDcuMywzNi41LDcuMyw3Mi40czI5LjIsNjUuMSw2NS4xLDY1LjFzNjUuMS0yOS4yLDY1LjEtNjUuMSAgICAgICAgICAgICBTMTA4LjMsNy4zLDcyLjQsNy4zeiIvPiAgICAgICA8L2c+ICAgIDwvZz48L2c+PC9zdmc+';
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDggNDgiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICM5ZjQyYzY7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPGcgaWQ9Il94MkRfLXByb2R1Y3Rpb24iPgogICAgPGcgaWQ9ImxvZ29tYXJrLmNpcmNsZS5jb2xvciI+CiAgICAgIDxwYXRoIGlkPSJiYWNrZ3JvdW5kIiBjbGFzcz0iY2xzLTEiIGQ9Ik00OCwyNGMwLDYuNjItMi42OSwxMi42Mi03LjAzLDE2Ljk3LTQuMzQsNC4zNC0xMC4zNCw3LjAzLTE2Ljk3LDcuMDNDMTAuNzUsNDgsMCwzNy4yNSwwLDI0YzAtNi42MywyLjY5LTEyLjYzLDcuMDMtMTYuOTdDMTEuMzcsMi42OCwxNy4zNywwLDI0LDBzMTIuNjMsMi42OCwxNi45Nyw3LjAzYy4xNC4xNC4yNy4yOC40LjQyLjQ4LjUuOTQsMS4wMiwxLjM3LDEuNTYuMjEuMjYuNDEuNTIuNi43OS40My41Ny44MiwxLjE2LDEuMTgsMS43Ni4xOC4yOS4zNS41OC41MS44Ny4zNS42NC42OCwxLjI5Ljk2LDEuOTcsMS4zLDIuOTQsMi4wMSw2LjE4LDIuMDEsOS42WiIvPgogICAgICA8ZyBpZD0iY2hlY2tib3giPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTEyLjkzLDE4LjY3bC0xLjQ3LDEuNDYsMTQuNCwxNC40LDEuNDctMS40Ny00LjMyLTQuMzEsMTkuNzMtMTkuNzRjLS40My0uNTQtLjg5LTEuMDYtMS4zNy0xLjU2bC0xOS44MywxOS44My04LjYxLTguNjFaTTI4LjAyLDMyLjM3bDEuNDYtMS40Ni0yLjE1LTIuMTYsMTcuMTktMTcuMTljLS4zNi0uNi0uNzUtMS4xOS0xLjE4LTEuNzZsLTE4Ljk0LDE4Ljk1LDMuNjIsMy42MlpNMzAuMTgsMzAuMjFsMTUuODEtMTUuODFjLS4yOC0uNjgtLjYxLTEuMzMtLjk2LTEuOTdsLTE2LjMyLDE2LjMyLDEuNDcsMS40NlpNMTMuNjIsMTcuOTdsNy45Miw3LjkyLDEuNDctMS40Ny03LjkyLTcuOTItMS40NywxLjQ3Wk0yNS4xNywyMi4yN2wtNy45Mi03LjkyLTEuNDcsMS40Nyw3LjkyLDcuOTIsMS40Ny0xLjQ3Wk0yNCw0MS4zMmMtOS41NSwwLTE3LjMyLTcuNzctMTcuMzItMTcuMzJTMTQuNDUsNi42NywyNCw2LjY3YzIuNiwwLDUuMTEuNTYsNy40NCwxLjY4bC44OS0xLjg3Yy0yLjYxLTEuMjUtNS40Mi0xLjg4LTguMzMtMS44OEMxMy4zMSw0LjYsNC42MSwxMy4zLDQuNjEsMjRzOC43LDE5LjQsMTkuNCwxOS40YzcuNjQsMCwxNC41OS00LjUxLDE3LjcxLTExLjQ4bC0xLjg5LS44NWMtMi43OSw2LjIzLTksMTAuMjYtMTUuODIsMTAuMjZaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPg==';
|
||||
|
||||
const { value = 0, votes = 0 } = ratings.trakt;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import TmdbRating from 'Components/TmdbRating';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import TraktRating from 'Components/TraktRating';
|
||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import { icons, kinds, sizes, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
@@ -753,11 +753,15 @@ class MovieDetails extends Component {
|
||||
<InteractiveImportModal
|
||||
isOpen={isInteractiveImportModalOpen}
|
||||
movieId={id}
|
||||
modalTitle={translate('ManageFiles')}
|
||||
title={title}
|
||||
folder={path}
|
||||
initialSortKey="relativePath"
|
||||
initialSortDirection={sortDirections.ASCENDING}
|
||||
showMovie={false}
|
||||
allowMovieChange={false}
|
||||
showFilterExistingFiles={true}
|
||||
showDelete={true}
|
||||
showImportMode={false}
|
||||
modalTitle={translate('ManageFiles')}
|
||||
onModalClose={this.onInteractiveImportModalClose}
|
||||
/>
|
||||
|
||||
|
||||
@@ -92,6 +92,19 @@ function MovieDetailsLinks(props: MovieDetailsLinksProps) {
|
||||
MDBList
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://www.blu-ray.com/search/?quicksearch=1&quicksearch_keyword=${imdbId}§ion=theatrical`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Blu-ray
|
||||
</Label>
|
||||
</Link>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import MediaInfoProps from 'typings/MediaInfo';
|
||||
import formatBitrate from 'Utilities/Number/formatBitrate';
|
||||
import getEntries from 'Utilities/Object/getEntries';
|
||||
|
||||
function MediaInfo(props: MediaInfoProps) {
|
||||
@@ -16,9 +17,19 @@ function MediaInfo(props: MediaInfoProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionListItem key={key} title={title} data={props[key]} />
|
||||
);
|
||||
if (key === 'audioBitrate' || key === 'videoBitrate') {
|
||||
return (
|
||||
<DescriptionListItem
|
||||
key={key}
|
||||
title={title}
|
||||
data={
|
||||
<span title={value.toString()}>{formatBitrate(value)}</span>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <DescriptionListItem key={key} title={title} data={value} />;
|
||||
})}
|
||||
</DescriptionList>
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
display: flex;
|
||||
color: var(--helpTextColor);
|
||||
|
||||
.icon {
|
||||
margin-top: 3px;
|
||||
.identifier {
|
||||
margin-top: 8px;
|
||||
margin-right: 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
interface CssExports {
|
||||
'footNote': string;
|
||||
'groups': string;
|
||||
'icon': string;
|
||||
'identifier': string;
|
||||
'namingSelect': string;
|
||||
'namingSelectContainer': string;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useCallback, useState } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
@@ -10,7 +9,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { icons, sizes } from 'Helpers/Props';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import NamingConfig from 'typings/Settings/NamingConfig';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import NamingOption from './NamingOption';
|
||||
@@ -88,32 +87,32 @@ const fileNameTokens = [
|
||||
];
|
||||
|
||||
const movieTokens = [
|
||||
{ token: '{Movie Title}', example: "Movie's Title", footNote: true },
|
||||
{ token: '{Movie Title:DE}', example: 'Titel des Films', footNote: true },
|
||||
{ token: '{Movie CleanTitle}', example: 'Movies Title', footNote: true },
|
||||
{ token: '{Movie Title}', example: "Movie's Title", footNotes: '1' },
|
||||
{ token: '{Movie Title:DE}', example: 'Titel des Films', footNotes: '1' },
|
||||
{ token: '{Movie CleanTitle}', example: 'Movies Title', footNotes: '1' },
|
||||
{
|
||||
token: '{Movie CleanTitle:DE}',
|
||||
example: 'Titel des Films',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie TitleThe}', example: "Movie's Title, The", footNote: true },
|
||||
{ token: '{Movie TitleThe}', example: "Movie's Title, The", footNotes: '1' },
|
||||
{
|
||||
token: '{Movie CleanTitleThe}',
|
||||
example: 'Movies Title, The',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας', footNote: true },
|
||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας', footNotes: '1' },
|
||||
{
|
||||
token: '{Movie CleanOriginalTitle}',
|
||||
example: 'Τίτλος ταινίας',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
|
||||
{ token: '{Movie TitleFirstCharacter:DE}', example: 'T' },
|
||||
{
|
||||
token: '{Movie Collection}',
|
||||
example: 'The Movie Collection',
|
||||
footNote: true,
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie Certification}', example: 'R' },
|
||||
{ token: '{Release Year}', example: '2009' },
|
||||
@@ -131,12 +130,21 @@ const qualityTokens = [
|
||||
|
||||
const mediaInfoTokens = [
|
||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]', footNote: true },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]', footNotes: '1' },
|
||||
|
||||
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
|
||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]', footNote: true },
|
||||
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]', footNote: true },
|
||||
{
|
||||
token: '{MediaInfo AudioLanguages}',
|
||||
example: '[EN+DE]',
|
||||
footNotes: '1,2',
|
||||
},
|
||||
{
|
||||
token: '{MediaInfo AudioLanguagesAll}',
|
||||
example: '[EN]',
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]', footNotes: '1' },
|
||||
|
||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
|
||||
@@ -146,11 +154,11 @@ const mediaInfoTokens = [
|
||||
];
|
||||
|
||||
const releaseGroupTokens = [
|
||||
{ token: '{Release Group}', example: 'Rls Grp', footNote: true },
|
||||
{ token: '{Release Group}', example: 'Rls Grp', footNotes: '1' },
|
||||
];
|
||||
|
||||
const editionTokens = [
|
||||
{ token: '{Edition Tags}', example: 'IMAX', footNote: true },
|
||||
{ token: '{Edition Tags}', example: 'IMAX', footNotes: '1' },
|
||||
];
|
||||
|
||||
const customFormatTokens = [
|
||||
@@ -287,13 +295,13 @@ function NamingModal(props: NamingModalProps) {
|
||||
|
||||
<FieldSet legend={translate('Movie')}>
|
||||
<div className={styles.groups}>
|
||||
{movieTokens.map(({ token, example, footNote }) => {
|
||||
{movieTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -303,7 +311,7 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('MovieFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
@@ -346,13 +354,13 @@ function NamingModal(props: NamingModalProps) {
|
||||
|
||||
<FieldSet legend={translate('MediaInfo')}>
|
||||
<div className={styles.groups}>
|
||||
{mediaInfoTokens.map(({ token, example, footNote }) => {
|
||||
{mediaInfoTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -362,20 +370,25 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('MediaInfoFootNote')} />
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<sup className={styles.identifier}>2</sup>
|
||||
<InlineMarkdown data={translate('MediaInfoFootNote2')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('ReleaseGroup')}>
|
||||
<div className={styles.groups}>
|
||||
{releaseGroupTokens.map(({ token, example, footNote }) => {
|
||||
{releaseGroupTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -385,20 +398,20 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('ReleaseGroupFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('Edition')}>
|
||||
<div className={styles.groups}>
|
||||
{editionTokens.map(({ token, example, footNote }) => {
|
||||
{editionTokens.map(({ token, example, footNotes }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
footNotes={footNotes}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={handleOptionPress}
|
||||
@@ -408,7 +421,7 @@ function NamingModal(props: NamingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<sup className={styles.identifier}>1</sup>
|
||||
<InlineMarkdown data={translate('EditionFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
padding: 6px;
|
||||
background-color: var(--popoverBodyBackgroundColor);
|
||||
|
||||
.footNote {
|
||||
.footNotes {
|
||||
padding: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'example': string;
|
||||
'footNote': string;
|
||||
'footNotes': string;
|
||||
'isFullFilename': string;
|
||||
'large': string;
|
||||
'lower': string;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useCallback } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { Size } from 'Helpers/Props/sizes';
|
||||
import TokenCase from './TokenCase';
|
||||
import TokenSeparator from './TokenSeparator';
|
||||
@@ -14,7 +12,7 @@ interface NamingOptionProps {
|
||||
example: string;
|
||||
tokenCase: TokenCase;
|
||||
isFullFilename?: boolean;
|
||||
footNote?: boolean;
|
||||
footNotes?: string;
|
||||
size?: Extract<Size, keyof typeof styles>;
|
||||
onPress: ({
|
||||
isFullFilename,
|
||||
@@ -32,7 +30,7 @@ function NamingOption(props: NamingOptionProps) {
|
||||
example,
|
||||
tokenCase,
|
||||
isFullFilename = false,
|
||||
footNote = false,
|
||||
footNotes,
|
||||
size = 'small',
|
||||
onPress,
|
||||
} = props;
|
||||
@@ -66,8 +64,10 @@ function NamingOption(props: NamingOptionProps) {
|
||||
<div className={styles.example}>
|
||||
{example.replace(/ /g, tokenSeparator)}
|
||||
|
||||
{footNote ? (
|
||||
<Icon className={styles.footNote} name={icons.FOOTNOTE} />
|
||||
{footNotes ? (
|
||||
<div className={styles.footNotes}>
|
||||
<sup>{footNotes}</sup>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Alert from 'Components/Alert';
|
||||
@@ -13,6 +13,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import {
|
||||
saveMetadata,
|
||||
@@ -41,6 +42,8 @@ function EditMetadataModalContent({
|
||||
(state: AppState) => state.settings.metadata
|
||||
);
|
||||
|
||||
const wasSaving = usePrevious(isSaving);
|
||||
|
||||
const { settings, ...otherSettings } = useMemo(() => {
|
||||
const item = items.find((item) => item.id === id)!;
|
||||
|
||||
@@ -69,6 +72,12 @@ function EditMetadataModalContent({
|
||||
dispatch(saveMetadata({ id }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
onModalClose();
|
||||
}
|
||||
}, [isSaving, wasSaving, saveError, onModalClose]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
|
||||
@@ -86,7 +86,7 @@ export const actionHandlers = handleThunks({
|
||||
section,
|
||||
...item,
|
||||
term,
|
||||
queued: true,
|
||||
isQueued: true,
|
||||
items: []
|
||||
}));
|
||||
|
||||
@@ -151,6 +151,8 @@ export const actionHandlers = handleThunks({
|
||||
abortCurrentLookup = abortRequest;
|
||||
|
||||
request.done((data) => {
|
||||
const selectedMovie = queued.selectedMovie || data[0];
|
||||
|
||||
dispatch(updateItem({
|
||||
section,
|
||||
id: queued.id,
|
||||
@@ -158,8 +160,8 @@ export const actionHandlers = handleThunks({
|
||||
isPopulated: true,
|
||||
error: null,
|
||||
items: data,
|
||||
queued: false,
|
||||
selectedMovie: queued.selectedMovie || data[0],
|
||||
isQueued: false,
|
||||
selectedMovie,
|
||||
updateOnly: true
|
||||
}));
|
||||
});
|
||||
@@ -171,7 +173,7 @@ export const actionHandlers = handleThunks({
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: xhr,
|
||||
queued: false,
|
||||
isQueued: false,
|
||||
updateOnly: true
|
||||
}));
|
||||
});
|
||||
@@ -278,7 +280,23 @@ export const actionHandlers = handleThunks({
|
||||
export const reducers = createHandleActions({
|
||||
|
||||
[CANCEL_LOOKUP_MOVIE]: function(state) {
|
||||
return Object.assign({}, state, { isLookingUpMovie: false });
|
||||
queue.splice(0, queue.length);
|
||||
|
||||
const items = state.items.map((item) => {
|
||||
if (item.isQueued) {
|
||||
return {
|
||||
...item,
|
||||
isQueued: false
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return Object.assign({}, state, {
|
||||
isLookingUpMovie: false,
|
||||
items
|
||||
});
|
||||
},
|
||||
|
||||
[CLEAR_IMPORT_MOVIE]: function(state) {
|
||||
|
||||
@@ -210,6 +210,12 @@ export const defaultState = {
|
||||
name: 'rejectionCount',
|
||||
label: () => translate('RejectionCount'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'movieRequested',
|
||||
label: () => translate('MovieRequested'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
}
|
||||
],
|
||||
selectedFilterKey: 'all'
|
||||
|
||||
19
frontend/src/Utilities/Number/formatBitrate.ts
Normal file
19
frontend/src/Utilities/Number/formatBitrate.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { filesize } from 'filesize';
|
||||
|
||||
function formatBitrate(input: string | number) {
|
||||
const size = Number(input);
|
||||
|
||||
if (isNaN(size)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { value, symbol } = filesize(size, {
|
||||
base: 10,
|
||||
round: 1,
|
||||
output: 'object',
|
||||
});
|
||||
|
||||
return `${value} ${symbol}/s`;
|
||||
}
|
||||
|
||||
export default formatBitrate;
|
||||
@@ -23,12 +23,13 @@ const error = console.error;
|
||||
function logError(...parameters: any[]) {
|
||||
const filter = parameters.find((parameter) => {
|
||||
return (
|
||||
parameter.includes(
|
||||
typeof parameter === 'string' &&
|
||||
(parameter.includes(
|
||||
'Support for defaultProps will be removed from function components in a future major release'
|
||||
) ||
|
||||
parameter.includes(
|
||||
'findDOMNode is deprecated and will be removed in the next major release'
|
||||
)
|
||||
parameter.includes(
|
||||
'findDOMNode is deprecated and will be removed in the next major release'
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -341,10 +341,11 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
var isCifs = targetDriveFormat == "cifs";
|
||||
var isBtrfs = sourceDriveFormat == "btrfs" && targetDriveFormat == "btrfs";
|
||||
var isZfs = sourceDriveFormat == "zfs" && targetDriveFormat == "zfs";
|
||||
|
||||
if (mode.HasFlag(TransferMode.Copy))
|
||||
{
|
||||
if (isBtrfs)
|
||||
if (isBtrfs || isZfs)
|
||||
{
|
||||
if (_diskProvider.TryCreateRefLink(sourcePath, targetPath))
|
||||
{
|
||||
@@ -358,7 +359,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (mode.HasFlag(TransferMode.Move))
|
||||
{
|
||||
if (isBtrfs)
|
||||
if (isBtrfs || isZfs)
|
||||
{
|
||||
if (isSameMount && _diskProvider.TryRenameFile(sourcePath, targetPath))
|
||||
{
|
||||
|
||||
@@ -17,37 +17,6 @@ namespace NzbDrone.Common.Disk
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
|
||||
private readonly HashSet<string> _setToRemove = new HashSet<string>
|
||||
{
|
||||
// Windows
|
||||
"boot",
|
||||
"bootmgr",
|
||||
"cache",
|
||||
"msocache",
|
||||
"recovery",
|
||||
"$recycle.bin",
|
||||
"recycler",
|
||||
"system volume information",
|
||||
"temporary internet files",
|
||||
"windows",
|
||||
|
||||
// OS X
|
||||
".fseventd",
|
||||
".spotlight",
|
||||
".trashes",
|
||||
".vol",
|
||||
"cachedmessages",
|
||||
"caches",
|
||||
"trash",
|
||||
|
||||
// QNAP
|
||||
".@__thumb",
|
||||
|
||||
// Synology
|
||||
"@eadir",
|
||||
"#recycle"
|
||||
};
|
||||
|
||||
public FileSystemLookupService(IDiskProvider diskProvider, IRuntimeInfo runtimeInfo)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
@@ -158,7 +127,7 @@ namespace NzbDrone.Common.Disk
|
||||
})
|
||||
.ToList();
|
||||
|
||||
directories.RemoveAll(d => _setToRemove.Contains(d.Name.ToLowerInvariant()));
|
||||
directories.RemoveAll(d => SpecialFolders.IsSpecialFolder(d.Name));
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
47
src/NzbDrone.Common/Disk/SpecialFolders.cs
Normal file
47
src/NzbDrone.Common/Disk/SpecialFolders.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Disk;
|
||||
|
||||
public static class SpecialFolders
|
||||
{
|
||||
private static readonly HashSet<string> _specialFolders = new HashSet<string>
|
||||
{
|
||||
// Windows
|
||||
"boot",
|
||||
"bootmgr",
|
||||
"cache",
|
||||
"msocache",
|
||||
"recovery",
|
||||
"$recycle.bin",
|
||||
"recycler",
|
||||
"system volume information",
|
||||
"temporary internet files",
|
||||
"windows",
|
||||
|
||||
// OS X
|
||||
".fseventd",
|
||||
".spotlight",
|
||||
".trashes",
|
||||
".vol",
|
||||
"cachedmessages",
|
||||
"caches",
|
||||
"trash",
|
||||
|
||||
// QNAP
|
||||
".@__thumb",
|
||||
|
||||
// Synology
|
||||
"@eadir",
|
||||
"#recycle"
|
||||
};
|
||||
|
||||
public static bool IsSpecialFolder(string folder)
|
||||
{
|
||||
if (folder == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _specialFolders.Contains(folder.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
@@ -54,10 +54,8 @@ namespace NzbDrone.Common.Extensions
|
||||
foreach (var item in src)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
if (!result.ContainsKey(key))
|
||||
{
|
||||
result[key] = item;
|
||||
}
|
||||
|
||||
result.TryAdd(key, item);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -69,10 +67,9 @@ namespace NzbDrone.Common.Extensions
|
||||
foreach (var item in src)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
if (!result.ContainsKey(key))
|
||||
{
|
||||
result[key] = valueSelector(item);
|
||||
}
|
||||
var value = valueSelector(item);
|
||||
|
||||
result.TryAdd(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation;
|
||||
|
||||
public class CleansingClefLogLayout : CompactJsonLayout
|
||||
{
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
base.RenderFormattedMessage(logEvent, target);
|
||||
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
var result = CleanseLogMessage.Cleanse(target.ToString());
|
||||
target.Clear();
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Layouts;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation;
|
||||
|
||||
public class CleansingConsoleLogLayout : SimpleLayout
|
||||
{
|
||||
public CleansingConsoleLogLayout(string format)
|
||||
: base(format)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
base.RenderFormattedMessage(logEvent, target);
|
||||
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
var result = CleanseLogMessage.Cleanse(target.ToString());
|
||||
target.Clear();
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using NLog.Targets;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public class NzbDroneFileTarget : FileTarget
|
||||
public class CleansingFileTarget : FileTarget
|
||||
{
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
@@ -3,7 +3,6 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NLog.Targets;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -13,9 +12,11 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public static class NzbDroneLogger
|
||||
{
|
||||
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
public const string ConsoleLogLayout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
public static CompactJsonLayout ClefLogLayout = new CompactJsonLayout();
|
||||
private const string FileLogLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
private const string ConsoleFormat = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
private static readonly CleansingConsoleLogLayout CleansingConsoleLayout = new (ConsoleFormat);
|
||||
private static readonly CleansingClefLogLayout ClefLogLayout = new ();
|
||||
|
||||
private static bool _isConfigured;
|
||||
|
||||
@@ -119,11 +120,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
? formatEnumValue
|
||||
: ConsoleLogFormat.Standard;
|
||||
|
||||
coloredConsoleTarget.Layout = logFormat switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => ClefLogLayout,
|
||||
_ => ConsoleLogLayout
|
||||
};
|
||||
ConfigureConsoleLayout(coloredConsoleTarget, logFormat);
|
||||
|
||||
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
|
||||
|
||||
@@ -140,7 +137,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
|
||||
{
|
||||
var fileTarget = new NzbDroneFileTarget();
|
||||
var fileTarget = new CleansingFileTarget();
|
||||
|
||||
fileTarget.Name = name;
|
||||
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName);
|
||||
@@ -153,7 +150,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
fileTarget.MaxArchiveFiles = maxArchiveFiles;
|
||||
fileTarget.EnableFileDelete = true;
|
||||
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
|
||||
fileTarget.Layout = FILE_LOG_LAYOUT;
|
||||
fileTarget.Layout = FileLogLayout;
|
||||
|
||||
var loggingRule = new LoggingRule("*", minLogLevel, fileTarget);
|
||||
|
||||
@@ -172,7 +169,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
fileTarget.ConcurrentWrites = false;
|
||||
fileTarget.ConcurrentWriteAttemptDelay = 50;
|
||||
fileTarget.ConcurrentWriteAttempts = 100;
|
||||
fileTarget.Layout = FILE_LOG_LAYOUT;
|
||||
fileTarget.Layout = FileLogLayout;
|
||||
|
||||
var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
|
||||
|
||||
@@ -217,6 +214,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
return GetLogger(obj.GetType());
|
||||
}
|
||||
|
||||
public static void ConfigureConsoleLayout(ColoredConsoleTarget target, ConsoleLogFormat format)
|
||||
{
|
||||
target.Layout = format switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
|
||||
_ => NzbDroneLogger.CleansingConsoleLayout
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConsoleLogFormat
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.2" />
|
||||
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.15" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.9" />
|
||||
<PackageReference Include="Sentry" Version="4.0.2" />
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Migration;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class stevenlu_update_urlFixture : MigrationTest<stevenlu_update_url>
|
||||
{
|
||||
[Test]
|
||||
public void should_update_stevenlu_url()
|
||||
{
|
||||
var db = WithMigrationTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("ImportLists").Row(new
|
||||
{
|
||||
Enabled = true,
|
||||
EnableAuto = true,
|
||||
Name = "StevenLu List",
|
||||
QualityProfileId = 1,
|
||||
MinimumAvailability = 1,
|
||||
RootFolderPath = "/movies",
|
||||
Monitor = 0,
|
||||
SearchOnAdd = true,
|
||||
Tags = "[]",
|
||||
Implementation = "StevenLuImport",
|
||||
ConfigContract = "StevenLuSettings",
|
||||
Settings = new StevenLuSettings241
|
||||
{
|
||||
Link = "https://s3.amazonaws.com/popular-movies/movies.json"
|
||||
}.ToJson()
|
||||
});
|
||||
});
|
||||
|
||||
var items = db.Query<ImportListDefinition241>("SELECT \"Id\", \"Settings\" FROM \"ImportLists\"");
|
||||
|
||||
items.Should().HaveCount(1);
|
||||
items.First().Settings.Link.Should().Be("https://popular-movies-data.stevenlu.com/movies.json");
|
||||
}
|
||||
}
|
||||
|
||||
public class ImportListDefinition241 : ModelBase
|
||||
{
|
||||
public StevenLuSettings241 Settings { get; set; }
|
||||
}
|
||||
|
||||
public class StevenLuSettings241
|
||||
{
|
||||
public string Link { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -20,32 +20,32 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
private List<ReleaseInfo> _reports;
|
||||
private RemoteMovie _remoteEpisode;
|
||||
|
||||
private Mock<IDecisionEngineSpecification> _pass1;
|
||||
private Mock<IDecisionEngineSpecification> _pass2;
|
||||
private Mock<IDecisionEngineSpecification> _pass3;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _pass1;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _pass2;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _pass3;
|
||||
|
||||
private Mock<IDecisionEngineSpecification> _fail1;
|
||||
private Mock<IDecisionEngineSpecification> _fail2;
|
||||
private Mock<IDecisionEngineSpecification> _fail3;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _fail1;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _fail2;
|
||||
private Mock<IDownloadDecisionEngineSpecification> _fail3;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_pass1 = new Mock<IDecisionEngineSpecification>();
|
||||
_pass2 = new Mock<IDecisionEngineSpecification>();
|
||||
_pass3 = new Mock<IDecisionEngineSpecification>();
|
||||
_pass1 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_pass2 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_pass3 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
|
||||
_fail1 = new Mock<IDecisionEngineSpecification>();
|
||||
_fail2 = new Mock<IDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IDecisionEngineSpecification>();
|
||||
_fail1 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_fail2 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IDownloadDecisionEngineSpecification>();
|
||||
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Accept);
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Accept);
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Accept);
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Accept);
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Accept);
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Accept);
|
||||
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Reject("fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Reject("fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(Decision.Reject("fail3"));
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteMovie>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail3"));
|
||||
|
||||
_reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "Trolls.2016.720p.WEB-DL.DD5.1.H264-FGT" } };
|
||||
_remoteEpisode = new RemoteMovie
|
||||
@@ -58,9 +58,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
.Setup(c => c.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>())).Returns(_remoteEpisode);
|
||||
}
|
||||
|
||||
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
|
||||
private void GivenSpecifications(params Mock<IDownloadDecisionEngineSpecification>[] mocks)
|
||||
{
|
||||
Mocker.SetConstant<IEnumerable<IDecisionEngineSpecification>>(mocks.Select(c => c.Object));
|
||||
Mocker.SetConstant<IEnumerable<IDownloadDecisionEngineSpecification>>(mocks.Select(c => c.Object));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -167,6 +167,50 @@ namespace NzbDrone.Core.Test.Download.Aggregation.Aggregators
|
||||
Mocker.GetMock<IIndexerFactory>().VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_multi_languages_when_release_as_specified_language_and_indexer_has_multi_languages_configuration()
|
||||
{
|
||||
var releaseTitle = "Some.Movie.2024.MULTi.VFF.VFQ.1080p.BluRay.DTS.HDMA.x264-RlsGroup";
|
||||
var indexerDefinition = new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Settings = new TorrentRssIndexerSettings { MultiLanguages = new List<int> { Language.Original.Id, Language.French.Id } }
|
||||
};
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(v => v.Find(1))
|
||||
.Returns(indexerDefinition);
|
||||
|
||||
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.French }, releaseTitle);
|
||||
_remoteMovie.Release.IndexerId = 1;
|
||||
_remoteMovie.Release.Title = releaseTitle;
|
||||
|
||||
Subject.Aggregate(_remoteMovie).Languages.Should().BeEquivalentTo(new List<Language> { _movie.MovieMetadata.Value.OriginalLanguage, Language.French });
|
||||
Mocker.GetMock<IIndexerFactory>().Verify(c => c.Find(1), Times.Once());
|
||||
Mocker.GetMock<IIndexerFactory>().VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_multi_languages_when_release_as_other_language_and_indexer_has_multi_languages_configuration()
|
||||
{
|
||||
var releaseTitle = "Some.Movie.2024.MULTi.GERMAN.1080p.BluRay.DTS.HDMA.x264-RlsGroup";
|
||||
var indexerDefinition = new IndexerDefinition
|
||||
{
|
||||
Id = 1,
|
||||
Settings = new TorrentRssIndexerSettings { MultiLanguages = new List<int> { Language.Original.Id, Language.French.Id } }
|
||||
};
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(v => v.Find(1))
|
||||
.Returns(indexerDefinition);
|
||||
|
||||
_remoteMovie.ParsedMovieInfo = GetParsedMovieInfo(new List<Language> { Language.German }, releaseTitle);
|
||||
_remoteMovie.Release.IndexerId = 1;
|
||||
_remoteMovie.Release.Title = releaseTitle;
|
||||
|
||||
Subject.Aggregate(_remoteMovie).Languages.Should().BeEquivalentTo(new List<Language> { _movie.MovieMetadata.Value.OriginalLanguage, Language.French, Language.German });
|
||||
Mocker.GetMock<IIndexerFactory>().Verify(c => c.Find(1), Times.Once());
|
||||
Mocker.GetMock<IIndexerFactory>().VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_original_when_indexer_has_no_multi_languages_configuration()
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.History;
|
||||
@@ -107,11 +106,11 @@ namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new Rejection("Rejected!")), "Test Failure"),
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure"),
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1999.mkv" }, new Rejection("Rejected!")), "Test Failure")
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1999.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure")
|
||||
});
|
||||
|
||||
Subject.Import(_trackedDownload);
|
||||
@@ -131,11 +130,11 @@ namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new Rejection("Rejected!")), "Test Failure"),
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure"),
|
||||
|
||||
new ImportResult(
|
||||
new ImportDecision(
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new Rejection("Rejected!")), "Test Failure")
|
||||
new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure")
|
||||
});
|
||||
|
||||
_trackedDownload.RemoteMovie.Movie = new Movie();
|
||||
|
||||
@@ -189,8 +189,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
{
|
||||
var decisions = new List<DownloadDecision>();
|
||||
RemoteMovie remoteMovie = null;
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!")));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!")));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!")));
|
||||
|
||||
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
|
||||
}
|
||||
@@ -201,7 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var remoteMovie = GetRemoteMovie(new QualityModel(Quality.HDTV720p));
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Never());
|
||||
@@ -214,7 +214,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(removeMovie));
|
||||
decisions.Add(new DownloadDecision(removeMovie, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(removeMovie, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Never());
|
||||
@@ -226,8 +226,8 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
var remoteEpisode = GetRemoteMovie(new QualityModel(Quality.HDTV720p));
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
|
||||
|
||||
await Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Once());
|
||||
|
||||
@@ -95,5 +95,22 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
|
||||
VerifySingleItem(DownloadItemStatus.Completed);
|
||||
}
|
||||
|
||||
[TestCase("@eaDir")]
|
||||
[TestCase(".@__thumb")]
|
||||
public void GetItems_should_not_include_special_subfolders(string folderName)
|
||||
{
|
||||
GivenCompletedItem();
|
||||
|
||||
var targetDir = Path.Combine(_completedDownloadFolder, folderName);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.GetDirectories(_completedDownloadFolder))
|
||||
.Returns(new[] { targetDir });
|
||||
|
||||
var items = Subject.GetItems(_completedDownloadFolder, TimeSpan.FromMilliseconds(50)).ToList();
|
||||
|
||||
items.Count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,6 +711,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.1006066990976857f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_just_under_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(1.0f);
|
||||
GivenCompletedTorrent(state, ratio: 0.9999f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
|
||||
@@ -723,6 +747,30 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f);
|
||||
GivenCompletedTorrent(state, ratio: 1.1006066990976857f, ratioLimit: 1.1f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_be_removable_and_should_allow_move_files_if_just_under_overridden_max_ratio_reached_after_rounding_and_paused(string state)
|
||||
{
|
||||
GivenGlobalSeedLimits(2.0f);
|
||||
GivenCompletedTorrent(state, ratio: 0.9999f, ratioLimit: 1.0f);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
item.CanBeRemoved.Should().BeTrue();
|
||||
item.CanMoveFiles.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
[TestCase("stoppedUP")]
|
||||
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_remoteMovie.ParsedMovieInfo = _parsedMovieInfo;
|
||||
_remoteMovie.Release = _release;
|
||||
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
|
||||
|
||||
_heldReleases = new List<PendingRelease>();
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_remoteMovie.ParsedMovieInfo = _parsedMovieInfo;
|
||||
_remoteMovie.Release = _release;
|
||||
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
|
||||
|
||||
_heldReleases = new List<PendingRelease>();
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
_remoteMovie.ParsedMovieInfo = _parsedMovieInfo;
|
||||
_remoteMovie.Release = _release;
|
||||
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new Rejection("Temp Rejected", RejectionType.Temporary));
|
||||
_temporarilyRejected = new DownloadDecision(_remoteMovie, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
|
||||
|
||||
Mocker.GetMock<IPendingReleaseRepository>()
|
||||
.Setup(s => s.All())
|
||||
|
||||
@@ -76,6 +76,19 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
Subject.Check().ShouldBeWarning(wikiFragment: "downloads-in-root-folder");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_if_downloading_inside_root_folder()
|
||||
{
|
||||
var rootFolderPath = "c:\\Test".AsOsAgnostic();
|
||||
var downloadRootPath = "c:\\Test\\Downloads".AsOsAgnostic();
|
||||
|
||||
GivenRootFolder(rootFolderPath);
|
||||
|
||||
_clientStatus.OutputRootFolders = new List<OsPath> { new (downloadRootPath) };
|
||||
|
||||
Subject.Check().ShouldBeWarning();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_ok_if_not_downloading_to_root_folder()
|
||||
{
|
||||
@@ -87,7 +100,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("DownloadClientExceptions")]
|
||||
[TestCaseSource(nameof(DownloadClientExceptions))]
|
||||
public void should_return_ok_if_client_throws_downloadclientexception(Exception ex)
|
||||
{
|
||||
_downloadClient.Setup(s => s.GetStatus())
|
||||
|
||||
@@ -7,7 +7,6 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
@@ -46,9 +45,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
.With(s => s.Path = @"C:\Test\TV\30 Rock".AsOsAgnostic())
|
||||
.Build();
|
||||
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision(new LocalMovie(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
|
||||
|
||||
_approvedDecisions.Add(new ImportDecision(
|
||||
new LocalMovie
|
||||
|
||||
@@ -4,7 +4,6 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MovieImport;
|
||||
@@ -49,13 +48,13 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
_fail2 = new Mock<IImportDecisionEngineSpecification>();
|
||||
_fail3 = new Mock<IImportDecisionEngineSpecification>();
|
||||
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
|
||||
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
|
||||
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail3"));
|
||||
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail1"));
|
||||
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail2"));
|
||||
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail3"));
|
||||
|
||||
_movie = Builder<Movie>.CreateNew()
|
||||
.With(e => e.Path = @"C:\Test\Movie".AsOsAgnostic())
|
||||
|
||||
@@ -54,6 +54,17 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Specifications
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) part1.mkv",
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) part2.mkv"
|
||||
})]
|
||||
|
||||
[TestCase(new object[]
|
||||
{
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) pt1.mkv",
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) pt2.mkv"
|
||||
})]
|
||||
[TestCase(new object[]
|
||||
{
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) P1.mkv",
|
||||
@"C:\Test\Downloaded\Bad Boys (2006) P2.mkv"
|
||||
})]
|
||||
[TestCase(new object[]
|
||||
{
|
||||
@"C:\Test\Downloaded\blah blah - cd 1.mvk",
|
||||
|
||||
@@ -52,6 +52,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title : Other Title 2010 x264.720p.Blu-ray Rip HD.VOSTFR.VFF. ONLY")]
|
||||
[TestCase("Movie Title 2019 HEVC.2160p.Blu-ray 4K.VOSTFR.VFF. JATO")]
|
||||
[TestCase("Movie.Title.1956.MULTi.VF.Bluray.1080p.REMUX.AC3.x264")]
|
||||
[TestCase("Movie.Title.2016.ENG-ITA-FRE.AAC.1080p.WebDL.x264")]
|
||||
[TestCase("Movie Title 2016 (BDrip 1080p ENG-ITA-FRE) Multisub x264")]
|
||||
public void should_parse_language_french(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
@@ -87,7 +89,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.German.1080p.XviD-LOL")]
|
||||
[TestCase("Movie.Title.2016.GERMAN.DUBBED.WS.WEBRiP.XviD.REPACK-TVP")]
|
||||
[TestCase("Movie Title 2016 - Kampfhaehne - mkv - by Videomann")]
|
||||
[TestCase("Movie.Title.2016.Ger.Dub.AAC.1080p.WebDL.x264-TKP21")]
|
||||
[TestCase("Movie.Title.2016.Ger.AAC.1080p.WebDL.x264-TKP21")]
|
||||
[TestCase("Movie.Title.2016.Hun/Ger/Ita.AAC.1080p.WebDL.x264-TKP21")]
|
||||
[TestCase("Movie.Title.2016.1080p.10Bit.HEVC.WEBRip.HIN-ENG-GER.DD5.1.H.265")]
|
||||
[TestCase("Movie.Title.2016.HU-IT-DE.AAC.1080p.WebDL.x264")]
|
||||
public void should_parse_language_german(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
@@ -96,6 +104,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.Italian.1080p.XviD-LOL")]
|
||||
[TestCase("Movie.Title.2016.ENG-FRE-ITA.AAC.1080p.WebDL.x264")]
|
||||
[TestCase("Movie Title 2016 (BDrip 1080p ENG-FRE-ITA) Multisub x264")]
|
||||
public void should_parse_language_italian(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
@@ -120,6 +130,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.Japanese.1080p.XviD-LOL")]
|
||||
[TestCase("Movie.Title (1988) 2160p HDR 5.1 Eng - Jpn x265 10bit")]
|
||||
[TestCase("Movie Title (1985) (1080p.AC3 ITA-ENG-JPN)")]
|
||||
public void should_parse_language_japanese(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
@@ -291,6 +303,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.Korean.1080p.XviD-LOL")]
|
||||
[TestCase("Movie Title [2006] BDRip 720p [Kor Rus] GROUP")]
|
||||
[TestCase("Movie.Title.2019.KOR.1080p.HDRip.H264.AAC-GROUP")]
|
||||
public void should_parse_language_korean(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Genre";
|
||||
|
||||
[FieldDefinition(1, Label = "Genre(s)", Type = FieldType.Tag)]
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationGenre", Type = FieldType.Tag)]
|
||||
public IEnumerable<string> Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Original Language";
|
||||
|
||||
[FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(OriginalLanguageFieldConverter))]
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationOriginalLanguage", Type = FieldType.Select, SelectOptions = typeof(OriginalLanguageFieldConverter))]
|
||||
public int Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Quality Profile";
|
||||
|
||||
[FieldDefinition(1, Label = "Quality Profile", Type = FieldType.QualityProfile)]
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationQualityProfile", Type = FieldType.QualityProfile)]
|
||||
public int Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Root Folder";
|
||||
|
||||
[FieldDefinition(1, Label = "Root Folder", Type = FieldType.RootFolder)]
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationRootFolder", Type = FieldType.RootFolder)]
|
||||
public string Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Runtime";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Runtime", Type = FieldType.Number)]
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationMinimumRuntime", Type = FieldType.Number, Unit = "minutes")]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Runtime", Type = FieldType.Number)]
|
||||
[FieldDefinition(2, Label = "AutoTaggingSpecificationMaximumRuntime", Type = FieldType.Number, Unit = "minutes")]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
{
|
||||
public class StatusSpecificationValidator : AbstractValidator<StatusSpecification>
|
||||
{
|
||||
public StatusSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Status).Custom((statusType, context) =>
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(MovieStatusType), statusType))
|
||||
{
|
||||
context.AddFailure($"Invalid status type condition value: {statusType}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class StatusSpecification : AutoTaggingSpecificationBase
|
||||
{
|
||||
private static readonly StatusSpecificationValidator Validator = new ();
|
||||
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Status";
|
||||
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationStatus", Type = FieldType.Select, SelectOptions = typeof(MovieStatusType))]
|
||||
public int Status { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
{
|
||||
return movie?.MovieMetadata?.Value?.Status == (MovieStatusType)Status;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ namespace NzbDrone.Core.AutoTagging.Specifications
|
||||
public override int Order => 1;
|
||||
public override string ImplementationName => "Year";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Year", Type = FieldType.Number)]
|
||||
[FieldDefinition(1, Label = "AutoTaggingSpecificationMinimumYear", Type = FieldType.Number)]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Year", Type = FieldType.Number)]
|
||||
[FieldDefinition(2, Label = "AutoTaggingSpecificationMaximumYear", Type = FieldType.Number)]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
public override int Order => 3;
|
||||
public override string ImplementationName => "Language";
|
||||
|
||||
[FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationLanguage", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))]
|
||||
public int Value { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationExceptLanguage", HelpText = "CustomFormatsSpecificationExceptLanguageHelpText", Type = FieldType.Checkbox)]
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
public override int Order => 7;
|
||||
public override string ImplementationName => "Quality Modifier";
|
||||
|
||||
[FieldDefinition(1, Label = "Quality Modifier", Type = FieldType.Select, SelectOptions = typeof(Modifier))]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationQualityModifier", Type = FieldType.Select, SelectOptions = typeof(Modifier))]
|
||||
public int Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
public override int Order => 6;
|
||||
public override string ImplementationName => "Resolution";
|
||||
|
||||
[FieldDefinition(1, Label = "Resolution", Type = FieldType.Select, SelectOptions = typeof(Resolution))]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationResolution", Type = FieldType.Select, SelectOptions = typeof(Resolution))]
|
||||
public int Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
|
||||
@@ -21,10 +21,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
public override int Order => 8;
|
||||
public override string ImplementationName => "Size";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Size", HelpText = "Release must be greater than this size", Unit = "GB", Type = FieldType.Number)]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationMinimumSize", HelpText = "CustomFormatsSpecificationMinimumSizeHelpText", Unit = "GB", Type = FieldType.Number)]
|
||||
public double Min { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Maximum Size", HelpText = "Release must be less than or equal to this size", Unit = "GB", Type = FieldType.Number)]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationMaximumSize", HelpText = "CustomFormatsSpecificationMaximumSizeHelpText", Unit = "GB", Type = FieldType.Number)]
|
||||
public double Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
public override int Order => 5;
|
||||
public override string ImplementationName => "Source";
|
||||
|
||||
[FieldDefinition(1, Label = "Source", Type = FieldType.Select, SelectOptions = typeof(QualitySource))]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationSource", Type = FieldType.Select, SelectOptions = typeof(QualitySource))]
|
||||
public int Value { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
public override int Order => 10;
|
||||
public override string ImplementationName => "Year";
|
||||
|
||||
[FieldDefinition(1, Label = "Minimum Year", Type = FieldType.Number)]
|
||||
[FieldDefinition(1, Label = "CustomFormatsSpecificationMinimumYear", Type = FieldType.Number)]
|
||||
public int Min { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Maximum Year", Type = FieldType.Number)]
|
||||
[FieldDefinition(2, Label = "CustomFormatsSpecificationMaximumYear", Type = FieldType.Number)]
|
||||
public int Max { get; set; }
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(241)]
|
||||
public class stevenlu_update_url : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(FixStevenLuListsLink);
|
||||
}
|
||||
|
||||
private void FixStevenLuListsLink(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var updated = new List<object>();
|
||||
|
||||
using (var getStevenLuListCmd = conn.CreateCommand())
|
||||
{
|
||||
getStevenLuListCmd.Transaction = tran;
|
||||
getStevenLuListCmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"ImportLists\" WHERE \"ConfigContract\" = 'StevenLuSettings'";
|
||||
|
||||
using var reader = getStevenLuListCmd.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetInt32(0);
|
||||
var settings = Json.Deserialize<JObject>(reader.GetString(1));
|
||||
|
||||
var link = settings.Value<string>("link");
|
||||
|
||||
if (link.IsNotNullOrWhiteSpace() && link.StartsWith("https://s3.amazonaws.com/popular-movies"))
|
||||
{
|
||||
settings["link"] = "https://popular-movies-data.stevenlu.com/movies.json";
|
||||
}
|
||||
|
||||
updated.Add(new
|
||||
{
|
||||
Id = id,
|
||||
Settings = settings.ToJson()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE \"ImportLists\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
|
||||
conn.Execute(updateSql, updated, transaction: tran);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class Decision
|
||||
{
|
||||
public bool Accepted { get; private set; }
|
||||
public string Reason { get; private set; }
|
||||
|
||||
private static readonly Decision AcceptDecision = new Decision { Accepted = true };
|
||||
private Decision()
|
||||
{
|
||||
}
|
||||
|
||||
public static Decision Accept()
|
||||
{
|
||||
return AcceptDecision;
|
||||
}
|
||||
|
||||
public static Decision Reject(string reason, params object[] args)
|
||||
{
|
||||
return Reject(string.Format(reason, args));
|
||||
}
|
||||
|
||||
public static Decision Reject(string reason)
|
||||
{
|
||||
return new Decision
|
||||
{
|
||||
Accepted = false,
|
||||
Reason = reason
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public RemoteMovie RemoteMovie { get; private set; }
|
||||
|
||||
public IEnumerable<Rejection> Rejections { get; private set; }
|
||||
public IEnumerable<DownloadRejection> Rejections { get; private set; }
|
||||
|
||||
public bool Approved => !Rejections.Any();
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadDecision(RemoteMovie movie, params Rejection[] rejections)
|
||||
public DownloadDecision(RemoteMovie movie, params DownloadRejection[] rejections)
|
||||
{
|
||||
RemoteMovie = movie;
|
||||
Rejections = rejections.ToList();
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return Math.Round(Math.Log10(age)) * -1;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
public class DownloadDecisionMaker : IMakeDownloadDecision
|
||||
{
|
||||
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
|
||||
private readonly IEnumerable<IDownloadDecisionEngineSpecification> _specifications;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ICustomFormatCalculationService _formatCalculator;
|
||||
private readonly IRemoteMovieAggregationService _aggregationService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
|
||||
public DownloadDecisionMaker(IEnumerable<IDownloadDecisionEngineSpecification> specifications,
|
||||
IParsingService parsingService,
|
||||
IConfigService configService,
|
||||
ICustomFormatCalculationService formatCalculator,
|
||||
@@ -85,9 +85,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
if (remoteMovie.Movie == null)
|
||||
{
|
||||
var reason = "Unknown Movie";
|
||||
|
||||
decision = new DownloadDecision(remoteMovie, new Rejection(reason));
|
||||
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnknownMovie, "Unknown Movie. Unable to identify correct movie using release name."));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -96,6 +94,8 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
remoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(remoteMovie, remoteMovie.Release.Size);
|
||||
remoteMovie.CustomFormatScore = remoteMovie?.Movie?.QualityProfile?.CalculateCustomFormatScore(remoteMovie.CustomFormats) ?? 0;
|
||||
|
||||
_logger.Trace("Custom Format Score of '{0}' [{1}] calculated for '{2}'", remoteMovie.CustomFormatScore, remoteMovie.CustomFormats?.ConcatToString(), report.Title);
|
||||
|
||||
remoteMovie.DownloadAllowed = remoteMovie.Movie != null;
|
||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||
}
|
||||
@@ -121,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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,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++;
|
||||
@@ -173,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))
|
||||
{
|
||||
@@ -190,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
|
||||
{
|
||||
@@ -198,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)
|
||||
@@ -210,7 +210,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
e.Data.Add("report", remoteMovie.Release.ToJson());
|
||||
e.Data.Add("parsed", remoteMovie.ParsedMovieInfo.ToJson());
|
||||
_logger.Error(e, "Couldn't evaluate decision on {0}, with spec: {1}", remoteMovie.Release.Title, spec.GetType().Name);
|
||||
return new Rejection($"{spec.GetType().Name}: {e.Message}");
|
||||
return new DownloadRejection(DownloadRejectionReason.DecisionError, $"{spec.GetType().Name}: {e.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
9
src/NzbDrone.Core/DecisionEngine/DownloadRejection.cs
Normal file
9
src/NzbDrone.Core/DecisionEngine/DownloadRejection.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Core.DecisionEngine;
|
||||
|
||||
public class DownloadRejection : Rejection<DownloadRejectionReason>
|
||||
{
|
||||
public DownloadRejection(DownloadRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
|
||||
: base(reason, message, type)
|
||||
{
|
||||
}
|
||||
}
|
||||
67
src/NzbDrone.Core/DecisionEngine/DownloadRejectionReason.cs
Normal file
67
src/NzbDrone.Core/DecisionEngine/DownloadRejectionReason.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
namespace NzbDrone.Core.DecisionEngine;
|
||||
|
||||
public enum DownloadRejectionReason
|
||||
{
|
||||
Unknown,
|
||||
UnknownMovie,
|
||||
UnableToParse,
|
||||
Error,
|
||||
DecisionError,
|
||||
Availability,
|
||||
MinimumAgeDelay,
|
||||
MovieNotMonitored,
|
||||
HistoryRecentCutoffMet,
|
||||
HistoryCdhDisabledCutoffMet,
|
||||
HistoryHigherPreference,
|
||||
HistoryHigherRevision,
|
||||
HistoryCutoffMet,
|
||||
HistoryCustomFormatCutoffMet,
|
||||
HistoryCustomFormatScore,
|
||||
HistoryCustomFormatScoreIncrement,
|
||||
HistoryUpgradesNotAllowed,
|
||||
NoMatchingTag,
|
||||
PropersDisabled,
|
||||
ProperForOldFile,
|
||||
WrongMovie,
|
||||
UnknownRuntime,
|
||||
BelowMinimumSize,
|
||||
AboveMaximumSize,
|
||||
AlreadyImportedSameHash,
|
||||
AlreadyImportedSameName,
|
||||
IndexerDisabled,
|
||||
Blocklisted,
|
||||
CustomFormatMinimumScore,
|
||||
MinimumFreeSpace,
|
||||
HardcodeSubtitles,
|
||||
WantedLanguage,
|
||||
MaximumSizeExceeded,
|
||||
MinimumAge,
|
||||
MaximumAge,
|
||||
Sample,
|
||||
ProtocolDisabled,
|
||||
QualityNotWanted,
|
||||
QualityUpgradesDisabled,
|
||||
QueueHigherPreference,
|
||||
QueueHigherRevision,
|
||||
QueueCutoffMet,
|
||||
QueueCustomFormatCutoffMet,
|
||||
QueueCustomFormatScore,
|
||||
QueueCustomFormatScoreIncrement,
|
||||
QueueUpgradesNotAllowed,
|
||||
QueuePropersDisabled,
|
||||
Raw,
|
||||
MustContainMissing,
|
||||
MustNotContainPresent,
|
||||
RepackDisabled,
|
||||
RepackUnknownReleaseGroup,
|
||||
RepackReleaseGroupDoesNotMatch,
|
||||
RequiredFlags,
|
||||
MinimumSeeders,
|
||||
DiskHigherPreference,
|
||||
DiskHigherRevision,
|
||||
DiskCutoffMet,
|
||||
DiskCustomFormatCutoffMet,
|
||||
DiskCustomFormatScore,
|
||||
DiskCustomFormatScoreIncrement,
|
||||
DiskUpgradesNotAllowed
|
||||
}
|
||||
34
src/NzbDrone.Core/DecisionEngine/DownloadSpecDecision.cs
Normal file
34
src/NzbDrone.Core/DecisionEngine/DownloadSpecDecision.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class DownloadSpecDecision
|
||||
{
|
||||
public bool Accepted { get; private set; }
|
||||
public DownloadRejectionReason Reason { get; set; }
|
||||
public string Message { get; private set; }
|
||||
|
||||
private static readonly DownloadSpecDecision AcceptDownloadSpecDecision = new () { Accepted = true };
|
||||
private DownloadSpecDecision()
|
||||
{
|
||||
}
|
||||
|
||||
public static DownloadSpecDecision Accept()
|
||||
{
|
||||
return AcceptDownloadSpecDecision;
|
||||
}
|
||||
|
||||
public static DownloadSpecDecision Reject(DownloadRejectionReason reason, string message, params object[] args)
|
||||
{
|
||||
return Reject(reason, string.Format(message, args));
|
||||
}
|
||||
|
||||
public static DownloadSpecDecision Reject(DownloadRejectionReason reason, string message)
|
||||
{
|
||||
return new DownloadSpecDecision
|
||||
{
|
||||
Accepted = false,
|
||||
Reason = reason,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class Rejection
|
||||
public class Rejection<TRejectionReason>
|
||||
{
|
||||
public string Reason { get; set; }
|
||||
public TRejectionReason Reason { get; set; }
|
||||
public string Message { get; set; }
|
||||
public RejectionType Type { get; set; }
|
||||
|
||||
public Rejection(string reason, RejectionType type = RejectionType.Permanent)
|
||||
public Rejection(TRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
|
||||
{
|
||||
Reason = reason;
|
||||
Message = message;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}] {1}", Type, Reason);
|
||||
return string.Format("[{0}] {1}", Type, Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AcceptableSizeSpecification : IDecisionEngineSpecification
|
||||
public class AcceptableSizeSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IQualityDefinitionService _qualityDefinitionService;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Beginning size check for: {0}", subject);
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (subject.Release.Size == 0)
|
||||
{
|
||||
_logger.Debug("Release has unknown size, skipping size check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var qualityDefinition = _qualityDefinitionService.Get(quality);
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var runtimeMessage = subject.Movie.Title;
|
||||
|
||||
_logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage);
|
||||
return Decision.Reject("{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.BelowMinimumSize, "{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
else if (subject.Movie.MovieMetadata.Value.Runtime == 0)
|
||||
{
|
||||
_logger.Debug("Movie runtime is 0, unable to validate size until it is available, rejecting");
|
||||
return Decision.Reject("Movie runtime is 0, unable to validate size until it is available");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.UnknownRuntime, "Movie runtime is 0, unable to validate size until it is available");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -77,12 +77,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (subject.Release.Size > maxSize)
|
||||
{
|
||||
_logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} for {3}), rejecting", subject, subject.Release.Size, maxSize, subject.Movie.Title);
|
||||
return Decision.Reject("{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), subject.Movie.Title);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.AboveMaximumSize, "{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), subject.Movie.Title);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Item: {0}, meets size constraints", subject);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AlreadyImportedSpecification : IDecisionEngineSpecification
|
||||
public class AlreadyImportedSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -27,14 +27,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||
|
||||
if (!cdhEnabled)
|
||||
{
|
||||
_logger.Debug("Skipping already imported check because CDH is disabled");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var movie = subject.Movie;
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!movie.HasFile)
|
||||
{
|
||||
_logger.Debug("Skipping already imported check for movie without file");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var historyForMovie = _historyService.GetByMovieId(movie.Id, null);
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (lastGrabbed == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var imported = historyForMovie.FirstOrDefault(h =>
|
||||
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (imported == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
// This is really only a guard against redownloading the same release over
|
||||
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
// match skip this check.
|
||||
if (lastGrabbed.Quality.Equals(imported.Quality))
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var release = subject.Release;
|
||||
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (torrentInfo?.InfoHash != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId)
|
||||
{
|
||||
_logger.Debug("Has same torrent hash as a grabbed and imported release");
|
||||
return Decision.Reject("Has same torrent hash as a grabbed and imported release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.AlreadyImportedSameHash, "Has same torrent hash as a grabbed and imported release");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,11 +91,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_logger.Debug("Has same release name as a grabbed and imported release");
|
||||
return Decision.Reject("Has same release name as a grabbed and imported release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.AlreadyImportedSameName, "Has same release name as a grabbed and imported release");
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class BlockedIndexerSpecification : IDecisionEngineSpecification
|
||||
public class BlockedIndexerSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
private readonly Logger _logger;
|
||||
@@ -27,15 +27,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var status = _blockedIndexerCache.Find(subject.Release.IndexerId.ToString());
|
||||
if (status != null)
|
||||
{
|
||||
return Decision.Reject($"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.IndexerDisabled, $"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
private IDictionary<string, IndexerStatus> FetchBlockedIndexer()
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class BlocklistSpecification : IDecisionEngineSpecification
|
||||
public class BlocklistSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IBlocklistService _blocklistService;
|
||||
private readonly Logger _logger;
|
||||
@@ -19,15 +19,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_blocklistService.Blocklisted(subject.Movie.Id, subject.Release))
|
||||
{
|
||||
_logger.Debug("{0} is blocklisted, rejecting.", subject.Release.Title);
|
||||
return Decision.Reject("Release is blocklisted");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Blocklisted, "Release is blocklisted");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class CustomFormatAllowedbyProfileSpecification : IDecisionEngineSpecification
|
||||
public class CustomFormatAllowedbyProfileSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CustomFormatAllowedbyProfileSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
_logger.Trace("Custom Format Score of {0} [{1}] above Movie's profile minimum {2}", score, subject.CustomFormats.ConcatToString(), minScore);
|
||||
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class FreeSpaceSpecification : IDecisionEngineSpecification
|
||||
public class FreeSpaceSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
@@ -21,15 +21,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public SpecificationPriority Priority => SpecificationPriority.Disk;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_configService.SkipFreeSpaceCheckWhenImporting)
|
||||
{
|
||||
_logger.Debug("Skipping free space check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var size = subject.Release.Size;
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
_logger.Debug("Unable to get available space for {0}. Skipping", path);
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var minimumSpace = _configService.MinimumFreeSpaceWhenImporting.Megabytes();
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var message = "Importing after download will exceed available disk space";
|
||||
|
||||
_logger.Debug(message);
|
||||
return Decision.Reject(message);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumFreeSpace, message);
|
||||
}
|
||||
|
||||
if (remainingSpace < minimumSpace)
|
||||
@@ -68,10 +68,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var message = $"Not enough free space ({minimumSpace.SizeSuffix()}) to import after download: {remainingSpace.SizeSuffix()}. (Settings: Media Management: Minimum Free Space)";
|
||||
|
||||
_logger.Debug(message);
|
||||
return Decision.Reject(message);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumFreeSpace, message);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class HardcodeSubsSpecification : IDecisionEngineSpecification
|
||||
public class HardcodeSubsSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -21,13 +21,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var hardcodeSubs = subject.ParsedMovieInfo.HardcodedSubs;
|
||||
|
||||
if (_configService.AllowHardcodedSubs || hardcodeSubs.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var whitelisted = _configService.WhitelistedHardcodedSubs.Split(',');
|
||||
@@ -35,12 +35,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (whitelisted != null && whitelisted.Any(t => (hardcodeSubs.ToLower().Contains(t.ToLower()) && t.IsNotNullOrWhiteSpace())))
|
||||
{
|
||||
_logger.Debug("Release hardcode subs ({0}) are in allowed values ({1})", hardcodeSubs, whitelisted);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Hardcode subs found: {0}", hardcodeSubs);
|
||||
return Decision.Reject("Hardcode subs found: {0}", hardcodeSubs);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HardcodeSubtitles, "Hardcode subs found: {0}", hardcodeSubs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public interface IDecisionEngineSpecification
|
||||
public interface IDownloadDecisionEngineSpecification
|
||||
{
|
||||
RejectionType Type { get; }
|
||||
|
||||
SpecificationPriority Priority { get; }
|
||||
|
||||
Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria);
|
||||
DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class LanguageSpecification : IDecisionEngineSpecification
|
||||
public class LanguageSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var wantedLanguage = subject.Movie.QualityProfile.Language;
|
||||
|
||||
if (wantedLanguage == Language.Any)
|
||||
{
|
||||
_logger.Debug("Profile allows any language, accepting release.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var originalLanguage = subject.Movie.MovieMetadata.Value.OriginalLanguage;
|
||||
@@ -34,10 +34,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!subject.Languages.Contains(originalLanguage))
|
||||
{
|
||||
_logger.Debug("Original Language({0}) is wanted, but found {1}", originalLanguage, subject.Languages.ToExtendedString());
|
||||
return Decision.Reject("Original Language ({0}) is wanted, but found {1}", originalLanguage, subject.Languages.ToExtendedString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.WantedLanguage, "Original Language ({0}) is wanted, but found {1}", originalLanguage, subject.Languages.ToExtendedString());
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedMovieInfo.Languages.ToExtendedString());
|
||||
@@ -45,10 +45,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!subject.Languages.Contains(wantedLanguage))
|
||||
{
|
||||
_logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.Languages.ToExtendedString(), wantedLanguage);
|
||||
return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.Languages.ToExtendedString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.WantedLanguage, "{0} is wanted, but found {1}", wantedLanguage, subject.Languages.ToExtendedString());
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class MaximumSizeSpecification : IDecisionEngineSpecification
|
||||
public class MaximumSizeSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var size = subject.Release.Size;
|
||||
var maximumSize = _configService.MaximumSize.Megabytes();
|
||||
@@ -28,13 +28,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (maximumSize == 0)
|
||||
{
|
||||
_logger.Debug("Maximum size is not set.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
_logger.Debug("Release has unknown size, skipping size check.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if release meets maximum size requirements. {0}", size.SizeSuffix());
|
||||
@@ -44,10 +44,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var message = $"{size.SizeSuffix()} is too big, maximum size is {maximumSize.SizeSuffix()} (Settings->Indexers->Maximum Size)";
|
||||
|
||||
_logger.Debug(message);
|
||||
return Decision.Reject(message);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MaximumSizeExceeded, message);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class MinimumAgeSpecification : IDecisionEngineSpecification
|
||||
public class MinimumAgeSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,12 +20,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
|
||||
{
|
||||
_logger.Debug("Not checking minimum age requirement for non-usenet report");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var age = subject.Release.AgeMinutes;
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (minimumAge == 0)
|
||||
{
|
||||
_logger.Debug("Minimum age is not set.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if report meets minimum age requirements. {0}", ageRounded);
|
||||
@@ -43,12 +43,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (age < minimumAge)
|
||||
{
|
||||
_logger.Debug("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAge, "Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
}
|
||||
|
||||
_logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", ageRounded, minimumAge);
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class NotSampleSpecification : IDecisionEngineSpecification
|
||||
public class NotSampleSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -17,15 +17,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes())
|
||||
{
|
||||
_logger.Debug("Sample release, rejecting.");
|
||||
return Decision.Reject("Sample");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Sample, "Sample");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Profiles.Delay;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class ProtocolSpecification : IDecisionEngineSpecification
|
||||
public class ProtocolSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IDelayProfileService _delayProfileService;
|
||||
private readonly Logger _logger;
|
||||
@@ -21,23 +21,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(subject.Movie.Tags);
|
||||
|
||||
if (subject.Release.DownloadProtocol == DownloadProtocol.Usenet && !delayProfile.EnableUsenet)
|
||||
{
|
||||
_logger.Debug("[{0}] Usenet is not enabled for this movie", subject.Release.Title);
|
||||
return Decision.Reject("Usenet is not enabled for this movie");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProtocolDisabled, "Usenet is not enabled for this movie");
|
||||
}
|
||||
|
||||
if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent && !delayProfile.EnableTorrent)
|
||||
{
|
||||
_logger.Debug("[{0}] Torrent is not enabled for this movie", subject.Release.Title);
|
||||
return Decision.Reject("Torrent is not enabled for this movie");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProtocolDisabled, "Torrent is not enabled for this movie");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class QualityAllowedByProfileSpecification : IDecisionEngineSpecification
|
||||
public class QualityAllowedByProfileSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedMovieInfo.Quality);
|
||||
|
||||
@@ -27,10 +27,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (!qualityOrGroup.Allowed)
|
||||
{
|
||||
_logger.Debug("Quality {0} rejected by Movie's quality profile", subject.ParsedMovieInfo.Quality);
|
||||
return Decision.Reject("{0} is not wanted in profile", subject.ParsedMovieInfo.Quality.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QualityNotWanted, "{0} is not wanted in profile", subject.ParsedMovieInfo.Quality.Quality);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using NzbDrone.Core.Queue;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class QueueSpecification : IDecisionEngineSpecification
|
||||
public class QueueSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IQueueService _queueService;
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var queue = _queueService.GetQueue();
|
||||
var matchingMovies = queue.Where(q => q.RemoteMovie?.Movie != null &&
|
||||
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
queuedItemCustomFormats,
|
||||
subject.ParsedMovieInfo.Quality))
|
||||
{
|
||||
return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCutoffMet, "Quality for release in queue already meets cutoff: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
@@ -80,25 +80,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
switch (upgradeableRejectReason)
|
||||
{
|
||||
case UpgradeableRejectReason.BetterQuality:
|
||||
return Decision.Reject("Release in queue is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueHigherPreference, "Release in queue is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality);
|
||||
|
||||
case UpgradeableRejectReason.BetterRevision:
|
||||
return Decision.Reject("Release in queue is of equal or higher revision: {0}", remoteMovie.ParsedMovieInfo.Quality.Revision);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueHigherRevision, "Release in queue is of equal or higher revision: {0}", remoteMovie.ParsedMovieInfo.Quality.Revision);
|
||||
|
||||
case UpgradeableRejectReason.QualityCutoff:
|
||||
return Decision.Reject("Release in queue meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCutoffMet, "Release in queue meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatCutoff:
|
||||
return Decision.Reject("Release in queue meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatCutoffMet, "Release in queue meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatScore:
|
||||
return Decision.Reject("Release in queue has an equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats));
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatScore, "Release in queue has an equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats));
|
||||
|
||||
case UpgradeableRejectReason.MinCustomFormatScore:
|
||||
return Decision.Reject("Release in queue has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatScoreIncrement, "Release in queue has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.UpgradesNotAllowed:
|
||||
return Decision.Reject("Release in queue and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueUpgradesNotAllowed, "Release in queue and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
}
|
||||
|
||||
if (_upgradableSpecification.IsRevisionUpgrade(remoteMovie.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality))
|
||||
@@ -106,12 +106,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueuePropersDisabled, "Proper downloading is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RawDiskSpecification : IDecisionEngineSpecification
|
||||
public class RawDiskSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private static readonly Regex[] DiscRegex = new[]
|
||||
{
|
||||
@@ -29,11 +29,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
foreach (var regex in DiscRegex)
|
||||
@@ -41,28 +41,28 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (regex.IsMatch(subject.Release.Title))
|
||||
{
|
||||
_logger.Debug("Release contains raw Bluray/DVD, rejecting.");
|
||||
return Decision.Reject("Raw Bluray/DVD release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw Bluray/DVD release");
|
||||
}
|
||||
}
|
||||
|
||||
if (subject.Release.Container.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (_dvdContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw DVD, rejecting.");
|
||||
return Decision.Reject("Raw DVD release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw DVD release");
|
||||
}
|
||||
|
||||
if (_blurayContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw Bluray, rejecting.");
|
||||
return Decision.Reject("Raw Bluray release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw Bluray release");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Profiles.Releases;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
|
||||
public class ReleaseRestrictionsSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IReleaseProfileService _releaseProfileService;
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if release meets restrictions: {0}", subject);
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var terms = string.Join(", ", requiredTerms);
|
||||
_logger.Debug("[{0}] does not contain one of the required terms: {1}", title, terms);
|
||||
return Decision.Reject("Does not contain one of the required terms: {0}", terms);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MustContainMissing, "Does not contain one of the required terms: {0}", terms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var terms = string.Join(", ", foundTerms);
|
||||
_logger.Debug("[{0}] contains these ignored terms: {1}", title, terms);
|
||||
return Decision.Reject("Contains these ignored terms: {0}", terms);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MustNotContainPresent, "Contains these ignored terms: {0}", terms);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("[{0}] No restrictions apply, allowing", subject);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
private List<string> ContainsAny(List<string> terms, string title)
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RepackSpecification : IDecisionEngineSpecification
|
||||
public class RepackSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -24,19 +24,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
|
||||
if (!subject.ParsedMovieInfo.Quality.Revision.IsRepack)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
|
||||
{
|
||||
_logger.Debug("Repacks are not preferred, skipping check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Movie.MovieFileId != 0)
|
||||
@@ -51,17 +51,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of repacks is disabled");
|
||||
return Decision.Reject("Repack downloading is disabled");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackDisabled, "Repack downloading is disabled");
|
||||
}
|
||||
|
||||
if (fileReleaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Reject("Unable to determine release group for the existing file");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackUnknownReleaseGroup, "Unable to determine release group for the existing file");
|
||||
}
|
||||
|
||||
if (releaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Reject("Unable to determine release group for this release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackUnknownReleaseGroup, "Unable to determine release group for this release");
|
||||
}
|
||||
|
||||
if (!fileReleaseGroup.Equals(releaseGroup, StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -70,7 +70,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
"Release is a repack for a different release group. Release Group: {0}. File release group: {1}",
|
||||
releaseGroup,
|
||||
fileReleaseGroup);
|
||||
return Decision.Reject(
|
||||
return DownloadSpecDecision.Reject(
|
||||
DownloadRejectionReason.RepackReleaseGroupDoesNotMatch,
|
||||
"Release is a repack for a different release group. Release Group: {0}. File release group: {1}",
|
||||
releaseGroup,
|
||||
fileReleaseGroup);
|
||||
@@ -78,7 +79,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RequiredIndexerFlagsSpecification : IDecisionEngineSpecification
|
||||
public class RequiredIndexerFlagsSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly Logger _logger;
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = subject.Release;
|
||||
|
||||
@@ -37,34 +37,35 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (torrentInfo == null || indexerSettings == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (indexerSettings is ITorrentIndexerSettings torrentIndexerSettings)
|
||||
{
|
||||
var requiredFlags = torrentIndexerSettings.RequiredFlags;
|
||||
var requiredFlag = (IndexerFlags)0;
|
||||
|
||||
if (requiredFlags == null || !requiredFlags.Any())
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var requiredFlag = (IndexerFlags)0;
|
||||
|
||||
foreach (var flag in requiredFlags)
|
||||
{
|
||||
if (torrentInfo.IndexerFlags.HasFlag((IndexerFlags)flag))
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
requiredFlag |= (IndexerFlags)flag;
|
||||
}
|
||||
|
||||
_logger.Debug("None of the required indexer flags {0} where found. Found flags: {1}", requiredFlag, torrentInfo.IndexerFlags);
|
||||
return Decision.Reject("None of the required indexer flags {0} where found. Found flags: {1}", requiredFlag, torrentInfo.IndexerFlags);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.RequiredFlags, "None of the required indexer flags {0} where found. Found flags: {1}", requiredFlag, torrentInfo.IndexerFlags);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RetentionSpecification : IDecisionEngineSpecification
|
||||
public class RetentionSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -19,12 +19,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
|
||||
{
|
||||
_logger.Debug("Not checking retention requirement for non-usenet report");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var age = subject.Release.Age;
|
||||
@@ -34,10 +34,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (retention > 0 && age > retention)
|
||||
{
|
||||
_logger.Debug("Report age: {0} rejected by user's retention limit", age);
|
||||
return Decision.Reject("Older than configured retention");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MaximumAge, "Older than configured retention");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class AvailabilitySpecification : IDecisionEngineSpecification
|
||||
public class AvailabilitySpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
@@ -19,22 +19,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria is { UserInvokedSearch: true })
|
||||
{
|
||||
_logger.Debug("Skipping availability check during search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var availabilityDelay = _configService.AvailabilityDelay;
|
||||
|
||||
if (!subject.Movie.IsAvailable(availabilityDelay))
|
||||
{
|
||||
return Decision.Reject("Movie {0} will only be considered available {1} days after {2}", subject.Movie, availabilityDelay, subject.Movie.MinimumAvailability.ToString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.Availability, "Movie {0} will only be considered available {1} days after {2}", subject.Movie, availabilityDelay, subject.Movie.MinimumAvailability.ToString());
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class DelaySpecification : IDecisionEngineSpecification
|
||||
public class DelaySpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IPendingReleaseService _pendingReleaseService;
|
||||
private readonly IUpgradableSpecification _qualityUpgradableSpecification;
|
||||
@@ -32,12 +32,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null && searchCriteria.UserInvokedSearch)
|
||||
{
|
||||
_logger.Debug("Ignoring delay for user invoked search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var profile = subject.Movie.QualityProfile;
|
||||
@@ -47,11 +47,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
if (delay == 0)
|
||||
{
|
||||
_logger.Debug("Profile does not require a waiting period before download for {0}.", subject.Release.DownloadProtocol);
|
||||
return Decision.Accept();
|
||||
_logger.Debug("Delay Profile does not require a waiting period before download for {0}.", subject.Release.DownloadProtocol);
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var comparer = new QualityModelComparer(profile);
|
||||
_logger.Debug("Delay Profile requires a waiting period of {0} minutes for {1}", delay, subject.Release.DownloadProtocol);
|
||||
|
||||
var qualityComparer = new QualityModelComparer(profile);
|
||||
|
||||
var file = subject.Movie.MovieFile;
|
||||
|
||||
@@ -71,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,12 +82,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (delayProfile.BypassIfHighestQuality)
|
||||
{
|
||||
var bestQualityInProfile = profile.LastAllowedQuality();
|
||||
var isBestInProfile = comparer.Compare(subject.ParsedMovieInfo.Quality.Quality, bestQualityInProfile) >= 0;
|
||||
var isBestInProfile = qualityComparer.Compare(subject.ParsedMovieInfo.Quality.Quality, bestQualityInProfile) >= 0;
|
||||
|
||||
if (isBestInProfile && isPreferredProtocol)
|
||||
{
|
||||
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,16 +108,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
if (oldest != null && oldest.Release.AgeMinutes > delay)
|
||||
{
|
||||
return Decision.Accept();
|
||||
_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 DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Release.AgeMinutes < delay)
|
||||
{
|
||||
_logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol);
|
||||
return Decision.Reject("Waiting for better quality release");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAgeDelay, "Waiting for better quality release");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class HistorySpecification : IDecisionEngineSpecification
|
||||
public class HistorySpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
@@ -33,12 +33,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
_logger.Debug("Skipping history check during search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
if (!recent && cdhEnabled)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var customFormats = _formatService.ParseCustomFormat(mostRecent, subject.Movie);
|
||||
@@ -77,10 +77,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
if (recent)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryRecentCutoffMet, "Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCdhDisabledCutoffMet, "CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
}
|
||||
|
||||
var rejectionSubject = recent ? "Recent" : "CDH is disabled and";
|
||||
@@ -88,32 +88,32 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
switch (upgradeableRejectReason)
|
||||
{
|
||||
case UpgradeableRejectReason.None:
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
|
||||
case UpgradeableRejectReason.BetterQuality:
|
||||
return Decision.Reject("{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryHigherPreference, "{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality);
|
||||
|
||||
case UpgradeableRejectReason.BetterRevision:
|
||||
return Decision.Reject("{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryHigherRevision, "{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision);
|
||||
|
||||
case UpgradeableRejectReason.QualityCutoff:
|
||||
return Decision.Reject("{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCutoffMet, "{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatCutoff:
|
||||
return Decision.Reject("{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatCutoffMet, "{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatScore:
|
||||
return Decision.Reject("{0} grab event in history has an equal or higher Custom Format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScore, "{0} grab event in history has an equal or higher Custom Format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
|
||||
case UpgradeableRejectReason.MinCustomFormatScore:
|
||||
return Decision.Reject("{0} grab event in history has Custom Format score within Custom Format score increment: {1}", rejectionSubject, qualityProfile.MinUpgradeFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScoreIncrement, "{0} grab event in history has Custom Format score within Custom Format score increment: {1}", rejectionSubject, qualityProfile.MinUpgradeFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.UpgradesNotAllowed:
|
||||
return Decision.Reject("{0} grab event in history and Quality Profile '{1}' does not allow upgrades", rejectionSubject, qualityProfile.Name);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryUpgradesNotAllowed, "{0} grab event in history and Quality Profile '{1}' does not allow upgrades", rejectionSubject, qualityProfile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class IndexerTagSpecification : IDecisionEngineSpecification
|
||||
public class IndexerTagSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
@@ -22,11 +22,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release == null || subject.Movie?.Tags == null || subject.Release.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
// If indexer has tags, check that at least one of them is present on the series
|
||||
@@ -47,10 +47,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
_logger.Debug("Indexer {0} has tags. None of these are present on movie {1}. Rejecting", subject.Release.Indexer, subject.Movie);
|
||||
|
||||
return Decision.Reject("Movie tags do not match any of the indexer tags");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.NoMatchingTag, "Movie tags do not match any of the indexer tags");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class MonitoredMovieSpecification : IDecisionEngineSpecification
|
||||
public class MonitoredMovieSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -16,23 +16,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
if (searchCriteria.UserInvokedSearch)
|
||||
{
|
||||
_logger.Debug("Skipping monitored check during search");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
|
||||
if (!subject.Movie.Monitored)
|
||||
{
|
||||
return Decision.Reject("Movie is not monitored");
|
||||
_logger.Debug("{0} is present in the DB but not tracked. Rejecting", subject.Movie);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MovieNotMonitored, "Movie is not monitored");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class ProperSpecification : IDecisionEngineSpecification
|
||||
public class ProperSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -23,11 +23,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
|
||||
@@ -35,12 +35,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
|
||||
{
|
||||
_logger.Debug("Propers are not preferred, skipping check");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Movie.MovieFile == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var file = subject.Movie.MovieFile;
|
||||
@@ -50,17 +50,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.PropersDisabled, "Proper downloading is disabled");
|
||||
}
|
||||
|
||||
if (file.DateAdded < DateTime.Today.AddDays(-7))
|
||||
{
|
||||
_logger.Debug("Proper for old file, rejecting: {0}", subject);
|
||||
return Decision.Reject("Proper for old file");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProperForOldFile, "Proper for old file");
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class MovieSpecification : IDecisionEngineSpecification
|
||||
public class MovieSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if movie matches searched movie");
|
||||
@@ -28,10 +28,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
if (subject.Movie.Id != searchCriteria.Movie.Id)
|
||||
{
|
||||
_logger.Debug("Movie {0} does not match {1}", subject.Movie, searchCriteria.Movie);
|
||||
return Decision.Reject("Wrong movie");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongMovie, "Wrong movie");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class TorrentSeedingSpecification : IDecisionEngineSpecification
|
||||
public class TorrentSeedingSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly Logger _logger;
|
||||
@@ -20,13 +20,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = subject.Release as TorrentInfo;
|
||||
|
||||
if (torrentInfo == null || torrentInfo.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
||||
@@ -49,11 +49,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (torrentInfo.Seeders.HasValue && torrentInfo.Seeders.Value < minimumSeeders)
|
||||
{
|
||||
_logger.Debug("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
return Decision.Reject("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumSeeders, "Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeAllowedSpecification : IDecisionEngineSpecification
|
||||
public class UpgradeAllowedSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly ICustomFormatCalculationService _formatService;
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var qualityProfile = subject.Movie.QualityProfile;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (file == null)
|
||||
{
|
||||
_logger.Debug("File is no longer available, skipping this file.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
file.Movie = subject.Movie;
|
||||
@@ -50,11 +50,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
_logger.Debug("Upgrading is not allowed by the quality profile");
|
||||
|
||||
return Decision.Reject("Existing file and the Quality profile does not allow upgrades");
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.QualityUpgradesDisabled, "Existing file and the Quality profile does not allow upgrades");
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeDiskSpecification : IDecisionEngineSpecification
|
||||
public class UpgradeDiskSpecification : IDownloadDecisionEngineSpecification
|
||||
{
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly ICustomFormatCalculationService _formatService;
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var qualityProfile = subject.Movie.QualityProfile;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (file == null)
|
||||
{
|
||||
_logger.Debug("File is no longer available, skipping this file.");
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
|
||||
file.Movie = subject.Movie;
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
var cutoff = qualityProfile.UpgradeAllowed ? qualityProfile.Cutoff : qualityProfile.FirststAllowedQuality().Id;
|
||||
var qualityCutoff = qualityProfile.Items[qualityProfile.GetIndex(cutoff).Index];
|
||||
|
||||
return Decision.Reject("Existing file meets cutoff: {0} [{1}]", qualityCutoff, customFormats.ConcatToString());
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCutoffMet, "Existing file meets cutoff: {0} [{1}]", qualityCutoff, customFormats.ConcatToString());
|
||||
}
|
||||
|
||||
var upgradeableRejectReason = _upgradableSpecification.IsUpgradable(qualityProfile,
|
||||
@@ -64,31 +64,31 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
switch (upgradeableRejectReason)
|
||||
{
|
||||
case UpgradeableRejectReason.None:
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
|
||||
case UpgradeableRejectReason.BetterQuality:
|
||||
return Decision.Reject("Existing file on disk is of equal or higher preference: {0}", file.Quality);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskHigherPreference, "Existing file on disk is of equal or higher preference: {0}", file.Quality);
|
||||
|
||||
case UpgradeableRejectReason.BetterRevision:
|
||||
return Decision.Reject("Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskHigherRevision, "Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision);
|
||||
|
||||
case UpgradeableRejectReason.QualityCutoff:
|
||||
return Decision.Reject("Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCutoffMet, "Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatCutoff:
|
||||
return Decision.Reject("Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatCutoffMet, "Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.CustomFormatScore:
|
||||
return Decision.Reject("Existing file on disk has a equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatScore, "Existing file on disk has a equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats));
|
||||
|
||||
case UpgradeableRejectReason.MinCustomFormatScore:
|
||||
return Decision.Reject("Existing file on disk has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatScoreIncrement, "Existing file on disk has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
|
||||
|
||||
case UpgradeableRejectReason.UpgradesNotAllowed:
|
||||
return Decision.Reject("Existing file on disk and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskUpgradesNotAllowed, "Existing file on disk and Quality Profile '{0}' does not allow upgrades", qualityProfile.Name);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return DownloadSpecDecision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Download.Aggregation.Aggregators
|
||||
languages = languages.Except(languagesToRemove).ToList();
|
||||
}
|
||||
|
||||
if ((languages.Count == 0 || (languages.Count == 1 && languages.First() == Language.Unknown)) && releaseInfo?.Title?.IsNotNullOrWhiteSpace() == true)
|
||||
if (releaseInfo?.Title?.IsNotNullOrWhiteSpace() == true)
|
||||
{
|
||||
IndexerDefinition indexer = null;
|
||||
|
||||
@@ -88,7 +88,14 @@ namespace NzbDrone.Core.Download.Aggregation.Aggregators
|
||||
if (indexer?.Settings is IIndexerSettings settings && settings.MultiLanguages.Any() && Parser.Parser.HasMultipleLanguages(releaseInfo.Title))
|
||||
{
|
||||
// Use indexer setting for Multi-languages
|
||||
languages = settings.MultiLanguages.Select(i => (Language)i).ToList();
|
||||
if (languages.Count == 0 || (languages.Count == 1 && languages.First() == Language.Unknown))
|
||||
{
|
||||
languages = settings.MultiLanguages.Select(i => (Language)i).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
languages.AddRange(settings.MultiLanguages.Select(i => (Language)i).Except(languages).ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -630,14 +630,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
if (torrent.RatioLimit >= 0)
|
||||
{
|
||||
if (torrent.Ratio >= torrent.RatioLimit)
|
||||
if (torrent.RatioLimit - torrent.Ratio <= 0.001f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (torrent.RatioLimit == -2 && config.MaxRatioEnabled)
|
||||
{
|
||||
if (Math.Round(torrent.Ratio, 2) >= config.MaxRatio)
|
||||
if (config.MaxRatio - torrent.Ratio <= 0.001f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user