1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-12 15:30:39 -04:00

Compare commits

..

4 Commits

Author SHA1 Message Date
ta264
c7c5d176be fixup! Modernize startup and ConfigFileProvider 2022-06-21 21:05:53 +01:00
ta264
31f082e516 Support legacy postgres options 2022-06-20 21:53:25 +01:00
ta264
a84ef3cd2f Rename configFileProvider 2022-06-20 21:35:11 +01:00
ta264
41e8f7aa45 Modernize startup and ConfigFileProvider 2022-06-20 21:35:11 +01:00
198 changed files with 2426 additions and 5370 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

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.2.2'
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'
@@ -173,6 +173,7 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --frontend
@@ -543,13 +544,13 @@ stages:
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
Radarr__PostgresHost: 'localhost'
Radarr__PostgresPort: '5432'
Radarr__PostgresUser: 'radarr'
Radarr__PostgresPassword: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
vmImage: 'ubuntu-18.04'
timeoutInMinutes: 10
@@ -576,7 +577,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: |
@@ -681,13 +681,13 @@ stages:
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
Radarr__PostgresHost: 'localhost'
Radarr__PostgresPort: '5432'
Radarr__PostgresUser: 'radarr'
Radarr__PostgresPassword: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
vmImage: 'ubuntu-18.04'
steps:
- task: UseDotNet@2
@@ -722,7 +722,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: |
@@ -977,6 +976,7 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --lint

View File

