1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-13 15:34:56 -04:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Qstick
74382d7250 New: Rework List Exclusion UI 2022-07-05 23:10:47 -05:00
Qstick
6659bc034c Remove general yarn restore key to avoid cross OS conflict 2022-07-05 17:15:52 -05:00
117 changed files with 1604 additions and 4794 deletions

View File

@@ -1,6 +1,7 @@
{
"paths": [
"frontend/src/**/*.js"
"frontend/src/**/*.js",
"src/NzbDrone.Core/Localization/Core/*.json"
],
"ignored": [
"**/node_modules/**/*"

View File

@@ -5,9 +5,9 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing open and closed issues
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
@@ -42,14 +42,12 @@ body:
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
- **Database**: Sqlite 3.36.0
value: |
- OS:
- Radarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
- Database:
render: markdown
validations:
required: true

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.2.3'
majorVersion: '4.2.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.400'
dotnetVersion: '6.0.300'
nodeVersion: '16.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
@@ -549,7 +549,7 @@ stages:
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
vmImage: 'ubuntu-18.04'
timeoutInMinutes: 10
@@ -576,7 +576,6 @@ 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 +686,7 @@ stages:
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
vmImage: 'ubuntu-18.04'
steps:
- task: UseDotNet@2
@@ -722,7 +721,6 @@ 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: |

View File

@@ -181,12 +181,13 @@ class Blocklist extends Component {
>
<TableBody>
{
items.map((item) => {
items.map((item, index) => {
return (
<BlocklistRowConnector
key={item.id}
isSelected={selectedState[item.id] || false}
columns={columns}
index={index}
{...item}
onSelectedChange={this.onSelectedChange}
/>

View File

@@ -66,8 +66,7 @@ class CollectionFooter extends Component {
monitor,
monitored,
qualityProfileId,
minimumAvailability,
rootFolderPath
minimumAvailability
} = this.state;
const changes = {};
@@ -88,10 +87,6 @@ class CollectionFooter extends Component {
changes.minimumAvailability = minimumAvailability;
}
if (rootFolderPath !== NO_CHANGE) {
changes.rootFolderPath = rootFolderPath;
}
this.props.onUpdateSelectedPress(changes);
};

View File

@@ -21,7 +21,6 @@ function HostSettings(props) {
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
@@ -91,21 +90,6 @@ function HostSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -15,12 +15,13 @@
.tmdbId,
.movieYear {
flex: 0 0 70px;
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
}
.actions {
display: flex;
justify-content: flex-end;
flex: 1 0 auto;
padding-right: 10px;
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 70px;
}

View File

@@ -1,13 +1,16 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportListExclusion.css';
import IconButton from 'Components/Link/IconButton';
class ImportListExclusion extends Component {
@@ -55,28 +58,82 @@ class ImportListExclusion extends Component {
render() {
const {
id,
isSelected,
onSelectedChange,
columns,
movieTitle,
tmdbId,
movieYear
} = this.props;
return (
<div
className={classNames(
styles.importExclusion
)}
>
<div className={styles.tmdbId}>{tmdbId}</div>
<div className={styles.movieTitle}>{movieTitle}</div>
<div className={styles.movieYear}>{movieYear}</div>
<TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
<div className={styles.actions}>
<Link
onPress={this.onEditImportExclusionPress}
>
<Icon name={icons.EDIT} />
</Link>
</div>
{
columns.map((column) => {
const {
name,
isVisible
} = column;
if (!isVisible) {
return null;
}
if (name === 'tmdbId') {
return (
<TableRowCell key={name}>
{tmdbId}
</TableRowCell>
);
}
if (name === 'movieTitle') {
return (
<TableRowCell key={name}>
{movieTitle}
</TableRowCell>
);
}
if (name === 'movieYear') {
return (
<TableRowCell key={name}>
{movieYear}
</TableRowCell>
);
}
if (name === 'actions') {
return (
<TableRowCell
key={name}
className={styles.actions}
>
<IconButton
title={translate('RemoveFromBlocklist')}
name={icons.EDIT}
onPress={this.onEditImportExclusionPress}
/>
<IconButton
title={translate('RemoveFromBlocklist')}
name={icons.REMOVE}
kind={kinds.DANGER}
onPress={this.onDeleteImportExclusionPress}
/>
</TableRowCell>
);
}
return null;
})
}
<EditImportListExclusionModalConnector
id={id}
@@ -94,7 +151,7 @@ class ImportListExclusion extends Component {
onConfirm={this.onConfirmDeleteImportExclusion}
onCancel={this.onDeleteImportExclusionModalClose}
/>
</div>
</TableRow>
);
}
}
@@ -104,6 +161,9 @@ ImportListExclusion.propTypes = {
movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired,
movieYear: PropTypes.number.isRequired,
isSelected: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};

View File

@@ -4,8 +4,12 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion';
import styles from './ImportListExclusions.css';
@@ -19,6 +23,10 @@ class ImportListExclusions extends Component {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {},
isAddImportExclusionModalOpen: false
};
}
@@ -26,6 +34,16 @@ class ImportListExclusions extends Component {
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
};
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
};
onAddImportExclusionPress = () => {
this.setState({ isAddImportExclusionModalOpen: true });
};
@@ -41,41 +59,50 @@ class ImportListExclusions extends Component {
const {
items,
onConfirmDeleteImportExclusion,
columns,
...otherProps
} = this.props;
const {
allSelected,
allUnselected,
selectedState
} = this.state;
return (
<FieldSet legend={translate('ListExclusions')}>
<PageSectionContent
errorMessage={translate('UnableToLoadListExclusions')}
{...otherProps}
>
<div className={styles.importListExclusionsHeader}>
<div className={styles.tmdbId}>
TMDb Id
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
</div>
<div>
{
items.map((item, index) => {
return (
<ImportListExclusion
key={item.id}
{...item}
{...otherProps}
index={index}
onConfirmDeleteImportExclusion={onConfirmDeleteImportExclusion}
/>
);
})
}
<Table
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns}
{...otherProps}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>
{
items.map((item, index) => {
return (
<ImportListExclusion
key={item.id}
isSelected={selectedState[item.id] || false}
{...item}
{...otherProps}
columns={columns}
index={index}
onSelectedChange={this.onSelectedChange}
onConfirmDeleteImportExclusion={onConfirmDeleteImportExclusion}
/>
);
})
}
</TableBody>
</Table>
</div>
<div className={styles.addImportExclusion}>
@@ -101,6 +128,7 @@ class ImportListExclusions extends Component {
ImportListExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};

View File

@@ -4,6 +4,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
import translate from 'Utilities/String/translate';
//
// Variables
@@ -48,7 +49,34 @@ export default {
items: [],
isSaving: false,
saveError: null,
pendingChanges: {}
pendingChanges: {},
columns: [
{
name: 'tmdbId',
label: 'TmdbId',
isSortable: true,
isVisible: true
},
{
name: 'movieTitle',
label: translate('Title'),
isSortable: true,
isVisible: true
},
{
name: 'movieYear',
label: translate('Year'),
isSortable: true,
isVisible: true
},
{
name: 'actions',
columnLabel: translate('Actions'),
isVisible: true,
isModifiable: false
}
]
},
//

View File

@@ -262,10 +262,10 @@ export const defaultState = {
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const collectionList = items.reduce((acc, movie) => {
if (movie.collection && movie.collection.title) {
if (movie.collection) {
acc.push({
id: movie.collection.title,
name: movie.collection.title
id: movie.collection.name,
name: movie.collection.name
});
}
@@ -561,7 +561,7 @@ export const actionHandlers = handleThunks({
}, []);
const promise = createAjaxRequest({
url: '/importlist/movie',
url: '/movie/import',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(allNewMovies)

View File

@@ -116,7 +116,7 @@ export const filterPredicates = {
const predicate = filterTypePredicates[type];
const { collection } = item;
return predicate(collection && collection.title ? collection.title : '', filterValue);
return predicate(collection ? collection.name : '', filterValue);
},
originalLanguage: function(item, filterValue, type) {

View File

@@ -1,12 +1,10 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, 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';
@@ -65,81 +63,19 @@ export const defaultState = {
}
],
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);
}
},
filterPredicates: {},
filterBuilderProps: [
{
name: 'title',
label: translate('Title'),
label: 'Title',
type: filterBuilderTypes.STRING
},
{
name: 'monitored',
label: translate('Monitored'),
label: '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
}
]
};

View File

@@ -165,11 +165,11 @@ export const actionHandlers = handleThunks({
requestData.quality = quality;
}
if (releaseGroup !== undefined) {
if (releaseGroup) {
requestData.releaseGroup = releaseGroup;
}
if (edition !== undefined) {
if (edition) {
requestData.edition = edition;
}
@@ -201,11 +201,11 @@ export const actionHandlers = handleThunks({
props.quality = quality;
}
if (edition !== undefined) {
if (edition) {
props.edition = edition;
}
if (releaseGroup !== undefined) {
if (releaseGroup) {
props.releaseGroup = releaseGroup;
}

View File

@@ -227,7 +227,7 @@ export const defaultState = {
collection: function(item) {
const { collection ={} } = item;
return collection.title;
return collection.name;
},
originalLanguage: function(item) {
@@ -339,10 +339,10 @@ export const defaultState = {
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const collectionList = items.reduce((acc, movie) => {
if (movie.collection && movie.collection.title) {
if (movie.collection) {
acc.push({
id: movie.collection.title,
name: movie.collection.title
id: movie.collection.name,
name: movie.collection.name
});
}

View File

@@ -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.8",
"@microsoft/signalr": "6.0.5",
"@sentry/browser": "6.18.2",
"@sentry/integrations": "6.18.2",
"classnames": "2.3.1",

View File

@@ -90,7 +90,7 @@
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />

View File

@@ -8,6 +8,5 @@
<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>

View File

@@ -64,7 +64,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
@@ -85,24 +84,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
}
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
public void should_keep_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
cleansedMessage.Should().Contain("shouldkeep1");
cleansedMessage.Should().Contain("shouldkeep2");
cleansedMessage.Should().Contain("shouldkeep3");
}
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
[TestCase(@"Auth-Success ip 32.2.3.5")]

View File

@@ -20,21 +20,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
private static Exception[] FilteredExceptions = new Exception[]
{
new UnauthorizedAccessException(),
new AggregateException(new Exception[]
{
new UnauthorizedAccessException(),
new UnauthorizedAccessException()
})
};
private static Exception[] NonFilteredExceptions = new Exception[]
{
new AggregateException(new Exception[]
{
new UnauthorizedAccessException(),
new NotImplementedException()
})
new UnauthorizedAccessException()
};
[SetUp]
@@ -77,14 +63,6 @@ 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)

View File

@@ -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(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!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"),

View File

@@ -229,48 +229,21 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
if (FilterEvents)
{
var exceptions = new List<Exception>();
var aggEx = logEvent.Exception as AggregateException;
if (aggEx != null && aggEx.InnerExceptions.Count > 0)
var sqlEx = logEvent.Exception as SQLiteException;
if (sqlEx != null && FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
{
exceptions.AddRange(aggEx.InnerExceptions);
}
else
{
exceptions.Add(logEvent.Exception);
return false;
}
// If any are sentry then send to sentry
foreach (var ex in exceptions)
if (FilteredExceptionTypeNames.Contains(logEvent.Exception.GetType().Name))
{
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;
}
return false;
}
// The exception or aggregate exception children were not sentry exceptions
return false;
if (FilteredExceptionMessages.Any(x => logEvent.Exception.Message.Contains(x)))
{
return false;
}
}
return true;

View File

@@ -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.20.1" />
<PackageReference Include="Sentry" Version="3.15.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.5" />
<PackageReference Include="System.Text.Json" Version="6.0.4" />
<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" />

View File

@@ -27,20 +27,6 @@ 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()
{

View File

@@ -1,245 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras
{
[TestFixture]
public class ExtraServiceFixture : CoreTest<ExtraService>
{
private Movie _movie;
private MovieFile _movieFile;
private LocalMovie _localMovie;
private string _movieFolder;
private string _releaseFolder;
private Mock<IManageExtraFiles> _subtitleService;
private Mock<IManageExtraFiles> _otherExtraService;
[SetUp]
public void Setup()
{
_movieFolder = @"C:\Test\Movies\Movie Title".AsOsAgnostic();
_releaseFolder = @"C:\Test\Unsorted TV\Movie.Title.2022".AsOsAgnostic();
_movie = Builder<Movie>.CreateNew()
.With(s => s.Path = _movieFolder)
.Build();
_movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = Path.Combine(_movie.Path, "Movie Title - 2022.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Movie Title - 2022.mkv".AsOsAgnostic())
.Build();
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _movie)
.With(l => l.Path = Path.Combine(_releaseFolder, "Movie.Title.2022.mkv").AsOsAgnostic())
.Build();
_subtitleService = new Mock<IManageExtraFiles>();
_subtitleService.SetupGet(s => s.Order).Returns(0);
_subtitleService.Setup(s => s.CanImportFile(It.IsAny<LocalMovie>(), It.IsAny<MovieFile>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Returns(false);
_subtitleService.Setup(s => s.CanImportFile(It.IsAny<LocalMovie>(), It.IsAny<MovieFile>(), It.IsAny<string>(), ".srt", It.IsAny<bool>()))
.Returns(true);
_otherExtraService = new Mock<IManageExtraFiles>();
_otherExtraService.SetupGet(s => s.Order).Returns(1);
_otherExtraService.Setup(s => s.CanImportFile(It.IsAny<LocalMovie>(), It.IsAny<MovieFile>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
.Returns(true);
Mocker.SetConstant<IEnumerable<IManageExtraFiles>>(new[]
{
_subtitleService.Object,
_otherExtraService.Object
});
Mocker.GetMock<IDiskProvider>().Setup(s => s.FolderExists(It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
WithExistingFolder(_movie.Path);
WithExistingFile(_movieFile.Path);
WithExistingFile(_localMovie.Path);
Mocker.GetMock<IConfigService>().Setup(v => v.ImportExtraFiles).Returns(true);
Mocker.GetMock<IConfigService>().Setup(v => v.ExtraFileExtensions).Returns("nfo,srt");
}
private void WithExistingFolder(string path, bool exists = true)
{
var dir = Path.GetDirectoryName(path);
if (exists && dir.IsNotNullOrWhiteSpace())
{
WithExistingFolder(dir);
}
Mocker.GetMock<IDiskProvider>().Setup(v => v.FolderExists(path)).Returns(exists);
}
private void WithExistingFile(string path, bool exists = true, int size = 1000)
{
var dir = Path.GetDirectoryName(path);
if (exists && dir.IsNotNullOrWhiteSpace())
{
WithExistingFolder(dir);
}
Mocker.GetMock<IDiskProvider>().Setup(v => v.FileExists(path)).Returns(exists);
Mocker.GetMock<IDiskProvider>().Setup(v => v.GetFileSize(path)).Returns(size);
}
private void WithExistingFiles(List<string> files)
{
foreach (string file in files)
{
WithExistingFile(file);
}
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetFiles(_releaseFolder, It.IsAny<SearchOption>()))
.Returns(files.ToArray());
}
[Test]
public void should_not_pass_file_if_import_disabled()
{
Mocker.GetMock<IConfigService>().Setup(v => v.ImportExtraFiles).Returns(false);
var nfofile = Path.Combine(_releaseFolder, "Movie.Title.2022.nfo").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
nfofile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
_otherExtraService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
}
[Test]
[TestCase("Movie Title - 2022.sub")]
[TestCase("Movie Title - 2022.ass")]
public void should_not_pass_unwanted_file(string filePath)
{
Mocker.GetMock<IConfigService>().Setup(v => v.ImportExtraFiles).Returns(false);
var nfofile = Path.Combine(_releaseFolder, filePath).AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
nfofile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
_otherExtraService.Verify(v => v.CanImportFile(_localMovie, _movieFile, It.IsAny<string>(), It.IsAny<string>(), true), Times.Never());
}
[Test]
public void should_pass_subtitle_file_to_subtitle_service()
{
var subtitleFile = Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
subtitleFile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { subtitleFile }, true), Times.Once());
_otherExtraService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { subtitleFile }, true), Times.Never());
}
[Test]
public void should_pass_nfo_file_to_other_service()
{
var nfofile = Path.Combine(_releaseFolder, "Movie.Title.2022.nfo").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
nfofile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
_subtitleService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { nfofile }, true), Times.Never());
_otherExtraService.Verify(v => v.ImportFiles(_localMovie, _movieFile, new List<string> { nfofile }, true), Times.Once());
}
[Test]
public void should_search_subtitles_when_importing_from_job_folder()
{
_localMovie.FolderMovieInfo = new ParsedMovieInfo();
var subtitleFile = Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
subtitleFile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.AllDirectories), Times.Once);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.TopDirectoryOnly), Times.Never);
}
[Test]
public void should_not_search_subtitles_when_not_importing_from_job_folder()
{
_localMovie.FolderMovieInfo = null;
var subtitleFile = Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic();
var files = new List<string>
{
_localMovie.Path,
subtitleFile
};
WithExistingFiles(files);
Subject.ImportMovie(_localMovie, _movieFile, true);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.AllDirectories), Times.Never);
Mocker.GetMock<IDiskProvider>().Verify(v => v.GetFiles(_releaseFolder, SearchOption.TopDirectoryOnly), Times.Once);
}
}
}

View File

@@ -1,84 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras.Others
{
[TestFixture]
public class OtherExtraServiceFixture : CoreTest<OtherExtraService>
{
private Movie _movie;
private MovieFile _movieFile;
private LocalMovie _localMovie;
private string _movieFolder;
private string _releaseFolder;
[SetUp]
public void Setup()
{
_movieFolder = @"C:\Test\Movies\Movie Title".AsOsAgnostic();
_releaseFolder = @"C:\Test\Unsorted Movies\Movie.Title.2022".AsOsAgnostic();
_movie = Builder<Movie>.CreateNew()
.With(s => s.Path = _movieFolder)
.Build();
_movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = Path.Combine(_movie.Path, "Movie Title - 2022.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Movie Title - 2022.mkv")
.Build();
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _movie)
.With(l => l.Path = Path.Combine(_releaseFolder, "Movie.Title.2022.mkv").AsOsAgnostic())
.With(l => l.FileMovieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string> { "Movie Title" },
Year = 2022
})
.Build();
}
[Test]
[TestCase("Movie Title - 2022.nfo", "Movie Title - 2022.nfo")]
[TestCase("Movie.Title.2022.nfo", "Movie Title - 2022.nfo")]
[TestCase("Movie Title 2022.nfo", "Movie Title - 2022.nfo")]
[TestCase("Movie_Title_2022.nfo", "Movie Title - 2022.nfo")]
[TestCase(@"Movie.Title.2022\thumb.jpg", "Movie Title - 2022.jpg")]
public void should_import_matching_file(string filePath, string expectedOutputPath)
{
var files = new List<string> { Path.Combine(_releaseFolder, filePath).AsOsAgnostic() };
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(1);
results[0].RelativePath.AsOsAgnostic().PathEquals(expectedOutputPath.AsOsAgnostic()).Should().Be(true);
}
[Test]
public void should_not_import_multiple_nfo_files()
{
var files = new List<string>
{
Path.Combine(_releaseFolder, "Movie.Title.2022.nfo").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie_Title_2022.nfo").AsOsAgnostic(),
};
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(1);
}
}
}

View File

@@ -1,179 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MovieImport;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Extras.Subtitles
{
[TestFixture]
public class SubtitleServiceFixture : CoreTest<SubtitleService>
{
private Movie _movie;
private MovieFile _movieFile;
private LocalMovie _localMovie;
private string _MovieFolder;
private string _releaseFolder;
[SetUp]
public void Setup()
{
_MovieFolder = @"C:\Test\Movies\Movie Title".AsOsAgnostic();
_releaseFolder = @"C:\Test\Unsorted Movies\Movie.Title.2022".AsOsAgnostic();
_movie = Builder<Movie>.CreateNew()
.With(s => s.Path = _MovieFolder)
.Build();
_movieFile = Builder<MovieFile>.CreateNew()
.With(f => f.Path = Path.Combine(_movie.Path, "Movie Title - 2022.mkv").AsOsAgnostic())
.With(f => f.RelativePath = @"Movie Title - 2022.mkv".AsOsAgnostic())
.Build();
_localMovie = Builder<LocalMovie>.CreateNew()
.With(l => l.Movie = _movie)
.With(l => l.Path = Path.Combine(_releaseFolder, "Movie.Title.2022.mkv").AsOsAgnostic())
.With(l => l.FileMovieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string> { "Movie Title" },
Year = 2022
})
.Build();
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
Mocker.GetMock<IDetectSample>().Setup(s => s.IsSample(It.IsAny<MovieMetadata>(), It.IsAny<string>()))
.Returns(DetectSampleResult.NotSample);
}
[Test]
[TestCase("Movie.Title.2022.en.nfo")]
public void should_not_import_non_subtitle_file(string filePath)
{
var files = new List<string> { Path.Combine(_releaseFolder, filePath).AsOsAgnostic() };
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(0);
}
[Test]
[TestCase("Movie Title - 2022.srt", "Movie Title - 2022.srt")]
[TestCase("Movie.Title.2022.en.srt", "Movie Title - 2022.en.srt")]
[TestCase("Movie.Title.2022.english.srt", "Movie Title - 2022.en.srt")]
[TestCase("Movie Title 2022_en_sdh_forced.srt", "Movie Title - 2022.en.sdh.forced.srt")]
[TestCase("Movie_Title_2022 en.srt", "Movie Title - 2022.en.srt")]
[TestCase(@"Subs\Movie.Title.2022\2_en.srt", "Movie Title - 2022.en.srt")]
[TestCase("sub.srt", "Movie Title - 2022.srt")]
public void should_import_matching_subtitle_file(string filePath, string expectedOutputPath)
{
var files = new List<string> { Path.Combine(_releaseFolder, filePath).AsOsAgnostic() };
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(1);
results[0].RelativePath.AsOsAgnostic().PathEquals(expectedOutputPath.AsOsAgnostic()).Should().Be(true);
}
[Test]
public void should_import_multiple_subtitle_files_per_language()
{
var files = new List<string>
{
Path.Combine(_releaseFolder, "Movie.Title.2022.en.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.eng.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Subs", "Movie_Title_2022_en_forced.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Subs", "Movie.Title.2022", "2_fr.srt").AsOsAgnostic()
};
var expectedOutputs = new string[]
{
"Movie Title - 2022.1.en.srt",
"Movie Title - 2022.2.en.srt",
"Movie Title - 2022.en.forced.srt",
"Movie Title - 2022.fr.srt",
};
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(expectedOutputs.Length);
for (int i = 0; i < expectedOutputs.Length; i++)
{
results[i].RelativePath.AsOsAgnostic().PathEquals(expectedOutputs[i].AsOsAgnostic()).Should().Be(true);
}
}
[Test]
public void should_import_multiple_subtitle_files_per_language_with_tags()
{
var files = new List<string>
{
Path.Combine(_releaseFolder, "Movie.Title.2022.en.forced.cc.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.other.en.forced.cc.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.en.forced.sdh.srt").AsOsAgnostic(),
Path.Combine(_releaseFolder, "Movie.Title.2022.en.forced.default.srt").AsOsAgnostic(),
};
var expectedOutputs = new[]
{
"Movie Title - 2022.1.en.forced.cc.srt",
"Movie Title - 2022.2.en.forced.cc.srt",
"Movie Title - 2022.en.forced.sdh.srt",
"Movie Title - 2022.en.forced.default.srt"
};
var results = Subject.ImportFiles(_localMovie, _movieFile, files, true).ToList();
results.Count().Should().Be(expectedOutputs.Length);
for (int i = 0; i < expectedOutputs.Length; i++)
{
results[i].RelativePath.AsOsAgnostic().PathEquals(expectedOutputs[i].AsOsAgnostic()).Should().Be(true);
}
}
[Test]
[TestCase(@"Subs\2_en.srt", "Movie Title - 2022.en.srt")]
public void should_import_unmatching_subtitle_file_if_only_episode(string filePath, string expectedOutputPath)
{
var subtitleFile = Path.Combine(_releaseFolder, filePath).AsOsAgnostic();
var sampleFile = Path.Combine(_movie.Path, "Movie Title - 2022.sample.mkv").AsOsAgnostic();
var videoFiles = new string[]
{
_localMovie.Path,
sampleFile
};
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
.Returns(videoFiles);
Mocker.GetMock<IDetectSample>().Setup(s => s.IsSample(It.IsAny<MovieMetadata>(), sampleFile))
.Returns(DetectSampleResult.Sample);
var results = Subject.ImportFiles(_localMovie, _movieFile, new List<string> { subtitleFile }, true).ToList();
results.Count().Should().Be(1);
results[0].RelativePath.AsOsAgnostic().PathEquals(expectedOutputPath.AsOsAgnostic()).Should().Be(true);
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

@@ -47,8 +47,6 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 32, Language.Ukrainian },
new object[] { 33, Language.Persian },
new object[] { 34, Language.Bengali },
new object[] { 35, Language.Slovak },
new object[] { 36, Language.Latvian },
};
public static object[] ToIntCases =
@@ -90,8 +88,6 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Ukrainian, 32 },
new object[] { Language.Persian, 33 },
new object[] { Language.Bengali, 34 },
new object[] { Language.Slovak, 35 },
new object[] { Language.Latvian, 36 },
};
[Test]

View File

@@ -11,7 +11,6 @@ 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;
@@ -53,10 +52,6 @@ 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)

View File

@@ -1,67 +0,0 @@
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");
}
}
}

View File

@@ -20,7 +20,9 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[Platform(Exclude = "Win")]
[TestFixture]
public class FileNameBuilderFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;

View File

@@ -1,4 +1,4 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework.Internal;
@@ -47,25 +47,5 @@ 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");
}
}
}

View File

@@ -27,22 +27,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Unknown);
}
[TestCase("Movie Title - 2022.en.sub")]
[TestCase("Movie Title - 2022.EN.sub")]
[TestCase("Movie Title - 2022.eng.sub")]
[TestCase("Movie Title - 2022.ENG.sub")]
[TestCase("Movie Title - 2022.English.sub")]
[TestCase("Movie Title - 2022.english.sub")]
[TestCase("Movie Title - 2022.en.cc.sub")]
[TestCase("Movie Title - 2022.en.sdh.sub")]
[TestCase("Movie Title - 2022.en.forced.sub")]
[TestCase("Movie Title - 2022.en.sdh.forced.sub")]
public void should_parse_subtitle_language_english(string fileName)
{
var result = LanguageParser.ParseSubtitleLanguage(fileName);
result.Should().Be(Language.English);
}
[TestCase("Movie.Title.1994.French.1080p.XviD-LOL")]
[TestCase("Movie Title : Other Title 2011 AVC.1080p.Blu-ray HD.VOSTFR.VFF")]
[TestCase("Movie Title - Other Title 2011 Bluray 4k HDR HEVC AC3 VFF")]
@@ -358,26 +342,6 @@ 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.2022.LV.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.lv.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.LATVIAN.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.Latvian.WEBRip.XviD-LOL")]
public void should_parse_language_latvian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Languages.Should().BeEquivalentTo(Language.Latvian);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -46,7 +46,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("A.I.Artificial.Movie.(2001)", "A.I. Artificial Movie")]
[TestCase("A.Movie.Name.(1998)", "A Movie Name")]
[TestCase("www.Torrenting.com - Movie.2008.720p.X264-DIMENSION", "Movie")]
[TestCase("www.5MovieRulz.tc - Movie (2000) Malayalam HQ HDRip - x264 - AAC - 700MB.mkv", "Movie")]
[TestCase("Movie: The Movie World 2013", "Movie: The Movie World")]
[TestCase("Movie.The.Final.Chapter.2016", "Movie The Final Chapter")]
[TestCase("Der.Movie.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", "Der Movie James")]

View File

@@ -41,14 +41,6 @@ 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)]

