1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-30 18:25:57 -04:00

Compare commits

...

56 Commits

Author SHA1 Message Date
servarr[bot]
06a96ef2d1 Fixed: Movies poster view on mobile devices (#9659)
(cherry picked from commit c0b30a50281c38a103c2f800064d0ec964fae6e0)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-21 03:44:01 +02:00
Bogdan
c77ce2459c Transpile logical assignment operators with babel
(cherry picked from commit 3cf4d2907e32e81050f35cda042dcc2b4641d40d)
2024-01-21 03:43:33 +02:00
servarr[bot]
083989d151 New: Log warning if less than 1 GB free space during update
* New: Log warning if less than 1 GB free space during update

(cherry picked from commit e66ba84fc0b5b120dd4e87f6b8ae1b3c038ee72b)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-21 03:43:10 +02:00
Mark McDowall
c003fe16de Fixed: Don't clone indexer API Key
(cherry picked from commit d336aaf3f04136471970155b5a7cc876770c64ff)
2024-01-20 07:43:06 +02:00
Mark McDowall
bc9b2cd283 Refactor icons on full color calendar events
(cherry picked from commit 9884f6f282560ff2a0ea193e9306c6284cf8672c)

Closes #9646
2024-01-19 16:10:55 +02:00
Bogdan
d0e400c55a Wrap values in log messages in FileListParser
Closes #9644
2024-01-19 09:51:12 +02:00
Stevie Robinson
77863dc2cf New: Drop commands table content before postgres migration
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>

(cherry picked from commit 8dd3b45c90209136c0bd0a861061c6d20837d62f)
2024-01-19 09:51:04 +02:00
Stevie Robinson
18dc6f60b0 Round off the seeded ratio when checking for removal candidates
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>

(cherry picked from commit c6bb6ad8788fb1c20ed466a495f2b47034947145)
2024-01-19 08:15:59 +02:00
Bogdan
49501a55ae Check paged requests using PageSize for Import Lists 2024-01-18 09:30:28 +02:00
Weblate
d5d77a4f1a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Blair Noctis <fqmxz5hyfba7ft85@neon.casa>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Deleted User <noreply+2593@weblate.org>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-01-18 09:24:07 +02:00
Bogdan
0ae8952b38 Add SizeOnDisk and HasFile to MovieResource 2024-01-17 06:25:50 +02:00
Bogdan
6292ff76b0 Rename episode to movie 2024-01-17 05:21:25 +02:00
Bogdan
646d271e81 Add title to invalid Plex RSS item log
Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2024-01-17 01:15:57 +02:00
Bogdan
3d2ca830bc Fixed: Importing Plex RSS lists with invalid items 2024-01-17 01:13:21 +02:00
Bogdan
da02ec3b04 Add missing import needed for Added column 2024-01-17 00:49:17 +02:00
bakerboy448
cc9a443473 Update logging to indicate a hardlink is being attempted
(cherry picked from commit 16e5ffa467f72e52c750143c835f6ee1c1c2460b)

Closes #9611
2024-01-17 00:12:17 +02:00
Stevie Robinson
81b6bf521d Add missing translation keys from Indexer Settings
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>
(cherry picked from commit 07fbb0d1f464513ed28721d6c91d428dd54818cf)

Closes #9627
2024-01-17 00:10:56 +02:00
Bogdan
7edb892eb4 Throw download as failed for invalid magnet links
(cherry picked from commit 091449d9bff9023ca27a85cc1048296f7d5ea37b)

Closes #9625
Fixes #9125
2024-01-17 00:07:04 +02:00
Blair Noctis
3b36921787 Fixed: Improve help text for download client priority
(cherry picked from commit 1bba7e177b5b4173e620cd014ffdc231023309a0)

Closes #9622
2024-01-17 00:06:12 +02:00
Rubicj
c2d8bc85d0 New: Added column in Queue
(cherry picked from commit 57445bbe57a84990e284ef97d42455a06587e1ee)

Closes #9621
2024-01-17 00:04:30 +02:00
Bogdan
3e55b1cf25 Fix Content-Type in FileList fixture 2024-01-16 21:51:55 +02:00
Bogdan
0b0c93081d Check Content-Type in FileList parser 2024-01-16 21:39:03 +02:00
Servarr
91fbad72c0 Automated API Docs update 2024-01-16 20:58:27 +02:00
Bogdan
35651ac59b New: Release Groups for movie table index
* New: Release Group for movie table index

Co-authored-by: Qstick <qstick@gmail.com>

* fixup! New: Release Group for movie table index

---------

Co-authored-by: Qstick <qstick@gmail.com>
2024-01-16 20:52:07 +02:00
Qstick
1932aec131 Improved http timeout handling
(cherry picked from commit f87a66fcba6ca9ca972fa1c747a940b216e0e5e3)
2024-01-16 08:48:21 +02:00
Stevie Robinson
ea470b4ee9 Sort Custom Filters
(cherry picked from commit e4b5d559df2d5f3d55e16aae5922509e84f31e64)
2024-01-16 08:05:18 +02:00
Qstick
1bb404a912 Fixed: Only use frames for Primary video stream for analysis 2024-01-15 23:16:22 -06:00
Weblate
374d20634d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Daniele Prevedello <dprevedello86@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Petr Vojar <vojar.petr@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: crayon3shawn <crayon3shawn@gmail.com>
Co-authored-by: hansaudun <hans@n5.no>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
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/it/
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/zh_TW/
Translation: Servarr/Radarr
2024-01-15 01:44:06 +02:00
Qstick
60d9aacac6 Build report can get sent before installer finished 2024-01-14 13:27:52 -06:00
Qstick
c5992ed944 Bump Inno version to 6.2.2 2024-01-14 12:59:58 -06:00
Mark McDowall
4c4073ce1c New: Support SABnzb's new format for sorters
(cherry picked from commit d484553b310af4257c841c37a503ef54650c1426)

Closes #9351
2024-01-14 18:08:18 +02:00
Jendrik Weise
d72f78d979 New: Custom import scripts can communicate information back
(cherry picked from commit b4ac495983d61819d9ab84f49c880957ba57418b)
2024-01-14 16:15:48 +02:00
Bogdan
dca9d69aaa Bump version to 5.3.2 2024-01-14 07:10:07 +02:00
Stevie Robinson
5a64826868 Add: New icon for deleted episodes with status missing from disk
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>
(cherry picked from commit 79907c881cc92ce9ee3973d5cf21749fe5fc58da)

Closes #9604
2024-01-14 03:47:30 +02:00
Mark McDowall
cda40312e0 New: Optional directory setting for Aria2
(cherry picked from commit fd17df0dd03a5feb088c3241a247eac20f0e8c6c)

Closes #9602
2024-01-14 03:43:37 +02:00
Bogdan
907779b4ce Fetch movie file entity from database to broadcast 2024-01-14 03:42:03 +02:00
Mark McDowall
cc03651af5 Don't use TestCase for single test
(cherry picked from commit 541d3307e1466b0353dc4149f502a4b62b4de616)
2024-01-14 03:41:26 +02:00
servarr[bot]
1ae98d618c Fixed: Movie posters flickering when width changes repeatedly
(cherry picked from commit 53cf5308931069638c23925596a3fd8aaccc5d98)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-14 03:40:54 +02:00
Bogdan
f5914da2f9 Remove double filtering in entity history repository 2024-01-12 22:05:51 +02:00
Bogdan
f7816aa5cd Fixed: Filter history by multiple event types in PG 2024-01-12 22:05:51 +02:00
Andrejs Ķīlis
a652ce50a9 Fixed: Latvian and Russian language parsing
Improved support for Latvian with test cases I have encountered in the wild and fixed a case where Russian is not recognized (RU instead of RUS).
2024-01-12 03:00:51 +02:00
Bogdan
58b726a292 Fixed: Improve torrent blocklist matching
Closes #9585
2024-01-12 02:56:32 +02:00
Bogdan
1d8cf6a7f5 Fixed: Persist release source for pending releases
Closes #9583
2024-01-12 02:54:31 +02:00
ilike2burnthing
2c3ad380ef Remove unsupported pagination for Nyaa
(cherry picked from commit fef525ddb8b5f91bb36b3c9e652663fccb098a00)

Closes #9582
2024-01-12 02:52:53 +02:00
Stevie Robinson
0e7874aacf Fix Missing HelpText Translation Keys
(cherry picked from commit 587b600d6c6bac64c99d12225360810ef283f0aa)

Closes #9576
2024-01-12 02:45:57 +02:00
Weblate
8638d82ad3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aleksandr <alyarmak@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Bradley BARBIER <bradley.barbier@outlook.fr>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: HuaBing <admin@hbcraft.cn>
Co-authored-by: JJonttuu <oikeaihminen@protonmail.com>
Co-authored-by: Juan Lores <juan.lores@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Piotr Komborski <piotr+github@kombor.ski>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Watashi <drazy24@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: boan51204 <je.991707@gmail.com>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: liangyi <994302767@qq.com>
Co-authored-by: marius-tu <marius.tubbesing94@gmail.com>
Co-authored-by: ragote <ragote@pm.me>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: twobuttonbob <madinlol@gmail.com>
Co-authored-by: 饶志华 <879467666@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
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/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2024-01-12 02:41:40 +02:00
Bogdan
f3d6a1f99d Fixed: Release source for release/push 2024-01-11 02:07:03 +02:00
Bogdan
fa036f5807 Sorting movie list by tags 2024-01-11 00:41:53 +02:00
Bogdan
a931f8a69f Fixed: Skip fewer slides with cast/crew on smaller screens
Fixes #9571
2024-01-10 20:51:40 +02:00
Bogdan
a491c9a4a0 Fixed: Parsing custom formats for releases titles containing colon 2024-01-10 20:34:24 +02:00
Bogdan
2aafb6369c Fix app name in healthcheck 2024-01-10 01:35:08 +02:00
Mark McDowall
ef8253044e Fixed: Blocklisting torrents from indexers that do not provide torrent hash
(cherry picked from commit 3541cd7ba877fb785c7f97123745abf51162eb8e)
2024-01-09 00:30:32 +02:00
Bogdan
c1feeb72ee New: Year specification for custom formats 2024-01-08 02:29:29 +02:00
Servarr
21560cd6cc Automated API Docs update 2024-01-07 16:36:16 +02:00
Bogdan
bda2b9b0b8 Fixed: Filter history by multiple event types 2024-01-07 16:07:36 +02:00
Bogdan
4630de9616 Bump version to 5.3.1 2024-01-07 11:10:52 +02:00
165 changed files with 2663 additions and 990 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.3.0'
majorVersion: '5.3.2'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
@@ -17,7 +17,7 @@ variables:
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417'
nodeVersion: '16.X'
innoVersion: '6.2.0'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11'
@@ -1242,6 +1242,7 @@ stages:
- stage: Report_Out
dependsOn:
- Analyze
- Installer
- Unit_Test
- Integration
- Automation

View File

@@ -254,7 +254,7 @@ InstallInno()
ProgressStart "Installing portable Inno Setup"
rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe

View File

@@ -2,6 +2,8 @@ const loose = true;
module.exports = {
plugins: [
'@babel/plugin-transform-logical-assignment-operators',
// Stage 1
'@babel/plugin-proposal-export-default-from',
['@babel/plugin-transform-optional-chaining', { loose }],

View File

@@ -6,7 +6,7 @@ import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './HistoryEventTypeCell.css';
function getIconName(eventType) {
function getIconName(eventType, data) {
switch (eventType) {
case 'grabbed':
return icons.DOWNLOADING;
@@ -17,7 +17,7 @@ function getIconName(eventType) {
case 'downloadFailed':
return icons.DOWNLOADING;
case 'movieFileDeleted':
return icons.DELETE;
return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE;
case 'movieFileRenamed':
return icons.ORGANIZE;
case 'downloadIgnored':
@@ -47,7 +47,7 @@ function getTooltip(eventType, data) {
case 'downloadFailed':
return translate('MovieDownloadFailedTooltip');
case 'movieFileDeleted':
return translate('MovieFileDeletedTooltip');
return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip');
case 'movieFileRenamed':
return translate('MovieFileRenamedTooltip');
case 'downloadIgnored':
@@ -58,7 +58,7 @@ function getTooltip(eventType, data) {
}
function HistoryEventTypeCell({ eventType, data }) {
const iconName = getIconName(eventType);
const iconName = getIconName(eventType, data);
const iconKind = getIconKind(eventType);
const tooltip = getTooltip(eventType, data);

View File

@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import ProgressBar from 'Components/ProgressBar';
// import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
@@ -97,6 +97,7 @@ class QueueRow extends Component {
outputPath,
downloadClient,
estimatedCompletionTime,
added,
timeleft,
size,
sizeleft,
@@ -315,6 +316,15 @@ class QueueRow extends Component {
);
}
if (name === 'added') {
return (
<RelativeDateCellConnector
key={name}
date={added}
/>
);
}
if (name === 'actions') {
return (
<TableRowCell
@@ -393,6 +403,7 @@ QueueRow.propTypes = {
outputPath: PropTypes.string,
downloadClient: PropTypes.string,
estimatedCompletionTime: PropTypes.string,
added: PropTypes.string,
timeleft: PropTypes.string,
size: PropTypes.number,
year: PropTypes.number,

View File

@@ -44,7 +44,16 @@ export interface CustomFilter {
filers: PropertyFilter[];
}
export interface AppSectionState {
dimensions: {
isSmallScreen: boolean;
width: number;
height: number;
};
}
interface AppState {
app: AppSectionState;
calendar: CalendarAppState;
commands: CommandAppState;
history: HistoryAppState;

View File

@@ -147,7 +147,7 @@ class AgendaEvent extends Component {
className={styles.statusIcon}
name={icons.MOVIE_FILE}
kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')}
title={translate('QualityCutoffNotMet')}
/>
}
</div>

View File

@@ -48,6 +48,10 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
.statusContainer {
display: flex;
align-items: center;
&:global(.fullColor) {
filter: var(--calendarFullColorFilter)
}
}
.statusIcon {

View File

@@ -76,12 +76,18 @@ class CalendarEvent extends Component {
{title}
</div>
<div className={styles.statusContainer}>
<div
className={classNames(
styles.statusContainer,
fullColorEvents && 'fullColor'
)}
>
{
queueItem ?
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
fullColorEvents={fullColorEvents}
/>
</span> :
null
@@ -98,12 +104,14 @@ class CalendarEvent extends Component {
}
{
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
showCutoffUnmetIcon &&
!!movieFile &&
movieFile.qualityCutoffNotMet ?
<Icon
className={styles.statusIcon}
name={icons.MOVIE_FILE}
kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')}
title={translate('QualityCutoffNotMet')}
/> :
null
}

View File

@@ -20,10 +20,11 @@ function Legend(props) {
if (showCutoffUnmetIcon) {
iconsToShow.push(
<LegendIconItem
name={translate('CutoffUnmet')}
name={translate('CutoffNotMet')}
icon={icons.MOVIE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
kind={kinds.WARNING}
fullColorEvents={fullColorEvents}
tooltip={translate('QualityCutoffNotMet')}
/>
);
}

View File

@@ -4,4 +4,8 @@
.icon {
margin-right: 5px;
&:global(.fullColorEvents) {
filter: var(--calendarFullColorFilter)
}
}

View File

@@ -1,3 +1,4 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
@@ -6,9 +7,9 @@ import styles from './LegendIconItem.css';
function LegendIconItem(props) {
const {
name,
fullColorEvents,
icon,
kind,
darken,
tooltip
} = props;
@@ -18,9 +19,11 @@ function LegendIconItem(props) {
title={tooltip}
>
<Icon
className={styles.icon}
className={classNames(
styles.icon,
fullColorEvents && 'fullColorEvents'
)}
name={icon}
darken={darken}
kind={kind}
/>
@@ -31,14 +34,10 @@ function LegendIconItem(props) {
LegendIconItem.propTypes = {
name: PropTypes.string.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
icon: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired,
darken: PropTypes.bool.isRequired,
tooltip: PropTypes.string.isRequired
};
LegendIconItem.defaultProps = {
darken: false
};
export default LegendIconItem;

View File

@@ -74,11 +74,7 @@ CollectionMovieLabel.propTypes = {
CollectionMovieLabel.defaultProps = {
isSaving: false,
statistics: {
episodeFileCount: 0,
totalEpisodeCount: 0,
percentOfEpisodes: 0
}
statistics: {}
};
export default CollectionMovieLabel;

View File

@@ -30,22 +30,24 @@ function CustomFiltersModalContent(props) {
<ModalBody>
{
customFilters.map((customFilter) => {
return (
<CustomFilter
key={customFilter.id}
id={customFilter.id}
label={customFilter.label}
filters={customFilter.filters}
selectedFilterKey={selectedFilterKey}
isDeleting={isDeleting}
deleteError={deleteError}
dispatchSetFilter={dispatchSetFilter}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
onEditPress={onEditCustomFilter}
/>
);
})
customFilters
.sort((a, b) => a.label.localeCompare(b.label))
.map((customFilter) => {
return (
<CustomFilter
key={customFilter.id}
id={customFilter.id}
label={customFilter.label}
filters={customFilter.filters}
selectedFilterKey={selectedFilterKey}
isDeleting={isDeleting}
deleteError={deleteError}
dispatchSetFilter={dispatchSetFilter}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
onEditPress={onEditCustomFilter}
/>
);
})
}
<div className={styles.addButtonContainer}>

View File

@@ -12,18 +12,10 @@
.info {
color: var(--infoColor);
&:global(.darken) {
color: color(var(--infoColor) shade(30%));
}
}
.pink {
color: var(--pink);
&:global(.darken) {
color: color(var(--pink) shade(30%));
}
}
.success {

View File

@@ -18,7 +18,6 @@ class Icon extends PureComponent {
kind,
size,
title,
darken,
isSpinning,
...otherProps
} = this.props;
@@ -27,8 +26,7 @@ class Icon extends PureComponent {
<FontAwesomeIcon
className={classNames(
className,
styles[kind],
darken && 'darken'
styles[kind]
)}
icon={name}
spin={isSpinning}
@@ -61,7 +59,6 @@ Icon.propTypes = {
kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
darken: PropTypes.bool.isRequired,
isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired
};
@@ -69,7 +66,6 @@ Icon.propTypes = {
Icon.defaultProps = {
kind: kinds.DEFAULT,
size: 14,
darken: false,
isSpinning: false,
fixedWidth: false
};

View File

@@ -40,18 +40,26 @@ class FilterMenuContent extends Component {
}
{
customFilters.map((filter) => {
return (
<FilterMenuItem
key={filter.id}
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
customFilters.length > 0 ?
<MenuItemSeparator /> :
null
}
{
customFilters
.sort((a, b) => a.label.localeCompare(b.label))
.map((filter) => {
return (
<FilterMenuItem
key={filter.id}
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
}
{

View File

@@ -59,6 +59,7 @@ import {
faEye as fasEye,
faFastBackward as fasFastBackward,
faFastForward as fasFastForward,
faFileCircleQuestion as fasFileCircleQuestion,
faFileExport as fasFileExport,
faFileInvoice as farFileInvoice,
faFilm as fasFilm,
@@ -159,6 +160,7 @@ export const EXPORT = fasFileExport;
export const EXTERNAL_LINK = fasExternalLinkAlt;
export const FATAL = fasTimesCircle;
export const FILE = farFile;
export const FILE_MISSING = fasFileCircleQuestion;
export const FILM = fasFilm;
export const FILTER = fasFilter;
export const FLAG = fasFlag;

View File

@@ -50,12 +50,16 @@ class DeleteMovieModalContent extends Component {
title,
path,
hasFile,
statistics,
deleteOptions,
sizeOnDisk,
onModalClose,
onDeleteOptionChange
} = this.props;
const {
sizeOnDisk = 0
} = statistics;
const deleteFiles = this.state.deleteFiles;
const addImportExclusion = deleteOptions.addImportExclusion;
@@ -151,12 +155,16 @@ class DeleteMovieModalContent extends Component {
DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.number.isRequired,
deleteOptions: PropTypes.object.isRequired,
onDeleteOptionChange: PropTypes.func.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
DeleteMovieModalContent.defaultProps = {
statistics: {}
};
export default DeleteMovieModalContent;

View File

@@ -52,7 +52,8 @@ class MovieCreditPosters extends Component {
render() {
const {
items,
itemComponent
itemComponent,
isSmallScreen
} = this.props;
const {
@@ -67,7 +68,7 @@ class MovieCreditPosters extends Component {
<Swiper
slidesPerView='auto'
spaceBetween={10}
slidesPerGroup={3}
slidesPerGroup={isSmallScreen ? 1 : 3}
navigation={true}
loop={false}
loopFillGroupWithBlank={true}

View File

@@ -238,7 +238,7 @@ class MovieDetails extends Component {
certification,
ratings,
path,
sizeOnDisk,
statistics,
qualityProfileId,
monitored,
studio,
@@ -267,6 +267,10 @@ class MovieDetails extends Component {
movieRuntimeFormat
} = this.props;
const {
sizeOnDisk = 0
} = statistics;
const {
isOrganizeModalOpen,
isEditMovieModalOpen,
@@ -734,7 +738,7 @@ MovieDetails.propTypes = {
certification: PropTypes.string,
ratings: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
sizeOnDisk: PropTypes.number.isRequired,
statistics: PropTypes.object.isRequired,
qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
@@ -773,9 +777,9 @@ MovieDetails.propTypes = {
MovieDetails.defaultProps = {
genres: [],
statistics: {},
tags: [],
isSaving: false,
sizeOnDisk: 0
isSaving: false
};
export default MovieDetails;

View File

@@ -17,13 +17,13 @@ function createUnoptimizedSelector() {
createClientSideCollectionSelector('movies', 'movieIndex'),
(movies: MoviesAppState) => {
return movies.items.map((m) => {
const { monitored, status, hasFile, sizeOnDisk } = m;
const { monitored, status, hasFile, statistics } = m;
return {
monitored,
status,
hasFile,
sizeOnDisk,
statistics,
};
});
}
@@ -44,16 +44,20 @@ export default function MovieIndexFooter() {
let monitored = 0;
let totalFileSize = 0;
movies.forEach((s) => {
if (s.hasFile) {
movies.forEach((m) => {
const { statistics = { sizeOnDisk: 0 } } = m;
const { sizeOnDisk = 0 } = statistics;
if (m.hasFile) {
movieFiles += 1;
}
if (s.monitored) {
if (m.monitored) {
monitored++;
}
totalFileSize += s.sizeOnDisk;
totalFileSize += sizeOnDisk;
});
return (

View File

@@ -13,6 +13,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
import { Statistics } from 'Movie/Movie';
import MoviePoster from 'Movie/MoviePoster';
import { executeCommand } from 'Store/Actions/commandActions';
import dimensions from 'Styles/Variables/dimensions';
@@ -66,17 +67,19 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
status,
path,
overview,
statistics = {} as Statistics,
images,
hasFile,
isAvailable,
tmdbId,
imdbId,
studio,
sizeOnDisk,
added,
youTubeTrailerId,
} = movie;
const { sizeOnDisk = 0 } = statistics;
const dispatch = useDispatch();
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);

View File

@@ -16,6 +16,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
import { Statistics } from 'Movie/Movie';
import MoviePoster from 'Movie/MoviePoster';
import { executeCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
@@ -75,12 +76,14 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
path,
movieFile,
ratings,
sizeOnDisk,
statistics = {} as Statistics,
certification,
originalTitle,
originalLanguage,
} = movie;
const { sizeOnDisk = 0 } = statistics;
const dispatch = useDispatch();
const [hasPosterError, setHasPosterError] = useState(false);
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);

View File

@@ -244,11 +244,15 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
if (isSmallScreen) {
const padding = bodyPaddingSmallScreen - 5;
const width = window.innerWidth - padding * 2;
const height = window.innerHeight;
setSize({
width: window.innerWidth - padding * 2,
height: window.innerHeight,
});
if (width !== size.width || height !== size.height) {
setSize({
width,
height,
});
}
return;
}
@@ -256,13 +260,18 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
if (current) {
const width = current.clientWidth;
const padding = bodyPadding - 5;
const finalWidth = width - padding * 2;
if (Math.abs(size.width - finalWidth) < 20 || size.width === finalWidth) {
return;
}
setSize({
width: width - padding * 2,
width: finalWidth,
height: window.innerHeight,
});
}
}, [isSmallScreen, scrollerRef, bounds]);
}, [isSmallScreen, size, scrollerRef, bounds]);
useEffect(() => {
const currentScrollerRef = scrollerRef.current as HTMLElement;

View File

@@ -38,6 +38,7 @@
flex: 1 0 125px;
}
.releaseGroups,
.inCinemas,
.physicalRelease,
.digitalRelease,

View File

@@ -20,6 +20,7 @@ interface CssExports {
'physicalRelease': string;
'popularity': string;
'qualityProfileId': string;
'releaseGroups': string;
'rottenTomatoesRating': string;
'runtime': string;
'sizeOnDisk': string;

View File

@@ -19,6 +19,7 @@ import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
import { Statistics } from 'Movie/Movie';
import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
import MovieTitleLink from 'Movie/MovieTitleLink';
import { executeCommand } from 'Store/Actions/commandActions';
@@ -60,6 +61,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
originalLanguage,
originalTitle,
added,
statistics = {} as Statistics,
year,
inCinemas,
digitalRelease,
@@ -67,7 +69,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
runtime,
minimumAvailability,
path,
sizeOnDisk,
genres = [],
ratings,
popularity,
@@ -82,6 +83,8 @@ function MovieIndexRow(props: MovieIndexRowProps) {
isSaving = false,
} = movie;
const { sizeOnDisk = 0, releaseGroups = [] } = statistics;
const dispatch = useDispatch();
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
@@ -380,6 +383,20 @@ function MovieIndexRow(props: MovieIndexRowProps) {
);
}
if (name === 'releaseGroups') {
const joinedReleaseGroups = releaseGroups.join(', ');
const truncatedReleaseGroups =
releaseGroups.length > 3
? `${releaseGroups.slice(0, 3).join(', ')}...`
: joinedReleaseGroups;
return (
<VirtualTableRowCell key={name} className={styles[name]}>
<span title={joinedReleaseGroups}>{truncatedReleaseGroups}</span>
</VirtualTableRowCell>
);
}
if (name === 'tags') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>

View File

@@ -31,6 +31,7 @@
flex: 1 0 125px;
}
.releaseGroups,
.inCinemas,
.physicalRelease,
.digitalRelease,

View File

@@ -17,6 +17,7 @@ interface CssExports {
'physicalRelease': string;
'popularity': string;
'qualityProfileId': string;
'releaseGroups': string;
'rottenTomatoesRating': string;
'runtime': string;
'sizeOnDisk': string;

View File

@@ -12,6 +12,12 @@ export interface Collection {
title: string;
}
export interface Statistics {
movieFileCount: number;
releaseGroups: string[];
sizeOnDisk: number;
}
export interface Ratings {
imdb: object;
tmdb: object;
@@ -42,11 +48,11 @@ interface Movie extends ModelBase {
runtime: number;
minimumAvailability: string;
path: string;
sizeOnDisk: number;
genres: string[];
ratings: Ratings;
popularity: number;
certification: string;
statistics: Statistics;
tags: number[];
images: Image[];
movieFile: MovieFile;

View File

@@ -133,7 +133,7 @@ class EditDownloadClientModalContent extends Component {
<FormInputGroup
type={inputTypes.NUMBER}
name="priority"
helpText={translate('PriorityHelpText')}
helpText={translate('DownloadClientPriorityHelpText')}
min={1}
max={50}
{...priority}

View File

@@ -150,7 +150,13 @@ export default {
delete selectedSchema.name;
selectedSchema.fields = selectedSchema.fields.map((field) => {
return { ...field };
const newField = { ...field };
if (newField.privacy === 'apiKey' || newField.privacy === 'password') {
newField.value = '';
}
return newField;
});
newState.selectedSchema = selectedSchema;

View File

@@ -128,6 +128,22 @@ export const filterPredicates = {
return predicate(originalLanguage ? originalLanguage.name : '', filterValue);
},
releaseGroups: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { statistics = {} } = item;
const { releaseGroups = [] } = statistics;
return predicate(releaseGroups, filterValue);
},
sizeOnDisk: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { statistics = {} } = item;
const sizeOnDisk = statistics && statistics.sizeOnDisk ? statistics.sizeOnDisk : 0;
return predicate(sizeOnDisk, filterValue);
},
inCinemas: function(item, filterValue, type) {
return dateFilterPredicate(item.inCinemas, filterValue, type);
},
@@ -290,6 +306,12 @@ export const sortPredicates = {
}
return Number.MAX_VALUE;
},
sizeOnDisk: function(item) {
const { statistics = {} } = item;
return statistics.sizeOnDisk || 0;
}
};

View File

@@ -206,10 +206,16 @@ export const defaultState = {
isSortable: true,
isVisible: false
},
{
name: 'releaseGroups',
label: () => translate('ReleaseGroup'),
isSortable: true,
isVisible: false
},
{
name: 'tags',
label: () => translate('Tags'),
isSortable: false,
isSortable: true,
isVisible: false
},
{
@@ -241,6 +247,17 @@ export const defaultState = {
return originalLanguage.name;
},
releaseGroups: function(item) {
const { statistics = {} } = item;
const { releaseGroups = [] } = statistics;
return releaseGroups.length ?
releaseGroups
.map((group) => group.toLowerCase())
.sort((a, b) => a.localeCompare(b)) :
undefined;
},
imdbRating: function(item) {
const { ratings = {} } = item;
@@ -313,6 +330,28 @@ export const defaultState = {
return collectionList.sort(sortByName);
}
},
{
name: 'releaseGroups',
label: () => translate('ReleaseGroups'),
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const groupList = items.reduce((acc, movie) => {
const { statistics = {} } = movie;
const { releaseGroups = [] } = statistics;
releaseGroups.forEach((releaseGroup) => {
acc.push({
id: releaseGroup,
name: releaseGroup
});
});
return acc;
}, []);
return groupList.sort(sortByName);
}
},
{
name: 'status',
label: () => translate('ReleaseStatus'),

View File

@@ -147,6 +147,12 @@ export const defaultState = {
isSortable: true,
isVisible: true
},
{
name: 'added',
label: () => translate('Added'),
isSortable: true,
isVisible: false
},
{
name: 'progress',
label: () => translate('Progress'),

View File

@@ -1,8 +1,9 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createDimensionsSelector() {
return createSelector(
(state) => state.app.dimensions,
(state: AppState) => state.app.dimensions,
(dimensions) => {
return dimensions;
}

View File

@@ -215,6 +215,8 @@ module.exports = {
calendarTextDim: '#eee',
calendarTextDimAlternate: '#fff',
calendarFullColorFilter: 'grayscale(90%) contrast(200%) saturate(50%)',
//
// Table

View File

@@ -216,6 +216,8 @@ module.exports = {
calendarTextDim: '#666',
calendarTextDimAlternate: '#242424',
calendarFullColorFilter: 'brightness(30%)',
//
// Table

View File

@@ -28,6 +28,7 @@ interface Queue extends ModelBase {
sizeleft: number;
timeleft: string;
estimatedCompletionTime: string;
added?: string;
status: string;
trackedDownloadStatus: QueueTrackedDownloadStatus;
trackedDownloadState: QueueTrackedDownloadState;

View File

@@ -1,4 +1,5 @@
export interface UiSettings {
theme: string;
showRelativeDates: boolean;
shortDateFormat: string;
longDateFormat: string;

View File

@@ -129,6 +129,16 @@ namespace NzbDrone.Common.Test.Http
response.Content.Should().NotBeNullOrWhiteSpace();
}
[Test]
public void should_throw_timeout_request()
{
var request = new HttpRequest($"https://{_httpBinHost}/delay/10");
request.RequestTimeout = new TimeSpan(0, 0, 5);
Assert.ThrowsAsync<WebException>(async () => await Subject.ExecuteAsync(request));
}
[Test]
public async Task should_execute_https_get()
{

View File

@@ -102,31 +102,38 @@ namespace NzbDrone.Common.Http.Dispatchers
var httpClient = GetClient(request.Url);
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
try
{
byte[] data = null;
try
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
byte[] data = null;
try
{
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
}
else
{
data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token);
}
}
else
catch (Exception ex)
{
data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token);
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode, responseMessage.Version);
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode, responseMessage.Version);
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null);
}
}

View File

@@ -92,6 +92,10 @@ namespace NzbDrone.Common.Http
{
data = new XElement("base64", Convert.ToBase64String(bytes));
}
else if (value is Dictionary<string, string> d)
{
data = new XElement("struct", d.Select(p => new XElement("member", new XElement("name", p.Key), new XElement("value", p.Value))));
}
else
{
throw new InvalidOperationException($"Unhandled argument type {value.GetType().Name}");

View File

@@ -543,6 +543,52 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
result.HasWarnings.Should().BeTrue();
}
[Test]
public void should_test_success_if_sorters_are_empty()
{
_config.Misc.enable_tv_sorting = false;
_config.Misc.tv_categories = null;
_config.Sorters = new List<SabnzbdSorter>();
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
}
[Test]
public void should_test_failed_if_sorter_is_enabled_for_non_tv_category()
{
_config.Misc.enable_tv_sorting = false;
_config.Misc.tv_categories = null;
_config.Sorters = Builder<SabnzbdSorter>.CreateListOfSize(1)
.All()
.With(s => s.is_active = true)
.With(s => s.sort_cats = new List<string> { "movie-custom" })
.Build()
.ToList();
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
}
[Test]
public void should_test_failed_if_sorter_is_enabled_for_tv_category()
{
_config.Misc.enable_tv_sorting = false;
_config.Misc.tv_categories = null;
_config.Sorters = Builder<SabnzbdSorter>.CreateListOfSize(1)
.All()
.With(s => s.is_active = true)
.With(s => s.sort_cats = new List<string> { "movie" })
.Build()
.ToList();
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeFalse();
}
[Test]
public void should_test_success_if_tv_sorting_disabled()
{

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Mocker.GetMock<IHttpClient>()
.Setup(o => o.ExecuteAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader(), recentFeed)));
.Returns<HttpRequest>(r => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, recentFeed)));
var releases = await Subject.FetchRecent();

View File

@@ -156,7 +156,7 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Subject.Scan(_movie);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<bool>()), Times.Once());
.Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<bool>()), Times.Exactly(2));
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());

View File

@@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
@@ -71,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
@@ -97,7 +98,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
@@ -123,7 +124,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(3));
@@ -146,7 +147,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo("media.mkv"), Times.Never());
@@ -173,7 +174,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenSuccessfulScan();
GivenFailedScan(Path.Combine(_movie.Path, "media2.mkv"));
Subject.Handle(new MovieScannedEvent(_movie));
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(1));
@@ -203,7 +204,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
GivenFileExists();
GivenSuccessfulScan();
Subject.Handle(new MovieScannedEvent(_movie));
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
Mocker.GetMock<IVideoFileInfoReader>()
.Verify(v => v.GetMediaInfo(It.IsAny<string>()), Times.Never());

View File

@@ -140,6 +140,8 @@ namespace NzbDrone.Core.Test.ParserTests
}
[TestCase("Movie.Title.1994.Russian.1080p.XviD-LOL")]
[TestCase("Movie.Title.2020.WEB-DLRip.AVC.AC3.EN.RU.ENSub.RUSub-LOL")]
[TestCase("Movie Title (2020) WEB-DL (720p) Rus-Eng")]
public void should_parse_language_russian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
@@ -388,6 +390,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2022.lv.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.LATVIAN.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.Latvian.WEBRip.XviD-LOL")]
[TestCase("Movie.Title.2022.1080p.WEB-DL.DDP5.1.Atmos.H.264.Lat.Eng")]
[TestCase("Movie.Title.2022.1080p.WEB-DL.LAV.RUS-NPPK")]
public void should_parse_language_latvian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Blocklisting
public interface IBlocklistService
{
bool Blocklisted(int movieId, ReleaseInfo release);
bool BlocklistedTorrentHash(int movieId, string hash);
PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec);
List<Blocklist> GetByMovieId(int movieId);
void Block(RemoteMovie remoteMovie, string message);
@@ -37,30 +38,34 @@ namespace NzbDrone.Core.Blocklisting
public bool Blocklisted(int movieId, ReleaseInfo release)
{
var blocklistedByTitle = _blocklistRepository.BlocklistedByTitle(movieId, release.Title);
if (release.DownloadProtocol == DownloadProtocol.Torrent)
{
var torrentInfo = release as TorrentInfo;
if (torrentInfo == null)
if (release is not TorrentInfo torrentInfo)
{
return false;
}
if (torrentInfo.InfoHash.IsNullOrWhiteSpace())
if (torrentInfo.InfoHash.IsNotNullOrWhiteSpace())
{
return blocklistedByTitle.Where(b => b.Protocol == DownloadProtocol.Torrent)
.Any(b => SameTorrent(b, torrentInfo));
var blocklistedByTorrentInfohash = _blocklistRepository.BlocklistedByTorrentInfoHash(movieId, torrentInfo.InfoHash);
return blocklistedByTorrentInfohash.Any(b => SameTorrent(b, torrentInfo));
}
var blocklistedByTorrentInfohash = _blocklistRepository.BlocklistedByTorrentInfoHash(movieId, torrentInfo.InfoHash);
return blocklistedByTorrentInfohash.Any(b => SameTorrent(b, torrentInfo));
return _blocklistRepository.BlocklistedByTitle(movieId, release.Title)
.Where(b => b.Protocol == DownloadProtocol.Torrent)
.Any(b => SameTorrent(b, torrentInfo));
}
return blocklistedByTitle.Where(b => b.Protocol == DownloadProtocol.Usenet)
.Any(b => SameNzb(b, release));
return _blocklistRepository.BlocklistedByTitle(movieId, release.Title)
.Where(b => b.Protocol == DownloadProtocol.Usenet)
.Any(b => SameNzb(b, release));
}
public bool BlocklistedTorrentHash(int movieId, string hash)
{
return _blocklistRepository.BlocklistedByTorrentInfoHash(movieId, hash).Any(b =>
b.TorrentInfoHash.Equals(hash, StringComparison.InvariantCultureIgnoreCase));
}
public PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec)

View File

@@ -0,0 +1,41 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.CustomFormats
{
public class YearSpecificationValidator : AbstractValidator<YearSpecification>
{
public YearSpecificationValidator()
{
RuleFor(c => c.Min).NotEmpty().GreaterThan(0);
RuleFor(c => c.Max).NotEmpty().GreaterThanOrEqualTo(c => c.Min);
}
}
public class YearSpecification : CustomFormatSpecificationBase
{
private static readonly YearSpecificationValidator Validator = new ();
public override int Order => 10;
public override string ImplementationName => "Year";
[FieldDefinition(1, Label = "Minimum Year", Type = FieldType.Number)]
public int Min { get; set; }
[FieldDefinition(2, Label = "Maximum Year", Type = FieldType.Number)]
public int Max { get; set; }
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var year = input.MovieInfo?.Year ?? input.Movie?.MovieMetadata?.Value?.Year;
return year >= Min && year <= Max;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -8,6 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
{
protected override void MainDbUpgrade()
{
Delete.FromTable("Commands").AllRows();
Alter.Table("Blocklist").AlterColumn("Date").AsDateTimeOffset().NotNullable();
Alter.Table("Blocklist").AlterColumn("PublishedDate").AsDateTimeOffset().Nullable();
Alter.Table("Collections").AlterColumn("Added").AsDateTimeOffset().Nullable();

View File

@@ -7,9 +7,9 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -26,11 +26,11 @@ namespace NzbDrone.Core.Download.Clients.Aria2
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download.Extensions;
@@ -97,8 +98,14 @@ namespace NzbDrone.Core.Download.Clients.Aria2
public string AddMagnet(Aria2Settings settings, string magnet)
{
var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List<string> { magnet });
var options = new Dictionary<string, string>();
if (settings.Directory.IsNotNullOrWhiteSpace())
{
options.Add("dir", settings.Directory);
}
var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List<string> { magnet }, options);
var gid = response.GetStringResponse();
return gid;
@@ -106,8 +113,16 @@ namespace NzbDrone.Core.Download.Clients.Aria2
public string AddTorrent(Aria2Settings settings, byte[] torrent)
{
var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent);
// Aria2's second parameter is an array of URIs and needs to be sent if options are provided, this satisfies that requirement.
var emptyListOfUris = new List<string>();
var options = new Dictionary<string, string>();
if (settings.Directory.IsNotNullOrWhiteSpace())
{
options.Add("dir", settings.Directory);
}
var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent, emptyListOfUris, options);
var gid = response.GetStringResponse();
return gid;

View File

@@ -40,6 +40,10 @@ namespace NzbDrone.Core.Download.Clients.Aria2
[FieldDefinition(4, Label = "Secret token", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string SecretToken { get; set; }
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientAriaSettingsDirectoryHelpText")]
public string Directory { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
private readonly Logger _logger;
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly INamingConfigService _namingConfigService;
private readonly ICached<Dictionary<string, WatchFolderItem>> _watchFolderItemCache;
public ScanWatchFolder(ICacheManager cacheManager, IDiskScanService diskScanService, INamingConfigService namingConfigService, IDiskProvider diskProvider, Logger logger)
public ScanWatchFolder(ICacheManager cacheManager, IDiskScanService diskScanService, IDiskProvider diskProvider, Logger logger)
{
_logger = logger;
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_namingConfigService = namingConfigService;
_watchFolderItemCache = cacheManager.GetCache<Dictionary<string, WatchFolderItem>>(GetType());
}

View File

@@ -7,6 +7,7 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
@@ -27,11 +28,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_scanWatchFolder = scanWatchFolder;

View File

@@ -22,12 +22,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
public UsenetBlackhole(IScanWatchFolder scanWatchFolder,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_scanWatchFolder = scanWatchFolder;

View File

@@ -7,9 +7,9 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -24,11 +24,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View File

@@ -8,10 +8,10 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@@ -35,11 +35,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;

View File

@@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@@ -32,12 +31,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IDownloadStationTaskProxySelector dsTaskProxySelector,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;

View File

@@ -6,10 +6,10 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Flood.Models;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@@ -26,11 +26,11 @@ namespace NzbDrone.Core.Download.Clients.Flood
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
_downloadSeedConfigProvider = downloadSeedConfigProvider;

View File

@@ -3,14 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@@ -21,15 +20,14 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
private readonly IFreeboxDownloadProxy _proxy;
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ICacheManager cacheManager,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View File

@@ -6,10 +6,10 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Hadouken.Models;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -24,11 +24,11 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View File

@@ -8,7 +8,6 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -22,12 +21,11 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
public NzbVortex(INzbVortexProxy proxy,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_proxy = proxy;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;

View File

@@ -10,7 +10,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -26,12 +25,11 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
public Nzbget(INzbgetProxy proxy,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_proxy = proxy;
}

View File

@@ -21,11 +21,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
public Pneumatic(IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
: base(configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(configService, diskProvider, remotePathMappingService, logger)
{
_httpClient = httpClient;
}

View File

@@ -8,9 +8,9 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -32,12 +32,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ICacheManager cacheManager,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxySelector = proxySelector;
@@ -609,7 +609,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
else if (torrent.RatioLimit == -2 && config.MaxRatioEnabled)
{
if (torrent.Ratio >= config.MaxRatio)
if (Math.Round(torrent.Ratio, 2) >= config.MaxRatio)
{
return true;
}

View File

@@ -10,7 +10,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -24,12 +23,11 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public Sabnzbd(ISabnzbdProxy proxy,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, nzbValidationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_proxy = proxy;
}
@@ -484,6 +482,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
}
// New in SABnzbd 4.1, but on older versions this will be empty and not apply
if (config.Sorters.Any(s => s.is_active && ContainsCategory(s.sort_cats, Settings.MovieCategory)))
{
return new NzbDroneValidationFailure("MovieCategory", "Disable TV Sorting")
{
InfoLink = _proxy.GetBaseUrl(Settings, "config/sorting/"),
DetailedDescription = "You must disable sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it."
};
}
if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.MovieCategory))
{
return new NzbDroneValidationFailure("MovieCategory", "Disable TV Sorting")

View File

@@ -11,11 +11,13 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
Categories = new List<SabnzbdCategory>();
Servers = new List<object>();
Sorters = new List<SabnzbdSorter>();
}
public SabnzbdConfigMisc Misc { get; set; }
public List<SabnzbdCategory> Categories { get; set; }
public List<object> Servers { get; set; }
public List<SabnzbdSorter> Sorters { get; set; }
}
public class SabnzbdConfigMisc
@@ -42,4 +44,22 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public OsPath FullPath { get; set; }
}
public class SabnzbdSorter
{
public SabnzbdSorter()
{
sort_cats = new List<string>();
sort_type = new List<int>();
}
public string name { get; set; }
public int order { get; set; }
public string min_size { get; set; }
public string multipart_label { get; set; }
public string sort_string { get; set; }
public List<string> sort_cats { get; set; }
public List<int> sort_type { get; set; }
public bool is_active { get; set; }
}
}

View File

@@ -4,9 +4,9 @@ using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Core.Download.Clients.Transmission
@@ -17,11 +17,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
}

View File

@@ -6,9 +6,9 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -23,11 +23,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View File

@@ -2,10 +2,10 @@ using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Core.Download.Clients.Vuze
@@ -18,11 +18,11 @@ namespace NzbDrone.Core.Download.Clients.Vuze
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
}

View File

@@ -8,11 +8,11 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.rTorrent;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@@ -31,13 +31,13 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IDownloadSeedConfigProvider downloadSeedConfigProvider,
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;

View File

@@ -8,9 +8,9 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@@ -27,11 +27,11 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;

View File

@@ -8,7 +8,6 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@@ -22,7 +21,6 @@ namespace NzbDrone.Core.Download
where TSettings : IProviderConfig, new()
{
protected readonly IConfigService _configService;
protected readonly INamingConfigService _namingConfigService;
protected readonly IDiskProvider _diskProvider;
protected readonly IRemotePathMappingService _remotePathMappingService;
protected readonly Logger _logger;
@@ -76,13 +74,11 @@ namespace NzbDrone.Core.Download
protected TSettings Settings => (TSettings)Definition.Settings;
protected DownloadClientBase(IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
{
_configService = configService;
_namingConfigService = namingConfigService;
_diskProvider = diskProvider;
_remotePathMappingService = remotePathMappingService;
_logger = logger;

View File

@@ -103,6 +103,11 @@ namespace NzbDrone.Core.Download
_logger.Trace("Release {0} no longer available on indexer.", remoteMovie);
throw;
}
catch (ReleaseBlockedException)
{
_logger.Trace("Release {0} previously added to blocklist, not sending to download client again.", remoteMovie);
throw;
}
catch (DownloadClientRejectedReleaseException)
{
_logger.Trace("Release {0} rejected by download client, possible duplicate.", remoteMovie);
@@ -127,7 +132,7 @@ namespace NzbDrone.Core.Download
movieGrabbedEvent.DownloadClientId = downloadClient.Definition.Id;
movieGrabbedEvent.DownloadClientName = downloadClient.Definition.Name;
if (!string.IsNullOrWhiteSpace(downloadClientId))
if (downloadClientId.IsNotNullOrWhiteSpace())
{
movieGrabbedEvent.DownloadId = downloadClientId;
}

View File

@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download
private void PublishDownloadFailedEvent(List<MovieHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{
var historyItem = historyItems.First();
var historyItem = historyItems.Last();
Enum.TryParse(historyItem.Data.GetValueOrDefault(MovieHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
var downloadFailedEvent = new DownloadFailedEvent

View File

@@ -203,6 +203,7 @@ namespace NzbDrone.Core.Download.Pending
RemoteMovie = pendingRelease.RemoteMovie,
Timeleft = timeleft,
EstimatedCompletionTime = ect,
Added = pendingRelease.Added,
Status = pendingRelease.Reason.ToString(),
Protocol = pendingRelease.RemoteMovie.Release.DownloadProtocol,
Indexer = pendingRelease.RemoteMovie.Release.Indexer
@@ -326,7 +327,8 @@ namespace NzbDrone.Core.Download.Pending
Reason = reason,
AdditionalInfo = new PendingReleaseAdditionalInfo
{
MovieMatchType = decision.RemoteMovie.MovieMatchType
MovieMatchType = decision.RemoteMovie.MovieMatchType,
ReleaseSource = decision.RemoteMovie.ReleaseSource
}
};

View File

@@ -6,6 +6,7 @@ using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
@@ -21,18 +22,20 @@ namespace NzbDrone.Core.Download
where TSettings : IProviderConfig, new()
{
protected readonly IHttpClient _httpClient;
private readonly IBlocklistService _blocklistService;
protected readonly ITorrentFileInfoReader _torrentFileInfoReader;
protected TorrentClientBase(ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IBlocklistService blocklistService,
Logger logger)
: base(configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(configService, diskProvider, remotePathMappingService, logger)
{
_httpClient = httpClient;
_blocklistService = blocklistService;
_torrentFileInfoReader = torrentFileInfoReader;
}
@@ -87,7 +90,7 @@ namespace NzbDrone.Core.Download
{
try
{
return DownloadFromMagnetUrl(remoteMovie, magnetUrl);
return DownloadFromMagnetUrl(remoteMovie, indexer, magnetUrl);
}
catch (NotSupportedException ex)
{
@@ -101,7 +104,7 @@ namespace NzbDrone.Core.Download
{
try
{
return DownloadFromMagnetUrl(remoteMovie, magnetUrl);
return DownloadFromMagnetUrl(remoteMovie, indexer, magnetUrl);
}
catch (NotSupportedException ex)
{
@@ -150,7 +153,7 @@ namespace NzbDrone.Core.Download
{
if (locationHeader.StartsWith("magnet:"))
{
return DownloadFromMagnetUrl(remoteMovie, locationHeader);
return DownloadFromMagnetUrl(remoteMovie, indexer, locationHeader);
}
request.Url += new HttpUri(locationHeader);
@@ -193,6 +196,9 @@ namespace NzbDrone.Core.Download
var filename = string.Format("{0}.torrent", FileNameBuilder.CleanFileName(remoteMovie.Release.Title));
var hash = _torrentFileInfoReader.GetHashFromTorrentFile(torrentFile);
EnsureReleaseIsNotBlocklisted(remoteMovie, indexer, hash);
var actualHash = AddFromTorrentFile(remoteMovie, hash, filename, torrentFile);
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
@@ -206,7 +212,7 @@ namespace NzbDrone.Core.Download
return actualHash;
}
private string DownloadFromMagnetUrl(RemoteMovie remoteMovie, string magnetUrl)
private string DownloadFromMagnetUrl(RemoteMovie remoteMovie, IIndexer indexer, string magnetUrl)
{
string hash = null;
string actualHash = null;
@@ -217,13 +223,13 @@ namespace NzbDrone.Core.Download
}
catch (FormatException ex)
{
_logger.Error(ex, "Failed to parse magnetlink for movie '{0}': '{1}'", remoteMovie.Release.Title, magnetUrl);
return null;
throw new ReleaseDownloadException(remoteMovie.Release, "Failed to parse magnetlink for movie '{0}': '{1}'", ex, remoteMovie.Release.Title, magnetUrl);
}
if (hash != null)
{
EnsureReleaseIsNotBlocklisted(remoteMovie, indexer, hash);
actualHash = AddFromMagnetLink(remoteMovie, hash, magnetUrl);
}
@@ -237,5 +243,30 @@ namespace NzbDrone.Core.Download
return actualHash;
}
private void EnsureReleaseIsNotBlocklisted(RemoteMovie remoteMovie, IIndexer indexer, string hash)
{
var indexerSettings = indexer?.Definition?.Settings as ITorrentIndexerSettings;
var torrentInfo = remoteMovie.Release as TorrentInfo;
var torrentInfoHash = torrentInfo?.InfoHash;
// If the release didn't come from an interactive search,
// the hash wasn't known during processing and the
// indexer is configured to reject blocklisted releases
// during grab check if it's already been blocklisted.
if (torrentInfo != null && torrentInfoHash.IsNullOrWhiteSpace())
{
// If the hash isn't known from parsing we set it here so it can be used for blocklisting.
torrentInfo.InfoHash = hash;
if (remoteMovie.ReleaseSource != ReleaseSourceType.InteractiveSearch &&
indexerSettings?.RejectBlocklistedTorrentHashesWhileGrabbing == true &&
_blocklistService.BlocklistedTorrentHash(remoteMovie.Movie.Id, hash))
{
throw new ReleaseBlockedException(remoteMovie.Release, "Release previously added to blocklist");
}
}
}
}
}

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
public TrackedDownloadStatusMessage[] StatusMessages { get; private set; }
public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; }
public DateTime? Added { get; set; }
public bool IsTrackable { get; set; }
public bool HasNotifiedManualInteractionRequired { get; set; }

View File

@@ -141,6 +141,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == MovieHistoryEventType.Grabbed);
trackedDownload.Indexer = grabbedEvent?.Data["indexer"];
trackedDownload.Added = grabbedEvent?.Date;
if (parsedMovieInfo == null ||
trackedDownload.RemoteMovie == null ||

View File

@@ -21,12 +21,11 @@ namespace NzbDrone.Core.Download
protected UsenetClientBase(IHttpClient httpClient,
IConfigService configService,
INamingConfigService namingConfigService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
Logger logger)
: base(configService, namingConfigService, diskProvider, remotePathMappingService, logger)
: base(configService, diskProvider, remotePathMappingService, logger)
{
_httpClient = httpClient;
_nzbValidationService = nzbValidationService;

View File

@@ -0,0 +1,28 @@
using System;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Exceptions
{
public class ReleaseBlockedException : ReleaseDownloadException
{
public ReleaseBlockedException(ReleaseInfo release, string message, params object[] args)
: base(release, message, args)
{
}
public ReleaseBlockedException(ReleaseInfo release, string message)
: base(release, message)
{
}
public ReleaseBlockedException(ReleaseInfo release, string message, Exception innerException, params object[] args)
: base(release, message, innerException, args)
{
}
public ReleaseBlockedException(ReleaseInfo release, string message, Exception innerException)
: base(release, message, innerException)
{
}
}
}

View File

@@ -2,45 +2,33 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Extras
{
public class ExistingExtraFileService : IHandle<MovieScannedEvent>
public interface IExistingExtraFiles
{
List<string> ImportExtraFiles(Movie movie, List<string> possibleExtraFiles);
}
public class ExistingExtraFileService : IExistingExtraFiles, IHandle<MovieScannedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly List<IImportExistingExtraFiles> _existingExtraFileImporters;
private readonly Logger _logger;
public ExistingExtraFileService(IDiskProvider diskProvider,
IDiskScanService diskScanService,
IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters,
public ExistingExtraFileService(IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters,
Logger logger)
{
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_existingExtraFileImporters = existingExtraFileImporters.OrderBy(e => e.Order).ToList();
_logger = logger;
}
public void Handle(MovieScannedEvent message)
public List<string> ImportExtraFiles(Movie movie, List<string> possibleExtraFiles)
{
var movie = message.Movie;
if (!_diskProvider.FolderExists(movie.Path))
{
return;
}
_logger.Debug("Looking for existing extra files in {0}", movie.Path);
var filesOnDisk = _diskScanService.GetNonVideoFiles(movie.Path);
var possibleExtraFiles = _diskScanService.FilterPaths(movie.Path, filesOnDisk, false);
var importedFiles = new List<string>();
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
@@ -50,6 +38,15 @@ namespace NzbDrone.Core.Extras
importedFiles.AddRange(imported.Select(f => Path.Combine(movie.Path, f.RelativePath)));
}
return importedFiles;
}
public void Handle(MovieScannedEvent message)
{
var movie = message.Movie;
var possibleExtraFiles = message.PossibleExtraFiles;
var importedFiles = ImportExtraFiles(movie, possibleExtraFiles);
_logger.Info("Found {0} possible extra files, imported {1} files.", possibleExtraFiles.Count, importedFiles.Count);
}
}

View File

@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Extras
{
public interface IExtraService
{
void MoveFilesAfterRename(Movie movie, MovieFile movieFile);
void ImportMovie(LocalMovie localMovie, MovieFile movieFile, bool isReadOnly);
}
@@ -139,6 +140,16 @@ namespace NzbDrone.Core.Extras
}
}
public void MoveFilesAfterRename(Movie movie, MovieFile movieFile)
{
var movieFiles = new List<MovieFile> { movieFile };
foreach (var extraFileManager in _extraFileManagers)
{
extraFileManager.MoveFilesAfterRename(movie, movieFiles);
}
}
public void Handle(MovieRenamedEvent message)
{
var movie = message.Movie;

View File

@@ -97,23 +97,21 @@ namespace NzbDrone.Core.History
public PagingSpec<MovieHistory> GetPaged(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities)
{
pagingSpec.Records = GetPagedRecords(PagedBuilder(pagingSpec, languages, qualities), pagingSpec, PagedQuery);
pagingSpec.Records = GetPagedRecords(PagedBuilder(languages, qualities), pagingSpec, PagedQuery);
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\"";
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder(pagingSpec, languages, qualities).Select(typeof(MovieHistory)), pagingSpec, countTemplate);
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder(languages, qualities).Select(typeof(MovieHistory)), pagingSpec, countTemplate);
return pagingSpec;
}
private SqlBuilder PagedBuilder(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities)
private SqlBuilder PagedBuilder(int[] languages, int[] qualities)
{
var builder = Builder()
.Join<MovieHistory, Movie>((h, m) => h.MovieId == m.Id)
.Join<Movie, QualityProfile>((m, p) => m.QualityProfileId == p.Id)
.LeftJoin<Movie, MovieMetadata>((m, mm) => m.MovieMetadataId == mm.Id);
AddFilters(builder, pagingSpec);
if (languages is { Length: > 0 })
{
builder.Where($"({BuildLanguageWhereClause(languages)})");

View File

@@ -20,12 +20,14 @@ namespace NzbDrone.Core.ImportLists
public abstract class HttpImportListBase<TSettings> : ImportListBase<TSettings>
where TSettings : IProviderConfig, new()
{
protected const int MaxNumResultsPerQuery = 1000;
protected readonly IHttpClient _httpClient;
public override bool Enabled => true;
public bool SupportsPaging => PageSize > 20;
public override bool EnableAuto => false;
public virtual int PageSize => 20;
public virtual int PageSize => 0;
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
public abstract IImportListRequestGenerator GetRequestGenerator();
@@ -71,6 +73,16 @@ namespace NzbDrone.Core.ImportLists
var page = FetchPage(request, parser);
pagedMovies.AddRange(page);
if (pagedMovies.Count >= MaxNumResultsPerQuery)
{
break;
}
if (!IsFullPage(page))
{
break;
}
}
movies.AddRange(pagedMovies.Where(IsValidItem));
@@ -161,6 +173,11 @@ namespace NzbDrone.Core.ImportLists
return true;
}
protected virtual bool IsFullPage(IList<ImportListMovie> page)
{
return PageSize != 0 && page.Count >= PageSize;
}
protected virtual IList<ImportListMovie> FetchPage(ImportListRequest request, IParseImportListResponse parser)
{
var response = FetchImportListResponse(request);

View File

@@ -3,15 +3,17 @@ using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.ImportLists.ImportListMovies;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Exceptions;
namespace NzbDrone.Core.ImportLists.Rss.Plex
{
public class PlexRssImportParser : RssImportBaseParser
{
private readonly Logger _logger;
public PlexRssImportParser(Logger logger)
: base(logger)
{
_logger = logger;
}
protected override ImportListMovie ProcessItem(XElement item)
@@ -45,7 +47,9 @@ namespace NzbDrone.Core.ImportLists.Rss.Plex
if (info.ImdbId.IsNullOrWhiteSpace() && info.TmdbId == 0)
{
throw new UnsupportedFeedException("Each item in the RSS feed must have a guid element with a IMDB ID or TMDB ID");
_logger.Warn("Each item in the RSS feed must have a guid element with a IMDB ID or TMDB ID: '{0}'", info.Title);
return null;
}
return info;

View File

@@ -19,8 +19,6 @@ namespace NzbDrone.Core.Indexers.FileList
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse,
@@ -28,6 +26,13 @@ namespace NzbDrone.Core.Indexers.FileList
indexerResponse.HttpResponse.StatusCode);
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
throw new IndexerException(indexerResponse, "Unexpected response header '{0}' from indexer request, expected '{1}'", indexerResponse.HttpResponse.Headers.ContentType, HttpAccept.Json.Value);
}
var torrentInfos = new List<ReleaseInfo>();
var queryResults = JsonConvert.DeserializeObject<List<FileListTorrent>>(indexerResponse.Content);
foreach (var result in queryResults)

View File

@@ -64,6 +64,9 @@ namespace NzbDrone.Core.Indexers.FileList
[FieldDefinition(7)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -65,6 +65,9 @@ namespace NzbDrone.Core.Indexers.HDBits
[FieldDefinition(9)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
[FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -53,6 +53,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents
[FieldDefinition(4)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers
{
@@ -9,5 +9,6 @@ namespace NzbDrone.Core.Indexers
// TODO: System.Text.Json requires setter be public for sub-object deserialization in 3.0. https://github.com/dotnet/corefx/issues/42515
SeedCriteriaSettings SeedCriteria { get; set; }
bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
}
}

View File

@@ -10,7 +10,6 @@ namespace NzbDrone.Core.Indexers.Nyaa
public override string Name => "Nyaa";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override int PageSize => 100;
public Nyaa(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger)
@@ -19,7 +18,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new NyaaRequestGenerator() { Settings = Settings, PageSize = PageSize };
return new NyaaRequestGenerator() { Settings = Settings };
}
public override IParseIndexerResponse GetParser()

View File

@@ -9,65 +9,44 @@ namespace NzbDrone.Core.Indexers.Nyaa
{
public NyaaSettings Settings { get; set; }
public int MaxPages { get; set; }
public int PageSize { get; set; }
public NyaaRequestGenerator()
{
MaxPages = 30;
PageSize = 100;
}
public virtual IndexerPageableRequestChain GetRecentRequests()
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(MaxPages, null));
pageableRequests.Add(GetPagedRequests(null));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, string term)
{
var baseUrl = string.Format("{0}/?page=rss{1}", Settings.BaseUrl.TrimEnd('/'), Settings.AdditionalParameters);
if (term != null)
{
baseUrl += "&term=" + term;
}
if (PageSize == 0)
{
yield return new IndexerRequest(baseUrl, HttpAccept.Rss);
}
else
{
yield return new IndexerRequest(baseUrl, HttpAccept.Rss);
for (var page = 1; page < maxPages; page++)
{
yield return new IndexerRequest($"{baseUrl}&offset={page + 1}", HttpAccept.Rss);
}
}
}
private string PrepareQuery(string query)
{
return query.Replace(' ', '+');
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
foreach (var queryTitle in searchCriteria.SceneTitles)
{
pageableRequests.Add(GetPagedRequests(MaxPages, PrepareQuery(string.Format("{0} {1}", queryTitle, searchCriteria.Movie.Year))));
pageableRequests.Add(GetPagedRequests(PrepareQuery($"{queryTitle} {searchCriteria.Movie.Year}")));
}
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
{
var baseUrl = $"{Settings.BaseUrl.TrimEnd('/')}/?page=rss{Settings.AdditionalParameters}";
if (term != null)
{
baseUrl += "&term=" + term;
}
yield return new IndexerRequest(baseUrl, HttpAccept.Rss);
}
private string PrepareQuery(string query)
{
return query.Replace(' ', '+');
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}

View File

@@ -51,6 +51,9 @@ namespace NzbDrone.Core.Indexers.Nyaa
[FieldDefinition(5)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -47,11 +47,14 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
[FieldDefinition(4, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(5)]
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(6)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{

View File

@@ -45,11 +45,14 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
[FieldDefinition(4, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(5)]
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(6)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public NzbDroneValidationResult Validate()
{

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