@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
@@ -190,7 +190,7 @@ class AddNewMovieSearchResult extends Component {
<div>
<Label size={sizes.LARGE}>
<TmdbRating
<HeartRating
ratings={ratings}
iconSize={13}
/>

View File

@@ -1,5 +1,4 @@
.container {
display: flex;
.movie {
padding: 10px 20px;
width: 100%;
@@ -7,19 +6,3 @@
background-color: $menuItemHoverBackgroundColor;
}
}
.movie {
flex: 1 0 0;
overflow: hidden;
}
.tmdbLink {
composes: link from '~Components/Link/Link.css';
margin-left: auto;
color: $textColor;
}
.tmdbLinkIcon {
margin-left: 10px;
}

View File

@@ -1,8 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import ImportMovieTitle from './ImportMovieTitle';
import styles from './ImportMovieSearchResult.css';
@@ -20,7 +18,6 @@ class ImportMovieSearchResult extends Component {
render() {
const {
tmdbId,
title,
year,
studio,
@@ -28,30 +25,17 @@ class ImportMovieSearchResult extends Component {
} = this.props;
return (
<div className={styles.container}>
<Link
className={styles.movie}
onPress={this.onPress}
>
<ImportMovieTitle
title={title}
year={year}
network={studio}
isExistingMovie={isExistingMovie}
/>
</Link>
<Link
className={styles.tmdbLink}
to={`https://www.themoviedb.org/movie/${tmdbId}`}
>
<Icon
className={styles.tmdbLinkIcon}
name={icons.EXTERNAL_LINK}
size={16}
/>
</Link>
</div>
<Link
className={styles.movie}
onPress={this.onPress}
>
<ImportMovieTitle
title={title}
year={year}
network={studio}
isExistingMovie={isExistingMovie}
/>
</Link>
);
}
}

View File

@@ -1,9 +1,6 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput';
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
@@ -25,9 +22,6 @@ class CollectionFooter extends Component {
this.state = {
monitor: NO_CHANGE,
monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE,
destinationRootFolder: null
};
}
@@ -42,10 +36,7 @@ class CollectionFooter extends Component {
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitor: NO_CHANGE,
qualityProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
minimumAvailability: NO_CHANGE
monitor: NO_CHANGE
});
}
@@ -64,10 +55,7 @@ class CollectionFooter extends Component {
onUpdateSelectedPress = () => {
const {
monitor,
monitored,
qualityProfileId,
minimumAvailability,
rootFolderPath
monitored
} = this.state;
const changes = {};
@@ -80,18 +68,6 @@ class CollectionFooter extends Component {
changes.monitor = monitor;
}
if (qualityProfileId !== NO_CHANGE) {
changes.qualityProfileId = qualityProfileId;
}
if (minimumAvailability !== NO_CHANGE) {
changes.minimumAvailability = minimumAvailability;
}
if (rootFolderPath !== NO_CHANGE) {
changes.rootFolderPath = rootFolderPath;
}
this.props.onUpdateSelectedPress(changes);
};
@@ -106,10 +82,7 @@ class CollectionFooter extends Component {
const {
monitored,
monitor,
qualityProfileId,
minimumAvailability,
rootFolderPath
monitor
} = this.state;
const monitoredOptions = [
@@ -152,52 +125,6 @@ class CollectionFooter extends Component {
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
<QualityProfileSelectInputConnector
name="qualityProfileId"
value={qualityProfileId}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MinimumAvailability')}
isSaving={isSaving && minimumAvailability !== NO_CHANGE}
/>
<AvailabilitySelectInput
name="minimumAvailability"
value={minimumAvailability}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
<RootFolderSelectInputConnector
name="rootFolderPath"
value={rootFolderPath}
includeNoChange={true}
isDisabled={!selectedCount}
selectedValueOptions={{ includeFreeSpace: false }}
onChange={this.onInputChange}
/>
</div>
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<CollectionFooterLabel

View File

@@ -0,0 +1,5 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

File diff suppressed because one or more lines are too long

View File

@@ -22,11 +22,11 @@ class ImdbRating extends PureComponent {
let ratingString = '0.0';
if (rating) {
ratingString = `${rating.value.toFixed(1)}`;
ratingString = `${rating.value}`;
}
return (
<span title={`${rating ? rating.votes : 0} votes`}>
<span title={`${rating.votes} votes`}>
{
!hideIcon &&
<img

View File

@@ -21,11 +21,9 @@ class RottenTomatoRating extends PureComponent {
const rating = ratings.rottenTomatoes;
let ratingString = '0%';
let ratingImage = rtFresh;
if (rating) {
ratingString = `${rating.value}%`;
ratingImage = rating.value > 50 ? rtFresh : rtRotten;
}
return (
@@ -34,7 +32,7 @@ class RottenTomatoRating extends PureComponent {
!hideIcon &&
<img
className={styles.image}
src={ratingImage}
src={rating.value > 50 ? rtFresh : rtRotten}
style={{
height: `${iconSize}px`
}}

View File

@@ -22,7 +22,7 @@ class TmdbRating extends PureComponent {
let ratingString = '0%';
if (rating) {
ratingString = `${(rating.value * 10).toFixed()}%`;
ratingString = `${rating.value * 10}%`;
}
return (

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import TmdbRating from 'Components/TmdbRating';
import HeartRating from 'Components/HeartRating';
import { getMovieStatusDetails } from 'Movie/MovieStatus';
import formatRuntime from 'Utilities/Date/formatRuntime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
@@ -111,7 +111,7 @@ function DiscoverMoviePosterInfo(props) {
if (sortKey === 'ratings' && ratings) {
return (
<div className={styles.info}>
<TmdbRating
<HeartRating
ratings={ratings}
/>
</div>

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import ImportListListConnector from 'Components/ImportListListConnector';
import IconButton from 'Components/Link/IconButton';
@@ -7,7 +8,6 @@ import Link from 'Components/Link/Link';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover';
import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal';
import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal';
@@ -245,7 +245,7 @@ class DiscoverMovieRow extends Component {
key={name}
className={styles[name]}
>
<TmdbRating
<HeartRating
ratings={ratings}
/>
</VirtualTableRowCell>

View File

@@ -102,21 +102,12 @@ function MovieIndexSortMenu(props) {
</SortMenuItem>
<SortMenuItem
name="imdbRating"
name="ratings"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('ImdbRating')}
</SortMenuItem>
<SortMenuItem
name="tmdbRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('TmdbRating')}
{translate('Ratings')}
</SortMenuItem>
<SortMenuItem

View File

@@ -77,9 +77,7 @@
flex: 0 0 120px;
}
.imdbRating,
.tmdbRating,
.rottenTomatoesRating {
.ratings {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 80px;

View File

@@ -84,9 +84,7 @@
flex: 0 0 120px;
}
.imdbRating,
.tmdbRating,
.rottenTomatoesRating {
.ratings {
composes: cell;
flex: 0 0 80px;

View File

@@ -1,15 +1,13 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TagListConnector from 'Components/TagListConnector';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds } from 'Helpers/Props';
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
@@ -351,39 +349,13 @@ class MovieIndexRow extends Component {
);
}
if (name === 'tmdbRating') {
if (name === 'ratings') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<TmdbRating
ratings={ratings}
/>
</VirtualTableRowCell>
);
}
if (name === 'rottenTomatoesRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<RottenTomatoRating
ratings={ratings}
/>
</VirtualTableRowCell>
);
}
if (name === 'imdbRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<ImdbRating
<HeartRating
ratings={ratings}
/>
</VirtualTableRowCell>

View File

@@ -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 : '', filterValue);
return predicate(collection ? collection.name : '', filterValue);
},
originalLanguage: function(item, filterValue, type) {
@@ -162,14 +162,6 @@ export const filterPredicates = {
return predicate(rating, filterValue);
},
rottenTomatoesRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.rottenTomatoes ? item.ratings.rottenTomatoes.value : 0;
return predicate(rating, filterValue);
},
imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[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
}
]
};
@@ -318,32 +254,27 @@ export const actionHandlers = handleThunks({
const {
collectionIds,
monitored,
monitor,
qualityProfileId,
rootFolderPath,
minimumAvailability
monitor
} = payload;
const response = {};
const collections = [];
if (payload.hasOwnProperty('monitored')) {
response.monitored = monitored;
}
collectionIds.forEach((id) => {
const collectionToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
collectionToUpdate.monitored = monitored;
}
collections.push(collectionToUpdate);
});
if (payload.hasOwnProperty('monitor')) {
response.monitorMovies = monitor === 'monitored';
}
if (payload.hasOwnProperty('qualityProfileId')) {
response.qualityProfileId = qualityProfileId;
}
if (payload.hasOwnProperty('minimumAvailability')) {
response.minimumAvailability = minimumAvailability;
}
response.rootFolderPath = rootFolderPath;
response.collectionIds = collectionIds;
response.collections = collections;
dispatch(set({
section,

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

@@ -178,20 +178,8 @@ export const defaultState = {
isVisible: true
},
{
name: 'tmdbRating',
label: translate('TmdbRating'),
isSortable: true,
isVisible: false
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
isSortable: true,
isVisible: false
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
name: 'ratings',
label: translate('Ratings'),
isSortable: true,
isVisible: false
},
@@ -236,22 +224,10 @@ export const defaultState = {
return originalLanguage.name;
},
imdbRating: function(item) {
ratings: function(item) {
const { ratings = {} } = item;
return ratings.imdb ? ratings.imdb.value : 0;
},
tmdbRating: function(item) {
const { ratings = {} } = item;
return ratings.tmdb ? ratings.tmdb.value : 0;
},
rottenTomatoesRating: function(item) {
const { ratings = {} } = item;
return ratings.rottenTomatoes ? ratings.rottenTomatoes.value : 0;
return ratings.tmdb? ratings.tmdb.value : 0;
}
},
@@ -341,8 +317,8 @@ export const defaultState = {
const collectionList = items.reduce((acc, movie) => {
if (movie.collection) {
acc.push({
id: movie.collection.title,
name: movie.collection.title
id: movie.collection.name,
name: movie.collection.name
});
}
@@ -437,11 +413,6 @@ export const defaultState = {
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),

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

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -13,7 +13,7 @@ namespace NzbDrone.Common.Test
{
[TestFixture]
public class ConfigFileProviderTest : TestBase<ConfigFileProvider>
public class ConfigFileWriterTest : TestBase<ConfigFileWriter>
{
private string _configFileContents;
private string _configFilePath;
@@ -45,56 +45,7 @@ namespace NzbDrone.Common.Test
.Callback<string, string>((p, t) => _configFileContents = t);
}
[Test]
public void GetValue_Success()
{
const string key = "Port";
const string value = "7878";
var result = Subject.GetValue(key, value);
result.Should().Be(value);
}
[Test]
public void GetInt_Success()
{
const string key = "Port";
const int value = 7878;
var result = Subject.GetValueInt(key, value);
result.Should().Be(value);
}
[Test]
public void GetBool_Success()
{
const string key = "LaunchBrowser";
const bool value = true;
var result = Subject.GetValueBoolean(key, value);
result.Should().BeTrue();
}
[Test]
public void GetLaunchBrowser_Success()
{
var result = Subject.LaunchBrowser;
result.Should().Be(true);
}
[Test]
public void GetPort_Success()
{
const int value = 7878;
var result = Subject.Port;
result.Should().Be(value);
}
/*
[Test]
public void SetValue_bool()
@@ -120,17 +71,6 @@ namespace NzbDrone.Common.Test
result.Should().Be(value);
}
[Test]
public void GetValue_New_Key()
{
const string key = "Hello";
const string value = "World";
var result = Subject.GetValue(key, value);
result.Should().Be(value);
}
[Test]
public void GetAuthenticationType_No_Existing_Value()
{
@@ -139,6 +79,7 @@ namespace NzbDrone.Common.Test
result.Should().Be(AuthenticationType.None);
}
/*
[Test]
public void SaveDictionary_should_save_proper_value()
{
@@ -170,32 +111,6 @@ namespace NzbDrone.Common.Test
Subject.Port.Should().Be(port);
Subject.SslPort.Should().Be(sslPort);
}
[Test]
public void should_throw_if_config_file_is_empty()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(_configFilePath))
.Returns(true);
Assert.Throws<InvalidConfigFileException>(() => Subject.GetValue("key", "value"));
}
[Test]
public void should_throw_if_config_file_contains_only_null_character()
{
_configFileContents = "\0";
Assert.Throws<InvalidConfigFileException>(() => Subject.GetValue("key", "value"));
}
[Test]
public void should_throw_if_config_file_contains_invalid_xml()
{
_configFileContents = "{ \"key\": \"value\" }";
Assert.Throws<InvalidConfigFileException>(() => Subject.GetValue("key", "value"));
}
}*/
}
}

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

@@ -2,6 +2,7 @@ using System.Linq;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
@@ -10,7 +11,7 @@ using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
@@ -32,7 +33,8 @@ namespace NzbDrone.Common.Test
.AddStartupContext(new StartupContext("first", "second"));
container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
container.RegisterInstance(new Mock<IConfiguration>().Object);
container.RegisterInstance(new Mock<IOptionsMonitor<ConfigFileOptions>>().Object);
var serviceProvider = container.GetServiceProvider();

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[^;=]*?(_?(?<!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

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using NLog;
using NLog.Fluent;
@@ -10,46 +8,47 @@ namespace NzbDrone.Common.Instrumentation.Extensions
{
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
{
return logBuilder.Property("Sentry", fingerprint);
}
public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
}
public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
}
public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
}
public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
{
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
}
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
{
SentryLogger.ForLogEvent(level)
.CopyLogEvent(logBuilder.LogEvent)
SentryLogger.Log(level)
.CopyLogEvent(logBuilder.LogEventInfo)
.SentryFingerprint(fingerprint)
.Log();
.Write();
return logBuilder.Property<string>("Sentry", null);
return logBuilder.Property("Sentry", null);
}
private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
{
return logBuilder.TimeStamp(logEvent.TimeStamp)
return logBuilder.LoggerName(logEvent.LoggerName)
.TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters)
.Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
.Exception(logEvent.Exception);
}
}

View File

@@ -1,16 +1,13 @@
using System;
using System.Text;
using NLog;
using NLog;
using NLog.Targets;
namespace NzbDrone.Common.Instrumentation
{
public class NzbDroneFileTarget : FileTarget
{
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
protected override string GetFormattedMessage(LogEventInfo logEvent)
{
var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
target.Append(result);
return CleanseLogMessage.Cleanse(Layout.Render(logEvent));
}
}
}

View File

@@ -34,8 +34,6 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached)
{
RegisterDebugger();
@@ -99,21 +97,10 @@ namespace NzbDrone.Common.Instrumentation
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
var loggingRule = new LoggingRule("*", LogLevel.Trace, target);
LogManager.Configuration.AddTarget("debugger", target);
LogManager.Configuration.LoggingRules.Add(loggingRule);
}
private static void RegisterGlobalFilters()
{
LogManager.Setup().LoadConfiguration(c =>
{
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
});
}
private static void RegisterConsole()
{
var level = LogLevel.Trace;

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

@@ -8,12 +8,12 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<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="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="3.15.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
<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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;

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

@@ -56,51 +56,6 @@ namespace NzbDrone.Core.Test.Datastore.Migration
movies.First().CollectionTmdbId.Should().Be(collections.First().TmdbId);
}
[Test]
public void should_skip_collection_from_movie_without_name()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Movies").Row(new
{
Monitored = true,
MinimumAvailability = 4,
ProfileId = 1,
MovieFileId = 0,
MovieMetadataId = 1,
Path = string.Format("/Movies/{0}", "Title"),
});
c.Insert.IntoTable("MovieMetadata").Row(new
{
Title = "Title",
CleanTitle = "CleanTitle",
Status = 3,
Images = new[] { new { CoverType = "Poster" } }.ToJson(),
Recommendations = new[] { 1 }.ToJson(),
Runtime = 90,
OriginalTitle = "Title",
CleanOriginalTitle = "CleanTitle",
OriginalLanguage = 1,
TmdbId = 132456,
Collection = new { TmdbId = 11 }.ToJson(),
LastInfoSync = DateTime.UtcNow,
});
});
var collections = db.Query<Collection208>("SELECT \"Id\", \"Title\", \"TmdbId\", \"Monitored\" FROM \"Collections\"");
collections.Should().HaveCount(1);
collections.First().TmdbId.Should().Be(11);
collections.First().Title.Should().Be("Collection 11");
collections.First().Monitored.Should().BeFalse();
var movies = db.Query<Movie208>("SELECT \"Id\", \"CollectionTmdbId\" FROM \"MovieMetadata\"");
movies.Should().HaveCount(1);
movies.First().CollectionTmdbId.Should().Be(collections.First().TmdbId);
}
[Test]
public void should_not_duplicate_collection()
{

View File

@@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Npgsql;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
@@ -124,13 +126,13 @@ namespace NzbDrone.Core.Test.Framework
private void CreatePostgresDb()
{
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
var options = Mocker.Resolve<IOptionsMonitor<ConfigFileOptions>>().CurrentValue;
PostgresDatabase.Create(options, MigrationType);
}
private void DropPostgresDb()
{
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
var options = Mocker.Resolve<IOptionsMonitor<ConfigFileOptions>>().CurrentValue;
PostgresDatabase.Drop(options, MigrationType);
}
@@ -174,12 +176,11 @@ namespace NzbDrone.Core.Test.Framework
SetupLogging();
// populate the possible postgres options
var postgresOptions = PostgresDatabase.GetTestOptions();
_databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
var options = PostgresDatabase.GetTestOptions();
_databaseType = options.PostgresHost.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
// Set up remaining container services
Mocker.SetConstant(Options.Create(postgresOptions));
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>().Setup(x => x.CurrentValue).Returns(options);
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());

View File

@@ -0,0 +1,51 @@
using System;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class LegacyPostgresCheckFixture : CoreTest<LegacyPostgresCheck>
{
[SetUp]
public void Setup()
{
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Warning {0} -> {1}");
}
[TearDown]
public void Teardown()
{
foreach (var name in new[] { "__Postgres__Host", "__Postgres__Port", ":Postgres:Host", ":Postgres:Port" })
{
Environment.SetEnvironmentVariable(BuildInfo.AppName + name, null);
}
}
[Test]
public void should_return_ok_normally()
{
Subject.Check().ShouldBeOk();
}
[TestCase("__")]
[TestCase(":")]
public void should_return_error_if_vars_defined(string separator)
{
Environment.SetEnvironmentVariable(BuildInfo.AppName + separator + "Postgres" + separator + "Host", "localhost");
Environment.SetEnvironmentVariable(BuildInfo.AppName + separator + "Postgres" + separator + "Port", "localhost");
var result = Subject.Check();
result.ShouldBeError("Warning " + BuildInfo.AppName + separator + "Postgres" + separator + "Host, " +
BuildInfo.AppName + separator + "Postgres" + separator + "Port -> " +
BuildInfo.AppName + separator + "PostgresHost, " +
BuildInfo.AppName + separator + "PostgresPort");
}
}
}

View File

@@ -1,9 +1,11 @@
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
@@ -20,9 +22,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
private void GivenValidBranch(string branch)
{
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.Branch)
.Returns(branch);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { Branch = branch });
}
[TestCase("aphrodite")]

View File

@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_ok_on_movie_imported_event()
{
GivenFolderExists(_downloadRootPath);
var importEvent = new MovieFileImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem());
var importEvent = new MovieImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem());
Subject.Check(importEvent).ShouldBeOk();
}

View File

@@ -1,3 +1,4 @@
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
@@ -19,6 +20,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(c => c.CurrentValue)
.Returns(new ConfigFileOptions());
}
[Test]
@@ -44,9 +49,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
const string startupFolder = @"/opt/nzbdrone";
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true });
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
@@ -67,9 +72,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI";
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true });
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
@@ -91,13 +96,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
{
PosixOnly();
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateMechanism)
.Returns(UpdateMechanism.Script);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true, UpdateMechanism = UpdateMechanism.Script });
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.HistoryTests
DownloadId = "abcd"
};
Subject.Handle(new MovieFileImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem));
Subject.Handle(new MovieImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem));
Mocker.GetMock<IHistoryRepository>()
.Verify(v => v.Insert(It.Is<MovieHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localMovie.Path))));