View File

@@ -100,10 +100,6 @@ 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);

View File

@@ -1,161 +0,0 @@
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");
}
}
}

View File

@@ -8,7 +8,6 @@ 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;
@@ -152,107 +151,5 @@ 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");
}
}
}

View File

@@ -428,8 +428,6 @@ namespace NzbDrone.Core.Configuration
public CertificateValidationType CertificateValidation =>
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
private string GetValue(string key)
{
return GetValue(key, string.Empty);

View File

@@ -104,6 +104,5 @@ namespace NzbDrone.Core.Configuration
int BackupRetention { get; }
CertificateValidationType CertificateValidation { get; }
string ApplicationUrl { get; }
}
}

View File

@@ -1,5 +1,4 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
@@ -19,8 +18,6 @@ namespace NzbDrone.Core.CustomFormats
return (ICustomFormatSpecification)MemberwiseClone();
}
public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo)
{
var match = IsSatisfiedByWithoutNegate(movieInfo);

View File

@@ -1,5 +1,4 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
@@ -12,8 +11,6 @@ namespace NzbDrone.Core.CustomFormats
bool Negate { get; set; }
bool Required { get; set; }
NzbDroneValidationResult Validate();
ICustomFormatSpecification Clone();
bool IsSatisfiedBy(ParsedMovieInfo movieInfo);
}

