1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

49 Commits

Author SHA1 Message Date
bakerboy448 0054318658 Fixed: Parse Group ZØNEHD 2022-07-18 22:23:41 -05:00
bakerboy448 03a3f4522a New: Parse Group HONE 2022-07-18 22:23:41 -05:00
bakerboy448 3d3562dcda New: (Discord) Include Custom Formats & Score On Grab
Fixes #6733
2022-07-18 21:24:37 -05:00
Weblate 7a079c5e0c Translated using Weblate (Catalan) [skip ci]
Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 0.1% (1 of 1145 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 99.9% (1144 of 1145 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1145 of 1145 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.3% (1125 of 1144 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: M0C <pigers@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Sytha <tharaud.sylvain@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: libsu <libsu@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-07-18 20:17:12 -05:00
Terebi42 4d70798f2f Fixed: User Triggered Auto Searches now ignores monitored status (#7422) 2022-07-18 18:25:46 -05:00
Robin Dadswell d55864f869 Fixed: Postgres timezone issues (#7183)
[common]

Co-authored-by: ta264 <ta264@users.noreply.github.com>
2022-07-18 14:57:15 +01:00
Qstick 3c41c84fb0 Speed up and reduce meta calls for Imdb Lists when mapping 2022-07-17 12:57:00 -05:00
Qstick eae9a6d6e0 Fixed: ImportListMovies not saved if from a list without TMDBIds 2022-07-17 12:55:13 -05:00
Mark Mckessock 867f8f5835 Match 'HQCAM' as CAM source (#7412)
* Add HQCAM source regex

* Add cam testcases
2022-07-15 23:09:12 -05:00
Qstick 0c81387cfb Fix RefreshMovieServiceFixture folder service mock 2022-07-15 22:36:35 -05:00
Qstick c5fb5200de Fixed: Collections not deleted on Movie Delete 2022-07-15 22:08:25 -05:00
Qstick cc306fcd36 Fixed: Bulk Collection RootFolder change failure 2022-07-15 21:57:32 -05:00
Qstick 2bb7984961 New: Collection Folder, Genre, QualityProfile Filters 2022-07-15 21:57:32 -05:00
Qstick 21e605452a Fixed: Trim RootFolderPath on Migration 2022-07-15 21:57:31 -05:00
Qstick 476f5b5bfd Avoid multiple metadata DB calls on list mapping 2022-07-15 21:57:31 -05:00
Qstick b6920cfe82 Fixed: Prevent excluded movies from being added by collections 2022-07-15 21:57:31 -05:00
Qstick e89b98d0f6 Fixed: Avoid NullRef in MapMovieToTmdbMovie 2022-07-14 22:08:11 -05:00
bakerboy448 1db690ad39 Fixed: Notifiarr - Better HTTP Error Handling
also quiet sentry
2022-07-14 19:08:16 -05:00
Qstick d5c524719b Fix Nullref on Collection delete 2022-07-12 19:20:46 -05:00
bakerboy448 ced6586860 New: (Notifiarr) Custom Formats in OnGrab 2022-07-12 08:44:59 -05:00
Servarr 8b3019821a Automated API Docs update 2022-07-10 13:03:18 -05:00
Qstick 16ed68d5de New: Custom Format Spec Validation
Fixes #7405
2022-07-10 12:25:42 -05:00
Qstick 098a893083 Fixed: Don't fail on single failure for Discover bulk add
Fixes #7409
2022-07-09 19:11:16 -05:00
Qstick 548e3400b5 Remove general yarn restore key to avoid cross OS conflict 2022-07-09 18:59:15 -05:00
Weblate 5c31e3f1a2 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 97.6% (1117 of 1144 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Maxent <rouaultmaxent@gmail.com>
Co-authored-by: Moritz Ellerbrock <github@elmoritz.eu>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-07-09 00:16:35 -05:00
Qstick 7404793dcf Fixed: Don't call for server notifications on event driven check
[common]
2022-07-03 12:36:53 -05:00
Qstick d8af17ce3d Rename MovieImportedEvent to MovieFileImportedEvent 2022-07-03 12:35:15 -05:00
bakerboy448 44c912f02d Fixed: Improved parsing WebDL Releases 2022-07-03 11:55:06 -05:00
Alien21 b104368e23 New: adding a link to tmdb in the import combobox movie search results (#7352)
Co-authored-by: Alien21 <alien21@alien21.com>
2022-07-02 18:32:55 -05:00
Qstick aa0104b6bc Fixed: Housekeeper doesn't remove collections that have MovieMeta from lists 2022-07-02 16:29:12 -05:00
Qstick 69fcd8ec94 Fixed: Notify on Bulk Adds (Lists, Collections, Imports)
Closes #7351
2022-07-02 15:53:56 -05:00
Robin Dadswell a59928c66a Updated NLog Version (#7365)
[common]
2022-07-02 16:26:59 +01:00
Weblate 1cb7ae11a2 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1144 of 1144 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-07-02 09:33:13 -05:00
Qstick ca519047dd Fixed: Migration 208 fails when collection doesn't have name 2022-07-01 19:45:05 -05:00
Qstick f15a6abde0 Fixed: Don't call AddMovies if no movies to add from Collection 2022-06-26 20:25:50 -05:00
Qstick 2aacebc938 New: Default to IMDb Ratings in Kodi Metadata
Fixes #7071
2022-06-26 20:25:50 -05:00
Weblate 120e9b673e Translated using Weblate (Slovak) [skip ci]
Currently translated at 9.5% (109 of 1143 strings)

Translated using Weblate (Norwegian Bokmål) [skip ci]

Currently translated at 22.3% (256 of 1143 strings)

Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]

Currently translated at 0.1% (2 of 1143 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1143 of 1143 strings)

Added translation using Weblate (Lithuanian) [skip ci]

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: loksum213108 <lok3222003@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2022-06-26 18:43:59 -05:00
Qstick 0a77a13fa8 New: Separate Ratings Columns
Fixes #7281
2022-06-26 17:23:07 -05:00
Qstick 383f9647c3 Fixed: Add support for more Anime release formats
Ref #6954
2022-06-26 15:30:05 -05:00
Weblate 7f7c672b93 Translated using Weblate (Portuguese) [skip ci]
Currently translated at 98.2% (1123 of 1143 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1143 of 1143 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 97.9% (1119 of 1143 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: RckCell <pabloant86@gmail.com>
Co-authored-by: Vitor Brito <main@vitorbrito.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translation: Servarr/Radarr
2022-06-25 18:16:54 -05:00
Servarr 2690ad8fe1 Automated API Docs update 2022-06-25 18:16:32 -05:00
Qstick 801204b6de New: Bulk Edit Collections Profile, Root, Availability
Fixes #7350
2022-06-25 16:01:01 -05:00
Servarr cb9514abaf Automated API Docs update 2022-06-25 15:30:31 -05:00
Qstick fd22cb44f6 Fixed: Collections Improvements
Fixes #7383
2022-06-25 15:23:39 -05:00
Qstick 2d68716376 Add back Movie Credits and Alt Titles Indexes 2022-06-23 19:48:41 -05:00
Qstick b97e76c8b8 Fixed: Validate if equals or child for startup folder
(cherry picked from commit 0991cfe27efd6ddb533227b25754661e18d7e9ad)
2022-06-23 00:34:03 -04:00
bakerboy448 bfad4a8cd1 New: Notifiarr include Media Info in Download Notifications 2022-06-22 18:12:33 -05:00
bakerboy448 61f05710f5 New: Notifiarr moved from webhook to API 2022-06-22 18:12:33 -05:00
Weblate a8ecefd91f Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (1143 of 1143 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 97.8% (1118 of 1143 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 99.9% (1142 of 1143 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 99.9% (1142 of 1143 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Marcin <ml.cichy@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-06-22 09:51:52 -05:00
165 changed files with 3409 additions and 1276 deletions
+10 -10
View File
@@ -173,7 +173,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock' key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: | restoreKeys: |
yarn | "$(osName)" yarn | "$(osName)"
yarn
path: $(yarnCacheFolder) path: $(yarnCacheFolder)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- bash: ./build.sh --frontend - bash: ./build.sh --frontend
@@ -544,10 +543,10 @@ stages:
variables: variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz' pattern: 'Radarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests artifactName: linux-x64-tests
Radarr__PostgresHost: 'localhost' Radarr__Postgres__Host: 'localhost'
Radarr__PostgresPort: '5432' Radarr__Postgres__Port: '5432'
Radarr__PostgresUser: 'radarr' Radarr__Postgres__User: 'radarr'
Radarr__PostgresPassword: 'radarr' Radarr__Postgres__Password: 'radarr'
pool: pool:
vmImage: 'ubuntu-18.04' vmImage: 'ubuntu-18.04'
@@ -577,6 +576,7 @@ stages:
-e POSTGRES_PASSWORD=radarr \ -e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \ -e POSTGRES_USER=radarr \
-p 5432:5432/tcp \ -p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14 postgres:14
displayName: Start postgres displayName: Start postgres
- bash: | - bash: |
@@ -681,10 +681,10 @@ stages:
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables: variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz' pattern: 'Radarr.*.linux-core-x64.tar.gz'
Radarr__PostgresHost: 'localhost' Radarr__Postgres__Host: 'localhost'
Radarr__PostgresPort: '5432' Radarr__Postgres__Port: '5432'
Radarr__PostgresUser: 'radarr' Radarr__Postgres__User: 'radarr'
Radarr__PostgresPassword: 'radarr' Radarr__Postgres__Password: 'radarr'
pool: pool:
vmImage: 'ubuntu-18.04' vmImage: 'ubuntu-18.04'
@@ -722,6 +722,7 @@ stages:
-e POSTGRES_PASSWORD=radarr \ -e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \ -e POSTGRES_USER=radarr \
-p 5432:5432/tcp \ -p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14 postgres:14
displayName: Start postgres displayName: Start postgres
- bash: | - bash: |
@@ -976,7 +977,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock' key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: | restoreKeys: |
yarn | "$(osName)" yarn | "$(osName)"
yarn
path: $(yarnCacheFolder) path: $(yarnCacheFolder)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- bash: ./build.sh --lint - bash: ./build.sh --lint
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Label from 'Components/Label'; import Label from 'Components/Label';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks'; import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
@@ -190,7 +190,7 @@ class AddNewMovieSearchResult extends Component {
<div> <div>
<Label size={sizes.LARGE}> <Label size={sizes.LARGE}>
<HeartRating <TmdbRating
ratings={ratings} ratings={ratings}
iconSize={13} iconSize={13}
/> />
@@ -1,4 +1,5 @@
.movie { .container {
display: flex;
padding: 10px 20px; padding: 10px 20px;
width: 100%; width: 100%;
@@ -6,3 +7,19 @@
background-color: $menuItemHoverBackgroundColor; 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,6 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import ImportMovieTitle from './ImportMovieTitle'; import ImportMovieTitle from './ImportMovieTitle';
import styles from './ImportMovieSearchResult.css'; import styles from './ImportMovieSearchResult.css';
@@ -18,6 +20,7 @@ class ImportMovieSearchResult extends Component {
render() { render() {
const { const {
tmdbId,
title, title,
year, year,
studio, studio,
@@ -25,17 +28,30 @@ class ImportMovieSearchResult extends Component {
} = this.props; } = this.props;
return ( return (
<Link <div className={styles.container}>
className={styles.movie} <Link
onPress={this.onPress} className={styles.movie}
> onPress={this.onPress}
<ImportMovieTitle >
title={title} <ImportMovieTitle
year={year} title={title}
network={studio} year={year}
isExistingMovie={isExistingMovie} network={studio}
/> isExistingMovie={isExistingMovie}
</Link> />
</Link>
<Link
className={styles.tmdbLink}
to={`https://www.themoviedb.org/movie/${tmdbId}`}
>
<Icon
className={styles.tmdbLinkIcon}
name={icons.EXTERNAL_LINK}
size={16}
/>
</Link>
</div>
); );
} }
} }
+76 -3
View File
@@ -1,6 +1,9 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; 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 SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton'; import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
@@ -22,6 +25,9 @@ class CollectionFooter extends Component {
this.state = { this.state = {
monitor: NO_CHANGE, monitor: NO_CHANGE,
monitored: NO_CHANGE, monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE,
destinationRootFolder: null destinationRootFolder: null
}; };
} }
@@ -36,7 +42,10 @@ class CollectionFooter extends Component {
if (prevProps.isSaving && !isSaving && !saveError) { if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({ this.setState({
monitored: NO_CHANGE, monitored: NO_CHANGE,
monitor: NO_CHANGE monitor: NO_CHANGE,
qualityProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
minimumAvailability: NO_CHANGE
}); });
} }
@@ -55,7 +64,10 @@ class CollectionFooter extends Component {
onUpdateSelectedPress = () => { onUpdateSelectedPress = () => {
const { const {
monitor, monitor,
monitored monitored,
qualityProfileId,
minimumAvailability,
rootFolderPath
} = this.state; } = this.state;
const changes = {}; const changes = {};
@@ -68,6 +80,18 @@ class CollectionFooter extends Component {
changes.monitor = monitor; 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); this.props.onUpdateSelectedPress(changes);
}; };
@@ -82,7 +106,10 @@ class CollectionFooter extends Component {
const { const {
monitored, monitored,
monitor monitor,
qualityProfileId,
minimumAvailability,
rootFolderPath
} = this.state; } = this.state;
const monitoredOptions = [ const monitoredOptions = [
@@ -125,6 +152,52 @@ class CollectionFooter extends Component {
/> />
</div> </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.buttonContainer}>
<div className={styles.buttonContainerContent}> <div className={styles.buttonContainerContent}>
<CollectionFooterLabel <CollectionFooterLabel
-5
View File
@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -22,11 +22,11 @@ class ImdbRating extends PureComponent {
let ratingString = '0.0'; let ratingString = '0.0';
if (rating) { if (rating) {
ratingString = `${rating.value}`; ratingString = `${rating.value.toFixed(1)}`;
} }
return ( return (
<span title={`${rating.votes} votes`}> <span title={`${rating ? rating.votes : 0} votes`}>
{ {
!hideIcon && !hideIcon &&
<img <img
@@ -21,9 +21,11 @@ class RottenTomatoRating extends PureComponent {
const rating = ratings.rottenTomatoes; const rating = ratings.rottenTomatoes;
let ratingString = '0%'; let ratingString = '0%';
let ratingImage = rtFresh;
if (rating) { if (rating) {
ratingString = `${rating.value}%`; ratingString = `${rating.value}%`;
ratingImage = rating.value > 50 ? rtFresh : rtRotten;
} }
return ( return (
@@ -32,7 +34,7 @@ class RottenTomatoRating extends PureComponent {
!hideIcon && !hideIcon &&
<img <img
className={styles.image} className={styles.image}
src={rating.value > 50 ? rtFresh : rtRotten} src={ratingImage}
style={{ style={{
height: `${iconSize}px` height: `${iconSize}px`
}} }}
+1 -1
View File
@@ -22,7 +22,7 @@ class TmdbRating extends PureComponent {
let ratingString = '0%'; let ratingString = '0%';
if (rating) { if (rating) {
ratingString = `${rating.value * 10}%`; ratingString = `${(rating.value * 10).toFixed()}%`;
} }
return ( return (
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import HeartRating from 'Components/HeartRating'; import TmdbRating from 'Components/TmdbRating';
import { getMovieStatusDetails } from 'Movie/MovieStatus'; import { getMovieStatusDetails } from 'Movie/MovieStatus';
import formatRuntime from 'Utilities/Date/formatRuntime'; import formatRuntime from 'Utilities/Date/formatRuntime';
import getRelativeDate from 'Utilities/Date/getRelativeDate'; import getRelativeDate from 'Utilities/Date/getRelativeDate';
@@ -111,7 +111,7 @@ function DiscoverMoviePosterInfo(props) {
if (sortKey === 'ratings' && ratings) { if (sortKey === 'ratings' && ratings) {
return ( return (
<div className={styles.info}> <div className={styles.info}>
<HeartRating <TmdbRating
ratings={ratings} ratings={ratings}
/> />
</div> </div>
@@ -1,6 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import ImportListListConnector from 'Components/ImportListListConnector'; import ImportListListConnector from 'Components/ImportListListConnector';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
@@ -8,6 +7,7 @@ import Link from 'Components/Link/Link';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell'; import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal'; import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal';
import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal'; import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal';
@@ -245,7 +245,7 @@ class DiscoverMovieRow extends Component {
key={name} key={name}
className={styles[name]} className={styles[name]}
> >
<HeartRating <TmdbRating
ratings={ratings} ratings={ratings}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
@@ -102,12 +102,21 @@ function MovieIndexSortMenu(props) {
</SortMenuItem> </SortMenuItem>
<SortMenuItem <SortMenuItem
name="ratings" name="imdbRating"
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
onPress={onSortSelect} onPress={onSortSelect}
> >
{translate('Ratings')} {translate('ImdbRating')}
</SortMenuItem>
<SortMenuItem
name="tmdbRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('TmdbRating')}
</SortMenuItem> </SortMenuItem>
<SortMenuItem <SortMenuItem
@@ -77,7 +77,9 @@
flex: 0 0 120px; flex: 0 0 120px;
} }
.ratings { .imdbRating,
.tmdbRating,
.rottenTomatoesRating {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 80px; flex: 0 0 80px;
@@ -84,7 +84,9 @@
flex: 0 0 120px; flex: 0 0 120px;
} }
.ratings { .imdbRating,
.tmdbRating,
.rottenTomatoesRating {
composes: cell; composes: cell;
flex: 0 0 80px; flex: 0 0 80px;
@@ -1,13 +1,15 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell'; import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TagListConnector from 'Components/TagListConnector'; import TagListConnector from 'Components/TagListConnector';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal'; import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
@@ -349,13 +351,39 @@ class MovieIndexRow extends Component {
); );
} }
if (name === 'ratings') { if (name === 'tmdbRating') {
return ( return (
<VirtualTableRowCell <VirtualTableRowCell
key={name} key={name}
className={styles[name]} className={styles[name]}
> >
<HeartRating <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
ratings={ratings} ratings={ratings}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
@@ -561,7 +561,7 @@ export const actionHandlers = handleThunks({
}, []); }, []);
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: '/movie/import', url: '/importlist/movie',
method: 'POST', method: 'POST',
contentType: 'application/json', contentType: 'application/json',
data: JSON.stringify(allNewMovies) data: JSON.stringify(allNewMovies)
@@ -162,6 +162,14 @@ export const filterPredicates = {
return predicate(rating, filterValue); 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) { imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type]; const predicate = filterTypePredicates[type];
@@ -1,10 +1,12 @@
import _ from 'lodash'; import _ from 'lodash';
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions'; import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props'; import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewMovie from 'Utilities/Movie/getNewMovie'; import getNewMovie from 'Utilities/Movie/getNewMovie';
import translate from 'Utilities/String/translate';
import { set, update, updateItem } from './baseActions'; import { set, update, updateItem } from './baseActions';
import createHandleActions from './Creators/createHandleActions'; import createHandleActions from './Creators/createHandleActions';
import createSaveProviderHandler from './Creators/createSaveProviderHandler'; import createSaveProviderHandler from './Creators/createSaveProviderHandler';
@@ -63,19 +65,81 @@ export const defaultState = {
} }
], ],
filterPredicates: {}, filterPredicates: {
genres: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
let allGenres = [];
item.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
});
const genres = Array.from(new Set(allGenres)).slice(0, 3);
return predicate(genres, filterValue);
},
totalMovies: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { movies } = item;
const totalMovies = movies.length;
return predicate(totalMovies, filterValue);
}
},
filterBuilderProps: [ filterBuilderProps: [
{ {
name: 'title', name: 'title',
label: 'Title', label: translate('Title'),
type: filterBuilderTypes.STRING type: filterBuilderTypes.STRING
}, },
{ {
name: 'monitored', name: 'monitored',
label: 'Monitored', label: translate('Monitored'),
type: filterBuilderTypes.EXACT, type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.BOOL 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
} }
] ]
}; };
@@ -254,27 +318,32 @@ export const actionHandlers = handleThunks({
const { const {
collectionIds, collectionIds,
monitored, monitored,
monitor monitor,
qualityProfileId,
rootFolderPath,
minimumAvailability
} = payload; } = payload;
const response = {}; const response = {};
const collections = [];
collectionIds.forEach((id) => { if (payload.hasOwnProperty('monitored')) {
const collectionToUpdate = { id }; response.monitored = monitored;
}
if (payload.hasOwnProperty('monitored')) {
collectionToUpdate.monitored = monitored;
}
collections.push(collectionToUpdate);
});
if (payload.hasOwnProperty('monitor')) { if (payload.hasOwnProperty('monitor')) {
response.monitorMovies = monitor === 'monitored'; response.monitorMovies = monitor === 'monitored';
} }
response.collections = collections; if (payload.hasOwnProperty('qualityProfileId')) {
response.qualityProfileId = qualityProfileId;
}
if (payload.hasOwnProperty('minimumAvailability')) {
response.minimumAvailability = minimumAvailability;
}
response.rootFolderPath = rootFolderPath;
response.collectionIds = collectionIds;
dispatch(set({ dispatch(set({
section, section,
@@ -178,8 +178,20 @@ export const defaultState = {
isVisible: true isVisible: true
}, },
{ {
name: 'ratings', name: 'tmdbRating',
label: translate('Ratings'), label: translate('TmdbRating'),
isSortable: true,
isVisible: false
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
isSortable: true,
isVisible: false
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
isSortable: true, isSortable: true,
isVisible: false isVisible: false
}, },
@@ -224,10 +236,22 @@ export const defaultState = {
return originalLanguage.name; return originalLanguage.name;
}, },
ratings: function(item) { imdbRating: function(item) {
const { ratings = {} } = item; const { ratings = {} } = item;
return ratings.tmdb? ratings.tmdb.value : 0; 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;
} }
}, },
@@ -413,6 +437,11 @@ export const defaultState = {
label: translate('ImdbRating'), label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER type: filterBuilderTypes.NUMBER
}, },
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
type: filterBuilderTypes.NUMBER
},
{ {
name: 'imdbVotes', name: 'imdbVotes',
label: translate('ImdbVotes'), label: translate('ImdbVotes'),
+1
View File
@@ -8,5 +8,6 @@
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" /> <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="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="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> </packageSources>
</configuration> </configuration>
@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@@ -13,7 +13,7 @@ namespace NzbDrone.Common.Test
{ {
[TestFixture] [TestFixture]
public class ConfigFileWriterTest : TestBase<ConfigFileWriter> public class ConfigFileProviderTest : TestBase<ConfigFileProvider>
{ {
private string _configFileContents; private string _configFileContents;
private string _configFilePath; private string _configFilePath;
@@ -45,7 +45,56 @@ namespace NzbDrone.Common.Test
.Callback<string, string>((p, t) => _configFileContents = t); .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] [Test]
public void SetValue_bool() public void SetValue_bool()
@@ -71,6 +120,17 @@ namespace NzbDrone.Common.Test
result.Should().Be(value); 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] [Test]
public void GetAuthenticationType_No_Existing_Value() public void GetAuthenticationType_No_Existing_Value()
{ {
@@ -79,7 +139,6 @@ namespace NzbDrone.Common.Test
result.Should().Be(AuthenticationType.None); result.Should().Be(AuthenticationType.None);
} }
/*
[Test] [Test]
public void SaveDictionary_should_save_proper_value() public void SaveDictionary_should_save_proper_value()
{ {
@@ -111,6 +170,32 @@ namespace NzbDrone.Common.Test
Subject.Port.Should().Be(port); Subject.Port.Should().Be(port);
Subject.SslPort.Should().Be(sslPort); 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"));
}
} }
} }
@@ -2,7 +2,6 @@ using System.Linq;
using DryIoc; using DryIoc;
using DryIoc.Microsoft.DependencyInjection; using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -11,7 +10,7 @@ using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -33,8 +32,7 @@ namespace NzbDrone.Common.Test
.AddStartupContext(new StartupContext("first", "second")); .AddStartupContext(new StartupContext("first", "second"));
container.RegisterInstance(new Mock<IHostLifetime>().Object); container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IConfiguration>().Object); container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
container.RegisterInstance(new Mock<IOptionsMonitor<ConfigFileOptions>>().Object);
var serviceProvider = container.GetServiceProvider(); var serviceProvider = container.GetServiceProvider();
@@ -1,4 +1,6 @@
using System.Linq; using System;
using System.Collections.Generic;
using System.Linq;
using NLog; using NLog;
using NLog.Fluent; using NLog.Fluent;
@@ -8,47 +10,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
{ {
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry"); public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return logBuilder.Property("Sentry", fingerprint); return logBuilder.Property("Sentry", fingerprint);
} }
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
} }
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
} }
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
} }
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
} }
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint) private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
{ {
SentryLogger.Log(level) SentryLogger.ForLogEvent(level)
.CopyLogEvent(logBuilder.LogEventInfo) .CopyLogEvent(logBuilder.LogEvent)
.SentryFingerprint(fingerprint) .SentryFingerprint(fingerprint)
.Write(); .Log();
return logBuilder.Property("Sentry", null); return logBuilder.Property<string>("Sentry", null);
} }
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent) private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
{ {
return logBuilder.LoggerName(logEvent.LoggerName) return logBuilder.TimeStamp(logEvent.TimeStamp)
.TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters) .Message(logEvent.Message, logEvent.Parameters)
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value)) .Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
.Exception(logEvent.Exception); .Exception(logEvent.Exception);
} }
} }
@@ -1,13 +1,16 @@
using NLog; using System;
using System.Text;
using NLog;
using NLog.Targets; using NLog.Targets;
namespace NzbDrone.Common.Instrumentation namespace NzbDrone.Common.Instrumentation
{ {
public class NzbDroneFileTarget : FileTarget public class NzbDroneFileTarget : FileTarget
{ {
protected override string GetFormattedMessage(LogEventInfo logEvent) protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{ {
return CleanseLogMessage.Cleanse(Layout.Render(logEvent)); var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
target.Append(result);
} }
} }
} }
@@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext); var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached) if (Debugger.IsAttached)
{ {
RegisterDebugger(); RegisterDebugger();
@@ -97,10 +99,21 @@ 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}}"; 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); var loggingRule = new LoggingRule("*", LogLevel.Trace, target);
LogManager.Configuration.AddTarget("debugger", target); LogManager.Configuration.AddTarget("debugger", target);
LogManager.Configuration.LoggingRules.Add(loggingRule); 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() private static void RegisterConsole()
{ {
var level = LogLevel.Trace; var level = LogLevel.Trace;
+3 -3
View File
@@ -8,10 +8,10 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.14" /> <PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.15.0" /> <PackageReference Include="Sentry" Version="3.15.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" /> <PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.4" /> <PackageReference Include="System.Text.Json" Version="6.0.4" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
@@ -27,6 +27,20 @@ namespace NzbDrone.Core.Test.Datastore
Mocker.Resolve<IDatabase>().Vacuum(); 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] [Test]
public void get_version() public void get_version()
{ {
@@ -56,6 +56,51 @@ namespace NzbDrone.Core.Test.Datastore.Migration
movies.First().CollectionTmdbId.Should().Be(collections.First().TmdbId); 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] [Test]
public void should_not_duplicate_collection() public void should_not_duplicate_collection()
{ {
+6 -7
View File
@@ -3,11 +3,9 @@ using System.Collections.Generic;
using System.Data.SQLite; using System.Data.SQLite;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq;
using Npgsql; using Npgsql;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -126,13 +124,13 @@ namespace NzbDrone.Core.Test.Framework
private void CreatePostgresDb() private void CreatePostgresDb()
{ {
var options = Mocker.Resolve<IOptionsMonitor<ConfigFileOptions>>().CurrentValue; var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
PostgresDatabase.Create(options, MigrationType); PostgresDatabase.Create(options, MigrationType);
} }
private void DropPostgresDb() private void DropPostgresDb()
{ {
var options = Mocker.Resolve<IOptionsMonitor<ConfigFileOptions>>().CurrentValue; var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
PostgresDatabase.Drop(options, MigrationType); PostgresDatabase.Drop(options, MigrationType);
} }
@@ -176,11 +174,12 @@ namespace NzbDrone.Core.Test.Framework
SetupLogging(); SetupLogging();
// populate the possible postgres options // populate the possible postgres options
var options = PostgresDatabase.GetTestOptions(); var postgresOptions = PostgresDatabase.GetTestOptions();
_databaseType = options.PostgresHost.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite; _databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
// Set up remaining container services // Set up remaining container services
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>().Setup(x => x.CurrentValue).Returns(options); Mocker.SetConstant(Options.Create(postgresOptions));
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>()); Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>()); Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
@@ -1,51 +0,0 @@
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,11 +1,9 @@
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.HealthCheck.Checks; using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Test.HealthCheck.Checks namespace NzbDrone.Core.Test.HealthCheck.Checks
{ {
@@ -22,9 +20,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
private void GivenValidBranch(string branch) private void GivenValidBranch(string branch)
{ {
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.CurrentValue) .SetupGet(s => s.Branch)
.Returns(new ConfigFileOptions { Branch = branch }); .Returns(branch);
} }
[TestCase("aphrodite")] [TestCase("aphrodite")]
@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_ok_on_movie_imported_event() public void should_return_ok_on_movie_imported_event()
{ {
GivenFolderExists(_downloadRootPath); GivenFolderExists(_downloadRootPath);
var importEvent = new MovieImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem()); var importEvent = new MovieFileImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem());
Subject.Check(importEvent).ShouldBeOk(); Subject.Check(importEvent).ShouldBeOk();
} }
@@ -1,4 +1,3 @@
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@@ -20,10 +19,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<ILocalizationService>() Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>())) .Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message"); .Returns("Some Warning Message");
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(c => c.CurrentValue)
.Returns(new ConfigFileOptions());
} }
[Test] [Test]
@@ -49,9 +44,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
const string startupFolder = @"/opt/nzbdrone"; const string startupFolder = @"/opt/nzbdrone";
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.CurrentValue) .Setup(s => s.UpdateAutomatically)
.Returns(new ConfigFileOptions { UpdateAutomatically = true }); .Returns(true);
Mocker.GetMock<IAppFolderInfo>() Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder) .Setup(s => s.StartUpFolder)
@@ -72,9 +67,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
const string startupFolder = @"/opt/nzbdrone"; const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI"; const string uiFolder = @"/opt/nzbdrone/UI";
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.CurrentValue) .Setup(s => s.UpdateAutomatically)
.Returns(new ConfigFileOptions { UpdateAutomatically = true }); .Returns(true);
Mocker.GetMock<IAppFolderInfo>() Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder) .Setup(s => s.StartUpFolder)
@@ -96,9 +91,13 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
{ {
PosixOnly(); PosixOnly();
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.CurrentValue) .Setup(s => s.UpdateAutomatically)
.Returns(new ConfigFileOptions { UpdateAutomatically = true, UpdateMechanism = UpdateMechanism.Script }); .Returns(true);
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateMechanism)
.Returns(UpdateMechanism.Script);
Mocker.GetMock<IAppFolderInfo>() Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder) .Setup(s => s.StartUpFolder)
@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.HistoryTests
DownloadId = "abcd" DownloadId = "abcd"
}; };
Subject.Handle(new MovieImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem)); Subject.Handle(new MovieFileImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem));
Mocker.GetMock<IHistoryRepository>() Mocker.GetMock<IHistoryRepository>()
.Verify(v => v.Insert(It.Is<MovieHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localMovie.Path)))); .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] [Test]
public void should_not_delete_unorphaned_collection_items() public void should_delete_orphaned_collection_with_meta_but_no_movie_items()
{ {
var collection = Builder<MovieCollection>.CreateNew() var collection = Builder<MovieCollection>.CreateNew()
.With(h => h.Id = 3) .With(h => h.Id = 3)
@@ -40,6 +40,27 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
Db.Insert(movie); 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(); Subject.Clean();
AllStoredModels.Should().HaveCount(1); AllStoredModels.Should().HaveCount(1);
} }
@@ -4,12 +4,10 @@ using System.IO;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Events; using NzbDrone.Core.Movies.Events;
@@ -33,10 +31,6 @@ namespace NzbDrone.Core.Test.MediaCoverTests
.Build(); .Build();
Mocker.GetMock<IMovieService>().Setup(m => m.GetMovie(It.Is<int>(id => id == _movie.Id))).Returns(_movie); 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] [Test]
@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true); Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IEventAggregator>() Mocker.GetMock<IEventAggregator>()
.Verify(v => v.PublishEvent(It.IsAny<MovieImportedEvent>()), Times.Once()); .Verify(v => v.PublishEvent(It.IsAny<MovieFileImportedEvent>()), Times.Once());
} }
[Test] [Test]
@@ -11,6 +11,7 @@ using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Collections; using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Commands; using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Credits; using NzbDrone.Core.Movies.Credits;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
@@ -52,6 +53,10 @@ namespace NzbDrone.Core.Test.MovieTests
Mocker.GetMock<IProvideMovieInfo>() Mocker.GetMock<IProvideMovieInfo>()
.Setup(s => s.GetMovieInfo(It.IsAny<int>())) .Setup(s => s.GetMovieInfo(It.IsAny<int>()))
.Callback<int>((i) => { throw new MovieNotFoundException(i); }); .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) private void GivenNewMovieInfo(MovieMetadata movie)
@@ -67,6 +67,35 @@ namespace NzbDrone.Core.Test.ParserTests
Parser.Parser.ParseMovieTitle(postTitle).PrimaryMovieTitle.Should().Be(title); 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("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("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)] [TestCase("Kick.Movie.2.2013.German.DTS.DL.720p.BluRay.x264-Pate", "Kick Movie 2", "", 2013)]
@@ -41,6 +41,14 @@ namespace NzbDrone.Core.Test.ParserTests
ParseAndVerifyQuality(title, Source.TELESYNC, proper, Resolution.R720p); 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("S07E23 .avi ", false)]
[TestCase("Movie Name S02E01 HDTV XviD 2HD", false)] [TestCase("Movie Name S02E01 HDTV XviD 2HD", false)]
[TestCase("Movie Name S05E11 PROPER HDTV XviD 2HD", true)] [TestCase("Movie Name S05E11 PROPER HDTV XviD 2HD", true)]
@@ -189,6 +197,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2020.MULTi.1080p.WEB.H264-ALLDAYiN (S:285/L:11)", false)] [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 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("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) public void should_parse_webdl1080p_quality(string title, bool proper)
{ {
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R1080p); ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R1080p);
@@ -207,6 +216,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Name.2016.03.14.2160p.WEB.PROPER.h264-spamTV", true)] [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("[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("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) public void should_parse_webdl2160p_quality(string title, bool proper)
{ {
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R2160p); ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R2160p);
@@ -100,6 +100,9 @@ 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("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("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("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")]
public void should_parse_exception_release_group(string title, string expected) public void should_parse_exception_release_group(string title, string expected)
{ {
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common; using NzbDrone.Common;
@@ -61,9 +60,9 @@ namespace NzbDrone.Core.Test.UpdateTests
Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 }); 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<IRuntimeInfo>().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\Radarr.exe");
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.CurrentValue) .SetupGet(s => s.UpdateAutomatically)
.Returns(new ConfigFileOptions { UpdateAutomatically = true }); .Returns(true);
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderWritable(It.IsAny<string>())) .Setup(c => c.FolderWritable(It.IsAny<string>()))
@@ -78,9 +77,13 @@ namespace NzbDrone.Core.Test.UpdateTests
private void GivenInstallScript(string path) private void GivenInstallScript(string path)
{ {
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>() Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.CurrentValue) .SetupGet(s => s.UpdateMechanism)
.Returns(new ConfigFileOptions { UpdateAutomatically = true, UpdateMechanism = UpdateMechanism.Script, UpdateScriptPath = path }); .Returns(UpdateMechanism.Script);
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateScriptPath)
.Returns(path);
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(path, StringComparison.Ordinal)) .Setup(s => s.FileExists(path, StringComparison.Ordinal))
@@ -331,7 +334,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand()); Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IConfigFileWriter>() Mocker.GetMock<IConfigFileProvider>()
.Verify(v => v.SaveConfigDictionary(It.Is<Dictionary<string, object>>(d => d.ContainsKey("Branch") && (string)d["Branch"] == "fake")), Times.Once()); .Verify(v => v.SaveConfigDictionary(It.Is<Dictionary<string, object>>(d => d.ContainsKey("Branch") && (string)d["Branch"] == "fake")), Times.Once());
} }
@@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@@ -16,16 +15,16 @@ namespace NzbDrone.Core.Analytics
public class AnalyticsService : IAnalyticsService public class AnalyticsService : IAnalyticsService
{ {
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
public AnalyticsService(IHistoryService historyService, IOptionsMonitor<ConfigFileOptions> configFileOptions) public AnalyticsService(IHistoryService historyService, IConfigFileProvider configFileProvider)
{ {
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_historyService = historyService; _historyService = historyService;
} }
public bool IsEnabled => (_configFileOptions.CurrentValue.AnalyticsEnabled && RuntimeInfo.IsProduction) || RuntimeInfo.IsDevelopment; public bool IsEnabled => (_configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction) || RuntimeInfo.IsDevelopment;
public bool InstallIsActive public bool InstallIsActive
{ {
@@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -20,22 +19,22 @@ namespace NzbDrone.Core.Authentication
User FindUser(Guid identifier); User FindUser(Guid identifier);
} }
public class UserService : IUserService public class UserService : IUserService, IHandle<ApplicationStartedEvent>
{ {
private readonly IUserRepository _repo; private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
public UserService(IUserRepository repo, public UserService(IUserRepository repo,
IAppFolderInfo appFolderInfo, IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions) IConfigFileProvider configFileProvider)
{ {
_repo = repo; _repo = repo;
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
} }
public User Add(string username, string password) public User Add(string username, string password)
@@ -103,5 +102,28 @@ namespace NzbDrone.Core.Authentication
{ {
return _repo.FindUser(identifier); 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);
}
} }
} }
@@ -1,71 +0,0 @@
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;
}
}
}
@@ -0,0 +1,401 @@
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());
}
}
}
@@ -1,219 +0,0 @@
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());
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace NzbDrone.Core.Configuration
{
[AttributeUsage(AttributeTargets.Property)]
public class PersistAttribute : Attribute
{
}
}
@@ -1,4 +1,5 @@
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
@@ -18,6 +19,8 @@ namespace NzbDrone.Core.CustomFormats
return (ICustomFormatSpecification)MemberwiseClone(); return (ICustomFormatSpecification)MemberwiseClone();
} }
public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo) public bool IsSatisfiedBy(ParsedMovieInfo movieInfo)
{ {
var match = IsSatisfiedByWithoutNegate(movieInfo); var match = IsSatisfiedByWithoutNegate(movieInfo);
@@ -1,4 +1,5 @@
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
@@ -11,6 +12,8 @@ namespace NzbDrone.Core.CustomFormats
bool Negate { get; set; } bool Negate { get; set; }
bool Required { get; set; } bool Required { get; set; }
NzbDroneValidationResult Validate();
ICustomFormatSpecification Clone(); ICustomFormatSpecification Clone();
bool IsSatisfiedBy(ParsedMovieInfo movieInfo); bool IsSatisfiedBy(ParsedMovieInfo movieInfo);
} }
@@ -1,11 +1,31 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats 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 public class IndexerFlagSpecification : CustomFormatSpecificationBase
{ {
private static readonly IndexerFlagSpecificationValidator Validator = new IndexerFlagSpecificationValidator();
public override int Order => 4; public override int Order => 4;
public override string ImplementationName => "Indexer Flag"; public override string ImplementationName => "Indexer Flag";
@@ -17,5 +37,10 @@ namespace NzbDrone.Core.CustomFormats
var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?; var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?;
return flags?.HasFlag((IndexerFlags)Value) == true; return flags?.HasFlag((IndexerFlags)Value) == true;
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,11 +1,31 @@
using System.Linq;
using FluentValidation;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats 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 public class LanguageSpecification : CustomFormatSpecificationBase
{ {
private static readonly LanguageSpecificationValidator Validator = new LanguageSpecificationValidator();
public override int Order => 3; public override int Order => 3;
public override string ImplementationName => "Language"; public override string ImplementationName => "Language";
@@ -19,5 +39,10 @@ namespace NzbDrone.Core.CustomFormats
: (Language)Value; : (Language)Value;
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false; return movieInfo?.Languages?.Contains(comparedLanguage) ?? false;
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,11 +1,31 @@
using System;
using FluentValidation;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats 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 public class QualityModifierSpecification : CustomFormatSpecificationBase
{ {
private static readonly QualityModifierSpecificationValidator Validator = new QualityModifierSpecificationValidator();
public override int Order => 7; public override int Order => 7;
public override string ImplementationName => "Quality Modifier"; public override string ImplementationName => "Quality Modifier";
@@ -16,5 +36,10 @@ namespace NzbDrone.Core.CustomFormats
{ {
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value; return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,10 +1,23 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats 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 public abstract class RegexSpecificationBase : CustomFormatSpecificationBase
{ {
private static readonly RegexSpecificationBaseValidator Validator = new RegexSpecificationBaseValidator();
protected Regex _regex; protected Regex _regex;
protected string _raw; protected string _raw;
@@ -15,7 +28,11 @@ namespace NzbDrone.Core.CustomFormats
set set
{ {
_raw = value; _raw = value;
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (value.IsNotNullOrWhiteSpace())
{
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
} }
} }
@@ -28,5 +45,10 @@ namespace NzbDrone.Core.CustomFormats
return _regex.IsMatch(compared); return _regex.IsMatch(compared);
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,11 +1,23 @@
using FluentValidation;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
public class ResolutionSpecificationValidator : AbstractValidator<ResolutionSpecification>
{
public ResolutionSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class ResolutionSpecification : CustomFormatSpecificationBase public class ResolutionSpecification : CustomFormatSpecificationBase
{ {
private static readonly ResolutionSpecificationValidator Validator = new ResolutionSpecificationValidator();
public override int Order => 6; public override int Order => 6;
public override string ImplementationName => "Resolution"; public override string ImplementationName => "Resolution";
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
{ {
return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value; return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,11 +1,24 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
public class SizeSpecificationValidator : AbstractValidator<SizeSpecification>
{
public SizeSpecificationValidator()
{
RuleFor(c => c.Min).GreaterThan(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
}
}
public class SizeSpecification : CustomFormatSpecificationBase public class SizeSpecification : CustomFormatSpecificationBase
{ {
private static readonly SizeSpecificationValidator Validator = new SizeSpecificationValidator();
public override int Order => 8; public override int Order => 8;
public override string ImplementationName => "Size"; public override string ImplementationName => "Size";
@@ -21,5 +34,10 @@ namespace NzbDrone.Core.CustomFormats
return size > Min.Gigabytes() && size <= Max.Gigabytes(); return size > Min.Gigabytes() && size <= Max.Gigabytes();
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,11 +1,23 @@
using FluentValidation;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
public class SourceSpecificationValidator : AbstractValidator<SourceSpecification>
{
public SourceSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class SourceSpecification : CustomFormatSpecificationBase public class SourceSpecification : CustomFormatSpecificationBase
{ {
private static readonly SourceSpecificationValidator Validator = new SourceSpecificationValidator();
public override int Order => 5; public override int Order => 5;
public override string ImplementationName => "Source"; public override string ImplementationName => "Source";
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
{ {
return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value; return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
} }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
} }
} }
@@ -1,6 +1,5 @@
using System; using System;
using System.Data.SQLite; using System.Data.SQLite;
using Microsoft.Extensions.Options;
using Npgsql; using Npgsql;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -17,25 +16,16 @@ namespace NzbDrone.Core.Datastore
public class ConnectionStringFactory : IConnectionStringFactory public class ConnectionStringFactory : IConnectionStringFactory
{ {
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
// Catch legacy config, to be removed soon public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
private readonly PostgresOptions _postgresOptions;
public ConnectionStringFactory(IAppFolderInfo appFolderInfo,
IOptionsMonitor<ConfigFileOptions> configFileOptions)
{ {
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_postgresOptions = PostgresOptions.GetOptions();
var isPostgres = _configFileOptions.CurrentValue.PostgresHost.IsNotNullOrWhiteSpace() || _postgresOptions.Host.IsNotNullOrWhiteSpace(); MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
var mainDb = _configFileOptions.CurrentValue.PostgresMainDb ?? _postgresOptions.MainDb;
var logDb = _configFileOptions.CurrentValue.PostgresLogDb ?? _postgresOptions.LogDb;
MainDbConnectionString = isPostgres ? GetPostgresConnectionString(mainDb) :
GetConnectionString(appFolderInfo.GetDatabase()); GetConnectionString(appFolderInfo.GetDatabase());
LogDbConnectionString = isPostgres ? GetPostgresConnectionString(logDb) : LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
GetConnectionString(appFolderInfo.GetLogDatabase()); GetConnectionString(appFolderInfo.GetLogDatabase());
} }
@@ -73,10 +63,10 @@ namespace NzbDrone.Core.Datastore
var connectionBuilder = new NpgsqlConnectionStringBuilder(); var connectionBuilder = new NpgsqlConnectionStringBuilder();
connectionBuilder.Database = dbName; connectionBuilder.Database = dbName;
connectionBuilder.Host = _configFileOptions.CurrentValue.PostgresHost ?? _postgresOptions.Host; connectionBuilder.Host = _configFileProvider.PostgresHost;
connectionBuilder.Username = _configFileOptions.CurrentValue.PostgresUser ?? _postgresOptions.User; connectionBuilder.Username = _configFileProvider.PostgresUser;
connectionBuilder.Password = _configFileOptions.CurrentValue.PostgresPassword ?? _postgresOptions.Password; connectionBuilder.Password = _configFileProvider.PostgresPassword;
connectionBuilder.Port = _configFileOptions.CurrentValue.PostgresPort > 0 ? _configFileOptions.CurrentValue.PostgresPort : _postgresOptions.Port; connectionBuilder.Port = _configFileProvider.PostgresPort;
connectionBuilder.Enlist = false; connectionBuilder.Enlist = false;
return connectionBuilder.ConnectionString; return connectionBuilder.ConnectionString;
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.Datastore.Migration
var moviePath = reader.GetString(3); var moviePath = reader.GetString(3);
var data = STJson.Deserialize<MovieCollection207>(collection); var data = STJson.Deserialize<MovieCollection207>(collection);
if (newCollections.Any(d => d.TmdbId == data.TmdbId)) if (data.TmdbId == 0 || newCollections.Any(d => d.TmdbId == data.TmdbId))
{ {
continue; continue;
} }
@@ -104,15 +104,17 @@ namespace NzbDrone.Core.Datastore.Migration
rootFolderPath = moviePath.GetParentPath(); rootFolderPath = moviePath.GetParentPath();
} }
var collectionName = data.Name ?? $"Collection {data.TmdbId}";
newCollections.Add(new MovieCollection208 newCollections.Add(new MovieCollection208
{ {
TmdbId = data.TmdbId, TmdbId = data.TmdbId,
Title = data.Name, Title = collectionName,
CleanTitle = data.Name.CleanMovieTitle(), CleanTitle = collectionName.CleanMovieTitle(),
SortTitle = Parser.Parser.NormalizeTitle(data.Name), SortTitle = Parser.Parser.NormalizeTitle(collectionName),
Added = added, Added = added,
QualityProfileId = qualityProfileId, QualityProfileId = qualityProfileId,
RootFolderPath = rootFolderPath, RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
SearchOnAdd = true, SearchOnAdd = true,
MinimumAvailability = minimumAvailability MinimumAvailability = minimumAvailability
}); });
@@ -0,0 +1,15 @@
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");
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(212)]
public class postgres_update_timestamp_columns_to_with_timezone : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Blocklist").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("Blocklist").AlterColumn("PublishedDate").AsDateTimeOffset().Nullable();
Alter.Table("Collections").AlterColumn("Added").AsDateTimeOffset().Nullable();
Alter.Table("Collections").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable();
Alter.Table("Commands").AlterColumn("QueuedAt").AsDateTimeOffset().NotNullable();
Alter.Table("Commands").AlterColumn("StartedAt").AsDateTimeOffset().Nullable();
Alter.Table("Commands").AlterColumn("EndedAt").AsDateTimeOffset().Nullable();
Alter.Table("DownloadClientStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
Alter.Table("DownloadClientStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
Alter.Table("DownloadClientStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
Alter.Table("DownloadHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("ExtraFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable();
Alter.Table("ExtraFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
Alter.Table("History").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("ImportListStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
Alter.Table("ImportListStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
Alter.Table("ImportListStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("InitialFailure").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("MostRecentFailure").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("DisabledTill").AsDateTimeOffset().Nullable();
Alter.Table("IndexerStatus").AlterColumn("CookiesExpirationDate").AsDateTimeOffset().Nullable();
Alter.Table("MetadataFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
Alter.Table("MetadataFiles").AlterColumn("Added").AsDateTimeOffset().Nullable();
Alter.Table("MovieFiles").AlterColumn("DateAdded").AsDateTimeOffset().NotNullable();
Alter.Table("MovieMetadata").AlterColumn("DigitalRelease").AsDateTimeOffset().Nullable();
Alter.Table("MovieMetadata").AlterColumn("InCinemas").AsDateTimeOffset().Nullable();
Alter.Table("MovieMetadata").AlterColumn("LastInfoSync").AsDateTimeOffset().Nullable();
Alter.Table("MovieMetadata").AlterColumn("PhysicalRelease").AsDateTimeOffset().Nullable();
Alter.Table("Movies").AlterColumn("Added").AsDateTimeOffset().Nullable();
Alter.Table("PendingReleases").AlterColumn("Added").AsDateTimeOffset().NotNullable();
Alter.Table("ScheduledTasks").AlterColumn("LastExecution").AsDateTimeOffset().NotNullable();
Alter.Table("ScheduledTasks").AlterColumn("LastStartTime").AsDateTimeOffset().Nullable();
Alter.Table("SubtitleFiles").AlterColumn("Added").AsDateTimeOffset().NotNullable();
Alter.Table("SubtitleFiles").AlterColumn("LastUpdated").AsDateTimeOffset().NotNullable();
Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable();
}
protected override void LogDbUpgrade()
{
Alter.Table("Logs").AlterColumn("Time").AsDateTimeOffset().NotNullable();
Alter.Table("UpdateHistory").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("VersionInfo").AlterColumn("AppliedOn").AsDateTimeOffset().Nullable();
}
}
}
@@ -1,5 +1,4 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@@ -19,7 +18,7 @@ namespace NzbDrone.Core.Datastore
.Build(); .Build();
var postgresOptions = new PostgresOptions(); var postgresOptions = new PostgresOptions();
config.GetSection($"{BuildInfo.AppName}:Postgres").Bind(postgresOptions); config.GetSection("Radarr:Postgres").Bind(postgresOptions);
return postgresOptions; return postgresOptions;
} }
@@ -180,14 +180,14 @@ namespace NzbDrone.Core.Download
} }
else else
{ {
_logger.Debug() _logger.ForDebugEvent()
.Message("No Movies were just imported, but all movies were previously imported, possible issue with download history.") .Message("No Movies were just imported, but all movies were previously imported, possible issue with download history.")
.Property("MovieId", trackedDownload.RemoteMovie.Movie.Id) .Property("MovieId", trackedDownload.RemoteMovie.Movie.Id)
.Property("DownloadId", trackedDownload.DownloadItem.DownloadId) .Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
.Property("Title", trackedDownload.DownloadItem.Title) .Property("Title", trackedDownload.DownloadItem.Title)
.Property("Path", trackedDownload.ImportItem.OutputPath.ToString()) .Property("Path", trackedDownload.ImportItem.OutputPath.ToString())
.WriteSentryWarn("DownloadHistoryIncomplete") .WriteSentryWarn("DownloadHistoryIncomplete")
.Write(); .Log();
} }
trackedDownload.State = TrackedDownloadState.Imported; trackedDownload.State = TrackedDownloadState.Imported;
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download.History
public class DownloadHistoryService : IDownloadHistoryService, public class DownloadHistoryService : IDownloadHistoryService,
IHandle<MovieGrabbedEvent>, IHandle<MovieGrabbedEvent>,
IHandle<MovieImportedEvent>, IHandle<MovieFileImportedEvent>,
IHandle<DownloadCompletedEvent>, IHandle<DownloadCompletedEvent>,
IHandle<DownloadFailedEvent>, IHandle<DownloadFailedEvent>,
IHandle<DownloadIgnoredEvent>, IHandle<DownloadIgnoredEvent>,
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Download.History
_repository.Insert(history); _repository.Insert(history);
} }
public void Handle(MovieImportedEvent message) public void Handle(MovieFileImportedEvent message)
{ {
if (!message.NewDownload) if (!message.NewDownload)
{ {
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
public class DownloadMonitoringService : IExecute<RefreshMonitoredDownloadsCommand>, public class DownloadMonitoringService : IExecute<RefreshMonitoredDownloadsCommand>,
IExecute<CheckForFinishedDownloadCommand>, IExecute<CheckForFinishedDownloadCommand>,
IHandle<MovieGrabbedEvent>, IHandle<MovieGrabbedEvent>,
IHandle<MovieImportedEvent>, IHandle<MovieFileImportedEvent>,
IHandle<DownloadsProcessedEvent>, IHandle<DownloadsProcessedEvent>,
IHandle<TrackedDownloadsRemovedEvent> IHandle<TrackedDownloadsRemovedEvent>
{ {
@@ -167,7 +167,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_refreshDebounce.Execute(); _refreshDebounce.Execute();
} }
public void Handle(MovieImportedEvent message) public void Handle(MovieFileImportedEvent message)
{ {
_refreshDebounce.Execute(); _refreshDebounce.Execute();
} }
@@ -158,22 +158,32 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{ {
var setRating = new XElement("ratings"); var setRating = new XElement("ratings");
if (movie.MovieMetadata.Value.Ratings.Tmdb?.Votes > 0) var defaultRatingSet = false;
{
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) if (movie.MovieMetadata.Value.Ratings.Imdb?.Votes > 0)
{ {
var setRateImdb = new XElement("rating", new XAttribute("name", "imdb"), new XAttribute("max", "10")); var setRateImdb = new XElement("rating", new XAttribute("name", "imdb"), new XAttribute("max", "10"), new XAttribute("default", "true"));
setRateImdb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Imdb.Value)); setRateImdb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Imdb.Value));
setRateImdb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Imdb.Votes)); setRateImdb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Imdb.Votes));
defaultRatingSet = true;
setRating.Add(setRateImdb); 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); details.Add(setRating);
} }
@@ -10,7 +10,7 @@ using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks namespace NzbDrone.Core.HealthCheck.Checks
{ {
[CheckOn(typeof(ProviderUpdatedEvent<IImportList>))] [CheckOn(typeof(ProviderUpdatedEvent<IImportList>))]
[CheckOn(typeof(MovieImportedEvent), CheckOnCondition.FailedOnly)] [CheckOn(typeof(MovieFileImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieImportFailedEvent), CheckOnCondition.SuccessfulOnly)] [CheckOn(typeof(MovieImportFailedEvent), CheckOnCondition.SuccessfulOnly)]
public class ImportListRootFolderCheck : HealthCheckBase public class ImportListRootFolderCheck : HealthCheckBase
{ {
@@ -1,47 +0,0 @@
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,13 +2,15 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
using NzbDrone.Core.Movies.Collections; using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Events; using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.HealthCheck.Checks namespace NzbDrone.Core.HealthCheck.Checks
{ {
[CheckOn(typeof(CollectionEditedEvent), CheckOnCondition.Always)] [CheckOn(typeof(ModelEvent<RootFolder>))]
public class MovieCollectionRootFolderCheck : HealthCheckBase public class MovieCollectionRootFolderCheck : HealthCheckBase
{ {
private readonly IMovieCollectionService _collectionService; private readonly IMovieCollectionService _collectionService;
@@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
@@ -10,9 +9,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
[CheckOn(typeof(ConfigSavedEvent))] [CheckOn(typeof(ConfigSavedEvent))]
public class ReleaseBranchCheck : HealthCheckBase public class ReleaseBranchCheck : HealthCheckBase
{ {
private readonly IOptionsMonitor<ConfigFileOptions> _configFileService; private readonly IConfigFileProvider _configFileService;
public ReleaseBranchCheck(IOptionsMonitor<ConfigFileOptions> configFileService, ILocalizationService localizationService) public ReleaseBranchCheck(IConfigFileProvider configFileService, ILocalizationService localizationService)
: base(localizationService) : base(localizationService)
{ {
_configFileService = configFileService; _configFileService = configFileService;
@@ -20,11 +19,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check() public override HealthCheck Check()
{ {
var currentBranch = _configFileService.CurrentValue.Branch.ToLower(); var currentBranch = _configFileService.Branch.ToLower();
if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch)) if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch))
{ {
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(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch-is-not-a-valid-release-branch");
} }
return new HealthCheck(GetType()); return new HealthCheck(GetType());
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
[CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))] [CheckOn(typeof(ProviderUpdatedEvent<IDownloadClient>))]
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))] [CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
[CheckOn(typeof(ModelEvent<RemotePathMapping>))] [CheckOn(typeof(ModelEvent<RemotePathMapping>))]
[CheckOn(typeof(MovieImportedEvent), CheckOnCondition.FailedOnly)] [CheckOn(typeof(MovieFileImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieImportFailedEvent), CheckOnCondition.SuccessfulOnly)] [CheckOn(typeof(MovieImportFailedEvent), CheckOnCondition.SuccessfulOnly)]
public class RemotePathMappingCheck : HealthCheckBase, IProvideHealthCheck public class RemotePathMappingCheck : HealthCheckBase, IProvideHealthCheck
{ {
@@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -17,13 +16,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly ICheckUpdateService _checkUpdateService; private readonly ICheckUpdateService _checkUpdateService;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
private readonly IOsInfo _osInfo; private readonly IOsInfo _osInfo;
public UpdateCheck(IDiskProvider diskProvider, public UpdateCheck(IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo, IAppFolderInfo appFolderInfo,
ICheckUpdateService checkUpdateService, ICheckUpdateService checkUpdateService,
IOptionsMonitor<ConfigFileOptions> configFileOptions, IConfigFileProvider configFileProvider,
IOsInfo osInfo, IOsInfo osInfo,
ILocalizationService localizationService) ILocalizationService localizationService)
: base(localizationService) : base(localizationService)
@@ -31,7 +30,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
_diskProvider = diskProvider; _diskProvider = diskProvider;
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_checkUpdateService = checkUpdateService; _checkUpdateService = checkUpdateService;
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_osInfo = osInfo; _osInfo = osInfo;
} }
@@ -40,8 +39,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
var startupFolder = _appFolderInfo.StartUpFolder; var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI"); var uiFolder = Path.Combine(startupFolder, "UI");
if ((OsInfo.IsWindows || _configFileOptions.CurrentValue.UpdateAutomatically) && if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) &&
_configFileOptions.CurrentValue.UpdateMechanism == UpdateMechanism.BuiltIn && _configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
!_osInfo.IsDocker) !_osInfo.IsDocker)
{ {
if (OsInfo.IsOsx && startupFolder.GetAncestorFolders().Contains("AppTranslocation")) if (OsInfo.IsOsx && startupFolder.GetAncestorFolders().Contains("AppTranslocation"))
@@ -77,12 +77,15 @@ namespace NzbDrone.Core.HealthCheck
.ToDictionary(g => g.Key, g => g.ToArray()); .ToDictionary(g => g.Key, g => g.ToArray());
} }
private void PerformHealthCheck(IProvideHealthCheck[] healthChecks) private void PerformHealthCheck(IProvideHealthCheck[] healthChecks, bool performServerChecks = false)
{ {
var results = healthChecks.Select(c => c.Check()) var results = healthChecks.Select(c => c.Check())
.ToList(); .ToList();
results.AddRange(_serverSideNotificationService.GetServerChecks()); if (performServerChecks)
{
results.AddRange(_serverSideNotificationService.GetServerChecks());
}
foreach (var result in results) foreach (var result in results)
{ {
@@ -108,17 +111,17 @@ namespace NzbDrone.Core.HealthCheck
{ {
if (message.Trigger == CommandTrigger.Manual) if (message.Trigger == CommandTrigger.Manual)
{ {
PerformHealthCheck(_healthChecks); PerformHealthCheck(_healthChecks, true);
} }
else else
{ {
PerformHealthCheck(_scheduledHealthChecks); PerformHealthCheck(_scheduledHealthChecks, true);
} }
} }
public void HandleAsync(ApplicationStartedEvent message) public void HandleAsync(ApplicationStartedEvent message)
{ {
PerformHealthCheck(_startupHealthChecks); PerformHealthCheck(_startupHealthChecks, true);
} }
public void HandleAsync(IEvent message) public void HandleAsync(IEvent message)
@@ -2,8 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.Extensions.Options;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Cloud; using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -20,28 +20,41 @@ namespace NzbDrone.Core.HealthCheck
public class ServerSideNotificationService : IServerSideNotificationService public class ServerSideNotificationService : IServerSideNotificationService
{ {
private readonly IHttpClient _client; private readonly IHttpClient _client;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder; private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
private readonly Logger _logger; private readonly Logger _logger;
public ServerSideNotificationService(IHttpClient client, IOptionsMonitor<ConfigFileOptions> configFileOptions, IRadarrCloudRequestBuilder cloudRequestBuilder, Logger logger) private readonly ICached<List<HealthCheck>> _cache;
public ServerSideNotificationService(IHttpClient client,
IConfigFileProvider configFileProvider,
IRadarrCloudRequestBuilder cloudRequestBuilder,
ICacheManager cacheManager,
Logger logger)
{ {
_client = client; _client = client;
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_cloudRequestBuilder = cloudRequestBuilder.Services; _cloudRequestBuilder = cloudRequestBuilder.Services;
_logger = logger; _logger = logger;
_cache = cacheManager.GetCache<List<HealthCheck>>(GetType());
} }
public List<HealthCheck> GetServerChecks() public List<HealthCheck> GetServerChecks()
{
return _cache.Get("ServerChecks", () => RetrieveServerChecks(), TimeSpan.FromHours(2));
}
private List<HealthCheck> RetrieveServerChecks()
{ {
var request = _cloudRequestBuilder.Create() var request = _cloudRequestBuilder.Create()
.Resource("/notification") .Resource("/notification")
.AddQueryParam("version", BuildInfo.Version) .AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()) .AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture) .AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant()) .AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("branch", _configFileOptions.CurrentValue.Branch) .AddQueryParam("branch", _configFileProvider.Branch)
.Build(); .Build();
try try
{ {
_logger.Trace("Getting server side health notifications"); _logger.Trace("Getting server side health notifications");
+4 -4
View File
@@ -27,13 +27,13 @@ namespace NzbDrone.Core.History
List<MovieHistory> FindByDownloadId(string downloadId); List<MovieHistory> FindByDownloadId(string downloadId);
List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType); List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType);
void UpdateMany(List<MovieHistory> toUpdate); void UpdateMany(List<MovieHistory> toUpdate);
string FindDownloadId(MovieImportedEvent trackedDownload); string FindDownloadId(MovieFileImportedEvent trackedDownload);
List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType); List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType);
} }
public class HistoryService : IHistoryService, public class HistoryService : IHistoryService,
IHandle<MovieGrabbedEvent>, IHandle<MovieGrabbedEvent>,
IHandle<MovieImportedEvent>, IHandle<MovieFileImportedEvent>,
IHandle<DownloadFailedEvent>, IHandle<DownloadFailedEvent>,
IHandle<MovieFileDeletedEvent>, IHandle<MovieFileDeletedEvent>,
IHandle<MovieFileRenamedEvent>, IHandle<MovieFileRenamedEvent>,
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.History
_historyRepository.UpdateMany(toUpdate); _historyRepository.UpdateMany(toUpdate);
} }
public string FindDownloadId(MovieImportedEvent trackedDownload) public string FindDownloadId(MovieFileImportedEvent trackedDownload)
{ {
_logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedMovie.Path); _logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedMovie.Path);
@@ -170,7 +170,7 @@ namespace NzbDrone.Core.History
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
public void Handle(MovieImportedEvent message) public void Handle(MovieFileImportedEvent message)
{ {
if (!message.NewDownload) if (!message.NewDownload)
{ {
@@ -16,12 +16,10 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
using (var mapper = _database.OpenConnection()) using (var mapper = _database.OpenConnection())
{ {
mapper.Execute(@"DELETE FROM ""Collections"" mapper.Execute(@"DELETE FROM ""Collections"" WHERE ""TmdbId"" IN (SELECT ""X"".""TmdbId"" FROM (SELECT ""Collections"".""TmdbId"", COUNT(""Movies"".""Id"") as ""MovieCount"" FROM ""Collections""
WHERE ""TmdbId"" IN ( LEFT OUTER JOIN ""MovieMetadata"" ON ""Collections"".""TmdbId"" = ""MovieMetadata"".""CollectionTmdbId""
SELECT ""Collections"".""TmdbId"" FROM ""Collections"" LEFT OUTER JOIN ""Movies"" ON ""Movies"".""MovieMetadataId"" = ""MovieMetadata"".""Id""
LEFT OUTER JOIN ""MovieMetadata"" GROUP BY ""Collections"".""Id"") AS ""X"" WHERE ""X"".""MovieCount"" = 0)");
ON ""Collections"".""TmdbId"" = ""MovieMetadata"".""CollectionTmdbId""
WHERE ""MovieMetadata"".""Id"" IS NULL)");
} }
} }
} }
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.ImportLists
private readonly IImportListStatusService _importListStatusService; private readonly IImportListStatusService _importListStatusService;
private readonly IImportListMovieService _listMovieService; private readonly IImportListMovieService _listMovieService;
private readonly ISearchForNewMovie _movieSearch; private readonly ISearchForNewMovie _movieSearch;
private readonly IProvideMovieInfo _movieInfoService;
private readonly IMovieMetadataService _movieMetadataService; private readonly IMovieMetadataService _movieMetadataService;
private readonly Logger _logger; private readonly Logger _logger;
@@ -30,6 +31,7 @@ namespace NzbDrone.Core.ImportLists
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,
IImportListMovieService listMovieService, IImportListMovieService listMovieService,
ISearchForNewMovie movieSearch, ISearchForNewMovie movieSearch,
IProvideMovieInfo movieInfoService,
IMovieMetadataService movieMetadataService, IMovieMetadataService movieMetadataService,
Logger logger) Logger logger)
{ {
@@ -37,6 +39,7 @@ namespace NzbDrone.Core.ImportLists
_importListStatusService = importListStatusService; _importListStatusService = importListStatusService;
_listMovieService = listMovieService; _listMovieService = listMovieService;
_movieSearch = movieSearch; _movieSearch = movieSearch;
_movieInfoService = movieInfoService;
_movieMetadataService = movieMetadataService; _movieMetadataService = movieMetadataService;
_logger = logger; _logger = logger;
} }
@@ -84,20 +87,10 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure) if (!importListReports.AnyFailure)
{ {
// TODO some opportunity to bulk map here if we had the tmdbIds var alreadyMapped = result.Movies.Where(x => importListReports.Movies.Any(r => r.TmdbId == x.TmdbId));
var listMovies = importListReports.Movies.Select(x => var listMovies = MapMovieReports(importListReports.Movies.Where(x => !result.Movies.Any(r => r.TmdbId == x.TmdbId)).ToList()).Where(x => x.TmdbId > 0).ToList();
{
// 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 = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id); listMovies.ForEach(m => m.ListId = importList.Definition.Id);
@@ -148,13 +141,10 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure) if (!importListReports.AnyFailure)
{ {
// TODO some opportunity to bulk map here if we had the tmdbIds var listMovies = MapMovieReports(importListReports.Movies).Where(x => x.TmdbId > 0).ToList();
var listMovies = importListReports.Movies.Select(x =>
{
return MapMovieReport(x);
}).Where(x => x.TmdbId > 0).ToList();
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList(); listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
result.Movies.AddRange(listMovies); result.Movies.AddRange(listMovies);
_listMovieService.SyncMoviesForList(listMovies, importList.Definition.Id); _listMovieService.SyncMoviesForList(listMovies, importList.Definition.Id);
@@ -173,21 +163,30 @@ namespace NzbDrone.Core.ImportLists
return result; return result;
} }
private ImportListMovie MapMovieReport(ImportListMovie report) private List<ImportListMovie> MapMovieReports(List<ImportListMovie> reports)
{ {
var mappedMovie = _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = report.Title, TmdbId = report.TmdbId, ImdbId = report.ImdbId, Year = report.Year }); var mappedMovies = reports.Select(m => _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = m.Title, TmdbId = m.TmdbId, ImdbId = m.ImdbId, Year = m.Year }))
.Where(x => x != null)
.ToList();
var mappedListMovie = new ImportListMovie { ListId = report.ListId }; _movieMetadataService.UpsertMany(mappedMovies);
if (mappedMovie != null) var mappedListMovies = new List<ImportListMovie>();
foreach (var movieMeta in mappedMovies)
{ {
_movieMetadataService.Upsert(mappedMovie); var mappedListMovie = new ImportListMovie();
mappedListMovie.MovieMetadata = mappedMovie; if (movieMeta != null)
mappedListMovie.MovieMetadataId = mappedMovie.Id; {
mappedListMovie.MovieMetadata = movieMeta;
mappedListMovie.MovieMetadataId = movieMeta.Id;
}
mappedListMovies.Add(mappedListMovie);
} }
return mappedListMovie; return mappedListMovies;
} }
} }
} }
@@ -42,14 +42,15 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var movieId in message.MovieIds) foreach (var movieId in message.MovieIds)
{ {
var movie = _movieService.GetMovie(movieId); var movie = _movieService.GetMovie(movieId);
var userInvokedSearch = message.Trigger == CommandTrigger.Manual;
if (!movie.Monitored && message.Trigger != CommandTrigger.Manual) if (!movie.Monitored && !userInvokedSearch)
{ {
_logger.Debug("Movie {0} is not monitored, skipping search", movie.Title); _logger.Debug("Movie {0} is not monitored, skipping search", movie.Title);
continue; continue;
} }
var decisions = _releaseSearchService.MovieSearch(movieId, false, false); var decisions = _releaseSearchService.MovieSearch(movieId, userInvokedSearch, false);
downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count;
} }
@@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options;
using NLog; using NLog;
using NLog.Config; using NLog.Config;
using NLog.Targets.Syslog; using NLog.Targets.Syslog;
@@ -18,21 +17,21 @@ namespace NzbDrone.Core.Instrumentation
{ {
public class ReconfigureLogging : IHandleAsync<ConfigFileSavedEvent> public class ReconfigureLogging : IHandleAsync<ConfigFileSavedEvent>
{ {
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
public ReconfigureLogging(IOptionsMonitor<ConfigFileOptions> configFileOptions) public ReconfigureLogging(IConfigFileProvider configFileProvider)
{ {
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
} }
public void Reconfigure() public void Reconfigure()
{ {
var minimumLogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.LogLevel); var minimumLogLevel = LogLevel.FromString(_configFileProvider.LogLevel);
LogLevel minimumConsoleLogLevel; LogLevel minimumConsoleLogLevel;
if (_configFileOptions.CurrentValue.ConsoleLogLevel.IsNotNullOrWhiteSpace()) if (_configFileProvider.ConsoleLogLevel.IsNotNullOrWhiteSpace())
{ {
minimumConsoleLogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.ConsoleLogLevel); minimumConsoleLogLevel = LogLevel.FromString(_configFileProvider.ConsoleLogLevel);
} }
else if (minimumLogLevel > LogLevel.Info) else if (minimumLogLevel > LogLevel.Info)
{ {
@@ -43,10 +42,10 @@ namespace NzbDrone.Core.Instrumentation
minimumConsoleLogLevel = LogLevel.Info; minimumConsoleLogLevel = LogLevel.Info;
} }
if (_configFileOptions.CurrentValue.SyslogServer.IsNotNullOrWhiteSpace()) if (_configFileProvider.SyslogServer.IsNotNullOrWhiteSpace())
{ {
var syslogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.SyslogLevel); var syslogLevel = LogLevel.FromString(_configFileProvider.SyslogLevel);
SetSyslogParameters(_configFileOptions.CurrentValue.SyslogServer, _configFileOptions.CurrentValue.SyslogPort, syslogLevel); SetSyslogParameters(_configFileProvider.SyslogServer, _configFileProvider.SyslogPort, syslogLevel);
} }
var rules = LogManager.Configuration.LoggingRules; var rules = LogManager.Configuration.LoggingRules;
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Instrumentation
SetLogRotation(); SetLogRotation();
//Log Sql //Log Sql
SqlBuilderExtensions.LogSql = _configFileOptions.CurrentValue.LogSql; SqlBuilderExtensions.LogSql = _configFileProvider.LogSql;
//Sentry //Sentry
ReconfigureSentry(); ReconfigureSentry();
@@ -96,7 +95,7 @@ namespace NzbDrone.Core.Instrumentation
{ {
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>()) foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
{ {
target.MaxArchiveFiles = _configFileOptions.CurrentValue.LogRotate; target.MaxArchiveFiles = _configFileProvider.LogRotate;
} }
} }
@@ -105,8 +104,8 @@ namespace NzbDrone.Core.Instrumentation
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault(); var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null) if (sentryTarget != null)
{ {
sentryTarget.SentryEnabled = (RuntimeInfo.IsProduction && _configFileOptions.CurrentValue.AnalyticsEnabled) || RuntimeInfo.IsDevelopment; sentryTarget.SentryEnabled = (RuntimeInfo.IsProduction && _configFileProvider.AnalyticsEnabled) || RuntimeInfo.IsDevelopment;
sentryTarget.FilterEvents = _configFileOptions.CurrentValue.FilterSentryEvents; sentryTarget.FilterEvents = _configFileProvider.FilterSentryEvents;
} }
} }
@@ -118,9 +117,8 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Protocol = ProtocolType.Udp; syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
syslogTarget.MessageSend.Udp.Port = syslogPort; syslogTarget.MessageSend.Udp.Port = syslogPort;
syslogTarget.MessageSend.Udp.Server = syslogServer; syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424; syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileOptions.CurrentValue.InstanceName; syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;
var loggingRule = new LoggingRule("*", minimumLogLevel, syslogTarget); var loggingRule = new LoggingRule("*", minimumLogLevel, syslogTarget);
@@ -1,5 +1,4 @@
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Sentry; using NzbDrone.Common.Instrumentation.Sentry;
@@ -12,15 +11,15 @@ namespace NzbDrone.Core.Instrumentation
{ {
public class ReconfigureSentry : IHandleAsync<ApplicationStartedEvent> public class ReconfigureSentry : IHandleAsync<ApplicationStartedEvent>
{ {
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
private readonly IPlatformInfo _platformInfo; private readonly IPlatformInfo _platformInfo;
private readonly IMainDatabase _database; private readonly IMainDatabase _database;
public ReconfigureSentry(IOptionsMonitor<ConfigFileOptions> configFileOptions, public ReconfigureSentry(IConfigFileProvider configFileProvider,
IPlatformInfo platformInfo, IPlatformInfo platformInfo,
IMainDatabase database) IMainDatabase database)
{ {
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_platformInfo = platformInfo; _platformInfo = platformInfo;
_database = database; _database = database;
} }
@@ -31,7 +30,7 @@ namespace NzbDrone.Core.Instrumentation
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault(); var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null) if (sentryTarget != null)
{ {
sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileOptions.CurrentValue.Branch, _platformInfo); sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileProvider.Branch, _platformInfo);
} }
} }
File diff suppressed because it is too large Load Diff
+36 -10
View File
@@ -59,7 +59,7 @@
"IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung verfügbar sind, erfasst Radarr nicht automatisch neue Releases auf", "IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung verfügbar sind, erfasst Radarr nicht automatisch neue Releases auf",
"IndexerSearchCheckNoAutomaticMessage": "Keine Indexer mit aktivierter automatischer Suche verfügbar. Radarr liefert keine automatischen Suchergebnisse", "IndexerSearchCheckNoAutomaticMessage": "Keine Indexer mit aktivierter automatischer Suche verfügbar. Radarr liefert keine automatischen Suchergebnisse",
"IndexerSearchCheckNoAvailableIndexersMessage": "Alle suchfähigen Indexer sind aufgrund der kürzlichen Indexerfehler vorübergehend nicht verfügbar", "IndexerSearchCheckNoAvailableIndexersMessage": "Alle suchfähigen Indexer sind aufgrund der kürzlichen Indexerfehler vorübergehend nicht verfügbar",
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit interaktiver Suche aktiviert, Radarr liefert keine interaktiven Suchergebnisse", "IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit aktivierter interaktiver Suche verfügbar, Radarr liefert keine interaktiven Suchergebnisse",
"IndexerStatusCheckAllClientMessage": "Alle Indexer sind aufgrund von Fehlern nicht verfügbar", "IndexerStatusCheckAllClientMessage": "Alle Indexer sind aufgrund von Fehlern nicht verfügbar",
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}", "IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
"Indexers": "Indexer", "Indexers": "Indexer",
@@ -256,7 +256,7 @@
"AlternativeTitle": "Alternative Titel", "AlternativeTitle": "Alternative Titel",
"AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.", "AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Age": "Alter", "Age": "Alter",
"AddNewMovie": "Neuer Film...", "AddNewMovie": "Neuen Film hinzufügen",
"AddList": "Liste hinzufügen", "AddList": "Liste hinzufügen",
"SystemTimeCheckMessage": "Die Systemzeit ist um einen Tag versetzt. Bis die Zeit korrigiert wurde, könnten die geplanten Aufgaben nicht korrekt ausgeführt werden", "SystemTimeCheckMessage": "Die Systemzeit ist um einen Tag versetzt. Bis die Zeit korrigiert wurde, könnten die geplanten Aufgaben nicht korrekt ausgeführt werden",
"UnsavedChanges": "Ungespeicherte Änderungen", "UnsavedChanges": "Ungespeicherte Änderungen",
@@ -506,7 +506,7 @@
"ScriptPath": "Script Pfad", "ScriptPath": "Script Pfad",
"SetPermissions": "Rechte setzen", "SetPermissions": "Rechte setzen",
"SetPermissionsLinuxHelpTextWarning": "Ändere diese Einstellung nur, wenn du weißt was sie bewirken.", "SetPermissionsLinuxHelpTextWarning": "Ändere diese Einstellung nur, wenn du weißt was sie bewirken.",
"ShouldMonitorHelpText": "Beobachte Filme die von dieser Liste hinzugefügt wurden", "ShouldMonitorHelpText": "Sollen von dieser Liste hinzugefügte Filme oder Sammlungen als beobachtet hinzugefügt werden",
"ShowAsAllDayEvents": "Als Ganztags Events anzeigen", "ShowAsAllDayEvents": "Als Ganztags Events anzeigen",
"ShowCutoffUnmetIconHelpText": "Symbol zeigen wenn die Qualitätsschwelle noch nicht erreicht wurde", "ShowCutoffUnmetIconHelpText": "Symbol zeigen wenn die Qualitätsschwelle noch nicht erreicht wurde",
"ShowMovieInformationHelpText": "Genre und Zertifizierung anzeigen", "ShowMovieInformationHelpText": "Genre und Zertifizierung anzeigen",
@@ -633,14 +633,14 @@
"ShowGenres": "Genres anzeigen", "ShowGenres": "Genres anzeigen",
"ShowCertification": "Zertifikation anzeigen", "ShowCertification": "Zertifikation anzeigen",
"SettingsRuntimeFormat": "Laufzeit Format", "SettingsRuntimeFormat": "Laufzeit Format",
"SearchOnAddHelpText": "Suche nach den Filmen auf der Liste nach dem hinzufügen", "SearchOnAddHelpText": "Nach Filmen auf dieser Liste suchen, wenn sie der Bibliothek hinzugefügt werden",
"RSSSyncIntervalHelpTextWarning": "Dies wird alle Indexer betreffen. Bitte folge deren Regeln", "RSSSyncIntervalHelpTextWarning": "Dies wird alle Indexer betreffen. Bitte folge deren Regeln",
"RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt", "RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
"RetryingDownloadInterp": "Herunterladen nochmal versuchen {0} um {1}", "RetryingDownloadInterp": "Herunterladen nochmal versuchen {0} um {1}",
"RemovingTag": "Tag entfernen", "RemovingTag": "Tag entfernen",
"ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}", "ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}",
"Queued": "Eingereiht", "Queued": "Eingereiht",
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen?", "QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen",
"Pending": "Ausstehend", "Pending": "Ausstehend",
"Paused": "Pausiert", "Paused": "Pausiert",
"NegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.", "NegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
@@ -1047,7 +1047,7 @@
"RemotePathMappingCheckFilesLocalWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.", "RemotePathMappingCheckFilesLocalWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"RemotePathMappingCheckFilesBadDockerPath": "Docker erkannt; Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.", "RemotePathMappingCheckFilesBadDockerPath": "Docker erkannt; Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckFilesWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.", "RemotePathMappingCheckFilesWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise müssen die Verzeichnisrechte angepasst werden.", "RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Remote-Pfadzuordnungen und die Downloader Einstellungen.", "RemotePathMappingCheckWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.", "RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"RemotePathMappingCheckLocalFolderMissing": "Downloader {0} speichert Downloads in {1}, aber dieses Verzeichnis scheint nicht zu existieren. Möglicherweise eine fehlende oder falsche Remote-Pfadzuordnung.", "RemotePathMappingCheckLocalFolderMissing": "Downloader {0} speichert Downloads in {1}, aber dieses Verzeichnis scheint nicht zu existieren. Möglicherweise eine fehlende oder falsche Remote-Pfadzuordnung.",
@@ -1112,9 +1112,35 @@
"OriginalTitle": "Originaler Titel", "OriginalTitle": "Originaler Titel",
"OriginalLanguage": "Originale Sprache", "OriginalLanguage": "Originale Sprache",
"Database": "Datenbank", "Database": "Datenbank",
"AllCollectionsHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.", "AllCollectionsHiddenDueToFilter": "Alle Filme sind auf Grund des Filters ausgeblendet.",
"Collections": "Sammlung", "Collections": "Sammlungen",
"MonitorMovies": "Film beobachten", "MonitorMovies": "Film beobachten",
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren.", "NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren",
"RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )" "RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )",
"CollectionOptions": "Sammlung Optionen",
"ChooseImportMode": "Wähle eine Importmethode",
"CollectionsSelectedInterp": "{0} Ausgewählte Sammlung(en)",
"MovieCollectionMissingRoot": "Fehlender Stammordner für die Filmsammlung: {0}",
"EditCollection": "Sammlung bearbeiten",
"MonitoredCollectionHelpText": "Beobachten zur automatischen Aufnahme von Filmen aus dieser Sammlung in die Bibliothek",
"MovieOnly": "Nur Film",
"ScrollMovies": "Filme scrollen",
"ShowCollectionDetails": "Status der Sammlung anzeigen",
"RefreshCollections": "Sammlungen aktualisieren",
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (min. 1 Minute)",
"ShowOverview": "Übersicht anzeigen",
"ShowPosters": "Plakate anzeigen",
"CollectionShowDetailsHelpText": "Status und Eigenschaften der Sammlung anzeigen",
"CollectionShowPostersHelpText": "Poster der Sammlungseinträge zeigen",
"SearchOnAddCollectionHelpText": "Nach Filmen in dieser Sammlung suchen, wenn sie der Bibliothek hinzugefügt werden",
"CollectionShowOverviewsHelpText": "Sammlungsübersichten anzeigen",
"MonitorCollection": "Sammlung beobachten",
"MovieAndCollection": "Film und Sammlung",
"MovieCollectionMultipleMissingRoots": "Es fehlen mehrere Stammordner für Filmsammlungen: {0}",
"OnMovieAdded": "Bei Film hinzugefügt",
"OnMovieAddedHelpText": "Ausführen wenn ein Film hinzugefügt wurde",
"UnableToLoadCollections": "Sammlungen können nicht geladen werden",
"InstanceName": "Instanzname",
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
"RottenTomatoesRating": "Tomato Bewertung"
} }
+2 -1
View File
@@ -696,7 +696,6 @@
"PosterOptions": "Poster Options", "PosterOptions": "Poster Options",
"Posters": "Posters", "Posters": "Posters",
"PosterSize": "Poster Size", "PosterSize": "Poster Size",
"PostgresLegacyEnvironmentVariables": "You have defined the following legacy PostgreSQL environment variables: {0}. Please update them to: {1}",
"PreferAndUpgrade": "Prefer and Upgrade", "PreferAndUpgrade": "Prefer and Upgrade",
"PreferIndexerFlags": "Prefer Indexer Flags", "PreferIndexerFlags": "Prefer Indexer Flags",
"PreferIndexerFlagsHelpText": "Prioritize releases with special flags", "PreferIndexerFlagsHelpText": "Prioritize releases with special flags",
@@ -860,6 +859,7 @@
"RootFolderCheckMultipleMessage": "Multiple root folders are missing: {0}", "RootFolderCheckMultipleMessage": "Multiple root folders are missing: {0}",
"RootFolderCheckSingleMessage": "Missing root folder: {0}", "RootFolderCheckSingleMessage": "Missing root folder: {0}",
"RootFolders": "Root Folders", "RootFolders": "Root Folders",
"RottenTomatoesRating": "Tomato Rating",
"RSS": "RSS", "RSS": "RSS",
"RSSIsNotSupportedWithThisIndexer": "RSS is not supported with this indexer", "RSSIsNotSupportedWithThisIndexer": "RSS is not supported with this indexer",
"RSSSync": "RSS Sync", "RSSSync": "RSS Sync",
@@ -1027,6 +1027,7 @@
"Torrents": "Torrents", "Torrents": "Torrents",
"TorrentsDisabled": "Torrents Disabled", "TorrentsDisabled": "Torrents Disabled",
"TotalFileSize": "Total File Size", "TotalFileSize": "Total File Size",
"TotalMovies": "Total Movies",
"TotalSpace": "Total Space", "TotalSpace": "Total Space",
"Trace": "Trace", "Trace": "Trace",
"Trailer": "Trailer", "Trailer": "Trailer",
+5 -1
View File
@@ -1115,5 +1115,9 @@
"AllCollectionsHiddenDueToFilter": "Todas las películas están ocultas debido al filtro aplicado.", "AllCollectionsHiddenDueToFilter": "Todas las películas están ocultas debido al filtro aplicado.",
"Collections": "Colección", "Collections": "Colección",
"MonitorMovies": "Monitorear Película", "MonitorMovies": "Monitorear Película",
"NoCollections": "No se encontraron películas, para comenzar, querrá agregar una nueva película o importar algunas existentes." "NoCollections": "No se encontraron películas, para comenzar, querrá agregar una nueva película o importar algunas existentes.",
"ChooseImportMode": "Elegir Modo de Importación",
"CollectionOptions": "Opciones de la Colección",
"CollectionShowDetailsHelpText": "Mostrar el estado y propiedades de la colección",
"CollectionShowOverviewsHelpText": "Mostrar resumenes de la colección"
} }
+7 -5
View File
@@ -220,7 +220,7 @@
"MoveFiles": "Siirrä tiedostoja", "MoveFiles": "Siirrä tiedostoja",
"MovieFiles": "Elokuvatiedostot", "MovieFiles": "Elokuvatiedostot",
"MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella", "MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella",
"NextExecution": "Seuraava toteutus", "NextExecution": "Seuraava suoritus",
"NoAltTitle": "Ei vaihtoehtoisia nimikkeitä.", "NoAltTitle": "Ei vaihtoehtoisia nimikkeitä.",
"NoEventsFound": "Tapahtumia ei löytynyt", "NoEventsFound": "Tapahtumia ei löytynyt",
"NoLinks": "Ei linkkejä", "NoLinks": "Ei linkkejä",
@@ -320,8 +320,8 @@
"ImportErrors": "Tuontivirheet", "ImportErrors": "Tuontivirheet",
"Imported": "Tuodut", "Imported": "Tuodut",
"InvalidFormat": "Väärä muoto", "InvalidFormat": "Väärä muoto",
"LastDuration": "Viimeisin kesto", "LastDuration": "Edellinen kesto",
"LastExecution": "Viimeinen toteutus", "LastExecution": "Edellinen suoritus",
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät", "ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
"ListExclusions": "Listojen poikkeussäännöt", "ListExclusions": "Listojen poikkeussäännöt",
"Indexer": "Tietolähde", "Indexer": "Tietolähde",
@@ -1115,7 +1115,7 @@
"RefreshMonitoredIntervalHelpText": "Miten usein valvottujen latausten tiedot päivitetään lataustyökaluilta (vähimmäisaika on 1 minuutti).", "RefreshMonitoredIntervalHelpText": "Miten usein valvottujen latausten tiedot päivitetään lataustyökaluilta (vähimmäisaika on 1 minuutti).",
"RssSyncHelpText": "Aikaväli minuutteina. Arvo nolla kytkee toiminnon pois käytöstä ja lopettaen samalla automaattisen julkaisujen kaappauksen täysin.", "RssSyncHelpText": "Aikaväli minuutteina. Arvo nolla kytkee toiminnon pois käytöstä ja lopettaen samalla automaattisen julkaisujen kaappauksen täysin.",
"InstanceName": "Instanssin nimi", "InstanceName": "Instanssin nimi",
"InstanceNameHelpText": "Instanssin nimi välilehdellä ja sovelluksen Syslog-nimeksi", "InstanceNameHelpText": "Instanssin nimi välilehdellä ja järjestelmälokissa",
"AllCollectionsHiddenDueToFilter": "Käytössä oleva suodatin on piilottanut kaikki kokoelmat.", "AllCollectionsHiddenDueToFilter": "Käytössä oleva suodatin on piilottanut kaikki kokoelmat.",
"Collections": "Kokoelmat", "Collections": "Kokoelmat",
"MonitorMovies": "Valvo elokuvia", "MonitorMovies": "Valvo elokuvia",
@@ -1141,5 +1141,7 @@
"CollectionShowOverviewsHelpText": "Näytä kokoelmien katsaukset", "CollectionShowOverviewsHelpText": "Näytä kokoelmien katsaukset",
"CollectionOptions": "Kokoelmien valinnat", "CollectionOptions": "Kokoelmien valinnat",
"CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet", "CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet",
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet" "CollectionShowPostersHelpText": "Näytä kokoelmien julisteet",
"RottenTomatoesRating": "Tomaattiarvio",
"TotalMovies": "Elokuvia yhteensä"
} }
+22 -4
View File
@@ -106,7 +106,7 @@
"RemotePathMappings": "Mappages de chemins distants", "RemotePathMappings": "Mappages de chemins distants",
"ReleaseBranchCheckOfficialBranchMessage": "La branche {0} n'est pas une branche de version Radarr valide, vous ne recevrez pas de mises à jour", "ReleaseBranchCheckOfficialBranchMessage": "La branche {0} n'est pas une branche de version Radarr valide, vous ne recevrez pas de mises à jour",
"RefreshAndScan": "Actualiser et analyser", "RefreshAndScan": "Actualiser et analyser",
"Refresh": "Rafraîchir", "Refresh": "Actualiser",
"Ratings": "Évaluations", "Ratings": "Évaluations",
"Queue": "File d'attente", "Queue": "File d'attente",
"QualitySettingsSummary": "Tailles qualité et dénomination", "QualitySettingsSummary": "Tailles qualité et dénomination",
@@ -318,7 +318,7 @@
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés", "ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
"ChangeFileDate": "Changer la date du fichier", "ChangeFileDate": "Changer la date du fichier",
"CertificationCountryHelpText": "Choisir un pays pour les classifications de films", "CertificationCountryHelpText": "Choisir un pays pour les classifications de films",
"CertificateValidationHelpText": "Change la rigueur de la vérification du certificat HTTPS. Ne changez rien sauf si vous comprenez les risques.", "CertificateValidationHelpText": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne pas changer à moins que vous ne compreniez les risques.",
"CertificateValidation": "Validation du certificat", "CertificateValidation": "Validation du certificat",
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales", "BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
"Branch": "Branche", "Branch": "Branche",
@@ -1114,10 +1114,28 @@
"SetReleaseGroup": "Régler le groupe de publication", "SetReleaseGroup": "Régler le groupe de publication",
"RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute", "RefreshMonitoredIntervalHelpText": "Intervalle en minutes entre chaque vérification des téléchargements, minimum 1 minute",
"AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.", "AllCollectionsHiddenDueToFilter": "Tous les films sont masqués en raison du filtre appliqué.",
"Collections": "Collection", "Collections": "Collections",
"MonitorMovies": "Surveiller le film", "MonitorMovies": "Surveiller le film",
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.", "NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
"RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)", "RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)",
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)", "CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
"ChooseImportMode": "Mode d'importation" "ChooseImportMode": "Mode d'importation",
"CollectionOptions": "Options de collection",
"CollectionShowDetailsHelpText": "Afficher l'état et les propriétés de la collection",
"CollectionShowOverviewsHelpText": "Afficher les aperçus des collections",
"OnMovieAddedHelpText": "À l'ajout d'un film",
"MovieAndCollection": "Film et collection",
"ShowOverview": "Afficher l'aperçu",
"MovieCollectionMissingRoot": "Dossier racine manquant pour la collection de films : {0}",
"OnMovieAdded": "À l'ajout d'un film",
"MonitorCollection": "Surveiller la collection",
"MonitoredCollectionHelpText": "Surveiller pour ajouter automatiquement les films de cette collection à la bibliothèque",
"MovieCollectionMultipleMissingRoots": "Plusieurs dossiers racine manquent pour les collections de films: {0}",
"MovieOnly": "Film seulement",
"RefreshCollections": "Actualiser les collections",
"SearchOnAddCollectionHelpText": "Rechercher des films dans cette collection lorsqu'ils sont ajoutés à Radarr",
"UnableToLoadCollections": "Impossible de charger les collections",
"RottenTomatoesRating": "Classement Rotten Tomatoes",
"ShowCollectionDetails": "Afficher l'état de la collection",
"EditCollection": "Modifier la collection"
} }
+12 -7
View File
@@ -21,7 +21,7 @@
"AddExclusion": "Kivétel hozzáadása", "AddExclusion": "Kivétel hozzáadása",
"Activity": "Aktivitás", "Activity": "Aktivitás",
"Error": "Hiba", "Error": "Hiba",
"Ended": "Befejezve", "Ended": "Vége lett",
"EnableCompletedDownloadHandlingHelpText": "A befejezett letöltések automatikus importálása a letöltési kliensből", "EnableCompletedDownloadHandlingHelpText": "A befejezett letöltések automatikus importálása a letöltési kliensből",
"EnableColorImpairedModeHelpText": "Megváltoztatott színek, hogy a színvak felhasználók jobban meg tudják különböztetni a színkódolt információkat", "EnableColorImpairedModeHelpText": "Megváltoztatott színek, hogy a színvak felhasználók jobban meg tudják különböztetni a színkódolt információkat",
"EnableAutomaticSearchHelpTextWarning": "Interaktív keresés esetén is felhasználható", "EnableAutomaticSearchHelpTextWarning": "Interaktív keresés esetén is felhasználható",
@@ -434,7 +434,7 @@
"RadarrSupportsAnyIndexer": "A Radarr minden indexert támogat, amely a Newznab szabványt használja, valamint az alább felsorolt egyéb indexereket.", "RadarrSupportsAnyIndexer": "A Radarr minden indexert támogat, amely a Newznab szabványt használja, valamint az alább felsorolt egyéb indexereket.",
"RadarrSupportsAnyDownloadClient": "A Radarr számos népszerű torrent és usenet letöltési ügyfelet támogat.", "RadarrSupportsAnyDownloadClient": "A Radarr számos népszerű torrent és usenet letöltési ügyfelet támogat.",
"QuickImport": "Automatikus Áthelyezés", "QuickImport": "Automatikus Áthelyezés",
"Queued": "Sorban", "Queued": "Sorba helyezve",
"Queue": "Várakozási sor", "Queue": "Várakozási sor",
"QualitySettingsSummary": "Minőségi méretek és elnevezés", "QualitySettingsSummary": "Minőségi méretek és elnevezés",
"QualitySettings": "Minőségi beállítások", "QualitySettings": "Minőségi beállítások",
@@ -616,7 +616,7 @@
"Level": "Szint", "Level": "Szint",
"LaunchBrowserHelpText": " Nyisson meg egy böngészőt, és az alkalmazás indításakor lépjen a Radarr kezdőlapjára.", "LaunchBrowserHelpText": " Nyisson meg egy böngészőt, és az alkalmazás indításakor lépjen a Radarr kezdőlapjára.",
"LastWriteTime": "Utolsó írási idő", "LastWriteTime": "Utolsó írási idő",
"LastDuration": "Utolsó Hossza", "LastDuration": "Utolsó időtartam",
"Languages": "Nyelvek", "Languages": "Nyelvek",
"LanguageHelpText": "Nyelv a Releasekhez", "LanguageHelpText": "Nyelv a Releasekhez",
"Language": "Nyelv", "Language": "Nyelv",
@@ -896,7 +896,7 @@
"HomePage": "Kezdőlap", "HomePage": "Kezdőlap",
"LogOnly": "Csak naplózás", "LogOnly": "Csak naplózás",
"LastUsed": "Utoljára használt", "LastUsed": "Utoljára használt",
"LastExecution": "Utoljára végrehajtott", "LastExecution": "Utol végrehajtás",
"Large": "Óriási", "Large": "Óriási",
"KeepAndUnmonitorMovie": "Megtartás és megfigyelés kikapcsolása a filmnél", "KeepAndUnmonitorMovie": "Megtartás és megfigyelés kikapcsolása a filmnél",
"InvalidFormat": "Érvénytelen Formátum", "InvalidFormat": "Érvénytelen Formátum",
@@ -992,7 +992,7 @@
"NoLinks": "Nincsenek Linkek", "NoLinks": "Nincsenek Linkek",
"NoEventsFound": "Nem található esemény", "NoEventsFound": "Nem található esemény",
"NoAltTitle": "Nincs alternatív cím.", "NoAltTitle": "Nincs alternatív cím.",
"NextExecution": "Következő futtatás", "NextExecution": "Következő végrehajtás",
"Negated": "Megtagadva", "Negated": "Megtagadva",
"Negate": "Megtagadás", "Negate": "Megtagadás",
"MultiLanguage": "Többnyelvű", "MultiLanguage": "Többnyelvű",
@@ -1105,7 +1105,7 @@
"Never": "Soha", "Never": "Soha",
"Rating": "Értékelés", "Rating": "Értékelés",
"SetReleaseGroup": "Kiadási csoport beállítása", "SetReleaseGroup": "Kiadási csoport beállítása",
"Started": "Elindult", "Started": "Elkezdődött",
"Waiting": "Várakozás", "Waiting": "Várakozás",
"OnApplicationUpdateHelpText": "Alkalmazásfrissítésről", "OnApplicationUpdateHelpText": "Alkalmazásfrissítésről",
"SizeLimit": "Méretkorlát", "SizeLimit": "Méretkorlát",
@@ -1137,5 +1137,10 @@
"CollectionOptions": "Gyűjtemény baállítások", "CollectionOptions": "Gyűjtemény baállítások",
"CollectionShowOverviewsHelpText": "Gyűjtemények áttekintésének megjelenítése", "CollectionShowOverviewsHelpText": "Gyűjtemények áttekintésének megjelenítése",
"CollectionShowDetailsHelpText": "A gyűjtemény állapotának és tulajdonságainak megjelenítése", "CollectionShowDetailsHelpText": "A gyűjtemény állapotának és tulajdonságainak megjelenítése",
"CollectionShowPostersHelpText": "Gyűjteményelemek posztereinek megjelenítése" "CollectionShowPostersHelpText": "Gyűjteményelemek posztereinek megjelenítése",
"OnMovieAdded": "Film hozzáadásakor",
"OnMovieAddedHelpText": "Film hozzáadásakor",
"ShowPosters": "Poszterek megjelenítése",
"InstanceNameHelpText": "Példánynév a böngésző lapon és a syslog alkalmazás neve",
"RottenTomatoesRating": "Tomato Értékelés"
} }
@@ -0,0 +1 @@
{}
+78 -1
View File
@@ -177,5 +177,82 @@
"ColonReplacementFormatHelpText": "Endre hvordan Radarr håndterer kolonerstatning", "ColonReplacementFormatHelpText": "Endre hvordan Radarr håndterer kolonerstatning",
"ConnectionLostAutomaticMessage": "Radarr vil forsøke å koble til automatisk, eller du kan klikke oppdater nedenfor.", "ConnectionLostAutomaticMessage": "Radarr vil forsøke å koble til automatisk, eller du kan klikke oppdater nedenfor.",
"ConnectSettingsSummary": "Varslinger, tilkoblinger til mediaservere/avspillere, og tilpassede scripts", "ConnectSettingsSummary": "Varslinger, tilkoblinger til mediaservere/avspillere, og tilpassede scripts",
"CopyUsingHardlinksHelpTextWarning": "I blant kan låste filer forhindre å endre navn på filer som blir delt. Du kan midlertidig deaktivere deling og bruke Raddars navnefunksjon for å jobbe rundt dette." "CopyUsingHardlinksHelpTextWarning": "I blant kan låste filer forhindre å endre navn på filer som blir delt. Du kan midlertidig deaktivere deling og bruke Raddars navnefunksjon for å jobbe rundt dette.",
"Movies": "Film",
"DeleteCustomFormat": "Klon egendefinert format",
"Deleted": "Slett",
"Indexers": "Indeksere",
"Queued": "Kø",
"Title": "Tittel",
"Updates": "Oppdater",
"Lists": "Liste",
"Search": "Søk",
"Titles": "Tittel",
"Password": "Passord",
"Peers": "Likemenn",
"Port": "Port",
"Posters": "Plakater",
"Protocol": "Protokoll",
"Quality": "kvalitet",
"QualityProfile": "Kvaltietsprofil",
"Ratings": "Vurdering",
"Refresh": "Oppdater",
"Reload": "Likemenn",
"Remove": "Slett",
"Replace": "Erstatt",
"Required": "Kreve",
"Restrictions": "Begrensning",
"RootFolder": "Rotmappe",
"RootFolders": "Rotmappe",
"Runtime": "Kjøretid",
"Scheduled": "Planlagt",
"Seeders": "Delere",
"Settings": "Innstillinger",
"SettingsRemotePathMappingLocalPath": "Lokal sti",
"Torrents": "Torrents",
"Trakt": "Trakt",
"URLBase": "URL Base",
"Username": "Brukernavn",
"Delete": "Slett",
"Info": "Info",
"LocalPath": "Lokal sti",
"Usenet": "Usenet",
"QualityProfiles": "Kvaltietsprofil",
"Queue": "Kø",
"Rating": "Vurdering",
"UI": "Grensesnitt",
"UpdateMechanismHelpText": "Bruk Prowlarrs innebygde oppdaterer eller et skript",
"RSS": "RSS",
"DelayProfile": "Utsetningsprofil",
"DelayProfiles": "Utsetningsprofil",
"Enable": "Aktiver",
"Enabled": "Aktiver",
"Events": "Hendelse",
"Files": "Fil",
"Filter": "Filter",
"Filters": "Filtre",
"Formats": "Format",
"Grab": "Hent",
"Host": "Vert",
"Hostname": "Vertsnavn",
"ICalFeed": "iCal Feed",
"iCalLink": "iCal Link",
"IMDb": "IMDb",
"Indexer": "Indekser",
"Metadata": "metadata",
"Movie": "Film",
"Crew": "Besetning",
"Details": "detaljer",
"RecyclingBin": "Papirkurv",
"Custom": "Tilpass",
"CustomFormat": "Egendefinert format",
"CustomFormats": "Egendefinert format",
"Cutoff": "Avskjæring",
"Disabled": "deaktivert",
"DownloadClient": "Nedlastingsklient",
"DownloadClients": "Nedlastingsklient",
"Language": "språk",
"List": "Liste",
"New": "Ny",
"RemotePathMappings": "Ekstern portmapping"
} }
+44 -35
View File
@@ -246,10 +246,10 @@
"ImportMechanismHealthCheckMessage": "Włącz obsługę ukończonego pobierania", "ImportMechanismHealthCheckMessage": "Włącz obsługę ukończonego pobierania",
"IndexersSettingsSummary": "Indeksatory i ograniczenia dotyczące wersji", "IndexersSettingsSummary": "Indeksatory i ograniczenia dotyczące wersji",
"InvalidFormat": "Niepoprawny format", "InvalidFormat": "Niepoprawny format",
"ChmodFolder": "chmod Folder", "ChmodFolder": "chmod Folderu",
"ChmodFolderHelpText": "Ósemkowy, stosowany podczas importu / zmiany nazwy do folderów multimedialnych i plików (bez bitów wykonania)", "ChmodFolderHelpText": "Ósemkowy, stosowany podczas importu / zmiany nazwy do folderów multimedialnych i plików (bez bitów wykonania)",
"ChmodFolderHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Radarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania prawidłowo ustawia uprawnienia.", "ChmodFolderHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Radarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania prawidłowo ustawia uprawnienia.",
"ChmodGroup": "chmod Group", "ChmodGroup": "chmod Grupy",
"ChmodGroupHelpText": "Nazwa grupy lub identyfikator. Użyj gid dla zdalnych systemów plików.", "ChmodGroupHelpText": "Nazwa grupy lub identyfikator. Użyj gid dla zdalnych systemów plików.",
"ChmodGroupHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Radarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania używa tej samej grupy co Radarr.", "ChmodGroupHelpTextWarning": "Działa to tylko wtedy, gdy użytkownik uruchamiający Radarr jest właścicielem pliku. Lepiej jest upewnić się, że klient pobierania używa tej samej grupy co Radarr.",
"ListSyncLevelHelpTextWarning": "Pliki filmowe zostaną trwale usunięte, co może spowodować wyczyszczenie biblioteki, jeśli listy są puste", "ListSyncLevelHelpTextWarning": "Pliki filmowe zostaną trwale usunięte, co może spowodować wyczyszczenie biblioteki, jeśli listy są puste",
@@ -259,7 +259,7 @@
"Medium": "Średni", "Medium": "Średni",
"Minutes": "Minuty", "Minutes": "Minuty",
"ListExclusions": "Wykluczenia z listy", "ListExclusions": "Wykluczenia z listy",
"MissingFromDisk": "Radarr nie mógł znaleźć pliku na dysku, więc został odłączony od filmu w bazie danych.", "MissingFromDisk": "Radarr nie mógł znaleźć pliku na dysku, więc został odłączony od filmu w bazie danych",
"Monday": "poniedziałek", "Monday": "poniedziałek",
"MoveFiles": "Przenieś pliki", "MoveFiles": "Przenieś pliki",
"MovieIsRecommend": "Film jest zalecany na podstawie niedawnego dodania", "MovieIsRecommend": "Film jest zalecany na podstawie niedawnego dodania",
@@ -484,8 +484,8 @@
"ClickToChangeMovie": "Kliknij, aby zmienić film", "ClickToChangeMovie": "Kliknij, aby zmienić film",
"ClickToChangeQuality": "Kliknij, aby zmienić jakość", "ClickToChangeQuality": "Kliknij, aby zmienić jakość",
"CloneCustomFormat": "Klonuj format niestandardowy", "CloneCustomFormat": "Klonuj format niestandardowy",
"CloneFormatTag": "Clone Format Tag", "CloneFormatTag": "Klonuj Tag Formatu",
"CloneIndexer": "Clone Indexer", "CloneIndexer": "Sklonuj Indekser",
"CloneProfile": "Klonuj profil", "CloneProfile": "Klonuj profil",
"Close": "Blisko", "Close": "Blisko",
"ColonReplacement": "Wymiana okrężnicy", "ColonReplacement": "Wymiana okrężnicy",
@@ -497,7 +497,7 @@
"Connection": "Połączenie", "Connection": "Połączenie",
"ConnectionLost": "Utracono połączenie", "ConnectionLost": "Utracono połączenie",
"ConnectionLostAutomaticMessage": "Radarr spróbuje połączyć się automatycznie lub możesz kliknąć przycisk przeładuj poniżej.", "ConnectionLostAutomaticMessage": "Radarr spróbuje połączyć się automatycznie lub możesz kliknąć przycisk przeładuj poniżej.",
"ConnectionLostMessage": "Radarr utracił połączenie z zapleczem i będzie musiał zostać ponownie załadowany, aby przywrócić funkcjonalność.", "ConnectionLostMessage": "Radarr utracił połączenie z silnikiem programu, aby przywrócić funkcjonalność musi zostać zrestartowany.",
"Connections": "Znajomości", "Connections": "Znajomości",
"ConnectSettings": "Ustawienia połączenia", "ConnectSettings": "Ustawienia połączenia",
"ConnectSettingsSummary": "Powiadomienia, połączenia z serwerami / odtwarzaczami multimedialnymi i niestandardowe skrypty", "ConnectSettingsSummary": "Powiadomienia, połączenia z serwerami / odtwarzaczami multimedialnymi i niestandardowe skrypty",
@@ -545,7 +545,7 @@
"DeleteRestrictionHelpText": "Czy na pewno chcesz usunąć to ograniczenie?", "DeleteRestrictionHelpText": "Czy na pewno chcesz usunąć to ograniczenie?",
"DeleteSelectedMovie": "Usuń wybrane filmy", "DeleteSelectedMovie": "Usuń wybrane filmy",
"DeleteTagMessageText": "Czy na pewno chcesz usunąć tag „{0}”?", "DeleteTagMessageText": "Czy na pewno chcesz usunąć tag „{0}”?",
"DeleteTheMovieFolder": "Folder filmów „{0} i cała jego zawartość zostaną usunięte.", "DeleteTheMovieFolder": "Folder '{0}' i cała jego zawartość zostaną usunięte.",
"DestinationPath": "Ścieżka docelowa", "DestinationPath": "Ścieżka docelowa",
"DestinationRelativePath": "Względna ścieżka celu", "DestinationRelativePath": "Względna ścieżka celu",
"DetailedProgressBar": "Szczegółowy pasek postępu", "DetailedProgressBar": "Szczegółowy pasek postępu",
@@ -570,7 +570,7 @@
"DownloadFailed": "Pobieranie nie udane", "DownloadFailed": "Pobieranie nie udane",
"DownloadFailedInterp": "Pobieranie nie powiodło się: {0}", "DownloadFailedInterp": "Pobieranie nie powiodło się: {0}",
"Downloading": "Ściąganie", "Downloading": "Ściąganie",
"DownloadPropersAndRepacks": "Propers and Repacks", "DownloadPropersAndRepacks": "Poprawione i przepakowane",
"DownloadPropersAndRepacksHelpTextWarning": "Użyj niestandardowych formatów do automatycznej aktualizacji do Propers / Repacks", "DownloadPropersAndRepacksHelpTextWarning": "Użyj niestandardowych formatów do automatycznej aktualizacji do Propers / Repacks",
"DownloadWarningCheckDownloadClientForMoreDetails": "Ostrzeżenie dotyczące pobierania: sprawdź klienta pobierania, aby uzyskać więcej informacji", "DownloadWarningCheckDownloadClientForMoreDetails": "Ostrzeżenie dotyczące pobierania: sprawdź klienta pobierania, aby uzyskać więcej informacji",
"Edition": "Wydanie", "Edition": "Wydanie",
@@ -638,8 +638,8 @@
"GoToInterp": "Idź do {0}", "GoToInterp": "Idź do {0}",
"Grab": "Chwycić", "Grab": "Chwycić",
"Grabbed": "Złapał", "Grabbed": "Złapał",
"GrabID": "Grab ID", "GrabID": "Pobierz ID",
"GrabRelease": "Grab Release", "GrabRelease": "Pobierz Wydanie",
"GrabReleaseMessageText": "Radarr nie był w stanie określić, dla którego filmu jest to wydanie. Radarr może nie być w stanie automatycznie zaimportować tej wersji. Czy chcesz złapać „{0}”?", "GrabReleaseMessageText": "Radarr nie był w stanie określić, dla którego filmu jest to wydanie. Radarr może nie być w stanie automatycznie zaimportować tej wersji. Czy chcesz złapać „{0}”?",
"GrabSelected": "Wybierz wybrane", "GrabSelected": "Wybierz wybrane",
"Group": "Grupa", "Group": "Grupa",
@@ -650,7 +650,7 @@
"HideAdvanced": "Ukryj zaawansowane", "HideAdvanced": "Ukryj zaawansowane",
"History": "Historia", "History": "Historia",
"Host": "Gospodarz", "Host": "Gospodarz",
"iCalLink": "iCal Link", "iCalLink": "Łącze do iCal",
"IconForCutoffUnmet": "Ikona Cutoff Unmet", "IconForCutoffUnmet": "Ikona Cutoff Unmet",
"IgnoredAddresses": "Ignorowane adresy", "IgnoredAddresses": "Ignorowane adresy",
"IgnoreDeletedMovies": "Nie monitoruj usuniętych filmów", "IgnoreDeletedMovies": "Nie monitoruj usuniętych filmów",
@@ -669,7 +669,7 @@
"IncludeUnknownMovieItemsHelpText": "Pokaż elementy bez filmu w kolejce. Może to obejmować usunięte filmy lub cokolwiek innego w kategorii Radarr", "IncludeUnknownMovieItemsHelpText": "Pokaż elementy bez filmu w kolejce. Może to obejmować usunięte filmy lub cokolwiek innego w kategorii Radarr",
"ImportMovies": "Importuj filmy", "ImportMovies": "Importuj filmy",
"IndexerPriority": "Priorytet indeksatora", "IndexerPriority": "Priorytet indeksatora",
"IndexerPriorityHelpText": "Priorytet indeksera od 1 (najwyższy) do 50 (najniższy). Domyślnie: 25. Używany podczas pobierania wydań przy wystąpieniu równoważnych wydań. Przy synchronizacji RSS i wyszukiwaniu, Radarr wciąż będzie korzystał ze wszystkich indekserów.", "IndexerPriorityHelpText": "Priorytet indeksera od 1 (najwyższy) do 50 (najniższy). Domyślnie: 25. Używany podczas pobierania wydań przy wystąpieniu równoważnych wydań. Przy synchronizacji RSS i wyszukiwaniu, Radarr wciąż będzie korzystał ze wszystkich indekserów",
"Indexers": "Indeksatory", "Indexers": "Indeksatory",
"IndexerSearchCheckNoAutomaticMessage": "Brak dostępnych indeksatorów z włączoną funkcją automatycznego wyszukiwania, Radarr nie zapewni żadnych automatycznych wyników wyszukiwania", "IndexerSearchCheckNoAutomaticMessage": "Brak dostępnych indeksatorów z włączoną funkcją automatycznego wyszukiwania, Radarr nie zapewni żadnych automatycznych wyników wyszukiwania",
"IndexerSearchCheckNoAvailableIndexersMessage": "Wszystkie indeksatory z możliwością wyszukiwania są tymczasowo niedostępne z powodu ostatnich błędów indeksatora", "IndexerSearchCheckNoAvailableIndexersMessage": "Wszystkie indeksatory z możliwością wyszukiwania są tymczasowo niedostępne z powodu ostatnich błędów indeksatora",
@@ -701,7 +701,7 @@
"Logs": "Dzienniki", "Logs": "Dzienniki",
"LookingForReleaseProfiles1": "Szukasz profili wersji? Próbować", "LookingForReleaseProfiles1": "Szukasz profili wersji? Próbować",
"LookingForReleaseProfiles2": "zamiast.", "LookingForReleaseProfiles2": "zamiast.",
"LowerCase": "Małe litery", "LowerCase": "Z małej litery",
"ManualImportSelectLanguage": "Import ręczny - wybierz język", "ManualImportSelectLanguage": "Import ręczny - wybierz język",
"ManualImportSelectMovie": "Import ręczny - wybierz film", "ManualImportSelectMovie": "Import ręczny - wybierz film",
"MappedDrivesRunningAsService": "Zmapowane dyski sieciowe nie są dostępne, gdy działają jako usługa systemu Windows. Więcej informacji można znaleźć w FAQ", "MappedDrivesRunningAsService": "Zmapowane dyski sieciowe nie są dostępne, gdy działają jako usługa systemu Windows. Więcej informacji można znaleźć w FAQ",
@@ -740,7 +740,7 @@
"MovieInvalidFormat": "Film: nieprawidłowy format", "MovieInvalidFormat": "Film: nieprawidłowy format",
"MultiLanguage": "Wielojęzyczny", "MultiLanguage": "Wielojęzyczny",
"Negate": "Negować", "Negate": "Negować",
"Negated": "Negated", "Negated": "Zanegowane",
"NoListRecommendations": "Nie znaleziono żadnych pozycji na liście ani rekomendacji. Aby rozpocząć, musisz dodać nowy film, zaimportować istniejące lub dodać listę.", "NoListRecommendations": "Nie znaleziono żadnych pozycji na liście ani rekomendacji. Aby rozpocząć, musisz dodać nowy film, zaimportować istniejące lub dodać listę.",
"NoTagsHaveBeenAddedYet": "Żadne tagi nie zostały jeszcze dodane", "NoTagsHaveBeenAddedYet": "Żadne tagi nie zostały jeszcze dodane",
"Options": "Opcje", "Options": "Opcje",
@@ -828,16 +828,16 @@
"RSSSync": "Synchronizacja RSS", "RSSSync": "Synchronizacja RSS",
"RSSSyncInterval": "Częstotliwość synchronizacji RSS", "RSSSyncInterval": "Częstotliwość synchronizacji RSS",
"RSSSyncIntervalHelpTextWarning": "Dotyczy to wszystkich indeksujących, prosimy o przestrzeganie zasad przez nich określonych", "RSSSyncIntervalHelpTextWarning": "Dotyczy to wszystkich indeksujących, prosimy o przestrzeganie zasad przez nich określonych",
"Runtime": "Runtime", "Runtime": "Długość",
"Save": "Zapisać", "Save": "Zapisać",
"SaveChanges": "Zapisz zmiany", "SaveChanges": "Zapisz zmiany",
"SaveSettings": "Zapisz ustawienia", "SaveSettings": "Zapisz ustawienia",
"Scheduled": "Planowy", "Scheduled": "Planowy",
"Score": "Wynik", "Score": "Wynik",
"Script": "Scenariusz", "Script": "Scenariusz",
"ScriptPath": "Script Path", "ScriptPath": "Ścieżka do Skryptu",
"SearchAll": "Wyszukaj wszystko", "SearchAll": "Wyszukaj wszystko",
"SearchCutoffUnmet": "Search Cutoff Unmet", "SearchCutoffUnmet": "Niespełnione Parametry Wyszukiwania",
"SearchFailedPleaseTryAgainLater": "Wyszukiwanie nie powiodło się, spróbuj ponownie później.", "SearchFailedPleaseTryAgainLater": "Wyszukiwanie nie powiodło się, spróbuj ponownie później.",
"SearchFiltered": "Szukaj przefiltrowane", "SearchFiltered": "Szukaj przefiltrowane",
"SearchForMissing": "Wyszukaj brakujące", "SearchForMissing": "Wyszukaj brakujące",
@@ -893,7 +893,7 @@
"Shutdown": "Zamknąć", "Shutdown": "Zamknąć",
"SizeOnDisk": "Rozmiar dysku", "SizeOnDisk": "Rozmiar dysku",
"SkipFreeSpaceCheck": "Pomiń sprawdzanie wolnego miejsca", "SkipFreeSpaceCheck": "Pomiń sprawdzanie wolnego miejsca",
"SkipFreeSpaceCheckWhenImportingHelpText": "Użyj, gdy Radarr nie może wykryć wolnego miejsca w folderze głównym filmu", "SkipFreeSpaceCheckWhenImportingHelpText": "Zaznacz, jeśli Radarr nie będzie wstanie wykryć ilości wolnego miejsca w główny folderze filmów",
"Small": "Mały", "Small": "Mały",
"Socks4": "Skarpetki 4", "Socks4": "Skarpetki 4",
"Socks5": "Socks5 (Wsparcie TOR)", "Socks5": "Socks5 (Wsparcie TOR)",
@@ -932,10 +932,10 @@
"Time": "Czas", "Time": "Czas",
"Title": "Tytuł", "Title": "Tytuł",
"Titles": "Tytuły", "Titles": "Tytuły",
"TMDBId": "TMDb Id", "TMDBId": "Identyfikator TMDb",
"TmdbIdHelpText": "Identyfikator TMDb filmu do wykluczenia", "TmdbIdHelpText": "Identyfikator TMDb filmu do wykluczenia",
"Today": "Dzisiaj", "Today": "Dzisiaj",
"TorrentDelay": "Torrent Delay", "TorrentDelay": "Opóźnienie Torrenta",
"TorrentDelayHelpText": "Opóźnienie w ciągu kilku minut, aby poczekać przed złapaniem torrenta", "TorrentDelayHelpText": "Opóźnienie w ciągu kilku minut, aby poczekać przed złapaniem torrenta",
"Torrents": "Torrenty", "Torrents": "Torrenty",
"TorrentsDisabled": "Torrenty wyłączone", "TorrentsDisabled": "Torrenty wyłączone",
@@ -946,7 +946,7 @@
"Type": "Rodzaj", "Type": "Rodzaj",
"UI": "UI", "UI": "UI",
"UILanguage": "Język interfejsu użytkownika", "UILanguage": "Język interfejsu użytkownika",
"UILanguageHelpText": "Język, którego Radarr będzie używać w interfejsie użytkownika", "UILanguageHelpText": "Język, interfejsu użytkownika używanego przez Radarr",
"UILanguageHelpTextWarning": "Wymagane przeładowanie przeglądarki", "UILanguageHelpTextWarning": "Wymagane przeładowanie przeglądarki",
"UISettings": "Ustawienia interfejsu użytkownika", "UISettings": "Ustawienia interfejsu użytkownika",
"UISettingsSummary": "Opcje z osłabionym kalendarzem, datą i kolorem", "UISettingsSummary": "Opcje z osłabionym kalendarzem, datą i kolorem",
@@ -989,7 +989,7 @@
"UnsavedChanges": "Niezapisane zmiany", "UnsavedChanges": "Niezapisane zmiany",
"UnselectAll": "Odznacz wszystko", "UnselectAll": "Odznacz wszystko",
"UpdateAll": "Aktualizuj wszystko", "UpdateAll": "Aktualizuj wszystko",
"UpperCase": "Duże litery", "UpperCase": "Z dużej litery",
"UpdateCheckUINotWritableMessage": "Nie można zainstalować aktualizacji, ponieważ użytkownik „{1}” nie ma prawa zapisu w folderze interfejsu użytkownika „{0}”.", "UpdateCheckUINotWritableMessage": "Nie można zainstalować aktualizacji, ponieważ użytkownik „{1}” nie ma prawa zapisu w folderze interfejsu użytkownika „{0}”.",
"UpdateMechanismHelpText": "Użyj wbudowanego aktualizatora Radarr lub skryptu", "UpdateMechanismHelpText": "Użyj wbudowanego aktualizatora Radarr lub skryptu",
"UpdateScriptPathHelpText": "Ścieżka do niestandardowego skryptu, który pobiera wyodrębniony pakiet aktualizacji i obsługuje pozostałą część procesu aktualizacji", "UpdateScriptPathHelpText": "Ścieżka do niestandardowego skryptu, który pobiera wyodrębniony pakiet aktualizacji i obsługuje pozostałą część procesu aktualizacji",
@@ -1006,7 +1006,7 @@
"VisitGithubCustomFormatsAphrodite": "Odwiedź wiki, aby uzyskać więcej informacji: ", "VisitGithubCustomFormatsAphrodite": "Odwiedź wiki, aby uzyskać więcej informacji: ",
"WaitingToProcess": "Czekam na przetworzenie", "WaitingToProcess": "Czekam na przetworzenie",
"Wanted": "Chciał", "Wanted": "Chciał",
"Warn": "Ostrzec", "Warn": "Ostrzeż",
"Week": "Tydzień", "Week": "Tydzień",
"WeekColumnHeader": "Nagłówek kolumny tygodnia", "WeekColumnHeader": "Nagłówek kolumny tygodnia",
"Weeks": "Tygodni", "Weeks": "Tygodni",
@@ -1043,26 +1043,26 @@
"Download": "Ściągnij", "Download": "Ściągnij",
"DownloadClientCheckDownloadingToRoot": "Klient pobierania {0} umieszcza pliki do pobrania w folderze głównym {1}. Nie należy pobierać do folderu głównego.", "DownloadClientCheckDownloadingToRoot": "Klient pobierania {0} umieszcza pliki do pobrania w folderze głównym {1}. Nie należy pobierać do folderu głównego.",
"DeleteFileLabel": "Usuń {0} pliki filmowe", "DeleteFileLabel": "Usuń {0} pliki filmowe",
"UnableToAddRootFolder": "Nie można załadować folderów głównych", "UnableToAddRootFolder": "Nie można dodać folderu głównego",
"AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist": "Czy na pewno chcesz usunąć wybrane elementy z czarnej listy?", "AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist": "Czy na pewno chcesz usunąć wybrane elementy z czarnej listy?",
"Blocklist": "Czarna lista", "Blocklist": "Czarna lista",
"BlocklistRelease": "Wydanie czarnej listy", "BlocklistRelease": "Wydanie czarnej listy",
"RemoveFromBlocklist": "Usuń z czarnej listy", "RemoveFromBlocklist": "Usuń z czarnej listy",
"UnableToLoadBlocklist": "Nie można załadować czarnej listy", "UnableToLoadBlocklist": "Nie można załadować listy blokowania",
"Blocklisted": "Czarna lista", "Blocklisted": "Czarna lista",
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Czy na pewno chcesz usunąć {0} element {1} z kolejki?", "AreYouSureYouWantToRemoveSelectedItemFromQueue": "Czy na pewno chcesz usunąć {0} element {1} z kolejki?",
"BlocklistReleases": "Wydanie czarnej listy", "BlocklistReleases": "Wydanie czarnej listy",
"Filters": "Filtr", "Filters": "Filtry",
"LocalPath": "Ścieżka lokalna", "LocalPath": "Ścieżka lokalna",
"List": "Listy", "List": "Lista",
"Rating": "Oceny", "Rating": "Ocena",
"RemotePath": "Zdalna ścieżka", "RemotePath": "Ścieżka zdalna",
"SelectLanguages": "Wybierz język", "SelectLanguages": "Wybierz język",
"AllCollectionsHiddenDueToFilter": "Wszystkie kolekcje są ukryte ze względu na zastosowany filtr.", "AllCollectionsHiddenDueToFilter": "Wszystkie kolekcje są ukryte ze względu na zastosowany filtr.",
"Collections": "Kolekcja", "Collections": "Kolekcje",
"RssSyncHelpText": "Odstęp w minutach. Ustaw na zero, aby wyłączyć (zatrzyma to wszystkie automatyczne przechwytywanie zwolnień)", "RssSyncHelpText": "Odstęp w minutach. Ustaw na zero, aby wyłączyć (zatrzyma to wszystkie automatyczne przechwytywanie zwolnień)",
"NoCollections": "Nie znaleziono żadnych filmów. Aby rozpocząć, musisz dodać nowy film lub zaimportować istniejące.", "NoCollections": "Nie znaleziono żadnych filmów. Aby rozpocząć, musisz dodać nowy film lub zaimportować istniejące",
"MonitorMovies": "Monitoruj film", "MonitorMovies": "Monitoruj filmy",
"ClickToChangeReleaseGroup": "Kliknij, by zmienić grupę wydającą", "ClickToChangeReleaseGroup": "Kliknij, by zmienić grupę wydającą",
"RemotePathMappingCheckDownloadPermissions": "Radarr widzi film {0}, lecz nie ma do niego dostępu. Najprawdopodobniej to wynik błędu w uprawnieniach dostępu.", "RemotePathMappingCheckDownloadPermissions": "Radarr widzi film {0}, lecz nie ma do niego dostępu. Najprawdopodobniej to wynik błędu w uprawnieniach dostępu.",
"NotificationTriggersHelpText": "Wybierz zdarzenia, które mają uruchamiać to powiadomienie", "NotificationTriggersHelpText": "Wybierz zdarzenia, które mają uruchamiać to powiadomienie",
@@ -1078,7 +1078,7 @@
"ManualImportSetReleaseGroup": "Import ręczny - podaj nazwę grupy", "ManualImportSetReleaseGroup": "Import ręczny - podaj nazwę grupy",
"Never": "Nigdy", "Never": "Nigdy",
"RemotePathMappingCheckFilesWrongOSPath": "Zdalny klient pobierania {0} zgłosił pliki w {1}, lecz nie jest to poprawna ścieżka {2}. Zmień ustawienia zdalnego mapowania ścieżek i klienta pobierania.", "RemotePathMappingCheckFilesWrongOSPath": "Zdalny klient pobierania {0} zgłosił pliki w {1}, lecz nie jest to poprawna ścieżka {2}. Zmień ustawienia zdalnego mapowania ścieżek i klienta pobierania.",
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, lecz Radarr nie widzi tego folderu. Być może należy zmienić ustawienia uprawnień dostępu.", "RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, ale Radarr nie widzi tego folderu. Być może należy zmienić uprawnienia dostępu do folderu/ścieżkę dostępu.",
"RemoveFailed": "Usuń nieudane", "RemoveFailed": "Usuń nieudane",
"RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.", "RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.",
"ShowCollectionDetails": "Pokaż stan kolekcji", "ShowCollectionDetails": "Pokaż stan kolekcji",
@@ -1092,7 +1092,7 @@
"EditCollection": "Edytuj kolekcję", "EditCollection": "Edytuj kolekcję",
"From": "z", "From": "z",
"IndexerTagHelpText": "Korzystaj z tego indeksera wyłącznie w przypadku filmów z co najmniej jednym pasującym tagiem. Pozostaw pole puste, by używać do wszystkich filmów.", "IndexerTagHelpText": "Korzystaj z tego indeksera wyłącznie w przypadku filmów z co najmniej jednym pasującym tagiem. Pozostaw pole puste, by używać do wszystkich filmów.",
"Letterboxd": "Letterboxd", "Letterboxd": "Z Letterboxd",
"MonitorCollection": "Monitoruj kolekcję", "MonitorCollection": "Monitoruj kolekcję",
"MovieAndCollection": "Film i kolekcja", "MovieAndCollection": "Film i kolekcja",
"MovieCollectionMissingRoot": "Brak katalogu głównego dla kolekcji filmów: {0}", "MovieCollectionMissingRoot": "Brak katalogu głównego dla kolekcji filmów: {0}",
@@ -1134,5 +1134,14 @@
"TaskUserAgentTooltip": "User-Agent podawany przez aplikację wywołującą API", "TaskUserAgentTooltip": "User-Agent podawany przez aplikację wywołującą API",
"SetReleaseGroup": "Ustaw grupę wydającą", "SetReleaseGroup": "Ustaw grupę wydającą",
"SearchOnAddCollectionHelpText": "Po dodaniu do biblioteki wyszukaj filmy z tej kolekcji", "SearchOnAddCollectionHelpText": "Po dodaniu do biblioteki wyszukaj filmy z tej kolekcji",
"Database": "Baza danych" "Database": "Baza danych",
"OnMovieAddedHelpText": "Przy dodaniu filmu",
"ShowPosters": "Pokaż plakaty",
"OnMovieAdded": "Przy dodaniu filmu",
"CollectionShowPostersHelpText": "Pokaż plakaty elementów kolekcji",
"CollectionOptions": "Opcje Kolekcji",
"CollectionShowDetailsHelpText": "Pokaż status i właściwości kolekcji",
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji",
"TotalMovies": "Filmów całkowicie",
"RottenTomatoesRating": "Ocena Tomato"
} }
+8 -1
View File
@@ -1116,5 +1116,12 @@
"Collections": "Coleção", "Collections": "Coleção",
"RssSyncHelpText": "Intervalo em minutos. Defina como zero para desativar (isso parará toda a captura automática)", "RssSyncHelpText": "Intervalo em minutos. Defina como zero para desativar (isso parará toda a captura automática)",
"MonitorMovies": "Monitorar filme", "MonitorMovies": "Monitorar filme",
"NoCollections": "Nenhum filme encontrado. Para começar, adiciona um novo filme ou importa alguns já existentes." "NoCollections": "Nenhum filme encontrado. Para começar, adiciona um novo filme ou importa alguns já existentes.",
"MovieAndCollection": "Filme e Coleção",
"CollectionsSelectedInterp": "{0} Coleções Selecionadas",
"EditCollection": "Editar Coleção",
"ChooseImportMode": "Selecionar Modo de Importação",
"InstanceName": "Nome da Instancia",
"CollectionOptions": "Opções de Coleção",
"CollectionShowDetailsHelpText": "Mostrar estado da coleção e proprieades"
} }
+10 -8
View File
@@ -1,5 +1,5 @@
{ {
"LastExecution": "Execução mais recente", "LastExecution": "Última Execução",
"Large": "Grande", "Large": "Grande",
"Languages": "Idiomas", "Languages": "Idiomas",
"LanguageHelpText": "Idioma das versões", "LanguageHelpText": "Idioma das versões",
@@ -19,7 +19,7 @@
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas", "IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
"IndexersSettingsSummary": "Indexadores e restrições de lançamento", "IndexersSettingsSummary": "Indexadores e restrições de lançamento",
"IndexerSettings": "Configurações do indexador", "IndexerSettings": "Configurações do indexador",
"IndexerSearchCheckNoInteractiveMessage": "Sem indexadores disponíveis com a Pesquisa Interativa habilitada, o Radarr não fornecerá resultados interativos de pesquisa", "IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativo",
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador", "IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática", "IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
"Indexers": "Indexadores", "Indexers": "Indexadores",
@@ -571,7 +571,7 @@
"NoChange": "Sem alteração", "NoChange": "Sem alteração",
"NoBackupsAreAvailable": "Não há backups disponíveis", "NoBackupsAreAvailable": "Não há backups disponíveis",
"NoAltTitle": "Nenhum título alternativo.", "NoAltTitle": "Nenhum título alternativo.",
"NextExecution": "Próxima execução", "NextExecution": "Próxima Execução",
"New": "Novo", "New": "Novo",
"NetCore": ".NET", "NetCore": ".NET",
"ShowAsAllDayEvents": "Mostrar como eventos de dia inteiro", "ShowAsAllDayEvents": "Mostrar como eventos de dia inteiro",
@@ -668,7 +668,7 @@
"SomeResultsHiddenFilter": "Alguns resultados estão ocultos pelo filtro aplicado", "SomeResultsHiddenFilter": "Alguns resultados estão ocultos pelo filtro aplicado",
"Socks5": "Socks5 (suporte ao TOR)", "Socks5": "Socks5 (suporte ao TOR)",
"Small": "Pequeno", "Small": "Pequeno",
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar quando o Radarr não conseguir detectar espaço livre da pasta raiz do filme", "SkipFreeSpaceCheckWhenImportingHelpText": "Use quando o Radarr não conseguir detectar espaço livre na pasta raiz do filme",
"SkipFreeSpaceCheck": "Ignorar verificação de espaço livre", "SkipFreeSpaceCheck": "Ignorar verificação de espaço livre",
"SizeOnDisk": "Tamanho em disco", "SizeOnDisk": "Tamanho em disco",
"Size": "Tamanho", "Size": "Tamanho",
@@ -961,7 +961,7 @@
"UpdateSelected": "Atualizar selecionado(s)", "UpdateSelected": "Atualizar selecionado(s)",
"UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização", "UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização",
"Updates": "Atualizações", "Updates": "Atualizações",
"UpdateMechanismHelpText": "Usar o atualizador embutido do Radarr ou um script", "UpdateMechanismHelpText": "Use o atualizador integrado do Radarr ou um script",
"UpdateCheckUINotWritableMessage": "Não é possível instalar a atualização porque a pasta de interface do usuário '{0}' não é gravável pelo usuário '{1}'.", "UpdateCheckUINotWritableMessage": "Não é possível instalar a atualização porque a pasta de interface do usuário '{0}' não é gravável pelo usuário '{1}'.",
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.", "UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.", "UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
@@ -1053,7 +1053,7 @@
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.", "RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.", "RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.", "RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
"RemotePathMappingCheckGenericPermissions": "Cliente para download {0} coloca downloads em {1}, mas o Radarr não pode ver este diretório. Você pode precisar ajustar as permissões da pasta.", "RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca downloads em {1} mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
"RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.", "RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.",
"RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.", "RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
"RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.", "RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.",
@@ -1119,7 +1119,7 @@
"AllCollectionsHiddenDueToFilter": "Todos os filmes estão ocultos devido ao filtro aplicado.", "AllCollectionsHiddenDueToFilter": "Todos os filmes estão ocultos devido ao filtro aplicado.",
"Collections": "Coleção", "Collections": "Coleção",
"MonitorMovies": "Monitorar filme", "MonitorMovies": "Monitorar filme",
"NoCollections": "Nenhum filme encontrado. Para começar, adicione um novo filme ou importe alguns existentes.", "NoCollections": "Nenhum filme encontrado. Para começar, adicione um novo filme ou importe alguns existentes",
"MovieOnly": "Somente Filme", "MovieOnly": "Somente Filme",
"UnableToLoadCollections": "Não foi possível carregar as coleções", "UnableToLoadCollections": "Não foi possível carregar as coleções",
"ChooseImportMode": "Escolher o Modo de Importação", "ChooseImportMode": "Escolher o Modo de Importação",
@@ -1141,5 +1141,7 @@
"CollectionOptions": "Opções de Coleção", "CollectionOptions": "Opções de Coleção",
"CollectionShowDetailsHelpText": "Mostrar estado e propriedades da coleção", "CollectionShowDetailsHelpText": "Mostrar estado e propriedades da coleção",
"CollectionShowOverviewsHelpText": "Mostrar visão geral da coleção", "CollectionShowOverviewsHelpText": "Mostrar visão geral da coleção",
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção" "CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção",
"RottenTomatoesRating": "Avaliação Tomato",
"TotalMovies": "Total de Filmes"
} }
+2 -1
View File
@@ -110,5 +110,6 @@
"DeleteNotificationMessageText": "Naozaj chcete zmazať značku formátu {0} ?", "DeleteNotificationMessageText": "Naozaj chcete zmazať značku formátu {0} ?",
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Naozaj chcete odobrať {0} položku {1} z fronty?", "AreYouSureYouWantToRemoveSelectedItemFromQueue": "Naozaj chcete odobrať {0} položku {1} z fronty?",
"ImportCustomFormat": "Pridať vlastný formát", "ImportCustomFormat": "Pridať vlastný formát",
"DeleteRestrictionHelpText": "Naozaj chcete zmazať tento profil oneskorenia?" "DeleteRestrictionHelpText": "Naozaj chcete zmazať tento profil oneskorenia?",
"AllCollectionsHiddenDueToFilter": "Všetky filmy sú skryté kvôli použitému filtru."
} }
+10 -3
View File
@@ -637,7 +637,7 @@
"InstallLatest": "安装最新版", "InstallLatest": "安装最新版",
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}", "IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用", "IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,Radarr 不会提供任何手动搜索结果", "IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,Radarr 不会提供任何手动搜索结果",
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片", "IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片",
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时,下列索引器都已不可用:{0}", "IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时,下列索引器都已不可用:{0}",
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有索引器均不可用", "IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有索引器均不可用",
@@ -740,7 +740,7 @@
"UnableToAddANewNotificationPleaseTryAgain": "无法添加新通知,请稍后重试。", "UnableToAddANewNotificationPleaseTryAgain": "无法添加新通知,请稍后重试。",
"URLBase": "基本URL", "URLBase": "基本URL",
"RemovedMovieCheckMultipleMessage": "电影“{0}”已从TMDb移除", "RemovedMovieCheckMultipleMessage": "电影“{0}”已从TMDb移除",
"SkipFreeSpaceCheckWhenImportingHelpText": "当 Radarr 无法从您的电影根文件夹中检测到空闲空间时使用", "SkipFreeSpaceCheckWhenImportingHelpText": "当 Radarr 无法从movie根目录检测到空间时使用",
"UnableToAddANewQualityProfilePleaseTryAgain": "无法添加新影片质量配置,请稍后重试。", "UnableToAddANewQualityProfilePleaseTryAgain": "无法添加新影片质量配置,请稍后重试。",
"ThisCannotBeCancelled": "在不禁用所有索引器的情况下,一旦启动就无法取消。", "ThisCannotBeCancelled": "在不禁用所有索引器的情况下,一旦启动就无法取消。",
"SearchCutoffUnmet": "搜索未满足终止条件的", "SearchCutoffUnmet": "搜索未满足终止条件的",
@@ -1135,5 +1135,12 @@
"NoCollections": "没有发现集合,点击添加新的电影或者导入已经存在的电影", "NoCollections": "没有发现集合,点击添加新的电影或者导入已经存在的电影",
"InstanceNameHelpText": "选项卡及日志应用名称", "InstanceNameHelpText": "选项卡及日志应用名称",
"ScrollMovies": "滚动电影", "ScrollMovies": "滚动电影",
"CollectionOptions": "Collection Options" "CollectionOptions": "集选项Collection Options",
"CollectionShowDetailsHelpText": "显示集合collection的状态和属性",
"CollectionShowOverviewsHelpText": "显示集合collection的概述",
"CollectionShowPostersHelpText": "显示集合项目海报",
"RottenTomatoesRating": "烂番茄指数",
"ShowPosters": "显示海报",
"OnMovieAdded": "电影添加时",
"OnMovieAddedHelpText": "电影添加时"
} }
@@ -1,4 +1,7 @@
{ {
"About": "關於", "About": "關於",
"Add": "添加" "Add": "添加",
"AcceptConfirmationModal": "接受確認模式",
"Actions": "‎行動‎",
"Activity": "‎活動‎"
} }
@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@@ -19,13 +18,13 @@ namespace NzbDrone.Core.MediaCover
public class MediaCoverProxy : IMediaCoverProxy public class MediaCoverProxy : IMediaCoverProxy
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
private readonly ICached<string> _cache; private readonly ICached<string> _cache;
public MediaCoverProxy(IHttpClient httpClient, IOptionsMonitor<ConfigFileOptions> configFileOptions, ICacheManager cacheManager) public MediaCoverProxy(IHttpClient httpClient, IConfigFileProvider configFileProvider, ICacheManager cacheManager)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_cache = cacheManager.GetCache<string>(GetType()); _cache = cacheManager.GetCache<string>(GetType());
} }
@@ -38,7 +37,7 @@ namespace NzbDrone.Core.MediaCover
_cache.ClearExpired(); _cache.ClearExpired();
var fileName = Path.GetFileName(url); var fileName = Path.GetFileName(url);
return _configFileOptions.CurrentValue.UrlBase + @"/MediaCoverProxy/" + hash + "/" + fileName; return _configFileProvider.UrlBase + @"/MediaCoverProxy/" + hash + "/" + fileName;
} }
public string GetUrl(string hash) public string GetUrl(string hash)
@@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using Microsoft.Extensions.Options;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@@ -36,7 +35,7 @@ namespace NzbDrone.Core.MediaCover
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly ICoverExistsSpecification _coverExistsSpecification; private readonly ICoverExistsSpecification _coverExistsSpecification;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions; private readonly IConfigFileProvider _configFileProvider;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
@@ -52,7 +51,7 @@ namespace NzbDrone.Core.MediaCover
IDiskProvider diskProvider, IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo, IAppFolderInfo appFolderInfo,
ICoverExistsSpecification coverExistsSpecification, ICoverExistsSpecification coverExistsSpecification,
IOptionsMonitor<ConfigFileOptions> configFileOptions, IConfigFileProvider configFileProvider,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.MediaCover
_httpClient = httpClient; _httpClient = httpClient;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_coverExistsSpecification = coverExistsSpecification; _coverExistsSpecification = coverExistsSpecification;
_configFileOptions = configFileOptions; _configFileProvider = configFileProvider;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
@@ -105,7 +104,7 @@ namespace NzbDrone.Core.MediaCover
var filePath = GetCoverPath(movieId, mediaCover.CoverType); var filePath = GetCoverPath(movieId, mediaCover.CoverType);
mediaCover.RemoteUrl = mediaCover.Url; mediaCover.RemoteUrl = mediaCover.Url;
mediaCover.Url = _configFileOptions.CurrentValue.UrlBase + @"/MediaCover/" + movieId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + movieId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
FileInfo file; FileInfo file;
var fileExists = false; var fileExists = false;
@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.Events namespace NzbDrone.Core.MediaFiles.Events
{ {
public class MovieImportedEvent : IEvent public class MovieFileImportedEvent : IEvent
{ {
public LocalMovie MovieInfo { get; private set; } public LocalMovie MovieInfo { get; private set; }
public MovieFile ImportedMovie { get; private set; } public MovieFile ImportedMovie { get; private set; }
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.MediaFiles.Events
public DownloadClientItemClientInfo DownloadClientInfo { get; set; } public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
public string DownloadId { get; private set; } public string DownloadId { get; private set; }
public MovieImportedEvent(LocalMovie movieInfo, MovieFile importedMovie, List<MovieFile> oldFiles, bool newDownload, DownloadClientItem downloadClientItem) public MovieFileImportedEvent(LocalMovie movieInfo, MovieFile importedMovie, List<MovieFile> oldFiles, bool newDownload, DownloadClientItem downloadClientItem)
{ {
MovieInfo = movieInfo; MovieInfo = movieInfo;
ImportedMovie = importedMovie; ImportedMovie = importedMovie;

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