View File

@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
}
[Test]
public void should_delete_orphaned_collection_with_meta_but_no_movie_items()
public void should_not_delete_unorphaned_collection_items()
{
var collection = Builder<MovieCollection>.CreateNew()
.With(h => h.Id = 3)
@@ -40,27 +40,6 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
Db.Insert(movie);
Subject.Clean();
AllStoredModels.Should().HaveCount(0);
}
[Test]
public void should_not_delete_unorphaned_collection()
{
var collection = Builder<MovieCollection>.CreateNew()
.With(h => h.Id = 3)
.With(h => h.TmdbId = 123456)
.With(h => h.Title = "Some Credit")
.BuildNew();
Db.Insert(collection);
var movieMeta = Builder<MovieMetadata>.CreateNew().With(m => m.CollectionTmdbId = collection.TmdbId).BuildNew();
Db.Insert(movieMeta);
var movie = Builder<Movie>.CreateNew().With(m => m.MovieMetadataId = movieMeta.Id).BuildNew();
Db.Insert(movie);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}

View File

@@ -4,10 +4,12 @@ using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Events;
@@ -31,6 +33,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
.Build();
Mocker.GetMock<IMovieService>().Setup(m => m.GetMovie(It.Is<int>(id => id == _movie.Id))).Returns(_movie);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(x => x.CurrentValue)
.Returns(new ConfigFileOptions());
}
[Test]

View File

