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

Compare commits

...

44 Commits

Author SHA1 Message Date
ta264
7641190475 Test postgres in non-UTC docker 2022-07-17 11:38:54 +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
99 changed files with 1251 additions and 399 deletions

View File

@@ -173,7 +173,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --frontend
@@ -577,6 +576,7 @@ stages:
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
@@ -722,6 +722,7 @@ stages:
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
@@ -976,7 +977,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --lint

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -561,7 +561,7 @@ export const actionHandlers = handleThunks({
}, []);
const promise = createAjaxRequest({
url: '/movie/import',
url: '/importlist/movie',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(allNewMovies)

View File

@@ -162,6 +162,14 @@ export const filterPredicates = {
return predicate(rating, filterValue);
},
rottenTomatoesRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.rottenTomatoes ? item.ratings.rottenTomatoes.value : 0;
return predicate(rating, filterValue);
},
imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];

View File

@@ -1,10 +1,12 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewMovie from 'Utilities/Movie/getNewMovie';
import translate from 'Utilities/String/translate';
import { set, update, updateItem } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
@@ -63,19 +65,81 @@ export const defaultState = {
}
],
filterPredicates: {},
filterPredicates: {
genres: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
let allGenres = [];
item.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
});
const genres = Array.from(new Set(allGenres)).slice(0, 3);
return predicate(genres, filterValue);
},
totalMovies: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { movies } = item;
const totalMovies = movies.length;
return predicate(totalMovies, filterValue);
}
},
filterBuilderProps: [
{
name: 'title',
label: 'Title',
label: translate('Title'),
type: filterBuilderTypes.STRING
},
{
name: 'monitored',
label: 'Monitored',
label: translate('Monitored'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.BOOL
},
{
name: 'qualityProfileId',
label: translate('QualityProfile'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.QUALITY_PROFILE
},
{
name: 'rootFolderPath',
label: translate('RootFolder'),
type: filterBuilderTypes.STRING
},
{
name: 'genres',
label: translate('Genres'),
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const genreList = items.reduce((acc, collection) => {
let collectionGenres = [];
collection.movies.forEach((movie) => {
collectionGenres = collectionGenres.concat(movie.genres);
});
const genres = Array.from(new Set(collectionGenres)).slice(0, 3);
genres.forEach((genre) => {
acc.push({
id: genre,
name: genre
});
});
return acc;
}, []);
return genreList.sort(sortByName);
}
},
{
name: 'totalMovies',
label: translate('TotalMovies'),
type: filterBuilderTypes.NUMBER
}
]
};
@@ -254,27 +318,32 @@ export const actionHandlers = handleThunks({
const {
collectionIds,
monitored,
monitor
monitor,
qualityProfileId,
rootFolderPath,
minimumAvailability
} = payload;
const response = {};
const collections = [];
collectionIds.forEach((id) => {
const collectionToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
collectionToUpdate.monitored = monitored;
}
collections.push(collectionToUpdate);
});
if (payload.hasOwnProperty('monitored')) {
response.monitored = monitored;
}
if (payload.hasOwnProperty('monitor')) {
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({
section,

View File

@@ -178,8 +178,20 @@ export const defaultState = {
isVisible: true
},
{
name: 'ratings',
label: translate('Ratings'),
name: 'tmdbRating',
label: translate('TmdbRating'),
isSortable: true,
isVisible: false
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
isSortable: true,
isVisible: false
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
isSortable: true,
isVisible: false
},
@@ -224,10 +236,22 @@ export const defaultState = {
return originalLanguage.name;
},
ratings: function(item) {
imdbRating: function(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'),
type: filterBuilderTypes.NUMBER
},
{
name: 'rottenTomatoesRating',
label: translate('RottenTomatoesRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),

View File

@@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Fluent;
@@ -8,47 +10,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
{
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);
}
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);
}
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);
}
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);
}
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);
}
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
{
SentryLogger.Log(level)
.CopyLogEvent(logBuilder.LogEventInfo)
SentryLogger.ForLogEvent(level)
.CopyLogEvent(logBuilder.LogEvent)
.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)
.TimeStamp(logEvent.TimeStamp)
return logBuilder.TimeStamp(logEvent.TimeStamp)
.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);
}
}

View File

@@ -1,13 +1,16 @@
using NLog;
using System;
using System.Text;
using NLog;
using NLog.Targets;
namespace NzbDrone.Common.Instrumentation
{
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);
}
}
}

View File

@@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached)
{
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}}";
var loggingRule = new LoggingRule("*", LogLevel.Trace, target);
LogManager.Configuration.AddTarget("debugger", target);
LogManager.Configuration.LoggingRules.Add(loggingRule);
}
private static void RegisterGlobalFilters()
{
LogManager.Setup().LoadConfiguration(c =>
{
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
});
}
private static void RegisterConsole()
{
var level = LogLevel.Trace;

View File

@@ -8,10 +8,10 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.15.0" />
<PackageReference Include="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="System.Text.Json" Version="6.0.4" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />

View File

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

View File

@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_ok_on_movie_imported_event()
{
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();
}

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.HistoryTests
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>()
.Verify(v => v.Insert(It.Is<MovieHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localMovie.Path))));

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Credits;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@@ -52,6 +53,10 @@ namespace NzbDrone.Core.Test.MovieTests
Mocker.GetMock<IProvideMovieInfo>()
.Setup(s => s.GetMovieInfo(It.IsAny<int>()))
.Callback<int>((i) => { throw new MovieNotFoundException(i); });
Mocker.GetMock<IRootFolderService>()
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
.Returns(string.Empty);
}
private void GivenNewMovieInfo(MovieMetadata movie)

View File

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

View File

@@ -41,6 +41,14 @@ namespace NzbDrone.Core.Test.ParserTests
ParseAndVerifyQuality(title, Source.TELESYNC, proper, Resolution.R720p);
}
[TestCase("Movie Name 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)]
[TestCase("Movie Name (2018) 720p Hindi HQ CAMrip x264 AAC 1.4GB", false)]
public void should_parse_cam(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
}
[TestCase("S07E23 .avi ", false)]
[TestCase("Movie Name S02E01 HDTV XviD 2HD", false)]
[TestCase("Movie Name S05E11 PROPER HDTV XviD 2HD", true)]
@@ -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 WEB 1080p x264-JiHEFF (S:317/L:28)", false)]
[TestCase("Movie.Titles.2020.1080p.NF.WEB.DD2.0.x264-SNEAkY", false)]
[TestCase("The.Movie.2022.NORDiC.1080p.DV.HDR.WEB.H 265-NiDHUG", false)]
public void should_parse_webdl1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R1080p);
@@ -207,6 +216,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Name.2016.03.14.2160p.WEB.PROPER.h264-spamTV", true)]
[TestCase("[HorribleSubs] Movie Title! 2018 [Web][MKV][h264][2160p][AAC 2.0][Softsubs (HorribleSubs)]", false)]
[TestCase("Movie Name 2020 WEB-DL 4K H265 10bit HDR DDP5.1 Atmos-PTerWEB", false)]
[TestCase("The.Movie.2022.NORDiC.2160p.DV.HDR.WEB.H.265-NiDHUG", false)]
public void should_parse_webdl2160p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R2160p);

View File

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

View File

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

View File