View File

@@ -1,31 +1,11 @@
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";
@@ -37,10 +17,5 @@ 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));
}
}
}

View File

@@ -1,31 +1,11 @@
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";
@@ -34,15 +14,10 @@ namespace NzbDrone.Core.CustomFormats
protected override bool IsSatisfiedByWithoutNegate(ParsedMovieInfo movieInfo)
{
var comparedLanguage = movieInfo != null && Value == Language.Original.Id && movieInfo.ExtraInfo.ContainsKey("OriginalLanguage")
var comparedLanguage = movieInfo != null && Name == "Original" && 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));
}
}
}

View File

@@ -1,31 +1,11 @@
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";
@@ -36,10 +16,5 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,38 +1,21 @@
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;
[FieldDefinition(1, Label = "Regular Expression", HelpText = "Custom Format RegEx is Case Insensitive")]
[FieldDefinition(1, Label = "Regular Expression")]
public string Value
{
get => _raw;
set
{
_raw = value;
if (value.IsNotNullOrWhiteSpace())
{
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
}
@@ -45,10 +28,5 @@ namespace NzbDrone.Core.CustomFormats
return _regex.IsMatch(compared);
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,23 +1,11 @@
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";
@@ -28,10 +16,5 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,24 +1,11 @@
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";
@@ -34,10 +21,5 @@ namespace NzbDrone.Core.CustomFormats
return size > Min.Gigabytes() && size <= Max.Gigabytes();
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,23 +1,11 @@
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";
@@ -28,10 +16,5 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Datastore.Migration
SortTitle = Parser.Parser.NormalizeTitle(collectionName),
Added = added,
QualityProfileId = qualityProfileId,
RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
RootFolderPath = rootFolderPath,
SearchOnAdd = true,
MinimumAvailability = minimumAvailability
});

View File

@@ -1,57 +0,0 @@
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();
}
}
}

View File

@@ -1,14 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(214)]
public class add_language_tags_to_subtitle_files : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("SubtitleFiles").AddColumn("LanguageTags").AsString().Nullable();
}
}
}

View File

@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
@@ -30,6 +31,7 @@ namespace NzbDrone.Core.Extras
public void Handle(MovieScannedEvent message)
{
var movie = message.Movie;
var extraFiles = new List<ExtraFile>();
if (!_diskProvider.FolderExists(movie.Path))
{
@@ -41,16 +43,17 @@ namespace NzbDrone.Core.Extras
var filesOnDisk = _diskScanService.GetNonVideoFiles(movie.Path);
var possibleExtraFiles = _diskScanService.FilterPaths(movie.Path, filesOnDisk, false);
var filteredFiles = possibleExtraFiles;
var importedFiles = new List<string>();
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
{
var imported = existingExtraFileImporter.ProcessFiles(movie, possibleExtraFiles, importedFiles);
var imported = existingExtraFileImporter.ProcessFiles(movie, filteredFiles, importedFiles);
importedFiles.AddRange(imported.Select(f => Path.Combine(movie.Path, f.RelativePath)));
}
_logger.Info("Found {0} possible extra files, imported {1} files.", possibleExtraFiles.Count, importedFiles.Count);
_logger.Info("Found {0} extra files", extraFiles.Count);
}
}
}

View File

@@ -31,6 +31,7 @@ namespace NzbDrone.Core.Extras
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
private readonly List<IManageExtraFiles> _extraFileManagers;
private readonly Logger _logger;
public ExtraService(IMediaFileService mediaFileService,
IMovieService movieService,
@@ -44,6 +45,7 @@ namespace NzbDrone.Core.Extras
_diskProvider = diskProvider;
_configService = configService;
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
_logger = logger;
}
public void ImportMovie(LocalMovie localMovie, MovieFile movieFile, bool isReadOnly)
@@ -60,42 +62,61 @@ namespace NzbDrone.Core.Extras
return;
}
var folderSearchOption = localMovie.FolderMovieInfo == null
? SearchOption.TopDirectoryOnly
: SearchOption.AllDirectories;
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.AllDirectories).Where(f => f != localMovie.Path);
var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(e => e.Trim(' ', '.')
.Insert(0, "."))
.Select(e => e.Trim(' ', '.'))
.ToList();
var sourceFolder = _diskProvider.GetParentFolder(localMovie.Path);
var files = _diskProvider.GetFiles(sourceFolder, folderSearchOption);
var managedFiles = _extraFileManagers.Select((i) => new List<string>()).ToArray();
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase)).ToList();
var filteredFilenames = new List<string>();
var hasNfo = false;
foreach (var file in files)
foreach (var matchingFilename in matchingFilenames)
{
var extension = Path.GetExtension(file);
var matchingExtension = wantedExtensions.FirstOrDefault(e => e.Equals(extension));
// Filter out duplicate NFO files
if (matchingFilename.EndsWith(".nfo", StringComparison.InvariantCultureIgnoreCase))
{
if (hasNfo)
{
continue;
}
hasNfo = true;
}
filteredFilenames.Add(matchingFilename);
}
foreach (var matchingFilename in filteredFilenames)
{
var matchingExtension = wantedExtensions.FirstOrDefault(e => matchingFilename.EndsWith(e));
if (matchingExtension == null)
{
continue;
}
for (int i = 0; i < _extraFileManagers.Count; i++)
try
{
if (_extraFileManagers[i].CanImportFile(localMovie, movieFile, file, extension, isReadOnly))
foreach (var extraFileManager in _extraFileManagers)
{
managedFiles[i].Add(file);
break;
var extension = Path.GetExtension(matchingFilename);
var extraFile = extraFileManager.Import(localMovie.Movie, movieFile, matchingFilename, extension, isReadOnly);
if (extraFile != null)
{
break;
}
}
}
}
for (int i = 0; i < _extraFileManagers.Count; i++)
{
_extraFileManagers[i].ImportFiles(localMovie, movieFile, managedFiles[i], isReadOnly);
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import extra file: {0}", matchingFilename);
}
}
}

View File