@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IEventAggregator>()
.Verify(v => v.PublishEvent(It.IsAny<MovieFileImportedEvent>()), Times.Once());
.Verify(v => v.PublishEvent(It.IsAny<MovieImportedEvent>()), Times.Once());
}
[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

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

@@ -342,16 +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.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -67,35 +67,6 @@ namespace NzbDrone.Core.Test.ParserTests
Parser.Parser.ParseMovieTitle(postTitle).PrimaryMovieTitle.Should().Be(title);
}
[TestCase("[MTBB] Kimi no Na wa. (2016) v2 [97681524].mkv", "Kimi no Na wa", "MTBB", 2016)]
[TestCase("[sam] Toward the Terra (1980) [BD 1080p TrueHD].mkv", "Toward the Terra", "sam", 1980)]
public void should_parse_anime_movie_title(string postTitle, string title, string releaseGroup, int year)
{
ParsedMovieInfo movie = Parser.Parser.ParseMovieTitle(postTitle);
using (new AssertionScope())
{
movie.PrimaryMovieTitle.Should().Be(title);
movie.ReleaseGroup.Should().Be(releaseGroup);
movie.Year.Should().Be(year);
}
}
[TestCase("[Arid] Cowboy Bebop - Knockin' on Heaven's Door v2 [00F4CDA0].mkv", "Cowboy Bebop - Knockin' on Heaven's Door", "Arid")]
[TestCase("[Baws] Evangelion 1.11 - You Are (Not) Alone v2 (1080p BD HEVC FLAC) [BF42B1C8].mkv", "Evangelion 1 11 - You Are (Not) Alone", "Baws")]
[TestCase("[Arid] 5 Centimeters per Second (BDRip 1920x1080 Hi10 FLAC) [FD8B6FF2].mkv", "5 Centimeters per Second", "Arid")]
[TestCase("[Baws] Evangelion 2.22 - You Can (Not) Advance (1080p BD HEVC FLAC) [56E7A5B8].mkv", "Evangelion 2 22 - You Can (Not) Advance", "Baws")]
[TestCase("[sam] Goblin Slayer - Goblin's Crown [BD 1080p FLAC] [CD298D48].mkv", "Goblin Slayer - Goblin's Crown", "sam")]
[TestCase("[Kulot] Violet Evergarden Gaiden Eien to Jidou Shuki Ningyou [Dual-Audio][BDRip 1920x804 HEVC FLACx2] [91FC62A8].mkv", "Violet Evergarden Gaiden Eien to Jidou Shuki Ningyou", "Kulot")]
public void should_parse_anime_movie_title_without_year(string postTitle, string title, string releaseGroup)
{
ParsedMovieInfo movie = Parser.Parser.ParseMovieTitle(postTitle);
using (new AssertionScope())
{
movie.PrimaryMovieTitle.Should().Be(title);
movie.ReleaseGroup.Should().Be(releaseGroup);
}
}
[TestCase("Movie.Aufbruch.nach.Pandora.Extended.2009.German.DTS.720p.BluRay.x264-SoW", "Movie Aufbruch nach Pandora", "Extended", 2009)]
[TestCase("Drop.Movie.1994.German.AC3D.DL.720p.BluRay.x264-KLASSiGERHD", "Drop Movie", "", 1994)]
[TestCase("Kick.Movie.2.2013.German.DTS.DL.720p.BluRay.x264-Pate", "Kick Movie 2", "", 2013)]

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)]
@@ -197,7 +189,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2020.MULTi.1080p.WEB.H264-ALLDAYiN (S:285/L:11)", false)]
[TestCase("Movie Title (2020) MULTi WEB 1080p x264-JiHEFF (S:317/L:28)", false)]
[TestCase("Movie.Titles.2020.1080p.NF.WEB.DD2.0.x264-SNEAkY", false)]
[TestCase("The.Movie.2022.NORDiC.1080p.DV.HDR.WEB.H 265-NiDHUG", false)]
public void should_parse_webdl1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R1080p);
@@ -216,7 +207,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Name.2016.03.14.2160p.WEB.PROPER.h264-spamTV", true)]
[TestCase("[HorribleSubs] Movie Title! 2018 [Web][MKV][h264][2160p][AAC 2.0][Softsubs (HorribleSubs)]", false)]
[TestCase("Movie Name 2020 WEB-DL 4K H265 10bit HDR DDP5.1 Atmos-PTerWEB", false)]
[TestCase("The.Movie.2022.NORDiC.2160p.DV.HDR.WEB.H.265-NiDHUG", false)]
public void should_parse_webdl2160p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R2160p);

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

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
@@ -60,9 +61,9 @@ namespace NzbDrone.Core.Test.UpdateTests
Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 });
Mocker.GetMock<IRuntimeInfo>().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\Radarr.exe");
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true });
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderWritable(It.IsAny<string>()))
@@ -77,13 +78,9 @@ namespace NzbDrone.Core.Test.UpdateTests
private void GivenInstallScript(string path)
{
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateMechanism)
.Returns(UpdateMechanism.Script);
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateScriptPath)
.Returns(path);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true, UpdateMechanism = UpdateMechanism.Script, UpdateScriptPath = path });
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(path, StringComparison.Ordinal))
@@ -334,7 +331,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IConfigFileProvider>()
Mocker.GetMock<IConfigFileWriter>()
.Verify(v => v.SaveConfigDictionary(It.Is<Dictionary<string, object>>(d => d.ContainsKey("Branch") && (string)d["Branch"] == "fake")), Times.Once());
}

View File

