mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-05 13:21:25 -05:00
Compare commits
61 Commits
list-exclu
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b2c48c12e | ||
|
|
d730161800 | ||
|
|
66c1af0555 | ||
|
|
dca00db317 | ||
|
|
812e5ac5a3 | ||
|
|
d01bae92bf | ||
|
|
1a6bf51741 | ||
|
|
f3e7843150 | ||
|
|
886b9b1c05 | ||
|
|
d8891ee4ea | ||
|
|
192dd9c137 | ||
|
|
b549fddf95 | ||
|
|
c1f538ed97 | ||
|
|
e72f8097fb | ||
|
|
3eec088306 | ||
|
|
ad097dd1a2 | ||
|
|
b4b38a5318 | ||
|
|
b0717a0803 | ||
|
|
4d1d08d345 | ||
|
|
e689817508 | ||
|
|
3b191caf16 | ||
|
|
cc6ca0b067 | ||
|
|
57cb63fb18 | ||
|
|
20f709d22a | ||
|
|
5d8775ac96 | ||
|
|
4890972e16 | ||
|
|
40dc808f61 | ||
|
|
97077e09d2 | ||
|
|
9ba7027d00 | ||
|
|
9903e70925 | ||
|
|
3a6f3666f5 | ||
|
|
915c66be50 | ||
|
|
70b22e483a | ||
|
|
cad1191da5 | ||
|
|
43910af127 | ||
|
|
f01c477b81 | ||
|
|
0054318658 | ||
|
|
03a3f4522a | ||
|
|
3d3562dcda | ||
|
|
7a079c5e0c | ||
|
|
4d70798f2f | ||
|
|
d55864f869 | ||
|
|
3c41c84fb0 | ||
|
|
eae9a6d6e0 | ||
|
|
867f8f5835 | ||
|
|
0c81387cfb | ||
|
|
c5fb5200de | ||
|
|
cc306fcd36 | ||
|
|
2bb7984961 | ||
|
|
21e605452a | ||
|
|
476f5b5bfd | ||
|
|
b6920cfe82 | ||
|
|
e89b98d0f6 | ||
|
|
1db690ad39 | ||
|
|
d5c524719b | ||
|
|
ced6586860 | ||
|
|
8b3019821a | ||
|
|
16ed68d5de | ||
|
|
098a893083 | ||
|
|
548e3400b5 | ||
|
|
5c31e3f1a2 |
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"paths": [
|
||||
"frontend/src/**/*.js",
|
||||
"src/NzbDrone.Core/Localization/Core/*.json"
|
||||
"frontend/src/**/*.js"
|
||||
],
|
||||
"ignored": [
|
||||
"**/node_modules/**/*"
|
||||
|
||||
@@ -9,13 +9,13 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.2.0'
|
||||
majorVersion: '4.2.2'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.300'
|
||||
dotnetVersion: '6.0.400'
|
||||
nodeVersion: '16.X'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
@@ -173,7 +173,6 @@ stages:
|
||||
key: 'yarn | "$(osName)" | yarn.lock'
|
||||
restoreKeys: |
|
||||
yarn | "$(osName)"
|
||||
yarn
|
||||
path: $(yarnCacheFolder)
|
||||
displayName: Cache Yarn packages
|
||||
- bash: ./build.sh --frontend
|
||||
@@ -550,7 +549,7 @@ stages:
|
||||
Radarr__Postgres__Password: 'radarr'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
timeoutInMinutes: 10
|
||||
|
||||
@@ -577,6 +576,7 @@ stages:
|
||||
-e POSTGRES_PASSWORD=radarr \
|
||||
-e POSTGRES_USER=radarr \
|
||||
-p 5432:5432/tcp \
|
||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||
postgres:14
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
@@ -687,7 +687,7 @@ stages:
|
||||
Radarr__Postgres__Password: 'radarr'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
@@ -722,6 +722,7 @@ stages:
|
||||
-e POSTGRES_PASSWORD=radarr \
|
||||
-e POSTGRES_USER=radarr \
|
||||
-p 5432:5432/tcp \
|
||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||
postgres:14
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
@@ -976,7 +977,6 @@ stages:
|
||||
key: 'yarn | "$(osName)" | yarn.lock'
|
||||
restoreKeys: |
|
||||
yarn | "$(osName)"
|
||||
yarn
|
||||
path: $(yarnCacheFolder)
|
||||
displayName: Cache Yarn packages
|
||||
- bash: ./build.sh --lint
|
||||
|
||||
@@ -66,7 +66,8 @@ class CollectionFooter extends Component {
|
||||
monitor,
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability
|
||||
minimumAvailability,
|
||||
rootFolderPath
|
||||
} = this.state;
|
||||
|
||||
const changes = {};
|
||||
@@ -87,6 +88,10 @@ class CollectionFooter extends Component {
|
||||
changes.minimumAvailability = minimumAvailability;
|
||||
}
|
||||
|
||||
if (rootFolderPath !== NO_CHANGE) {
|
||||
changes.rootFolderPath = rootFolderPath;
|
||||
}
|
||||
|
||||
this.props.onUpdateSelectedPress(changes);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ const authenticationMethodOptions = [
|
||||
{ key: 'forms', value: translate('AuthForm') }
|
||||
];
|
||||
|
||||
const authenticationRequiredOptions = [
|
||||
{ key: 'enabled', value: 'Enabled' },
|
||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
||||
];
|
||||
|
||||
const certificateValidationOptions = [
|
||||
{ key: 'enabled', value: translate('Enabled') },
|
||||
{ key: 'disabledForLocalAddresses', value: translate('CertValidationNoLocal') },
|
||||
@@ -68,6 +73,7 @@ class SecuritySettings extends Component {
|
||||
|
||||
const {
|
||||
authenticationMethod,
|
||||
authenticationRequired,
|
||||
username,
|
||||
password,
|
||||
apiKey,
|
||||
@@ -92,7 +98,24 @@ class SecuritySettings extends Component {
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
authenticationEnabled &&
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>Authentication Required</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="authenticationRequired"
|
||||
values={authenticationRequiredOptions}
|
||||
helpText="Change which requests authentication is required for. Do not change unless you understand the risks."
|
||||
onChange={onInputChange}
|
||||
{...authenticationRequired}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Username')}</FormLabel>
|
||||
|
||||
@@ -102,11 +125,12 @@ class SecuritySettings extends Component {
|
||||
onChange={onInputChange}
|
||||
{...username}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
authenticationEnabled &&
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Password')}</FormLabel>
|
||||
|
||||
@@ -116,7 +140,8 @@ class SecuritySettings extends Component {
|
||||
onChange={onInputChange}
|
||||
{...password}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
|
||||
@@ -561,7 +561,7 @@ export const actionHandlers = handleThunks({
|
||||
}, []);
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/movie/import',
|
||||
url: '/importlist/movie',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(allNewMovies)
|
||||
|
||||
@@ -116,7 +116,7 @@ export const filterPredicates = {
|
||||
const predicate = filterTypePredicates[type];
|
||||
const { collection } = item;
|
||||
|
||||
return predicate(collection ? collection.name : '', filterValue);
|
||||
return predicate(collection ? collection.title : '', filterValue);
|
||||
},
|
||||
|
||||
originalLanguage: function(item, filterValue, type) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getNewMovie from 'Utilities/Movie/getNewMovie';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { set, update, updateItem } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
|
||||
@@ -63,19 +65,81 @@ export const defaultState = {
|
||||
}
|
||||
],
|
||||
|
||||
filterPredicates: {},
|
||||
filterPredicates: {
|
||||
genres: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
|
||||
let allGenres = [];
|
||||
item.movies.forEach((movie) => {
|
||||
allGenres = allGenres.concat(movie.genres);
|
||||
});
|
||||
|
||||
const genres = Array.from(new Set(allGenres)).slice(0, 3);
|
||||
|
||||
return predicate(genres, filterValue);
|
||||
},
|
||||
totalMovies: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
const { movies } = item;
|
||||
|
||||
const totalMovies = movies.length;
|
||||
return predicate(totalMovies, filterValue);
|
||||
}
|
||||
},
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
label: translate('Title'),
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'monitored',
|
||||
label: 'Monitored',
|
||||
label: translate('Monitored'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: translate('QualityProfile'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: translate('RootFolder'),
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
label: translate('Genres'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const genreList = items.reduce((acc, collection) => {
|
||||
let collectionGenres = [];
|
||||
collection.movies.forEach((movie) => {
|
||||
collectionGenres = collectionGenres.concat(movie.genres);
|
||||
});
|
||||
|
||||
const genres = Array.from(new Set(collectionGenres)).slice(0, 3);
|
||||
|
||||
genres.forEach((genre) => {
|
||||
acc.push({
|
||||
id: genre,
|
||||
name: genre
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return genreList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'totalMovies',
|
||||
label: translate('TotalMovies'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -165,11 +165,11 @@ export const actionHandlers = handleThunks({
|
||||
requestData.quality = quality;
|
||||
}
|
||||
|
||||
if (releaseGroup) {
|
||||
if (releaseGroup !== undefined) {
|
||||
requestData.releaseGroup = releaseGroup;
|
||||
}
|
||||
|
||||
if (edition) {
|
||||
if (edition !== undefined) {
|
||||
requestData.edition = edition;
|
||||
}
|
||||
|
||||
@@ -201,11 +201,11 @@ export const actionHandlers = handleThunks({
|
||||
props.quality = quality;
|
||||
}
|
||||
|
||||
if (edition) {
|
||||
if (edition !== undefined) {
|
||||
props.edition = edition;
|
||||
}
|
||||
|
||||
if (releaseGroup) {
|
||||
if (releaseGroup !== undefined) {
|
||||
props.releaseGroup = releaseGroup;
|
||||
}
|
||||
|
||||
|
||||
@@ -341,8 +341,8 @@ export const defaultState = {
|
||||
const collectionList = items.reduce((acc, movie) => {
|
||||
if (movie.collection) {
|
||||
acc.push({
|
||||
id: movie.collection.name,
|
||||
name: movie.collection.name
|
||||
id: movie.collection.title,
|
||||
name: movie.collection.title
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"@microsoft/signalr": "6.0.5",
|
||||
"@microsoft/signalr": "6.0.8",
|
||||
"@sentry/browser": "6.18.2",
|
||||
"@sentry/integrations": "6.18.2",
|
||||
"classnames": "2.3.1",
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||
_runner.KillAll();
|
||||
_runner.Start();
|
||||
_runner.Start(true);
|
||||
|
||||
driver.Url = "http://localhost:7878";
|
||||
|
||||
|
||||
@@ -20,7 +20,21 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
|
||||
private static Exception[] FilteredExceptions = new Exception[]
|
||||
{
|
||||
new UnauthorizedAccessException()
|
||||
new UnauthorizedAccessException(),
|
||||
new AggregateException(new Exception[]
|
||||
{
|
||||
new UnauthorizedAccessException(),
|
||||
new UnauthorizedAccessException()
|
||||
})
|
||||
};
|
||||
|
||||
private static Exception[] NonFilteredExceptions = new Exception[]
|
||||
{
|
||||
new AggregateException(new Exception[]
|
||||
{
|
||||
new UnauthorizedAccessException(),
|
||||
new NotImplementedException()
|
||||
})
|
||||
};
|
||||
|
||||
[SetUp]
|
||||
@@ -63,6 +77,14 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
_subject.IsSentryMessage(log).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("NonFilteredExceptions")]
|
||||
public void should_not_filter_event_for_filtered_exception_types(Exception ex)
|
||||
{
|
||||
var log = GivenLogEvent(LogLevel.Error, ex, "test");
|
||||
_subject.IsSentryMessage(log).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("FilteredExceptions")]
|
||||
public void should_not_filter_event_for_filtered_exception_types_if_filtering_disabled(Exception ex)
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\b[^;=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||
|
||||
@@ -229,21 +229,48 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
{
|
||||
if (FilterEvents)
|
||||
{
|
||||
var sqlEx = logEvent.Exception as SQLiteException;
|
||||
if (sqlEx != null && FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
var aggEx = logEvent.Exception as AggregateException;
|
||||
|
||||
if (aggEx != null && aggEx.InnerExceptions.Count > 0)
|
||||
{
|
||||
return false;
|
||||
exceptions.AddRange(aggEx.InnerExceptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
exceptions.Add(logEvent.Exception);
|
||||
}
|
||||
|
||||
if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
|
||||
// If any are sentry then send to sentry
|
||||
foreach (var ex in exceptions)
|
||||
{
|
||||
return false;
|
||||
var isSentry = true;
|
||||
|
||||
var sqlEx = ex as SQLiteException;
|
||||
if (sqlEx != null && !FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
|
||||
{
|
||||
isSentry = false;
|
||||
}
|
||||
|
||||
if (FilteredExceptionTypeNames.Contains(ex.GetType().Name))
|
||||
{
|
||||
isSentry = false;
|
||||
}
|
||||
|
||||
if (FilteredExceptionMessages.Any(x => ex.Message.Contains(x)))
|
||||
{
|
||||
isSentry = false;
|
||||
}
|
||||
|
||||
if (isSentry)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FilteredExceptionMessages.Any(x => logEvent.Exception.Message.Contains(x)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// The exception or aggregate exception children were not sentry exceptions
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.15.0" />
|
||||
<PackageReference Include="Sentry" Version="3.20.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
|
||||
@@ -27,6 +27,20 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
Mocker.Resolve<IDatabase>().Vacuum();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void postgres_should_not_contain_timestamp_without_timezone_columns()
|
||||
{
|
||||
if (Db.DatabaseType != DatabaseType.PostgreSQL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mocker.Resolve<IDatabase>()
|
||||
.OpenConnection().Query("SELECT table_name, column_name, data_type FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = 'public' AND data_type = 'timestamp without time zone'")
|
||||
.Should()
|
||||
.BeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_version()
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.Collections;
|
||||
using NzbDrone.Core.Movies.Commands;
|
||||
using NzbDrone.Core.Movies.Credits;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -52,6 +53,10 @@ namespace NzbDrone.Core.Test.MovieTests
|
||||
Mocker.GetMock<IProvideMovieInfo>()
|
||||
.Setup(s => s.GetMovieInfo(It.IsAny<int>()))
|
||||
.Callback<int>((i) => { throw new MovieNotFoundException(i); });
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
|
||||
.Returns(string.Empty);
|
||||
}
|
||||
|
||||
private void GivenNewMovieInfo(MovieMetadata movie)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class EditionTagsFixture : CoreTest<FileNameBuilder>
|
||||
{
|
||||
private Movie _movie;
|
||||
private MovieFile _movieFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_movie = Builder<Movie>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = "South Park")
|
||||
.Build();
|
||||
|
||||
_movieFile = new MovieFile { Quality = new QualityModel(), ReleaseGroup = "SonarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
_namingConfig.RenameMovies = true;
|
||||
|
||||
Mocker.GetMock<INamingConfigService>()
|
||||
.Setup(c => c.GetConfig()).Returns(_namingConfig);
|
||||
|
||||
Mocker.GetMock<IQualityDefinitionService>()
|
||||
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
|
||||
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
|
||||
|
||||
Mocker.GetMock<ICustomFormatService>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new List<CustomFormat>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_edition_tag()
|
||||
{
|
||||
_movieFile.Edition = "Uncut";
|
||||
_namingConfig.StandardMovieFormat = "{Movie Title} [{Edition Tags}]";
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be("South Park [Uncut]");
|
||||
}
|
||||
|
||||
[TestCase("{Movie Title} {edition-{Edition Tags}}")]
|
||||
public void should_conditional_hide_edition_tags_in_plex_format(string movieFormat)
|
||||
{
|
||||
_movieFile.Edition = "";
|
||||
_namingConfig.StandardMovieFormat = movieFormat;
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be("South Park");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Internal;
|
||||
@@ -47,5 +47,25 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
Subject.GetMovieFolder(_movie)
|
||||
.Should().Be($"Movie Title ({_movie.TmdbId})");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_imdb_tag()
|
||||
{
|
||||
_namingConfig.MovieFolderFormat = "{Movie Title} {imdb-{ImdbId}}";
|
||||
|
||||
Subject.GetMovieFolder(_movie)
|
||||
.Should().Be($"Movie Title {{imdb-{_movie.ImdbId}}}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_imdb_tag_if_null()
|
||||
{
|
||||
_namingConfig.MovieFolderFormat = "{Movie Title} {imdb-{ImdbId}}";
|
||||
|
||||
_movie.ImdbId = null;
|
||||
|
||||
Subject.GetMovieFolder(_movie)
|
||||
.Should().Be($"Movie Title");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +342,16 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
result.Languages.Should().BeEquivalentTo(Language.Bengali);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.HDTV.x264.SK-iCZi")]
|
||||
[TestCase("Movie.Title.2019.1080p.HDTV.x265.iNTERNAL.SK-iCZi")]
|
||||
[TestCase("Movie.Title.2018.SLOVAK.DUAL.2160p.UHD.BluRay.x265-iCZi")]
|
||||
[TestCase("Movie.Title.1990.SLOVAK.HDTV.x264-iCZi")]
|
||||
public void should_parse_language_slovak(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle);
|
||||
result.Languages.Should().BeEquivalentTo(Language.Slovak);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.en.sub")]
|
||||
[TestCase("Movie Title.eng.sub")]
|
||||
[TestCase("Movie.Title.eng.forced.sub")]
|
||||
|
||||
@@ -41,6 +41,14 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
ParseAndVerifyQuality(title, Source.TELESYNC, proper, Resolution.R720p);
|
||||
}
|
||||
|
||||
[TestCase("Movie Name 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
|
||||
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)]
|
||||
[TestCase("Movie Name (2018) 720p Hindi HQ CAMrip x264 AAC 1.4GB", false)]
|
||||
public void should_parse_cam(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
|
||||
}
|
||||
|
||||
[TestCase("S07E23 .avi ", false)]
|
||||
[TestCase("Movie Name S02E01 HDTV XviD 2HD", false)]
|
||||
[TestCase("Movie Name S05E11 PROPER HDTV XviD 2HD", true)]
|
||||
|
||||
@@ -100,6 +100,10 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Yet Another Anime Movie 2012 [Kametsu] [Blu-ray][MKV][h264 10-bit][1080p][FLAC 5.1][Dual Audio][Softsubs (Kametsu)]", "Kametsu")]
|
||||
[TestCase("Another.Anime.Film.Name.2016.JPN.Blu-Ray.Remux.AVC.DTS-MA.BluDragon", "BluDragon")]
|
||||
[TestCase("A Movie in the Name (1964) (1080p BluRay x265 r00t)", "r00t")]
|
||||
[TestCase("Movie Title (2022) (2160p ATV WEB-DL Hybrid H265 DV HDR DDP Atmos 5.1 English - HONE)", "HONE")]
|
||||
[TestCase("Movie Title (2009) (2160p PMTP WEB-DL Hybrid H265 DV HDR10+ DDP Atmos 5.1 English - HONE)", "HONE")]
|
||||
[TestCase("Why.Cant.You.Use.Normal.Characters.2021.2160p.UHD.HDR10+.BluRay.TrueHD.Atmos.7.1.x265-ZØNEHD", "ZØNEHD")]
|
||||
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
|
||||
public void should_parse_exception_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
|
||||
161
src/NzbDrone.Core.Test/ParserTests/SlugParserFixture.cs
Normal file
161
src/NzbDrone.Core.Test/ParserTests/SlugParserFixture.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using FluentAssertions;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SlugParserFixture : CoreTest
|
||||
{
|
||||
[TestCase("tèst", "test")]
|
||||
[TestCase("têst", "test")]
|
||||
[TestCase("tëst", "test")]
|
||||
[TestCase("tËst", "test")]
|
||||
[TestCase("áccent", "accent")]
|
||||
[TestCase("àccent", "accent")]
|
||||
[TestCase("âccent", "accent")]
|
||||
[TestCase("Äccent", "accent")]
|
||||
[TestCase("åccent", "accent")]
|
||||
[TestCase("acceñt", "accent")]
|
||||
[TestCase("ßtest", "test")]
|
||||
[TestCase("œtest", "test")]
|
||||
[TestCase("Œtest", "test")]
|
||||
[TestCase("Øtest", "test")]
|
||||
public void should_replace_accents(string input, string result)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input).Should().Be(result);
|
||||
}
|
||||
|
||||
[TestCase("Test'Result")]
|
||||
[TestCase("Test$Result")]
|
||||
[TestCase("Test(Result")]
|
||||
[TestCase("Test)Result")]
|
||||
[TestCase("Test*Result")]
|
||||
[TestCase("Test?Result")]
|
||||
[TestCase("Test/Result")]
|
||||
[TestCase("Test=Result")]
|
||||
[TestCase("Test\\Result")]
|
||||
public void should_replace_special_characters(string input)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input).Should().Be("testresult");
|
||||
}
|
||||
|
||||
[TestCase("ThIS IS A MiXeD CaSe SensItIvE ValUe")]
|
||||
public void should_lowercase_capitals(string input)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input).Should().Be("this-is-a-mixed-case-sensitive-value");
|
||||
}
|
||||
|
||||
[TestCase("test----")]
|
||||
[TestCase("test____")]
|
||||
[TestCase("test-_--_")]
|
||||
public void should_trim_trailing_dashes_and_underscores(string input)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input).Should().Be("test");
|
||||
}
|
||||
|
||||
[TestCase("test result")]
|
||||
[TestCase("test result")]
|
||||
public void should_replace_spaces_with_dash(string input)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input).Should().Be("test-result");
|
||||
}
|
||||
|
||||
[TestCase("test result", "test-result")]
|
||||
[TestCase("test-----result", "test-result")]
|
||||
[TestCase("test_____result", "test_result")]
|
||||
public void should_replace_double_occurence(string input, string result)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input).Should().Be(result);
|
||||
}
|
||||
|
||||
[TestCase("Test'Result")]
|
||||
[TestCase("Test$Result")]
|
||||
[TestCase("Test(Result")]
|
||||
[TestCase("Test)Result")]
|
||||
[TestCase("Test*Result")]
|
||||
[TestCase("Test?Result")]
|
||||
[TestCase("Test/Result")]
|
||||
[TestCase("Test=Result")]
|
||||
[TestCase("Test\\Result")]
|
||||
public void should_replace_special_characters_with_dash_when_enabled(string input)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input, true).Should().Be("test-result");
|
||||
}
|
||||
|
||||
[TestCase("Test'Result")]
|
||||
[TestCase("Test$Result")]
|
||||
[TestCase("Test(Result")]
|
||||
[TestCase("Test)Result")]
|
||||
[TestCase("Test*Result")]
|
||||
[TestCase("Test?Result")]
|
||||
[TestCase("Test/Result")]
|
||||
[TestCase("Test=Result")]
|
||||
[TestCase("Test\\Result")]
|
||||
public void should__not_replace_special_characters_with_dash_when_disabled(string input)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input, false).Should().Be("testresult");
|
||||
}
|
||||
|
||||
[TestCase("test----", "-_", "test")]
|
||||
[TestCase("test____", "-_", "test")]
|
||||
[TestCase("test-_-_", "-_", "test")]
|
||||
[TestCase("test----", "-", "test")]
|
||||
[TestCase("test____", "-", "test____")]
|
||||
[TestCase("test-_-_", "-", "test-_-_")]
|
||||
[TestCase("test----", "_", "test----")]
|
||||
[TestCase("test____", "_", "test")]
|
||||
[TestCase("test-_-_", "_", "test-_-")]
|
||||
[TestCase("test----", "", "test----")]
|
||||
[TestCase("test____", "", "test____")]
|
||||
[TestCase("test-_-_", "", "test-_-_")]
|
||||
public void should_trim_trailing_dashes_and_underscores_based_on_list(string input, string trimList, string result)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input, false, trimList, "").Should().Be(result);
|
||||
}
|
||||
|
||||
[TestCase("test----result", "-_", "test-result")]
|
||||
[TestCase("test____result", "-_", "test_result")]
|
||||
[TestCase("test_-_-result", "-_", "test-result")]
|
||||
[TestCase("test-_-_result", "-_", "test_result")]
|
||||
[TestCase("test----result", "-", "test-result")]
|
||||
[TestCase("test____result", "-", "test____result")]
|
||||
[TestCase("test-_-_result", "-", "test-_-_result")]
|
||||
[TestCase("test----result", "_", "test----result")]
|
||||
[TestCase("test____result", "_", "test_result")]
|
||||
[TestCase("test-_-_result", "_", "test-_-_result")]
|
||||
[TestCase("test----result", "", "test----result")]
|
||||
[TestCase("test____result", "", "test____result")]
|
||||
[TestCase("test-_-_result", "", "test-_-_result")]
|
||||
public void should_replace_duplicate_characters_based_on_list(string input, string deduplicateChars, string result)
|
||||
{
|
||||
Parser.Parser.ToUrlSlug(input, false, "", deduplicateChars).Should().Be(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_handle_null_trim_parameters()
|
||||
{
|
||||
Parser.Parser.ToUrlSlug("test", false, null, "-_").Should().Be("test");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_handle_null_dedupe_parameters()
|
||||
{
|
||||
Parser.Parser.ToUrlSlug("test", false, "-_", null).Should().Be("test");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_handle_empty_trim_parameters()
|
||||
{
|
||||
Parser.Parser.ToUrlSlug("test", false, "", "-_").Should().Be("test");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_handle_empty_dedupe_parameters()
|
||||
{
|
||||
Parser.Parser.ToUrlSlug("test", false, "-_", "").Should().Be("test");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -151,5 +152,107 @@ namespace NzbDrone.Core.Test.RootFolderTests
|
||||
unmappedFolders.Count.Should().BeGreaterThan(0);
|
||||
unmappedFolders.Should().NotContain(u => u.Name == subFolder);
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase(null)]
|
||||
public void should_handle_non_configured_recycle_bin(string recycleBinPath)
|
||||
{
|
||||
var rootFolder = Builder<RootFolder>.CreateNew()
|
||||
.With(r => r.Path = @"C:\Test\TV")
|
||||
.Build();
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
rootFolder = Builder<RootFolder>.CreateNew()
|
||||
.With(r => r.Path = @"/Test/TV")
|
||||
.Build();
|
||||
}
|
||||
|
||||
var subFolders = new[]
|
||||
{
|
||||
"Series1",
|
||||
"Series2",
|
||||
"Series3"
|
||||
};
|
||||
|
||||
var folders = subFolders.Select(f => Path.Combine(@"C:\Test\TV", f)).ToArray();
|
||||
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
folders = subFolders.Select(f => Path.Combine(@"/Test/TV", f)).ToArray();
|
||||
}
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.RecycleBin)
|
||||
.Returns(recycleBinPath);
|
||||
|
||||
Mocker.GetMock<IRootFolderRepository>()
|
||||
.Setup(s => s.Get(It.IsAny<int>()))
|
||||
.Returns(rootFolder);
|
||||
|
||||
Mocker.GetMock<IMovieRepository>()
|
||||
.Setup(s => s.AllMoviePaths())
|
||||
.Returns(new Dictionary<int, string>());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetDirectories(rootFolder.Path))
|
||||
.Returns(folders);
|
||||
|
||||
var unmappedFolders = Subject.Get(rootFolder.Id, true).UnmappedFolders;
|
||||
|
||||
unmappedFolders.Count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_exclude_recycle_bin()
|
||||
{
|
||||
var rootFolder = Builder<RootFolder>.CreateNew()
|
||||
.With(r => r.Path = @"C:\Test\TV")
|
||||
.Build();
|
||||
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
rootFolder = Builder<RootFolder>.CreateNew()
|
||||
.With(r => r.Path = @"/Test/TV")
|
||||
.Build();
|
||||
}
|
||||
|
||||
var subFolders = new[]
|
||||
{
|
||||
"Series1",
|
||||
"Series2",
|
||||
"Series3",
|
||||
"BIN"
|
||||
};
|
||||
|
||||
var folders = subFolders.Select(f => Path.Combine(@"C:\Test\TV", f)).ToArray();
|
||||
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
folders = subFolders.Select(f => Path.Combine(@"/Test/TV", f)).ToArray();
|
||||
}
|
||||
|
||||
var recycleFolder = Path.Combine(OsInfo.IsNotWindows ? @"/Test/TV" : @"C:\Test\TV", "BIN");
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.RecycleBin)
|
||||
.Returns(recycleFolder);
|
||||
|
||||
Mocker.GetMock<IRootFolderRepository>()
|
||||
.Setup(s => s.Get(It.IsAny<int>()))
|
||||
.Returns(rootFolder);
|
||||
|
||||
Mocker.GetMock<IMovieRepository>()
|
||||
.Setup(s => s.AllMoviePaths())
|
||||
.Returns(new Dictionary<int, string>());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetDirectories(rootFolder.Path))
|
||||
.Returns(folders);
|
||||
|
||||
var unmappedFolders = Subject.Get(rootFolder.Id, true).UnmappedFolders;
|
||||
|
||||
unmappedFolders.Count.Should().Be(3);
|
||||
unmappedFolders.Should().NotContain(u => u.Name == "BIN");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace NzbDrone.Core.Authentication
|
||||
{
|
||||
public enum AuthenticationRequiredType
|
||||
{
|
||||
Enabled = 0,
|
||||
DisabledForLocalAddresses = 1
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
namespace NzbDrone.Core.Authentication
|
||||
namespace NzbDrone.Core.Authentication
|
||||
{
|
||||
public enum AuthenticationType
|
||||
{
|
||||
None = 0,
|
||||
Basic = 1,
|
||||
Forms = 2
|
||||
Forms = 2,
|
||||
External = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace NzbDrone.Core.Configuration
|
||||
bool EnableSsl { get; }
|
||||
bool LaunchBrowser { get; }
|
||||
AuthenticationType AuthenticationMethod { get; }
|
||||
AuthenticationRequiredType AuthenticationRequired { get; }
|
||||
bool AnalyticsEnabled { get; }
|
||||
string LogLevel { get; }
|
||||
string ConsoleLogLevel { get; }
|
||||
@@ -191,6 +192,8 @@ namespace NzbDrone.Core.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationRequiredType AuthenticationRequired => GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled);
|
||||
|
||||
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
|
||||
|
||||
public string Branch => GetValue("Branch", "master").ToLowerInvariant();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
@@ -18,6 +19,8 @@ namespace NzbDrone.Core.CustomFormats
|
||||
return (ICustomFormatSpecification)MemberwiseClone();
|
||||
}
|
||||
|
||||
public abstract NzbDroneValidationResult Validate();
|
||||
|
||||
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo)
|
||||
{
|
||||
var match = IsSatisfiedByWithoutNegate(movieInfo);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
@@ -11,6 +12,8 @@ namespace NzbDrone.Core.CustomFormats
|
||||
bool Negate { get; set; }
|
||||
bool Required { get; set; }
|
||||
|
||||
NzbDroneValidationResult Validate();
|
||||
|
||||
ICustomFormatSpecification Clone();
|
||||
bool IsSatisfiedBy(ParsedMovieInfo movieInfo);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class IndexerFlagSpecificationValidator : AbstractValidator<IndexerFlagSpecification>
|
||||
{
|
||||
public IndexerFlagSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
RuleFor(c => c.Value).Custom((qualityValue, context) =>
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue))
|
||||
{
|
||||
context.AddFailure(string.Format("Invalid indexer flag condition value: {0}", qualityValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class IndexerFlagSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly IndexerFlagSpecificationValidator Validator = new IndexerFlagSpecificationValidator();
|
||||
|
||||
public override int Order => 4;
|
||||
public override string ImplementationName => "Indexer Flag";
|
||||
|
||||
@@ -17,5 +37,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?;
|
||||
return flags?.HasFlag((IndexerFlags)Value) == true;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class LanguageSpecificationValidator : AbstractValidator<LanguageSpecification>
|
||||
{
|
||||
public LanguageSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
RuleFor(c => c.Value).Custom((value, context) =>
|
||||
{
|
||||
if (!Language.All.Any(o => o.Id == value))
|
||||
{
|
||||
context.AddFailure(string.Format("Invalid Language condition value: {0}", value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class LanguageSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly LanguageSpecificationValidator Validator = new LanguageSpecificationValidator();
|
||||
|
||||
public override int Order => 3;
|
||||
public override string ImplementationName => "Language";
|
||||
|
||||
@@ -14,10 +34,15 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
|
||||
{
|
||||
var comparedLanguage = movieInfo != null && Name == "Original" && movieInfo.ExtraInfo.ContainsKey("OriginalLanguage")
|
||||
var comparedLanguage = movieInfo != null && Value == Language.Original.Id && movieInfo.ExtraInfo.ContainsKey("OriginalLanguage")
|
||||
? (Language)movieInfo.ExtraInfo["OriginalLanguage"]
|
||||
: (Language)Value;
|
||||
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class QualityModifierSpecificationValidator : AbstractValidator<QualityModifierSpecification>
|
||||
{
|
||||
public QualityModifierSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
RuleFor(c => c.Value).Custom((qualityValue, context) =>
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Modifier), qualityValue))
|
||||
{
|
||||
context.AddFailure(string.Format("Invalid quality modifier condition value: {0}", qualityValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class QualityModifierSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly QualityModifierSpecificationValidator Validator = new QualityModifierSpecificationValidator();
|
||||
|
||||
public override int Order => 7;
|
||||
public override string ImplementationName => "Quality Modifier";
|
||||
|
||||
@@ -16,5 +36,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class RegexSpecificationBaseValidator : AbstractValidator<RegexSpecificationBase>
|
||||
{
|
||||
public RegexSpecificationBaseValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty().WithMessage("Regex Pattern must not be empty");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class RegexSpecificationBase : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly RegexSpecificationBaseValidator Validator = new RegexSpecificationBaseValidator();
|
||||
|
||||
protected Regex _regex;
|
||||
protected string _raw;
|
||||
|
||||
@@ -15,7 +28,11 @@ namespace NzbDrone.Core.CustomFormats
|
||||
set
|
||||
{
|
||||
_raw = value;
|
||||
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
if (value.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +45,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
return _regex.IsMatch(compared);
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class ResolutionSpecificationValidator : AbstractValidator<ResolutionSpecification>
|
||||
{
|
||||
public ResolutionSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class ResolutionSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly ResolutionSpecificationValidator Validator = new ResolutionSpecificationValidator();
|
||||
|
||||
public override int Order => 6;
|
||||
public override string ImplementationName => "Resolution";
|
||||
|
||||
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class SizeSpecificationValidator : AbstractValidator<SizeSpecification>
|
||||
{
|
||||
public SizeSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
|
||||
RuleFor(c => c.Max).GreaterThan(c => c.Min);
|
||||
}
|
||||
}
|
||||
|
||||
public class SizeSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly SizeSpecificationValidator Validator = new SizeSpecificationValidator();
|
||||
|
||||
public override int Order => 8;
|
||||
public override string ImplementationName => "Size";
|
||||
|
||||
@@ -21,5 +34,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
return size > Min.Gigabytes() && size <= Max.Gigabytes();
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class SourceSpecificationValidator : AbstractValidator<SourceSpecification>
|
||||
{
|
||||
public SourceSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class SourceSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly SourceSpecificationValidator Validator = new SourceSpecificationValidator();
|
||||
|
||||
public override int Order => 5;
|
||||
public override string ImplementationName => "Source";
|
||||
|
||||
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
SortTitle = Parser.Parser.NormalizeTitle(collectionName),
|
||||
Added = added,
|
||||
QualityProfileId = qualityProfileId,
|
||||
RootFolderPath = rootFolderPath,
|
||||
RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
|
||||
SearchOnAdd = true,
|
||||
MinimumAvailability = minimumAvailability
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(212)]
|
||||
public class postgres_update_timestamp_columns_to_with_timezone : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Blocklist").AlterColumn("Date").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("Blocklist").AlterColumn("PublishedDate").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("Collections").AlterColumn("Added").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("Collections").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("Commands").AlterColumn("QueuedAt").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("Commands").AlterColumn("StartedAt").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("Commands").AlterColumn("EndedAt").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("DownloadClientStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("DownloadClientStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("DownloadClientStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("DownloadHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("ExtraFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("ExtraFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("History").AlterColumn("Date").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("ImportListStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("ImportListStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("ImportListStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("IndexerStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("IndexerStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("IndexerStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("IndexerStatus").AlterColumn("CookiesExpirationDate").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("MetadataFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("MetadataFiles").AlterColumn("Added").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("MovieFiles").AlterColumn("DateAdded").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("MovieMetadata").AlterColumn("DigitalRelease").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("MovieMetadata").AlterColumn("InCinemas").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("MovieMetadata").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("MovieMetadata").AlterColumn("PhysicalRelease").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("Movies").AlterColumn("Added").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("PendingReleases").AlterColumn("Added").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("ScheduledTasks").AlterColumn("LastExecution").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("ScheduledTasks").AlterColumn("LastStartTime").AsDateTimeOffset().Nullable();
|
||||
Alter.Table("SubtitleFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("SubtitleFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable();
|
||||
}
|
||||
|
||||
protected override void LogDbUpgrade()
|
||||
{
|
||||
Alter.Table("Logs").AlterColumn("Time").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("UpdateHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable();
|
||||
Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,8 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
|
||||
details.Add(new XElement("trailer", "plugin://plugin.video.youtube/play/?video_id=" + movie.MovieMetadata.Value.YouTubeTrailerId));
|
||||
|
||||
details.Add(new XElement("watched", watched));
|
||||
|
||||
if (movieFile.MediaInfo != null)
|
||||
{
|
||||
var sceneName = movieFile.GetSceneOrFileName();
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.ImportLists
|
||||
private readonly IImportListStatusService _importListStatusService;
|
||||
private readonly IImportListMovieService _listMovieService;
|
||||
private readonly ISearchForNewMovie _movieSearch;
|
||||
private readonly IProvideMovieInfo _movieInfoService;
|
||||
private readonly IMovieMetadataService _movieMetadataService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -30,6 +31,7 @@ namespace NzbDrone.Core.ImportLists
|
||||
IImportListStatusService importListStatusService,
|
||||
IImportListMovieService listMovieService,
|
||||
ISearchForNewMovie movieSearch,
|
||||
IProvideMovieInfo movieInfoService,
|
||||
IMovieMetadataService movieMetadataService,
|
||||
Logger logger)
|
||||
{
|
||||
@@ -37,6 +39,7 @@ namespace NzbDrone.Core.ImportLists
|
||||
_importListStatusService = importListStatusService;
|
||||
_listMovieService = listMovieService;
|
||||
_movieSearch = movieSearch;
|
||||
_movieInfoService = movieInfoService;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -84,20 +87,10 @@ namespace NzbDrone.Core.ImportLists
|
||||
|
||||
if (!importListReports.AnyFailure)
|
||||
{
|
||||
// TODO some opportunity to bulk map here if we had the tmdbIds
|
||||
var listMovies = importListReports.Movies.Select(x =>
|
||||
{
|
||||
// Is it existing in result
|
||||
var movie = result.Movies.FirstOrDefault(r => r.TmdbId == x.TmdbId);
|
||||
|
||||
if (movie != null)
|
||||
{
|
||||
movie.ListId = importList.Definition.Id;
|
||||
}
|
||||
|
||||
return movie ?? MapMovieReport(x);
|
||||
}).Where(x => x.TmdbId > 0).ToList();
|
||||
var alreadyMapped = result.Movies.Where(x => importListReports.Movies.Any(r => r.TmdbId == x.TmdbId));
|
||||
var listMovies = MapMovieReports(importListReports.Movies.Where(x => !result.Movies.Any(r => r.TmdbId == x.TmdbId)).ToList()).Where(x => x.TmdbId > 0).ToList();
|
||||
|
||||
listMovies.AddRange(alreadyMapped);
|
||||
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
|
||||
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
|
||||
|
||||
@@ -148,13 +141,10 @@ namespace NzbDrone.Core.ImportLists
|
||||
|
||||
if (!importListReports.AnyFailure)
|
||||
{
|
||||
// TODO some opportunity to bulk map here if we had the tmdbIds
|
||||
var listMovies = importListReports.Movies.Select(x =>
|
||||
{
|
||||
return MapMovieReport(x);
|
||||
}).Where(x => x.TmdbId > 0).ToList();
|
||||
var listMovies = MapMovieReports(importListReports.Movies).Where(x => x.TmdbId > 0).ToList();
|
||||
|
||||
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
|
||||
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
|
||||
|
||||
result.Movies.AddRange(listMovies);
|
||||
_listMovieService.SyncMoviesForList(listMovies, importList.Definition.Id);
|
||||
@@ -173,21 +163,31 @@ namespace NzbDrone.Core.ImportLists
|
||||
return result;
|
||||
}
|
||||
|
||||
private ImportListMovie MapMovieReport(ImportListMovie report)
|
||||
private List<ImportListMovie> MapMovieReports(List<ImportListMovie> reports)
|
||||
{
|
||||
var mappedMovie = _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = report.Title, TmdbId = report.TmdbId, ImdbId = report.ImdbId, Year = report.Year });
|
||||
var mappedMovies = reports.Select(m => _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = m.Title, TmdbId = m.TmdbId, ImdbId = m.ImdbId, Year = m.Year }))
|
||||
.Where(x => x != null)
|
||||
.DistinctBy(x => x.TmdbId)
|
||||
.ToList();
|
||||
|
||||
var mappedListMovie = new ImportListMovie { ListId = report.ListId };
|
||||
_movieMetadataService.UpsertMany(mappedMovies);
|
||||
|
||||
if (mappedMovie != null)
|
||||
var mappedListMovies = new List<ImportListMovie>();
|
||||
|
||||
foreach (var movieMeta in mappedMovies)
|
||||
{
|
||||
_movieMetadataService.Upsert(mappedMovie);
|
||||
var mappedListMovie = new ImportListMovie();
|
||||
|
||||
mappedListMovie.MovieMetadata = mappedMovie;
|
||||
mappedListMovie.MovieMetadataId = mappedMovie.Id;
|
||||
if (movieMeta != null)
|
||||
{
|
||||
mappedListMovie.MovieMetadata = movieMeta;
|
||||
mappedListMovie.MovieMetadataId = movieMeta.Id;
|
||||
}
|
||||
|
||||
mappedListMovies.Add(mappedListMovie);
|
||||
}
|
||||
|
||||
return mappedListMovie;
|
||||
return mappedListMovies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,13 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
|
||||
{
|
||||
var link = string.Empty;
|
||||
|
||||
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim());
|
||||
// Trakt slug rules:
|
||||
// - replace all special characters with a dash
|
||||
// - replaces multiple dashes with a single dash
|
||||
// - allows underscore as a valid character
|
||||
// - does not trim underscore from the end
|
||||
// - allows multiple underscores in a row
|
||||
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim(), true, "-", "-");
|
||||
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
|
||||
|
||||
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
@@ -9,6 +11,24 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
: base()
|
||||
{
|
||||
RuleFor(c => c.TraktListType).NotNull();
|
||||
|
||||
// Loose validation @TODO
|
||||
RuleFor(c => c.Rating)
|
||||
.Matches(@"^\d+\-\d+$", RegexOptions.IgnoreCase)
|
||||
.When(c => c.Rating.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Not a valid rating");
|
||||
|
||||
// Any valid certification
|
||||
RuleFor(c => c.Certification)
|
||||
.Matches(@"^\bNR\b|\bG\b|\bPG\b|\bPG\-13\b|\bR\b|\bNC\-17\b$", RegexOptions.IgnoreCase)
|
||||
.When(c => c.Certification.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Not a valid cerification");
|
||||
|
||||
// Loose validation @TODO
|
||||
RuleFor(c => c.Years)
|
||||
.Matches(@"^\d+(\-\d+)?$", RegexOptions.IgnoreCase)
|
||||
.When(c => c.Years.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Not a valid year or range of years");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,5 +43,17 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
|
||||
|
||||
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktPopularListType), HelpText = "Type of list you're seeking to import from")]
|
||||
public int TraktListType { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Rating", HelpText = "Filter movies by rating range (0-100)")]
|
||||
public string Rating { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
|
||||
public string Certification { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
|
||||
public string Genres { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Years", HelpText = "Filter movies by year or year range")]
|
||||
public string Years { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
@@ -29,24 +28,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
.WithMessage("Must authenticate with Trakt")
|
||||
.When(c => c.AccessToken.IsNotNullOrWhiteSpace() && c.RefreshToken.IsNotNullOrWhiteSpace());
|
||||
|
||||
// Loose validation @TODO
|
||||
RuleFor(c => c.Rating)
|
||||
.Matches(@"^\d+\-\d+$", RegexOptions.IgnoreCase)
|
||||
.When(c => c.Rating.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Not a valid rating");
|
||||
|
||||
// Any valid certification
|
||||
RuleFor(c => c.Certification)
|
||||
.Matches(@"^\bNR\b|\bG\b|\bPG\b|\bPG\-13\b|\bR\b|\bNC\-17\b$", RegexOptions.IgnoreCase)
|
||||
.When(c => c.Certification.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Not a valid cerification");
|
||||
|
||||
// Loose validation @TODO
|
||||
RuleFor(c => c.Years)
|
||||
.Matches(@"^\d+(\-\d+)?$", RegexOptions.IgnoreCase)
|
||||
.When(c => c.Years.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Not a valid year or range of years");
|
||||
|
||||
// Limit not smaller than 1 and not larger than 100
|
||||
RuleFor(c => c.Limit)
|
||||
.GreaterThan(0)
|
||||
@@ -62,10 +43,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
public TraktSettingsBase()
|
||||
{
|
||||
SignIn = "startOAuth";
|
||||
Rating = "0-100";
|
||||
Certification = "NR,G,PG,PG-13,R,NC-17";
|
||||
Genres = "";
|
||||
Years = "";
|
||||
Limit = 100;
|
||||
}
|
||||
|
||||
@@ -84,18 +61,6 @@ namespace NzbDrone.Core.ImportLists.Trakt
|
||||
[FieldDefinition(0, Label = "Auth User", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)]
|
||||
public string AuthUser { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Rating", HelpText = "Filter movies by rating range (0-100)")]
|
||||
public string Rating { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
|
||||
public string Certification { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
|
||||
public string Genres { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Years", HelpText = "Filter movies by year or year range")]
|
||||
public string Years { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Limit", HelpText = "Limit the number of movies to get")]
|
||||
public int Limit { get; set; }
|
||||
|
||||
|
||||
@@ -42,14 +42,15 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
foreach (var movieId in message.MovieIds)
|
||||
{
|
||||
var movie = _movieService.GetMovie(movieId);
|
||||
var userInvokedSearch = message.Trigger == CommandTrigger.Manual;
|
||||
|
||||
if (!movie.Monitored && message.Trigger != CommandTrigger.Manual)
|
||||
if (!movie.Monitored && !userInvokedSearch)
|
||||
{
|
||||
_logger.Debug("Movie {0} is not monitored, skipping search", movie.Title);
|
||||
continue;
|
||||
}
|
||||
|
||||
var decisions = _releaseSearchService.MovieSearch(movieId, false, false);
|
||||
var decisions = _releaseSearchService.MovieSearch(movieId, userInvokedSearch, false);
|
||||
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
if (InfoHashElementName.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return item.FindDecendants(InfoHashElementName).FirstOrDefault().Value;
|
||||
return item.FindDecendants(InfoHashElementName).FirstOrDefault()?.Value;
|
||||
}
|
||||
|
||||
var magnetUrl = GetMagnetUrl(item);
|
||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
if (MagnetElementName.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var magnetURL = item.FindDecendants(MagnetElementName).FirstOrDefault().Value;
|
||||
var magnetURL = item.FindDecendants(MagnetElementName).FirstOrDefault()?.Value;
|
||||
if (magnetURL.IsNotNullOrWhiteSpace() && magnetURL.StartsWith("magnet:"))
|
||||
{
|
||||
return magnetURL;
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace NzbDrone.Core.Languages
|
||||
public static Language Ukrainian => new Language(32, "Ukrainian");
|
||||
public static Language Persian => new Language(33, "Persian");
|
||||
public static Language Bengali => new Language(34, "Bengali");
|
||||
public static Language Slovak => new Language(35, "Slovak");
|
||||
public static Language Any => new Language(-1, "Any");
|
||||
public static Language Original => new Language(-2, "Original");
|
||||
|
||||
@@ -149,6 +150,7 @@ namespace NzbDrone.Core.Languages
|
||||
Ukrainian,
|
||||
Persian,
|
||||
Bengali,
|
||||
Slovak,
|
||||
Any,
|
||||
Original
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -256,7 +256,7 @@
|
||||
"AlternativeTitle": "Alternative Titel",
|
||||
"AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
|
||||
"Age": "Alter",
|
||||
"AddNewMovie": "Neuer Film...",
|
||||
"AddNewMovie": "Neuen Film hinzufügen",
|
||||
"AddList": "Liste hinzufügen",
|
||||
"SystemTimeCheckMessage": "Die Systemzeit ist um einen Tag versetzt. Bis die Zeit korrigiert wurde, könnten die geplanten Aufgaben nicht korrekt ausgeführt werden",
|
||||
"UnsavedChanges": "Ungespeicherte Änderungen",
|
||||
@@ -640,7 +640,7 @@
|
||||
"RemovingTag": "Tag entfernen",
|
||||
"ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}",
|
||||
"Queued": "Eingereiht",
|
||||
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen?",
|
||||
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen",
|
||||
"Pending": "Ausstehend",
|
||||
"Paused": "Pausiert",
|
||||
"NegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
|
||||
@@ -1047,7 +1047,7 @@
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Docker erkannt; Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckFilesWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise müssen die Verzeichnisrechte angepasst werden.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
|
||||
"RemotePathMappingCheckWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Remote-Pfadzuordnungen und die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Downloader {0} speichert Downloads in {1}, aber dieses Verzeichnis scheint nicht zu existieren. Möglicherweise eine fehlende oder falsche Remote-Pfadzuordnung.",
|
||||
@@ -1112,10 +1112,10 @@
|
||||
"OriginalTitle": "Originaler Titel",
|
||||
"OriginalLanguage": "Originale Sprache",
|
||||
"Database": "Datenbank",
|
||||
"AllCollectionsHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
|
||||
"Collections": "Sammlung",
|
||||
"AllCollectionsHiddenDueToFilter": "Alle Filme sind auf Grund des Filters ausgeblendet.",
|
||||
"Collections": "Sammlungen",
|
||||
"MonitorMovies": "Film beobachten",
|
||||
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren.",
|
||||
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren",
|
||||
"RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )",
|
||||
"CollectionOptions": "Sammlung Optionen",
|
||||
"ChooseImportMode": "Wähle eine Importmethode",
|
||||
@@ -1127,7 +1127,7 @@
|
||||
"ScrollMovies": "Filme scrollen",
|
||||
"ShowCollectionDetails": "Status der Sammlung anzeigen",
|
||||
"RefreshCollections": "Sammlungen aktualisieren",
|
||||
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (Min. 1 Minute).",
|
||||
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (min. 1 Minute)",
|
||||
"ShowOverview": "Übersicht anzeigen",
|
||||
"ShowPosters": "Plakate anzeigen",
|
||||
"CollectionShowDetailsHelpText": "Status und Eigenschaften der Sammlung anzeigen",
|
||||
@@ -1141,5 +1141,6 @@
|
||||
"OnMovieAddedHelpText": "Ausführen wenn ein Film hinzugefügt wurde",
|
||||
"UnableToLoadCollections": "Sammlungen können nicht geladen werden",
|
||||
"InstanceName": "Instanzname",
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname"
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
|
||||
"RottenTomatoesRating": "Tomato Bewertung"
|
||||
}
|
||||
|
||||
@@ -1027,6 +1027,7 @@
|
||||
"Torrents": "Torrents",
|
||||
"TorrentsDisabled": "Torrents Disabled",
|
||||
"TotalFileSize": "Total File Size",
|
||||
"TotalMovies": "Total Movies",
|
||||
"TotalSpace": "Total Space",
|
||||
"Trace": "Trace",
|
||||
"Trailer": "Trailer",
|
||||
|
||||
@@ -1119,5 +1119,6 @@
|
||||
"ChooseImportMode": "Elegir Modo de Importación",
|
||||
"CollectionOptions": "Opciones de la Colección",
|
||||
"CollectionShowDetailsHelpText": "Mostrar el estado y propiedades de la colección",
|
||||
"CollectionShowOverviewsHelpText": "Mostrar resumenes de la colección"
|
||||
"CollectionShowOverviewsHelpText": "Mostrar resumenes de la colección",
|
||||
"CollectionShowPostersHelpText": "Mostrar póster de artículos de colección"
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
"MoveFiles": "Siirrä tiedostoja",
|
||||
"MovieFiles": "Elokuvatiedostot",
|
||||
"MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella",
|
||||
"NextExecution": "Seuraava toteutus",
|
||||
"NextExecution": "Seuraava suoritus",
|
||||
"NoAltTitle": "Ei vaihtoehtoisia nimikkeitä.",
|
||||
"NoEventsFound": "Tapahtumia ei löytynyt",
|
||||
"NoLinks": "Ei linkkejä",
|
||||
@@ -320,8 +320,8 @@
|
||||
"ImportErrors": "Tuontivirheet",
|
||||
"Imported": "Tuodut",
|
||||
"InvalidFormat": "Väärä muoto",
|
||||
"LastDuration": "Viimeisin kesto",
|
||||
"LastExecution": "Viimeinen toteutus",
|
||||
"LastDuration": "Edellinen kesto",
|
||||
"LastExecution": "Edellinen suoritus",
|
||||
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
|
||||
"ListExclusions": "Listojen poikkeussäännöt",
|
||||
"Indexer": "Tietolähde",
|
||||
@@ -1142,5 +1142,6 @@
|
||||
"CollectionOptions": "Kokoelmien valinnat",
|
||||
"CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet",
|
||||
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet",
|
||||
"RottenTomatoesRating": "Tomaattiarvio"
|
||||
"RottenTomatoesRating": "Tomaattiarvio",
|
||||
"TotalMovies": "Elokuvia yhteensä"
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"RemotePathMappings": "Mappages de chemins distants",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "La branche {0} n'est pas une branche de version Radarr valide, vous ne recevrez pas de mises à jour",
|
||||
"RefreshAndScan": "Actualiser et analyser",
|
||||
"Refresh": "Rafraîchir",
|
||||
"Refresh": "Actualiser",
|
||||
"Ratings": "Évaluations",
|
||||
"Queue": "File d'attente",
|
||||
"QualitySettingsSummary": "Tailles qualité et dénomination",
|
||||
@@ -318,7 +318,7 @@
|
||||
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
|
||||
"ChangeFileDate": "Changer la date du fichier",
|
||||
"CertificationCountryHelpText": "Choisir un pays pour les classifications de films",
|
||||
"CertificateValidationHelpText": "Change la rigueur de la vérification du certificat HTTPS. Ne changez rien sauf si vous comprenez les risques.",
|
||||
"CertificateValidationHelpText": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne pas changer à moins que vous ne compreniez les risques.",
|
||||
"CertificateValidation": "Validation du certificat",
|
||||
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
|
||||
"Branch": "Branche",
|
||||
@@ -1114,10 +1114,28 @@
|
||||
"SetReleaseGroup": "Régler le groupe de publication",
|
||||
"RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute",
|
||||
"AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.",
|
||||
"Collections": "Collection",
|
||||
"Collections": "Collections",
|
||||
"MonitorMovies": "Surveiller le film",
|
||||
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
|
||||
"RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)",
|
||||
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
|
||||
"ChooseImportMode": "Mode d'importation"
|
||||
"ChooseImportMode": "Mode d'importation",
|
||||
"CollectionOptions": "Options de collection",
|
||||
"CollectionShowDetailsHelpText": "Afficher l'état et les propriétés de la collection",
|
||||
"CollectionShowOverviewsHelpText": "Afficher les aperçus des collections",
|
||||
"OnMovieAddedHelpText": "À l'ajout d'un film",
|
||||
"MovieAndCollection": "Film et collection",
|
||||
"ShowOverview": "Afficher l'aperçu",
|
||||
"MovieCollectionMissingRoot": "Dossier racine manquant pour la collection de films : {0}",
|
||||
"OnMovieAdded": "À l'ajout d'un film",
|
||||
"MonitorCollection": "Surveiller la collection",
|
||||
"MonitoredCollectionHelpText": "Surveiller pour ajouter automatiquement les films de cette collection à la bibliothèque",
|
||||
"MovieCollectionMultipleMissingRoots": "Plusieurs dossiers racine manquent pour les collections de films: {0}",
|
||||
"MovieOnly": "Film seulement",
|
||||
"RefreshCollections": "Actualiser les collections",
|
||||
"SearchOnAddCollectionHelpText": "Rechercher des films dans cette collection lorsqu'ils sont ajoutés à Radarr",
|
||||
"UnableToLoadCollections": "Impossible de charger les collections",
|
||||
"RottenTomatoesRating": "Classement Rotten Tomatoes",
|
||||
"ShowCollectionDetails": "Afficher l'état de la collection",
|
||||
"EditCollection": "Modifier la collection"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"AddExclusion": "Kivétel hozzáadása",
|
||||
"Activity": "Aktivitás",
|
||||
"Error": "Hiba",
|
||||
"Ended": "Befejezve",
|
||||
"Ended": "Vége lett",
|
||||
"EnableCompletedDownloadHandlingHelpText": "A befejezett letöltések automatikus importálása a letöltési kliensből",
|
||||
"EnableColorImpairedModeHelpText": "Megváltoztatott színek, hogy a színvak felhasználók jobban meg tudják különböztetni a színkódolt információkat",
|
||||
"EnableAutomaticSearchHelpTextWarning": "Interaktív keresés esetén is felhasználható",
|
||||
@@ -434,7 +434,7 @@
|
||||
"RadarrSupportsAnyIndexer": "A Radarr minden indexert támogat, amely a Newznab szabványt használja, valamint az alább felsorolt egyéb indexereket.",
|
||||
"RadarrSupportsAnyDownloadClient": "A Radarr számos népszerű torrent és usenet letöltési ügyfelet támogat.",
|
||||
"QuickImport": "Automatikus Áthelyezés",
|
||||
"Queued": "Sorban",
|
||||
"Queued": "Sorba helyezve",
|
||||
"Queue": "Várakozási sor",
|
||||
"QualitySettingsSummary": "Minőségi méretek és elnevezés",
|
||||
"QualitySettings": "Minőségi beállítások",
|
||||
@@ -616,7 +616,7 @@
|
||||
"Level": "Szint",
|
||||
"LaunchBrowserHelpText": " Nyisson meg egy böngészőt, és az alkalmazás indításakor lépjen a Radarr kezdőlapjára.",
|
||||
"LastWriteTime": "Utolsó írási idő",
|
||||
"LastDuration": "Utolsó Hossza",
|
||||
"LastDuration": "Utolsó időtartam",
|
||||
"Languages": "Nyelvek",
|
||||
"LanguageHelpText": "Nyelv a Releasekhez",
|
||||
"Language": "Nyelv",
|
||||
@@ -896,7 +896,7 @@
|
||||
"HomePage": "Kezdőlap",
|
||||
"LogOnly": "Csak naplózás",
|
||||
"LastUsed": "Utoljára használt",
|
||||
"LastExecution": "Utoljára végrehajtott",
|
||||
"LastExecution": "Utolsó végrehajtás",
|
||||
"Large": "Óriási",
|
||||
"KeepAndUnmonitorMovie": "Megtartás és megfigyelés kikapcsolása a filmnél",
|
||||
"InvalidFormat": "Érvénytelen Formátum",
|
||||
@@ -992,7 +992,7 @@
|
||||
"NoLinks": "Nincsenek Linkek",
|
||||
"NoEventsFound": "Nem található esemény",
|
||||
"NoAltTitle": "Nincs alternatív cím.",
|
||||
"NextExecution": "Következő futtatás",
|
||||
"NextExecution": "Következő végrehajtás",
|
||||
"Negated": "Megtagadva",
|
||||
"Negate": "Megtagadás",
|
||||
"MultiLanguage": "Többnyelvű",
|
||||
@@ -1105,7 +1105,7 @@
|
||||
"Never": "Soha",
|
||||
"Rating": "Értékelés",
|
||||
"SetReleaseGroup": "Kiadási csoport beállítása",
|
||||
"Started": "Elindult",
|
||||
"Started": "Elkezdődött",
|
||||
"Waiting": "Várakozás",
|
||||
"OnApplicationUpdateHelpText": "Alkalmazásfrissítésről",
|
||||
"SizeLimit": "Méretkorlát",
|
||||
@@ -1142,5 +1142,6 @@
|
||||
"OnMovieAddedHelpText": "Film hozzáadásakor",
|
||||
"ShowPosters": "Poszterek megjelenítése",
|
||||
"InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve",
|
||||
"RottenTomatoesRating": "Tomato Értékelés"
|
||||
"RottenTomatoesRating": "Tomato Értékelés",
|
||||
"TotalMovies": "Összes film"
|
||||
}
|
||||
|
||||
@@ -254,5 +254,6 @@
|
||||
"Language": "språk",
|
||||
"List": "Liste",
|
||||
"New": "Ny",
|
||||
"RemotePathMappings": "Ekstern portmapping"
|
||||
"RemotePathMappings": "Ekstern portmapping",
|
||||
"Languages": "språk"
|
||||
}
|
||||
|
||||
@@ -1062,7 +1062,7 @@
|
||||
"Collections": "Kolekcje",
|
||||
"RssSyncHelpText": "Odstęp w minutach. Ustaw na zero, aby wyłączyć (zatrzyma to wszystkie automatyczne przechwytywanie zwolnień)",
|
||||
"NoCollections": "Nie znaleziono żadnych filmów. Aby rozpocząć, musisz dodać nowy film lub zaimportować istniejące",
|
||||
"MonitorMovies": "Monitoruj film",
|
||||
"MonitorMovies": "Monitoruj filmy",
|
||||
"ClickToChangeReleaseGroup": "Kliknij, by zmienić grupę wydającą",
|
||||
"RemotePathMappingCheckDownloadPermissions": "Radarr widzi film {0}, lecz nie ma do niego dostępu. Najprawdopodobniej to wynik błędu w uprawnieniach dostępu.",
|
||||
"NotificationTriggersHelpText": "Wybierz zdarzenia, które mają uruchamiać to powiadomienie",
|
||||
@@ -1078,7 +1078,7 @@
|
||||
"ManualImportSetReleaseGroup": "Import ręczny - podaj nazwę grupy",
|
||||
"Never": "Nigdy",
|
||||
"RemotePathMappingCheckFilesWrongOSPath": "Zdalny klient pobierania {0} zgłosił pliki w {1}, lecz nie jest to poprawna ścieżka {2}. Zmień ustawienia zdalnego mapowania ścieżek i klienta pobierania.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, lecz Radarr nie widzi tego folderu. Być może należy zmienić uprawnienia dostępu do folderu.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, ale Radarr nie widzi tego folderu. Być może należy zmienić uprawnienia dostępu do folderu/ścieżkę dostępu.",
|
||||
"RemoveFailed": "Usuń nieudane",
|
||||
"RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.",
|
||||
"ShowCollectionDetails": "Pokaż stan kolekcji",
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"CollectionShowPostersHelpText": "Pokaż plakaty elementów kolekcji",
|
||||
"CollectionOptions": "Opcje Kolekcji",
|
||||
"CollectionShowDetailsHelpText": "Pokaż status i właściwości kolekcji",
|
||||
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji"
|
||||
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji",
|
||||
"TotalMovies": "Filmów całkowicie",
|
||||
"RottenTomatoesRating": "Ocena Tomato"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"LastExecution": "Execução mais recente",
|
||||
"LastExecution": "Última Execução",
|
||||
"Large": "Grande",
|
||||
"Languages": "Idiomas",
|
||||
"LanguageHelpText": "Idioma das versões",
|
||||
@@ -19,7 +19,7 @@
|
||||
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
|
||||
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
|
||||
"IndexerSettings": "Configurações do indexador",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativo",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
|
||||
"Indexers": "Indexadores",
|
||||
@@ -571,7 +571,7 @@
|
||||
"NoChange": "Sem alteração",
|
||||
"NoBackupsAreAvailable": "Não há backups disponíveis",
|
||||
"NoAltTitle": "Nenhum título alternativo.",
|
||||
"NextExecution": "Próxima execução",
|
||||
"NextExecution": "Próxima Execução",
|
||||
"New": "Novo",
|
||||
"NetCore": ".NET",
|
||||
"ShowAsAllDayEvents": "Mostrar como eventos de dia inteiro",
|
||||
@@ -668,7 +668,7 @@
|
||||
"SomeResultsHiddenFilter": "Alguns resultados estão ocultos pelo filtro aplicado",
|
||||
"Socks5": "Socks5 (suporte ao TOR)",
|
||||
"Small": "Pequeno",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar o Radarr quando for impossível detectar o espaço livre da sua pasta raiz do filme",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Use quando o Radarr não conseguir detectar espaço livre na pasta raiz do filme",
|
||||
"SkipFreeSpaceCheck": "Ignorar verificação de espaço livre",
|
||||
"SizeOnDisk": "Tamanho em disco",
|
||||
"Size": "Tamanho",
|
||||
@@ -961,7 +961,7 @@
|
||||
"UpdateSelected": "Atualizar selecionado(s)",
|
||||
"UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização",
|
||||
"Updates": "Atualizações",
|
||||
"UpdateMechanismHelpText": "Usar atualizador integrado do Radarr ou um script",
|
||||
"UpdateMechanismHelpText": "Use o atualizador integrado do Radarr ou um script",
|
||||
"UpdateCheckUINotWritableMessage": "Não é possível instalar a atualização porque a pasta de interface do usuário '{0}' não é gravável pelo usuário '{1}'.",
|
||||
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
|
||||
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
|
||||
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
|
||||
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente para download {0} põe os downloads em {1}, mas o Radarr não pode ver esse diretório. Você pode precisar ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca downloads em {1} mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.",
|
||||
@@ -1142,5 +1142,6 @@
|
||||
"CollectionShowDetailsHelpText": "Mostrar estado e propriedades da coleção",
|
||||
"CollectionShowOverviewsHelpText": "Mostrar visão geral da coleção",
|
||||
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção",
|
||||
"RottenTomatoesRating": "Avaliação Tomato"
|
||||
"RottenTomatoesRating": "Avaliação Tomato",
|
||||
"TotalMovies": "Total de Filmes"
|
||||
}
|
||||
|
||||
@@ -637,7 +637,7 @@
|
||||
"InstallLatest": "安装最新版",
|
||||
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
|
||||
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,Radarr 不会提供任何手动搜索结果",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,Radarr 不会提供任何手动搜索的结果",
|
||||
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时,下列索引器都已不可用:{0}",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有索引器均不可用",
|
||||
@@ -740,7 +740,7 @@
|
||||
"UnableToAddANewNotificationPleaseTryAgain": "无法添加新通知,请稍后重试。",
|
||||
"URLBase": "基本URL",
|
||||
"RemovedMovieCheckMultipleMessage": "电影“{0}”已从TMDb移除",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "当 Radarr 无法从您的电影根文件夹中检测到空闲空间时使用",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "当 Radarr 无法从movie根目录检测到空间时使用",
|
||||
"UnableToAddANewQualityProfilePleaseTryAgain": "无法添加新影片质量配置,请稍后重试。",
|
||||
"ThisCannotBeCancelled": "在不禁用所有索引器的情况下,一旦启动就无法取消。",
|
||||
"SearchCutoffUnmet": "搜索未满足终止条件的",
|
||||
@@ -1135,5 +1135,12 @@
|
||||
"NoCollections": "没有发现集合,点击添加新的电影或者导入已经存在的电影",
|
||||
"InstanceNameHelpText": "选项卡及日志应用名称",
|
||||
"ScrollMovies": "滚动电影",
|
||||
"CollectionOptions": "Collection Options"
|
||||
"CollectionOptions": "集选项Collection Options",
|
||||
"CollectionShowDetailsHelpText": "显示集合collection的状态和属性",
|
||||
"CollectionShowOverviewsHelpText": "显示集合collection的概述",
|
||||
"CollectionShowPostersHelpText": "显示集合项目海报",
|
||||
"RottenTomatoesRating": "烂番茄指数",
|
||||
"ShowPosters": "显示海报",
|
||||
"OnMovieAdded": "电影添加时",
|
||||
"OnMovieAddedHelpText": "电影添加时"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"About": "關於",
|
||||
"Add": "添加",
|
||||
"Add": "新增",
|
||||
"AcceptConfirmationModal": "接受確認模式",
|
||||
"Actions": "行動",
|
||||
"Activity": "活動"
|
||||
"Actions": "執行",
|
||||
"Activity": "活動",
|
||||
"AddIndexer": "新增索引",
|
||||
"AddingTag": "新增標籤",
|
||||
"AddDownloadClient": "新增下載器",
|
||||
"Added": "以新增",
|
||||
"Age": "年齡",
|
||||
"All": "全部",
|
||||
"Analytics": "分析"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
@@ -260,6 +261,20 @@ namespace NzbDrone.Core.MediaFiles
|
||||
};
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(fileInfo.Name);
|
||||
|
||||
if (extension.IsNullOrWhiteSpace() || !MediaFileExtensions.Extensions.Contains(extension))
|
||||
{
|
||||
_logger.Debug("[{0}] has an unsupported extension: '{1}'", fileInfo.FullName, extension);
|
||||
|
||||
return new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalMovie { Path = fileInfo.FullName },
|
||||
new Rejection($"Invalid video file, unsupported extension: '{extension}'")),
|
||||
$"Invalid video file, unsupported extension: '{extension}'")
|
||||
};
|
||||
}
|
||||
|
||||
if (downloadClientItem == null)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||
|
||||
@@ -327,6 +327,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
}
|
||||
else if (movie.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
newMovie = _movieMetadataService.FindByImdbId(Parser.Parser.NormalizeImdbId(movie.ImdbId));
|
||||
|
||||
if (newMovie != null)
|
||||
{
|
||||
return newMovie;
|
||||
}
|
||||
|
||||
newMovie = GetMovieByImdbId(movie.ImdbId);
|
||||
}
|
||||
else
|
||||
@@ -337,7 +344,16 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
yearStr = $" {movie.Year}";
|
||||
}
|
||||
|
||||
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault().MovieMetadata;
|
||||
var newMovieObject = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault();
|
||||
|
||||
if (newMovieObject == null)
|
||||
{
|
||||
newMovie = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
newMovie = newMovieObject.MovieMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
if (newMovie == null)
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Movies.Collections
|
||||
|
||||
var collection = FindByTmdbId(collectionTmdbId);
|
||||
|
||||
_repo.Delete(collectionTmdbId);
|
||||
_repo.Delete(collection.Id);
|
||||
|
||||
_eventAggregator.PublishEvent(new CollectionDeletedEvent(collection));
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Movies
|
||||
_diskProvider.CreateFolder(new DirectoryInfo(destinationPath).Parent.FullName);
|
||||
_diskTransferService.TransferFolder(sourcePath, destinationPath, TransferMode.Move);
|
||||
|
||||
_logger.ProgressInfo("{0} moved successfully to {1}", movie.Title, movie.Path);
|
||||
_logger.ProgressInfo("{0} moved successfully to {1}", movie.Title, destinationPath);
|
||||
|
||||
_eventAggregator.PublishEvent(new MovieMovedEvent(movie, sourcePath, destinationPath));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Movies
|
||||
public interface IMovieMetadataRepository : IBasicRepository<MovieMetadata>
|
||||
{
|
||||
MovieMetadata FindByTmdbId(int tmdbId);
|
||||
MovieMetadata FindByImdbId(string imdbId);
|
||||
List<MovieMetadata> FindById(List<int> tmdbIds);
|
||||
List<MovieMetadata> GetMoviesWithCollections();
|
||||
List<MovieMetadata> GetMoviesByCollectionTmdbId(int collectionId);
|
||||
@@ -27,9 +28,14 @@ namespace NzbDrone.Core.Movies
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public MovieMetadata FindByTmdbId(int tmdbid)
|
||||
public MovieMetadata FindByTmdbId(int tmdbId)
|
||||
{
|
||||
return Query(x => x.TmdbId == tmdbid).FirstOrDefault();
|
||||
return Query(x => x.TmdbId == tmdbId).FirstOrDefault();
|
||||
}
|
||||
|
||||
public MovieMetadata FindByImdbId(string imdbId)
|
||||
{
|
||||
return Query(x => x.ImdbId == imdbId).FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<MovieMetadata> FindById(List<int> tmdbIds)
|
||||
|
||||
@@ -5,7 +5,8 @@ namespace NzbDrone.Core.Movies
|
||||
public interface IMovieMetadataService
|
||||
{
|
||||
MovieMetadata Get(int id);
|
||||
MovieMetadata FindByTmdbId(int tmdbid);
|
||||
MovieMetadata FindByTmdbId(int tmdbId);
|
||||
MovieMetadata FindByImdbId(string imdbId);
|
||||
List<MovieMetadata> GetMoviesWithCollections();
|
||||
List<MovieMetadata> GetMoviesByCollectionTmdbId(int collectionId);
|
||||
bool Upsert(MovieMetadata movie);
|
||||
@@ -21,9 +22,14 @@ namespace NzbDrone.Core.Movies
|
||||
_movieMetadataRepository = movieMetadataRepository;
|
||||
}
|
||||
|
||||
public MovieMetadata FindByTmdbId(int tmdbid)
|
||||
public MovieMetadata FindByTmdbId(int tmdbId)
|
||||
{
|
||||
return _movieMetadataRepository.FindByTmdbId(tmdbid);
|
||||
return _movieMetadataRepository.FindByTmdbId(tmdbId);
|
||||
}
|
||||
|
||||
public MovieMetadata FindByImdbId(string imdbId)
|
||||
{
|
||||
return _movieMetadataRepository.FindByImdbId(imdbId);
|
||||
}
|
||||
|
||||
public List<MovieMetadata> GetMoviesWithCollections()
|
||||
|
||||
@@ -119,10 +119,18 @@ namespace NzbDrone.Core.Movies
|
||||
return FindByTitle(new List<string> { title }, year, otherTitles, candidates);
|
||||
}
|
||||
|
||||
public Movie FindByTitle(List<string> cleanTitles, int? year, List<string> otherTitles, List<Movie> candidates)
|
||||
public Movie FindByTitle(List<string> titles, int? year, List<string> otherTitles, List<Movie> candidates)
|
||||
{
|
||||
var cleanTitles = titles.Select(t => t.CleanMovieTitle().ToLowerInvariant());
|
||||
|
||||
var result = candidates.Where(x => cleanTitles.Contains(x.MovieMetadata.Value.CleanTitle)).FirstWithYear(year);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result =
|
||||
candidates.Where(movie => cleanTitles.Contains(movie.MovieMetadata.Value.CleanOriginalTitle)).FirstWithYear(year);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result =
|
||||
|
||||
@@ -3,12 +3,11 @@ using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.ImportLists.ImportExclusions;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Movies.Collections;
|
||||
using NzbDrone.Core.Movies.Commands;
|
||||
using NzbDrone.Core.Movies.Events;
|
||||
|
||||
namespace NzbDrone.Core.Movies
|
||||
{
|
||||
@@ -19,6 +18,7 @@ namespace NzbDrone.Core.Movies
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IMovieMetadataService _movieMetadataService;
|
||||
private readonly IAddMovieService _addMovieService;
|
||||
private readonly IImportExclusionsService _importExclusionService;
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Movies
|
||||
IMovieService movieService,
|
||||
IMovieMetadataService movieMetadataService,
|
||||
IAddMovieService addMovieService,
|
||||
IImportExclusionsService importExclusionsService,
|
||||
Logger logger)
|
||||
{
|
||||
_movieInfo = movieInfo;
|
||||
@@ -34,6 +35,7 @@ namespace NzbDrone.Core.Movies
|
||||
_movieService = movieService;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
_addMovieService = addMovieService;
|
||||
_importExclusionService = importExclusionsService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -99,7 +101,8 @@ namespace NzbDrone.Core.Movies
|
||||
{
|
||||
var existingMovies = _movieService.AllMovieTmdbIds();
|
||||
var collectionMovies = _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId);
|
||||
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId));
|
||||
var excludedMovies = _importExclusionService.GetAllExclusions().Select(e => e.TmdbId);
|
||||
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId)).Where(m => !excludedMovies.Contains(m.TmdbId));
|
||||
|
||||
if (moviesToAdd.Any())
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Movies
|
||||
SearchOnAdd = movie.AddOptions?.SearchForMovie ?? false,
|
||||
QualityProfileId = movie.ProfileId,
|
||||
MinimumAvailability = movie.MinimumAvailability,
|
||||
RootFolderPath = _folderService.GetBestRootFolderPath(movie.Path)
|
||||
RootFolderPath = _folderService.GetBestRootFolderPath(movie.Path).TrimEnd('/', '\\', ' ')
|
||||
});
|
||||
|
||||
movieMetadata.CollectionTmdbId = newCollection.TmdbId;
|
||||
|
||||
@@ -98,6 +98,14 @@ namespace NzbDrone.Core.Notifications.Discord
|
||||
discordField.Name = "Links";
|
||||
discordField.Value = GetLinksString(message.Movie);
|
||||
break;
|
||||
case DiscordGrabFieldType.CustomFormats:
|
||||
discordField.Name = "Custom Formats";
|
||||
discordField.Value = string.Join("|", message.RemoteMovie.CustomFormats);
|
||||
break;
|
||||
case DiscordGrabFieldType.CustomFormatScore:
|
||||
discordField.Name = "Custom Format Score";
|
||||
discordField.Value = message.RemoteMovie.CustomFormatScore.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace NzbDrone.Core.Notifications.Discord
|
||||
Links,
|
||||
Release,
|
||||
Poster,
|
||||
Fanart
|
||||
Fanart,
|
||||
CustomFormats,
|
||||
CustomFormatScore
|
||||
}
|
||||
|
||||
public enum DiscordImportFieldType
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
variables.Add("Radarr_Download_Client", message.DownloadClientName ?? string.Empty);
|
||||
variables.Add("Radarr_Download_Client_Type", message.DownloadClientType ?? string.Empty);
|
||||
variables.Add("Radarr_Download_Id", message.DownloadId ?? string.Empty);
|
||||
variables.Add("Radarr_Release_CustomFormat", string.Join("|", remoteMovie.CustomFormats));
|
||||
variables.Add("Radarr_Release_CustomFormatScore", remoteMovie.CustomFormatScore.ToString());
|
||||
|
||||
_proxy.SendNotification(variables, Settings);
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
{
|
||||
public enum NotifiarrEnvironment
|
||||
{
|
||||
Live,
|
||||
Development
|
||||
}
|
||||
}
|
||||
@@ -54,17 +54,19 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
_logger.Error(ex, "API key is invalid: " + ex.Message);
|
||||
return new ValidationFailure("APIKey", "API key is invalid");
|
||||
case 400:
|
||||
_logger.Error(ex, "Unable to send test message. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
return new ValidationFailure("", "Unable to send test message. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
case 520:
|
||||
case 521:
|
||||
case 522:
|
||||
case 523:
|
||||
case 524:
|
||||
_logger.Error(ex, "Unable to send test notification: " + ex.Message);
|
||||
return new ValidationFailure("", "Unable to send test notification");
|
||||
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("", "Cloudflare Related HTTP Error - Unable to send test message");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("APIKey", "Unable to send test notification");
|
||||
_logger.Error(ex, "Unknown HTTP Error - Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("", "Unknown HTTP Error - Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -77,7 +79,7 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = settings.Environment == (int)NotifiarrEnvironment.Development ? "https://dev.notifiarr.com" : "https://notifiarr.com";
|
||||
var url = "https://notifiarr.com";
|
||||
var requestBuilder = new HttpRequestBuilder(url + "/api/v1/notification/radarr/" + settings.APIKey).Post();
|
||||
requestBuilder.AddFormParameter("instanceName", settings.InstanceName).Build();
|
||||
|
||||
@@ -95,19 +97,21 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
switch ((int)ex.Response.StatusCode)
|
||||
{
|
||||
case 401:
|
||||
_logger.Error(ex, "API key is invalid");
|
||||
throw;
|
||||
_logger.Error("", "API key is invalid");
|
||||
throw new NotifiarrException("API key is invalid", ex);
|
||||
case 400:
|
||||
_logger.Error(ex, "Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
throw new NotifiarrException("Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr", ex);
|
||||
case 520:
|
||||
case 521:
|
||||
case 522:
|
||||
case 523:
|
||||
case 524:
|
||||
_logger.Error(ex, "Unable to send notification");
|
||||
throw;
|
||||
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send notification");
|
||||
throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex);
|
||||
}
|
||||
|
||||
throw new NotifiarrException("Unable to send notification", ex);
|
||||
throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
public string APIKey { get; set; }
|
||||
[FieldDefinition(1, Label = "Instance Name", Advanced = true, HelpText = "Unique name for this instance", HelpLink = "https://notifiarr.com")]
|
||||
public string InstanceName { get; set; }
|
||||
[FieldDefinition(2, Label = "Environment", Advanced = true, Type = FieldType.Select, SelectOptions = typeof(NotifiarrEnvironment), HelpText = "Live unless told otherwise", HelpLink = "https://notifiarr.com")]
|
||||
public int Environment { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
|
||||
66
src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs
Normal file
66
src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using FluentValidation.Results;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Movies;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Ntfy
|
||||
{
|
||||
public class Ntfy : NotificationBase<NtfySettings>
|
||||
{
|
||||
private readonly INtfyProxy _proxy;
|
||||
|
||||
public Ntfy(INtfyProxy proxy)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
public override string Name => "ntfy.sh";
|
||||
|
||||
public override string Link => "https://ntfy.sh/";
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieAdded(Movie movie)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_ADDED_TITLE_BRANDED, $"{movie.Title} added to library", Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
|
||||
{
|
||||
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
||||
{
|
||||
_proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
failures.AddIfNotNull(_proxy.Test(Settings));
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/NzbDrone.Core/Notifications/Ntfy/NtfyException.cs
Normal file
18
src/NzbDrone.Core/Notifications/Ntfy/NtfyException.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Ntfy
|
||||
{
|
||||
public class NtfyException : NzbDroneException
|
||||
{
|
||||
public NtfyException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NtfyException(string message, Exception innerException, params object[] args)
|
||||
: base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/NzbDrone.Core/Notifications/Ntfy/NtfyPriority.cs
Normal file
11
src/NzbDrone.Core/Notifications/Ntfy/NtfyPriority.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace NzbDrone.Core.Notifications.Ntfy
|
||||
{
|
||||
public enum NtfyPriority
|
||||
{
|
||||
Min = 1,
|
||||
Low = 2,
|
||||
Default = 3,
|
||||
High = 4,
|
||||
Max = 5
|
||||
}
|
||||
}
|
||||
138
src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs
Normal file
138
src/NzbDrone.Core/Notifications/Ntfy/NtfyProxy.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Ntfy
|
||||
{
|
||||
public interface INtfyProxy
|
||||
{
|
||||
void SendNotification(string title, string message, NtfySettings settings);
|
||||
|
||||
ValidationFailure Test(NtfySettings settings);
|
||||
}
|
||||
|
||||
public class NtfyProxy : INtfyProxy
|
||||
{
|
||||
private const string DEFAULT_PUSH_URL = "https://ntfy.sh";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NtfyProxy(IHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SendNotification(string title, string message, NtfySettings settings)
|
||||
{
|
||||
var error = false;
|
||||
|
||||
var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? NtfyProxy.DEFAULT_PUSH_URL : settings.ServerUrl;
|
||||
|
||||
foreach (var topic in settings.Topics)
|
||||
{
|
||||
var request = BuildTopicRequest(serverUrl, topic);
|
||||
|
||||
try
|
||||
{
|
||||
SendNotification(title, message, request, settings);
|
||||
}
|
||||
catch (NtfyException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message to {0}", topic);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
throw new NtfyException("Unable to send Ntfy notifications to all topics");
|
||||
}
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildTopicRequest(string serverUrl, string topic)
|
||||
{
|
||||
var trimServerUrl = serverUrl.TrimEnd('/');
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder($"{trimServerUrl}/{topic}").Post();
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
public ValidationFailure Test(NtfySettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
const string title = "Radarr - Test Notification";
|
||||
|
||||
const string body = "This is a test message from Radarr";
|
||||
|
||||
SendNotification(title, body, settings);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.Error(ex, "Authorization is required");
|
||||
return new ValidationFailure("UserName", "Authorization is required");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
return new ValidationFailure("ServerUrl", "Unable to send test message");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
return new ValidationFailure("", "Unable to send test message");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SendNotification(string title, string message, HttpRequestBuilder requestBuilder, NtfySettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
requestBuilder.Headers.Add("X-Title", title);
|
||||
requestBuilder.Headers.Add("X-Message", message);
|
||||
requestBuilder.Headers.Add("X-Priority", settings.Priority.ToString());
|
||||
|
||||
if (settings.Tags.Any())
|
||||
{
|
||||
requestBuilder.Headers.Add("X-Tags", settings.Tags.Join(","));
|
||||
}
|
||||
|
||||
if (!settings.ClickUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.Headers.Add("X-Click", settings.ClickUrl);
|
||||
}
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace())
|
||||
{
|
||||
request.Credentials = new BasicNetworkCredential(settings.UserName, settings.Password);
|
||||
}
|
||||
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.Error(ex, "Authorization is required");
|
||||
throw;
|
||||
}
|
||||
|
||||
throw new NtfyException("Unable to send text message: {0}", ex, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs
Normal file
63
src/NzbDrone.Core/Notifications/Ntfy/NtfySettings.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Ntfy
|
||||
{
|
||||
public class NtfySettingsValidator : AbstractValidator<NtfySettings>
|
||||
{
|
||||
public NtfySettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Topics).NotEmpty();
|
||||
RuleFor(c => c.Priority).InclusiveBetween(1, 5);
|
||||
RuleFor(c => c.ServerUrl).IsValidUrl().When(c => !c.ServerUrl.IsNullOrWhiteSpace());
|
||||
RuleFor(c => c.ClickUrl).IsValidUrl().When(c => !c.ClickUrl.IsNullOrWhiteSpace());
|
||||
RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace());
|
||||
RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace());
|
||||
RuleForEach(c => c.Topics).NotEmpty().Matches("[a-zA-Z0-9_-]+").Must(c => !InvalidTopics.Contains(c)).WithMessage("Invalid topic");
|
||||
}
|
||||
|
||||
private static List<string> InvalidTopics => new List<string> { "announcements", "app", "docs", "settings", "stats", "mytopic-rw", "mytopic-ro", "mytopic-wo" };
|
||||
}
|
||||
|
||||
public class NtfySettings : IProviderConfig
|
||||
{
|
||||
private static readonly NtfySettingsValidator Validator = new NtfySettingsValidator();
|
||||
|
||||
public NtfySettings()
|
||||
{
|
||||
Topics = Array.Empty<string>();
|
||||
Priority = 3;
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Server Url", Type = FieldType.Url, HelpLink = "https://ntfy.sh/docs/install/", HelpText = "Leave blank to use public server (https://ntfy.sh)", Placeholder = "https://ntfy.sh")]
|
||||
public string ServerUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))]
|
||||
public int Priority { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag, Placeholder = "Topic1234,Topic4321")]
|
||||
public IEnumerable<string> Topics { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", Placeholder = "warning,skull", HelpLink = "https://ntfy.sh/docs/emojis/")]
|
||||
public IEnumerable<string> Tags { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Click Url", Type = FieldType.Url, HelpText = "Optional link when user clicks notification", Placeholder = "https://myserver.example.com/radarr")]
|
||||
public string ClickUrl { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,20 +38,9 @@ namespace NzbDrone.Core.Organizer
|
||||
private readonly ICustomFormatService _formatService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9|]+))?(?<suffix>[- ._)\]]*)\}",
|
||||
private static readonly Regex TitleRegex = new Regex(@"(?<tag>\{(?:imdb-|edition-))?\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9|]+))?(?<suffix>[-} ._)\]]*)\}",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex TagsRegex = new Regex(@"(?<tags>\{tags(?:\:0+)?})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<absolute>{absolute(?:\:0+)?})(?<separator>[- ._]+?(?={))?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean|Original)?(Title|Filename)(The)?)(?::(?<customFormat>[a-z0-9|]+))?\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
@@ -61,11 +50,6 @@ namespace NzbDrone.Core.Organizer
|
||||
private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
//TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc)
|
||||
private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
|
||||
|
||||
private static readonly Regex TitlePrefixRegex = new Regex(@"^(The|An|A) (.*?)((?: *\([^)]+\))*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex ReservedDeviceNamesRegex = new Regex(@"^(?:aux|com[1-9]|con|lpt[1-9]|nul|prn)\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -133,7 +117,7 @@ namespace NzbDrone.Core.Organizer
|
||||
AddQualityTokens(tokenHandlers, movie, movieFile);
|
||||
AddMediaInfoTokens(tokenHandlers, movieFile);
|
||||
AddMovieFileTokens(tokenHandlers, movieFile);
|
||||
AddTagsTokens(tokenHandlers, movieFile);
|
||||
AddEditionTagsTokens(tokenHandlers, movieFile);
|
||||
AddCustomFormats(tokenHandlers, movie, movieFile, customFormats);
|
||||
|
||||
var splitPatterns = pattern.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -193,7 +177,7 @@ namespace NzbDrone.Core.Organizer
|
||||
AddQualityTokens(tokenHandlers, movie, movieFile);
|
||||
AddMediaInfoTokens(tokenHandlers, movieFile);
|
||||
AddMovieFileTokens(tokenHandlers, movieFile);
|
||||
AddTagsTokens(tokenHandlers, movieFile);
|
||||
AddEditionTagsTokens(tokenHandlers, movieFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -297,7 +281,7 @@ namespace NzbDrone.Core.Organizer
|
||||
return movie.Title;
|
||||
}
|
||||
|
||||
private void AddTagsTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)
|
||||
private void AddEditionTagsTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)
|
||||
{
|
||||
if (movieFile.Edition.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -497,6 +481,7 @@ namespace NzbDrone.Core.Organizer
|
||||
var tokenMatch = new TokenMatch
|
||||
{
|
||||
RegexMatch = match,
|
||||
Tag = match.Groups["tag"].Value,
|
||||
Prefix = match.Groups["prefix"].Value,
|
||||
Separator = match.Groups["separator"].Value,
|
||||
Suffix = match.Groups["suffix"].Value,
|
||||
@@ -531,7 +516,7 @@ namespace NzbDrone.Core.Organizer
|
||||
|
||||
if (!replacementText.IsNullOrWhiteSpace())
|
||||
{
|
||||
replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix;
|
||||
replacementText = tokenMatch.Tag + tokenMatch.Prefix + replacementText + tokenMatch.Suffix;
|
||||
}
|
||||
|
||||
return replacementText;
|
||||
@@ -598,6 +583,7 @@ namespace NzbDrone.Core.Organizer
|
||||
internal sealed class TokenMatch
|
||||
{
|
||||
public Match RegexMatch { get; set; }
|
||||
public string Tag { get; set; }
|
||||
public string Prefix { get; set; }
|
||||
public string Separator { get; set; }
|
||||
public string Suffix { get; set; }
|
||||
|
||||
@@ -42,7 +42,8 @@ namespace NzbDrone.Core.Parser
|
||||
new IsoLanguage("uk", "", "ukr", "Ukrainian", Language.Ukrainian),
|
||||
new IsoLanguage("fa", "", "fas", "Persian", Language.Persian),
|
||||
new IsoLanguage("be", "", "ben", "Bengali", Language.Bengali),
|
||||
new IsoLanguage("lt", "", "lit", "Lithuanian", Language.Lithuanian)
|
||||
new IsoLanguage("lt", "", "lit", "Lithuanian", Language.Lithuanian),
|
||||
new IsoLanguage("sk", "", "slk", "Slovak", Language.Slovak)
|
||||
};
|
||||
|
||||
public static IsoLanguage Find(string isoCode)
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex CaseSensitiveLanguageRegex = new Regex(@"(?:(?i)(?<!SUB[\W|_|^]))(?:(?<lithuanian>\bLT\b)|
|
||||
(?<czech>\bCZ\b)|
|
||||
(?<polish>\bPL\b)|
|
||||
(?<bulgarian>\bBG\b))(?:(?i)(?![\W|_|^]SUB))",
|
||||
(?<bulgarian>\bBG\b))(?:(?i)(?![\W|_|^]SUB))|
|
||||
(?<slovak>\bSK\b)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
private static readonly Regex SubtitleLanguageRegex = new Regex(".+?[-_. ](?<iso_code>[a-z]{2,3})(?:[-_. ]forced)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -182,6 +183,11 @@ namespace NzbDrone.Core.Parser
|
||||
languages.Add(Language.Bengali);
|
||||
}
|
||||
|
||||
if (lowerTitle.Contains("slovak"))
|
||||
{
|
||||
languages.Add(Language.Slovak);
|
||||
}
|
||||
|
||||
// Case sensitive
|
||||
var caseSensitiveMatch = CaseSensitiveLanguageRegex.Match(title);
|
||||
|
||||
@@ -205,6 +211,11 @@ namespace NzbDrone.Core.Parser
|
||||
languages.Add(Language.Bulgarian);
|
||||
}
|
||||
|
||||
if (caseSensitiveMatch.Groups["slovak"].Captures.Cast<Capture>().Any())
|
||||
{
|
||||
languages.Add(Language.Slovak);
|
||||
}
|
||||
|
||||
var matches = LanguageRegex.Matches(title);
|
||||
|
||||
foreach (Match match in matches)
|
||||
|
||||
@@ -147,11 +147,11 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
//Handle Exception Release Groups that don't follow -RlsGrp; Manual List
|
||||
//groups whose releases end with RlsGroup) or RlsGroup]
|
||||
private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<releasegroup>(Tigole|Joy|YIFY|YTS.MX|YTS.LT|FreetheFish|afm72|Anna|Bandi|Ghost|Kappa|MONOLITH|Qman|RZeroX|SAMPA|Silence|theincognito|t3nzin|Vyndros|HDO|DusIctv|DHD|SEV|CtrlHD|-ZR-|ADC|XZVN|RH|Kametsu|r00t)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<releasegroup>(Joy|YIFY|YTS.MX|YTS.LT|FreetheFish|afm72|Anna|Bandi|Ghost|Kappa|MONOLITH|Qman|RZeroX|SAMPA|Silence|theincognito|t3nzin|Vyndros|HDO|DusIctv|DHD|SEV|CtrlHD|-ZR-|ADC|XZVN|RH|Kametsu|r00t|HONE)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
//Handle Exception Release Groups that don't follow -RlsGrp; Manual List
|
||||
// name only...BE VERY CAREFUL WITH THIS, HIGH CHANCE OF FALSE POSITIVES
|
||||
private static readonly Regex ExceptionReleaseGroupRegexExact = new Regex(@"(?<releasegroup>KRaLiMaRKo|E\.N\.D|D\-Z0N3|Koten_Gars|BluDragon)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex ExceptionReleaseGroupRegexExact = new Regex(@"(?<releasegroup>KRaLiMaRKo|E\.N\.D|D\-Z0N3|Koten_Gars|BluDragon|ZØNEHD|Tigole)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|'|\|)+", RegexOptions.Compiled);
|
||||
private static readonly Regex SpecialCharRegex = new Regex(@"(\&|\:|\\|\/)+", RegexOptions.Compiled);
|
||||
@@ -401,7 +401,7 @@ namespace NzbDrone.Core.Parser
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ToUrlSlug(string value)
|
||||
public static string ToUrlSlug(string value, bool invalidDashReplacement = false, string trimEndChars = "-_", string deduplicateChars = "-_")
|
||||
{
|
||||
//First to lower case
|
||||
value = value.ToLowerInvariant();
|
||||
@@ -412,14 +412,23 @@ namespace NzbDrone.Core.Parser
|
||||
//Replace spaces
|
||||
value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled);
|
||||
|
||||
//Should invalid characters be replaced with dash or empty string?
|
||||
string replaceCharacter = invalidDashReplacement ? "-" : string.Empty;
|
||||
|
||||
//Remove invalid chars
|
||||
value = Regex.Replace(value, @"[^a-z0-9\s-_]", "", RegexOptions.Compiled);
|
||||
value = Regex.Replace(value, @"[^a-z0-9\s-_]", replaceCharacter, RegexOptions.Compiled);
|
||||
|
||||
//Trim dashes from end
|
||||
value = value.Trim('-', '_');
|
||||
//Trim dashes or underscores from end, or user defined character set
|
||||
if (!string.IsNullOrEmpty(trimEndChars))
|
||||
{
|
||||
value = value.Trim(trimEndChars.ToCharArray());
|
||||
}
|
||||
|
||||
//Replace double occurences of - or _
|
||||
value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled);
|
||||
//Replace double occurrences of - or _, or user defined character set
|
||||
if (!string.IsNullOrEmpty(deduplicateChars))
|
||||
{
|
||||
value = Regex.Replace(value, @"([" + deduplicateChars + "]){2,}", "$1", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Parser
|
||||
(?<scr>SCR|SCREENER|DVDSCR|DVDSCREENER)|
|
||||
(?<ts>TS[-_. ]|TELESYNC|HD-TS|HDTS|PDVD|TSRip|HDTSRip)|
|
||||
(?<tc>TC|TELECINE|HD-TC|HDTC)|
|
||||
(?<cam>CAMRIP|CAM|HDCAM|HD-CAM)|
|
||||
(?<cam>CAMRIP|CAM|HDCAM|HQCAM|HD-CAM)|
|
||||
(?<wp>WORKPRINT|WP)|
|
||||
(?<pdtv>PDTV)|
|
||||
(?<sdtv>SDTV)|
|
||||
|
||||
@@ -5,24 +5,24 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Equ" Version="2.3.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="3.3.2" />
|
||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.3" />
|
||||
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.0.1.93" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.2" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.5" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />
|
||||
|
||||
@@ -148,12 +148,18 @@ namespace NzbDrone.Core.RootFolders
|
||||
var possibleMovieFolders = _diskProvider.GetDirectories(path).ToList();
|
||||
var unmappedFolders = possibleMovieFolders.Except(moviePaths.Select(s => s.Value), PathEqualityComparer.Instance).ToList();
|
||||
|
||||
foreach (string unmappedFolder in unmappedFolders)
|
||||
var recycleBinPath = _configService.RecycleBin;
|
||||
|
||||
foreach (var unmappedFolder in unmappedFolders)
|
||||
{
|
||||
var di = new DirectoryInfo(unmappedFolder.Normalize());
|
||||
|
||||
if ((!di.Attributes.HasFlag(FileAttributes.System) && !di.Attributes.HasFlag(FileAttributes.Hidden)) || di.Attributes.ToString() == "-1")
|
||||
{
|
||||
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
|
||||
if (string.IsNullOrWhiteSpace(recycleBinPath) || di.FullName.PathNotEquals(recycleBinPath))
|
||||
{
|
||||
results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,8 @@ namespace NzbDrone.Host
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["dataProtectionFolder"]));
|
||||
|
||||
services.AddSingleton<IAuthorizationPolicyProvider, UiAuthorizationPolicyProvider>();
|
||||
services.AddSingleton<IAuthorizationHandler, UiAuthorizationHandler>();
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("SignalR", policy =>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.8" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
||||
@@ -37,12 +37,12 @@ namespace NzbDrone.Test.Common
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
public void Start(bool enableAuth = false)
|
||||
{
|
||||
AppData = Path.Combine(TestContext.CurrentContext.TestDirectory, "_intg_" + TestBase.GetUID());
|
||||
Directory.CreateDirectory(AppData);
|
||||
|
||||
GenerateConfigFile();
|
||||
GenerateConfigFile(enableAuth);
|
||||
|
||||
string consoleExe;
|
||||
if (OsInfo.IsWindows)
|
||||
@@ -166,7 +166,7 @@ namespace NzbDrone.Test.Common
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateConfigFile()
|
||||
private void GenerateConfigFile(bool enableAuth)
|
||||
{
|
||||
var configFile = Path.Combine(AppData, "config.xml");
|
||||
|
||||
@@ -179,6 +179,8 @@ namespace NzbDrone.Test.Common
|
||||
new XElement(nameof(ConfigFileProvider.ApiKey), apiKey),
|
||||
new XElement(nameof(ConfigFileProvider.LogLevel), "trace"),
|
||||
new XElement(nameof(ConfigFileProvider.AnalyticsEnabled), false),
|
||||
new XElement(nameof(ConfigFileProvider.AuthenticationMethod), enableAuth ? "Forms" : "None"),
|
||||
new XElement(nameof(ConfigFileProvider.AuthenticationRequired), "DisabledForLocalAddresses"),
|
||||
new XElement(nameof(ConfigFileProvider.Port), Port)));
|
||||
|
||||
var data = xDoc.ToString();
|
||||
|
||||
@@ -53,11 +53,25 @@ namespace Radarr.Api.V3.Collections
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public List<CollectionResource> GetCollections()
|
||||
public List<CollectionResource> GetCollections(int? tmdbId)
|
||||
{
|
||||
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
|
||||
var collectionResources = new List<CollectionResource>();
|
||||
|
||||
return MapToResource(_collectionService.GetAllCollections(), collectionMovies).ToList();
|
||||
if (tmdbId.HasValue)
|
||||
{
|
||||
var collection = _collectionService.FindByTmdbId(tmdbId.Value);
|
||||
|
||||
if (collection != null)
|
||||
{
|
||||
collectionResources.AddIfNotNull(MapToResource(collection));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionResources = MapToResource(_collectionService.GetAllCollections()).ToList();
|
||||
}
|
||||
|
||||
return collectionResources;
|
||||
}
|
||||
|
||||
[RestPutById]
|
||||
@@ -116,10 +130,11 @@ namespace Radarr.Api.V3.Collections
|
||||
return Accepted(updated);
|
||||
}
|
||||
|
||||
private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> collections, List<MovieMetadata> collectionMovies)
|
||||
private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> collections)
|
||||
{
|
||||
// Avoid calling for naming spec on every movie in filenamebuilder
|
||||
var namingConfig = _namingService.GetConfig();
|
||||
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
@@ -167,7 +182,7 @@ namespace Radarr.Api.V3.Collections
|
||||
[NonAction]
|
||||
public void Handle(CollectionDeletedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Deleted, MapToResource(message.Collection));
|
||||
BroadcastResourceChange(ModelAction.Deleted, message.Collection.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Radarr.Api.V3.Config
|
||||
public bool EnableSsl { get; set; }
|
||||
public bool LaunchBrowser { get; set; }
|
||||
public AuthenticationType AuthenticationMethod { get; set; }
|
||||
public AuthenticationRequiredType AuthenticationRequired { get; set; }
|
||||
public bool AnalyticsEnabled { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
@@ -56,6 +57,7 @@ namespace Radarr.Api.V3.Config
|
||||
EnableSsl = model.EnableSsl,
|
||||
LaunchBrowser = model.LaunchBrowser,
|
||||
AuthenticationMethod = model.AuthenticationMethod,
|
||||
AuthenticationRequired = model.AuthenticationRequired,
|
||||
AnalyticsEnabled = model.AnalyticsEnabled,
|
||||
|
||||
//Username
|
||||
|
||||
@@ -2,10 +2,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Validation;
|
||||
using Radarr.Http;
|
||||
using Radarr.Http.REST;
|
||||
using Radarr.Http.REST.Attributes;
|
||||
@@ -50,6 +52,9 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource)
|
||||
{
|
||||
var model = customFormatResource.ToModel(_specifications);
|
||||
|
||||
Validate(model);
|
||||
|
||||
return Created(_formatService.Insert(model).Id);
|
||||
}
|
||||
|
||||
@@ -57,6 +62,9 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
public ActionResult<CustomFormatResource> Update(CustomFormatResource resource)
|
||||
{
|
||||
var model = resource.ToModel(_specifications);
|
||||
|
||||
Validate(model);
|
||||
|
||||
_formatService.Update(model);
|
||||
|
||||
return Accepted(model.Id);
|
||||
@@ -89,6 +97,25 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
return schema;
|
||||
}
|
||||
|
||||
private void Validate(CustomFormat definition)
|
||||
{
|
||||
foreach (var spec in definition.Specifications)
|
||||
{
|
||||
var validationResult = spec.Validate();
|
||||
VerifyValidationResult(validationResult);
|
||||
}
|
||||
}
|
||||
|
||||
protected void VerifyValidationResult(ValidationResult validationResult)
|
||||
{
|
||||
var result = new NzbDroneValidationResult(validationResult.Errors);
|
||||
|
||||
if (!result.IsValid)
|
||||
{
|
||||
throw new ValidationException(result.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ICustomFormatSpecification> GetPresets()
|
||||
{
|
||||
yield return new ReleaseTitleSpecification
|
||||
|
||||
@@ -11,6 +11,7 @@ using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using Radarr.Api.V3.Movies;
|
||||
using Radarr.Http;
|
||||
|
||||
namespace Radarr.Api.V3.ImportLists
|
||||
@@ -19,6 +20,7 @@ namespace Radarr.Api.V3.ImportLists
|
||||
public class ImportListMoviesController : Controller
|
||||
{
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IAddMovieService _addMovieService;
|
||||
private readonly IProvideMovieInfo _movieInfo;
|
||||
private readonly IBuildFileNames _fileNameBuilder;
|
||||
private readonly IImportListMovieService _listMovieService;
|
||||
@@ -28,6 +30,7 @@ namespace Radarr.Api.V3.ImportLists
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
public ImportListMoviesController(IMovieService movieService,
|
||||
IAddMovieService addMovieService,
|
||||
IProvideMovieInfo movieInfo,
|
||||
IBuildFileNames fileNameBuilder,
|
||||
IImportListMovieService listMovieService,
|
||||
@@ -37,6 +40,7 @@ namespace Radarr.Api.V3.ImportLists
|
||||
IConfigService configService)
|
||||
{
|
||||
_movieService = movieService;
|
||||
_addMovieService = addMovieService;
|
||||
_movieInfo = movieInfo;
|
||||
_fileNameBuilder = fileNameBuilder;
|
||||
_listMovieService = listMovieService;
|
||||
@@ -92,6 +96,14 @@ namespace Radarr.Api.V3.ImportLists
|
||||
return realResults;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public object AddMovies([FromBody] List<MovieResource> resource)
|
||||
{
|
||||
var newMovies = resource.ToModel();
|
||||
|
||||
return _addMovieService.AddMovies(newMovies, true).ToResource(0);
|
||||
}
|
||||
|
||||
private IEnumerable<ImportListMoviesResource> MapToResource(IEnumerable<Movie> movies, Language language)
|
||||
{
|
||||
//Avoid calling for naming spec on every movie in filenamebuilder
|
||||
|
||||
@@ -94,6 +94,8 @@ namespace Radarr.Api.V3.Movies
|
||||
var translatedTitle = movieTranslation?.Title ?? model.Title;
|
||||
var translatedOverview = movieTranslation?.Overview ?? model.MovieMetadata.Value.Overview;
|
||||
|
||||
var collection = model.MovieMetadata.Value.CollectionTmdbId > 0 ? new MovieCollection { Title = model.MovieMetadata.Value.CollectionTitle, TmdbId = model.MovieMetadata.Value.CollectionTmdbId } : null;
|
||||
|
||||
return new MovieResource
|
||||
{
|
||||
Id = model.Id,
|
||||
@@ -141,7 +143,7 @@ namespace Radarr.Api.V3.Movies
|
||||
MovieFile = movieFile,
|
||||
YouTubeTrailerId = model.MovieMetadata.Value.YouTubeTrailerId,
|
||||
Studio = model.MovieMetadata.Value.Studio,
|
||||
Collection = new MovieCollection { Title = model.MovieMetadata.Value.CollectionTitle, TmdbId = model.MovieMetadata.Value.CollectionTmdbId },
|
||||
Collection = collection,
|
||||
Popularity = model.MovieMetadata.Value.Popularity
|
||||
};
|
||||
}
|
||||
|
||||
@@ -589,6 +589,16 @@
|
||||
"tags": [
|
||||
"Collection"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tmdbId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
@@ -3315,6 +3325,44 @@
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"ImportListMovies"
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MovieResource"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MovieResource"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MovieResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/indexer": {
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Radarr.Http.Authentication
|
||||
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
public const string DefaultScheme = "API Key";
|
||||
|
||||
public string Scheme => DefaultScheme;
|
||||
public string AuthenticationType = DefaultScheme;
|
||||
|
||||
|
||||
@@ -22,10 +22,16 @@ namespace Radarr.Http.Authentication
|
||||
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { });
|
||||
}
|
||||
|
||||
public static AuthenticationBuilder AddExternal(this AuthenticationBuilder authenticationBuilder, string name)
|
||||
{
|
||||
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { });
|
||||
}
|
||||
|
||||
public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services)
|
||||
{
|
||||
return services.AddAuthentication()
|
||||
.AddNone(AuthenticationType.None.ToString())
|
||||
.AddExternal(AuthenticationType.External.ToString())
|
||||
.AddBasic(AuthenticationType.Basic.ToString())
|
||||
.AddCookie(AuthenticationType.Forms.ToString(), options =>
|
||||
{
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace NzbDrone.Http.Authentication
|
||||
if (policyName.Equals(POLICY_NAME, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var policy = new AuthorizationPolicyBuilder(_config.AuthenticationMethod.ToString())
|
||||
.RequireAuthenticatedUser();
|
||||
.AddRequirements(new BypassableDenyAnonymousAuthorizationRequirement());
|
||||
|
||||
return Task.FromResult(policy.Build());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace NzbDrone.Http.Authentication
|
||||
{
|
||||
public class BypassableDenyAnonymousAuthorizationRequirement : DenyAnonymousAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user