@@ -8,7 +8,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Files
{
@@ -20,8 +19,7 @@ namespace NzbDrone.Core.Extras.Files
IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile);
IEnumerable<ExtraFile> CreateAfterMovieFolder(Movie movie, string movieFolder);
IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles);
bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly);
IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly);
ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly);
}
public abstract class ExtraFileManager<TExtraFile> : IManageExtraFiles
@@ -49,8 +47,7 @@ namespace NzbDrone.Core.Extras.Files
public abstract IEnumerable<ExtraFile> CreateAfterMovieImport(Movie movie, MovieFile movieFile);
public abstract IEnumerable<ExtraFile> CreateAfterMovieFolder(Movie movie, string movieFolder);
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Movie movie, List<MovieFile> movieFiles);
public abstract bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly);
public abstract IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly);
public abstract ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly);
protected TExtraFile ImportFile(Movie movie, MovieFile movieFile, string path, bool readOnly, string extension, string fileNameSuffix = null)
{

View File

@@ -261,7 +261,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
details.Add(new XElement("country"));
if (Settings.AddCollectionName && movie.MovieMetadata.Value.CollectionTitle != null)
if (movie.MovieMetadata.Value.CollectionTitle != null)
{
var setElement = new XElement("set");
@@ -305,8 +305,6 @@ 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();

View File

@@ -24,7 +24,6 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
MovieMetadataLanguage = (int)Language.English;
MovieImages = true;
UseMovieNfo = false;
AddCollectionName = true;
}
[FieldDefinition(0, Label = "Movie Metadata", Type = FieldType.Checkbox)]
@@ -42,9 +41,6 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
[FieldDefinition(4, Label = "Use Movie.nfo", Type = FieldType.Checkbox, HelpText = "Radarr will write metadata to movie.nfo instead of the default <movie-filename>.nfo")]
public bool UseMovieNfo { get; set; }
[FieldDefinition(5, Label = "Collection Name", Type = FieldType.Checkbox, HelpText = "Radarr will write the collection name to the .nfo file", Advanced = true)]
public bool AddCollectionName { get; set; }
public bool IsValid => true;
public NzbDroneValidationResult Validate()

View File

@@ -13,7 +13,6 @@ using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Metadata
{
@@ -192,14 +191,9 @@ namespace NzbDrone.Core.Extras.Metadata
return movedFiles;
}
public override bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly)
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
{
return false;
}
public override IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly)
{
return Enumerable.Empty<ExtraFile>();
return null;
}
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> movieMetadata)

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
@@ -9,16 +7,13 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Others
{
public class OtherExtraService : ExtraFileManager<OtherExtraFile>
{
private readonly IDiskProvider _diskProvider;
private readonly IOtherExtraFileService _otherExtraFileService;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly Logger _logger;
public OtherExtraService(IConfigService configService,
IDiskProvider diskProvider,
@@ -28,10 +23,8 @@ namespace NzbDrone.Core.Extras.Others
Logger logger)
: base(configService, diskProvider, diskTransferService, logger)
{
_diskProvider = diskProvider;
_otherExtraFileService = otherExtraFileService;
_mediaFileAttributeService = mediaFileAttributeService;
_logger = logger;
}
public override int Order => 2;
@@ -76,79 +69,17 @@ namespace NzbDrone.Core.Extras.Others
return movedFiles;
}
public override bool CanImportFile(LocalMovie localMovie, MovieFile movieFile, string path, string extension, bool readOnly)
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
{
return true;
}
var extraFile = ImportFile(movie, movieFile, path, readOnly, extension, null);
public override IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly)
{
var importedFiles = new List<ExtraFile>();
var filteredFiles = files.Where(f => CanImportFile(localMovie, movieFile, f, Path.GetExtension(f), isReadOnly)).ToList();
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var matchingFiles = new List<string>();
var hasNfo = false;
foreach (var file in filteredFiles)
if (extraFile != null)
{
try
{
// Filter out duplicate NFO files
if (file.EndsWith(".nfo", StringComparison.InvariantCultureIgnoreCase))
{
if (hasNfo)
{
continue;
}
hasNfo = true;
}
// Filename match
if (Path.GetFileNameWithoutExtension(file).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase))
{
matchingFiles.Add(file);
continue;
}
// Movie match
var fileMovieInfo = Parser.Parser.ParseMoviePath(file) ?? new ParsedMovieInfo();
if (fileMovieInfo.MovieTitle == null)
{
continue;
}
if (fileMovieInfo.MovieTitle == localMovie.FileMovieInfo.MovieTitle &&
fileMovieInfo.Year.Equals(localMovie.FileMovieInfo.Year))
{
matchingFiles.Add(file);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import extra file: {0}", file);
}
_mediaFileAttributeService.SetFilePermissions(path);
_otherExtraFileService.Upsert(extraFile);
}
foreach (string file in matchingFiles)
{
try
{
var extraFile = ImportFile(localMovie.Movie, movieFile, file, isReadOnly, Path.GetExtension(file), null);
_mediaFileAttributeService.SetFilePermissions(file);
_otherExtraFileService.Upsert(extraFile);
importedFiles.Add(extraFile);
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import extra file: {0}", file);
}
}
return importedFiles;
return extraFile;
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Languages;
@@ -6,17 +5,6 @@ namespace NzbDrone.Core.Extras.Subtitles
{
public class SubtitleFile : ExtraFile
{
public SubtitleFile()
{
LanguageTags = new List<string>();
}
public Language Language { get; set; }
public string AggregateString => Language + LanguageTagsAsString + Extension;
public List<string> LanguageTags { get; set; }
private string LanguageTagsAsString => string.Join(".", LanguageTags);
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -10,17 +9,13 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MovieImport;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Extras.Subtitles
{
public class SubtitleService : ExtraFileManager<SubtitleFile>
{
private readonly IDiskProvider _diskProvider;
private readonly IDetectSample _detectSample;
private readonly ISubtitleFileService _subtitleFileService;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly Logger _logger;
@@ -28,14 +23,11 @@ namespace NzbDrone.Core.Extras.Subtitles
public SubtitleService(IConfigService configService,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IDetectSample detectSample,
ISubtitleFileService subtitleFileService,
IMediaFileAttributeService mediaFileAttributeService,
Logger logger)
: base(configService, diskProvider, diskTransferService, logger)
{
_diskProvider = diskProvider;
_detectSample = detectSample;
_subtitleFileService = subtitleFileService;
_mediaFileAttributeService = mediaFileAttributeService;
_logger = logger;
@@ -72,16 +64,21 @@ namespace NzbDrone.Core.Extras.Subtitles
foreach (var movieFile in movieFiles)
{
var groupedExtraFilesForMovieFile = subtitleFiles.Where(m => m.MovieFileId == movieFile.Id)
.GroupBy(s => s.AggregateString).ToList();
.GroupBy(s => s.Language + s.Extension).ToList();
foreach (var group in groupedExtraFilesForMovieFile)
{
var groupCount = group.Count();
var copy = 1;
if (groupCount > 1)
{
_logger.Warn("Multiple subtitle files found with the same language and extension for {0}", Path.Combine(movie.Path, movieFile.RelativePath));
}
foreach (var subtitleFile in group)
{
var suffix = GetSuffix(subtitleFile.Language, copy, subtitleFile.LanguageTags, groupCount > 1);
var suffix = GetSuffix(subtitleFile.Language, copy, groupCount > 1);
movedFiles.AddIfNotNull(MoveFile(movie, movieFile, subtitleFile, suffix));
copy++;
@@ -94,141 +91,25 @@ namespace NzbDrone.Core.Extras.Subtitles
return movedFiles;
}
public override bool CanImportFile(LocalMovie localEpisode, MovieFile movieFile, string path, string extension, bool readOnly)
public override ExtraFile Import(Movie movie, MovieFile movieFile, string path, string extension, bool readOnly)
{
return SubtitleFileExtensions.Extensions.Contains(extension.ToLowerInvariant());
if (SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(path)))
{
var language = LanguageParser.ParseSubtitleLanguage(path);
var suffix = GetSuffix(language, 1, false);
var subtitleFile = ImportFile(movie, movieFile, path, readOnly, extension, suffix);
subtitleFile.Language = language;
_mediaFileAttributeService.SetFilePermissions(path);
_subtitleFileService.Upsert(subtitleFile);
return subtitleFile;
}
return null;
}
public override IEnumerable<ExtraFile> ImportFiles(LocalMovie localMovie, MovieFile movieFile, List<string> files, bool isReadOnly)
{
var importedFiles = new List<SubtitleFile>();
var filteredFiles = files.Where(f => CanImportFile(localMovie, movieFile, f, Path.GetExtension(f), isReadOnly)).ToList();
var sourcePath = localMovie.Path;
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
var matchingFiles = new List<string>();
foreach (var file in filteredFiles)
{
try
{
// Filename match
if (Path.GetFileNameWithoutExtension(file).StartsWithIgnoreCase(sourceFileName))
{
matchingFiles.Add(file);
continue;
}
// Movie match
var fileMovieInfo = Parser.Parser.ParseMoviePath(file) ?? new ParsedMovieInfo();
if (fileMovieInfo.MovieTitle == null)
{
continue;
}
if (fileMovieInfo.MovieTitle == localMovie.FileMovieInfo.MovieTitle &&
fileMovieInfo.Year.Equals(localMovie.FileMovieInfo.Year))
{
matchingFiles.Add(file);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import subtitle file: {0}", file);
}
}
// Use any sub if only episode in folder
if (matchingFiles.Count == 0 && filteredFiles.Count > 0)
{
var videoFiles = _diskProvider.GetFiles(sourceFolder, SearchOption.AllDirectories)
.Where(file => MediaFileExtensions.Extensions.Contains(Path.GetExtension(file)))
.ToList();
if (videoFiles.Count() > 2)
{
return importedFiles;
}
// Filter out samples
videoFiles = videoFiles.Where(file =>
{
var sample = _detectSample.IsSample(localMovie.Movie.MovieMetadata, file);
if (sample == DetectSampleResult.Sample)
{
return false;
}
return true;
}).ToList();
if (videoFiles.Count == 1)
{
matchingFiles.AddRange(filteredFiles);
_logger.Warn("Imported any available subtitle file for movie: {0}", localMovie);
}
}
var subtitleFiles = new List<SubtitleFile>();
foreach (string file in matchingFiles)
{
var language = LanguageParser.ParseSubtitleLanguage(file);
var extension = Path.GetExtension(file);
var languageTags = LanguageParser.ParseLanguageTags(file);
var subFile = new SubtitleFile
{
Language = language,
Extension = extension
};
subFile.LanguageTags = languageTags.ToList();
subFile.RelativePath = PathExtensions.GetRelativePath(sourceFolder, file);
subtitleFiles.Add(subFile);
}
var groupedSubtitleFiles = subtitleFiles.GroupBy(s => s.AggregateString).ToList();
foreach (var group in groupedSubtitleFiles)
{
var groupCount = group.Count();
var copy = 1;
foreach (var file in group)
{
var path = Path.Combine(sourceFolder, file.RelativePath);
var language = file.Language;
var extension = file.Extension;
var suffix = GetSuffix(language, copy, file.LanguageTags, groupCount > 1);
try
{
var subtitleFile = ImportFile(localMovie.Movie, movieFile, path, isReadOnly, extension, suffix);
subtitleFile.Language = language;
subtitleFile.LanguageTags = file.LanguageTags;
_mediaFileAttributeService.SetFilePermissions(path);
_subtitleFileService.Upsert(subtitleFile);
importedFiles.Add(subtitleFile);
copy++;
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to import subtitle file: {0}", path);
}
}
}
return importedFiles;
}
private string GetSuffix(Language language, int copy, List<string> languageTags, bool multipleCopies = false)
private string GetSuffix(Language language, int copy, bool multipleCopies = false)
{
var suffixBuilder = new StringBuilder();
@@ -244,12 +125,6 @@ namespace NzbDrone.Core.Extras.Subtitles
suffixBuilder.Append(IsoLanguages.Get(language).TwoLetterCode);
}
if (languageTags.Any())
{
suffixBuilder.Append(".");
suffixBuilder.Append(string.Join(".", languageTags));
}
return suffixBuilder.ToString();
}
}

View File

@@ -23,7 +23,6 @@ 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;
@@ -31,7 +30,6 @@ namespace NzbDrone.Core.ImportLists
IImportListStatusService importListStatusService,
IImportListMovieService listMovieService,
ISearchForNewMovie movieSearch,
IProvideMovieInfo movieInfoService,
IMovieMetadataService movieMetadataService,
Logger logger)
{
@@ -39,7 +37,6 @@ namespace NzbDrone.Core.ImportLists
_importListStatusService = importListStatusService;
_listMovieService = listMovieService;
_movieSearch = movieSearch;
_movieInfoService = movieInfoService;
_movieMetadataService = movieMetadataService;
_logger = logger;
}
@@ -87,10 +84,20 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure)
{
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();
// 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();
listMovies.AddRange(alreadyMapped);
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
@@ -141,10 +148,13 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure)
{
var listMovies = MapMovieReports(importListReports.Movies).Where(x => x.TmdbId > 0).ToList();
// 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();
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);
@@ -163,31 +173,21 @@ namespace NzbDrone.Core.ImportLists
return result;
}
private List<ImportListMovie> MapMovieReports(List<ImportListMovie> reports)
private ImportListMovie MapMovieReport(ImportListMovie report)
{
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 mappedMovie = _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = report.Title, TmdbId = report.TmdbId, ImdbId = report.ImdbId, Year = report.Year });
_movieMetadataService.UpsertMany(mappedMovies);
var mappedListMovie = new ImportListMovie { ListId = report.ListId };
var mappedListMovies = new List<ImportListMovie>();
foreach (var movieMeta in mappedMovies)
if (mappedMovie != null)
{
var mappedListMovie = new ImportListMovie();
_movieMetadataService.Upsert(mappedMovie);
if (movieMeta != null)
{
mappedListMovie.MovieMetadata = movieMeta;
mappedListMovie.MovieMetadataId = movieMeta.Id;
}
mappedListMovies.Add(mappedListMovie);
mappedListMovie.MovieMetadata = mappedMovie;
mappedListMovie.MovieMetadataId = mappedMovie.Id;
}
return mappedListMovies;
return mappedListMovie;
}
}
}

View File

@@ -27,13 +27,7 @@ namespace NzbDrone.Core.ImportLists.Trakt.List
{
var link = string.Empty;
// 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, "-", "-");
var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim());
link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}";
var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken));

View File

@@ -1,6 +1,4 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.ImportLists.Trakt.Popular
@@ -11,24 +9,6 @@ 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");
}
}
@@ -39,25 +19,9 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
public TraktPopularSettings()
{
TraktListType = (int)TraktPopularListType.Popular;
Rating = "0-100";
Certification = "NR,G,PG,PG-13,R,NC-17";
Genres = "";
Years = "";
}
[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; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
@@ -28,6 +29,24 @@ 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)
@@ -43,6 +62,10 @@ 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;
}
@@ -61,6 +84,18 @@ 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; }

View File

@@ -42,15 +42,14 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var movieId in message.MovieIds)
{
var movie = _movieService.GetMovie(movieId);
var userInvokedSearch = message.Trigger == CommandTrigger.Manual;
if (!movie.Monitored && !userInvokedSearch)
if (!movie.Monitored && message.Trigger != CommandTrigger.Manual)
{
_logger.Debug("Movie {0} is not monitored, skipping search", movie.Title);
continue;
}
var decisions = _releaseSearchService.MovieSearch(movieId, userInvokedSearch, false);
var decisions = _releaseSearchService.MovieSearch(movieId, false, false);
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
}

View File

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

View File