@@ -1,11 +1,31 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class IndexerFlagSpecificationValidator : AbstractValidator<IndexerFlagSpecification>
{
public IndexerFlagSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((qualityValue, context) =>
{
if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue))
{
context.AddFailure(string.Format("Invalid indexer flag condition value: {0}", qualityValue));
}
});
}
}
public class IndexerFlagSpecification : CustomFormatSpecificationBase
{
private static readonly IndexerFlagSpecificationValidator Validator = new IndexerFlagSpecificationValidator();
public override int Order => 4;
public override string ImplementationName => "Indexer Flag";
@@ -17,5 +37,10 @@ namespace NzbDrone.Core.CustomFormats
var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?;
return flags?.HasFlag((IndexerFlags)Value) == true;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,31 @@
using System.Linq;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class LanguageSpecificationValidator : AbstractValidator<LanguageSpecification>
{
public LanguageSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((value, context) =>
{
if (!Language.All.Any(o => o.Id == value))
{
context.AddFailure(string.Format("Invalid Language condition value: {0}", value));
}
});
}
}
public class LanguageSpecification : CustomFormatSpecificationBase
{
private static readonly LanguageSpecificationValidator Validator = new LanguageSpecificationValidator();
public override int Order => 3;
public override string ImplementationName => "Language";
@@ -19,5 +39,10 @@ namespace NzbDrone.Core.CustomFormats
: (Language)Value;
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,31 @@
using System;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class QualityModifierSpecificationValidator : AbstractValidator<QualityModifierSpecification>
{
public QualityModifierSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
RuleFor(c => c.Value).Custom((qualityValue, context) =>
{
if (!Enum.IsDefined(typeof(Modifier), qualityValue))
{
context.AddFailure(string.Format("Invalid quality modifier condition value: {0}", qualityValue));
}
});
}
}
public class QualityModifierSpecification : CustomFormatSpecificationBase
{
private static readonly QualityModifierSpecificationValidator Validator = new QualityModifierSpecificationValidator();
public override int Order => 7;
public override string ImplementationName => "Quality Modifier";
@@ -16,5 +36,10 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,10 +1,23 @@
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class RegexSpecificationBaseValidator : AbstractValidator<RegexSpecificationBase>
{
public RegexSpecificationBaseValidator()
{
RuleFor(c => c.Value).NotEmpty().WithMessage("Regex Pattern must not be empty");
}
}
public abstract class RegexSpecificationBase : CustomFormatSpecificationBase
{
private static readonly RegexSpecificationBaseValidator Validator = new RegexSpecificationBaseValidator();
protected Regex _regex;
protected string _raw;
@@ -15,7 +28,11 @@ namespace NzbDrone.Core.CustomFormats
set
{
_raw = value;
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (value.IsNotNullOrWhiteSpace())
{
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
}
}
@@ -28,5 +45,10 @@ namespace NzbDrone.Core.CustomFormats
return _regex.IsMatch(compared);
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,23 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class ResolutionSpecificationValidator : AbstractValidator<ResolutionSpecification>
{
public ResolutionSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class ResolutionSpecification : CustomFormatSpecificationBase
{
private static readonly ResolutionSpecificationValidator Validator = new ResolutionSpecificationValidator();
public override int Order => 6;
public override string ImplementationName => "Resolution";
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,24 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class SizeSpecificationValidator : AbstractValidator<SizeSpecification>
{
public SizeSpecificationValidator()
{
RuleFor(c => c.Min).GreaterThan(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
}
}
public class SizeSpecification : CustomFormatSpecificationBase
{
private static readonly SizeSpecificationValidator Validator = new SizeSpecificationValidator();
public override int Order => 8;
public override string ImplementationName => "Size";
@@ -21,5 +34,10 @@ namespace NzbDrone.Core.CustomFormats
return size > Min.Gigabytes() && size <= Max.Gigabytes();
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,11 +1,23 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class SourceSpecificationValidator : AbstractValidator<SourceSpecification>
{
public SourceSpecificationValidator()
{
RuleFor(c => c.Value).NotEmpty();
}
}
public class SourceSpecification : CustomFormatSpecificationBase
{
private static readonly SourceSpecificationValidator Validator = new SourceSpecificationValidator();
public override int Order => 5;
public override string ImplementationName => "Source";
@@ -16,5 +28,10 @@ namespace NzbDrone.Core.CustomFormats
{
return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

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

View File

@@ -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");
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -77,12 +77,15 @@ namespace NzbDrone.Core.HealthCheck
.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())
.ToList();
results.AddRange(_serverSideNotificationService.GetServerChecks());
if (performServerChecks)
{
results.AddRange(_serverSideNotificationService.GetServerChecks());
}
foreach (var result in results)
{
@@ -108,17 +111,17 @@ namespace NzbDrone.Core.HealthCheck
{
if (message.Trigger == CommandTrigger.Manual)
{
PerformHealthCheck(_healthChecks);
PerformHealthCheck(_healthChecks, true);
}
else
{
PerformHealthCheck(_scheduledHealthChecks);
PerformHealthCheck(_scheduledHealthChecks, true);
}
}
public void HandleAsync(ApplicationStartedEvent message)
{
PerformHealthCheck(_startupHealthChecks);
PerformHealthCheck(_startupHealthChecks, true);
}
public void HandleAsync(IEvent message)

View File

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

View File

@@ -27,13 +27,13 @@ namespace NzbDrone.Core.History
List<MovieHistory> FindByDownloadId(string downloadId);
List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType);
void UpdateMany(List<MovieHistory> toUpdate);
string FindDownloadId(MovieImportedEvent trackedDownload);
string FindDownloadId(MovieFileImportedEvent trackedDownload);
List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType);
}
public class HistoryService : IHistoryService,
IHandle<MovieGrabbedEvent>,
IHandle<MovieImportedEvent>,
IHandle<MovieFileImportedEvent>,
IHandle<DownloadFailedEvent>,
IHandle<MovieFileDeletedEvent>,
IHandle<MovieFileRenamedEvent>,
@@ -97,7 +97,7 @@ namespace NzbDrone.Core.History
_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);
@@ -170,7 +170,7 @@ namespace NzbDrone.Core.History
_historyRepository.Insert(history);
}
public void Handle(MovieImportedEvent message)
public void Handle(MovieFileImportedEvent message)
{
if (!message.NewDownload)
{

View File

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

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.ImportLists
private readonly IImportListStatusService _importListStatusService;
private readonly IImportListMovieService _listMovieService;
private readonly ISearchForNewMovie _movieSearch;
private readonly IProvideMovieInfo _movieInfoService;
private readonly IMovieMetadataService _movieMetadataService;
private readonly Logger _logger;
@@ -30,6 +31,7 @@ namespace NzbDrone.Core.ImportLists
IImportListStatusService importListStatusService,
IImportListMovieService listMovieService,
ISearchForNewMovie movieSearch,
IProvideMovieInfo movieInfoService,
IMovieMetadataService movieMetadataService,
Logger logger)
{
@@ -37,6 +39,7 @@ namespace NzbDrone.Core.ImportLists
_importListStatusService = importListStatusService;
_listMovieService = listMovieService;
_movieSearch = movieSearch;
_movieInfoService = movieInfoService;
_movieMetadataService = movieMetadataService;
_logger = logger;
}
@@ -84,20 +87,10 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure)
{
// TODO some opportunity to bulk map here if we had the tmdbIds
var listMovies = importListReports.Movies.Select(x =>
{
// Is it existing in result
var movie = result.Movies.FirstOrDefault(r => r.TmdbId == x.TmdbId);
if (movie != null)
{
movie.ListId = importList.Definition.Id;
}
return movie ?? MapMovieReport(x);
}).Where(x => x.TmdbId > 0).ToList();
var alreadyMapped = result.Movies.Where(x => importListReports.Movies.Any(r => r.TmdbId == x.TmdbId));
var listMovies = MapMovieReports(importListReports.Movies.Where(x => !result.Movies.Any(r => r.TmdbId == x.TmdbId)).ToList()).Where(x => x.TmdbId > 0).ToList();
listMovies.AddRange(alreadyMapped);
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
@@ -148,13 +141,10 @@ namespace NzbDrone.Core.ImportLists
if (!importListReports.AnyFailure)
{
// TODO some opportunity to bulk map here if we had the tmdbIds
var listMovies = importListReports.Movies.Select(x =>
{
return MapMovieReport(x);
}).Where(x => x.TmdbId > 0).ToList();
var listMovies = MapMovieReports(importListReports.Movies).Where(x => x.TmdbId > 0).ToList();
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
result.Movies.AddRange(listMovies);
_listMovieService.SyncMoviesForList(listMovies, importList.Definition.Id);
@@ -173,21 +163,30 @@ namespace NzbDrone.Core.ImportLists
return result;
}
private ImportListMovie MapMovieReport(ImportListMovie report)
private List<ImportListMovie> MapMovieReports(List<ImportListMovie> reports)
{
var mappedMovie = _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = report.Title, TmdbId = report.TmdbId, ImdbId = report.ImdbId, Year = report.Year });
var mappedMovies = reports.Select(m => _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = m.Title, TmdbId = m.TmdbId, ImdbId = m.ImdbId, Year = m.Year }))
.Where(x => x != null)
.ToList();
var mappedListMovie = new ImportListMovie { ListId = report.ListId };
_movieMetadataService.UpsertMany(mappedMovies);
if (mappedMovie != null)
var mappedListMovies = new List<ImportListMovie>();
foreach (var movieMeta in mappedMovies)
{
_movieMetadataService.Upsert(mappedMovie);
var mappedListMovie = new ImportListMovie();
mappedListMovie.MovieMetadata = mappedMovie;
mappedListMovie.MovieMetadataId = mappedMovie.Id;
if (movieMeta != null)
{
mappedListMovie.MovieMetadata = movieMeta;
mappedListMovie.MovieMetadataId = movieMeta.Id;
}
mappedListMovies.Add(mappedListMovie);
}
return mappedListMovie;
return mappedListMovies;
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Config;
@@ -117,7 +117,6 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
syslogTarget.MessageSend.Udp.Port = syslogPort;
syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;

View File

@@ -59,7 +59,7 @@
"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",
"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",
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
"Indexers": "Indexer",
@@ -256,7 +256,7 @@
"AlternativeTitle": "Alternative Titel",
"AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Age": "Alter",
"AddNewMovie": "Neuer Film...",
"AddNewMovie": "Neuen Film hinzufügen",
"AddList": "Liste hinzufügen",
"SystemTimeCheckMessage": "Die Systemzeit ist um einen Tag versetzt. Bis die Zeit korrigiert wurde, könnten die geplanten Aufgaben nicht korrekt ausgeführt werden",
"UnsavedChanges": "Ungespeicherte Änderungen",
@@ -506,7 +506,7 @@
"ScriptPath": "Script Pfad",
"SetPermissions": "Rechte setzen",
"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",
"ShowCutoffUnmetIconHelpText": "Symbol zeigen wenn die Qualitätsschwelle noch nicht erreicht wurde",
"ShowMovieInformationHelpText": "Genre und Zertifizierung anzeigen",
@@ -633,14 +633,14 @@
"ShowGenres": "Genres anzeigen",
"ShowCertification": "Zertifikation anzeigen",
"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",
"RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
"RetryingDownloadInterp": "Herunterladen nochmal versuchen {0} um {1}",
"RemovingTag": "Tag entfernen",
"ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}",
"Queued": "Eingereiht",
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen?",
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen",
"Pending": "Ausstehend",
"Paused": "Pausiert",
"NegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
@@ -1047,7 +1047,7 @@
"RemotePathMappingCheckFilesLocalWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"RemotePathMappingCheckFilesBadDockerPath": "Docker erkannt; Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckFilesWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Remote-Pfadzuordnungen und die Downloader Einstellungen.",
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"RemotePathMappingCheckLocalFolderMissing": "Downloader {0} speichert Downloads in {1}, aber dieses Verzeichnis scheint nicht zu existieren. Möglicherweise eine fehlende oder falsche Remote-Pfadzuordnung.",
@@ -1112,9 +1112,35 @@
"OriginalTitle": "Originaler Titel",
"OriginalLanguage": "Originale Sprache",
"Database": "Datenbank",
"AllCollectionsHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
"Collections": "Sammlung",
"AllCollectionsHiddenDueToFilter": "Alle Filme sind auf Grund des Filters ausgeblendet.",
"Collections": "Sammlungen",
"MonitorMovies": "Film beobachten",
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren.",
"RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )"
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren",
"RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )",
"CollectionOptions": "Sammlung Optionen",
"ChooseImportMode": "Wähle eine Importmethode",
"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"
}

