1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-15 15:54:47 -04:00

Compare commits

..

29 Commits

Author SHA1 Message Date
Mark McDowall
79c03f2fe6 Fixed: Reject full DVD disk releases
(cherry picked from commit df2e867528249cf707788d8341c4a26293e179ba)
2023-10-14 21:06:36 -04:00
Bogdan
9b36404071 Fixed: Don't die in Collections when a collection doesn't have movies 2023-10-14 20:11:36 +03:00
Bogdan
ecfaea3885 Fixed: Don't die in FileNameBuilder when OriginalTitle is null 2023-10-13 19:49:31 +03:00
Bogdan
bfbeb4c62e Fixed: Ignore case when cleansing announce URLs 2023-10-12 05:03:22 +03:00
Bogdan
4b98d27f31 New: Tooltips for dates in MovieReleaseDates 2023-10-11 15:02:43 +03:00
Weblate
604d74270d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Timo <Tclemens@live.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-10-11 06:33:42 +03:00
Bogdan
15bb9139d1 New: Sort movies by release dates 2023-10-11 06:32:30 +03:00
Servarr
32722eb704 Automated API Docs update 2023-10-11 05:07:00 +03:00
Mark McDowall
e0c8a8f0d6 New: Download client option for redownloading failed releases from Interactive Search
(cherry picked from commit 87e0a7983a437a4d166aa8b9c9eaf78ea5431969)

Closes #9260
2023-10-11 04:50:39 +03:00
Bogdan
a3bb0541f0 Prevent NullRef on header assert 2023-10-11 04:29:20 +03:00
Bogdan
e78bc34514 Fixed: Fetch import lists without depending on Automatic Add 2023-10-11 03:38:28 +03:00
Servarr
35c4538288 Automated API Docs update 2023-10-11 02:27:16 +03:00
Bogdan
3981e816cd Remove PagingResourceFilter 2023-10-11 02:19:28 +03:00
Mark McDowall
9354031571 Log executing health check
Towards #6076

(cherry picked from commit 78b39bd2fecda60e04a1fef17ae17f62bd2b6914)
2023-10-10 07:04:52 +03:00
Mark McDowall
a01328dc8c Paging params in API docs
(cherry picked from commit bfaa7291e14a8d3847ef2154a52c363944560803)

Closes #9248
2023-10-10 07:03:41 +03:00
Stevie Robinson
8cb6295ddc New: Additional tooltips for icon buttons
(cherry picked from commit 8c07f0d3d19a48ed96d1ded54399c66bf2977b2a)

Closes #9253
2023-10-10 06:51:57 +03:00
Bogdan
99f7d8bcf5 New: Auto tag based on movie's quality profile
(cherry picked from commit 6de3e7c950bd939bab96ef2ae74337108ad5a212)

Closes #9254
2023-10-10 06:50:36 +03:00
Bogdan
f13d479b88 Add status test all button for IndexerLongTermStatusCheck
(cherry picked from commit 4ffa1816bd2305550abee20cea27e1296a99ddf6)
2023-10-10 06:48:04 +03:00
Bogdan
23eb637bc3 Fixed: Avoid logging evaluations when not using any Remote Path Mappings
(cherry picked from commit 44eb729ccc13237f4439006159bd616e8bdb5750)
2023-10-10 06:47:49 +03:00
Bogdan
3a786d0b9d Fixed: Displaying multiple values when adding custom filters
Closes #5810
2023-10-10 05:55:55 +03:00
Bogdan
6fb127235c Fixed: Calendar's agenda on mobile 2023-10-10 01:42:46 +03:00
Qstick
5517e578b6 Bump version to 5.1.0 2023-10-07 23:07:42 -05:00
nuxen
bced2e7b2e Fixed: Updated BR-DISK quality parsing 2023-10-08 06:49:37 +03:00
Bogdan
f7313369b5 Fixed: Show year in collection movies as labels 2023-10-08 06:12:18 +03:00
Servarr
b14e93e11f Automated API Docs update 2023-10-08 01:50:59 +03:00
Bogdan
f5692d6cf1 Fixed: Show year and fix sorting for collection movies 2023-10-08 01:41:24 +03:00
Weblate
a2d505c795 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Florian <sephrat.flo@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blankhang <blankhang@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-10-07 16:53:23 -05:00
Qstick
3d46bd2d8f Revert cover mapping for collections, optimize translation mapping 2023-10-07 15:43:59 -05:00
Bogdan
017f272201 Log Notifiarr errors as warnings 2023-10-07 22:56:22 +03:00
91 changed files with 688 additions and 466 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.0.3'
majorVersion: '5.1.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -1,6 +1,8 @@
import AppSectionState from 'App/State/AppSectionState';
import MovieCollection from 'typings/MovieCollection';
type MovieCollectionAppState = AppSectionState<MovieCollection>;
interface MovieCollectionAppState extends AppSectionState<MovieCollection> {
itemMap: Record<number, number>;
}
export default MovieCollectionAppState;

View File