@@ -105,8 +105,6 @@ 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 Latvian => new Language(36, "Latvian");
public static Language Any => new Language(-1, "Any");
public static Language Original => new Language(-2, "Original");
@@ -151,8 +149,6 @@ namespace NzbDrone.Core.Languages
Ukrainian,
Persian,
Bengali,
Slovak,
Latvian,
Any,
Original
};

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,7 @@
"AlternativeTitle": "Alternative Titel",
"AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Age": "Alter",
"AddNewMovie": "Neuen Film hinzufügen",
"AddNewMovie": "Neuer Film...",
"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",
@@ -639,8 +639,8 @@
"RetryingDownloadInterp": "Herunterladen nochmal versuchen {0} um {1}",
"RemovingTag": "Tag entfernen",
"ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}",
"Queued": "In der Warteschlange",
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen",
"Queued": "Eingereiht",
"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.",
@@ -653,7 +653,7 @@
"MarkAsFailedMessageText": "'{0}' wirklich als fehlgeschlagen markieren?",
"Manual": "Manuell",
"LogLevelTraceHelpTextWarning": "Trace logging sollte nur kurzzeitig aktiviert werden",
"LastDuration": "Letzte Dauer",
"LastDuration": "Letzte Laufzeit",
"IncludeRecommendationsHelpText": "Radarrs Empfehlungen in der Entdeckungsansicht mit einbeziehen",
"IncludeRadarrRecommendations": "Radarr Empfehlungen einbeziehen",
"ImportFailedInterp": "Import fehlgeschlagen: {0}",
@@ -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öglicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise 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 auf Grund des Filters ausgeblendet.",
"Collections": "Sammlungen",
"AllCollectionsHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Collections": "Sammlung",
"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,7 +1141,5 @@
"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",
"RottenTomatoesRating": "Tomato Bewertung",
"TotalMovies": "Filme insgesamt"
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname"
}

View File

@@ -50,8 +50,6 @@
"ApiKey": "API Key",
"AppDataDirectory": "AppData directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
"ApplicationURL": "Application URL",
"ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base",
"Apply": "Apply",
"ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "How to apply tags to the selected movies",
@@ -1029,7 +1027,6 @@
"Torrents": "Torrents",
"TorrentsDisabled": "Torrents Disabled",
"TotalFileSize": "Total File Size",
"TotalMovies": "Total Movies",
"TotalSpace": "Total Space",
"Trace": "Trace",
"Trailer": "Trailer",

View File

@@ -91,7 +91,7 @@
"Search": "Buscar",
"Scheduled": "Programado",
"SaveChanges": "Guardar Cambios",
"RSSSync": "Sincronizar RSS",
"RSSSync": "Sincronización RSS",
"RootFolders": "Carpetas de Origen",
"RootFolderCheckSingleMessage": "La carpeta de origen no existe: {0}",
"RootFolderCheckMultipleMessage": "Varias carpetas de origen no existen: {0}",
@@ -232,7 +232,7 @@
"OAuthPopupMessage": "Pop-ups bloqueados por su navegador",
"Name": "Nombre",
"MoveFiles": "Mover Archivos",
"Monitored": "Monitoreada",
"Monitored": "Monitoreadas",
"Message": "Mensaje",
"Location": "Localización",
"Level": "Nivel",
@@ -461,7 +461,7 @@
"ShowMonitoredHelpText": "Mostrar el estado de monitoreo debajo del poster",
"ShowCutoffUnmetIconHelpText": "Mostrar el icono para los ficheros cuando no se ha alcanzado el corte",
"ShowAsAllDayEvents": "Mostrar como Eventos de Todo el Día",
"ShouldMonitorHelpText": "Si las películas o colecciones en esta lista deberían ser agregadas como monitoreadas",
"ShouldMonitorHelpText": "Si se habilita, las películas añadidas desde esta lista serán también monitoreadas",
"SetPermissionsLinuxHelpTextWarning": "Si no estas seguro de lo que hacen estos ajustes, no los modifiques.",
"SetPermissionsLinuxHelpText": "Debe chmod ser ejecutado una vez los archivos hayan sido importados/renombrados?",
"SetPermissions": "Ajustar Permisos",
@@ -614,8 +614,8 @@
"CleanLibraryLevel": "Limpiar el Nivel de la Librería",
"BindAddress": "Dirección de Ligado",
"OpenBrowserOnStart": "Abrir navegador al arrancar",
"OnUpgradeHelpText": "Al Mejorar La Calidad",
"OnRenameHelpText": "Al Renombrar",
"OnUpgradeHelpText": "Al Actualizar",
"OnRenameHelpText": "En Renombrado",
"OnHealthIssueHelpText": "En Problema de Salud",
"OnGrabHelpText": "Al Capturar",
"SettingsRuntimeFormat": "Formato de Tiempo de ejecución",
@@ -633,7 +633,7 @@
"ShownClickToHide": "Mostrado, clic para ocultar",
"ShowGenres": "Mostrar Géneros",
"ShowCertification": "Mostrar Certificación",
"SearchOnAddHelpText": "Buscar películas en esta lista cuando se añada a la biblioteca",
"SearchOnAddHelpText": "Buscar películas en esta lista cuando se añadan a Radarr",
"RSSSyncIntervalHelpTextWarning": "Se aplicará a todos los indexers, por favor sigue las reglas de los mismos",
"RSSIsNotSupportedWithThisIndexer": "RSS no son soportadas por este indexer",
"RetryingDownloadInterp": "Re-intentando descarga {0} en {1}",
@@ -874,11 +874,11 @@
"MovieFilesTotaling": "Totalización de archivos de película",
"OnGrab": "Al Capturar",
"OnHealthIssue": "En Problema de Salud",
"OnImport": "Al Importar",
"OnImport": "Al importar",
"OnLatestVersion": "La última versión de Radarr ya está instalada",
"OnlyTorrent": "Solo Torrent",
"OnlyUsenet": "Solo Usenet",
"OnRename": "Al Renombrar",
"OnRename": "En Renombrado",
"PreferTorrent": "Prefiero Torrent",
"PreferUsenet": "Prefiero Usenet",
"Presets": "Preajustes",
@@ -988,12 +988,12 @@
"Score": "Puntuación",
"Script": "Guión",
"SearchCutoffUnmet": "Corte de búsqueda no alcanzado",
"SearchMissing": "Buscar Faltantes",
"SearchMissing": "Búsqueda que falta",
"Seconds": "Segundos",
"SelectDotDot": "Seleccione...",
"SelectLanguage": "Seleccionar Idioma",
"SelectMovie": "Seleccionar Película",
"SelectQuality": "Seleccionar Calidad",
"SelectLanguage": "Seleccione el idioma",
"SelectMovie": "Seleccionar película",
"SelectQuality": "Seleccionar calidad",
"Small": "Pequeña",
"Socks4": "Calcetines4",
"Socks5": "Socks5 (soporte TOR)",
@@ -1031,13 +1031,13 @@
"showCinemaReleaseHelpText": "Mostrar la fecha de estreno en el cine debajo del cartel",
"ShowReleaseDate": "Mostrar fecha de lanzamiento",
"ShowReleaseDateHelpText": "Mostrar la fecha de lanzamiento debajo del cartel",
"OnMovieDelete": "Al Eliminar Película",
"OnMovieDeleteHelpText": "Al Eliminar Película",
"OnMovieFileDelete": "Al Eliminar Archivo De Película",
"OnMovieFileDeleteHelpText": "Al Eliminar Archivo De Película",
"OnMovieFileDeleteForUpgrade": "Al Eliminar Archivo De Pelicula Para Mejorar La Calidad",
"OnMovieFileDeleteForUpgradeHelpText": "Al Eliminar Archivo De Pelicula Para Mejorar La Calidad",
"OnUpgrade": "Al Mejorar La Calidad",
"OnMovieDelete": "Al eliminar la película",
"OnMovieDeleteHelpText": "Al eliminar la película",
"OnMovieFileDelete": "Al eliminar archivo de película",
"OnMovieFileDeleteHelpText": "Al eliminar archivo de película",
"OnMovieFileDeleteForUpgrade": "En archivo de película Eliminar para actualizar",
"OnMovieFileDeleteForUpgradeHelpText": "En archivo de película Eliminar para actualizar",
"OnUpgrade": "Al Actualizar",
"Reddit": "Reddit",
"More": "Más",
"Download": "Descargar",
@@ -1064,7 +1064,7 @@
"List": "Lista",
"ManualImportSetReleaseGroup": "Importación manual - Configurar el grupo de lanzamiento",
"NotificationTriggersHelpText": "Seleccione qué eventos deben activar esta notificación",
"OnApplicationUpdate": "Al Actualizar La Aplicación",
"OnApplicationUpdate": "Al actualizar la aplicación",
"RemotePathMappingCheckFileRemoved": "El archivo {0} fue eliminado a mitad del proceso.",
"RemotePath": "Ruta remota",
"RemotePathMappingCheckBadDockerPath": "Está utilizando docker; el cliente de descarga {0} coloca las descargas en {1} pero esta no es una ruta válida {2}. Revisa tus mapeos de rutas remotas y la configuración del cliente de descarga.",
@@ -1072,7 +1072,7 @@
"RemotePathMappingCheckFilesBadDockerPath": "Está utilizando docker; el cliente de descarga {0} informó de archivos en {1} pero esta no es una ruta válida {2}. Revise sus mapeos de rutas remotas y la configuración del cliente de descarga.",
"RemotePathMappingCheckLocalWrongOSPath": "El cliente de descarga local {0} coloca las descargas en {1} pero ésta no es una ruta válida {2}. Revise la configuración de su cliente de descarga.",
"RemotePathMappingCheckRemoteDownloadClient": "El cliente de descarga remota {0} informó de la existencia de archivos en {1} pero este directorio no parece existir. Probablemente falta mapear la ruta remota.",
"SelectLanguages": "Seleccionar Idiomas",
"SelectLanguages": "Seleccionar idiomas",
"IndexerDownloadClientHelpText": "Especifica qué cliente de descargas se utiliza para las descargas de este indexador",
"AnnouncedMsg": "Película anunciada",
"Auto": "Auto",
@@ -1097,7 +1097,7 @@
"TmdbRating": "TMDb valoración",
"TmdbVotes": "Votos en TMDb",
"Filters": "Filtros",
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
"OnApplicationUpdateHelpText": "Al actualizar la aplicación",
"OriginalTitle": "Título original",
"OriginalLanguage": "Idioma original",
"Rating": "Puntuación",
@@ -1108,7 +1108,7 @@
"RemotePathMappingCheckFilesLocalWrongOSPath": "El cliente de descarga local {0} informó de la existencia de archivos en {1}, pero no es una ruta válida {2}. Revise la configuración de su cliente de descarga.",
"RemoveSelectedItem": "Eliminar el elemento seleccionado",
"RemoveSelectedItems": "Eliminar los elementos seleccionados",
"SelectReleaseGroup": "Seleccionar Grupo De Lanzamiento",
"SelectReleaseGroup": "Seleccionar el grupo de lanzamiento",
"SetReleaseGroup": "Configurar el grupo de lanzamiento",
"TaskUserAgentTooltip": "User-Agent proporcionado por la aplicación llamó a la API",
"RssSyncHelpText": "Intervalo en minutos. Ajustar a cero para inhabilitar (esto dentendrá toda captura de estrenos automática)",
@@ -1119,28 +1119,5 @@
"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",
"CollectionShowPostersHelpText": "Mostrar póster de artículos de colección",
"RefreshMonitoredIntervalHelpText": "Cada cuánto actualizar las descargas monitoreadeas desde los clientes de descarga, mínimo 1 minuto",
"SearchOnAddCollectionHelpText": "Buscar películas en esta colección cuando se añada a la biblioteca",
"TotalMovies": "Películas Totales",
"CollectionsSelectedInterp": "{0} Colecciones Seleccionadas",
"EditCollection": "Editar Colección",
"MonitorCollection": "Monitorear Colección",
"MonitoredCollectionHelpText": "Monitorear para que las películas de esta colección se añadan automáticamente a la biblioteca",
"MovieAndCollection": "Película y Colección",
"MovieCollectionMultipleMissingRoots": "Múltiples carpetas raices faltantes para colecciones de películas: {0}",
"OnMovieAdded": "Al Agregar Pelicula",
"OnMovieAddedHelpText": "Al Agregar Película",
"RefreshCollections": "Actualizar Colecciones",
"ShowCollectionDetails": "Mostrar Estado De Colección",
"InstanceName": "Nombre de Instancia",
"InstanceNameHelpText": "Nombre de instancia en pestaña y para nombre de aplicación en Syslog",
"RottenTomatoesRating": "Tomato Rating",
"ScrollMovies": "Desplazar Películas",
"ShowPosters": "Mostrar Posters",
"UnableToLoadCollections": "No se han podido cargar las colecciones",
"MovieCollectionMissingRoot": "Carpeta raíz faltante para colección de películas: {0}",
"MovieOnly": "Solo Película",
"Database": "Base de Datos"
"CollectionShowOverviewsHelpText": "Mostrar resumenes de la colección"
}

View File

@@ -220,7 +220,7 @@
"MoveFiles": "Siirrä tiedostoja",
"MovieFiles": "Elokuvatiedostot",
"MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella",
"NextExecution": "Seuraava suoritus",
"NextExecution": "Seuraava toteutus",
"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": "Edellinen kesto",
"LastExecution": "Edellinen suoritus",
"LastDuration": "Viimeisin kesto",
"LastExecution": "Viimeinen toteutus",
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
"ListExclusions": "Listojen poikkeussäännöt",
"Indexer": "Tietolähde",
@@ -1142,8 +1142,5 @@
"CollectionOptions": "Kokoelmien valinnat",
"CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet",
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet",
"RottenTomatoesRating": "Tomaattiarvio",
"TotalMovies": "Elokuvia yhteensä",
"ApplicationURL": "Sovelluksen URL-osoite",
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta."
"RottenTomatoesRating": "Tomaattiarvio"
}

