mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-12 15:30:39 -04:00
Compare commits
4 Commits
v4.2.2.650
...
update-con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c5d176be | ||
|
|
31f082e516 | ||
|
|
a84ef3cd2f | ||
|
|
41e8f7aa45 |
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"paths": [
|
||||
"frontend/src/**/*.js"
|
||||
"frontend/src/**/*.js",
|
||||
"src/NzbDrone.Core/Localization/Core/*.json"
|
||||
],
|
||||
"ignored": [
|
||||
"**/node_modules/**/*"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
5
frontend/src/Components/HeartRating.css
Normal file
5
frontend/src/Components/HeartRating.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.image {
|
||||
align-content: center;
|
||||
margin-right: 5px;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
69
frontend/src/Components/HeartRating.js
Normal file
69
frontend/src/Components/HeartRating.js
Normal file
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -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`
|
||||
}}
|
||||
|
||||
@@ -22,7 +22,7 @@ class TmdbRating extends PureComponent {
|
||||
let ratingString = '0%';
|
||||
|
||||
if (rating) {
|
||||
ratingString = `${(rating.value * 10).toFixed()}%`;
|
||||
ratingString = `${rating.value * 10}%`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -77,9 +77,7 @@
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
.imdbRating,
|
||||
.tmdbRating,
|
||||
.rottenTomatoesRating {
|
||||
.ratings {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 80px;
|
||||
|
||||
@@ -84,9 +84,7 @@
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
.imdbRating,
|
||||
.tmdbRating,
|
||||
.rottenTomatoesRating {
|
||||
.ratings {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 80px;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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>());
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
src/NzbDrone.Core/Configuration/ConfigFileOptions.cs
Normal file
71
src/NzbDrone.Core/Configuration/ConfigFileOptions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
219
src/NzbDrone.Core/Configuration/ConfigFileWriter.cs
Normal file
219
src/NzbDrone.Core/Configuration/ConfigFileWriter.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/NzbDrone.Core/Configuration/PersistAttribute.cs
Normal file
9
src/NzbDrone.Core/Configuration/PersistAttribute.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class PersistAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
47
src/NzbDrone.Core/HealthCheck/Checks/LegacyPostgresCheck.cs
Normal file
47
src/NzbDrone.Core/HealthCheck/Checks/LegacyPostgresCheck.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user