View File

@@ -859,6 +859,7 @@
"RootFolderCheckMultipleMessage": "Multiple root folders are missing: {0}",
"RootFolderCheckSingleMessage": "Missing root folder: {0}",
"RootFolders": "Root Folders",
"RottenTomatoesRating": "Tomato Rating",
"RSS": "RSS",
"RSSIsNotSupportedWithThisIndexer": "RSS is not supported with this indexer",
"RSSSync": "RSS Sync",
@@ -1026,6 +1027,7 @@
"Torrents": "Torrents",
"TorrentsDisabled": "Torrents Disabled",
"TotalFileSize": "Total File Size",
"TotalMovies": "Total Movies",
"TotalSpace": "Total Space",
"Trace": "Trace",
"Trailer": "Trailer",

View File

@@ -1115,5 +1115,9 @@
"AllCollectionsHiddenDueToFilter": "Todas las películas están ocultas debido al filtro aplicado.",
"Collections": "Colección",
"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"
}

View File

@@ -220,7 +220,7 @@
"MoveFiles": "Siirrä tiedostoja",
"MovieFiles": "Elokuvatiedostot",
"MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella",
"NextExecution": "Seuraava toteutus",
"NextExecution": "Seuraava suoritus",
"NoAltTitle": "Ei vaihtoehtoisia nimikkeitä.",
"NoEventsFound": "Tapahtumia ei löytynyt",
"NoLinks": "Ei linkkejä",
@@ -320,8 +320,8 @@
"ImportErrors": "Tuontivirheet",
"Imported": "Tuodut",
"InvalidFormat": "Väärä muoto",
"LastDuration": "Viimeisin kesto",
"LastExecution": "Viimeinen toteutus",
"LastDuration": "Edellinen kesto",
"LastExecution": "Edellinen suoritus",
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
"ListExclusions": "Listojen poikkeussäännöt",
"Indexer": "Tietolähde",
@@ -1115,7 +1115,7 @@
"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.",
"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.",
"Collections": "Kokoelmat",
"MonitorMovies": "Valvo elokuvia",
@@ -1141,5 +1141,6 @@
"CollectionShowOverviewsHelpText": "Näytä kokoelmien katsaukset",
"CollectionOptions": "Kokoelmien valinnat",
"CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet",
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet"
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet",
"RottenTomatoesRating": "Tomaattiarvio"
}

View File

@@ -318,7 +318,7 @@
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
"ChangeFileDate": "Changer la date du fichier",
"CertificationCountryHelpText": "Choisir un pays pour les classifications de films",
"CertificateValidationHelpText": "Change la rigueur de la vérification du certificat HTTPS. Ne changez rien sauf si vous comprenez les risques.",
"CertificateValidationHelpText": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne pas changer à moins que vous ne compreniez les risques.",
"CertificateValidation": "Validation du certificat",
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
"Branch": "Branche",
@@ -1119,5 +1119,8 @@
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
"RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)",
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
"ChooseImportMode": "Mode d'importation"
"ChooseImportMode": "Mode d'importation",
"CollectionOptions": "Options de collection",
"CollectionShowDetailsHelpText": "Afficher l'état et les propriétés de la collection",
"CollectionShowOverviewsHelpText": "Afficher les aperçus des collections"
}

View File

@@ -1137,5 +1137,10 @@
"CollectionOptions": "Gyűjtemény baállítások",
"CollectionShowOverviewsHelpText": "Gyűjtemények áttekintésének 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"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -177,5 +177,82 @@
"ColonReplacementFormatHelpText": "Endre hvordan Radarr håndterer kolonerstatning",
"ConnectionLostAutomaticMessage": "Radarr vil forsøke å koble til automatisk, eller du kan klikke oppdater nedenfor.",
"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"
}

View File