View File

@@ -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": "Actualiser",
"Refresh": "Rafraîchir",
"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": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne pas changer à moins que vous ne compreniez les risques.",
"CertificateValidationHelpText": "Change la rigueur de la vérification du certificat HTTPS. Ne changez rien sauf si vous comprenez les risques.",
"CertificateValidation": "Validation du certificat",
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
"Branch": "Branche",
@@ -1114,33 +1114,10 @@
"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": "Collections",
"Collections": "Collection",
"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",
"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",
"ApplicationURL": "URL de l'application",
"ApplicationUrlHelpText": "URL externe de cette application, y compris http(s)://, le port et l'URL de base",
"InstanceName": "Nom de l'instance",
"InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog",
"CollectionShowPostersHelpText": "Afficher les affiches des éléments de la collection"
"ChooseImportMode": "Mode d'importation"
}

View File

@@ -21,7 +21,7 @@
"AddExclusion": "Kivétel hozzáadása",
"Activity": "Aktivitás",
"Error": "Hiba",
"Ended": "Vége lett",
"Ended": "Befejezve",
"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": "Sorba helyezve",
"Queued": "Sorban",
"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ó időtartam",
"LastDuration": "Utolsó Hossza",
"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": "Utol végrehajtás",
"LastExecution": "Utoljára végrehajtott",
"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ő végrehajtás",
"NextExecution": "Következő futtatá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": "Elkezdődött",
"Started": "Elindult",
"Waiting": "Várakozás",
"OnApplicationUpdateHelpText": "Alkalmazásfrissítésről",
"SizeLimit": "Méretkorlát",
@@ -1142,8 +1142,5 @@
"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",
"TotalMovies": "Összes film",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is",
"ApplicationURL": "Alkalmazás URL-je"
"RottenTomatoesRating": "Tomato Értékelés"
}

View File

@@ -254,6 +254,5 @@
"Language": "språk",
"List": "Liste",
"New": "Ny",
"RemotePathMappings": "Ekstern portmapping",
"Languages": "språk"
"RemotePathMappings": "Ekstern portmapping"
}

View File

@@ -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 filmy",
"MonitorMovies": "Monitoruj film",
"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}, ale Radarr nie widzi tego folderu. Być może należy zmienić uprawnienia dostępu do folderu/ścieżkę dostępu.",
"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.",
"RemoveFailed": "Usuń nieudane",
"RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.",
"ShowCollectionDetails": "Pokaż stan kolekcji",
@@ -1141,7 +1141,5 @@
"CollectionShowPostersHelpText": "Pokaż plakaty elementów kolekcji",
"CollectionOptions": "Opcje Kolekcji",
"CollectionShowDetailsHelpText": "Pokaż status i właściwości kolekcji",
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji",
"TotalMovies": "Filmów całkowicie",
"RottenTomatoesRating": "Ocena Tomato"
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji"
}

View File

@@ -1,5 +1,5 @@
{
"LastExecution": "Última Execução",
"LastExecution": "Execução mais recente",
"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 habilitada, Radarr não irá prover quaisquer resultados para pesquisa interativa",
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
"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": "Use quando o Radarr não conseguir detectar espaço livre na pasta raiz do filme",
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar o Radarr quando for impossível detectar o espaço livre da sua pasta raiz do filme",
"SkipFreeSpaceCheck": "Ignorar verificação de espaço livre",
"SizeOnDisk": "Tamanho em disco",
"Size": "Tamanho",
@@ -928,8 +928,8 @@
"OnImport": "Ao importar",
"OnHealthIssueHelpText": "Ao ter problema de integridade",
"OnHealthIssue": "Em Problema de Saúde",
"OnGrabHelpText": "Em Espera",
"OnGrab": "Em Espera",
"OnGrabHelpText": "Ao obter",
"OnGrab": "Ao Baixar",
"OnDownloadHelpText": "Ao importar",
"Ok": "Ok",
"OAuthPopupMessage": "Os pop-ups estão bloqueados em seu navegador",
@@ -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": "Use o atualizador integrado do Radarr ou um script",
"UpdateMechanismHelpText": "Usar 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 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.",
"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.",
"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.",
@@ -1117,8 +1117,8 @@
"InstanceName": "Nome da instância",
"InstanceNameHelpText": "Nome da instância na guia e para o nome do aplicativo Syslog",
"AllCollectionsHiddenDueToFilter": "Todos os filmes estão ocultos devido ao filtro aplicado.",
"Collections": "Coleções",
"MonitorMovies": "Monitorar Filmes",
"Collections": "Coleção",
"MonitorMovies": "Monitorar filme",
"NoCollections": "Nenhum filme encontrado. Para começar, adicione um novo filme ou importe alguns existentes",
"MovieOnly": "Somente Filme",
"UnableToLoadCollections": "Não foi possível carregar as coleções",
@@ -1142,8 +1142,5 @@
"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",
"TotalMovies": "Total de Filmes",
"ApplicationURL": "URL da Aplicação",
"ApplicationUrlHelpText": "O URL externa deste aplicativo, incluindo http(s)://, porta e base de URL"
"RottenTomatoesRating": "Avaliação Tomato"
}

View File