@@ -1,5 +1,6 @@
using System;
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
@@ -15,16 +16,16 @@ namespace NzbDrone.Core.Analytics
public class AnalyticsService : IAnalyticsService
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IHistoryService _historyService;
public AnalyticsService(IHistoryService historyService, IConfigFileProvider configFileProvider)
public AnalyticsService(IHistoryService historyService, IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_historyService = historyService;
}
public bool IsEnabled => (_configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction) || RuntimeInfo.IsDevelopment;
public bool IsEnabled => (_configFileOptions.CurrentValue.AnalyticsEnabled && RuntimeInfo.IsProduction) || RuntimeInfo.IsDevelopment;
public bool InstallIsActive
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -19,22 +20,22 @@ namespace NzbDrone.Core.Authentication
User FindUser(Guid identifier);
}
public class UserService : IUserService, IHandle<ApplicationStartedEvent>
public class UserService : IUserService
{
private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public UserService(IUserRepository repo,
IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_repo = repo;
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public User Add(string username, string password)
@@ -102,28 +103,5 @@ namespace NzbDrone.Core.Authentication
{
return _repo.FindUser(identifier);
}
public void Handle(ApplicationStartedEvent message)
{
if (_repo.All().Any())
{
return;
}
var xDoc = _configFileProvider.LoadConfigFile();
var config = xDoc.Descendants("Config").Single();
var usernameElement = config.Descendants("Username").FirstOrDefault();
var passwordElement = config.Descendants("Password").FirstOrDefault();
if (usernameElement == null || passwordElement == null)
{
return;
}
var username = usernameElement.Value;
var password = passwordElement.Value;
Add(username, password);
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using Microsoft.Extensions.Configuration;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration
{
public class ConfigFileOptions
{
[Persist]
public string BindAddress { get; set; } = "*";
[Persist]
public int Port { get; set; } = 7878;
[Persist]
public int SslPort { get; set; } = 9898;
[Persist]
public bool EnableSsl { get; set; }
[Persist]
public bool LaunchBrowser { get; set; } = true;
public AuthenticationType AuthenticationMethod { get; set; }
public bool AnalyticsEnabled { get; set; } = true;
[Persist]
public string Branch { get; set; } = "master";
[Persist]
public string LogLevel { get; set; } = "info";
public string ConsoleLogLevel { get; set; } = string.Empty;
public bool LogSql { get; set; }
public int LogRotate { get; set; } = 50;
public bool FilterSentryEvents { get; set; } = true;
[Persist]
public string ApiKey { get; set; } = GenerateApiKey();
[Persist]
public string SslCertPath { get; set; }
[Persist]
public string SslCertPassword { get; set; }
[Persist]
public string UrlBase { get; set; } = string.Empty;
[Persist]
public string InstanceName { get; set; } = BuildInfo.AppName;
public bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; } = UpdateMechanism.BuiltIn;
public string UpdateScriptPath { get; set; } = string.Empty;
public string SyslogServer { get; set; } = string.Empty;
public int SyslogPort { get; set; } = 514;
public string SyslogLevel { get; set; } = "info";
public string PostgresHost { get; set; }
public int PostgresPort { get; set; }
public string PostgresUser { get; set; }
public string PostgresPassword { get; set; }
public string PostgresMainDb { get; set; } = BuildInfo.AppName.ToLower() + "-main";
public string PostgresLogDb { get; set; } = BuildInfo.AppName.ToLower() + "-log";
private static string GenerateApiKey()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
public static ConfigFileOptions GetOptions()
{
var config = new ConfigurationBuilder()
.AddEnvironmentVariables($"{BuildInfo.AppName}:")
.Build();
var options = new ConfigFileOptions();
config.Bind(options);
return options;
}
}
}

View File

@@ -1,401 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration
{
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
IExecute<ResetApiKeyCommand>
{
XDocument LoadConfigFile();
Dictionary<string, object> GetConfigDictionary();
void SaveConfigDictionary(Dictionary<string, object> configValues);
string BindAddress { get; }
int Port { get; }
int SslPort { get; }
bool EnableSsl { get; }
bool LaunchBrowser { get; }
AuthenticationType AuthenticationMethod { get; }
bool AnalyticsEnabled { get; }
string LogLevel { get; }
string ConsoleLogLevel { get; }
bool LogSql { get; }
int LogRotate { get; }
bool FilterSentryEvents { get; }
string Branch { get; }
string ApiKey { get; }
string SslCertPath { get; }
string SslCertPassword { get; }
string UrlBase { get; }
string UiFolder { get; }
string InstanceName { get; }
bool UpdateAutomatically { get; }
UpdateMechanism UpdateMechanism { get; }
string UpdateScriptPath { get; }
string SyslogServer { get; }
int SyslogPort { get; }
string SyslogLevel { get; }
string PostgresHost { get; }
int PostgresPort { get; }
string PostgresUser { get; }
string PostgresPassword { get; }
string PostgresMainDb { get; }
string PostgresLogDb { get; }
}
public class ConfigFileProvider : IConfigFileProvider
{
public const string CONFIG_ELEMENT_NAME = "Config";
private readonly IEventAggregator _eventAggregator;
private readonly IDiskProvider _diskProvider;
private readonly ICached<string> _cache;
private readonly PostgresOptions _postgresOptions;
private readonly string _configFile;
private static readonly Regex HiddenCharacterRegex = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly object Mutex = new object();
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
ICacheManager cacheManager,
IEventAggregator eventAggregator,
IDiskProvider diskProvider,
IOptions<PostgresOptions> postgresOptions)
{
_cache = cacheManager.GetCache<string>(GetType());
_eventAggregator = eventAggregator;
_diskProvider = diskProvider;
_configFile = appFolderInfo.GetConfigPath();
_postgresOptions = postgresOptions.Value;
}
public Dictionary<string, object> GetConfigDictionary()
{
var dict = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
var type = GetType();
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
var value = propertyInfo.GetValue(this, null);
dict.Add(propertyInfo.Name, value);
}
return dict;
}
public void SaveConfigDictionary(Dictionary<string, object> configValues)
{
_cache.Clear();
var allWithDefaults = GetConfigDictionary();
foreach (var configValue in configValues)
{
if (configValue.Key.Equals("ApiKey", StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
object currentValue;
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
if (currentValue == null)
{
continue;
}
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
if (!equal)
{
SetValue(configValue.Key.FirstCharToUpper(), configValue.Value.ToString());
}
}
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
}
public string BindAddress
{
get
{
const string defaultValue = "*";
string bindAddress = GetValue("BindAddress", defaultValue);
if (string.IsNullOrWhiteSpace(bindAddress))
{
return defaultValue;
}
return bindAddress;
}
}
public int Port => GetValueInt("Port", 7878);
public int SslPort => GetValueInt("SslPort", 9898);
public bool EnableSsl => GetValueBoolean("EnableSsl", false);
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true);
public string ApiKey
{
get
{
var apiKey = GetValue("ApiKey", GenerateApiKey());
if (apiKey.IsNullOrWhiteSpace())
{
apiKey = GenerateApiKey();
SetValue("ApiKey", apiKey);
}
return apiKey;
}
}
public AuthenticationType AuthenticationMethod
{
get
{
var enabled = GetValueBoolean("AuthenticationEnabled", false, false);
if (enabled)
{
SetValue("AuthenticationMethod", AuthenticationType.Basic);
return AuthenticationType.Basic;
}
return GetValueEnum("AuthenticationMethod", AuthenticationType.None);
}
}
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
public string Branch => GetValue("Branch", "master").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "radarr-main", persist: false);
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "radarr-log", persist: false);
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => GetValue("SslCertPath", "");
public string SslCertPassword => GetValue("SslCertPassword", "");
public string UrlBase
{
get
{
var urlBase = GetValue("UrlBase", "").Trim('/');
if (urlBase.IsNullOrWhiteSpace())
{
return urlBase;
}
return "/" + urlBase.Trim('/').ToLower();
}
}
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName);
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false);
public UpdateMechanism UpdateMechanism => GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false);
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false);
public string SyslogServer => GetValue("SyslogServer", "", persist: false);
public int SyslogPort => GetValueInt("SyslogPort", 514, persist: false);
public string SyslogLevel => GetValue("SyslogLevel", LogLevel, false).ToLowerInvariant();
public int GetValueInt(string key, int defaultValue, bool persist = true)
{
return Convert.ToInt32(GetValue(key, defaultValue, persist));
}
public bool GetValueBoolean(string key, bool defaultValue, bool persist = true)
{
return Convert.ToBoolean(GetValue(key, defaultValue, persist));
}
public T GetValueEnum<T>(string key, T defaultValue, bool persist = true)
{
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), persist);
}
public string GetValue(string key, object defaultValue, bool persist = true)
{
return _cache.Get(key, () =>
{
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var parentContainer = config;
var valueHolder = parentContainer.Descendants(key).ToList();
if (valueHolder.Count == 1)
{
return valueHolder.First().Value.Trim();
}
//Save the value
if (persist)
{
SetValue(key, defaultValue);
}
//return the default value
return defaultValue.ToString();
});
}
public void SetValue(string key, object value)
{
var valueString = value.ToString().Trim();
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var parentContainer = config;
var keyHolder = parentContainer.Descendants(key);
if (keyHolder.Count() != 1)
{
parentContainer.Add(new XElement(key, valueString));
}
else
{
parentContainer.Descendants(key).Single().Value = valueString;
}
_cache.Set(key, valueString);
SaveConfigFile(xDoc);
}
public void SetValue(string key, Enum value)
{
SetValue(key, value.ToString().ToLower());
}
private void EnsureDefaultConfigFile()
{
if (!File.Exists(_configFile))
{
SaveConfigDictionary(GetConfigDictionary());
}
}
private void DeleteOldValues()
{
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var type = GetType();
var properties = type.GetProperties();
foreach (var configValue in config.Descendants().ToList())
{
var name = configValue.Name.LocalName;
if (!properties.Any(p => p.Name == name))
{
config.Descendants(name).Remove();
}
}
SaveConfigFile(xDoc);
}
public XDocument LoadConfigFile()
{
try
{
lock (Mutex)
{
if (_diskProvider.FileExists(_configFile))
{
var contents = _diskProvider.ReadAllText(_configFile);
if (contents.IsNullOrWhiteSpace())
{
throw new InvalidConfigFileException($"{_configFile} is empty. Please delete the config file and Radarr will recreate it.");
}
if (contents.All(char.IsControl))
{
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Radarr will recreate it.");
}
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
}
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc;
}
}
catch (XmlException ex)
{
throw new InvalidConfigFileException($"{_configFile} is corrupt is invalid. Please delete the config file and Radarr will recreate it.", ex);
}
}
private void SaveConfigFile(XDocument xDoc)
{
lock (Mutex)
{
_diskProvider.WriteAllText(_configFile, xDoc.ToString());
}
}
private string GenerateApiKey()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
public void HandleAsync(ApplicationStartedEvent message)
{
EnsureDefaultConfigFile();
DeleteOldValues();
}
public void Execute(ResetApiKeyCommand message)
{
SetValue("ApiKey", GenerateApiKey());
}
}
}