@@ -246,10 +246,10 @@
"ImportMechanismHealthCheckMessage": "Włącz obsługę ukończonego pobierania",
"IndexersSettingsSummary": "Indeksatory i ograniczenia dotyczące wersji",
"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)",
"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.",
"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",
@@ -259,7 +259,7 @@
"Medium": "Średni",
"Minutes": "Minuty",
"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",
"MoveFiles": "Przenieś pliki",
"MovieIsRecommend": "Film jest zalecany na podstawie niedawnego dodania",
@@ -484,8 +484,8 @@
"ClickToChangeMovie": "Kliknij, aby zmienić film",
"ClickToChangeQuality": "Kliknij, aby zmienić jakość",
"CloneCustomFormat": "Klonuj format niestandardowy",
"CloneFormatTag": "Clone Format Tag",
"CloneIndexer": "Clone Indexer",
"CloneFormatTag": "Klonuj Tag Formatu",
"CloneIndexer": "Sklonuj Indekser",
"CloneProfile": "Klonuj profil",
"Close": "Blisko",
"ColonReplacement": "Wymiana okrężnicy",
@@ -497,7 +497,7 @@
"Connection": "Połączenie",
"ConnectionLost": "Utracono połączenie",
"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",
"ConnectSettings": "Ustawienia połączenia",
"ConnectSettingsSummary": "Powiadomienia, połączenia z serwerami / odtwarzaczami multimedialnymi i niestandardowe skrypty",
@@ -545,7 +545,7 @@
"DeleteRestrictionHelpText": "Czy na pewno chcesz usunąć to ograniczenie?",
"DeleteSelectedMovie": "Usuń wybrane filmy",
"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",
"DestinationRelativePath": "Względna ścieżka celu",
"DetailedProgressBar": "Szczegółowy pasek postępu",
@@ -570,7 +570,7 @@
"DownloadFailed": "Pobieranie nie udane",
"DownloadFailedInterp": "Pobieranie nie powiodło się: {0}",
"Downloading": "Ściąganie",
"DownloadPropersAndRepacks": "Propers and Repacks",
"DownloadPropersAndRepacks": "Poprawione i przepakowane",
"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",
"Edition": "Wydanie",
@@ -638,8 +638,8 @@
"GoToInterp": "Idź do {0}",
"Grab": "Chwycić",
"Grabbed": "Złapał",
"GrabID": "Grab ID",
"GrabRelease": "Grab Release",
"GrabID": "Pobierz ID",
"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}”?",
"GrabSelected": "Wybierz wybrane",
"Group": "Grupa",
@@ -650,7 +650,7 @@
"HideAdvanced": "Ukryj zaawansowane",
"History": "Historia",
"Host": "Gospodarz",
"iCalLink": "iCal Link",
"iCalLink": "Łącze do iCal",
"IconForCutoffUnmet": "Ikona Cutoff Unmet",
"IgnoredAddresses": "Ignorowane adresy",
"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",
"ImportMovies": "Importuj filmy",
"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",
"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",
@@ -701,7 +701,7 @@
"Logs": "Dzienniki",
"LookingForReleaseProfiles1": "Szukasz profili wersji? Próbować",
"LookingForReleaseProfiles2": "zamiast.",
"LowerCase": "Małe litery",
"LowerCase": "Z małej litery",
"ManualImportSelectLanguage": "Import ręczny - wybierz język",
"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",
@@ -740,7 +740,7 @@
"MovieInvalidFormat": "Film: nieprawidłowy format",
"MultiLanguage": "Wielojęzyczny",
"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ę.",
"NoTagsHaveBeenAddedYet": "Żadne tagi nie zostały jeszcze dodane",
"Options": "Opcje",
@@ -828,16 +828,16 @@
"RSSSync": "Synchronizacja RSS",
"RSSSyncInterval": "Częstotliwość synchronizacji RSS",
"RSSSyncIntervalHelpTextWarning": "Dotyczy to wszystkich indeksujących, prosimy o przestrzeganie zasad przez nich określonych",
"Runtime": "Runtime",
"Runtime": "Długość",
"Save": "Zapisać",
"SaveChanges": "Zapisz zmiany",
"SaveSettings": "Zapisz ustawienia",
"Scheduled": "Planowy",
"Score": "Wynik",
"Script": "Scenariusz",
"ScriptPath": "Script Path",
"ScriptPath": "Ścieżka do Skryptu",
"SearchAll": "Wyszukaj wszystko",
"SearchCutoffUnmet": "Search Cutoff Unmet",
"SearchCutoffUnmet": "Niespełnione Parametry Wyszukiwania",
"SearchFailedPleaseTryAgainLater": "Wyszukiwanie nie powiodło się, spróbuj ponownie później.",
"SearchFiltered": "Szukaj przefiltrowane",
"SearchForMissing": "Wyszukaj brakujące",
@@ -893,7 +893,7 @@
"Shutdown": "Zamknąć",
"SizeOnDisk": "Rozmiar dysku",
"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",
"Socks4": "Skarpetki 4",
"Socks5": "Socks5 (Wsparcie TOR)",
@@ -932,10 +932,10 @@
"Time": "Czas",
"Title": "Tytuł",
"Titles": "Tytuły",
"TMDBId": "TMDb Id",
"TMDBId": "Identyfikator TMDb",
"TmdbIdHelpText": "Identyfikator TMDb filmu do wykluczenia",
"Today": "Dzisiaj",
"TorrentDelay": "Torrent Delay",
"TorrentDelay": "Opóźnienie Torrenta",
"TorrentDelayHelpText": "Opóźnienie w ciągu kilku minut, aby poczekać przed złapaniem torrenta",
"Torrents": "Torrenty",
"TorrentsDisabled": "Torrenty wyłączone",
@@ -946,7 +946,7 @@
"Type": "Rodzaj",
"UI": "UI",
"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",
"UISettings": "Ustawienia interfejsu użytkownika",
"UISettingsSummary": "Opcje z osłabionym kalendarzem, datą i kolorem",
@@ -989,7 +989,7 @@
"UnsavedChanges": "Niezapisane zmiany",
"UnselectAll": "Odznacz 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}”.",
"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",
@@ -1006,7 +1006,7 @@
"VisitGithubCustomFormatsAphrodite": "Odwiedź wiki, aby uzyskać więcej informacji: ",
"WaitingToProcess": "Czekam na przetworzenie",
"Wanted": "Chciał",
"Warn": "Ostrzec",
"Warn": "Ostrzeż",
"Week": "Tydzień",
"WeekColumnHeader": "Nagłówek kolumny tygodnia",
"Weeks": "Tygodni",
@@ -1043,25 +1043,25 @@
"Download": "Ściągnij",
"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",
"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?",
"Blocklist": "Czarna lista",
"BlocklistRelease": "Wydanie 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",
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Czy na pewno chcesz usunąć {0} element {1} z kolejki?",
"BlocklistReleases": "Wydanie czarnej listy",
"Filters": "Filtr",
"Filters": "Filtry",
"LocalPath": "Ścieżka lokalna",
"List": "Listy",
"Rating": "Oceny",
"RemotePath": "Zdalna ścieżka",
"List": "Lista",
"Rating": "Ocena",
"RemotePath": "Ścieżka zdalna",
"SelectLanguages": "Wybierz język",
"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ń)",
"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",
"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.",
@@ -1078,7 +1078,7 @@
"ManualImportSetReleaseGroup": "Import ręczny - podaj nazwę grupy",
"Never": "Nigdy",
"RemotePathMappingCheckFilesWrongOSPath": "Zdalny klient pobierania {0} zgłosił pliki w {1}, lecz nie jest to poprawna ścieżka {2}. Zmień ustawienia zdalnego mapowania ścieżek i klienta pobierania.",
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, lecz Radarr nie widzi tego folderu. Być może należy zmienić ustawienia uprawnień dostępu.",
"RemotePathMappingCheckGenericPermissions": "Klient pobierania {0} umieszcza pobrane pliki w {1}, lecz Radarr nie widzi tego folderu. Być może należy zmienić uprawnienia dostępu do folderu.",
"RemoveFailed": "Usuń nieudane",
"RemoveDownloadsAlert": "Ustawienia usuwania zostały przeniesione do ustawień poszczególnych klientów pobierania powyżej.",
"ShowCollectionDetails": "Pokaż stan kolekcji",
@@ -1092,7 +1092,7 @@
"EditCollection": "Edytuj kolekcję",
"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.",
"Letterboxd": "Letterboxd",
"Letterboxd": "Z Letterboxd",
"MonitorCollection": "Monitoruj kolekcję",
"MovieAndCollection": "Film i kolekcja",
"MovieCollectionMissingRoot": "Brak katalogu głównego dla kolekcji filmów: {0}",
@@ -1134,5 +1134,12 @@
"TaskUserAgentTooltip": "User-Agent podawany przez aplikację wywołującą API",
"SetReleaseGroup": "Ustaw grupę wydającą",
"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"
}