@@ -44,7 +44,7 @@
"Activity": "Activitate",
"Actions": "Acțiuni",
"About": "Despre",
"IndexerStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită erorilor",
"IndexerStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită eșuărilor",
"IndexersSettingsSummary": "Restricții pentru indexatori și apariții",
"IndexerSearchCheckNoInteractiveMessage": "Niciun indexator cu Căutare interactivă nu este activ, Radarr nu va afișa niciun rezultat de căutare interactivă",
"IndexerSearchCheckNoAvailableIndexersMessage": "Toți indexatorii ce suportă căutare sunt indisponibili temporar datorită erorilor",
@@ -62,11 +62,11 @@
"Ignored": "Ignorat",
"iCalLink": "Legătură iCal",
"Host": "Gazdă",
"History": "Istoric",
"History": "Istorie",
"HideAdvanced": "Ascunde Avansat",
"Health": "Sănătate",
"GrabSelected": "Prinderea selectată",
"Grabbed": "În curs de descărcare",
"Grabbed": "Prins",
"Genres": "Genuri",
"GeneralSettingsSummary": "Port, SSL, utilizator/parolă, proxy, statistici și actualizări",
"General": "General",
@@ -76,7 +76,7 @@
"Folder": "Dosar",
"Filter": "Filtru",
"Files": "Fișiere",
"Filename": "Numele fișierului",
"Filename": "Numele Fișierului",
"FileManagement": "Administrarea de fișiere",
"FailedDownloadHandling": "Procesarea descărcării a eșuat",
"Failed": "Eșuat",
@@ -84,7 +84,7 @@
"Events": "Evenimente",
"Edit": "Editează",
"Downloaded": "Descărcat",
"DownloadClientStatusCheckSingleClientMessage": "Clienții de descărcare sunt indisponibili datorită erorilor: {0}",
"DownloadClientStatusCheckSingleClientMessage": "Clienții de descărcare sunt indisponibili datorită erorii: {0}",
"DownloadClientStatusCheckAllClientMessage": "Toți clienții de descărcare sunt indisponibili datorită erorilor",
"ProfilesSettingsSummary": "Calitate, Limbă și Profile de Întârziere",
"Profiles": "Profile",
@@ -143,9 +143,9 @@
"Language": "Limbă",
"KeyboardShortcuts": "Scurtături din tastatură",
"Info": "Info",
"IndexerStatusCheckSingleClientMessage": "Indexatoare indisponibile datorită erorilor: {0}",
"IndexerStatusCheckSingleClientMessage": "Indexator indisponibil datorită erorilor: {0}",
"ImportExistingMovies": "Importă Filme Existente",
"HealthNoIssues": "Nicio problemă de configurare",
"HealthNoIssues": "Nicio problemă în configurare",
"HardlinkCopyFiles": "Hardlink/Copiază Fișiere",
"Extension": "Extensie",
"Error": "Eroare",
@@ -168,7 +168,7 @@
"QualityProfile": "Profil de Calitate",
"QualityDefinitions": "Definiții de calitate",
"Quality": "Calitate",
"PtpOldSettingsCheckMessage": "Următoarele indexatoare PassThePopcorn au setări depreciate și ar trebui actualizate: {0}",
"PtpOldSettingsCheckMessage": "Următorul indexer PassThePopcorn are setări depreciate și ar trebui actualizate: {0}",
"ProxyCheckResolveIpMessage": "Nu am putut găsi adresa IP pentru Hostul Proxy Configurat {0}",
"ProxyCheckFailedToTestMessage": "Nu am putut testa proxy: {0}",
"ProxyCheckBadRequestMessage": "Testul proxy a eșuat. StatusCode: {0}",
@@ -260,7 +260,7 @@
"Queue": "Coadă",
"QualitySettingsSummary": "Calitate - mărimi și denumiri",
"AutoRedownloadFailedHelpText": "Căutați automat și încercați să descărcați o versiune diferită",
"BackupIntervalHelpText": "Interval între crearea copiile de siguranță automate",
"BackupIntervalHelpText": "Interval între copiile de rezervă automate",
"ApplyTagsHelpTexts1": "Cum se aplică etichete filmelor selectate",
"CustomFormatUnknownCondition": "Stare necunoscută pentru formatul personalizat „{0}”",
"QualitySettings": "Setări de calitate",
@@ -337,7 +337,7 @@
"ThisCannotBeCancelled": "Acest lucru nu poate fi anulat odată pornit fără a reporni Radarr.",
"TorrentDelayHelpText": "Întârziați în câteva minute pentru a aștepta înainte de a apuca un torent",
"Trigger": "Declanșator",
"UnableToAddANewIndexerPleaseTryAgain": "Nu se poate adăuga un nou indexator, vă rugăm să încercați din nou.",
"UnableToAddANewIndexerPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.",
"UnableToAddANewNotificationPleaseTryAgain": "Imposibil de adăugat o nouă notificare, încercați din nou.",
"UnableToAddANewRemotePathMappingPleaseTryAgain": "Imposibil de adăugat o nouă mapare a căilor la distanță, încercați din nou.",
"UnableToImportCheckLogs": "Descărcat - Imposibil de importat: verificați jurnalele pentru detalii",
@@ -369,7 +369,7 @@
"Importing": "Importând",
"AcceptConfirmationModal": "Acceptați Modul de confirmare",
"LastExecution": "Ultima executare",
"DBMigration": "Migrarea BD",
"DBMigration": "Migrarea DB",
"Manual": "Manual",
"OnHealthIssue": "Cu privire la problema sănătății",
"OnImport": "La import",
@@ -388,8 +388,8 @@
"AvailabilityDelayHelpText": "Suma de timp înainte sau după data disponibilă pentru căutarea filmului",
"Announced": "Anunțat",
"AddRemotePathMapping": "Adăugați maparea căilor la distanță",
"BackupRetentionHelpText": "Copiile de siguranță automate mai vechi decât perioada de păstrare vor fi curățate automat",
"Backups": "Copii de siguranță",
"BackupRetentionHelpText": "Copiile de rezervă automate mai vechi de perioada de păstrare vor fi curățate automat",
"Backups": "Copii de rezervă",
"BeforeUpdate": "Înainte de actualizare",
"BindAddress": "Adresa de legare",
"CancelProcessing": "Anulați procesarea",
@@ -404,7 +404,7 @@
"DeleteMovieFolderLabel": "Ștergeți dosarul filmului",
"DockerUpdater": "actualizați containerul de andocare pentru a primi actualizarea",
"EditGroups": "Editați grupurile",
"EditIndexer": "Editați indexator",
"EditIndexer": "Editați Indexer",
"Enabled": "Activat",
"ExcludeMovie": "Excludeți filmul",
"ExcludeTitle": "Excludeți {0}? Acest lucru îl va împiedica pe Radarr să adauge automat prin sincronizarea listei.",
@@ -413,7 +413,7 @@
"HttpHttps": "HTTP (S)",
"ICalFeed": "Feed iCal",
"ICalHttpUrlHelpText": "Copiați această adresă URL către clienții dvs. sau faceți clic pentru a vă abona dacă browserul dvs. acceptă webcal",
"Add": "Adaugă",
"Add": "Adăuga",
"AddCustomFormat": "Adăugați format personalizat",
"AddDelayProfile": "Adăugați un profil de întârziere",
"AddDownloadClient": "Adăugați client de descărcare",
@@ -440,7 +440,7 @@
"ChmodFolderHelpTextWarning": "Acest lucru funcționează numai dacă utilizatorul care rulează Radarr este proprietarul fișierului. Este mai bine să vă asigurați că clientul de descărcare setează corect permisiunile.",
"ChmodGroup": "chmod Group",
"ChmodGroupHelpText": "Numele grupului sau gid. Utilizați gid pentru sistemele de fișiere la distanță.",
"DeleteBackup": "Ștergeți copie de siguranță",
"DeleteBackup": "Ștergeți Backup",
"DeleteCustomFormat": "Ștergeți formatul personalizat",
"DeletedMsg": "Filmul a fost șters din TMDb",
"DeleteDownloadClient": "Ștergeți clientul de descărcare",
@@ -448,12 +448,12 @@
"Connection": "Conexiuni",
"CantFindMovie": "De ce nu-mi găsesc filmul?",
"CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS",
"AddIndexer": "Adăugați Indexator",
"AddIndexer": "Adăugați Indexer",
"AutomaticSearch": "Căutare automată",
"ClientPriority": "Prioritatea clientului",
"CertificationCountry": "Țara certificării",
"CertificationCountryHelpText": "Selectați Țara pentru certificări de film",
"HomePage": "Pagina principală",
"HomePage": "Pagina principala",
"IncludeHealthWarningsHelpText": "Includeți avertismente de sănătate",
"InvalidFormat": "Format invalid",
"LastDuration": "lastDuration",
@@ -508,7 +508,7 @@
"ShowGenres": "Afișați genurile",
"SuggestTranslationChange": "Sugerează modificarea traducerii",
"TestAllClients": "Testați toți clienții",
"TestAllIndexers": "Testați toate indexatoarele",
"TestAllIndexers": "Testați toți indexatorii",
"TestAllLists": "Testați toate listele",
"Queued": "În așteptare",
"TMDb": "TMDb",
@@ -524,8 +524,8 @@
"DetailedProgressBarHelpText": "Afișați textul pe bara de progres",
"EnableSSL": "Activați SSL",
"IncludeCustomFormatWhenRenaming": "Includeți format personalizat la redenumire",
"IndexerLongTermStatusCheckAllClientMessage": "Toți indexatorii sunt indisponibili datorită erorilor de mai mult de 6 ore",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexatori indisponibili datorită erorilor de mai mult de 6 ore: {0}",
"IndexerLongTermStatusCheckAllClientMessage": "Toți indexatorii nu sunt disponibili din cauza unor eșecuri de mai mult de 6 ore",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexatori indisponibili din cauza unor eșecuri de mai mult de 6 ore: {0}",
"LoadingMovieCreditsFailed": "Încărcarea creditelor filmului nu a reușit",
"LoadingMovieExtraFilesFailed": "Încărcarea fișierelor suplimentare ale filmului nu a reușit",
"LoadingMovieFilesFailed": "Încărcarea fișierelor film a eșuat",
@@ -559,7 +559,7 @@
"CleanLibraryLevel": "Nivel de bibliotecă curat",
"BranchUpdate": "Sucursală de utilizat pentru actualizarea Radarr",
"BranchUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare",
"BypassProxyForLocalAddresses": "Nu folosiți Proxy pentru adrese locale",
"BypassProxyForLocalAddresses": "Bypass Proxy pentru adrese locale",
"MovieTitleHelpText": "Titlul filmului de exclus (poate avea orice sens)",
"Local": "Local",
"MovieYear": "Anul filmului",
@@ -641,7 +641,7 @@
"ClickToChangeQuality": "Faceți clic pentru a schimba calitatea",
"CloneFormatTag": "Etichetă de format clonare",
"CloneIndexer": "Clonă Indexer",
"CloneProfile": "Clonați profil",
"CloneProfile": "Profil de clonare",
"CloseCurrentModal": "Închideți modul curent",
"ColonReplacement": "Înlocuirea colonului",
"ColonReplacementFormatHelpText": "Schimbați modul în care Radarr gestionează înlocuirea colonului",
@@ -666,7 +666,7 @@
"DefaultDelayProfile": "Acesta este profilul implicit. Se aplică tuturor filmelor care nu au un profil explicit.",
"DelayingDownloadUntilInterp": "Întârzierea descărcării până la {0} la {1}",
"DelayProfile": "Profile de întârziere",
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de siguranță „{0}”?",
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de rezervă „{0}”?",
"DeleteDelayProfile": "Ștergeți profilul de întârziere",
"DeleteDownloadClientMessageText": "Sigur doriți să ștergeți clientul de descărcare „{0}”?",
"DeleteEmptyFolders": "Ștergeți folderele goale",
@@ -686,13 +686,13 @@
"DestinationPath": "Calea de destinație",
"DestinationRelativePath": "Calea relativă a destinației",
"DetailedProgressBar": "Bara de progres detaliată",
"Discord": "Discord",
"Docker": "Docker",
"Discord": "Discordie",
"Docker": "Docher",
"Donations": "Donații",
"DoneEditingGroups": "Efectuat editarea grupurilor",
"DoNotPrefer": "Nu preferați",
"DoNotUpgradeAutomatically": "Nu faceți upgrade automat",
"DownloadClientSettings": "Setări client de descărcare",
"DownloadClientSettings": "Descărcați setările clientului",
"DownloadClientUnavailable": "Clientul de descărcare nu este disponibil",
"DownloadedAndMonitored": "Descărcat (monitorizat)",
"DownloadedButNotMonitored": "Descărcat (fără monitorizare)",
@@ -708,10 +708,10 @@
"EditPerson": "Editați persoana",
"EditQualityProfile": "Editați profilul de calitate",
"EditRestriction": "Editați restricția",
"Enable": "Activați",
"Enable": "Permite",
"EnableAutoHelpText": "Dacă este activată, Filme vor fi adăugate automat la Radarr din această listă",
"EnableAutomaticAdd": "Activați adăugarea automată",
"EnableAutomaticSearch": "Activați căutarea automată",
"EnableAutomaticSearch": "Activați Căutarea automată",
"EnableAutomaticSearchHelpText": "Va fi utilizat atunci când căutările automate sunt efectuate prin interfața de utilizare sau de către Radarr",
"EnableColorImpairedMode": "Activați modul afectat de culoare",
"EnableColorImpairedModeHelpText": "Stil modificat pentru a permite utilizatorilor cu deficiențe de culoare să distingă mai bine informațiile codificate prin culoare",
@@ -722,7 +722,7 @@
"EnableMediaInfoHelpText": "Extrageți informații video, cum ar fi rezoluția, runtime și informații despre codec din fișiere. Acest lucru necesită ca Radarr să citească părți ale fișierului care pot provoca activitate ridicată pe disc sau rețea în timpul scanărilor.",
"EnableRSS": "Activați RSS",
"EnableSslHelpText": " Necesită repornirea în funcție de administrator pentru a intra în vigoare",
"Ended": "Finalizat",
"Ended": "Încheiat",
"ErrorLoadingContents": "Eroare la încărcarea conținutului",
"ErrorLoadingPreviews": "Eroare la încărcarea previzualizărilor",
"ErrorRestoringBackup": "Eroare la restaurarea copiei de rezervă",
@@ -751,7 +751,7 @@
"FollowPerson": "Urmărește persoana",
"ForMoreInformationOnTheIndividualDownloadClients": "Pentru mai multe informații despre clienții individuali de descărcare, faceți clic pe butoanele de informații.",
"ForMoreInformationOnTheIndividualIndexers": "Pentru mai multe informații despre indexatorii individuali, faceți clic pe butoanele de informații.",
"GeneralSettings": "Setări generale",
"GeneralSettings": "setari generale",
"Global": "Global",
"GoToInterp": "Accesați {0}",
"Grab": "Apuca",
@@ -776,7 +776,7 @@
"IncludeRecommendationsHelpText": "Includeți filmele recomandate de Radarr în vizualizarea descoperire",
"IncludeUnmonitored": "Includeți Unmonitored",
"ImportMovies": "Importați filme",
"IndexerPriority": "Prioritatea indexatorului",
"IndexerPriority": "Prioritatea indexerului",
"IndexerPriorityHelpText": "Prioritatea indexerului de la 1 (cea mai mare) la 50 (cea mai mică). Implicit: 25.",
"IndexerSettings": "Setări Indexer",
"InstallLatest": "Instalați cele mai recente",
@@ -875,7 +875,7 @@
"Retention": "Retenţie",
"RetentionHelpText": "Numai Usenet: Setați la zero pentru a seta pentru păstrarea nelimitată",
"RSS": "RSS",
"RSSIsNotSupportedWithThisIndexer": "RSS nu este suportat de acest indexator",
"RSSIsNotSupportedWithThisIndexer": "RSS nu este acceptat cu acest indexer",
"RSSSyncInterval": "Interval de sincronizare RSS",
"RSSSyncIntervalHelpTextWarning": "Acest lucru se va aplica tuturor indexatorilor, vă rugăm să urmați regulile stabilite de aceștia",
"SaveSettings": "Salvează setările",
@@ -974,7 +974,7 @@
"UnableToLoadGeneralSettings": "Nu se pot încărca setările generale",
"UnableToLoadHistory": "Istoricul nu poate fi încărcat",
"UnableToLoadIndexerOptions": "Nu se pot încărca opțiunile indexerului",
"UnableToLoadIndexers": "Nu se pot încărca indexatoarele",
"UnableToLoadIndexers": "Imposibil de încărcat indexatori",
"UnableToLoadLanguages": "Nu se pot încărca limbile",
"UnableToLoadListExclusions": "Imposibil de încărcat excluderile din listă",
"UnableToLoadManualImportItems": "Imposibil de încărcat articole de import manual",
@@ -1052,7 +1052,7 @@
"Blocklisted": "Listă Neagră",
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Sigur doriți să eliminați {0} elementul {1} din coadă?",
"BlocklistReleases": "Lansare pe lista neagră",
"Filters": "Filtre",
"Filters": "Filtru",
"List": "Liste",
"LocalPath": "Calea locală",
"RemotePath": "Calea la distanță",

View File