@@ -42,9 +42,9 @@ function Agenda(props) {
<div className={styles.agenda}>
{
items.map((item, index) => {
const momentDate = moment(item.inCinemas);
const momentDate = moment(item.sortDate);
const showDate = index === 0 ||
!moment(items[index - 1].inCinemas).isSame(momentDate, 'day');
!moment(items[index - 1].sortDate).isSame(momentDate, 'day');
return (
<AgendaEventConnector

View File

@@ -88,7 +88,7 @@
}
@media only screen and (max-width: $breakpointSmall) {
.event {
.overlay {
flex-direction: column;
}
@@ -111,5 +111,4 @@
.releaseIcon {
margin-right: 20px;
width: 25px;
text-align: right;
}

View File

@@ -95,7 +95,7 @@ class AgendaEvent extends Component {
<div className={styles.overlay}>
<div className={styles.date}>
{(showDate) ? startTime.format(longDateFormat) : null}
{showDate ? startTime.format(longDateFormat) : null}
</div>
<div className={styles.releaseIcon}>

View File

@@ -21,6 +21,7 @@ function createMapStateToProps() {
return {
...collection,
movies: [...collection.movies].sort((a, b) => b.year - a.year),
genres: Array.from(new Set(allGenres)).slice(0, 3)
};
}

View File

@@ -61,6 +61,7 @@ class CollectionMovie extends Component {
const {
id,
title,
status,
overview,
year,
tmdbId,
@@ -123,11 +124,11 @@ class CollectionMovie extends Component {
<div className={styles.overlay}>
<div className={styles.overlayTitle}>
{title}
{title} {year > 0 ? `(${year})` : ''}
</div>
{
id &&
id ?
<div className={styles.overlayStatus}>
<MovieIndexProgressBar
monitored={monitored}
@@ -138,7 +139,8 @@ class CollectionMovie extends Component {
detailedProgressBar={detailedProgressBar}
isAvailable={isAvailable}
/>
</div>
</div> :
null
}
</div>
</Link>
@@ -171,6 +173,7 @@ CollectionMovie.propTypes = {
id: PropTypes.number,
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
monitored: PropTypes.bool,
collectionId: PropTypes.number.isRequired,

View File

@@ -5,7 +5,7 @@
margin: 2px 4px;
border: 1px solid var(--borderColor);
border-radius: 4px;
background-color: #eee;
background-color: var(--inputBackgroundColor);
cursor: default;
}
@@ -17,7 +17,7 @@
padding: 0 4px;
border-left: 4px;
border-left-style: solid;
background-color: var(--white);
background-color: var(--themeLightColor);
color: var(--defaultColor);
}

View File

@@ -14,6 +14,7 @@ class CollectionMovieLabel extends Component {
const {
id,
title,
year,
status,
monitored,
isAvailable,
@@ -35,9 +36,7 @@ class CollectionMovieLabel extends Component {
}
<span>
{
title
}
{title} {year > 0 ? `(${year})` : ''}
</span>
</div>
@@ -62,6 +61,7 @@ class CollectionMovieLabel extends Component {
CollectionMovieLabel.propTypes = {
id: PropTypes.number,
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string,
isAvailable: PropTypes.bool,
monitored: PropTypes.bool,

View File

@@ -28,7 +28,6 @@ function calculatePosterWidth(posterSize, isSmallScreen) {
}
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
const heights = [
overviewOptions.showPosters ? posterHeight : 75,
isSmallScreen ? columnPaddingSmallScreen : columnPadding
@@ -122,8 +121,8 @@ class CollectionOverviews extends Component {
overviewOptions
} = this.props;
const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
const posterHeight = calculatePosterHeight(posterWidth);
const posterWidth = overviewOptions.showPosters ? calculatePosterWidth(overviewOptions.size, isSmallScreen) : 0;
const posterHeight = overviewOptions.showPosters ? calculatePosterHeight(posterWidth) : 0;
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
this.setState({

View File

@@ -1,5 +1,5 @@
.tag {
height: 21px;
display: flex;
&.isLastTag {
.or {
@@ -18,4 +18,5 @@
.or {
margin: 0 3px;
color: var(--themeDarkColor);
line-height: 31px;
}

View File

@@ -7,7 +7,7 @@ import styles from './FilterBuilderRowValueTag.css';
function FilterBuilderRowValueTag(props) {
return (
<span
<div
className={styles.tag}
>
<TagInputTag
@@ -22,7 +22,7 @@ function FilterBuilderRowValueTag(props) {
{translate('Or')}
</div>
}
</span>
</div>
);
}

View File

@@ -2,8 +2,10 @@
display: flex;
justify-content: flex-end;
margin-right: $formLabelRightMarginWidth;
padding-top: 8px;
min-height: 35px;
text-align: end;
font-weight: bold;
line-height: 35px;
}
.hasError {

View File

@@ -37,6 +37,8 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.OAUTH;
case 'rootFolder':
return inputTypes.ROOT_FOLDER_SELECT;
case 'qualityProfile':
return inputTypes.QUALITY_PROFILE_SELECT;
default:
return inputTypes.TEXT;
}

View File

@@ -3,6 +3,7 @@ import React from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ModalContent.css';
function ModalContent(props) {
@@ -28,6 +29,7 @@ function ModalContent(props) {
<Icon
name={icons.CLOSE}
size={18}
title={translate('Close')}
/>
</Link>
}

View File

@@ -82,6 +82,7 @@ class PageHeader extends Component {
aria-label="Donate"
to="https://radarr.video/donate"
size={14}
title={translate('Donate')}
/>
<IconButton
className={styles.translate}

View File

@@ -24,6 +24,7 @@ function PageHeaderActionsMenu(props) {
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
title={translate('Menu')}
/>
</MenuButton>

View File

@@ -44,7 +44,7 @@ import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
import MovieDetailsLinks from './MovieDetailsLinks';
import MovieReleaseDatesConnector from './MovieReleaseDatesConnector';
import MovieReleaseDates from './MovieReleaseDates';
import MovieStatusLabel from './MovieStatusLabel';
import MovieTagsConnector from './MovieTagsConnector';
import MovieTitlesTable from './Titles/MovieTitlesTable';
@@ -433,7 +433,7 @@ class MovieDetails extends Component {
}
title={translate('ReleaseDates')}
body={
<MovieReleaseDatesConnector
<MovieReleaseDates
inCinemas={inCinemas}
physicalRelease={physicalRelease}
digitalRelease={digitalRelease}

View File

@@ -1,67 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import styles from './MovieReleaseDates.css';
function MovieReleaseDates(props) {
const {
showRelativeDates,
shortDateFormat,
timeFormat,
inCinemas,
physicalRelease,
digitalRelease
} = props;
return (
<div>
{
!!inCinemas &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.IN_CINEMAS}
/>
</div>
{getRelativeDate(inCinemas, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
{
!!digitalRelease &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.MOVIE_FILE}
/>
</div>
{getRelativeDate(digitalRelease, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
{
!!physicalRelease &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.DISC}
/>
</div>
{getRelativeDate(physicalRelease, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
</div>
);
}
MovieReleaseDates.propTypes = {
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
inCinemas: PropTypes.string,
physicalRelease: PropTypes.string,
digitalRelease: PropTypes.string
};
export default MovieReleaseDates;

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { useSelector } from 'react-redux';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';
import styles from './MovieReleaseDates.css';
interface MovieReleaseDatesProps {
inCinemas: string;
physicalRelease: string;
digitalRelease: string;
}
function MovieReleaseDates(props: MovieReleaseDatesProps) {
const { inCinemas, physicalRelease, digitalRelease } = props;
const { showRelativeDates, shortDateFormat, timeFormat } = useSelector(
createUISettingsSelector()
);
return (
<div>
{inCinemas ? (
<div title={translate('InCinemas')}>
<div className={styles.dateIcon}>
<Icon name={icons.IN_CINEMAS} />
</div>
{getRelativeDate(inCinemas, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: false,
})}
</div>
) : null}
{digitalRelease ? (
<div title={translate('DigitalRelease')}>
<div className={styles.dateIcon}>
<Icon name={icons.MOVIE_FILE} />
</div>
{getRelativeDate(digitalRelease, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: false,
})}
</div>
) : null}
{physicalRelease ? (
<div title={translate('PhysicalRelease')}>
<div className={styles.dateIcon}>
<Icon name={icons.DISC} />
</div>
{getRelativeDate(
physicalRelease,
shortDateFormat,
showRelativeDates,
{ timeFormat, timeForToday: false }
)}
</div>
) : null}
</div>
);
}
export default MovieReleaseDates;

View File

@@ -1,20 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import MovieReleaseDates from './MovieReleaseDates';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
export default connect(createMapStateToProps, null)(MovieReleaseDates);

View File

@@ -100,6 +100,15 @@ function MovieIndexSortMenu(props: MovieIndexSortMenuProps) {
{translate('DigitalRelease')}
</SortMenuItem>
<SortMenuItem
name="releaseDate"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('ReleaseDates')}
</SortMenuItem>
<SortMenuItem
name="tmdbRating"
sortKey={sortKey}

View File

@@ -80,8 +80,12 @@ function DownloadClientOptions(props) {
legend={translate('FailedDownloadHandling')}
>
<Form>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('Redownload')}</FormLabel>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailed')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -91,7 +95,28 @@ function DownloadClientOptions(props) {
{...settings.autoRedownloadFailed}
/>
</FormGroup>
{
settings.autoRedownloadFailed.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailedFromInteractiveSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="autoRedownloadFailedFromInteractiveSearch"
helpText={translate('AutoRedownloadFailedFromInteractiveSearchHelpText')}
onChange={onInputChange}
{...settings.autoRedownloadFailedFromInteractiveSearch}
/>
</FormGroup> :
null
}
</Form>
<Alert kind={kinds.INFO}>
{translate('RemoveDownloadsAlert')}
</Alert>

View File

@@ -1,4 +1,5 @@
import _ from 'lodash';
import moment from 'moment';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
@@ -240,16 +241,55 @@ export const sortPredicates = {
return item.year || undefined;
},
inCinemas: function(item) {
return item.inCinemas || '';
inCinemas: function(item, direction) {
if (item.inCinemas) {
return moment(item.inCinemas).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
},
physicalRelease: function(item) {
return item.physicalRelease || '';
physicalRelease: function(item, direction) {
if (item.physicalRelease) {
return moment(item.physicalRelease).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
},
digitalRelease: function(item) {
return item.digitalRelease || '';
digitalRelease: function(item, direction) {
if (item.digitalRelease) {
return moment(item.digitalRelease).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
},
releaseDate: function(item, direction) {
const { inCinemas, digitalRelease, physicalRelease } = item;
const releaseDate = digitalRelease || physicalRelease || inCinemas;
if (releaseDate) {
return moment(releaseDate).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
}
};

View File

@@ -1,17 +0,0 @@
import { createSelector } from 'reselect';
function createCollectionSelector() {
return createSelector(
(state, { collectionId }) => collectionId,
(state) => state.movieCollections.itemMap,
(state) => state.movieCollections.items,
(collectionId, itemMap, allCollections) => {
if (allCollections && itemMap && collectionId in itemMap) {
return allCollections[itemMap[collectionId]];
}
return undefined;
}
);
}
export default createCollectionSelector;

View File

@@ -0,0 +1,17 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createCollectionSelector() {
return createSelector(
(_: AppState, { collectionId }: { collectionId: number }) => collectionId,
(state: AppState) => state.movieCollections.itemMap,
(state: AppState) => state.movieCollections.items,
(collectionId, itemMap, allCollections) => {
return allCollections && itemMap && collectionId in itemMap
? allCollections[itemMap[collectionId]]
: undefined;
}
);
}
export default createCollectionSelector;

View File

@@ -72,6 +72,7 @@ function getInternalLink(source) {
function getTestLink(source, props) {
switch (source) {
case 'IndexerStatusCheck':
case 'IndexerLongTermStatusCheck':
return (
<SpinnerIconButton
name={icons.TEST}

View File

@@ -73,15 +73,15 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// Notifiarr
[TestCase(@"https://xxx.yyy/api/v1/notification/radarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Path
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
{
public static class NzbDroneLogger
{
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.fff}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private static bool _isConfigured;

View File

@@ -72,11 +72,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
}
[TestCase("How the Earth Was Made S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")]
[TestCase("The Universe S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")]
[TestCase("HELL ON WHEELS S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")]
[TestCase("Game.of.Thrones.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")]
[TestCase("Game of Thrones S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Series Title S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")]
[TestCase("Series Title S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")]
[TestCase("SERIES TITLE S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")]
[TestCase("Series.Title.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")]
[TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Someone.the.Entertainer.Presents.S01.NTSC.3xDVD9.MPEG-2.DD2.0")]
[TestCase("Series.Title.S00.The.Christmas.Special.2011.PAL.DVD5.DD2.0")]
[TestCase("Series.of.Desire.2000.S1_D01.NTSC.DVD5")]
public void should_return_false_if_matches_disc_format(string title)
{
_remoteMovie.Release.Title = title;

View File

@@ -364,6 +364,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("German.Only.Movie.2021.French.1080p.BluRay.AVC-UNTAVC")]
[TestCase("Movie.Title.2008.US.Directors.Cut.UHD.BD66.Blu-ray")]
[TestCase("Movie.2009.Blu.ray.AVC.DTS.HD.MA.5.1")]
[TestCase("[BD]Movie.Title.2008.2023.1080p.COMPLETE.BLURAY-RlsGrp")]
public void should_parse_brdisk_1080p_quality(string title)
{
ParseAndVerifyQuality(title, QualitySource.BLURAY, false, Resolution.R1080p, Modifier.BRDISK);

View File

@@ -66,7 +66,8 @@ namespace NzbDrone.Core.Annotations
OAuth,
Device,
TagSelect,
RootFolder
RootFolder,
QualityProfile
}
public enum HiddenType

View File

@@ -0,0 +1,36 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.AutoTagging.Specifications
{
public class QualityProfileSpecificationValidator : AbstractValidator<QualityProfileSpecification>
{
public QualityProfileSpecificationValidator()
{
RuleFor(c => c.Value).GreaterThan(0);
}
}
public class QualityProfileSpecification : AutoTaggingSpecificationBase
{
private static readonly QualityProfileSpecificationValidator Validator = new ();
public override int Order => 1;
public override string ImplementationName => "Quality Profile";
[FieldDefinition(1, Label = "Quality Profile", Type = FieldType.QualityProfile)]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
{
return Value == movie.QualityProfileId;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -190,6 +190,13 @@ namespace NzbDrone.Core.Configuration
set { SetValue("AutoRedownloadFailed", value); }
}
public bool AutoRedownloadFailedFromInteractiveSearch
{
get { return GetValueBoolean("AutoRedownloadFailedFromInteractiveSearch", true); }
set { SetValue("AutoRedownloadFailedFromInteractiveSearch", value); }
}
public bool CreateEmptyMovieFolders
{
get { return GetValueBoolean("CreateEmptyMovieFolders", false); }

View File

@@ -22,6 +22,7 @@ namespace NzbDrone.Core.Configuration
bool EnableCompletedDownloadHandling { get; set; }
bool AutoRedownloadFailed { get; set; }
bool AutoRedownloadFailedFromInteractiveSearch { get; set; }
// Media Management
bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; }

View File

@@ -12,7 +12,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
private static readonly Regex[] DiscRegex = new[]
{
new Regex(@"(?:dis[ck])(?:[-_. ]\d+[-_. ])(?:(?:(?:480|720|1080|2160)[ip]|)[-_. ])?(?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:\d?x?M?DVD-?[R59])", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly string[] _dvdContainerTypes = new[] { "vob", "iso" };
@@ -39,8 +40,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
if (regex.IsMatch(subject.Release.Title))
{
_logger.Debug("Release contains raw Bluray, rejecting.");
return Decision.Reject("Raw Bluray release");
_logger.Debug("Release contains raw Bluray/DVD, rejecting.");
return Decision.Reject("Raw Bluray/DVD release");
}
}

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download
@@ -23,5 +24,6 @@ namespace NzbDrone.Core.Download
public TrackedDownload TrackedDownload { get; set; }
public List<Language> Languages { get; set; }
public bool SkipRedownload { get; set; }
public ReleaseSourceType ReleaseSource { get; set; }
}
}

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
@@ -128,6 +130,7 @@ namespace NzbDrone.Core.Download
private void PublishDownloadFailedEvent(List<MovieHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{
var historyItem = historyItems.First();
Enum.TryParse(historyItem.Data.GetValueOrDefault(MovieHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
var downloadFailedEvent = new DownloadFailedEvent
{
@@ -140,7 +143,8 @@ namespace NzbDrone.Core.Download
Data = historyItem.Data,
TrackedDownload = trackedDownload,
Languages = historyItem.Languages,
SkipRedownload = skipRedownload
SkipRedownload = skipRedownload,
ReleaseSource = releaseSource
};
_eventAggregator.PublishEvent(downloadFailedEvent);

View File

@@ -5,6 +5,7 @@ using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Messaging;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
@@ -38,6 +39,12 @@ namespace NzbDrone.Core.Download
return;
}
if (message.ReleaseSource == ReleaseSourceType.InteractiveSearch && !_configService.AutoRedownloadFailedFromInteractiveSearch)
{
_logger.Debug("Auto redownloading failed movies from interactive search is disabled");
return;
}
if (message.MovieId != 0)
{
_logger.Debug("Failed download contains a movie, searching again.");

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Messaging;
@@ -28,6 +29,7 @@ namespace NzbDrone.Core.HealthCheck
private readonly IProvideHealthCheck[] _scheduledHealthChecks;
private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
private readonly ICached<HealthCheck> _healthCheckResults;
private readonly HashSet<IProvideHealthCheck> _pendingHealthChecks;
@@ -40,10 +42,12 @@ namespace NzbDrone.Core.HealthCheck
IEventAggregator eventAggregator,
ICacheManager cacheManager,
IDebounceManager debounceManager,
IRuntimeInfo runtimeInfo)
IRuntimeInfo runtimeInfo,
Logger logger)
{
_healthChecks = healthChecks.ToArray();
_eventAggregator = eventAggregator;
_logger = logger;
_healthCheckResults = cacheManager.GetCache<HealthCheck>(GetType());
_pendingHealthChecks = new HashSet<IProvideHealthCheck>();
@@ -88,7 +92,14 @@ namespace NzbDrone.Core.HealthCheck
try
{
var results = healthChecks.Select(c => c.Check())
var results = healthChecks.Select(c =>
{
_logger.Trace("Check health -> {0}", c.GetType().Name);
var result = c.Check();
_logger.Trace("Check health <- {0}", c.GetType().Name);
return result;
})
.ToList();
foreach (var result in results)

View File

@@ -43,9 +43,9 @@ namespace NzbDrone.Core.ImportLists
private void SyncAll()
{
if (_importListFactory.Enabled().Where(a => ((ImportListDefinition)a.Definition).EnableAuto).Empty())
if (_importListFactory.Enabled().Empty())
{
_logger.Debug("No import lists with automatic add enabled, skipping sync and cleaning");
_logger.Debug("No enabled import lists, skipping sync and cleaning");
return;
}

View File

@@ -147,7 +147,6 @@
"RefreshInformationAndScanDisk": "تحديث المعلومات ومسح القرص",
"RefreshAndScan": "التحديث والمسح الضوئي",
"Refresh": "تحديث",
"Redownload": "إعادة التنزيل",
"RecyclingBinCleanup": "تنظيف سلة إعادة التدوير",
"RecyclingBin": "صندوق إعادة التدوير",
"RecycleBinHelpText": "ستنتقل ملفات الأفلام إلى هنا عند حذفها بدلاً من حذفها نهائيًا",

View File

@@ -23,7 +23,6 @@
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
"Reason": "Причина",
"RecyclingBinCleanup": "Почистване на кошчето за рециклиране",
"Redownload": "Презареждане",
"Refresh": "Обнови",
"RefreshMovie": "Опресняване на филма",
"RegularExpressionsCanBeTested": "Регулярните изрази могат да бъдат тествани ",

View File

@@ -730,7 +730,6 @@
"RecycleBinCleanupDaysHelpTextWarning": "Els fitxers de la paperera de reciclatge més antics que el nombre de dies seleccionat es netejaran automàticament",
"RecycleBinHelpText": "Els fitxers de pel·lícula aniran aquí quan se suprimeixin en lloc de suprimir-se permanentment",
"RecyclingBinCleanup": "Neteja de la paperera de reciclatge",
"Redownload": "Torna a baixar",
"RefreshCollections": "Actualitza col·leccions",
"RefreshInformationAndScanDisk": "Actualitza la informació i escaneja el disc",
"RefreshLists": "Actualitza llistes",

View File

@@ -315,7 +315,6 @@
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
"Reason": "Důvod",
"RecyclingBinCleanup": "Recyklace koše Vyčištění",
"Redownload": "Znovu stáhnout",
"Refresh": "Obnovit",
"RefreshMovie": "Obnovit film",
"RegularExpressionsCanBeTested": "Regulární výrazy lze testovat ",

View File

@@ -313,7 +313,6 @@
"ReadTheWikiForMoreInformation": "Læs Wiki for mere information",
"Reason": "Grund",
"RecyclingBinCleanup": "Oprydning af papirkurven",
"Redownload": "Genindlæs",
"Refresh": "Opdater",
"RefreshMovie": "Opdater film",
"RegularExpressionsCanBeTested": "Regulære udtryk kan testes ",

View File

@@ -461,7 +461,6 @@
"RecycleBinHelpText": "Gelöschte Filmdateien werden hierher verschoben anstatt sie direkt endgültig zu löschen",
"RecyclingBin": "Papierkorb",
"RecyclingBinCleanup": "Papierkorb aufräumen",
"Redownload": "Nochmal herunterladen",
"RefreshInformationAndScanDisk": "Metadaten aktualisieren und Festplatte scannen",
"RefreshMovie": "Film aktualisieren",
"ReleaseRejected": "Release abgelehnt",
@@ -960,7 +959,7 @@
"AddRootFolder": "Stammordner hinzufügen",
"AddQualityProfile": "Qualitätsprofil hinzufügen",
"AddedToDownloadQueue": "Zur Downloadwarteschlange hinzugefügt",
"AddDownloadClient": "Zum Downloader hinzufügen",
"AddDownloadClient": "Downloadmanager hinzufügen",
"AddCustomFormat": "Eigenes Format hinzufügen",
"AddDelayProfile": "Verzögerungsprofil hinzufügen",
"Add": "Hinzufügen",
@@ -1170,12 +1169,14 @@
"ApplyTagsHelpTextHowToApplyImportLists": "Wie werden Tags zu ausgewählten Filmen zugeteilt",
"DeleteRootFolderMessageText": "Indexer '{0}' wirklich löschen?",
"DeleteRootFolder": "Stammordner löschen",
"AddConnection": "Sammlung bearbeiten",
"AddConnection": "Verbindung hinzufügen",
"BypassDelayIfAboveCustomFormatScoreMinimumScore": "Minimum der eigenen Formate Bewertungspunkte",
"NotificationStatusAllClientHealthCheckMessage": "Wegen Fehlern sind keine Applikationen verfügbar",
"NotificationStatusSingleClientHealthCheckMessage": "Applikationen wegen folgender Fehler nicht verfügbar: {0}",
"RemoveFromDownloadClientHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
"DownloadClientsLoadError": "Downloader konnten nicht geladen werden",
"IMDbId": "TMDb ID",
"DisabledForLocalAddresses": "Für Lokale Adressen deaktivieren"
"DisabledForLocalAddresses": "Für Lokale Adressen deaktivieren",
"AddCondition": "Bedingung hinzufügen",
"AddAutoTag": "Automatisches Tag hinzufügen"
}

View File

@@ -197,7 +197,6 @@
"AutomaticSearch": "Αυτόματη αναζήτηση",
"ChmodGroup": "Ομάδα chmod",
"Queued": "Σε ουρά",
"Redownload": "Κατεβάστε ξανά",
"Refresh": "Φρεσκάρω",
"RelativePath": "Σχετική διαδρομή",
"Released": "Κυκλοφόρησε",

View File

@@ -89,6 +89,9 @@
"AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username",
"AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.",
"Auto": "Auto",
"AutoRedownloadFailed": "Redownload Failed",
"AutoRedownloadFailedFromInteractiveSearch": "Redownload Failed from Interactive Search",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automatically search for and attempt to download a different release when failed release was grabbed from interactive search",
"AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release",
"AutoTagging": "Auto Tagging",
"AutoTaggingNegateHelpText": "If checked, the auto tagging rule will not apply if this {0} condition matches.",
@@ -908,7 +911,6 @@
"RecyclingBin": "Recycling Bin",
"RecyclingBinCleanup": "Recycling Bin Cleanup",
"Reddit": "Reddit",
"Redownload": "Redownload",
"Refresh": "Refresh",
"RefreshAndScan": "Refresh & Scan",
"RefreshCollections": "Refresh Collections",

View File

@@ -479,7 +479,6 @@
"ReleaseDates": "Fechas de Estreno",
"RefreshMovie": "Actualizar película",
"RefreshInformationAndScanDisk": "Actualizar la información al escanear el disco",
"Redownload": "Volver a descargar",
"RecyclingBinCleanup": "Limpieza de Papelera de Reciclaje",
"RecyclingBin": "Papelera de Reciclaje",
"RecycleBinHelpText": "Los archivos iran aquí una vez se hayan borrado en vez de ser borrados permanentemente",

View File

@@ -324,7 +324,6 @@
"AlternativeTitle": "Vaihtoehtoinen nimi",
"Age": "Ikä",
"RecyclingBinCleanup": "Roskakorin tyhjennys",
"Redownload": "Lataa uudelleen",
"RefreshMovie": "Päivitä elokuva",
"RejectionCount": "Hylkäämisluku",
"RelativePath": "Suhteellinen polku",

View File

@@ -685,7 +685,6 @@
"RegularExpressionsCanBeTested": "Les expressions régulières peuvent être testées ",
"RefreshMovie": "Actualiser le film",
"RefreshInformationAndScanDisk": "Actualiser les informations et analyser le disque",
"Redownload": "Télécharger à nouveau",
"RecyclingBinCleanup": "Nettoyage de la Corbeille",
"RecyclingBin": "Corbeille",
"RecycleBinHelpText": "Les fichiers vidéo iront ici lorsqu'ils seront supprimés au lieu d'être supprimés définitivement",
@@ -932,7 +931,7 @@
"Required": "Obligatoire",
"RestartReloadNote": "Remarque : Radarr redémarrera et rechargera automatiquement l'interface utilisateur pendant le processus de restauration.",
"RSS": "RSS",
"Score": "But",
"Score": "Score",
"Script": "Scénario",
"SearchCutoffUnmet": "Limite de recherche non satisfaite",
"SearchMissing": "Recherche manquante",
@@ -1243,5 +1242,30 @@
"TablePageSizeHelpText": "Nombre d'éléments à afficher sur chaque page",
"UnknownEventTooltip": "Événement inconnu",
"AppUpdated": "{appName} mis à jour",
"AppUpdatedVersion": "{appName} a été mis à jour vers la version `{version}`, pour profiter des derniers changements, vous devrez relancer {appName}"
"AppUpdatedVersion": "{appName} a été mis à jour vers la version `{version}`, pour profiter des derniers changements, vous devrez relancer {appName}",
"GrabId": "ID du grab",
"InteractiveImportNoMovie": "Une langue doit être choisie pour chacun des fichiers sélectionnés",
"InteractiveImportNoQuality": "Une langue doit être choisie pour chacun des fichiers sélectionnés",
"UnableToLoadAutoTagging": "Impossible de charger le balisage automatique",
"DelayingDownloadUntil": "Retarder le téléchargement jusqu'au {0} à {1}",
"DeleteSelectedMovieFilesHelpText": "Voulez-vous vraiment supprimer les fichiers vidéo sélectionnés ?",
"DeletedReasonMissingFromDisk": "Readarr n'a pas pu trouver le fichier sur le disque, il a donc été supprimé dans la base de données",
"DeletedReasonUpgrade": "Le fichier à été supprimé pour importer une version supérieure",
"OrganizeLoadError": "Erreur lors du chargement des aperçus",
"EditImportListImplementation": "Ajouter une liste d'importation - {implementationName}",
"EditIndexerImplementation": "Ajouter une condition - {implementationName}",
"MovieFileDeleted": "À la suppression d'un fichier vidéo",
"MovieFileDeletedTooltip": "À la suppression d'un fichier vidéo",
"BlocklistReleaseHelpText": "Empêche Lidarr de récupérer automatiquement cette version",
"InteractiveImportLoadError": "Impossible de charger les éléments d'importation manuelle",
"MovieSearchResultsLoadError": "Impossible de charger les résultats de cette recherche de films. Réessayez plus tard",
"QueueLoadError": "Erreur lors du chargement de la file",
"RemoveSelectedBlocklistMessageText": "Êtes-vous sûr de vouloir supprimer les films sélectionnés de la liste noire ?",
"RetryingDownloadOn": "Retarder le téléchargement jusqu'au {0} à {1}",
"ShowUnknownMovieItemsHelpText": "Afficher les éléments sans film dans la file d'attente. Cela peut inclure des films supprimés ou tout autre élément de la catégorie de Lidarr",
"TablePageSize": "Pagination",
"EditConditionImplementation": "Ajouter une connexion - {implementationName}",
"EditConnectionImplementation": "Ajouter une condition - {implementationName}",
"ReleaseProfiles": "profil de version",
"ReleaseProfilesLoadError": "Impossible de charger les profils de délai"
}

View File

@@ -227,7 +227,6 @@
"ReadTheWikiForMoreInformation": "קרא את הוויקי למידע נוסף",
"Reason": "סיבה",
"RecyclingBinCleanup": "ניקוי סל המיחזור",
"Redownload": "הורד מחדש",
"Refresh": "לְרַעֲנֵן",
"RefreshMovie": "רענן סרט",
"RejectionCount": "ספירת דחייה",

View File

@@ -442,7 +442,6 @@
"Presets": "प्रीसेट",
"Profiles": "प्रोफाइल",
"Reason": "कारण",
"Redownload": "redownload",
"Refresh": "ताज़ा करना",
"RefreshMovie": "फिल्म को रिफ्रेश करें",
"Reset": "रीसेट",

View File

@@ -392,7 +392,6 @@
"RefreshInformationAndScanDisk": "Információk frissítése és lemez átvizsgálása",
"RefreshAndScan": "Frissítés & Keresés",
"Refresh": "Frissítés",
"Redownload": "Letöltés újra",
"RecyclingBinCleanup": "Lomtár kiürítése",
"RecyclingBin": "Lomtár",
"RecycleBinHelpText": "A filmfájlok végleges törlés helyett ide kerülnek törléskor",

View File

@@ -289,7 +289,6 @@
"RadarrCalendarFeed": "Radarr dagatalstraumur",
"ReadTheWikiForMoreInformation": "Lestu Wiki fyrir frekari upplýsingar",
"RecyclingBinCleanup": "Hreinsun ruslakörfu",
"Redownload": "Endurhlaða",
"Refresh": "Hressa",
"RefreshMovie": "Hressa kvikmynd",
"RelativePath": "Hlutfallsleg leið",

View File

@@ -415,7 +415,6 @@
"RegularExpressionsCanBeTested": "Le espressioni regolari possono essere testate ",
"RefreshMovie": "Aggiorna il Film",
"RefreshInformationAndScanDisk": "Aggiorna le informazioni e scansiona il disco",
"Redownload": "Riscarica",
"RecyclingBinCleanup": "Pulizia del cestino",
"RecyclingBin": "Cestino",
"RecycleBinHelpText": "I file dei film andranno qui quando cancellati invece che venire eliminati definitivamente",

View File

@@ -268,7 +268,6 @@
"ReadTheWikiForMoreInformation": "詳細については、Wikiをお読みください",
"Reason": "理由",
"RecyclingBinCleanup": "ごみ箱のクリーンアップ",
"Redownload": "再ダウンロード",
"Refresh": "更新",
"RefreshMovie": "映画を更新する",
"RegularExpressionsCanBeTested": "正規表現をテストできます ",

View File

@@ -270,7 +270,6 @@
"ReadTheWikiForMoreInformation": "자세한 내용은 Wiki를 참조하십시오.",
"Reason": "이유",
"RecyclingBinCleanup": "재활용 빈 정리",
"Redownload": "다시 다운로드",
"Refresh": "새롭게 하다",
"RefreshMovie": "영화 새로 고침",
"RegularExpressionsCanBeTested": "정규식을 테스트 할 수 있습니다. ",

View File

@@ -512,7 +512,6 @@
"RecycleBinCleanupDaysHelpTextWarning": "Bestanden in de prullenbak ouder dan het geselecteerde aantal dagen zullen automatisch opgeschoond worden",
"RecyclingBin": "Prullenbak",
"RecyclingBinCleanup": "Prullenbak Opruimen",
"Redownload": "Opnieuw downloaden",
"RefreshInformationAndScanDisk": "Informatie vernieuwen en schijf herscannen",
"RefreshMovie": "Film vernieuwen",
"ProxyType": "Proxy Type",

View File

@@ -272,7 +272,6 @@
"RadarrCalendarFeed": "Kanał kalendarza radarowego",
"Reason": "Powód",
"RecyclingBinCleanup": "Czyszczenie kosza na śmieci",
"Redownload": "Pobierz ponownie",
"Refresh": "Odświeżać",
"RefreshMovie": "Odśwież film",
"RegularExpressionsCanBeTested": "Można testować wyrażenia regularne ",

View File

@@ -533,7 +533,6 @@
"Remove": "Remover",
"RefreshMovie": "Atualizar filme",
"RefreshInformationAndScanDisk": "Atualizar informações e analisar o disco",
"Redownload": "Transferir novamente",
"RecyclingBinCleanup": "Limpeza da reciclagem",
"RecyclingBin": "Reciclagem",
"Reason": "Razão",

View File

@@ -546,7 +546,6 @@
"RetentionHelpText": "Somente Usenet: defina como zero para definir a retenção ilimitada",
"RestartReloadNote": "Observação: o Radarr reiniciará automaticamente e recarregará a interface durante o processo de restauração.",
"RejectionCount": "Número de rejeição",
"Redownload": "Baixar novamente",
"UpgradeAllowedHelpText": "Se desabilitada, as qualidades não serão atualizadas",
"UpgradesAllowed": "Atualizações Permitidas",
"UpgradeUntilCustomFormatScore": "Atualizar até pontuação de formato personalizado",
@@ -1261,7 +1260,7 @@
"True": "Verdadeiro",
"HealthMessagesInfoBox": "Você pode encontrar mais informações sobre a causa dessas mensagens de verificação de integridade clicando no link da wiki (ícone do livro) no final da linha ou verificando seus [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, você pode entrar em contato com nosso suporte, nos links abaixo.",
"DefaultNameCopiedProfile": "{name} - Cópia",
"InvalidUILanguage": "Sua IU está definida com um idioma inválido, corrija-a e salve suas configurações",
"InvalidUILanguage": "Sua IU está configurada com um idioma inválido, corrija-a e salve suas configurações",
"AuthenticationMethod": "Método de Autenticação",
"AuthenticationMethodHelpTextWarning": "Selecione um método de autenticação válido",
"AuthenticationRequiredPasswordHelpTextWarning": "Insira uma nova senha",

View File

@@ -533,7 +533,6 @@
"Local": "Local",
"MovieYear": "Anul filmului",
"RecyclingBinCleanup": "Curățarea coșului de reciclare",
"Redownload": "Redescărcați",
"RefreshMovie": "Reîmprospătați filmul",
"MovieYearHelpText": "Anul filmului de exclus",
"MustContain": "Trebuie sa contina",

View File

@@ -367,7 +367,6 @@
"RefreshInformationAndScanDisk": "Обновить информацию и просканировать диск",
"RefreshAndScan": "Обновить & сканировать",
"Refresh": "Обновить",
"Redownload": "Перезакачать",
"RecyclingBinCleanup": "Очистка мусорной корзины",
"RecyclingBin": "Мусорная корзина",
"RecycleBinHelpText": "Файлы фильмов будут попадать сюда при удалении",

View File

@@ -437,7 +437,6 @@
"RemoveMovieAndKeepFiles": "Ta bort film och behåll filerna",
"RemoveMovieAndDeleteFiles": "Ta bort film och radera filerna",
"RemoveFilter": "Ta bort filter",
"Redownload": "Ladda ned igen",
"RefreshLists": "Uppdatera listor",
"RefreshInformationAndScanDisk": "Uppdatera information och skanna disken",
"RecentFolders": "Senaste mappar",

View File

@@ -387,7 +387,6 @@
"QueueIsEmpty": "คิวว่างเปล่า",
"RadarrCalendarFeed": "ฟีดปฏิทิน Radarr",
"ReadTheWikiForMoreInformation": "อ่าน Wiki สำหรับข้อมูลเพิ่มเติม",
"Redownload": "ดาวน์โหลดอีกครั้ง",
"Refresh": "รีเฟรช",
"RefreshMovie": "รีเฟรชภาพยนตร์",
"RegularExpressionsCanBeTested": "นิพจน์ทั่วไปสามารถทดสอบได้ ",

View File

@@ -591,7 +591,6 @@
"PtpOldSettingsCheckMessage": "Aşağıdaki PassThePopcorn dizinleyicilerinin ayarları kullanımdan kaldırıldı ve güncellenmeleri gerekiyor: {0}",
"Queued": "Sıraya alındı",
"RecyclingBinCleanup": "Geri Dönüşüm Kutusu Temizleme",
"Redownload": "Yeniden indir",
"RefreshMovie": "Filmi yenile",
"RejectionCount": "Reddetme Sayısı",
"RelativePath": "Göreceli yol",

View File

@@ -777,7 +777,6 @@
"ReadTheWikiForMoreInformation": "Читайте Wiki для отримання додаткової інформації",
"RecycleBinCleanupDaysHelpTextWarning": "Файли в кошику, старші за вибрану кількість днів, будуть очищені автоматично",
"RecycleBinHelpText": "Файли фільмів потраплять сюди після видалення, а не назавжди",
"Redownload": "Повторне завантаження",
"RejectionCount": "Кількість відмов",
"ReleaseBranchCheckOfficialBranchMessage": "Гілка {0} не є дійсною гілкою випуску Radarr, ви не отримуватимете оновлення",
"ReleaseStatus": "Статус випуску",

View File

@@ -343,7 +343,6 @@
"RadarrCalendarFeed": "Nguồn cấp dữ liệu lịch Radarr",
"ReadTheWikiForMoreInformation": "Đọc Wiki để biết thêm thông tin",
"AptUpdater": "Sử dụng apt để cài đặt bản cập nhật",
"Redownload": "Tải lại",
"ReleasedMsg": "Phim được phát hành",
"RecycleBinCleanupDaysHelpTextWarning": "Các tệp trong thùng rác cũ hơn số ngày đã chọn sẽ tự động được dọn dẹp",
"AuthBasic": "Cơ bản (Cửa sổ bật lên trình duyệt)",

View File

@@ -76,7 +76,7 @@
"BranchUpdate": "更新Radarr的分支",
"Branch": "分支",
"Calendar": "日历",
"BackupRetentionHelpText": "早于保留周期的自动备份将被自动清",
"BackupRetentionHelpText": "超过保留期限的自动备份将被自动清",
"BackupNow": "马上备份",
"BackupIntervalHelpText": "自动备份时间间隔",
"BackupFolderHelpText": "相对路径将在Radarr的AppData目录下",
@@ -98,7 +98,7 @@
"AddNewMovie": "添加新电影",
"AddRootFolder": "添加根目录",
"AddQualityProfile": "添加质量配置",
"AddNewTmdbIdMessage": "您可以使用电影TMDbId搜索。例如 'tmdb:71663'",
"AddNewTmdbIdMessage": "您可以使用电影TMDb Id进行搜索。例如 'tmdb:71663'",
"UpdateAll": "全部更新",
"AllMoviesHiddenDueToFilter": "根据应用的过滤项已隐藏全部的电影。",
"AllFiles": "全部文件",
@@ -699,7 +699,6 @@
"QualitySettings": "媒体质量设置",
"UseHardlinksInsteadOfCopy": "使用硬链接代替复制",
"RecycleBinHelpText": "影片文件会被移动到回收站以替代永久删除",
"Redownload": "重新下载",
"TableOptions": "表格选项",
"UpdateSelected": "更新已选",
"ShowUnknownMovieItems": "显示未知影片条目",
@@ -1213,7 +1212,7 @@
"BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "绕过首选协议延迟所需的最小自定义格式分数",
"ParseModalHelpTextDetails": "Radarr将尝试解析标题并向您显示有关它的详细信息",
"AppUpdated": "{appName} 升级",
"DelayingDownloadUntil": "将下载迟到 {date} 的 {time}",
"DelayingDownloadUntil": "将下载迟到 {date} 的 {time}",
"DeletedReasonManual": "文件已通过 UI 删除",
"DeletedReasonUpgrade": "升级时删除原文件",
"DownloadIgnored": "忽略下载",
@@ -1237,7 +1236,7 @@
"FullColorEventsHelpText": "改变样式,用状态颜色为整个事件着色,而不仅仅是左边缘。不适用于议程",
"HistoryLoadError": "无法加载历史记录",
"InfoUrl": "信息 URL",
"InvalidUILanguage": "您的UI设置为无效的语言,请纠正它并保存设置",
"InvalidUILanguage": "您的UI设置的语言无效,请纠正它并保存设置",
"LanguagesLoadError": "无法加载语言",
"MovieDownloadFailedTooltip": "电影下载失败",
"MovieDownloadIgnoredTooltip": "忽略电影下载",
@@ -1255,11 +1254,11 @@
"EditDownloadClientImplementation": "编辑下载客户端 - {implementationName}",
"EditIndexerImplementation": "编辑索引器 - {implementationName}",
"GrabId": "抓取ID",
"HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[logs]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。",
"HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[日志]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。",
"IMDbId": "IMDb Id",
"InteractiveImportNoMovie": "必须为每个选定的文件选择影片",
"MovieFileDeletedTooltip": "删除电影文件",
"MovieGrabbedHistoryTooltip": "从{indexer}抓取并发送到{downloadClient}的影片",
"MovieGrabbedHistoryTooltip": "从{indexer}获取电影并发送到{downloadClient}",
"IndexerDownloadClientHealthCheckMessage": "有无效下载客户端的索引器:{0}。",
"FullColorEvents": "全彩事件",
"InteractiveImportNoFilesFound": "在所选文件夹中没有找到视频文件",

View File

@@ -2,9 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies.Translations;
namespace NzbDrone.Core.Movies
{
@@ -45,37 +43,7 @@ namespace NzbDrone.Core.Movies
public List<MovieMetadata> GetMoviesWithCollections()
{
var movieDictionary = new Dictionary<int, MovieMetadata>();
var builder = new SqlBuilder(_database.DatabaseType)
.LeftJoin<MovieMetadata, MovieTranslation>((mm, t) => mm.Id == t.MovieMetadataId)
.Where<MovieMetadata>(x => x.CollectionTmdbId > 0);
_ = _database.QueryJoined<MovieMetadata, MovieTranslation>(
builder,
(metadata, translation) =>
{
if (!movieDictionary.TryGetValue(metadata.Id, out var movieEntry))
{
movieEntry = metadata;
movieDictionary.Add(movieEntry.Id, movieEntry);
}
if (translation != null)
{
movieEntry.Translations.Add(translation);
}
else
{
// Add a translation to avoid filename builder making another call thinking translations are not loaded
// Optimize this later by pulling translations with metadata always
movieEntry.Translations.Add(new MovieTranslation { Title = movieEntry.Title, Language = Language.English });
}
return movieEntry;
});
return movieDictionary.Values.ToList();
return Query(x => x.CollectionTmdbId > 0);
}
public List<MovieMetadata> GetMoviesByCollectionTmdbId(int collectionId)

View File

@@ -50,17 +50,17 @@ namespace NzbDrone.Core.Notifications.Notifiarr
switch ((int)responseCode)
{
case 401:
_logger.Error("HTTP 401 - API key is invalid");
_logger.Warn("HTTP 401 - API key is invalid");
throw new NotifiarrException("API key is invalid");
case 400:
// 400 responses shouldn't be treated as an actual error because it's a misconfiguration
// between Radarr and Notifiarr for a specific event, but shouldn't stop all events.
_logger.Error("HTTP 400 - Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
_logger.Warn("HTTP 400 - Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
break;
case 502:
case 503:
case 504:
_logger.Error("Unable to send notification. Service Unavailable");
_logger.Warn("Unable to send notification. Service Unavailable");
throw new NotifiarrException("Unable to send notification. Service Unavailable", ex);
case 520:
case 521:

View File

@@ -250,7 +250,7 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Movie TitleThe}"] = m => TitleThe(movie.Title);
tokenHandlers["{Movie TitleFirstCharacter}"] = m => TitleThe(GetLanguageTitle(movie, m.CustomFormat)).Substring(0, 1).FirstCharToUpper();
tokenHandlers["{Movie OriginalTitle}"] = m => movie.MovieMetadata.Value.OriginalTitle ?? string.Empty;
tokenHandlers["{Movie CleanOriginalTitle}"] = m => CleanTitle(movie.MovieMetadata.Value.OriginalTitle) ?? string.Empty;
tokenHandlers["{Movie CleanOriginalTitle}"] = m => CleanTitle(movie.MovieMetadata.Value.OriginalTitle ?? string.Empty);
tokenHandlers["{Movie Certification}"] = m => movie.MovieMetadata.Value.Certification ?? string.Empty;
tokenHandlers["{Movie Collection}"] = m => movie.MovieMetadata.Value.CollectionTitle ?? string.Empty;

View File

@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex MPEG2Regex = new (@"\b(?<mpeg2>MPEG[-_. ]?2)\b");
private static readonly Regex BRDISKRegex = new (@"^(?!.*\b((?<!HD[._ -]|HD)DVD|BDRip|720p|MKV|XviD|WMV|d3g|(BD)?REMUX|^(?=.*1080p)(?=.*HEVC)|[xh][-_. ]?26[45]|German.*[DM]L|((?<=\d{4}).*German.*([DM]L)?)(?=.*\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2)\b))\b)(((?=.*\b(Blu[-_. ]?ray|BD|HD[-_. ]?DVD)\b)(?=.*\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2|BDMV|ISO)\b))|^((?=.*\b(^((?=.*\b((.*_)?COMPLETE.*|Dis[ck])\b)(?=.*(Blu[-_. ]?ray|HD[-_. ]?DVD)))|3D[-_. ]?BD|BR[-_. ]?DISK|Full[-_. ]?Blu[-_. ]?ray|^((?=.*((BD|UHD)[-_. ]?(25|50|66|100|ISO)))))))).*",
private static readonly Regex BRDISKRegex = new (@"^(?!.*\b((?<!HD[._ -]|HD)DVD|BDRip|720p|MKV|XviD|WMV|d3g|(BD)?REMUX|^(?=.*1080p)(?=.*HEVC)|[xh][-_. ]?26[45]|German.*[DM]L|((?<=\d{4}).*German.*([DM]L)?)(?=.*\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2)\b))\b)(((?=.*\b(Blu[-_. ]?ray|BD|HD[-_. ]?DVD)\b)(?=.*\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2|BDMV|ISO)\b))|^((?=.*\b(((?=.*\b((.*_)?COMPLETE.*|Dis[ck])\b)(?=.*(Blu[-_. ]?ray|HD[-_. ]?DVD)))|3D[-_. ]?BD|BR[-_. ]?DISK|Full[-_. ]?Blu[-_. ]?ray|^((?=.*((BD|UHD)[-_. ]?(25|50|66|100|ISO)))))))).*",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ProperRegex = new (@"\b(?<proper>proper)\b",

View File

@@ -127,8 +127,16 @@ namespace NzbDrone.Core.RemotePathMappings
return remotePath;
}
var mappings = All();
if (mappings.Empty())
{
return remotePath;
}
_logger.Trace("Evaluating remote path remote mappings for match to host [{0}] and remote path [{1}]", host, remotePath.FullPath);
foreach (var mapping in All())
foreach (var mapping in mappings)
{
_logger.Trace("Checking configured remote path mapping: {0} - {1}", mapping.Host, mapping.RemotePath);
if (host.Equals(mapping.Host, StringComparison.InvariantCultureIgnoreCase) && new OsPath(mapping.RemotePath).Contains(remotePath))
@@ -150,8 +158,16 @@ namespace NzbDrone.Core.RemotePathMappings
return localPath;
}
var mappings = All();
if (mappings.Empty())
{
return localPath;
}
_logger.Trace("Evaluating remote path local mappings for match to host [{0}] and local path [{1}]", host, localPath.FullPath);
foreach (var mapping in All())
foreach (var mapping in mappings)
{
_logger.Trace("Checking configured remote path mapping {0} - {1}", mapping.Host, mapping.RemotePath);
if (host.Equals(mapping.Host, StringComparison.InvariantCultureIgnoreCase) && new OsPath(mapping.LocalPath).Contains(localPath))

View File

@@ -159,6 +159,8 @@ namespace NzbDrone.Host
{
{ apikeyQuery, Array.Empty<string>() }
});
c.DescribeAllParametersInCamelCase();
});
services

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Integration.Test.Client
{
// cache control header gets reordered on net core
var headers = response.Headers;
((string)headers.Single(c => c.Name == "Cache-Control").Value).Split(',').Select(x => x.Trim())
((string)headers.SingleOrDefault(c => c.Name == "Cache-Control")?.Value ?? string.Empty).Split(',').Select(x => x.Trim())
.Should().BeEquivalentTo("no-store, no-cache".Split(',').Select(x => x.Trim()));
headers.Single(c => c.Name == "Pragma").Value.Should().Be("no-cache");
headers.Single(c => c.Name == "Expires").Value.Should().Be("-1");
@@ -102,7 +102,7 @@ namespace NzbDrone.Integration.Test.Client
return Get<List<TResource>>(request);
}
public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, string filterValue = null)
public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, object filterValue = null)
{
var request = BuildRequest();
request.AddParameter("page", pageNumber);
@@ -112,8 +112,7 @@ namespace NzbDrone.Integration.Test.Client
if (filterKey != null && filterValue != null)
{
request.AddParameter("filterKey", filterKey);
request.AddParameter("filterValue", filterValue);
request.AddParameter(filterKey, filterValue);
}
return Get<PagingResource<TResource>>(request);

View File

@@ -25,9 +25,9 @@ namespace Radarr.Api.V3.Blocklist
[HttpGet]
[Produces("application/json")]
public PagingResource<BlocklistResource> GetBlocklist()
public PagingResource<BlocklistResource> GetBlocklist([FromQuery] PagingRequestResource paging)
{
var pagingResource = Request.ReadPagingResourceFromRequest<BlocklistResource>();
var pagingResource = new PagingResource<BlocklistResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, NzbDrone.Core.Blocklisting.Blocklist>("date", SortDirection.Descending);
return pagingSpec.ApplyToPage(_blocklistService.Paged, model => BlocklistResourceMapper.MapToResource(model, _formatCalculator));

View File

@@ -1,15 +1,17 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Languages;
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.Movies.Translations;
using NzbDrone.Core.Organizer;
using NzbDrone.SignalR;
using Radarr.Http;
@@ -27,28 +29,31 @@ namespace Radarr.Api.V3.Collections
private readonly IMovieCollectionService _collectionService;
private readonly IMovieService _movieService;
private readonly IMovieMetadataService _movieMetadataService;
private readonly IMovieTranslationService _movieTranslationService;
private readonly IConfigService _configService;
private readonly IBuildFileNames _fileNameBuilder;
private readonly INamingConfigService _namingService;
private readonly IManageCommandQueue _commandQueueManager;
private readonly Logger _logger;
public CollectionController(IBroadcastSignalRMessage signalRBroadcaster,
IMovieCollectionService collectionService,
IMovieService movieService,
IMovieMetadataService movieMetadataService,
IMovieTranslationService movieTranslationService,
IConfigService configService,
IBuildFileNames fileNameBuilder,
INamingConfigService namingService,
IManageCommandQueue commandQueueManager,
Logger logger)
IManageCommandQueue commandQueueManager)
: base(signalRBroadcaster)
{
_collectionService = collectionService;
_movieService = movieService;
_movieMetadataService = movieMetadataService;
_movieTranslationService = movieTranslationService;
_configService = configService;
_fileNameBuilder = fileNameBuilder;
_namingService = namingService;
_commandQueueManager = commandQueueManager;
_logger = logger;
}
protected override CollectionResource GetResourceById(int id)
@@ -75,8 +80,6 @@ namespace Radarr.Api.V3.Collections
collectionResources = MapToResource(_collectionService.GetAllCollections()).ToList();
}
_logger.Trace("Returning Collections");
return collectionResources;
}
@@ -140,24 +143,38 @@ namespace Radarr.Api.V3.Collections
{
// Avoid calling for naming spec on every movie in filenamebuilder
var namingConfig = _namingService.GetConfig();
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
var existingMoviesTmdbIds = _movieService.AllMovieWithCollectionsTmdbIds();
var configLanguage = (Language)_configService.MovieInfoLanguage;
var allCollectionMovies = _movieMetadataService.GetMoviesWithCollections()
.GroupBy(x => x.CollectionTmdbId)
.ToDictionary(x => x.Key, x => (IEnumerable<MovieMetadata>)x);
var translations = _movieTranslationService.GetAllTranslationsForLanguage(configLanguage);
var tdict = translations.ToDictionary(x => x.MovieMetadataId);
foreach (var collection in collections)
{
var resource = collection.ToResource();
foreach (var movie in collectionMovies.Where(m => m.CollectionTmdbId == collection.TmdbId))
allCollectionMovies.TryGetValue(collection.TmdbId, out var collectionMovies);
if (collectionMovies != null)
{
var movieResource = movie.ToResource();
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie }, namingConfig);
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
foreach (var movie in collectionMovies)
{
resource.MissingMovies++;
}
var translation = GetTranslationFromDict(tdict, movie, configLanguage);
resource.Movies.Add(movieResource);
var movieResource = movie.ToResource(translation);
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie }, namingConfig);
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
{
resource.MissingMovies++;
}
resource.Movies.Add(movieResource);
}
}
yield return resource;
@@ -169,10 +186,14 @@ namespace Radarr.Api.V3.Collections
var resource = collection.ToResource();
var existingMoviesTmdbIds = _movieService.AllMovieWithCollectionsTmdbIds();
var namingConfig = _namingService.GetConfig();
var configLanguage = (Language)_configService.MovieInfoLanguage;
foreach (var movie in _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId))
{
var movieResource = movie.ToResource();
var translations = _movieTranslationService.GetAllTranslationsForMovieMetadata(movie.Id);
var translation = GetMovieTranslation(translations, movie, configLanguage);
var movieResource = movie.ToResource(translation);
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie }, namingConfig);
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
@@ -186,6 +207,54 @@ namespace Radarr.Api.V3.Collections
return resource;
}
private MovieTranslation GetMovieTranslation(List<MovieTranslation> translations, MovieMetadata movieMetadata, Language configLanguage)
{
if (configLanguage == Language.Original)
{
return new MovieTranslation
{
Title = movieMetadata.OriginalTitle,
Overview = movieMetadata.Overview
};
}
var translation = translations.FirstOrDefault(t => t.Language == configLanguage && t.MovieMetadataId == movieMetadata.Id);
if (translation == null)
{
translation = new MovieTranslation
{
Title = movieMetadata.Title,
Language = Language.English
};
}
return translation;
}
private MovieTranslation GetTranslationFromDict(Dictionary<int, MovieTranslation> translations, MovieMetadata movieMetadata, Language configLanguage)
{
if (configLanguage == Language.Original)
{
return new MovieTranslation
{
Title = movieMetadata.OriginalTitle,
Overview = movieMetadata.Overview
};
}
if (!translations.TryGetValue(movieMetadata.Id, out var translation))
{
translation = new MovieTranslation
{
Title = movieMetadata.Title,
Language = Language.English
};
}
return translation;
}
[NonAction]
public void Handle(CollectionAddedEvent message)
{

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Translations;
namespace Radarr.Api.V3.Collections
{
@@ -11,6 +12,7 @@ namespace Radarr.Api.V3.Collections
public string Title { get; set; }
public string CleanTitle { get; set; }
public string SortTitle { get; set; }
public MovieStatusType Status { get; set; }
public string Overview { get; set; }
public int Runtime { get; set; }
public List<MediaCover> Images { get; set; }
@@ -22,18 +24,22 @@ namespace Radarr.Api.V3.Collections
public static class CollectionMovieResourceMapper
{
public static CollectionMovieResource ToResource(this MovieMetadata model)
public static CollectionMovieResource ToResource(this MovieMetadata model, MovieTranslation movieTranslation = null)
{
if (model == null)
{
return null;
}
var translatedTitle = movieTranslation?.Title ?? model.Title;
var translatedOverview = movieTranslation?.Overview ?? model.Overview;
return new CollectionMovieResource
{
TmdbId = model.TmdbId,
Title = model.Title,
Overview = model.Overview,
Title = translatedTitle,
Status = model.Status,
Overview = translatedOverview,
SortTitle = model.SortTitle,
Images = model.Images,
ImdbId = model.ImdbId,

View File

@@ -11,6 +11,7 @@ namespace Radarr.Api.V3.Config
public int CheckForFinishedDownloadInterval { get; set; }
public bool AutoRedownloadFailed { get; set; }
public bool AutoRedownloadFailedFromInteractiveSearch { get; set; }
}
public static class DownloadClientConfigResourceMapper
@@ -24,7 +25,8 @@ namespace Radarr.Api.V3.Config
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
CheckForFinishedDownloadInterval = model.CheckForFinishedDownloadInterval,
AutoRedownloadFailed = model.AutoRedownloadFailed
AutoRedownloadFailed = model.AutoRedownloadFailed,
AutoRedownloadFailedFromInteractiveSearch = model.AutoRedownloadFailedFromInteractiveSearch
};
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine.Specifications;
@@ -59,23 +60,20 @@ namespace Radarr.Api.V3.History
}
[HttpGet]
public PagingResource<HistoryResource> GetHistory(bool includeMovie)
[Produces("application/json")]
public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, bool includeMovie, int? eventType, string downloadId)
{
var pagingResource = Request.ReadPagingResourceFromRequest<HistoryResource>();
var pagingResource = new PagingResource<HistoryResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, MovieHistory>("date", SortDirection.Descending);
var eventTypeFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "eventType");
var downloadIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "downloadId");
if (eventTypeFilter != null)
if (eventType.HasValue)
{
var filterValue = (MovieHistoryEventType)Convert.ToInt32(eventTypeFilter.Value);
var filterValue = (MovieHistoryEventType)eventType.Value;
pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue);
}
if (downloadIdFilter != null)
if (downloadId.IsNotNullOrWhiteSpace())
{
var downloadId = downloadIdFilter.Value;
pagingSpec.FilterExpressions.Add(h => h.DownloadId == downloadId);
}

View File

@@ -28,7 +28,6 @@ namespace Radarr.Api.V3.ImportLists
private readonly IImportExclusionsService _importExclusionService;
private readonly INamingConfigService _namingService;
private readonly IMovieTranslationService _movieTranslationService;
private readonly IMapCoversToLocal _coverMapper;
private readonly IConfigService _configService;
public ImportListMoviesController(IMovieService movieService,
@@ -40,7 +39,6 @@ namespace Radarr.Api.V3.ImportLists
IImportExclusionsService importExclusionsService,
INamingConfigService namingService,
IMovieTranslationService movieTranslationService,
IMapCoversToLocal coverMapper,
IConfigService configService)
{
_movieService = movieService;
@@ -52,7 +50,6 @@ namespace Radarr.Api.V3.ImportLists
_importExclusionService = importExclusionsService;
_namingService = namingService;
_movieTranslationService = movieTranslationService;
_coverMapper = coverMapper;
_configService = configService;
}
@@ -118,7 +115,6 @@ namespace Radarr.Api.V3.ImportLists
foreach (var currentMovie in movies)
{
var resource = currentMovie.ToResource();
_coverMapper.ConvertToLocalUrls(0, resource.Images);
var poster = currentMovie.MovieMetadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
@@ -148,7 +144,6 @@ namespace Radarr.Api.V3.ImportLists
foreach (var currentMovie in movies)
{
var resource = currentMovie.ToResource();
_coverMapper.ConvertToLocalUrls(0, resource.Images);
var poster = currentMovie.MovieMetadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)

View File

@@ -1,5 +1,5 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Instrumentation;
using Radarr.Http;
using Radarr.Http.Extensions;
@@ -17,9 +17,10 @@ namespace Radarr.Api.V3.Logs
}
[HttpGet]
public PagingResource<LogResource> GetLogs()
[Produces("application/json")]
public PagingResource<LogResource> GetLogs([FromQuery] PagingRequestResource paging, string level)
{
var pagingResource = Request.ReadPagingResourceFromRequest<LogResource>();
var pagingResource = new PagingResource<LogResource>(paging);
var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>();
if (pageSpec.SortKey == "time")
@@ -27,11 +28,9 @@ namespace Radarr.Api.V3.Logs
pageSpec.SortKey = "id";
}
var levelFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "level");
if (levelFilter != null)
if (level.IsNotNullOrWhiteSpace())
{
switch (levelFilter.Value)
switch (level)
{
case "fatal":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal");

View File

@@ -129,9 +129,10 @@ namespace Radarr.Api.V3.Queue
}
[HttpGet]
public PagingResource<QueueResource> GetQueue(bool includeUnknownMovieItems = false, bool includeMovie = false)
[Produces("application/json")]
public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownMovieItems = false, bool includeMovie = false)
{
var pagingResource = Request.ReadPagingResourceFromRequest<QueueResource>();
var pagingResource = new PagingResource<QueueResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<QueueResource, NzbDrone.Core.Queue.Queue>("timeleft", SortDirection.Ascending);
return pagingSpec.ApplyToPage((spec) => GetQueue(spec, includeUnknownMovieItems), (q) => MapToResource(q, includeMovie));

View File

@@ -162,25 +162,25 @@
"schema": {
"type": "object",
"properties": {
"Username": {
"username": {
"type": "string"
},
"Password": {
"password": {
"type": "string"
},
"RememberMe": {
"rememberMe": {
"type": "string"
}
}
},
"encoding": {
"Username": {
"username": {
"style": "form"
},
"Password": {
"password": {
"style": "form"
},
"RememberMe": {
"rememberMe": {
"style": "form"
}
}
@@ -494,6 +494,40 @@
"tags": [
"Blocklist"
],
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
},
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 10
}
},
{
"name": "sortKey",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
}
],
"responses": {
"200": {
"description": "Success",
@@ -2413,32 +2447,69 @@
"History"
],
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
},
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 10
}
},
{
"name": "sortKey",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
},
{
"name": "includeMovie",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "eventType",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "downloadId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/HistoryResourcePagingResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/HistoryResourcePagingResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/HistoryResourcePagingResource"
}
}
}
}
@@ -4075,24 +4146,55 @@
"tags": [
"Log"
],
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
},
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 10
}
},
{
"name": "sortKey",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
},
{
"name": "level",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/LogResourcePagingResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/LogResourcePagingResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/LogResourcePagingResource"
}
}
}
}
@@ -5612,70 +5714,70 @@
],
"parameters": [
{
"name": "RenameMovies",
"name": "renameMovies",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "ReplaceIllegalCharacters",
"name": "replaceIllegalCharacters",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "ColonReplacementFormat",
"name": "colonReplacementFormat",
"in": "query",
"schema": {
"$ref": "#/components/schemas/ColonReplacementFormat"
}
},
{
"name": "StandardMovieFormat",
"name": "standardMovieFormat",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "MovieFolderFormat",
"name": "movieFolderFormat",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "IncludeQuality",
"name": "includeQuality",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "ReplaceSpaces",
"name": "replaceSpaces",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "Separator",
"name": "separator",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "NumberStyle",
"name": "numberStyle",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Id",
"name": "id",
"in": "query",
"schema": {
"type": "integer",
@@ -5683,7 +5785,7 @@
}
},
{
"name": "ResourceName",
"name": "resourceName",
"in": "query",
"schema": {
"type": "string"
@@ -6597,6 +6699,38 @@
"Queue"
],
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
},
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 10
}
},
{
"name": "sortKey",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
},
{
"name": "includeUnknownMovieItems",
"in": "query",
@@ -6618,20 +6752,10 @@
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/QueueResourcePagingResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/QueueResourcePagingResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/QueueResourcePagingResource"
}
}
}
}
@@ -8639,13 +8763,6 @@
"sortDirection": {
"$ref": "#/components/schemas/SortDirection"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PagingResourceFilter"
},
"nullable": true
},
"totalRecords": {
"type": "integer",
"format": "int32"
@@ -8691,6 +8808,9 @@
"type": "string",
"nullable": true
},
"status": {
"$ref": "#/components/schemas/MovieStatusType"
},
"overview": {
"type": "string",
"nullable": true
@@ -9314,6 +9434,9 @@
},
"autoRedownloadFailed": {
"type": "boolean"
},
"autoRedownloadFailedFromInteractiveSearch": {
"type": "boolean"
}
},
"additionalProperties": false
@@ -9632,13 +9755,6 @@
"sortDirection": {
"$ref": "#/components/schemas/SortDirection"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PagingResourceFilter"
},
"nullable": true
},
"totalRecords": {
"type": "integer",
"format": "int32"
@@ -10363,13 +10479,6 @@
"sortDirection": {
"$ref": "#/components/schemas/SortDirection"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PagingResourceFilter"
},
"nullable": true
},
"totalRecords": {
"type": "integer",
"format": "int32"
@@ -11613,20 +11722,6 @@
},
"additionalProperties": false
},
"PagingResourceFilter": {
"type": "object",
"properties": {
"key": {
"type": "string",
"nullable": true
},
"value": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"ParseResource": {
"type": "object",
"properties": {
@@ -12107,13 +12202,6 @@
"sortDirection": {
"$ref": "#/components/schemas/SortDirection"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PagingResourceFilter"
},
"nullable": true
},
"totalRecords": {
"type": "integer",
"format": "int32"

View File

@@ -4,7 +4,6 @@ using System.Linq;
using Microsoft.AspNetCore.Http;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Exceptions;
namespace Radarr.Http.Extensions
{
@@ -52,80 +51,6 @@ namespace Radarr.Http.Extensions
return defaultValue;
}
public static PagingResource<TResource> ReadPagingResourceFromRequest<TResource>(this HttpRequest request)
{
if (!int.TryParse(request.Query["PageSize"].ToString(), out var pageSize))
{
pageSize = 10;
}
if (!int.TryParse(request.Query["Page"].ToString(), out var page))
{
page = 1;
}
var pagingResource = new PagingResource<TResource>
{
PageSize = pageSize,
Page = page,
Filters = new List<PagingResourceFilter>()
};
if (request.Query["SortKey"].Any())
{
var sortKey = request.Query["SortKey"].ToString();
if (!VALID_SORT_KEYS.Contains(sortKey) &&
!TableMapping.Mapper.IsValidSortKey(sortKey))
{
throw new BadRequestException($"Invalid sort key {sortKey}");
}
pagingResource.SortKey = sortKey;
if (request.Query["SortDirection"].Any())
{
pagingResource.SortDirection = request.Query["SortDirection"].ToString()
.Equals("ascending", StringComparison.InvariantCultureIgnoreCase)
? SortDirection.Ascending
: SortDirection.Descending;
}
}
// For backwards compatibility with v2
if (request.Query["FilterKey"].Any())
{
var filter = new PagingResourceFilter
{
Key = request.Query["FilterKey"].ToString()
};
if (request.Query["FilterValue"].Any())
{
filter.Value = request.Query["FilterValue"].ToString();
}
pagingResource.Filters.Add(filter);
}
// v3 uses filters in key=value format
foreach (var pair in request.Query)
{
if (EXCLUDED_KEYS.Contains(pair.Key))
{
continue;
}
pagingResource.Filters.Add(new PagingResourceFilter
{
Key = pair.Key,
Value = pair.Value.ToString()
});
}
return pagingResource;
}
public static PagingResource<TResource> ApplyToPage<TResource, TModel>(this PagingSpec<TModel> pagingSpec, Func<PagingSpec<TModel>, PagingSpec<TModel>> function, Converter<TModel, TResource> mapper)
{
pagingSpec = function(pagingSpec);

View File

@@ -1,17 +1,39 @@
using System.Collections.Generic;
using System.ComponentModel;
using NzbDrone.Core.Datastore;
namespace Radarr.Http
{
public class PagingRequestResource
{
[DefaultValue(1)]
public int? Page { get; set; }
[DefaultValue(10)]
public int? PageSize { get; set; }
public string SortKey { get; set; }
public SortDirection? SortDirection { get; set; }
}
public class PagingResource<TResource>
{
public int Page { get; set; }
public int PageSize { get; set; }
public string SortKey { get; set; }
public SortDirection SortDirection { get; set; }
public List<PagingResourceFilter> Filters { get; set; }
public int TotalRecords { get; set; }
public List<TResource> Records { get; set; }
public PagingResource()
{
}
public PagingResource(PagingRequestResource requestResource)
{
Page = requestResource.Page ?? 1;
PageSize = requestResource.PageSize ?? 10;
SortKey = requestResource.SortKey;
SortDirection = requestResource.SortDirection ?? SortDirection.Descending;
}
}
public static class PagingResourceMapper

View File

@@ -1,8 +0,0 @@
namespace Radarr.Http
{
public class PagingResourceFilter
{
public string Key { get; set; }
public string Value { get; set; }
}
}