View File

@@ -1116,5 +1116,12 @@
"Collections": "Coleção",
"RssSyncHelpText": "Intervalo em minutos. Defina como zero para desativar (isso parará toda a captura automática)",
"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"
}

View File

@@ -1,5 +1,5 @@
{
"LastExecution": "Execução mais recente",
"LastExecution": "Última Execução",
"Large": "Grande",
"Languages": "Idiomas",
"LanguageHelpText": "Idioma das versões",
@@ -19,7 +19,7 @@
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
"IndexerSettings": "Configurações do indexador",
"IndexerSearchCheckNoInteractiveMessage": "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",
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
"Indexers": "Indexadores",
@@ -571,7 +571,7 @@
"NoChange": "Sem alteração",
"NoBackupsAreAvailable": "Não há backups disponíveis",
"NoAltTitle": "Nenhum título alternativo.",
"NextExecution": "Próxima execução",
"NextExecution": "Próxima Execução",
"New": "Novo",
"NetCore": ".NET",
"ShowAsAllDayEvents": "Mostrar como eventos de dia inteiro",
@@ -668,7 +668,7 @@
"SomeResultsHiddenFilter": "Alguns resultados estão ocultos pelo filtro aplicado",
"Socks5": "Socks5 (suporte ao TOR)",
"Small": "Pequeno",
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar 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",
"SizeOnDisk": "Tamanho em disco",
"Size": "Tamanho",
@@ -961,7 +961,7 @@
"UpdateSelected": "Atualizar selecionado(s)",
"UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização",
"Updates": "Atualizações",
"UpdateMechanismHelpText": "Usar 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}'.",
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
@@ -1053,7 +1053,7 @@
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
"RemotePathMappingCheckGenericPermissions": "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.",
"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.",
@@ -1119,7 +1119,7 @@
"AllCollectionsHiddenDueToFilter": "Todos os filmes estão ocultos devido ao filtro aplicado.",
"Collections": "Coleção",
"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",
"UnableToLoadCollections": "Não foi possível carregar as coleções",
"ChooseImportMode": "Escolher o Modo de Importação",
@@ -1141,5 +1141,6 @@
"CollectionOptions": "Opções de Coleção",
"CollectionShowDetailsHelpText": "Mostrar estado e propriedades da coleção",
"CollectionShowOverviewsHelpText": "Mostrar visão geral da coleção",
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção"
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção",
"RottenTomatoesRating": "Avaliação Tomato"
}

View File

@@ -110,5 +110,6 @@
"DeleteNotificationMessageText": "Naozaj chcete zmazať značku formátu {0} ?",
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Naozaj chcete odobrať {0} položku {1} z fronty?",
"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."
}

View File

@@ -1,4 +1,7 @@
{
"About": "關於",
"Add": "添加"
"Add": "添加",
"AcceptConfirmationModal": "接受確認模式",
"Actions": "‎行動‎",
"Activity": "‎活動‎"
}

View File

@@ -5,7 +5,7 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.Events
{
public class MovieImportedEvent : IEvent
public class MovieFileImportedEvent : IEvent
{
public LocalMovie MovieInfo { get; private set; }
public MovieFile ImportedMovie { get; private set; }
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.MediaFiles.Events
public DownloadClientItemClientInfo DownloadClientInfo { get; 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;
ImportedMovie = importedMovie;

View File

@@ -153,10 +153,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return "WMA";
}
Logger.Debug()
Logger.ForDebugEvent()
.Message("Unknown audio format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
.WriteSentryWarn("UnknownAudioFormatFFProbe", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
.Write();
.Log();
return mediaInfo.AudioFormat;
}
@@ -262,10 +262,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return "";
}
Logger.Debug()
Logger.ForDebugEvent()
.Message("Unknown video format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
.WriteSentryWarn("UnknownVideoFormatFFProbe", mediaInfo.ContainerFormat, videoFormat, videoCodecID)
.Write();
.Log();
return result;
}

View File

@@ -144,7 +144,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
_extraService.ImportMovie(localMovie, movieFile, copyOnly);
}
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, movieFile, oldFiles, newDownload, downloadClientItem));
_eventAggregator.PublishEvent(new MovieFileImportedEvent(localMovie, movieFile, oldFiles, newDownload, downloadClientItem));
}
catch (RootFolderNotFoundException e)
{

View File

@@ -327,6 +327,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
else if (movie.ImdbId.IsNotNullOrWhiteSpace())
{
newMovie = _movieMetadataService.FindByImdbId(Parser.Parser.NormalizeImdbId(movie.ImdbId));
if (newMovie != null)
{
return newMovie;
}
newMovie = GetMovieByImdbId(movie.ImdbId);
}
else
@@ -337,7 +344,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
yearStr = $" {movie.Year}";
}
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault().MovieMetadata;
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault()?.MovieMetadata ?? null;
}
if (newMovie == null)

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies.Events;
@@ -13,6 +14,7 @@ namespace NzbDrone.Core.Movies.Collections
IEnumerable<MovieCollection> GetCollections(IEnumerable<int> ids);
List<MovieCollection> GetAllCollections();
MovieCollection UpdateCollection(MovieCollection collection);
List<MovieCollection> UpdateCollections(List<MovieCollection> collections);
void RemoveCollection(MovieCollection collection);
bool Upsert(MovieCollection collection);
bool UpsertMany(List<MovieCollection> collections);
@@ -23,12 +25,14 @@ namespace NzbDrone.Core.Movies.Collections
private readonly IMovieCollectionRepository _repo;
private readonly IMovieService _movieService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public MovieCollectionService(IMovieCollectionRepository repo, IMovieService movieService, IEventAggregator eventAggregator)
public MovieCollectionService(IMovieCollectionRepository repo, IMovieService movieService, IEventAggregator eventAggregator, Logger logger)
{
_repo = repo;
_movieService = movieService;
_eventAggregator = eventAggregator;
_logger = logger;
}
public MovieCollection AddCollection(MovieCollection newCollection)
@@ -73,6 +77,21 @@ namespace NzbDrone.Core.Movies.Collections
return updatedCollection;
}
public List<MovieCollection> UpdateCollections(List<MovieCollection> collections)
{
_logger.Debug("Updating {0} movie collections", collections.Count);
foreach (var c in collections)
{
_logger.Trace("Updating: {0}", c.Title);
}
_repo.UpdateMany(collections);
_logger.Debug("{0} movie collections updated", collections.Count);
return collections;
}
public void RemoveCollection(MovieCollection collection)
{
_repo.Delete(collection);
@@ -103,7 +122,7 @@ namespace NzbDrone.Core.Movies.Collections
var collection = FindByTmdbId(collectionTmdbId);
_repo.Delete(collectionTmdbId);
_repo.Delete(collection.Id);
_eventAggregator.PublishEvent(new CollectionDeletedEvent(collection));
}

View File

@@ -5,11 +5,11 @@ namespace NzbDrone.Core.Movies.Events
{
public class MoviesImportedEvent : IEvent
{
public List<int> MovieIds { get; private set; }
public List<Movie> Movies { get; private set; }
public MoviesImportedEvent(List<int> movieIds)
public MoviesImportedEvent(List<Movie> movies)
{
MovieIds = movieIds;
Movies = movies;
}
}
}

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Movies
public void Handle(MoviesImportedEvent message)
{
_commandQueueManager.PushMany(message.MovieIds.Select(s => new RefreshMovieCommand(new List<int> { s }, true)).ToList());
_commandQueueManager.PushMany(message.Movies.Select(s => new RefreshMovieCommand(new List<int> { s.Id }, true)).ToList());
}
}
}

View File

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

View File

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

View File

@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Movies
{
_movieRepository.InsertMany(newMovies);
_eventAggregator.PublishEvent(new MoviesImportedEvent(newMovies.Select(s => s.Id).ToList()));
_eventAggregator.PublishEvent(new MoviesImportedEvent(newMovies));
return newMovies;
}

View File