@@ -170,7 +170,7 @@
"ExcludeTitle": "Исключить {0}? Radarr не будет автоматически добавлять сканируя лист.",
"InCinemasMsg": "Фильмы в кинотеатрах",
"IncludeRecommendationsHelpText": "Включить в отображении найденного фильмы рекомендованные Radar",
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 25. Используется при захвате выпусков в качестве средства разрешения конфликтов для равных в остальном выпусков, Radarr по-прежнему будет использовать все включенные индексаторы для синхронизации и поиска RSS.",
"IndexerPriorityHelpText": "Приоритет индексаторов от 1 (наивысший) до 50 (низший). По-умолчанию: 25.",
"IndexerSearchCheckNoAvailableIndexersMessage": "Все индексаторы с возможностью поиска временно выключены из-за ошибок",
"KeyboardShortcuts": "Горячие клавиши",
"CantFindMovie": "Почему я не могу найти фильм?",
@@ -244,7 +244,7 @@
"ApplyTagsHelpTexts2": "Добавить: добавить ярлыки к существующему списку",
"AptUpdater": "Используйте apt для установки обновления",
"AuthForm": "Формы (Страница авторизации)",
"AreYouSureYouWantToRemoveSelectedItemsFromQueue": "Вы уверены, что хотите удалить {0} элемент из очереди?",
"AreYouSureYouWantToRemoveSelectedItemsFromQueue": "Вы уверены, что хотите удалить {0} элемент{1} из очереди?",
"BeforeUpdate": "До обновления",
"BuiltIn": "Встроено",
"CalendarOptions": "Настройки календаря",
@@ -742,12 +742,12 @@
"ChmodFolderHelpText": "Восьмеричный, применяется при импорте / переименовании к медиа-папкам и файлам (без битов выполнения)",
"CheckDownloadClientForDetails": "проверьте клиент загрузки для более подробной информации",
"CertValidationNoLocal": "Отключено для локальных адресов",
"CertificateValidationHelpText": "Измените строгую проверку сертификации HTTPS. Не меняйте, если вы не понимаете риски.",
"CertificateValidationHelpText": "Изменить строгое подтверждение сертификации НТТР",
"BypassProxyForLocalAddresses": "Обход прокси для локальных адресов",
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",
"BranchUpdate": "Ветвь для обновления Radarr",
"Branch": "Ветка",
"BindAddressHelpText": "Действительный IPv4-адрес или '*' для всех интерфейсов",
"BindAddressHelpText": "Действительный IP4-адрес или '*' для всех интерфейсов",
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData Radarr",
"AvailabilityDelayHelpText": "Время до или после доступной даты для поиска фильма",
"AvailabilityDelay": "Задержка доступности",
@@ -908,7 +908,7 @@
"TimeFormat": "Формат времени",
"Time": "Время",
"ThisConditionMatchesUsingRegularExpressions": "Это условие соответствует использованию регулярных выражений. Обратите внимание, что символы {0} имеют особое значение и требуют экранирования с помощью {1}",
"ThisCannotBeCancelled": "Это действие нельзя отменить после запуска без отключения всех ваших индексаторов.",
"ThisCannotBeCancelled": "Нельзя отменить после запуска без перезапуска Radarr.",
"TheLogLevelDefault": "Уровень журнала по умолчанию - «Информация» и может быть изменен в",
"TestAllLists": "Тестировать все листы",
"TestAllIndexers": "Тестировать все индексаторы",
@@ -982,7 +982,7 @@
"ShowCertification": "Показать сертификаты",
"ShowAsAllDayEvents": "Показывать как мероприятия на весь день",
"ShowAdvanced": "Показать расширенные",
"ShouldMonitorHelpText": "Если включено, фильмы или коллекции, добавленные в этот список, будут добавлены, как отслеживаемые",
"ShouldMonitorHelpText": "Если включено, фильмы, добавленные в этот список, добавляются и отслеживаются",
"SettingsWeekColumnHeaderHelpText": "Отображается над каждым столбцом, когда неделя активна",
"SettingsWeekColumnHeader": "Заголовок столбца недели",
"SettingsTimeFormat": "Формат времени",
@@ -1015,7 +1015,7 @@
"Security": "Безопасность",
"Seconds": "Секунды",
"SearchSelected": "Искать выделенные",
"SearchOnAddHelpText": "Искать фильмы в этом списке при добавлении в библиотеку",
"SearchOnAddHelpText": "Искать фильмы в этом списке при добавлении в Radarr",
"SearchOnAdd": "Искать при добавлении",
"SearchMovie": "Поиск фильма",
"SearchMissing": "Поиск пропавших",
@@ -1103,44 +1103,10 @@
"ImdbVotes": "IMDb оценок",
"Duration": "Длительность",
"Rating": "Рейтинг",
"List": "Список",
"RssSyncHelpText": "Интервал в минутах. Установите 0, чтобы выключить (остановит все автоматические захваты релизов)",
"AllCollectionsHiddenDueToFilter": "Все фильмы скрыты в соответствии с фильтром.",
"Collections": "Коллекции",
"List": "Списки",
"RssSyncHelpText": "Интервал в минутах. Установите 0 чтобы выключить (остановит все автоматические захваты релизов)",
"AllCollectionsHiddenDueToFilter": "Все фильмы спрятаны в соответствии с фильтром.",
"Collections": "Коллекция",
"MonitorMovies": "Отслеживать фильм",
"NoCollections": "Коллекции не найдены. Для начала вам нужно добавить новый фильм или импортировать несколько существующих.",
"CollectionOptions": "Параметры коллекции",
"CollectionShowDetailsHelpText": "Показать статус и свойства коллекции",
"CollectionShowOverviewsHelpText": "Показать обзоры коллекций",
"CollectionShowPostersHelpText": "Показать постеры предметов коллекции",
"EditCollection": "Редактировать коллекцию",
"Auto": "Авто",
"MonitorCollection": "Отслеживание коллекции",
"MonitoredCollectionHelpText": "Контролировать, чтобы фильмы из этой коллекции автоматически добавлялись в библиотеку",
"MovieAndCollection": "Фильм и коллекция",
"MovieCollectionMissingRoot": "Отсутствует корневая папка для коллекции фильмов: {0}",
"MovieCollectionMultipleMissingRoots": "Для коллекций фильмов отсутствуют несколько корневых папок: {0}",
"MovieOnly": "Только фильм",
"ShowCollectionDetails": "Показать статус коллекции",
"OnMovieAddedHelpText": "Добавлено в фильм",
"RefreshCollections": "Обновить коллекции",
"RefreshMonitoredIntervalHelpText": "Как часто обновлять отслеживаемые загрузки с клиентов загрузки, минимум 1 минута",
"Never": "Никогда",
"OriginalTitle": "Оригинальное название",
"CollectionsSelectedInterp": "{0} коллекций выбрано",
"UnableToLoadCollections": "Не удалось загрузить коллекции",
"OnMovieAdded": "Добавлено в фильм",
"OriginalLanguage": "Язык оригинала",
"SearchOnAddCollectionHelpText": "Поиск фильмов в этой коллекции при добавлении в библиотеку",
"ShowOverview": "Показать обзор",
"ShowPosters": "Показать постеры",
"TotalMovies": "Всего фильмов",
"Waiting": "Ожидание",
"ChooseImportMode": "Выберите режим импорта",
"InstanceName": "Имя экземпляра",
"InstanceNameHelpText": "Имя экземпляра на вкладке и для имени приложения системного журнала",
"RottenTomatoesRating": "Tomato рейтинг",
"Started": "Запущено",
"Database": "База данных",
"From": "из"
"NoCollections": "Фильмов не найдено. Для начала вам нужно добавить новый фильм или импортировать уже существующие."
}

View File

@@ -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 无法从movie根目录检测到空间时使用",
"SkipFreeSpaceCheckWhenImportingHelpText": "当 Radarr 无法从您的电影根文件夹中检测到空闲空间时使用",
"UnableToAddANewQualityProfilePleaseTryAgain": "无法添加新影片质量配置,请稍后重试。",
"ThisCannotBeCancelled": "在不禁用所有索引器的情况下,一旦启动就无法取消。",
"SearchCutoffUnmet": "搜索未满足终止条件的",
@@ -1135,13 +1135,5 @@
"NoCollections": "没有发现集合,点击添加新的电影或者导入已经存在的电影",
"InstanceNameHelpText": "选项卡及日志应用名称",
"ScrollMovies": "滚动电影",
"CollectionOptions": "集选项Collection Options",
"CollectionShowDetailsHelpText": "显示集合collection的状态和属性",
"CollectionShowOverviewsHelpText": "显示集合collection的概述",
"CollectionShowPostersHelpText": "显示集合项目海报",
"RottenTomatoesRating": "烂番茄指数",
"ShowPosters": "显示海报",
"OnMovieAdded": "电影添加时",
"OnMovieAddedHelpText": "电影添加时",
"TotalMovies": "电影总数"
"CollectionOptions": "Collection Options"
}

View File

@@ -1,14 +1,7 @@
{
"About": "關於",
"Add": "新增",
"Add": "添加",
"AcceptConfirmationModal": "接受確認模式",
"Actions": "執行",
"Activity": "‎活動‎",
"AddIndexer": "新增索引",
"AddingTag": "新增標籤",
"AddDownloadClient": "新增下載器",
"Added": "以新增",
"Age": "年齡",
"All": "全部",
"Analytics": "分析"
"Actions": "‎行動‎",
"Activity": "‎活動‎"
}

View File

@@ -5,7 +5,6 @@ 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;
@@ -261,20 +260,6 @@ 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))

View File

@@ -327,13 +327,6 @@ 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
@@ -344,16 +337,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
yearStr = $" {movie.Year}";
}
var newMovieObject = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault();
if (newMovieObject == null)
{
newMovie = null;
}
else
{
newMovie = newMovieObject.MovieMetadata;
}
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault().MovieMetadata;
}
if (newMovie == null)

View File

@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Movies.Collections
var collection = FindByTmdbId(collectionTmdbId);
_repo.Delete(collection.Id);
_repo.Delete(collectionTmdbId);
_eventAggregator.PublishEvent(new CollectionDeletedEvent(collection));
}

View File

@@ -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, destinationPath);
_logger.ProgressInfo("{0} moved successfully to {1}", movie.Title, movie.Path);
_eventAggregator.PublishEvent(new MovieMovedEvent(movie, sourcePath, destinationPath));
}

View File

@@ -11,7 +11,6 @@ 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);
@@ -28,14 +27,9 @@ namespace NzbDrone.Core.Movies
_logger = logger;
}
public MovieMetadata FindByTmdbId(int tmdbId)
public MovieMetadata FindByTmdbId(int tmdbid)
{
return Query(x => x.TmdbId == tmdbId).FirstOrDefault();
}
public MovieMetadata FindByImdbId(string imdbId)
{
return Query(x => x.ImdbId == imdbId).FirstOrDefault();
return Query(x => x.TmdbId == tmdbid).FirstOrDefault();
}
public List<MovieMetadata> FindById(List<int> tmdbIds)

View File

@@ -5,8 +5,7 @@ namespace NzbDrone.Core.Movies
public interface IMovieMetadataService
{
MovieMetadata Get(int id);
MovieMetadata FindByTmdbId(int tmdbId);
MovieMetadata FindByImdbId(string imdbId);
MovieMetadata FindByTmdbId(int tmdbid);
List<MovieMetadata> GetMoviesWithCollections();
List<MovieMetadata> GetMoviesByCollectionTmdbId(int collectionId);
bool Upsert(MovieMetadata movie);
@@ -22,14 +21,9 @@ namespace NzbDrone.Core.Movies
_movieMetadataRepository = movieMetadataRepository;
}
public MovieMetadata FindByTmdbId(int tmdbId)
public MovieMetadata FindByTmdbId(int tmdbid)
{
return _movieMetadataRepository.FindByTmdbId(tmdbId);
}
public MovieMetadata FindByImdbId(string imdbId)
{
return _movieMetadataRepository.FindByImdbId(imdbId);
return _movieMetadataRepository.FindByTmdbId(tmdbid);
}
public List<MovieMetadata> GetMoviesWithCollections()

View File

@@ -119,18 +119,10 @@ namespace NzbDrone.Core.Movies
return FindByTitle(new List<string> { title }, year, otherTitles, candidates);
}
public Movie FindByTitle(List<string> titles, int? year, List<string> otherTitles, List<Movie> candidates)
public Movie FindByTitle(List<string> cleanTitles, 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 =

View File

@@ -3,11 +3,12 @@ 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
{
@@ -18,7 +19,6 @@ 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,7 +27,6 @@ namespace NzbDrone.Core.Movies
IMovieService movieService,
IMovieMetadataService movieMetadataService,
IAddMovieService addMovieService,
IImportExclusionsService importExclusionsService,
Logger logger)
{
_movieInfo = movieInfo;
@@ -35,7 +34,6 @@ namespace NzbDrone.Core.Movies
_movieService = movieService;
_movieMetadataService = movieMetadataService;
_addMovieService = addMovieService;
_importExclusionService = importExclusionsService;
_logger = logger;
}
@@ -101,8 +99,7 @@ namespace NzbDrone.Core.Movies
{
var existingMovies = _movieService.AllMovieTmdbIds();
var collectionMovies = _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId);
var excludedMovies = _importExclusionService.GetAllExclusions().Select(e => e.TmdbId);
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId)).Where(m => !excludedMovies.Contains(m.TmdbId));
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId));
if (moviesToAdd.Any())
{

View File

@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Movies
SearchOnAdd = movie.AddOptions?.SearchForMovie ?? false,
QualityProfileId = movie.ProfileId,
MinimumAvailability = movie.MinimumAvailability,
RootFolderPath = _folderService.GetBestRootFolderPath(movie.Path).TrimEnd('/', '\\', ' ')
RootFolderPath = _folderService.GetBestRootFolderPath(movie.Path)
});
movieMetadata.CollectionTmdbId = newCollection.TmdbId;

View File

@@ -98,14 +98,6 @@ 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())

View File

@@ -11,9 +11,7 @@ namespace NzbDrone.Core.Notifications.Discord
Links,
Release,
Poster,
Fanart,
CustomFormats,
CustomFormatScore
Fanart
}
public enum DiscordImportFieldType

View File

@@ -48,8 +48,6 @@ 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);
}

View File

@@ -0,0 +1,8 @@
namespace NzbDrone.Core.Notifications.Notifiarr
{
public enum NotifiarrEnvironment
{
Live,
Development
}
}

View File

@@ -54,19 +54,17 @@ 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, "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 notification: " + ex.Message);
return new ValidationFailure("", "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");
_logger.Error(ex, "Unable to send test message: " + ex.Message);
return new ValidationFailure("APIKey", "Unable to send test notification");
}
catch (Exception ex)
{
@@ -79,7 +77,7 @@ namespace NzbDrone.Core.Notifications.Notifiarr
{
try
{
var url = "https://notifiarr.com";
var url = settings.Environment == (int)NotifiarrEnvironment.Development ? "https://dev.notifiarr.com" : "https://notifiarr.com";
var requestBuilder = new HttpRequestBuilder(url + "/api/v1/notification/radarr/" + settings.APIKey).Post();
requestBuilder.AddFormParameter("instanceName", settings.InstanceName).Build();
@@ -97,21 +95,19 @@ namespace NzbDrone.Core.Notifications.Notifiarr
switch ((int)ex.Response.StatusCode)
{
case 401:
_logger.Error("", "API key is invalid");
throw new NotifiarrException("API key is invalid", ex);
_logger.Error(ex, "API key is invalid");
throw;
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, "Cloudflare Related HTTP Error - Unable to send notification");
throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex);
_logger.Error(ex, "Unable to send notification");
throw;
}
throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex);
throw new NotifiarrException("Unable to send notification", ex);
}
}
}

View File

@@ -21,6 +21,8 @@ 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()
{

View File

@@ -1,66 +0,0 @@
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);
}
}
}

View File

@@ -1,18 +0,0 @@
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)
{
}
}
}

View File

@@ -1,11 +0,0 @@
namespace NzbDrone.Core.Notifications.Ntfy
{
public enum NtfyPriority
{
Min = 1,
Low = 2,
Default = 3,
High = 4,
Max = 5
}
}

Some files were not shown because too many files have changed in this diff Show More