View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Configuration
{
public interface IConfigFileWriter : IHandleAsync<ApplicationStartedEvent>,
IExecute<ResetApiKeyCommand>
{
public void EnsureDefaultConfigFile();
void SaveConfigDictionary(Dictionary<string, object> configValues);
}
public class ConfigFileWriter : IConfigFileWriter
{
public static string CONFIG_ELEMENT_NAME = BuildInfo.AppName;
private readonly IEventAggregator _eventAggregator;
private readonly IDiskProvider _diskProvider;
private readonly IConfigurationRoot _configuration;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly Logger _logger;
private readonly string _configFile;
private static readonly object Mutex = new object();
public ConfigFileWriter(IAppFolderInfo appFolderInfo,
IEventAggregator eventAggregator,
IDiskProvider diskProvider,
IConfiguration configuration,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
Logger logger)
{
_eventAggregator = eventAggregator;
_diskProvider = diskProvider;
_configuration = configuration as IConfigurationRoot;
_configFileOptions = configFileOptions;
_logger = logger;
_configFile = appFolderInfo.GetConfigPath();
_configFileOptions.OnChange(OnChange);
}
private Dictionary<string, object> GetConfigDictionary()
{
var dict = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
var properties = typeof(ConfigFileOptions).GetProperties();
foreach (var propertyInfo in properties)
{
var value = propertyInfo.GetValue(_configFileOptions.CurrentValue, null);
dict.Add(propertyInfo.Name, value);
}
return dict;
}
public void SaveConfigDictionary(Dictionary<string, object> configValues)
{
var allWithDefaults = GetConfigDictionary();
var persistKeys = typeof(ConfigFileOptions).GetProperties()
.Where(x => Attribute.IsDefined(x, typeof(PersistAttribute)))
.Select(x => x.Name)
.ToList();
foreach (var configValue in configValues)
{
if (configValue.Key.Equals("ApiKey", StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
allWithDefaults.TryGetValue(configValue.Key, out var currentValue);
if (currentValue == null)
{
continue;
}
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
var persist = persistKeys.Contains(configValue.Key);
if (persist || !equal)
{
SetValue(configValue.Key.FirstCharToUpper(), configValue.Value.ToString());
}
}
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
}
public void SetValue(string key, object value)
{
var valueString = value.ToString().Trim();
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var keyHolder = config.Descendants(key);
if (keyHolder.Count() != 1)
{
config.Add(new XElement(key, valueString));
}
else
{
config.Descendants(key).Single().Value = valueString;
}
SaveConfigFile(xDoc);
}
public void EnsureDefaultConfigFile()
{
if (!File.Exists(_configFile))
{
SaveConfigDictionary(GetConfigDictionary());
SetValue(nameof(ConfigFileOptions.ApiKey), _configFileOptions.CurrentValue.ApiKey);
}
}
private void DeleteOldValues()
{
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var properties = typeof(ConfigFileOptions).GetProperties();
foreach (var configValue in config.Descendants().ToList())
{
var name = configValue.Name.LocalName;
if (!properties.Any(p => p.Name == name))
{
config.Descendants(name).Remove();
}
}
SaveConfigFile(xDoc);
}
public XDocument LoadConfigFile()
{
try
{
lock (Mutex)
{
if (_diskProvider.FileExists(_configFile))
{
var contents = _diskProvider.ReadAllText(_configFile);
if (contents.IsNullOrWhiteSpace())
{
throw new InvalidConfigFileException($"{_configFile} is empty. Please delete the config file and Radarr will recreate it.");
}
if (contents.All(char.IsControl))
{
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Radarr will recreate it.");
}
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
}
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc;
}
}
catch (XmlException ex)
{
throw new InvalidConfigFileException($"{_configFile} is corrupt is invalid. Please delete the config file and Radarr will recreate it.", ex);
}
}
private void SaveConfigFile(XDocument xDoc)
{
lock (Mutex)
{
_diskProvider.WriteAllText(_configFile, xDoc.ToString());
_configuration.Reload();
}
}
public void HandleAsync(ApplicationStartedEvent message)
{
DeleteOldValues();
}
public void Execute(ResetApiKeyCommand message)
{
SetValue(nameof(ConfigFileOptions.ApiKey), new ConfigFileOptions().ApiKey);
}
private void OnChange(ConfigFileOptions options)
{
_logger.Info("Config file updated");
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace NzbDrone.Core.Configuration
{
[AttributeUsage(AttributeTargets.Property)]
public class PersistAttribute : Attribute
{
}
}

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,23 +1,10 @@
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;
@@ -28,11 +15,7 @@ namespace NzbDrone.Core.CustomFormats
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

@@ -1,5 +1,6 @@
using System;
using System.Data.SQLite;
using Microsoft.Extensions.Options;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -16,16 +17,25 @@ namespace NzbDrone.Core.Datastore
public class ConnectionStringFactory : IConnectionStringFactory
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
// Catch legacy config, to be removed soon
private readonly PostgresOptions _postgresOptions;
public ConnectionStringFactory(IAppFolderInfo appFolderInfo,
IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_postgresOptions = PostgresOptions.GetOptions();
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
var isPostgres = _configFileOptions.CurrentValue.PostgresHost.IsNotNullOrWhiteSpace() || _postgresOptions.Host.IsNotNullOrWhiteSpace();
var mainDb = _configFileOptions.CurrentValue.PostgresMainDb ?? _postgresOptions.MainDb;
var logDb = _configFileOptions.CurrentValue.PostgresLogDb ?? _postgresOptions.LogDb;
MainDbConnectionString = isPostgres ? GetPostgresConnectionString(mainDb) :
GetConnectionString(appFolderInfo.GetDatabase());
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
LogDbConnectionString = isPostgres ? GetPostgresConnectionString(logDb) :
GetConnectionString(appFolderInfo.GetLogDatabase());
}
@@ -63,10 +73,10 @@ namespace NzbDrone.Core.Datastore
var connectionBuilder = new NpgsqlConnectionStringBuilder();
connectionBuilder.Database = dbName;
connectionBuilder.Host = _configFileProvider.PostgresHost;
connectionBuilder.Username = _configFileProvider.PostgresUser;
connectionBuilder.Password = _configFileProvider.PostgresPassword;
connectionBuilder.Port = _configFileProvider.PostgresPort;
connectionBuilder.Host = _configFileOptions.CurrentValue.PostgresHost ?? _postgresOptions.Host;
connectionBuilder.Username = _configFileOptions.CurrentValue.PostgresUser ?? _postgresOptions.User;
connectionBuilder.Password = _configFileOptions.CurrentValue.PostgresPassword ?? _postgresOptions.Password;
connectionBuilder.Port = _configFileOptions.CurrentValue.PostgresPort > 0 ? _configFileOptions.CurrentValue.PostgresPort : _postgresOptions.Port;
connectionBuilder.Enlist = false;
return connectionBuilder.ConnectionString;

View File

@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Datastore.Migration
var moviePath = reader.GetString(3);
var data = STJson.Deserialize<MovieCollection207>(collection);
if (data.TmdbId == 0 || newCollections.Any(d => d.TmdbId == data.TmdbId))
if (newCollections.Any(d => d.TmdbId == data.TmdbId))
{
continue;
}
@@ -104,17 +104,15 @@ namespace NzbDrone.Core.Datastore.Migration
rootFolderPath = moviePath.GetParentPath();
}
var collectionName = data.Name ?? $"Collection {data.TmdbId}";
newCollections.Add(new MovieCollection208
{
TmdbId = data.TmdbId,
Title = collectionName,
CleanTitle = collectionName.CleanMovieTitle(),
SortTitle = Parser.Parser.NormalizeTitle(collectionName),
Title = data.Name,
CleanTitle = data.Name.CleanMovieTitle(),
SortTitle = Parser.Parser.NormalizeTitle(data.Name),
Added = added,
QualityProfileId = qualityProfileId,
RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
RootFolderPath = rootFolderPath,
SearchOnAdd = true,
MinimumAvailability = minimumAvailability
});

View File

@@ -1,15 +0,0 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(211)]
public class more_movie_meta_index : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.Index("IX_AlternativeTitles_MovieMetadataId").OnTable("AlternativeTitles").OnColumn("MovieMetadataId");
Create.Index("IX_Credits_MovieMetadataId").OnTable("Credits").OnColumn("MovieMetadataId");
}
}
}

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,4 +1,5 @@
using Microsoft.Extensions.Configuration;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.Datastore
{
@@ -18,7 +19,7 @@ namespace NzbDrone.Core.Datastore
.Build();
var postgresOptions = new PostgresOptions();
config.GetSection("Radarr:Postgres").Bind(postgresOptions);
config.GetSection($"{BuildInfo.AppName}:Postgres").Bind(postgresOptions);
return postgresOptions;
}

View File