@@ -3,22 +3,22 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.ImportLists.ImportExclusions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Events;
namespace NzbDrone.Core.Movies
{
public class RefreshCollectionService : IExecute<RefreshCollectionsCommand>, IHandle<CollectionEditedEvent>
public class RefreshCollectionService : IExecute<RefreshCollectionsCommand>
{
private readonly IProvideMovieInfo _movieInfo;
private readonly IMovieCollectionService _collectionService;
private readonly IMovieService _movieService;
private readonly IMovieMetadataService _movieMetadataService;
private readonly IAddMovieService _addMovieService;
private readonly IImportExclusionsService _importExclusionService;
private readonly Logger _logger;
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Movies
IMovieService movieService,
IMovieMetadataService movieMetadataService,
IAddMovieService addMovieService,
IImportExclusionsService importExclusionsService,
Logger logger)
{
_movieInfo = movieInfo;
@@ -34,6 +35,7 @@ namespace NzbDrone.Core.Movies
_movieService = movieService;
_movieMetadataService = movieMetadataService;
_addMovieService = addMovieService;
_importExclusionService = importExclusionsService;
_logger = logger;
}
@@ -99,21 +101,26 @@ namespace NzbDrone.Core.Movies
{
var existingMovies = _movieService.AllMovieTmdbIds();
var collectionMovies = _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId);
var excludedMovies = _importExclusionService.GetAllExclusions().Select(e => e.TmdbId);
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId)).Where(m => !excludedMovies.Contains(m.TmdbId));
_addMovieService.AddMovies(collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId)).Select(m => new Movie
if (moviesToAdd.Any())
{
TmdbId = m.TmdbId,
Title = m.Title,
ProfileId = collection.QualityProfileId,
RootFolderPath = collection.RootFolderPath,
MinimumAvailability = collection.MinimumAvailability,
AddOptions = new AddMovieOptions
_addMovieService.AddMovies(moviesToAdd.Select(m => new Movie
{
SearchForMovie = collection.SearchOnAdd,
AddMethod = AddMovieMethod.Collection
},
Monitored = true
}).ToList());
TmdbId = m.TmdbId,
Title = m.Title,
ProfileId = collection.QualityProfileId,
RootFolderPath = collection.RootFolderPath,
MinimumAvailability = collection.MinimumAvailability,
AddOptions = new AddMovieOptions
{
SearchForMovie = collection.SearchOnAdd,
AddMethod = AddMovieMethod.Collection
},
Monitored = true
}).ToList());
}
}
}
@@ -151,10 +158,5 @@ namespace NzbDrone.Core.Movies
}
}
}
public void Handle(CollectionEditedEvent message)
{
SyncCollectionMovies(message.Collection);
}
}
}

View File

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

View File

@@ -6,6 +6,7 @@ using System.Linq;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Notifications.Notifiarr
@@ -47,6 +48,8 @@ namespace NzbDrone.Core.Notifications.Notifiarr
variables.Add("Radarr_Download_Client", message.DownloadClientName ?? string.Empty);
variables.Add("Radarr_Download_Client_Type", message.DownloadClientType ?? string.Empty);
variables.Add("Radarr_Download_Id", message.DownloadId ?? string.Empty);
variables.Add("Radarr_Release_CustomFormat", string.Join("|", remoteMovie.CustomFormats));
variables.Add("Radarr_Release_CustomFormatScore", remoteMovie.CustomFormatScore.ToString());
_proxy.SendNotification(variables, Settings);
}
@@ -77,6 +80,15 @@ namespace NzbDrone.Core.Notifications.Notifiarr
variables.Add("Radarr_MovieFile_SceneName", movieFile.SceneName ?? string.Empty);
variables.Add("Radarr_MovieFile_SourcePath", sourcePath);
variables.Add("Radarr_MovieFile_SourceFolder", Path.GetDirectoryName(sourcePath));
variables.Add("Radarr_MovieFile_MediaInfo_AudioChannels", MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString());
variables.Add("Radarr_MovieFile_MediaInfo_AudioCodec", MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, null));
variables.Add("Radarr_MovieFile_MediaInfo_AudioLanguages", movieFile.MediaInfo.AudioLanguages.Distinct().ConcatToString(" / "));
variables.Add("Radarr_MovieFile_MediaInfo_Languages", movieFile.MediaInfo.AudioLanguages.ConcatToString(" / "));
variables.Add("Radarr_MovieFile_MediaInfo_Height", movieFile.MediaInfo.Height.ToString());
variables.Add("Radarr_MovieFile_MediaInfo_Width", movieFile.MediaInfo.Width.ToString());
variables.Add("Radarr_MovieFile_MediaInfo_Subtitles", movieFile.MediaInfo.Subtitles.ConcatToString(" / "));
variables.Add("Radarr_MovieFile_MediaInfo_VideoCodec", MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, null));
variables.Add("Radarr_MovieFile_MediaInfo_VideoDynamicRangeType", MediaInfoFormatter.FormatVideoDynamicRangeType(movieFile.MediaInfo));
variables.Add("Radarr_Download_Id", message.DownloadId ?? string.Empty);
variables.Add("Radarr_Download_Client", message.DownloadClientInfo?.Name ?? string.Empty);
variables.Add("Radarr_Download_Client_Type", message.DownloadClientInfo?.Type ?? string.Empty);

View File

@@ -54,17 +54,19 @@ namespace NzbDrone.Core.Notifications.Notifiarr
_logger.Error(ex, "API key is invalid: " + ex.Message);
return new ValidationFailure("APIKey", "API key is invalid");
case 400:
_logger.Error(ex, "Unable to send test message. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
return new ValidationFailure("", "Unable to send test message. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
case 520:
case 521:
case 522:
case 523:
case 524:
_logger.Error(ex, "Unable to send test notification: " + ex.Message);
return new ValidationFailure("", "Unable to send test notification");
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send test message: " + ex.Message);
return new ValidationFailure("", "Cloudflare Related HTTP Error - Unable to send test message");
}
_logger.Error(ex, "Unable to send test message: " + ex.Message);
return new ValidationFailure("APIKey", "Unable to send test notification");
_logger.Error(ex, "Unknown HTTP Error - Unable to send test message: " + ex.Message);
return new ValidationFailure("", "Unknown HTTP Error - Unable to send test message");
}
catch (Exception ex)
{
@@ -78,8 +80,7 @@ namespace NzbDrone.Core.Notifications.Notifiarr
try
{
var url = settings.Environment == (int)NotifiarrEnvironment.Development ? "https://dev.notifiarr.com" : "https://notifiarr.com";
var requestBuilder = new HttpRequestBuilder(url + "/notifier.php").Post();
requestBuilder.AddFormParameter("api", settings.APIKey).Build();
var requestBuilder = new HttpRequestBuilder(url + "/api/v1/notification/radarr/" + settings.APIKey).Post();
requestBuilder.AddFormParameter("instanceName", settings.InstanceName).Build();
foreach (string key in message.Keys)
@@ -96,19 +97,21 @@ namespace NzbDrone.Core.Notifications.Notifiarr
switch ((int)ex.Response.StatusCode)
{
case 401:
_logger.Error(ex, "API key is invalid");
throw;
_logger.Error("", "API key is invalid");
throw new NotifiarrException("API key is invalid", ex);
case 400:
_logger.Error(ex, "Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
throw new NotifiarrException("Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr", ex);
case 520:
case 521:
case 522:
case 523:
case 524:
_logger.Error(ex, "Unable to send notification");
throw;
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send notification");
throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex);
}
throw new NotifiarrException("Unable to send notification", ex);
throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex);
}
}
}

View File