@@ -180,14 +180,14 @@ namespace NzbDrone.Core.Download
}
else
{
_logger.ForDebugEvent()
_logger.Debug()
.Message("No Movies were just imported, but all movies were previously imported, possible issue with download history.")
.Property("MovieId", trackedDownload.RemoteMovie.Movie.Id)
.Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
.Property("Title", trackedDownload.DownloadItem.Title)
.Property("Path", trackedDownload.ImportItem.OutputPath.ToString())
.WriteSentryWarn("DownloadHistoryIncomplete")
.Log();
.Write();
}
trackedDownload.State = TrackedDownloadState.Imported;

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download.History
public class DownloadHistoryService : IDownloadHistoryService,
IHandle<MovieGrabbedEvent>,
IHandle<MovieFileImportedEvent>,
IHandle<MovieImportedEvent>,
IHandle<DownloadCompletedEvent>,
IHandle<DownloadFailedEvent>,
IHandle<DownloadIgnoredEvent>,
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Download.History
_repository.Insert(history);
}
public void Handle(MovieFileImportedEvent message)
public void Handle(MovieImportedEvent message)
{
if (!message.NewDownload)
{

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
public class DownloadMonitoringService : IExecute<RefreshMonitoredDownloadsCommand>,
IExecute<CheckForFinishedDownloadCommand>,
IHandle<MovieGrabbedEvent>,
IHandle<MovieFileImportedEvent>,
IHandle<MovieImportedEvent>,
IHandle<DownloadsProcessedEvent>,
IHandle<TrackedDownloadsRemovedEvent>
{
@@ -167,7 +167,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_refreshDebounce.Execute();
}
public void Handle(MovieFileImportedEvent message)
public void Handle(MovieImportedEvent message)
{
_refreshDebounce.Execute();
}

View File

@@ -158,32 +158,22 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{
var setRating = new XElement("ratings");
var defaultRatingSet = false;
if (movie.MovieMetadata.Value.Ratings.Tmdb?.Votes > 0)
{
var setRatethemoviedb = new XElement("rating", new XAttribute("name", "themoviedb"), new XAttribute("max", "10"), new XAttribute("default", "true"));
setRatethemoviedb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Tmdb.Value));
setRatethemoviedb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Tmdb.Votes));
setRating.Add(setRatethemoviedb);
}
if (movie.MovieMetadata.Value.Ratings.Imdb?.Votes > 0)
{
var setRateImdb = new XElement("rating", new XAttribute("name", "imdb"), new XAttribute("max", "10"), new XAttribute("default", "true"));
var setRateImdb = new XElement("rating", new XAttribute("name", "imdb"), new XAttribute("max", "10"));
setRateImdb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Imdb.Value));
setRateImdb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Imdb.Votes));
defaultRatingSet = true;
setRating.Add(setRateImdb);
}
if (movie.MovieMetadata.Value.Ratings.Tmdb?.Votes > 0)
{
var setRatethemoviedb = new XElement("rating", new XAttribute("name", "themoviedb"), new XAttribute("max", "10"));
setRatethemoviedb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Tmdb.Value));
setRatethemoviedb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Tmdb.Votes));
if (!defaultRatingSet)
{
setRatethemoviedb.SetAttributeValue("default", "true");
}
setRating.Add(setRatethemoviedb);
}
details.Add(setRating);
}
@@ -305,8 +295,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

@@ -10,7 +10,7 @@ using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderUpdatedEvent<IImportList>))]
[CheckOn(typeof(MovieFileImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieImportFailedEvent), CheckOnCondition.SuccessfulOnly)]
public class ImportListRootFolderCheck : HealthCheckBase
{

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections;
using System.Linq;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Localization;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class LegacyPostgresCheck : HealthCheckBase
{
private readonly Logger _logger;
public LegacyPostgresCheck(ILocalizationService localizationService, Logger logger)
: base(localizationService)
{
_logger = logger;
}
public override HealthCheck Check()
{
var legacyVars = Environment
.GetEnvironmentVariables()
.Cast<DictionaryEntry>()
.Select(x => x.Key.ToString())
.Where(k => k.StartsWith(BuildInfo.AppName + "__Postgres__") || k.StartsWith(BuildInfo.AppName + ":Postgres:"))
.ToList();
if (legacyVars.Count == 0)
{
return new HealthCheck(GetType());
}
var legacyString = legacyVars.OrderBy(x => x).ConcatToString();
var newString = legacyString
.Replace(BuildInfo.AppName + "__Postgres__", BuildInfo.AppName + "__Postgres")
.Replace(BuildInfo.AppName + ":Postgres:", BuildInfo.AppName + ":Postgres");
return new HealthCheck(GetType(),
HealthCheckResult.Error,
string.Format(_localizationService.GetLocalizedString("PostgresLegacyEnvironmentVariables"), legacyString, newString));
}
public override bool CheckOnSchedule => false;
}
}

View File