@@ -17,9 +17,10 @@ namespace NzbDrone.Core.Notifications
public class NotificationService
: IHandle<MovieRenamedEvent>,
IHandle<MovieGrabbedEvent>,
IHandle<MovieImportedEvent>,
IHandle<MovieFileImportedEvent>,
IHandle<MoviesDeletedEvent>,
IHandle<MovieAddedEvent>,
IHandle<MoviesImportedEvent>,
IHandle<MovieFileDeletedEvent>,
IHandle<HealthCheckFailedEvent>,
IHandle<UpdateInstalledEvent>,
@@ -119,7 +120,7 @@ namespace NzbDrone.Core.Notifications
}
}
public void Handle(MovieImportedEvent message)
public void Handle(MovieFileImportedEvent message)
{
if (!message.NewDownload)
{
@@ -174,6 +175,27 @@ namespace NzbDrone.Core.Notifications
}
}
public void Handle(MoviesImportedEvent message)
{
foreach (var notification in _notificationFactory.OnMovieAddedEnabled())
{
try
{
foreach (var movie in message.Movies)
{
if (ShouldHandleMovie(notification.Definition, movie))
{
notification.OnMovieAdded(movie);
}
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to send OnMovieAdded notification to: " + notification.Definition.Name);
}
}
}
public void Handle(MovieRenamedEvent message)
{
foreach (var notification in _notificationFactory.OnRenameEnabled())

View File

@@ -20,6 +20,18 @@ namespace NzbDrone.Core.Parser
private static readonly Regex[] ReportMovieTitleRegex = new[]
{
//Anime [Subgroup] and Year
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(1(8|9)|20)\d{2}(?!p|i|x|\d+|\]|\W\d+)))+.*?(?<hash>\[\w{8}\])?(?:$|\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime [Subgroup] no year, versioned title, hash
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>(?![(\[]).+?)((v)(?:\d{1,2})(?:([-_. ])))(\[.*)?(?:[\[(][^])])?.*?(?<hash>\[\w{8}\])(?:$|\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime [Subgroup] no year, info in double sets of brackets, hash
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>(?![(\[]).+?)(\[.*).*?(?<hash>\[\w{8}\])(?:$|\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime [Subgroup] no year, info in parentheses or brackets, hash
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>(?![(\[]).+)(?:[\[(][^])]).*?(?<hash>\[\w{8}\])(?:$|\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Some german or french tracker formats (missing year, ...) (Only applies to german and TrueFrench releases) - see ParserFixture for examples and tests - french removed as it broke all movies w/ french titles
new Regex(@"^(?<title>(?![(\[]).+?)((\W|_))(" + EditionRegex + @".{1,3})?(?:(?<!(19|20)\d{2}.*?)(German|TrueFrench))(.+?)(?=((19|20)\d{2}|$))(?<year>(19|20)\d{2}(?!p|i|\d+|\]|\W\d+))?(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -41,7 +53,7 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?<title>(?![(\[]).+?)?(?:(?:[-_\W](?<![)!]))*(?<year>(1(8|9)|20)\d{2}(?!p|i|\d+|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
//As a last resort for movies that have ( or [ in their title.
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(1(8|9)|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![)\[!]))*(?<year>(1(8|9)|20)\d{2}(?!p|i|\d+|\]|\W\d+)))+(\W+|_|$)(?!\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled)
};
private static readonly Regex[] ReportMovieTitleFolderRegex = new[]
@@ -97,7 +109,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7,8})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex ReportTmdbId = new Regex(@"tmdb(id)?-(?<tmdbid>\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"(?:(480|540|576|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(8|10)b(it)?|10-bit)\s*?",
private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"(?:(480|540|576|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(8|10)b(it)?|10-bit)\s*?(?![a-b0-9])",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.Compiled);

View File

@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex SourceRegex = new Regex(@"\b(?:
(?<bluray>M?BluRay|Blu-Ray|HD.?DVD|BD(?!$)|UHDBD|UHD2BD|BDISO|BDMux|BD25|BD50|BR.?DISK)|
(?<webdl>WEB[-_. ]DL(?:mux)?|WEBDL|AmazonHD|iTunesHD|MaxdomeHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh]26[45]|DDP?5[. ]1)|[. ](?-i:WEB)$|(?:\d{3,4}0p)[-. ]WEB[-. ]|[-. ]WEB[-. ]\d{3,4}0p|\b\s\/\sWEB\s\/\s\b|(?:AMZN|NF|DP)[. -]WEB[. -])|
(?<webdl>WEB[-_. ]DL(?:mux)?|WEBDL|AmazonHD|iTunesHD|MaxdomeHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh][ .]?26[45]|DDP?5[. ]1)|[. ](?-i:WEB)$|(?:\d{3,4}0p)[-. ]WEB[-. ]|[-. ]WEB[-. ]\d{3,4}0p|\b\s\/\sWEB\s\/\s\b|(?:AMZN|NF|DP)[. -]WEB[. -])|
(?<webrip>WebRip|Web-Rip|WEBMux)|
(?<hdtv>HDTV)|
(?<bdrip>BDRip|BDLight)|
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Parser
(?<scr>SCR|SCREENER|DVDSCR|DVDSCREENER)|
(?<ts>TS[-_. ]|TELESYNC|HD-TS|HDTS|PDVD|TSRip|HDTSRip)|
(?<tc>TC|TELECINE|HD-TC|HDTC)|
(?<cam>CAMRIP|CAM|HDCAM|HD-CAM)|
(?<cam>CAMRIP|CAM|HDCAM|HQCAM|HD-CAM)|
(?<wp>WORKPRINT|WP)|
(?<pdtv>PDTV)|
(?<sdtv>SDTV)|

View File

@@ -19,7 +19,7 @@
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="MonoTorrent" Version="2.0.5" />
<PackageReference Include="System.Text.Json" Version="6.0.4" />

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Validation.Paths
private readonly IAppFolderInfo _appFolderInfo;
public StartupFolderValidator(IAppFolderInfo appFolderInfo)
: base("Path cannot be an ancestor of the start up folder")
: base("Path cannot be {relationship} the start up folder")
{
_appFolderInfo = appFolderInfo;
}
@@ -21,7 +21,24 @@ namespace NzbDrone.Core.Validation.Paths
return true;
}
return !_appFolderInfo.StartUpFolder.IsParentPath(context.PropertyValue.ToString());
var startupFolder = _appFolderInfo.StartUpFolder;
var folder = context.PropertyValue.ToString();
if (startupFolder.PathEquals(folder))
{
context.MessageFormatter.AppendArgument("relationship", "set to");
return false;
}
if (startupFolder.IsParentPath(folder))
{
context.MessageFormatter.AppendArgument("relationship", "child of");
return false;
}
return true;
}
}
}

View File

@@ -6,7 +6,7 @@
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="RestSharp" Version="106.15.0" />
</ItemGroup>

View File

@@ -6,7 +6,7 @@
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />

View File