@@ -2,15 +2,13 @@ using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ModelEvent<RootFolder>))]
[CheckOn(typeof(CollectionEditedEvent), CheckOnCondition.Always)]
public class MovieCollectionRootFolderCheck : HealthCheckBase
{
private readonly IMovieCollectionService _collectionService;

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Localization;
@@ -9,9 +10,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
[CheckOn(typeof(ConfigSavedEvent))]
public class ReleaseBranchCheck : HealthCheckBase
{
private readonly IConfigFileProvider _configFileService;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileService;
public ReleaseBranchCheck(IConfigFileProvider configFileService, ILocalizationService localizationService)
public ReleaseBranchCheck(IOptionsMonitor<ConfigFileOptions> configFileService, ILocalizationService localizationService)
: base(localizationService)
{
_configFileService = configFileService;
@@ -19,11 +20,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var currentBranch = _configFileService.Branch.ToLower();
var currentBranch = _configFileService.CurrentValue.Branch.ToLower();
if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch-is-not-a-valid-release-branch");
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.CurrentValue.Branch), "#branch-is-not-a-valid-release-branch");
}
return new HealthCheck(GetType());

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
[CheckOn(typeof(MovieFileImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieImportFailedEvent), CheckOnCondition.SuccessfulOnly)]
public class RemotePathMappingCheck : HealthCheckBase, IProvideHealthCheck
{

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -16,13 +17,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
private readonly IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo;
private readonly ICheckUpdateService _checkUpdateService;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IOsInfo _osInfo;
public UpdateCheck(IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo,
ICheckUpdateService checkUpdateService,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
IOsInfo osInfo,
ILocalizationService localizationService)
: base(localizationService)
@@ -30,7 +31,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
_diskProvider = diskProvider;
_appFolderInfo = appFolderInfo;
_checkUpdateService = checkUpdateService;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_osInfo = osInfo;
}
@@ -39,8 +40,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI");
if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) &&
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
if ((OsInfo.IsWindows || _configFileOptions.CurrentValue.UpdateAutomatically) &&
_configFileOptions.CurrentValue.UpdateMechanism == UpdateMechanism.BuiltIn &&
!_osInfo.IsDocker)
{
if (OsInfo.IsOsx && startupFolder.GetAncestorFolders().Contains("AppTranslocation"))

View File

@@ -77,15 +77,12 @@ namespace NzbDrone.Core.HealthCheck
.ToDictionary(g => g.Key, g => g.ToArray());
}
private void PerformHealthCheck(IProvideHealthCheck[] healthChecks, bool performServerChecks = false)
private void PerformHealthCheck(IProvideHealthCheck[] healthChecks)
{
var results = healthChecks.Select(c => c.Check())
.ToList();
if (performServerChecks)
{
results.AddRange(_serverSideNotificationService.GetServerChecks());
}
results.AddRange(_serverSideNotificationService.GetServerChecks());
foreach (var result in results)
{
@@ -111,17 +108,17 @@ namespace NzbDrone.Core.HealthCheck
{
if (message.Trigger == CommandTrigger.Manual)
{
PerformHealthCheck(_healthChecks, true);
PerformHealthCheck(_healthChecks);
}
else
{
PerformHealthCheck(_scheduledHealthChecks, true);
PerformHealthCheck(_scheduledHealthChecks);
}
}
public void HandleAsync(ApplicationStartedEvent message)
{
PerformHealthCheck(_startupHealthChecks, true);
PerformHealthCheck(_startupHealthChecks);
}
public void HandleAsync(IEvent message)

View File

@@ -2,8 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
@@ -20,41 +20,28 @@ namespace NzbDrone.Core.HealthCheck
public class ServerSideNotificationService : IServerSideNotificationService
{
private readonly IHttpClient _client;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
private readonly Logger _logger;
private readonly ICached<List<HealthCheck>> _cache;
public ServerSideNotificationService(IHttpClient client,
IConfigFileProvider configFileProvider,
IRadarrCloudRequestBuilder cloudRequestBuilder,
ICacheManager cacheManager,
Logger logger)
public ServerSideNotificationService(IHttpClient client, IOptionsMonitor<ConfigFileOptions> configFileOptions, IRadarrCloudRequestBuilder cloudRequestBuilder, Logger logger)
{
_client = client;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_cloudRequestBuilder = cloudRequestBuilder.Services;
_logger = logger;
_cache = cacheManager.GetCache<List<HealthCheck>>(GetType());
}
public List<HealthCheck> GetServerChecks()
{
return _cache.Get("ServerChecks", () => RetrieveServerChecks(), TimeSpan.FromHours(2));
}
private List<HealthCheck> RetrieveServerChecks()
{
var request = _cloudRequestBuilder.Create()
.Resource("/notification")
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("branch", _configFileProvider.Branch)
.Build();
.Resource("/notification")
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("branch", _configFileOptions.CurrentValue.Branch)
.Build();
try
{
_logger.Trace("Getting server side health notifications");

View File

@@ -27,13 +27,13 @@ namespace NzbDrone.Core.History
List<MovieHistory> FindByDownloadId(string downloadId);
List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType);
void UpdateMany(List<MovieHistory> toUpdate);
string FindDownloadId(MovieFileImportedEvent trackedDownload);
string FindDownloadId(MovieImportedEvent trackedDownload);
List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType);
}
public class HistoryService : IHistoryService,
IHandle<MovieGrabbedEvent>,
IHandle<MovieFileImportedEvent>,
IHandle<MovieImportedEvent>,
IHandle<DownloadFailedEvent>,
IHandle<MovieFileDeletedEvent>,
IHandle<MovieFileRenamedEvent>,
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.History
_historyRepository.UpdateMany(toUpdate);
}
public string FindDownloadId(MovieFileImportedEvent trackedDownload)
public string FindDownloadId(MovieImportedEvent trackedDownload)
{
_logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedMovie.Path);
@@ -170,7 +170,7 @@ namespace NzbDrone.Core.History
_historyRepository.Insert(history);
}
public void Handle(MovieFileImportedEvent message)
public void Handle(MovieImportedEvent message)
{
if (!message.NewDownload)
{

View File

@@ -16,10 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ""Collections"" WHERE ""TmdbId"" IN (SELECT ""X"".""TmdbId"" FROM (SELECT ""Collections"".""TmdbId"", COUNT(""Movies"".""Id"") as ""MovieCount"" FROM ""Collections""
LEFT OUTER JOIN ""MovieMetadata"" ON ""Collections"".""TmdbId"" = ""MovieMetadata"".""CollectionTmdbId""
LEFT OUTER JOIN ""Movies"" ON ""Movies"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
GROUP BY ""Collections"".""Id"") AS ""X"" WHERE ""X"".""MovieCount"" = 0)");
mapper.Execute(@"DELETE FROM ""Collections""
WHERE ""TmdbId"" IN (
SELECT ""Collections"".""TmdbId"" FROM ""Collections""
LEFT OUTER JOIN ""MovieMetadata""
ON ""Collections"".""TmdbId"" = ""MovieMetadata"".""CollectionTmdbId""
WHERE ""MovieMetadata"".""Id"" IS NULL)");
}
}
}

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");
}
}
@@ -43,17 +23,5 @@ namespace NzbDrone.Core.ImportLists.Trakt.Popular
[FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TraktPopularListType), HelpText = "Type of list you're seeking to import from")]
public int TraktListType { get; set; }
[FieldDefinition(2, Label = "Rating", HelpText = "Filter movies by rating range (0-100)")]
public string Rating { get; set; }
[FieldDefinition(3, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
public string Certification { get; set; }
[FieldDefinition(4, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
public string Genres { get; set; }
[FieldDefinition(5, Label = "Years", HelpText = "Filter movies by year or year range")]
public string Years { get; set; }
}
}

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

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
using NLog;
using NLog.Config;
using NLog.Targets.Syslog;
@@ -17,21 +18,21 @@ namespace NzbDrone.Core.Instrumentation
{
public class ReconfigureLogging : IHandleAsync<ConfigFileSavedEvent>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public ReconfigureLogging(IConfigFileProvider configFileProvider)
public ReconfigureLogging(IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public void Reconfigure()
{
var minimumLogLevel = LogLevel.FromString(_configFileProvider.LogLevel);
var minimumLogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.LogLevel);
LogLevel minimumConsoleLogLevel;
if (_configFileProvider.ConsoleLogLevel.IsNotNullOrWhiteSpace())
if (_configFileOptions.CurrentValue.ConsoleLogLevel.IsNotNullOrWhiteSpace())
{
minimumConsoleLogLevel = LogLevel.FromString(_configFileProvider.ConsoleLogLevel);
minimumConsoleLogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.ConsoleLogLevel);
}
else if (minimumLogLevel > LogLevel.Info)
{
@@ -42,10 +43,10 @@ namespace NzbDrone.Core.Instrumentation
minimumConsoleLogLevel = LogLevel.Info;
}
if (_configFileProvider.SyslogServer.IsNotNullOrWhiteSpace())
if (_configFileOptions.CurrentValue.SyslogServer.IsNotNullOrWhiteSpace())
{
var syslogLevel = LogLevel.FromString(_configFileProvider.SyslogLevel);
SetSyslogParameters(_configFileProvider.SyslogServer, _configFileProvider.SyslogPort, syslogLevel);
var syslogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.SyslogLevel);
SetSyslogParameters(_configFileOptions.CurrentValue.SyslogServer, _configFileOptions.CurrentValue.SyslogPort, syslogLevel);
}
var rules = LogManager.Configuration.LoggingRules;
@@ -60,7 +61,7 @@ namespace NzbDrone.Core.Instrumentation
SetLogRotation();
//Log Sql
SqlBuilderExtensions.LogSql = _configFileProvider.LogSql;
SqlBuilderExtensions.LogSql = _configFileOptions.CurrentValue.LogSql;
//Sentry
ReconfigureSentry();
@@ -95,7 +96,7 @@ namespace NzbDrone.Core.Instrumentation
{
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
{
target.MaxArchiveFiles = _configFileProvider.LogRotate;
target.MaxArchiveFiles = _configFileOptions.CurrentValue.LogRotate;
}
}
@@ -104,8 +105,8 @@ namespace NzbDrone.Core.Instrumentation
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.SentryEnabled = (RuntimeInfo.IsProduction && _configFileProvider.AnalyticsEnabled) || RuntimeInfo.IsDevelopment;
sentryTarget.FilterEvents = _configFileProvider.FilterSentryEvents;
sentryTarget.SentryEnabled = (RuntimeInfo.IsProduction && _configFileOptions.CurrentValue.AnalyticsEnabled) || RuntimeInfo.IsDevelopment;
sentryTarget.FilterEvents = _configFileOptions.CurrentValue.FilterSentryEvents;
}
}
@@ -117,8 +118,9 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
syslogTarget.MessageSend.Udp.Port = syslogPort;
syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileOptions.CurrentValue.InstanceName;
var loggingRule = new LoggingRule("*", minimumLogLevel, syslogTarget);

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System.Linq;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Sentry;
@@ -11,15 +12,15 @@ namespace NzbDrone.Core.Instrumentation
{
public class ReconfigureSentry : IHandleAsync<ApplicationStartedEvent>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IPlatformInfo _platformInfo;
private readonly IMainDatabase _database;
public ReconfigureSentry(IConfigFileProvider configFileProvider,
public ReconfigureSentry(IOptionsMonitor<ConfigFileOptions> configFileOptions,
IPlatformInfo platformInfo,
IMainDatabase database)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_platformInfo = platformInfo;
_database = database;
}
@@ -30,7 +31,7 @@ namespace NzbDrone.Core.Instrumentation
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileProvider.Branch, _platformInfo);
sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileOptions.CurrentValue.Branch, _platformInfo);
}
}

View File

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

File diff suppressed because it is too large Load Diff

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