@@ -4,7 +4,7 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog" Version="5.0.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,10 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Commands;
using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.SignalR;
@@ -25,13 +28,15 @@ namespace Radarr.Api.V3.Collections
private readonly IMovieMetadataService _movieMetadataService;
private readonly IBuildFileNames _fileNameBuilder;
private readonly INamingConfigService _namingService;
private readonly IManageCommandQueue _commandQueueManager;
public CollectionController(IBroadcastSignalRMessage signalRBroadcaster,
IMovieCollectionService collectionService,
IMovieService movieService,
IMovieMetadataService movieMetadataService,
IBuildFileNames fileNameBuilder,
INamingConfigService namingService)
INamingConfigService namingService,
IManageCommandQueue commandQueueManager)
: base(signalRBroadcaster)
{
_collectionService = collectionService;
@@ -39,6 +44,7 @@ namespace Radarr.Api.V3.Collections
_movieMetadataService = movieMetadataService;
_fileNameBuilder = fileNameBuilder;
_namingService = namingService;
_commandQueueManager = commandQueueManager;
}
protected override CollectionResource GetResourceById(int id)
@@ -49,9 +55,7 @@ namespace Radarr.Api.V3.Collections
[HttpGet]
public List<CollectionResource> GetCollections()
{
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
return MapToResource(_collectionService.GetAllCollections(), collectionMovies).ToList();
return MapToResource(_collectionService.GetAllCollections()).ToList();
}
[RestPutById]
@@ -67,40 +71,54 @@ namespace Radarr.Api.V3.Collections
}
[HttpPut]
public ActionResult UpdateCollections(CollectionUpdateResource collectionResources)
public ActionResult UpdateCollections(CollectionUpdateResource resource)
{
var collectionsToUpdate = _collectionService.GetCollections(collectionResources.Collections.Select(c => c.Id));
var update = new List<CollectionResource>();
var collectionsToUpdate = _collectionService.GetCollections(resource.CollectionIds);
foreach (var c in collectionResources.Collections)
foreach (var collection in collectionsToUpdate)
{
var collection = collectionsToUpdate.Single(n => n.Id == c.Id);
if (c.Monitored.HasValue)
if (resource.Monitored.HasValue)
{
collection.Monitored = c.Monitored.Value;
collection.Monitored = resource.Monitored.Value;
}
if (collectionResources.MonitorMovies.HasValue)
if (resource.QualityProfileId.HasValue)
{
collection.QualityProfileId = resource.QualityProfileId.Value;
}
if (resource.MinimumAvailability.HasValue)
{
collection.MinimumAvailability = resource.MinimumAvailability.Value;
}
if (resource.RootFolderPath.IsNotNullOrWhiteSpace())
{
collection.RootFolderPath = resource.RootFolderPath;
}
if (resource.MonitorMovies.HasValue)
{
var movies = _movieService.GetMoviesByCollectionTmdbId(collection.TmdbId);
movies.ForEach(c => c.Monitored = collectionResources.MonitorMovies.Value);
movies.ForEach(c => c.Monitored = resource.MonitorMovies.Value);
_movieService.UpdateMovie(movies, true);
}
var updatedCollection = _collectionService.UpdateCollection(collection);
update.Add(updatedCollection.ToResource());
}
return Accepted(update);
var updated = _collectionService.UpdateCollections(collectionsToUpdate.ToList()).ToResource();
_commandQueueManager.Push(new RefreshCollectionsCommand());
return Accepted(updated);
}
private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> collections, List<MovieMetadata> collectionMovies)
private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> collections)
{
// Avoid calling for naming spec on every movie in filenamebuilder
var namingConfig = _namingService.GetConfig();
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
foreach (var collection in collections)
{
@@ -125,7 +143,7 @@ namespace Radarr.Api.V3.Collections
foreach (var movie in _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId))
{
var movieResource = movie.ToResource();
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { Title = movie.Title, Year = movie.Year, ImdbId = movie.ImdbId, TmdbId = movie.TmdbId });
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie });
resource.Movies.Add(movieResource);
}
@@ -148,7 +166,7 @@ namespace Radarr.Api.V3.Collections
[NonAction]
public void Handle(CollectionDeletedEvent message)
{
BroadcastResourceChange(ModelAction.Deleted, MapToResource(message.Collection));
BroadcastResourceChange(ModelAction.Deleted, message.Collection.Id);
}
}
}

View File

@@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Movies;
namespace Radarr.Api.V3.Collections
{
public class CollectionUpdateResource
{
public List<CollectionUpdateCollectionResource> Collections { get; set; }
public List<int> CollectionIds { get; set; }
public bool? Monitored { get; set; }
public bool? MonitorMovies { get; set; }
public int? QualityProfileId { get; set; }
public string RootFolderPath { get; set; }
public MovieStatusType? MinimumAvailability { get; set; }
}
}

View File

@@ -2,10 +2,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Validation;
using Radarr.Http;
using Radarr.Http.REST;
using Radarr.Http.REST.Attributes;
@@ -50,6 +52,9 @@ namespace Radarr.Api.V3.CustomFormats
public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource)
{
var model = customFormatResource.ToModel(_specifications);
Validate(model);
return Created(_formatService.Insert(model).Id);
}
@@ -57,6 +62,9 @@ namespace Radarr.Api.V3.CustomFormats
public ActionResult<CustomFormatResource> Update(CustomFormatResource resource)
{
var model = resource.ToModel(_specifications);
Validate(model);
_formatService.Update(model);
return Accepted(model.Id);
@@ -89,6 +97,25 @@ namespace Radarr.Api.V3.CustomFormats
return schema;
}
private void Validate(CustomFormat definition)
{
foreach (var spec in definition.Specifications)
{
var validationResult = spec.Validate();
VerifyValidationResult(validationResult);
}
}
protected void VerifyValidationResult(ValidationResult validationResult)
{
var result = new NzbDroneValidationResult(validationResult.Errors);
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
}
private IEnumerable<ICustomFormatSpecification> GetPresets()
{
yield return new ReleaseTitleSpecification

View File

@@ -11,6 +11,7 @@ using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using Radarr.Api.V3.Movies;
using Radarr.Http;
namespace Radarr.Api.V3.ImportLists
@@ -19,6 +20,7 @@ namespace Radarr.Api.V3.ImportLists
public class ImportListMoviesController : Controller
{
private readonly IMovieService _movieService;
private readonly IAddMovieService _addMovieService;
private readonly IProvideMovieInfo _movieInfo;
private readonly IBuildFileNames _fileNameBuilder;
private readonly IImportListMovieService _listMovieService;
@@ -28,6 +30,7 @@ namespace Radarr.Api.V3.ImportLists
private readonly IConfigService _configService;
public ImportListMoviesController(IMovieService movieService,
IAddMovieService addMovieService,
IProvideMovieInfo movieInfo,
IBuildFileNames fileNameBuilder,
IImportListMovieService listMovieService,
@@ -37,6 +40,7 @@ namespace Radarr.Api.V3.ImportLists
IConfigService configService)
{
_movieService = movieService;
_addMovieService = addMovieService;
_movieInfo = movieInfo;
_fileNameBuilder = fileNameBuilder;
_listMovieService = listMovieService;
@@ -92,6 +96,14 @@ namespace Radarr.Api.V3.ImportLists
return realResults;
}
[HttpPost]
public object AddMovies([FromBody] List<MovieResource> resource)
{
var newMovies = resource.ToModel();
return _addMovieService.AddMovies(newMovies, true).ToResource(0);
}
private IEnumerable<ImportListMoviesResource> MapToResource(IEnumerable<Movie> movies, Language language)
{
//Avoid calling for naming spec on every movie in filenamebuilder

View File

@@ -32,7 +32,7 @@ namespace Radarr.Api.V3.Movies
{
[V3ApiController]
public class MovieController : RestControllerWithSignalR<MovieResource, Movie>,
IHandle<MovieImportedEvent>,
IHandle<MovieFileImportedEvent>,
IHandle<MovieFileDeletedEvent>,
IHandle<MovieUpdatedEvent>,
IHandle<MovieEditedEvent>,
@@ -264,7 +264,7 @@ namespace Radarr.Api.V3.Movies
}
[NonAction]
public void Handle(MovieImportedEvent message)
public void Handle(MovieFileImportedEvent message)
{
var availDelay = _configService.AvailabilityDelay;
var translations = _movieTranslationService.GetAllTranslationsForMovieMetadata(message.ImportedMovie.Movie.MovieMetadataId);

View File

@@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="Ical.Net" Version="4.2.0" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Radarr.Core.csproj" />

View File

@@ -3315,6 +3315,44 @@
"description": "Success"
}
}
},
"post": {
"tags": [
"ImportListMovies"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MovieResource"
}
}
},
"text/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MovieResource"
}
}
},
"application/*+json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MovieResource"
}
}
}
}
},
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/v3/indexer": {
@@ -8705,33 +8743,36 @@
},
"additionalProperties": false
},
"CollectionUpdateCollectionResource": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"monitored": {
"type": "boolean",
"nullable": true
}
},
"additionalProperties": false
},
"CollectionUpdateResource": {
"type": "object",
"properties": {
"collections": {
"collectionIds": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CollectionUpdateCollectionResource"
"type": "integer",
"format": "int32"
},
"nullable": true
},
"monitored": {
"type": "boolean",
"nullable": true
},
"monitorMovies": {
"type": "boolean",
"nullable": true
},
"qualityProfileId": {
"type": "integer",
"format": "int32",
"nullable": true
},
"rootFolderPath": {
"type": "string",
"nullable": true
},
"minimumAvailability": {
"$ref": "#/components/schemas/MovieStatusType"
}
},
"additionalProperties": false

View File

@@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="ImpromptuInterface" Version="7.0.1" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Radarr.Core.csproj" />