1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-17 21:26:13 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Taloth Saldono
6143d20462 Untested code to get a cached SeedCriteria by infohash to rtorrent handler 2021-02-04 00:16:36 +01:00
239 changed files with 1827 additions and 3904 deletions

View File

@@ -2,7 +2,7 @@
name: Bug Report
about: Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit, Discord, Forums, or IRC first. Exceptions do not mean you found a bug!
title: ''
labels: ''
labels: 'bug'
assignees: ''
---
@@ -32,6 +32,5 @@ assignees: ''
- Sonarr Branch: <!--[e.g. master, develop , phantom-develop]-->
**Trace Logs**
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
<!-- Trace logs are named Sonarr.trace.txt or Sonarr.trace.#.txt and will contain "trace" in them-->
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**

View File

@@ -1,20 +1,14 @@
---
name: Feature request
about: Suggest an idea for Sonarr
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the problem**
A clear and concise description of the problem you're looking to solve.
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Describe any solutions you think might work**
A clear and concise description of any solutions or features you've considered.
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Add any other context or screenshots about the feature request here.

View File

@@ -17,7 +17,7 @@ Setup guides, [FAQ](https://wiki.servarr.com/Sonarr_FAQ), the more information w
### Getting started ###
1. Fork Sonarr
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
2. Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
3. Install the required Node Packages `yarn install`
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
5. Build the project in Visual Studio, Setting startup project to `Sonarr.Console` and framework to `x86`

View File

@@ -1,5 +1,4 @@
fromdos ./debian/*
chmod ugo-x ./debian/*
cp -r ./debian ./debian_backup
BuildVersion=${dependent_build_number:-3.10.0.999}

View File

@@ -1 +1 @@
10
8

View File

@@ -8,10 +8,7 @@ db_input high sonarr/owning_group || true
db_endblock
db_go
db_beginblock
db_input low sonarr/owning_umask || true
db_input low sonarr/config_directory || true
db_endblock
db_go
exit 0

View File

@@ -9,8 +9,6 @@ db_get sonarr/owning_user
USER="$RET"
db_get sonarr/owning_group
GROUP="$RET"
db_get sonarr/owning_umask
UMASK="$RET"
db_get sonarr/config_directory
CONFDIR="$RET"
@@ -66,11 +64,9 @@ fi
# Create data directory
if [ ! -d "$CONFDIR" ]; then
mkdir -p "$CONFDIR"
chown -R $USER:$GROUP "$CONFDIR"
fi
# Set permissions on data directory (always do this instead only on creation in case user was changed via dpkg-reconfigure)
chown -R $USER:$GROUP "$CONFDIR"
#BEGIN BUILTIN UPDATER
# Apply patch if present
if [ "$UPDATER" = "BuiltIn" ] && [ -f /usr/lib/sonarr/bin_patch/release_info ]; then
@@ -96,7 +92,7 @@ fi
chown -R $USER:$GROUP /usr/lib/sonarr
# Update sonarr.service file
sed -i "s:User=\w*:User=$USER:g; s:Group=\w*:Group=$GROUP:g; s:UMask=[0-9]*:UMask=$UMASK:g; s:-data=.*$:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
sed -i "s:User=sonarr:User=$USER:g; s:Group=sonarr:Group=$GROUP:g; s:-data=/var/lib/sonarr:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
#BEGIN BUILTIN UPDATER
if [ "$UPDATER" = "BuiltIn" ]; then

View File

@@ -1,3 +1,2 @@
ignores msbuild
ignores libmediainfo0v5
ignores libc6

View File

@@ -1,6 +1,3 @@
# This file is owned by the sonarr package, DO NOT MODIFY MANUALLY
# Instead use 'dpkg-reconfigure -plow sonarr' to modify User/Group/UMask/-data
# Or use systemd built-in override functionality using 'systemctl edit sonarr'
[Unit]
Description=Sonarr Daemon
After=network.target

View File

@@ -14,12 +14,6 @@ Description: Sonarr group:
Any media files created by Sonarr will be writeable by this group.
It's advisable to keep the group the same between download client, Sonarr and media centers.
Template: sonarr/owning_umask
Type: string
Default: 0002
Description: Sonarr umask:
Specifies the umask of the files created by Sonarr. 0002 means the files will be created with 664 as permissions.
Template: sonarr/config_directory
Type: string
Default: /var/lib/sonarr

View File

@@ -1,25 +1,16 @@
FROM ubuntu:focal AS builder
FROM ubuntu:xenial AS builder
ENV DEBIAN_FRONTEND noninteractive
ENV MONO_VERSION 5.18
RUN apt-get update && \
apt-get -y -o Dpkg::Options::="--force-confold" install --no-install-recommends \
apt-transport-https \
wget dirmngr gpg gpg-agent \
# add-apt-repository for PPAs
software-properties-common && \
rm -rf /var/lib/apt/lists/*
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian stable-focal main" > /etc/apt/sources.list.d/mono-official-stable.list && \
echo "deb http://download.mono-project.com/repo/debian stable-xenial/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-get update && apt-get install -y \
devscripts build-essential tofrodos \
dh-make dh-systemd \
cli-common-dev \
mono-complete \
sqlite3 libcurl4 mediainfo
RUN apt-get upgrade -y
sqlite3 libcurl3 mediainfo
RUN apt-cache policy mono-complete
RUN apt-cache policy cli-common-dev

View File

@@ -63,9 +63,9 @@ Name: "{commonappdata}\NzbDrone\bin"; Type: filesandordirs
Name: "{app}"; Type: filesandordirs
[Run]
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
Filename: "{app}\Sonarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u"; Flags: runhidden waituntilterminated;
Filename: "{app}\Sonarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\Sonarr.exe"; Description: "Open Sonarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
Filename: "{app}\Sonarr.exe"; Description: "Start Sonarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;

View File

@@ -1,8 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatAge from 'Utilities/Number/formatAge';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import formatAge from 'Utilities/Number/formatAge';
import Link from 'Components/Link/Link';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
@@ -23,7 +22,6 @@ function HistoryDetails(props) {
const {
indexer,
releaseGroup,
preferredWordScore,
nzbInfoUrl,
downloadClient,
downloadId,
@@ -42,35 +40,24 @@ function HistoryDetails(props) {
/>
{
indexer ?
!!indexer &&
<DescriptionListItem
title="Indexer"
data={indexer}
/> :
null
/>
}
{
releaseGroup ?
!!releaseGroup &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Release Group"
data={releaseGroup}
/> :
null
/>
}
{
preferredWordScore && preferredWordScore !== '0' ?
<DescriptionListItem
title="Preferred Word Score"
data={formatPreferredWordScore(preferredWordScore)}
/> :
null
}
{
nzbInfoUrl ?
!!nzbInfoUrl &&
<span>
<DescriptionListItemTitle>
Info URL
@@ -79,44 +66,39 @@ function HistoryDetails(props) {
<DescriptionListItemDescription>
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
</DescriptionListItemDescription>
</span> :
null
</span>
}
{
downloadClient ?
!!downloadClient &&
<DescriptionListItem
title="Download Client"
data={downloadClient}
/> :
null
/>
}
{
downloadId ?
!!downloadId &&
<DescriptionListItem
title="Grab ID"
data={downloadId}
/> :
null
/>
}
{
age || ageHours || ageMinutes ?
!!indexer &&
<DescriptionListItem
title="Age (when grabbed)"
data={formatAge(age, ageHours, ageMinutes)}
/> :
null
/>
}
{
publishedDate ?
!!publishedDate &&
<DescriptionListItem
title="Published Date"
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/> :
null
/>
}
</DescriptionList>
);
@@ -136,12 +118,11 @@ function HistoryDetails(props) {
/>
{
message ?
!!message &&
<DescriptionListItem
title="Message"
data={message}
/> :
null
/>
}
</DescriptionList>
);
@@ -149,7 +130,6 @@ function HistoryDetails(props) {
if (eventType === 'downloadFolderImported') {
const {
preferredWordScore,
droppedPath,
importedPath
} = data;
@@ -163,32 +143,21 @@ function HistoryDetails(props) {
/>
{
droppedPath ?
!!droppedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Source"
data={droppedPath}
/> :
null
/>
}
{
importedPath ?
!!importedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Imported To"
data={importedPath}
/> :
null
}
{
preferredWordScore && preferredWordScore !== '0' ?
<DescriptionListItem
title="Preferred Word Score"
data={formatPreferredWordScore(preferredWordScore)}
/> :
null
/>
}
</DescriptionList>
);
@@ -196,8 +165,7 @@ function HistoryDetails(props) {
if (eventType === 'episodeFileDeleted') {
const {
reason,
preferredWordScore
reason
} = data;
let reasonMessage = '';
@@ -227,15 +195,6 @@ function HistoryDetails(props) {
title="Reason"
data={reasonMessage}
/>
{
preferredWordScore && preferredWordScore !== '0' ?
<DescriptionListItem
title="Preferred Word Score"
data={formatPreferredWordScore(preferredWordScore)}
/> :
null
}
</DescriptionList>
);
}
@@ -287,12 +246,11 @@ function HistoryDetails(props) {
/>
{
message ?
!!message &&
<DescriptionListItem
title="Message"
data={message}
/> :
null
/>
}
</DescriptionList>
);

View File

@@ -10,12 +10,6 @@
width: 80px;
}
.preferredWordScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}
.releaseGroup {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import { icons } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
@@ -195,17 +194,6 @@ class HistoryRow extends Component {
);
}
if (name === 'preferredWordScore') {
return (
<TableRowCell
key={name}
className={styles.preferredWordScore}
>
{formatPreferredWordScore(data.preferredWordScore)}
</TableRowCell>
);
}
if (name === 'releaseGroup') {
return (
<TableRowCell

View File

@@ -44,8 +44,12 @@ class Queue extends Component {
};
}
shouldComponentUpdate() {
if (this._shouldBlockRefresh) {
shouldComponentUpdate(nextProps) {
if (!this._shouldBlockRefresh) {
return true;
}
if (hasDifferentItems(this.props.items, nextProps.items)) {
return false;
}
@@ -111,20 +115,19 @@ class Queue extends Component {
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true }, () => {
this._shouldBlockRefresh = true;
});
this._shouldBlockRefresh = true;
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = (payload) => {
this._shouldBlockRefresh = false;
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false });
this._shouldBlockRefresh = false;
}
onConfirmRemoveModalClose = () => {
this._shouldBlockRefresh = false;
this.setState({ isConfirmRemoveModalOpen: false });
this._shouldBlockRefresh = false;
}
//

View File

@@ -3,7 +3,3 @@
width: 30px;
}
.noMessages {
margin-bottom: 10px;
}

View File

@@ -12,10 +12,7 @@ function getDetailedPopoverBody(statusMessages) {
{
statusMessages.map(({ title, messages }) => {
return (
<div
key={title}
className={messages.length ? undefined: styles.noMessages}
>
<div key={title}>
{title}
<ul>
{

View File

@@ -30,7 +30,9 @@ class AddNewSeriesModalContent extends Component {
this.state = {
seriesType: props.initialSeriesType === seriesTypes.STANDARD ?
props.seriesType.value :
props.initialSeriesType
props.initialSeriesType,
searchForMissingEpisodes: false,
searchForCutoffUnmetEpisodes: false
};
}
@@ -43,6 +45,14 @@ class AddNewSeriesModalContent extends Component {
//
// Listeners
onSearchForMissingEpisodesChange = ({ value }) => {
this.setState({ searchForMissingEpisodes: value });
}
onSearchForCutoffUnmetEpisodesChange = ({ value }) => {
this.setState({ searchForCutoffUnmetEpisodes: value });
}
onQualityProfileIdChange = ({ value }) => {
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
}
@@ -53,10 +63,14 @@ class AddNewSeriesModalContent extends Component {
onAddSeriesPress = () => {
const {
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
seriesType
} = this.state;
this.props.onAddSeriesPress(
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
seriesType
);
}
@@ -77,8 +91,6 @@ class AddNewSeriesModalContent extends Component {
languageProfileId,
seriesType,
seasonFolder,
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
folder,
tags,
showLanguageProfile,
@@ -89,6 +101,11 @@ class AddNewSeriesModalContent extends Component {
...otherProps
} = this.props;
const {
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -254,8 +271,8 @@ class AddNewSeriesModalContent extends Component {
containerClassName={styles.searchInputContainer}
className={styles.searchInput}
name="searchForMissingEpisodes"
onChange={onInputChange}
{...searchForMissingEpisodes}
value={searchForMissingEpisodes}
onChange={this.onSearchForMissingEpisodesChange}
/>
</label>
@@ -268,8 +285,8 @@ class AddNewSeriesModalContent extends Component {
containerClassName={styles.searchInputContainer}
className={styles.searchInput}
name="searchForCutoffUnmetEpisodes"
onChange={onInputChange}
{...searchForCutoffUnmetEpisodes}
value={searchForCutoffUnmetEpisodes}
onChange={this.onSearchForCutoffUnmetEpisodesChange}
/>
</label>
</div>
@@ -302,8 +319,6 @@ AddNewSeriesModalContent.propTypes = {
languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired,
searchForMissingEpisodes: PropTypes.object.isRequired,
searchForCutoffUnmetEpisodes: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,

View File

@@ -55,7 +55,7 @@ class AddNewSeriesModalContentConnector extends Component {
this.props.setAddSeriesDefault({ [name]: value });
}
onAddSeriesPress = (seriesType) => {
onAddSeriesPress = (searchForMissingEpisodes, searchForCutoffUnmetEpisodes, seriesType) => {
const {
tvdbId,
rootFolderPath,
@@ -63,8 +63,6 @@ class AddNewSeriesModalContentConnector extends Component {
qualityProfileId,
languageProfileId,
seasonFolder,
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
tags
} = this.props;
@@ -76,9 +74,9 @@ class AddNewSeriesModalContentConnector extends Component {
languageProfileId: languageProfileId.value,
seriesType,
seasonFolder: seasonFolder.value,
searchForMissingEpisodes: searchForMissingEpisodes.value,
searchForCutoffUnmetEpisodes: searchForCutoffUnmetEpisodes.value,
tags: tags.value
tags: tags.value,
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes
});
}
@@ -104,8 +102,6 @@ AddNewSeriesModalContentConnector.propTypes = {
languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired,
searchForMissingEpisodes: PropTypes.object.isRequired,
searchForCutoffUnmetEpisodes: PropTypes.object.isRequired,
tags: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired,
setAddSeriesDefault: PropTypes.func.isRequired,

View File

@@ -85,21 +85,3 @@
display: inline-block;
margin: 5px -5px 5px 0;
}
.mobileCloseButtonContainer {
display: flex;
justify-content: flex-end;
height: 40px;
border-bottom: 1px solid $borderColor;
}
.mobileCloseButton {
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
&:hover {
color: $modalCloseButtonHoverColor;
}
}

View File

@@ -518,18 +518,6 @@ class EnhancedSelectInput extends Component {
scrollDirection={scrollDirections.NONE}
>
<Scroller className={styles.optionsModalScroller}>
<div className={styles.mobileCloseButtonContainer}>
<Link
className={styles.mobileCloseButton}
onPress={this.onOptionsModalClose}
>
<Icon
name={icons.CLOSE}
size={18}
/>
</Link>
</div>
{
values.map((v, index) => {
const hasParent = v.parentKey !== undefined;

View File

@@ -54,8 +54,4 @@
&:last-child {
border: none;
}
&:hover {
background-color: unset;
}
}

View File

@@ -12,9 +12,7 @@ class EnhancedSelectInputOption extends Component {
//
// Listeners
onPress = (e) => {
e.preventDefault();
onPress = () => {
const {
id,
onSelect

View File

@@ -10,16 +10,13 @@ const ADD_NEW_KEY = 'addNew';
function createMapStateToProps() {
return createSelector(
(state) => state.rootFolders,
(state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, value, includeMissingValue, includeNoChange) => {
(rootFolders, includeNoChange) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
freeSpace: rootFolder.freeSpace,
isMissing: false
freeSpace: rootFolder.freeSpace
};
});
@@ -27,8 +24,7 @@ function createMapStateToProps() {
values.unshift({
key: 'noChange',
value: 'No Change',
isDisabled: true,
isMissing: false
isDisabled: true
});
}
@@ -41,15 +37,6 @@ function createMapStateToProps() {
});
}
if (includeMissingValue && !values.find((v) => v.key === value)) {
values.push({
key: value,
value,
isMissing: true,
isDisabled: true
});
}
values.push({
key: ADD_NEW_KEY,
value: 'Add a new path'

View File

@@ -27,9 +27,3 @@
color: $darkGray;
font-size: $smallFontSize;
}
.isMissing {
margin-left: 15px;
color: $dangerColor;
font-size: $smallFontSize;
}

View File

@@ -10,7 +10,6 @@ function RootFolderSelectInputOption(props) {
id,
value,
freeSpace,
isMissing,
seriesFolder,
isMobile,
isWindows,
@@ -44,20 +43,11 @@ function RootFolderSelectInputOption(props) {
</div>
{
freeSpace == null ?
null :
freeSpace != null &&
<div className={styles.freeSpace}>
{formatBytes(freeSpace)} Free
</div>
}
{
isMissing ?
<div className={styles.isMissing}>
Missing
</div> :
null
}
</div>
</EnhancedSelectInputOption>
);
@@ -67,7 +57,6 @@ RootFolderSelectInputOption.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
isMissing: PropTypes.boolean,
seriesFolder: PropTypes.string,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool

View File

@@ -1,5 +1,5 @@
.loadingMessage {
margin: 10px 10px 0;
margin: 50px 10px 0;
text-align: center;
font-weight: 300;
font-size: 36px;

View File

@@ -1,12 +1,3 @@
.page {
composes: page from '~./Page.css';
}
.logoFull {
margin-top: 50px;
margin-right: auto;
margin-left: auto;
width: 48px;
height: 48px;
opacity: 0.65;
}

View File

@@ -3,15 +3,9 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import LoadingMessage from 'Components/Loading/LoadingMessage';
import styles from './LoadingPage.css';
const sonarrLogo = 'data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjIxNi45IiB2aWV3Qm94PSIwIDAgMjE2LjcgMjE2LjkiIHdpZHRoPSIyMTYuNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMTYuNyAxMDguNDVjMCAyOS44MzMtMTAuNTMzIDU1LjQtMzEuNiA3Ni43LS43LjgzMy0xLjQ4MyAxLjYtMi4zNSAyLjMtMy40NjYgMy40LTcuMTMzIDYuNDg0LTExIDkuMjUtMTguMjY3IDEzLjQ2Ny0zOS4zNjcgMjAuMi02My4zIDIwLjItMjMuOTY3IDAtNDUuMDMzLTYuNzMzLTYzLjItMjAuMi00LjgtMy40LTkuMy03LjI1LTEzLjUtMTEuNTUtMTYuMzY3LTE2LjI2Ni0yNi40MTctMzUuMTY3LTMwLjE1LTU2LjctLjczMy00LjItMS4yMTctOC40NjctMS40NS0xMi44LS4xLTIuNC0uMTUtNC44LS4xNS03LjIgMC0yLjUzMy4wNS00Ljk1LjE1LTcuMjUgMC0uMjMzLjA2Ni0uNDY3LjItLjcgMS41NjctMjYuNiAxMi4wMzMtNDkuNTgzIDMxLjQtNjguOTVDNTMuMDUgMTAuNTE3IDc4LjYxNyAwIDEwOC40NSAwYzI5LjkzMyAwIDU1LjQ4NCAxMC41MTcgNzYuNjUgMzEuNTUgMjEuMDY3IDIxLjQzMyAzMS42IDQ3LjA2NyAzMS42IDc2Ljl6IiBmaWxsPSIjRUVFIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KICA8cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xOTQuNjUgNDIuNWwtMjIuNCAyMi40QzE1OS4xNTIgNzcuOTk4IDE1OCA4OS40IDE1OCAxMDkuNWMwIDE3LjkzNCAyLjg1MiAzNC4zNTIgMTYuMiA0Ny43IDkuNzQ2IDkuNzQ2IDE5IDE4Ljk1IDE5IDE4Ljk1LTIuNSAzLjA2Ny01LjIgNi4wNjctOC4xIDktLjcuODMzLTEuNDgzIDEuNi0yLjM1IDIuMy0yLjUzMyAyLjUtNS4xNjcgNC44MTctNy45IDYuOTVsLTE3LjU1LTE3LjU1Yy0xNS41OTgtMTUuNi0yNy45OTYtMTcuMS00OC42LTE3LjEtMTkuNzcgMC0zMy4yMjMgMS44MjItNDcuNyAxNi4zLTguNjQ3IDguNjQ3LTE4LjU1IDE4LjYtMTguNTUgMTguNi0zLjc2Ny0yLjg2Ny03LjMzMy02LjAzNC0xMC43LTkuNS0yLjgtMi44LTUuNDE3LTUuNjY3LTcuODUtOC42IDAgMCA5Ljc5OC05Ljg0OCAxOS4xNS0xOS4yIDEzLjg1Mi0xMy44NTMgMTYuMS0yOS45MTYgMTYuMS00Ny44NSAwLTE3LjUtMi44NzQtMzMuODIzLTE1LjYtNDYuNTUtOC44MzUtOC44MzYtMjEuMDUtMjEtMjEuMDUtMjEgMi44MzMtMy42IDUuOTE3LTcuMDY3IDkuMjUtMTAuNCAyLjkzNC0yLjg2NyA1LjkzNC01LjU1IDktOC4wNUw2MS4xIDQzLjg1Qzc0LjEwMiA1Ni44NTIgOTAuNzY3IDYwLjIgMTA4LjcgNjAuMmMxOC40NjcgMCAzNS4wNzctMy41NzcgNDguNi0xNy4xIDguMzItOC4zMiAxOS4zLTE5LjI1IDE5LjMtMTkuMjUgMi45IDIuMzY3IDUuNzMzIDQuOTMzIDguNSA3LjcgMy40NjcgMy41MzMgNi42NSA3LjE4MyA5LjU1IDEwLjk1eiIgZmlsbD0iIzNBM0Y1MSIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiAgPGcgY2xpcC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik03OC43IDExNGMtLjItMS4xNjctLjMzMi0yLjM1LS40LTMuNTUtLjAzMi0uNjY3LS4wNS0xLjMzMy0uMDUtMiAwLS43LjAxOC0xLjM2Ny4wNS0yIDAtLjA2Ny4wMTgtLjEzMy4wNS0uMi40MzUtNy4zNjcgMy4zMzQtMTMuNzMzIDguNy0xOS4xIDUuOS01LjgzMyAxMi45ODQtOC43NSAyMS4yNS04Ljc1IDguMyAwIDE1LjM4NCAyLjkxNyAyMS4yNSA4Ljc1IDUuODM0IDUuOTM0IDguNzUgMTMuMDMzIDguNzUgMjEuMyAwIDguMjY3LTIuOTE2IDE1LjM1LTguNzUgMjEuMjUtLjIuMjMzLS40MTYuNDUtLjY1LjY1LS45NjYuOTMzLTEuOTgyIDEuNzgzLTMuMDUgMi41NS01LjA2NSAzLjczMy0xMC45MTYgNS42LTE3LjU1IDUuNnMtMTIuNDY2LTEuODY2LTE3LjUtNS42Yy0xLjMzMi0uOTM0LTIuNTgyLTItMy43NS0zLjItNC41MzItNC41LTcuMzE2LTkuNzM0LTguMzUtMTUuN3oiIGZpbGw9IiMwQ0YiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgogICAgPHBhdGggZD0iTTE1Ny44IDU5Ljc1bC0xNSAxNC42NU0zMC43ODUgMzIuNTI2TDcxLjY1IDczLjI1bTg0LjYgODQuMjVsMjcuODA4IDI4Ljc4bTEuODU1LTE1My44OTRMMTU3LjggNTkuNzVtLTEyNS40NSAxMjZsMjcuMzUtMjcuNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMENGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMSIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICA8cGF0aCBkPSJNMTU3LjggNTkuNzVsLTE2Ljk1IDE3LjJNNTguOTcgNjAuNjA0bDE3LjIgMTcuMTVNNTkuNjIzIDE1OC40M2wxNi43NS0xNy40bTYxLjkyOC0xLjM5NmwxOC4wMjggMTcuOTQ1IiBmaWxsPSJub25lIiBzdHJva2U9IiMwQ0YiIHN0cm9rZS1taXRlcmxpbWl0PSIxIiBzdHJva2Utd2lkdGg9IjciLz4KICA8L2c+Cjwvc3ZnPg==';
function LoadingPage() {
return (
<div className={styles.page}>
<img
className={styles.logoFull}
src={sonarrLogo}
/>
<LoadingMessage />
<LoadingIndicator />
</div>

View File

@@ -191,7 +191,7 @@ class TableOptionsModal extends Component {
<TableOptionsColumnDragSource
key={name}
name={name}
label={columnLabel || label}
label={label || columnLabel}
isVisible={isVisible}
isModifiable={true}
index={index}
@@ -209,7 +209,7 @@ class TableOptionsModal extends Component {
<TableOptionsColumn
key={name}
name={name}
label={columnLabel || label}
label={label || columnLabel}
isVisible={isVisible}
index={index}
isModifiable={false}

View File

@@ -14,7 +14,6 @@
&.inverse {
background-color: $themeDarkColor;
box-shadow: 0 5px 10px $popoverShadowInverseColor;
color: $white;
}
}

View File

@@ -6,24 +6,6 @@ import EpisodeDetailsModalContentConnector from './EpisodeDetailsModalContentCon
class EpisodeDetailsModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
closeOnBackgroundClick: false
};
}
//
// Listeners
onTabChange = (isSearch) => {
this.setState({ closeOnBackgroundClick: !isSearch });
}
//
// Render
@@ -38,12 +20,10 @@ class EpisodeDetailsModal extends Component {
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
closeOnBackgroundClick={this.state.closeOnBackgroundClick}
onModalClose={onModalClose}
>
<EpisodeDetailsModalContentConnector
{...otherProps}
onTabChange={this.onTabChange}
onModalClose={onModalClose}
/>
</Modal>

View File

@@ -37,9 +37,7 @@ class EpisodeDetailsModalContent extends Component {
// Listeners
onTabSelect = (index, lastIndex) => {
const selectedTab = tabs[index];
this.props.onTabChange(selectedTab === 'search');
this.setState({ selectedTab });
this.setState({ selectedTab: tabs[index] });
}
//
@@ -208,7 +206,6 @@ EpisodeDetailsModalContent.propTypes = {
selectedTab: PropTypes.string.isRequired,
startInteractiveSearch: PropTypes.bool.isRequired,
onMonitorEpisodePress: PropTypes.func.isRequired,
onTabChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -300,7 +300,7 @@ class InteractiveImportModalContent extends Component {
isPopulated && !!items.length && !isFetching && !isFetching &&
<Table
columns={columns}
horizontalScroll={true}
horizontalScroll={false}
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}

View File

@@ -3,7 +3,6 @@ import React, { Component } from 'react';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatBytes from 'Utilities/Number/formatBytes';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import Icon from 'Components/Icon';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
@@ -194,7 +193,8 @@ class InteractiveSearchRow extends Component {
</TableRowCell>
<TableRowCell className={styles.preferredWordScore}>
{formatPreferredWordScore(preferredWordScore)}
{preferredWordScore > 0 && `+${preferredWordScore}`}
{preferredWordScore < 0 && preferredWordScore}
</TableRowCell>
<TableRowCell className={styles.rejected}>

View File

@@ -26,7 +26,7 @@ function SeriesAlternateTitles({ alternateTitles }) {
}
SeriesAlternateTitles.propTypes = {
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
};
export default SeriesAlternateTitles;

View File

@@ -119,13 +119,6 @@
margin: 5px 10px 5px 0;
}
.fileCountMessage {
padding: 5px;
white-space: nowrap;
font-weight: 300;
font-size: 15px;
}
.path,
.sizeOnDisk,
.qualityProfileName,

View File

@@ -432,32 +432,22 @@ class SeriesDetails extends Component {
</span>
</Label>
<Tooltip
anchor={
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.DRIVE}
size={17}
/>
<Label
className={styles.detailsLabel}
title={episodeFilesCountMessage}
size={sizes.LARGE}
>
<Icon
name={icons.DRIVE}
size={17}
/>
<span className={styles.sizeOnDisk}>
{
formatBytes(sizeOnDisk || 0)
}
</span>
</Label>
}
tooltip={
<span>
{episodeFilesCountMessage}
</span>
}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
<span className={styles.sizeOnDisk}>
{
formatBytes(sizeOnDisk || 0)
}
</span>
</Label>
<Label
className={styles.detailsLabel}
@@ -704,7 +694,7 @@ SeriesDetails.propTypes = {
overview: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
seasons: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,

View File

@@ -131,7 +131,6 @@ function EditImportListModalContent(props) {
name="rootFolderPath"
helpText={'Root Folder list items will be added to'}
{...rootFolderPath}
includeMissingValue={true}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -23,8 +23,8 @@ const separatorOptions = [
const caseOptions = [
{ key: 'title', value: 'Default Case' },
{ key: 'lower', value: 'Lowercase' },
{ key: 'upper', value: 'Uppercase' }
{ key: 'lower', value: 'Lower Case' },
{ key: 'upper', value: 'Upper Case' }
];
const fileNameTokens = [

View File

@@ -9,12 +9,3 @@
margin-bottom: 30px;
}
.triggers {
margin-top: 3px;
}
.triggerEvents {
margin-top: 10px;
user-select: none;
}

View File

@@ -13,7 +13,6 @@ import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
import styles from './EditNotificationModalContent.css';
@@ -103,111 +102,132 @@ function EditNotificationModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Triggers</FormLabel>
<FormLabel>On Grab</FormLabel>
<div className={styles.triggers}>
<FormInputHelpText
text="Select which events should trigger this conection"
link="https://wiki.servarr.com/Sonarr_Settings#Connections"
/>
<div className={styles.triggerEvents}>
<FormInputGroup
type={inputTypes.CHECK}
name="onGrab"
helpText="On Grab"
isDisabled={!supportsOnGrab.value}
{...onGrab}
onChange={onInputChange}
/>
<FormInputGroup
type={inputTypes.CHECK}
name="onDownload"
helpText="On Import"
isDisabled={!supportsOnDownload.value}
{...onDownload}
onChange={onInputChange}
/>
{
onDownload.value ?
<FormInputGroup
type={inputTypes.CHECK}
name="onUpgrade"
helpText="On Upgrade"
isDisabled={!supportsOnUpgrade.value}
{...onUpgrade}
onChange={onInputChange}
/> :
null
}
<FormInputGroup
type={inputTypes.CHECK}
name="onRename"
helpText="On Rename"
isDisabled={!supportsOnRename.value}
{...onRename}
onChange={onInputChange}
/>
<FormInputGroup
type={inputTypes.CHECK}
name="onSeriesDelete"
helpText="On Series Delete"
isDisabled={!supportsOnSeriesDelete.value}
{...onSeriesDelete}
onChange={onInputChange}
/>
<FormInputGroup
type={inputTypes.CHECK}
name="onEpisodeFileDelete"
helpText="On Episode File Delete"
isDisabled={!supportsOnEpisodeFileDelete.value}
{...onEpisodeFileDelete}
onChange={onInputChange}
/>
{
onEpisodeFileDelete.value ?
<FormInputGroup
type={inputTypes.CHECK}
name="onEpisodeFileDeleteForUpgrade"
helpText="On Episode File Delete For Upgrade"
isDisabled={!supportsOnEpisodeFileDeleteForUpgrade.value}
{...onEpisodeFileDeleteForUpgrade}
onChange={onInputChange}
/> :
null
}
<FormInputGroup
type={inputTypes.CHECK}
name="onHealthIssue"
helpText="On Health Issue"
isDisabled={!supportsOnHealthIssue.value}
{...onHealthIssue}
onChange={onInputChange}
/>
{
onHealthIssue.value ?
<FormInputGroup
type={inputTypes.CHECK}
name="includeHealthWarnings"
helpText="Include Health Warnings"
isDisabled={!supportsOnHealthIssue.value}
{...includeHealthWarnings}
onChange={onInputChange}
/> :
null
}
</div>
</div>
<FormInputGroup
type={inputTypes.CHECK}
name="onGrab"
helpText="Be notified when episodes are available for download and has been sent to a download client"
isDisabled={!supportsOnGrab.value}
{...onGrab}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>On Import</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onDownload"
helpText="Be notified when episodes are successfully imported"
isDisabled={!supportsOnDownload.value}
{...onDownload}
onChange={onInputChange}
/>
</FormGroup>
{
onDownload.value &&
<FormGroup>
<FormLabel>On Upgrade</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onUpgrade"
helpText="Be notified when episodes are upgraded to a better quality"
isDisabled={!supportsOnUpgrade.value}
{...onUpgrade}
onChange={onInputChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>On Rename</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onRename"
helpText="Be notified when episodes are renamed"
isDisabled={!supportsOnRename.value}
{...onRename}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>On Series Delete</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onSeriesDelete"
helpText="Be notified when series are deleted"
isDisabled={!supportsOnSeriesDelete.value}
{...onSeriesDelete}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>On Episode File Delete</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onEpisodeFileDelete"
helpText="Be notified when episode files are deleted"
isDisabled={!supportsOnEpisodeFileDelete.value}
{...onEpisodeFileDelete}
onChange={onInputChange}
/>
</FormGroup>
{
onEpisodeFileDelete.value ?
<FormGroup>
<FormLabel>On Episode File Delete For Upgrade</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onEpisodeFileDeleteForUpgrade"
helpText="Be notified when episode files are deleted for upgrades"
isDisabled={!supportsOnEpisodeFileDeleteForUpgrade.value}
{...onEpisodeFileDeleteForUpgrade}
onChange={onInputChange}
/>
</FormGroup> :
null
}
<FormGroup>
<FormLabel>On Health Issue</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onHealthIssue"
helpText="Be notified on health check failures"
isDisabled={!supportsOnHealthIssue.value}
{...onHealthIssue}
onChange={onInputChange}
/>
</FormGroup>
{
onHealthIssue.value &&
<FormGroup>
<FormLabel>Include Health Warnings</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeHealthWarnings"
helpText="Be notified on health warnings in addition to errors"
isDisabled={!supportsOnHealthIssue.value}
{...includeHealthWarnings}
onChange={onInputChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>Tags</FormLabel>

View File

@@ -39,7 +39,6 @@ function EditDelayProfileModalContent(props) {
enableTorrent,
usenetDelay,
torrentDelay,
bypassIfHighestQuality,
tags
} = item;
@@ -108,20 +107,6 @@ function EditDelayProfileModalContent(props) {
</FormGroup>
}
{
<FormGroup>
<FormLabel>Bypass if Highest Quality</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="bypassIfHighestQuality"
{...bypassIfHighestQuality}
helpText="Bypass delay when release has the highest enabled quality in the quality profile"
onChange={onInputChange}
/>
</FormGroup>
}
{
id === 1 ?
<Alert>

View File

@@ -29,7 +29,6 @@ function EditReleaseProfileModalContent(props) {
const {
id,
name,
enabled,
required,
ignored,
@@ -47,20 +46,6 @@ function EditReleaseProfileModalContent(props) {
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="name"
{...name}
placeholder="Optional name"
canEdit={true}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Enable Profile</FormLabel>

View File

@@ -9,11 +9,3 @@
flex-wrap: wrap;
margin-top: 5px;
}
.name {
@add-mixin truncate;
margin-bottom: 20px;
font-weight: 300;
font-size: 24px;
}

View File

@@ -56,7 +56,6 @@ class ReleaseProfile extends Component {
render() {
const {
id,
name,
enabled,
required,
ignored,
@@ -80,14 +79,6 @@ class ReleaseProfile extends Component {
overlayContent={true}
onPress={this.onEditReleaseProfilePress}
>
{
name ?
<div className={styles.name}>
{name}
</div> :
null
}
<div>
{
split(required).map((item) => {
@@ -193,7 +184,6 @@ class ReleaseProfile extends Component {
ReleaseProfile.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string,
enabled: PropTypes.bool.isRequired,
required: PropTypes.string.isRequired,
ignored: PropTypes.string.isRequired,

View File

@@ -37,8 +37,6 @@ export const defaultState = {
languageProfileId: 0,
seriesType: seriesTypes.STANDARD,
seasonFolder: true,
searchForMissingEpisodes: false,
searchForCutoffUnmetEpisodes: false,
tags: []
}
};

View File

@@ -1,9 +1,7 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import { createThunk, handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
function getDimensions(width, height) {
@@ -24,8 +22,6 @@ function getDimensions(width, height) {
export const section = 'app';
const messagesSection = 'app.messages';
let abortPingServer = null;
let pingTimeout = null;
//
// State
@@ -54,8 +50,6 @@ export const SET_VERSION = 'app/setVersion';
export const SET_APP_VALUE = 'app/setAppValue';
export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible';
export const PING_SERVER = 'app/pingServer';
//
// Action Creators
@@ -65,70 +59,6 @@ export const setIsSidebarVisible = createAction(SET_IS_SIDEBAR_VISIBLE);
export const setAppValue = createAction(SET_APP_VALUE);
export const showMessage = createAction(SHOW_MESSAGE);
export const hideMessage = createAction(HIDE_MESSAGE);
export const pingServer = createThunk(PING_SERVER);
//
// Helpers
function pingServerAfterTimeout(getState, dispatch) {
if (abortPingServer) {
abortPingServer();
abortPingServer = null;
}
if (pingTimeout) {
clearTimeout(pingTimeout);
pingTimeout = null;
}
pingTimeout = setTimeout(() => {
if (!getState().isRestarting && getState().isConnected) {
return;
}
const ajaxOptions = {
url: '/system/status',
method: 'GET',
contentType: 'application/json'
};
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
abortPingServer = abortRequest;
request.done(() => {
abortPingServer = null;
pingTimeout = null;
dispatch(setAppValue({
isRestarting: false
}));
});
request.fail((xhr) => {
abortPingServer = null;
pingTimeout = null;
// Unauthorized, but back online
if (xhr.status === 401) {
dispatch(setAppValue({
isRestarting: false
}));
} else {
pingServerAfterTimeout(getState, dispatch);
}
});
}, 5000);
}
//
// Action Handlers
export const actionHandlers = handleThunks({
[PING_SERVER]: function(getState, payload, dispatch) {
pingServerAfterTimeout(getState, dispatch);
}
});
//
// Reducers
@@ -205,3 +135,4 @@ export const reducers = createHandleActions({
}
}, defaultState, section);

View File

@@ -86,8 +86,11 @@ export const actionHandlers = handleThunks({
} = payload;
const promise = createAjaxRequest({
url: `/history/failed/${historyId}`,
method: 'POST'
url: '/history/failed',
method: 'POST',
data: {
id: historyId
}
}).request;
promise.done(() => {

View File

@@ -1,9 +1,7 @@
import React from 'react';
import { createAction } from 'redux-actions';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import { filterTypes, icons, sortDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
import { filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
@@ -82,15 +80,6 @@ export const defaultState = {
label: 'Release Group',
isVisible: false
},
{
name: 'preferredWordScore',
columnLabel: 'Preferred Word Score',
label: React.createElement(Icon, {
name: icons.SCORE,
title: 'Preferred word score'
}),
isVisible: false
},
{
name: 'details',
columnLabel: 'Details',
@@ -244,9 +233,11 @@ export const actionHandlers = handleThunks({
}));
const promise = createAjaxRequest({
url: `/history/failed/${id}`,
url: '/history/failed',
method: 'POST',
dataType: 'json'
data: {
id
}
}).request;
promise.done(() => {

View File

@@ -63,8 +63,6 @@ export const defaultState = {
};
export const persistState = [
'interactiveImport.sortKey',
'interactiveImport.sortDirection',
'interactiveImport.recentFolders',
'interactiveImport.importMode'
];

View File

@@ -8,7 +8,7 @@ import createSetClientSideCollectionSortReducer from './Creators/Reducers/create
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
import createHandleActions from './Creators/createHandleActions';
import { set, updateItem } from './baseActions';
import { filters, filterPredicates, filterBuilderProps, sortPredicates } from './seriesActions';
import { filters, filterPredicates, filterBuilderProps } from './seriesActions';
//
// Variables
@@ -27,7 +27,6 @@ export const defaultState = {
sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'sortTitle',
secondarySortDirection: sortDirections.ASCENDING,
sortPredicates,
selectedFilterKey: 'all',
filters,
filterPredicates,
@@ -98,8 +97,7 @@ export const persistState = [
'seriesEditor.sortKey',
'seriesEditor.sortDirection',
'seriesEditor.selectedFilterKey',
'seriesEditor.customFilters',
'seriesEditor.columns'
'seriesEditor.customFilters'
];
//

View File

@@ -10,7 +10,6 @@ import createFetchHandler from './Creators/createFetchHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createHandleActions from './Creators/createHandleActions';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import { pingServer } from './appActions';
import { set } from './baseActions';
//
@@ -352,7 +351,6 @@ export const actionHandlers = handleThunks({
promise.done(() => {
dispatch(setAppValue({ isRestarting: true }));
dispatch(pingServer());
});
},

View File

@@ -29,32 +29,27 @@ function mergeColumns(path, initialState, persistedState, computedState) {
const columns = [];
// Add persisted columns in the same order they're currently in
// as long as they haven't been removed.
persistedColumns.forEach((persistedColumn) => {
const column = initialColumns.find((i) => i.name === persistedColumn.name);
if (column) {
columns.push({
...column,
isVisible: persistedColumn.isVisible
});
}
});
// Add any columns added to the app in the initial position.
initialColumns.forEach((initialColumn, index) => {
const persistedColumnIndex = persistedColumns.findIndex((i) => i.name === initialColumn.name);
initialColumns.forEach((initialColumn) => {
const persistedColumnIndex = _.findIndex(persistedColumns, { name: initialColumn.name });
const column = Object.assign({}, initialColumn);
const persistedColumn = persistedColumnIndex > -1 ? persistedColumns[persistedColumnIndex] : undefined;
if (persistedColumnIndex === -1) {
columns.splice(index, 0, column);
if (persistedColumn) {
column.isVisible = persistedColumn.isVisible;
}
});
// Set the columns in the persisted state
_.set(computedState, path, columns);
// If there is a persisted column, it's index doesn't exceed the column list
// and it's modifiable, insert it in the proper position.
if (persistedColumn && columns.length - 1 > persistedColumnIndex && persistedColumn.isModifiable !== false) {
columns.splice(persistedColumnIndex, 0, column);
} else {
columns.push(column);
}
// Set the columns in the persisted state
_.set(computedState, path, columns);
});
}
function slicer(paths_) {

View File

@@ -36,7 +36,7 @@ class MoreInfo extends Component {
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to="https://discord.gg/73QUuf3bgA">discord.gg/73QUuf3bgA</Link>
<Link to="https://discord.gg/M6BvZn5">discord.gg/M6BvZn5</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>IRC</DescriptionListItemTitle>

View File

@@ -1,16 +0,0 @@
function formatPreferredWordScore(input) {
const score = Number(input);
if (score > 0) {
return `+${score}`;
}
if (score < 0) {
return score;
}
return '';
}
export default formatPreferredWordScore;

View File

@@ -28,15 +28,6 @@ function addApiKey(ajaxOptions) {
ajaxOptions.headers['X-Api-Key'] = window.Sonarr.apiKey;
}
function addContentType(ajaxOptions) {
if (
ajaxOptions.contentType == null &&
ajaxOptions.dataType === 'json' &&
(ajaxOptions.method === 'PUT' || ajaxOptions.method === 'POST')) {
ajaxOptions.contentType = 'application/json';
}
}
export default function createAjaxRequest(originalAjaxOptions) {
const requestXHR = new window.XMLHttpRequest();
let aborted = false;
@@ -55,7 +46,6 @@ export default function createAjaxRequest(originalAjaxOptions) {
moveBodyToQuery(ajaxOptions);
addRootUrl(ajaxOptions);
addApiKey(ajaxOptions);
addContentType(ajaxOptions);
}
const request = $.ajax({

View File

@@ -30,7 +30,7 @@
sizes="16x16"
href="/Content/Images/Icons/favicon-16x16.png"
/>
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
<link rel="manifest" href="/Content/Images/Icons/manifest.json" />
<link
rel="mask-icon"
href="/Content/Images/Icons/safari-pinned-tab.svg"

View File

@@ -30,7 +30,7 @@
sizes="16x16"
href="/Content/Images/Icons/favicon-16x16.png"
/>
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
<link rel="manifest" href="/Content/Images/Icons/manifest.json" />
<link
rel="mask-icon"
href="/Content/Images/Icons/safari-pinned-tab.svg"

View File

@@ -13,7 +13,6 @@ namespace NzbDrone.Api.Profiles.Delay
public DownloadProtocol PreferredProtocol { get; set; }
public int UsenetDelay { get; set; }
public int TorrentDelay { get; set; }
public bool BypassIfHighestQuality { get; set; }
public int Order { get; set; }
public HashSet<int> Tags { get; set; }
}
@@ -33,7 +32,6 @@ namespace NzbDrone.Api.Profiles.Delay
PreferredProtocol = model.PreferredProtocol,
UsenetDelay = model.UsenetDelay,
TorrentDelay = model.TorrentDelay,
BypassIfHighestQuality = model.BypassIfHighestQuality,
Order = model.Order,
Tags = new HashSet<int>(model.Tags)
};
@@ -52,7 +50,6 @@ namespace NzbDrone.Api.Profiles.Delay
PreferredProtocol = resource.PreferredProtocol,
UsenetDelay = resource.UsenetDelay,
TorrentDelay = resource.TorrentDelay,
BypassIfHighestQuality = resource.BypassIfHighestQuality,
Order = resource.Order,
Tags = new HashSet<int>(resource.Tags)
};

View File

@@ -179,38 +179,6 @@ namespace NzbDrone.Common.Test.Http
ExceptionVerification.IgnoreWarns();
}
[Test]
public void should_not_throw_on_suppressed_status_codes()
{
var request = new HttpRequest($"http://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
request.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
ExceptionVerification.IgnoreWarns();
}
[Test]
public void should_log_unsuccessful_status_codes()
{
var request = new HttpRequest($"http://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_not_log_unsuccessful_status_codes()
{
var request = new HttpRequest($"http://{_httpBinHost}/status/{HttpStatusCode.NotFound}");
request.LogHttpError = false;
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
ExceptionVerification.ExpectedWarns(0);
}
[Test]
public void should_not_follow_redirects_when_not_in_production()
{

View File

@@ -48,19 +48,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"""DownloadURL"":""https:\/\/broadcasthe.net\/torrents.php?action=download&id=123&authkey=mySecret&torrent_pass=mySecret""")]
// Plex
[TestCase(@" http://localhost:32400/library/metadata/12345/refresh?X-Plex-Client-Identifier=1234530f-422f-4aac-b6b3-01233210aaaa&X-Plex-Product=Sonarr&X-Plex-Platform=Windows&X-Plex-Platform-Version=7&X-Plex-Device-Name=Sonarr&X-Plex-Version=3.0.3.833&X-Plex-Token=mySecret")]
// Internal
[TestCase(@"OutputPath=/home/mySecret/Downloads")]
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
public void should_clean_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);

View File

@@ -88,38 +88,5 @@ namespace NzbDrone.Common.Test.TPLTests
(GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(100));
}
[Test]
public void should_extend_subkey_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(300));
Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me-sub"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(400));
}
[Test]
public void should_honor_basekey_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(0));
Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me-sub"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(200));
}
[Test]
public void should_not_extend_basekey_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(100));
Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me"] - _epoch).Should().BeCloseTo(TimeSpan.FromMilliseconds(200));
}
}
}

View File

@@ -85,7 +85,8 @@ namespace NzbDrone.Common.EnvironmentInfo
if (_diskProvider.FileExists(_appFolderInfo.GetDatabase())) return;
if (!_diskProvider.FileExists(oldDbFile)) return;
MoveSqliteDatabase(oldDbFile, _appFolderInfo.GetDatabase());
_diskProvider.MoveFile(oldDbFile, _appFolderInfo.GetDatabase());
CleanupSqLiteRollbackFiles();
RemovePidFile();
}
@@ -107,9 +108,12 @@ namespace NzbDrone.Common.EnvironmentInfo
// Rename the DB file
if (_diskProvider.FileExists(oldDbFile))
{
MoveSqliteDatabase(oldDbFile, _appFolderInfo.GetDatabase());
_diskProvider.MoveFile(oldDbFile, _appFolderInfo.GetDatabase());
}
// Remove SQLite rollback files
CleanupSqLiteRollbackFiles();
// Remove Old PID file
RemovePidFile();
@@ -123,6 +127,7 @@ namespace NzbDrone.Common.EnvironmentInfo
}
}
private void InitializeMonoApplicationData()
{
if (OsInfo.IsWindows) return;
@@ -153,37 +158,12 @@ namespace NzbDrone.Common.EnvironmentInfo
}
}
private void MoveSqliteDatabase(string source, string destination)
private void CleanupSqLiteRollbackFiles()
{
_logger.Info("Moving {0}* to {1}*", source, destination);
var dbSuffixes = new[] { "", "-shm", "-wal", "-journal" };
foreach (var suffix in dbSuffixes)
{
var sourceFile = source + suffix;
var destFile = destination + suffix;
if (_diskProvider.FileExists(destFile))
{
_diskProvider.DeleteFile(destFile);
}
if (_diskProvider.FileExists(sourceFile))
{
_diskProvider.CopyFile(sourceFile, destFile);
}
}
foreach (var suffix in dbSuffixes)
{
var sourceFile = source + suffix;
if (_diskProvider.FileExists(sourceFile))
{
_diskProvider.DeleteFile(sourceFile);
}
}
_diskProvider.GetFiles(_appFolderInfo.AppDataFolder, SearchOption.TopDirectoryOnly)
.Where(f => Path.GetFileName(f).StartsWith("nzbdrone.db"))
.ToList()
.ForEach(_diskProvider.DeleteFile);
}
private void RemovePidFile()

View File

@@ -24,7 +24,6 @@ namespace NzbDrone.Common.EnvironmentInfo
public const string TERMINATE = "terminateexisting";
public const string RESTART = "restart";
public const string REGISTER_URL = "registerurl";
public const string EXIT_IMMEDIATELY = "exitimmediately";
public StartupContext(params string[] args)
{
@@ -55,7 +54,6 @@ namespace NzbDrone.Common.EnvironmentInfo
public bool InstallService => Flags.Contains(INSTALL_SERVICE);
public bool UninstallService => Flags.Contains(UNINSTALL_SERVICE);
public bool RegisterUrl => Flags.Contains(REGISTER_URL);
public bool ExitImmediately => Flags.Contains(EXIT_IMMEDIATELY);
public string PreservedArguments
{

View File

@@ -85,12 +85,9 @@ namespace NzbDrone.Common.Http
_logger.Error("Server requested a redirect to [{0}] while in developer mode. Update the request URL to avoid this redirect.", response.Headers["Location"]);
}
if (!request.SuppressHttpError && response.HasHttpError && (request.SuppressHttpErrorStatusCodes == null || !request.SuppressHttpErrorStatusCodes.Contains(response.StatusCode)))
if (!request.SuppressHttpError && response.HasHttpError)
{
if (request.LogHttpError)
{
_logger.Warn("HTTP Error - {0}", response);
}
_logger.Warn("HTTP Error - {0}", response);
if ((int)response.StatusCode == 429)
{
@@ -114,7 +111,7 @@ namespace NzbDrone.Common.Http
if (request.RateLimit != TimeSpan.Zero)
{
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimitKey, request.RateLimit);
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimit);
}
_logger.Trace(request);

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -16,7 +15,6 @@ namespace NzbDrone.Common.Http
Headers = new HttpHeader();
AllowAutoRedirect = true;
StoreRequestCookie = true;
LogHttpError = true;
Cookies = new Dictionary<string, string>();
@@ -37,18 +35,15 @@ namespace NzbDrone.Common.Http
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public bool SuppressHttpError { get; set; }
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
public bool UseSimplifiedUserAgent { get; set; }
public bool AllowAutoRedirect { get; set; }
public bool ConnectionKeepAlive { get; set; }
public bool LogResponseContent { get; set; }
public bool LogHttpError { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public bool StoreRequestCookie { get; set; }
public bool StoreResponseCookie { get; set; }
public TimeSpan RequestTimeout { get; set; }
public TimeSpan RateLimit { get; set; }
public string RateLimitKey { get; set; }
public Stream ResponseStream { get; set; }
public override string ToString()

View File

@@ -19,7 +19,6 @@ namespace NzbDrone.Common.Http
public Dictionary<string, string> Segments { get; private set; }
public HttpHeader Headers { get; private set; }
public bool SuppressHttpError { get; set; }
public bool LogHttpError { get; set; }
public bool UseSimplifiedUserAgent { get; set; }
public bool AllowAutoRedirect { get; set; }
public bool ConnectionKeepAlive { get; set; }
@@ -42,7 +41,6 @@ namespace NzbDrone.Common.Http
Headers = new HttpHeader();
Cookies = new Dictionary<string, string>();
FormData = new List<HttpFormData>();
LogHttpError = true;
}
public HttpRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
@@ -103,7 +101,6 @@ namespace NzbDrone.Common.Http
{
request.Method = Method;
request.SuppressHttpError = SuppressHttpError;
request.LogHttpError = LogHttpError;
request.UseSimplifiedUserAgent = UseSimplifiedUserAgent;
request.AllowAutoRedirect = AllowAutoRedirect;
request.ConnectionKeepAlive = ConnectionKeepAlive;

View File

@@ -53,10 +53,7 @@ namespace NzbDrone.Common.Http
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
StatusCode == HttpStatusCode.MovedPermanently ||
StatusCode == HttpStatusCode.Found ||
StatusCode == HttpStatusCode.TemporaryRedirect ||
StatusCode == HttpStatusCode.RedirectMethod ||
StatusCode == HttpStatusCode.SeeOther;
StatusCode == HttpStatusCode.Found;
public string[] GetCookieHeaders()
{

View File

@@ -19,12 +19,9 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Path
new Regex(@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -18,12 +18,6 @@ namespace NzbDrone.Common.Instrumentation
{
var exception = e.Exception;
if (exception.InnerException is ObjectDisposedException disposedException && disposedException.ObjectName == "System.Net.HttpListenerRequest")
{
// We don't care about web connections
return;
}
Console.WriteLine("Task Error: {0}", exception);
Logger.Error(exception, "Task Error");
}

View File

@@ -195,10 +195,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
if (ex != null)
{
fingerPrint.Add(ex.GetType().FullName);
if (ex.TargetSite != null)
{
fingerPrint.Add(ex.TargetSite.ToString());
}
fingerPrint.Add(ex.TargetSite.ToString());
if (ex.InnerException != null)
{
fingerPrint.Add(ex.InnerException.GetType().FullName);

View File

@@ -2,14 +2,12 @@
using System.Collections.Concurrent;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.TPL
{
public interface IRateLimitService
{
void WaitAndPulse(string key, TimeSpan interval);
void WaitAndPulse(string key, string subKey, TimeSpan interval);
}
public class RateLimitService : IRateLimitService
@@ -25,37 +23,9 @@ namespace NzbDrone.Common.TPL
public void WaitAndPulse(string key, TimeSpan interval)
{
WaitAndPulse(key, null, interval);
}
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
{
var waitUntil = DateTime.UtcNow.Add(interval);
if (subKey.IsNotNullOrWhiteSpace())
{
// Expand the base key timer, but don't extend it beyond now+interval.
var baseUntil = _rateLimitStore.AddOrUpdate(key,
(s) => waitUntil,
(s, i) => new DateTime(Math.Max(waitUntil.Ticks, i.Ticks), DateTimeKind.Utc));
if (baseUntil > waitUntil)
{
waitUntil = baseUntil;
}
// Wait for the full key
var combinedKey = key + "-" + subKey;
waitUntil = _rateLimitStore.AddOrUpdate(combinedKey,
(s) => waitUntil,
(s, i) => new DateTime(Math.Max(waitUntil.Ticks, i.Add(interval).Ticks), DateTimeKind.Utc));
}
else
{
waitUntil = _rateLimitStore.AddOrUpdate(key,
(s) => waitUntil,
(s, i) => new DateTime(Math.Max(waitUntil.Ticks, i.Add(interval).Ticks), DateTimeKind.Utc));
}
var waitUntil = _rateLimitStore.AddOrUpdate(key,
(s) => DateTime.UtcNow + interval,
(s,i) => new DateTime(Math.Max(DateTime.UtcNow.Ticks, i.Ticks), DateTimeKind.Utc) + interval);
waitUntil -= interval;

View File

@@ -25,11 +25,10 @@ namespace NzbDrone.Console
public static void Main(string[] args)
{
RuntimePatcher.Initialize();
StartupContext startupArgs = null;
try
{
startupArgs = new StartupContext(args);
var startupArgs = new StartupContext(args);
try
{
NzbDroneLogger.Register(startupArgs, false, true);
@@ -46,21 +45,21 @@ namespace NzbDrone.Console
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(ex, "EPIC FAIL!");
Exit(ExitCodes.NonRecoverableFailure, startupArgs);
Exit(ExitCodes.NonRecoverableFailure);
}
catch (SocketException ex)
{
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(ex.Message + ". This can happen if another instance of Sonarr is already running another application is using the same port (default: 8989) or the user has insufficient permissions");
Exit(ExitCodes.RecoverableFailure, startupArgs);
Exit(ExitCodes.RecoverableFailure);
}
catch (RemoteAccessException ex)
{
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(ex, "EPIC FAIL!");
Exit(ExitCodes.Normal, startupArgs);
Exit(ExitCodes.Normal);
}
catch (Exception ex)
{
@@ -68,15 +67,15 @@ namespace NzbDrone.Console
System.Console.WriteLine("");
Logger.Fatal(ex, "EPIC FAIL!");
System.Console.WriteLine("EPIC FAIL! " + ex.ToString());
Exit(ExitCodes.UnknownFailure, startupArgs);
Exit(ExitCodes.UnknownFailure);
}
Logger.Info("Exiting main.");
Exit(ExitCodes.Normal, startupArgs);
Exit(ExitCodes.Normal);
}
private static void Exit(ExitCodes exitCode, StartupContext startupArgs)
private static void Exit(ExitCodes exitCode)
{
LogManager.Shutdown();
@@ -88,15 +87,6 @@ namespace NzbDrone.Console
if (exitCode == ExitCodes.NonRecoverableFailure)
{
if (startupArgs?.ExitImmediately == true)
{
System.Console.WriteLine("Non-recoverable failure, but set to exit immediately");
Environment.Exit((int)exitCode);
}
System.Console.WriteLine("Non-recoverable failure, waiting for user intervention...");
for (int i = 0; i < 3600; i++)
{

View File

@@ -1,100 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class email_multiple_addressesFixture : MigrationTest<email_multiple_addresses>
{
[Test]
public void should_convert_to_list_on_email_lists()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
Name = "Mail Sonarr",
Implementation = "Email",
Tags = "[]",
Settings = new EmailSettings173
{
Server = "smtp.gmail.com",
Port = 563,
To = "dont@email.me"
}.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition173>("SELECT * FROM Notifications");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.To.Count().Should().Be(1);
}
}
public class NotificationDefinition173
{
public int Id { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public EmailSettings174 Settings { get; set; }
public string Name { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
public bool OnRename { get; set; }
public bool OnSeriesDelete { get; set; }
public bool OnEpisodeFileDelete { get; set; }
public bool OnEpisodeFileDeleteForUpgrade { get; set; }
public bool OnHealthIssue { get; set; }
public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; }
public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; }
public bool SupportsOnSeriesDelete { get; set; }
public bool SupportsOnEpisodeFileDelete { get; set; }
public bool SupportsOnEpisodeFileDeleteForUpgrade { get; set; }
public bool SupportsOnHealthIssue { get; set; }
public bool IncludeHealthWarnings { get; set; }
public List<int> Tags { get; set; }
}
public class EmailSettings173
{
public string Server { get; set; }
public int Port { get; set; }
public bool RequireEncryption { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string From { get; set; }
public string To { get; set; }
}
public class EmailSettings174
{
public string Server { get; set; }
public int Port { get; set; }
public bool RequireEncryption { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string From { get; set; }
public IEnumerable<string> To { get; set; }
public IEnumerable<string> Cc { get; set; }
public IEnumerable<string> Bcc { get; set; }
}
}

View File

@@ -25,7 +25,6 @@ namespace NzbDrone.Core.Test.Datastore.SqliteSchemaDumperTests
[TestCase(@"CREATE TABLE ""Test """"Table"" (""My""""Id"" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)", "Test \"Table", "My\"Id")]
[TestCase(@"CREATE TABLE [Test Table] ([My Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)", "Test Table", "My Id")]
[TestCase(@" CREATE TABLE `Test ``Table` ( `My`` Id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ) ", "Test `Table", "My` Id")]
[TestCase(@"CREATE TABLE TestTable (MyId INTEGER NOT NULL, PRIMARY KEY(""MyId"" AUTOINCREMENT))", "TestTable", "MyId")]
public void should_parse_table_language_flavors(string sql, string tableName, string columnName)
{
var result = Subject.ReadTableSchema(sql);

View File

@@ -124,7 +124,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
}
[Test]
public void should_be_false_when_quality_and_language_is_last_allowed_in_profile_and_bypass_disabled()
public void should_be_true_when_quality_and_language_is_last_allowed_in_profile()
{
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p);
_remoteEpisode.ParsedEpisodeInfo.Language = Language.French;
@@ -132,17 +132,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_true_when_quality_and_language_is_last_allowed_in_profile_and_bypass_enabled()
{
_delayProfile.BypassIfHighestQuality = true;
_remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.Bluray720p);
_remoteEpisode.ParsedEpisodeInfo.Language = Language.French;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_true_when_release_is_older_than_delay()
{

View File

@@ -12,7 +12,6 @@ using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.QBittorrent;
using NzbDrone.Test.Common;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Download.Clients;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
{
@@ -72,23 +71,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
protected void GivenFailedDownload()
{
Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()))
.Throws<InvalidOperationException>();
Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.AddTorrentFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()))
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()))
.Throws<InvalidOperationException>();
}
protected void GivenSuccessfulDownload()
{
Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.AddTorrentFromFile(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()))
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()))
.Callback(() =>
{
var torrent = new QBittorrentTorrent
{
Hash = "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951",
Hash = "HASH",
Name = _title,
Size = 1000,
Progress = 1.0,
@@ -140,10 +135,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
.Setup(s => s.GetTorrentFiles(torrent.Hash.ToLower(), It.IsAny<QBittorrentSettings>()))
.Returns(new List<QBittorrentTorrentFile> { new QBittorrentTorrentFile { Name = torrent.Name } });
}
Mocker.GetMock<IQBittorrentProxy>()
.Setup(s => s.IsTorrentLoaded(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()))
.Returns<string, QBittorrentSettings>((hash, s) => torrents.Any(v => v.Hash.ToLower() == hash));
}
private void GivenTorrentFiles(string hash, List<QBittorrentTorrentFile> files)
@@ -475,7 +466,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Assert.DoesNotThrow(() => Subject.Download(remoteEpisode));
Mocker.GetMock<IQBittorrentProxy>()
.Verify(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<TorrentSeedConfiguration>(), It.IsAny<QBittorrentSettings>()), Times.Once());
.Verify(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
}
[Test]

View File

@@ -11,7 +11,6 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Indexers;
using System.Linq;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.Test.Download.TrackedDownloads
{
@@ -145,185 +144,5 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(0);
trackedDownload.RemoteEpisode.MappedSeasonNumber.Should().Be(0);
}
[Test]
public void should_unmap_tracked_download_if_episode_deleted()
{
GivenDownloadHistory();
var remoteEpisode = new RemoteEpisode
{
Series = new Series() { Id = 5 },
Episodes = new List<Episode> { new Episode { Id = 4 } },
ParsedEpisodeInfo = new ParsedEpisodeInfo()
{
SeriesTitle = "TV Series",
SeasonNumber = 1,
EpisodeNumbers = new[] { 1 }
},
MappedSeasonNumber = 0
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(remoteEpisode);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<EpisodeHistory>());
var client = new DownloadClientDefinition()
{
Id = 1,
Protocol = DownloadProtocol.Torrent
};
var item = new DownloadClientItem()
{
Title = "TV Series - S01E01",
DownloadId = "12345",
DownloadClientInfo = new DownloadClientItemClientInfo
{
Id = 1,
Type = "Blackhole",
Name = "Blackhole Client",
Protocol = DownloadProtocol.Torrent
}
};
Subject.TrackDownload(client, item);
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List<Episode>(), new List<Episode>(), remoteEpisode.Episodes));
var trackedDownloads = Subject.GetTrackedDownloads();
trackedDownloads.Should().HaveCount(1);
trackedDownloads.First().RemoteEpisode.Should().BeNull();
}
[Test]
public void should_not_throw_when_processing_deleted_episodes()
{
GivenDownloadHistory();
var remoteEpisode = new RemoteEpisode
{
Series = new Series() { Id = 5 },
Episodes = new List<Episode> { new Episode { Id = 4 } },
ParsedEpisodeInfo = new ParsedEpisodeInfo()
{
SeriesTitle = "TV Series",
SeasonNumber = 1,
EpisodeNumbers = new[] { 1 }
},
MappedSeasonNumber = 0
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<EpisodeHistory>());
var client = new DownloadClientDefinition()
{
Id = 1,
Protocol = DownloadProtocol.Torrent
};
var item = new DownloadClientItem()
{
Title = "TV Series - S01E01",
DownloadId = "12345",
DownloadClientInfo = new DownloadClientItemClientInfo
{
Id = 1,
Type = "Blackhole",
Name = "Blackhole Client",
Protocol = DownloadProtocol.Torrent
}
};
Subject.TrackDownload(client, item);
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List<Episode>(), new List<Episode>(), remoteEpisode.Episodes));
var trackedDownloads = Subject.GetTrackedDownloads();
trackedDownloads.Should().HaveCount(1);
trackedDownloads.First().RemoteEpisode.Should().BeNull();
}
[Test]
public void should_not_throw_when_processing_deleted_series()
{
GivenDownloadHistory();
var remoteEpisode = new RemoteEpisode
{
Series = new Series() { Id = 5 },
Episodes = new List<Episode> { new Episode { Id = 4 } },
ParsedEpisodeInfo = new ParsedEpisodeInfo()
{
SeriesTitle = "TV Series",
SeasonNumber = 1,
EpisodeNumbers = new[] { 1 }
},
MappedSeasonNumber = 0
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<EpisodeHistory>());
var client = new DownloadClientDefinition()
{
Id = 1,
Protocol = DownloadProtocol.Torrent
};
var item = new DownloadClientItem()
{
Title = "TV Series - S01E01",
DownloadId = "12345",
DownloadClientInfo = new DownloadClientItemClientInfo
{
Id = 1,
Type = "Blackhole",
Name = "Blackhole Client",
Protocol = DownloadProtocol.Torrent
}
};
Subject.TrackDownload(client, item);
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new SeriesDeletedEvent(remoteEpisode.Series, true, true));
var trackedDownloads = Subject.GetTrackedDownloads();
trackedDownloads.Should().HaveCount(1);
trackedDownloads.First().RemoteEpisode.Should().BeNull();
}
}
}

View File

@@ -1,4 +1,3 @@
using System.IO;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
@@ -77,37 +76,5 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.Calculate(_series, _episodeFile).Should().Be(20);
}
[Test]
public void should_return_score_for_original_path_folder_name_if_highest()
{
var folderName = "folder-name";
var fileName = "file-name";
_episodeFile.OriginalFilePath = Path.Combine(folderName, fileName);
GivenPreferredWordScore(_episodeFile.RelativePath, 20);
GivenPreferredWordScore(_episodeFile.Path, 50);
GivenPreferredWordScore(folderName, 60);
GivenPreferredWordScore(fileName, 50);
Subject.Calculate(_series, _episodeFile).Should().Be(60);
}
[Test]
public void should_return_score_for_original_path_file_name_if_highest()
{
var folderName = "folder-name";
var fileName = "file-name";
_episodeFile.OriginalFilePath = Path.Combine(folderName, fileName);
GivenPreferredWordScore(_episodeFile.RelativePath, 20);
GivenPreferredWordScore(_episodeFile.Path, 50);
GivenPreferredWordScore(folderName, 40);
GivenPreferredWordScore(fileName, 50);
Subject.Calculate(_series, _episodeFile).Should().Be(50);
}
}
}

View File

@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
@@ -60,11 +60,10 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
FileEpisodeInfo = fileEpisodeInfo,
FolderEpisodeInfo = folderEpisodeInfo,
Path = @"C:\Test\Unsorted TV\Series.Title.S01\Series.Title.S01E01.mkv".AsOsAgnostic(),
Series = _series,
OtherVideoFiles = true
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, true);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
@@ -83,7 +82,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
@@ -102,7 +101,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(folderEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
@@ -121,7 +120,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(fileEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());
@@ -144,7 +143,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
.Setup(s => s.ParseSpecialEpisodeTitle(fileEpisodeInfo, It.IsAny<string>(), _series))
.Returns(specialEpisodeInfo);
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetEpisodes(specialEpisodeInfo, _series, localEpisode.SceneSource, null), Times.Once());

View File

@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
_localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle);
Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language);
Subject.Aggregate(_localEpisode, null, false).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language);
}
[Test]
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
_localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle);
_localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle);
Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FolderEpisodeInfo.Language);
Subject.Aggregate(_localEpisode, null, false).Language.Should().Be(_localEpisode.FolderEpisodeInfo.Language);
}
[Test]
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
_localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle);
_localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle);
Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.DownloadClientEpisodeInfo.Language);
Subject.Aggregate(_localEpisode, null, false).Language.Should().Be(_localEpisode.DownloadClientEpisodeInfo.Language);
}
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
_localEpisode.FolderEpisodeInfo = GetParsedEpisodeInfo(Language.English, _simpleReleaseTitle);
_localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.French, _simpleReleaseTitle);
Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language);
Subject.Aggregate(_localEpisode, null, false).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language);
}
[Test]
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
_localEpisode.Episodes.First().Title = "The Swedish Job";
_localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.Swedish, "Series.Title.S01E01.The.Swedish.Job.720p.WEB-DL-RlsGrp");
Subject.Aggregate(_localEpisode, null).Language.Should().Be(Language.English);
Subject.Aggregate(_localEpisode, null, false).Language.Should().Be(Language.English);
}
@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
_localEpisode.Episodes.First().Title = "The Swedish Job";
_localEpisode.FileEpisodeInfo = GetParsedEpisodeInfo(Language.French, "Series.Title.S01E01.The.Swedish.Job.720p.WEB-DL-RlsGrp");
Subject.Aggregate(_localEpisode, null).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language);
Subject.Aggregate(_localEpisode, null, false).Language.Should().Be(_localEpisode.FileEpisodeInfo.Language);
}
}

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
GivenAugmenters(_fileExtensionAugmenter, nullMock);
var result = Subject.Aggregate(new LocalEpisode(), null);
var result = Subject.Aggregate(new LocalEpisode(), null, false);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Extension);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Extension);
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
GivenAugmenters(_fileExtensionAugmenter, _nameAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), null);
var result = Subject.Aggregate(new LocalEpisode(), null, false);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
GivenAugmenters(_fileExtensionAugmenter, _mediaInfoAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), null);
var result = Subject.Aggregate(new LocalEpisode(), null, false);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Extension);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
GivenAugmenters(_nameAugmenter, _mediaInfoAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), null);
var result = Subject.Aggregate(new LocalEpisode(), null, false);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem());
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem(), false);
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
{
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem());
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem(), false);
result.Quality.Revision.Version.Should().Be(1);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Unknown);
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem());
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem(), false);
result.Quality.Revision.Version.Should().Be(2);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem());
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem(), false);
result.Quality.Revision.Version.Should().Be(0);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem());
var result = Subject.Aggregate(new LocalEpisode(), new DownloadClientItem(), false);
result.Quality.Revision.Version.Should().Be(2);
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
localEpisode.ReleaseGroup.Should().Be("Wizzy");
}
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
localEpisode.ReleaseGroup.Should().Be("Wizzy");
}
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
localEpisode.ReleaseGroup.Should().Be("Viva");
}
@@ -92,7 +92,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
localEpisode.ReleaseGroup.Should().Be("Drone");
}
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Aggregation.Aggregators
Series = _series
};
Subject.Aggregate(localEpisode, null);
Subject.Aggregate(localEpisode, null, false);
localEpisode.ReleaseGroup.Should().Be("Wizzy");
}

View File

@@ -1,199 +0,0 @@
using System.Collections.Generic;
using System.IO;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{
[TestFixture]
public class GetSceneNameFixture : CoreTest
{
private LocalEpisode _localEpisode;
private string _seasonName = "series.title.s02.dvdrip.x264-ingot";
private string _episodeName = "series.title.s02e23.dvdrip.x264-ingot";
[SetUp]
public void Setup()
{
var series = Builder<Series>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.With(l => l.LanguageProfile = new LanguageProfile
{
Cutoff = Language.Spanish,
Languages = Languages.LanguageFixture.GetDefaultLanguages()
})
.With(s => s.Path = @"C:\Test\TV\Series Title".AsOsAgnostic())
.Build();
var episode = Builder<Episode>.CreateNew()
.Build();
_localEpisode = new LocalEpisode
{
Series = series,
Episodes = new List<Episode> {episode},
Path = Path.Combine(series.Path, "Series Title - S02E23 - Episode Title.mkv"),
Quality = new QualityModel(Quality.Bluray720p),
ReleaseGroup = "DRONE"
};
}
private void GivenExistingFileOnDisk()
{
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
.Returns(new List<EpisodeFile>());
}
[Test]
public void should_use_download_client_item_title_as_scene_name()
{
_localEpisode.DownloadClientEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _episodeName
};
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.Be(_episodeName);
}
[Test]
public void should_not_use_download_client_item_title_as_scene_name_if_full_season()
{
_localEpisode.DownloadClientEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _seasonName,
FullSeason = true
};
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _seasonName, _episodeName)
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.BeNull();
}
[Test]
public void should_not_use_download_client_item_title_as_scene_name_if_there_are_other_video_files()
{
_localEpisode.OtherVideoFiles = true;
_localEpisode.DownloadClientEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _seasonName,
FullSeason = false
};
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _seasonName, _episodeName)
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.BeNull();
}
[Test]
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
{
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _seasonName, _episodeName + ".mkv")
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.Be(_episodeName);
}
[Test]
public void should_not_use_file_name_as_scenename_if_it_doesnt_look_like_scenename()
{
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _episodeName, "aaaaa.mkv")
.AsOsAgnostic();
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.BeNull();
}
[Test]
public void should_use_folder_name_as_scenename_only_if_it_looks_like_scenename()
{
_localEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _episodeName
};
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.Be(_episodeName);
}
[Test]
public void should_not_use_folder_name_as_scenename_if_it_doesnt_look_like_scenename()
{
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _episodeName, "aaaaa.mkv")
.AsOsAgnostic();
_localEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = "aaaaa"
};
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.BeNull();
}
[Test]
public void should_not_use_folder_name_as_scenename_if_it_is_for_a_full_season()
{
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _episodeName, "aaaaa.mkv")
.AsOsAgnostic();
_localEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _seasonName,
FullSeason = true
};
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.BeNull();
}
[Test]
public void should_not_use_folder_name_as_scenename_if_there_are_other_video_files()
{
_localEpisode.OtherVideoFiles = true;
_localEpisode.Path = Path.Combine(@"C:\Test\Unsorted TV", _episodeName, "aaaaa.mkv")
.AsOsAgnostic();
_localEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _seasonName,
FullSeason = false
};
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.BeNull();
}
[TestCase(".mkv")]
[TestCase(".par2")]
[TestCase(".nzb")]
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
{
_localEpisode.DownloadClientEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = _episodeName + extension
};
SceneNameCalculator.GetSceneName(_localEpisode).Should()
.Be(_episodeName);
}
}
}

View File

@@ -93,8 +93,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
private void GivenAugmentationSuccess()
{
Mocker.GetMock<IAggregationService>()
.Setup(s => s.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>()))
.Callback<LocalEpisode, DownloadClientItem>((localEpisode, downloadClientItem) =>
.Setup(s => s.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Callback<LocalEpisode, DownloadClientItem, bool>((localEpisode, downloadClientItem, otherFiles) =>
{
localEpisode.Episodes = _localEpisode.Episodes;
});
@@ -164,7 +164,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
GivenSpecifications(_pass1);
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>()))
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Throws<TestException>();
_videoFiles = new List<string>
@@ -179,7 +179,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IAggregationService>()
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>()), Times.Exactly(_videoFiles.Count));
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
ExceptionVerification.ExpectedErrors(3);
}
@@ -200,7 +200,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
var decisions = Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IAggregationService>()
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>()), Times.Exactly(_videoFiles.Count));
.Verify(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
decisions.Should().HaveCount(3);
decisions.First().Rejections.Should().NotBeEmpty();
@@ -210,7 +210,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
public void should_return_a_decision_when_exception_is_caught()
{
Mocker.GetMock<IAggregationService>()
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>()))
.Setup(c => c.Augment(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
.Throws<TestException>();
_videoFiles = new List<string>

View File

@@ -1,4 +1,3 @@
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
@@ -7,7 +6,6 @@ using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
@@ -35,24 +33,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.With(p => p.FullSeason = false)
.Build())
.Build();
}
private void GivenEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, int[] episodeNumbers)
{
var seasonNumber = parsedEpisodeInfo.SeasonNumber;
var episodes = episodeNumbers.Select(n =>
Builder<Episode>.CreateNew()
.With(e => e.Id = seasonNumber * 10 + n)
.With(e => e.SeasonNumber = seasonNumber)
.With(e => e.EpisodeNumber = n)
.Build()
).ToList();
Mocker.GetMock<IParsingService>()
.Setup(s => s.GetEpisodes(parsedEpisodeInfo, It.IsAny<Series>(), true, null))
.Returns(episodes);
}
[Test]
@@ -88,9 +68,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new int[0];
_localEpisode.FolderEpisodeInfo.FullSeason = true;
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, new []{ 1, 2, 3, 4, 5 });
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
@@ -101,9 +78,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
@@ -114,22 +88,16 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_disregard_subfolder()
public void should_be_disregard_subfolder()
{
_localEpisode.FileEpisodeInfo.EpisodeNumbers = new[] { 5, 6 };
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E05E06.mkv".AsOsAgnostic();
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
}
@@ -138,9 +106,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
{
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic();
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
}
@@ -151,9 +116,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E05E06.mkv".AsOsAgnostic();
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
}
@@ -167,9 +129,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.SeasonNumber = 1;
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01.720p.HDTV-Sonarr\S02E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
@@ -184,9 +143,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.SeasonNumber = 1;
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01.720p.HDTV-Sonarr\S02E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
@@ -202,9 +158,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
_localEpisode.FolderEpisodeInfo.SeasonNumber = 1;
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
GivenEpisodes(_localEpisode.FileEpisodeInfo, _localEpisode.FileEpisodeInfo.EpisodeNumbers);
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
@@ -229,8 +182,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
}
};
GivenEpisodes(actualInfo, actualInfo.EpisodeNumbers);
Mocker.GetMock<IParsingService>()
.Setup(v => v.ParseSpecialEpisodeTitle(fileInfo, It.IsAny<string>(), 0, 0, null))
.Returns(actualInfo);
@@ -245,15 +196,12 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
[Test]
public void should_be_accepted_if_file_has_absolute_episode_number_and_folder_uses_standard()
{
_localEpisode.FileEpisodeInfo.SeasonNumber = 1;
_localEpisode.FileEpisodeInfo.SeasonNumber = 0;
_localEpisode.FileEpisodeInfo.AbsoluteEpisodeNumbers = new[] { 1 };
_localEpisode.FolderEpisodeInfo.SeasonNumber = 1;
_localEpisode.FolderEpisodeInfo.EpisodeNumbers = new[] { 1, 2 };
GivenEpisodes(_localEpisode.FileEpisodeInfo, new []{ 1 });
GivenEpisodes(_localEpisode.FolderEpisodeInfo, _localEpisode.FolderEpisodeInfo.EpisodeNumbers);
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01.720p.HDTV-Sonarr\S02E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();

View File

@@ -287,11 +287,14 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
Mocker.GetMock<IPreferredWordService>()
.Setup(s => s.Calculate(It.IsAny<Series>(), It.IsAny<string>(), 0))
.Returns(5);
Mocker.GetMock<IEpisodeFilePreferredWordCalculator>()
.Setup(s => s.Calculate(It.IsAny<Series>(), It.IsAny<EpisodeFile>()))
.Returns(10);
_localEpisode.PreferredWordScore = 5;
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
@@ -361,11 +364,14 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
Mocker.GetMock<IPreferredWordService>()
.Setup(s => s.Calculate(It.IsAny<Series>(), It.IsAny<string>(), 0))
.Returns(5);
Mocker.GetMock<IEpisodeFilePreferredWordCalculator>()
.Setup(s => s.Calculate(It.IsAny<Series>(), It.IsAny<EpisodeFile>()))
.Returns(1);
_localEpisode.PreferredWordScore = 5;
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
@@ -391,11 +397,14 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
Mocker.GetMock<IPreferredWordService>()
.Setup(s => s.Calculate(It.IsAny<Series>(), It.IsAny<string>(), 0))
.Returns(5);
Mocker.GetMock<IEpisodeFilePreferredWordCalculator>()
.Setup(s => s.Calculate(It.IsAny<Series>(), It.IsAny<EpisodeFile>()))
.Returns(5);
_localEpisode.PreferredWordScore = 5;
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)

View File

@@ -21,7 +21,7 @@ using NzbDrone.Test.Common;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
namespace NzbDrone.Core.Test.MediaFiles
{
[TestFixture]
public class ImportApprovedEpisodesFixture : CoreTest<ImportApprovedEpisodes>
@@ -169,6 +169,112 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Times.Never());
}
[Test]
public void should_use_nzb_title_as_scene_name()
{
GivenNewDownload();
_downloadClientItem.Title = "malcolm.in.the.middle.s02e05.dvdrip.xvid-ingot";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == _downloadClientItem.Title)));
}
[TestCase(".mkv")]
[TestCase(".par2")]
[TestCase(".nzb")]
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
{
GivenNewDownload();
var title = "malcolm.in.the.middle.s02e05.dvdrip.xvid-ingot";
_downloadClientItem.Title = title + extension;
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == title)));
}
[Test]
public void should_not_use_nzb_title_as_scene_name_if_full_season()
{
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv");
_downloadClientItem.Title = "malcolm.in.the.middle.s02.dvdrip.xvid-ingot";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == "malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot")));
}
[Test]
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "series.title.s02e23.dvdrip.xvid-ingot.mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == "series.title.s02e23.dvdrip.xvid-ingot")));
}
[Test]
public void should_not_use_file_name_as_scenename_if_it_doesnt_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == null)));
}
[Test]
public void should_use_folder_name_as_scenename_only_if_it_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
_approvedDecisions.First().LocalEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = "series.title.s02e23.dvdrip.xvid-ingot"
};
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == "series.title.s02e23.dvdrip.xvid-ingot")));
}
[Test]
public void should_not_use_folder_name_as_scenename_if_it_doesnt_looks_like_scenename()
{
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
_approvedDecisions.First().LocalEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = "aaaaa.mkv"
};
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == null)));
}
[Test]
public void should_not_use_folder_name_as_scenename_if_it_is_for_a_full_season()
{
GivenNewDownload();
_approvedDecisions.First().LocalEpisode.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
_approvedDecisions.First().LocalEpisode.FolderEpisodeInfo = new ParsedEpisodeInfo
{
ReleaseTitle = "series.title.s02.dvdrip.xvid-ingot.mkv",
FullSeason = true
};
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.SceneName == null)));
}
[Test]
public void should_import_larger_files_first()
{
@@ -377,18 +483,5 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<EpisodeFile>(c => c.OriginalFilePath == $"{name}\\subfolder\\{name}.mkv".AsOsAgnostic())));
}
[Test]
public void should_include_scene_name_with_new_downloads()
{
var firstDecision = _approvedDecisions.First();
firstDecision.LocalEpisode.SceneName = "Series.Title.S01E01.dvdrip-DRONE";
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeEpisodeFile(It.Is<EpisodeFile>(e => e.SceneName == firstDecision.LocalEpisode.SceneName), _approvedDecisions.First().LocalEpisode, false),
Times.Once());
}
}
}

View File

@@ -8,28 +8,20 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestFixture]
public class FormatVideoDynamicRangeFixture : TestBase
{
[TestCase(8, "", "", "", "", "")]
[TestCase(8, "BT.601 NTSC", "BT.709", "", "", "")]
[TestCase(10, "BT.2020", "PQ", "", "", "HDR")]
[TestCase(8, "BT.2020", "PQ", "", "", "")]
[TestCase(10, "BT.601 NTSC", "PQ", "", "", "")]
[TestCase(10, "BT.2020", "BT.709", "", "", "")]
[TestCase(10, "BT.2020", "HLG", "", "", "HDR")]
[TestCase(10, "", "", "Dolby Vision", "", "HDR")]
[TestCase(10, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
[TestCase(8, "", "", "Dolby Vision", "", "HDR")]
[TestCase(8, "", "", "SMPTE ST 2086", "HDR10", "HDR")]
[TestCase(10, "BT.2020", "PQ", "Dolby Vision / SMPTE ST 2086", "Blu-ray / HDR10", "HDR")]
public void should_format_video_dynamic_range(int bitDepth, string colourPrimaries, string transferCharacteristics, string hdrFormat, string hdrFormatCompatibility, string expectedVideoDynamicRange)
[TestCase(8, "BT.601 NTSC", "BT.709", "")]
[TestCase(10, "BT.2020", "PQ", "HDR")]
[TestCase(8, "BT.2020", "PQ", "")]
[TestCase(10, "BT.601 NTSC", "PQ", "")]
[TestCase(10, "BT.2020", "BT.709", "")]
[TestCase(10, "BT.2020", "HLG", "HDR")]
public void should_format_video_dynamic_range(int bitDepth, string colourPrimaries, string transferCharacteristics, string expectedVideoDynamicRange)
{
var mediaInfo = new MediaInfoModel
{
VideoBitDepth = bitDepth,
VideoColourPrimaries = colourPrimaries,
VideoTransferCharacteristics = transferCharacteristics,
VideoHdrFormat = hdrFormat,
VideoHdrFormatCompatibility = hdrFormatCompatibility,
SchemaRevision = 7
SchemaRevision = 5
};
MediaInfoFormatter.FormatVideoDynamicRange(mediaInfo).Should().Be(expectedVideoDynamicRange);

View File

@@ -65,8 +65,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
info.VideoTransferCharacteristics.Should().Be("BT.709");
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
info.VideoHdrFormat.Should().BeEmpty();
info.VideoHdrFormatCompatibility.Should().BeEmpty();
}
@@ -109,8 +107,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
info.VideoColourPrimaries.Should().Be("BT.601 NTSC");
info.VideoTransferCharacteristics.Should().Be("BT.709");
info.AudioAdditionalFeatures.Should().BeOneOf("", "LC");
info.VideoHdrFormat.Should().BeEmpty();
info.VideoHdrFormatCompatibility.Should().BeEmpty();
}
[Test]

View File

@@ -1,114 +0,0 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Notifications.Email;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.NotificationTests.EmailTests
{
[TestFixture]
public class EmailSettingsValidatorFixture : CoreTest<EmailSettingsValidator>
{
private EmailSettings _emailSettings;
private TestValidator<EmailSettings> _validator;
[SetUp]
public void Setup()
{
_validator = new TestValidator<EmailSettings>
{
v => v.RuleFor(s => s).SetValidator(Subject)
};
_emailSettings = Builder<EmailSettings>.CreateNew()
.With(s => s.Server = "someserver")
.With(s => s.Port = 567)
.With(s => s.RequireEncryption = true)
.With(s => s.From = "dont@email.me")
.With(s => s.To = new string[] { "dont@email.me" })
.Build();
}
[Test]
public void should_be_valid_if_all_settings_valid()
{
_validator.Validate(_emailSettings).IsValid.Should().BeTrue();
}
[Test]
public void should_not_be_valid_if_port_is_out_of_range()
{
_emailSettings.Port = 900000;
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[Test]
public void should_not_be_valid_if_server_is_empty()
{
_emailSettings.Server = "";
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[Test]
public void should_not_be_valid_if_from_is_empty()
{
_emailSettings.From = "";
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("sonarr")]
[TestCase("sonarr@sonarr")]
[TestCase("email.me")]
[Ignore("Allowed coz some email servers allow arbitrary source, we probably need to support 'Name <email>' syntax")]
public void should_not_be_valid_if_from_is_invalid(string email)
{
_emailSettings.From = email;
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("sonarr")]
[TestCase("sonarr@sonarr")]
[TestCase("email.me")]
public void should_not_be_valid_if_to_is_invalid(string email)
{
_emailSettings.To = new string[] { email };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("sonarr")]
[TestCase("sonarr@sonarr")]
[TestCase("email.me")]
public void should_not_be_valid_if_cc_is_invalid(string email)
{
_emailSettings.Cc = new string[] { email };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("sonarr")]
[TestCase("sonarr@sonarr")]
[TestCase("email.me")]
public void should_not_be_valid_if_bcc_is_invalid(string email)
{
_emailSettings.Bcc = new string[] { email };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[Test]
public void should_not_be_valid_if_to_bcc_cc_are_all_empty()
{
_emailSettings.To = new string[] { };
_emailSettings.Cc = new string[] { };
_emailSettings.Bcc = new string[] { };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
}
}

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using FluentValidation.Results;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
@@ -62,7 +60,7 @@ namespace NzbDrone.Core.Test.NotificationTests
TestLogger.Info("OnDownload was called");
}
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
public override void OnRename(Series series)
{
TestLogger.Info("OnRename was called");
}

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{
(Subject.Definition.Settings as SynologyIndexerSettings).UpdateLibrary = false;
Subject.OnRename(_series, new List<RenamedEpisodeFile>());
Subject.OnRename(_series);
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.UpdateFolder(_series.Path), Times.Never());
@@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test]
public void should_update_entire_series_folder_on_rename()
{
Subject.OnRename(_series, new List<RenamedEpisodeFile>());
Subject.OnRename(_series);
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.UpdateFolder(@"C:\Test\".AsOsAgnostic()), Times.Once());

View File

@@ -9,100 +9,100 @@ namespace NzbDrone.Core.Test.ParserTests
[TestFixture]
public class AbsoluteEpisodeNumberParserFixture : CoreTest
{
[TestCase("[SubDESU]_Show_One_07_(1280x720_x264-AAC)_[6B7FD717]", "Show One", 7, 0, 0)]
[TestCase("[Chihiro]_Show!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Show!!", 6, 0, 0)]
[TestCase("[Commie]_Some_Anime_Show_-_11_[65F220B4]", "Some Anime Show", 11, 0, 0)]
[TestCase("[Underwater]_Some_Anime_Show_-_12_(720p)_[5C7BC4F9]", "Some Anime Show", 12, 0, 0)]
[TestCase("[Commie]_Some_Anime_Show_-_15_[E76552EA]", "Some Anime Show", 15, 0, 0)]
[TestCase("[HorribleSubs]_Some_Anime_Show_-_33_[720p]", "Some Anime Show", 33, 0, 0)]
[TestCase("[HorribleSubs]_Some_Anime_Show_-_145_[720p]", "Some Anime Show", 145, 0, 0)]
[TestCase("[HorribleSubs] Some Anime Show - 13 [1080p].mkv", "Some Anime Show", 13, 0, 0)]
[TestCase("[Doremi].Some.Anime.Show.8.Go!.31.[1280x720].[C65D4B1F].mkv", "Some Anime Show 8 Go!", 31, 0, 0)]
[TestCase("[K-F] Some Anime Show 214", "Some Anime Show", 214, 0, 0)]
[TestCase("[K-F] Some Anime Show S10E14 214", "Some Anime Show", 214, 10, 14)]
[TestCase("[K-F] Some Anime Show 10x14 214", "Some Anime Show", 214, 10, 14)]
[TestCase("[K-F] Some Anime Show 214 10x14", "Some Anime Show", 214, 10, 14)]
[TestCase("Some Anime Show - 031 - The Resolution to Kill [Lunar].avi", "Some Anime Show", 31, 0, 0)]
[TestCase("Some Anime Show - 031 - The Resolution to Kill [Lunar]", "Some Anime Show", 31, 0, 0)]
[TestCase("[ACX]Some Anime Show 01 Role Play [Kosaka] [9C57891E].mkv", "Some Anime Show", 1, 0, 0)]
[TestCase("[SFW-sage] Some Anime Show S3 - 12 [720p][D07C91FC]", "Some Anime Show S3", 12, 0, 0)]
[TestCase("Some_Anime_Show_e66_time_is_money_part_one_marking_time", "Some Anime Show", 66, 0, 0)]
[TestCase("[Underwater-FFF] No Series Title No Life - 01 (720p) [27AAA0A0].mkv", "No Series Title No Life", 1, 0, 0)]
[TestCase("[FroZen] Series Title - 23 [DVD][7F6170E6]", "Series Title", 23, 0, 0)]
[TestCase("[Commie] Series Title - 32 [0BA19D5B]", "Series Title", 32, 0, 0)]
[TestCase("[Doki]Series Title - 07 (1280x720 Hi10P AAC) [80AF7DDE]", "Series Title", 7, 0, 0)]
[TestCase("[HorribleSubs] Series Title - 32 [480p]", "Series Title", 32, 0, 0)]
[TestCase("[CR] Series Title - 004 [480p][48CE2D0F]", "Series Title", 4, 0, 0)]
[TestCase("[Chibiki] Series Title!! - 42 [360p][7A4FC77B]", "Series Title!!", 42, 0, 0)]
[TestCase("[HorribleSubs] Series Title - 32 [1080p]", "Series Title", 32, 0, 0)]
[TestCase("[HorribleSubs] Series Title! S2 - 07 [720p]", "Series Title! S2", 7, 0, 0)]
[TestCase("[DeadFish] Series Title - 09v2 [720p][AAC]", "Series Title", 9, 0, 0)]
[TestCase("[Underwater-FFF] Series Title - 01 (720p) [27AAA0A0]", "Series Title", 1, 0, 0)]
[TestCase("[S-T-D] Series Title! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "Series Title!", 6, 0, 0)]
[TestCase("Series Title - 010 (720p) [27AAA0A0].mkv", "Series Title", 10, 0, 0)]
[TestCase("Initial_Series_Title - 01 DVD - Central Anime", "Initial Series Title", 1, 0, 0)]
[TestCase("Initial_Series_Title_-_01(DVD)_-_(Central_Anime)[5AF6F1E4].mkv", "Initial Series Title", 1, 0, 0)]
[TestCase("Initial_Series_Title_-_02(DVD)_-_(Central_Anime)[0CA65F00].mkv", "Initial Series Title", 2, 0, 0)]
[TestCase("Initial_Series_Title - 03 DVD - Central Anime", "Initial Series Title", 3, 0, 0)]
[TestCase("Initial_Series_Title_-_03(DVD)_-_(Central_Anime)[629BD592].mkv", "Initial Series Title", 3, 0, 0)]
[TestCase("Initial_Series_Title - 14 DVD - Central Anime", "Initial Series Title", 14, 0, 0)]
[TestCase("Initial_Series_Title_-_14(DVD)_-_(Central_Anime)[0183D922].mkv", "Initial Series Title", 14, 0, 0)]
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "High School DxD", 7, 0, 0)]
[TestCase("[Chihiro]_Working!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Working!!", 6, 0, 0)]
[TestCase("[Commie]_Senki_Zesshou_Symphogear_-_11_[65F220B4]", "Senki Zesshou Symphogear", 11, 0, 0)]
[TestCase("[Underwater]_Rinne_no_Lagrange_-_12_(720p)_[5C7BC4F9]", "Rinne no Lagrange", 12, 0, 0)]
[TestCase("[Commie]_Rinne_no_Lagrange_-_15_[E76552EA]", "Rinne no Lagrange", 15, 0, 0)]
[TestCase("[HorribleSubs]_Hunter_X_Hunter_-_33_[720p]", "Hunter X Hunter", 33, 0, 0)]
[TestCase("[HorribleSubs]_Fairy_Tail_-_145_[720p]", "Fairy Tail", 145, 0, 0)]
[TestCase("[HorribleSubs] Tonari no Kaibutsu-kun - 13 [1080p].mkv", "Tonari no Kaibutsu-kun", 13, 0, 0)]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", "Yes Pretty Cure 5 Go Go!", 31, 0, 0)]
[TestCase("[K-F] One Piece 214", "One Piece", 214, 0, 0)]
[TestCase("[K-F] One Piece S10E14 214", "One Piece", 214, 10, 14)]
[TestCase("[K-F] One Piece 10x14 214", "One Piece", 214, 10, 14)]
[TestCase("[K-F] One Piece 214 10x14", "One Piece", 214, 10, 14)]
// [TestCase("One Piece S10E14 214", "One Piece", 214, 10, 14)]
// [TestCase("One Piece 10x14 214", "One Piece", 214, 10, 14)]
// [TestCase("One Piece 214 10x14", "One Piece", 214, 10, 14)]
// [TestCase("214 One Piece 10x14", "One Piece", 214, 10, 14)]
[TestCase("Bleach - 031 - The Resolution to Kill [Lunar].avi", "Bleach", 31, 0, 0)]
[TestCase("Bleach - 031 - The Resolution to Kill [Lunar]", "Bleach", 31, 0, 0)]
[TestCase("[ACX]Hack Sign 01 Role Play [Kosaka] [9C57891E].mkv", "Hack Sign", 1, 0, 0)]
[TestCase("[SFW-sage] Bakuman S3 - 12 [720p][D07C91FC]", "Bakuman S3", 12, 0, 0)]
[TestCase("ducktales_e66_time_is_money_part_one_marking_time", "ducktales", 66, 0, 0)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0].mkv", "No Game No Life", 1, 0, 0)]
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", "Miyuki", 23, 0, 0)]
[TestCase("[Commie] Yowamushi Pedal - 32 [0BA19D5B]", "Yowamushi Pedal", 32, 0, 0)]
[TestCase("[Doki] Mahouka Koukou no Rettousei - 07 (1280x720 Hi10P AAC) [80AF7DDE]", "Mahouka Koukou no Rettousei", 7, 0, 0)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [480p]", "Yowamushi Pedal", 32, 0, 0)]
[TestCase("[CR] Sailor Moon - 004 [480p][48CE2D0F]", "Sailor Moon", 4, 0, 0)]
[TestCase("[Chibiki] Puchimas!! - 42 [360p][7A4FC77B]", "Puchimas!!", 42, 0, 0)]
[TestCase("[HorribleSubs] Yowamushi Pedal - 32 [1080p]", "Yowamushi Pedal", 32, 0, 0)]
[TestCase("[HorribleSubs] Love Live! S2 - 07 [720p]", "Love Live! S2", 7, 0, 0)]
[TestCase("[DeadFish] Onee-chan ga Kita - 09v2 [720p][AAC]", "Onee-chan ga Kita", 9, 0, 0)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0]", "No Game No Life", 1, 0, 0)]
[TestCase("[S-T-D] Soul Eater Not! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "Soul Eater Not!", 6, 0, 0)]
[TestCase("No Game No Life - 010 (720p) [27AAA0A0].mkv", "No Game No Life", 10, 0, 0)]
[TestCase("Initial D Fifth Stage - 01 DVD - Central Anime", "Initial D Fifth Stage", 1, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_01(DVD)_-_(Central_Anime)[5AF6F1E4].mkv", "Initial D Fifth Stage", 1, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_02(DVD)_-_(Central_Anime)[0CA65F00].mkv", "Initial D Fifth Stage", 2, 0, 0)]
[TestCase("Initial D Fifth Stage - 03 DVD - Central Anime", "Initial D Fifth Stage", 3, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_03(DVD)_-_(Central_Anime)[629BD592].mkv", "Initial D Fifth Stage", 3, 0, 0)]
[TestCase("Initial D Fifth Stage - 14 DVD - Central Anime", "Initial D Fifth Stage", 14, 0, 0)]
[TestCase("Initial_D_Fifth_Stage_-_14(DVD)_-_(Central_Anime)[0183D922].mkv", "Initial D Fifth Stage", 14, 0, 0)]
// [TestCase("Initial D - 4th Stage Ep 01.mkv", "Initial D - 4th Stage", 1, 0, 0)]
[TestCase("[ChihiroDesuYo].Series.Title.-.09.1280x720.10bit.AAC.[24CCE81D]", "Series Title", 9, 0, 0)]
[TestCase("Series Title - 001 - Fairy Tail", "Series Title", 001, 0, 0)]
[TestCase("Series Title - 049 - The Day of Fated Meeting", "Series Title", 049, 0, 0)]
[TestCase("Series Title - 050 - Special Request Watch Out for the Guy You Like!", "Series Title", 050, 0, 0)]
[TestCase("Series Title - 099 - Natsu vs. Gildarts", "Series Title", 099, 0, 0)]
[TestCase("Series Title - 100 - Mest", "Series Title", 100, 0, 0)]
[TestCase("[ChihiroDesuYo].No.Game.No.Life.-.09.1280x720.10bit.AAC.[24CCE81D]", "No Game No Life", 9, 0, 0)]
[TestCase("Fairy Tail - 001 - Fairy Tail", "Fairy Tail", 001, 0, 0)]
[TestCase("Fairy Tail - 049 - The Day of Fated Meeting", "Fairy Tail", 049, 0, 0)]
[TestCase("Fairy Tail - 050 - Special Request Watch Out for the Guy You Like!", "Fairy Tail", 050, 0, 0)]
[TestCase("Fairy Tail - 099 - Natsu vs. Gildarts", "Fairy Tail", 099, 0, 0)]
[TestCase("Fairy Tail - 100 - Mest", "Fairy Tail", 100, 0, 0)]
// [TestCase("Fairy Tail - 101 - Mest", "Fairy Tail", 101, 0, 0)] //This gets caught up in the 'see' numbering
[TestCase("[Exiled-Destiny] Series Title Ep01 (D2201EC5).mkv", "Series Title", 1, 0, 0)]
[TestCase("[Commie] Series Title - 23 [5396CA24].mkv", "Series Title", 23, 0, 0)]
[TestCase("[FFF] Series Title - 01 [1FB538B5].mkv", "Series Title", 1, 0, 0)]
[TestCase("[Hatsuyuki]Series_Title-01[1280x720][122E6EF8]", "Series Title", 1, 0, 0)]
[TestCase("[CBM]_Series_Title_-_11_-_511_Kinderheim_[6C70C4E4].mkv", "Series Title", 11, 0, 0)]
[TestCase("[HorribleSubs] Series Title 2 - 05 [720p].mkv", "Series Title 2", 5, 0, 0)]
[TestCase("[Commie] Series Title 2 - 05 [FCE4D070].mkv", "Series Title 2", 5, 0, 0)]
[TestCase("[Exiled-Destiny] Angel Beats Ep01 (D2201EC5).mkv", "Angel Beats", 1, 0, 0)]
[TestCase("[Commie] Nobunaga the Fool - 23 [5396CA24].mkv", "Nobunaga the Fool", 23, 0, 0)]
[TestCase("[FFF] Seikoku no Dragonar - 01 [1FB538B5].mkv", "Seikoku no Dragonar", 1, 0, 0)]
[TestCase("[Hatsuyuki]Fate_Zero-01[1280x720][122E6EF8]", "Fate Zero", 1, 0, 0)]
[TestCase("[CBM]_Monster_-_11_-_511_Kinderheim_[6C70C4E4].mkv", "Monster", 11, 0, 0)]
[TestCase("[HorribleSubs] Log Horizon 2 - 05 [720p].mkv", "Log Horizon 2", 5, 0, 0)]
[TestCase("[Commie] Log Horizon 2 - 05 [FCE4D070].mkv", "Log Horizon 2", 5, 0, 0)]
[TestCase("[DRONE]Series.Title.100", "Series Title", 100, 0, 0)]
[TestCase("[RlsGrp]Series.Title.2010.S01E01.001.HDTV-720p.x264-DTS", "Series Title 2010", 1, 1, 1)]
[TestCase("Series Title - 130 - Found You, Gohan! Harsh Training in the Kaioshin Realm! [Baaro][720p][5A1AD35B].mkv", "Series Title", 130, 0, 0)]
[TestCase("Series Title - 131 - A Merged Super-Warrior Is Born, His Name Is Gotenks!! [Baaro][720p][32E03F96].mkv", "Series Title", 131, 0, 0)]
[TestCase("[HorribleSubs] Series Title - 01 [1080p]", "Series Title", 1, 0, 0)]
[TestCase("[Jumonji-Giri]_[F-B]_Series_Title_Ep04_(0b0e2c10).mkv", "Series Title", 4, 0, 0)]
[TestCase("[Jumonji-Giri]_[F-B]_Series_Title_Ep08_(8246e542).mkv", "Series Title", 8, 0, 0)]
[TestCase("Knights Series Title - 01 [1080p 10b DTSHD-MA eng sub].mkv", "Knights Series Title", 1, 0, 0)]
[TestCase("Dragon Ball Kai - 130 - Found You, Gohan! Harsh Training in the Kaioshin Realm! [Baaro][720p][5A1AD35B].mkv", "Dragon Ball Kai", 130, 0, 0)]
[TestCase("Dragon Ball Kai - 131 - A Merged Super-Warrior Is Born, His Name Is Gotenks!! [Baaro][720p][32E03F96].mkv", "Dragon Ball Kai", 131, 0, 0)]
[TestCase("[HorribleSubs] Magic Kaito 1412 - 01 [1080p]", "Magic Kaito 1412", 1, 0, 0)]
[TestCase("[Jumonji-Giri]_[F-B]_Kagihime_Monogatari_Eikyuu_Alice_Rondo_Ep04_(0b0e2c10).mkv", "Kagihime Monogatari Eikyuu Alice Rondo", 4, 0, 0)]
[TestCase("[Jumonji-Giri]_[F-B]_Kagihime_Monogatari_Eikyuu_Alice_Rondo_Ep08_(8246e542).mkv", "Kagihime Monogatari Eikyuu Alice Rondo", 8, 0, 0)]
[TestCase("Knights of Sidonia - 01 [1080p 10b DTSHD-MA eng sub].mkv", "Knights of Sidonia", 1, 0, 0)]
[TestCase("Series Title (2010) {01} Episode Title (1).hdtv-720p", "Series Title (2010)", 1, 0, 0)]
[TestCase("[HorribleSubs] Series Title - 20 [720p].mkv", "Series Title", 20, 0, 0)]
[TestCase("[Hatsuyuki] Series Title (2014) - 017 (115) [1280x720][B2CFBC0F]", "Series Title (2014)", 17, 0, 0)]
[TestCase("[Hatsuyuki] Series Title (2014) - 018 (116) [1280x720][C4A3B16E]", "Series Title (2014)", 18, 0, 0)]
[TestCase("Series Title (2014) - 39 (137) [v2][720p.HDTV][Unison Fansub]", "Series Title (2014)", 39, 0, 0)]
[TestCase("[HorribleSubs] Series Title 21 - 101 [480p].mkv", "Series Title 21", 101, 0, 0)]
[TestCase("[Cthuyuu].Series.Title.-.03.[720p.H264.AAC][8AD82C3A]", "Series Title", 3, 0, 0)]
//[TestCase("Series.Title.-.03.(1280x720.HEVC.AAC)", "Series Title", 3, 0, 0)]
[TestCase("[Cthuyuu] Series Title - 03 [720p H264 AAC][8AD82C3A]", "Series Title", 3, 0, 0)]
[TestCase("Series Title Episode 56 [VOSTFR V2][720p][AAC]-Mystic Z-Team", "Series Title", 56, 0, 0)]
[TestCase("[Mystic Z-Team] Series Title Episode 69 [VOSTFR_Finale][1080p][AAC].mp4", "Series Title", 69, 0, 0)]
[TestCase("[Shark-Raws] Series Title #957 (NBN 1280x720 x264 AAC).mp4", "Series Title", 957, 0, 0)]
[TestCase("Series Title EP06 720p x265 AOZ.mp4", "Series Title", 6, 0, 0)]
[TestCase("Series Title 2018 EP06 720p x265 AOZ.mp4", "Series Title 2018", 6, 0, 0)]
[TestCase("Series Title 2018 06 720p x265 AOZ.mp4", "Series Title 2018", 6, 0, 0)]
[TestCase("Series Title S03 - EP14 VOSTFR [1080p] [HardSub] Yass'Kun", "Series Title S03", 14, 0, 0)]
[TestCase("Series Title S3 - 15 VOSTFR [720p]", "Series Title S3", 15, 0, 0)]
[TestCase("A Series: RE S2 - Episode 4 VOSTFR (1080p)", "A Series: RE S2", 4, 0, 0)]
[TestCase("To Another Series III - Episode 5 VOSTFR (1080p)", "To Another Series III", 5, 0, 0)]
[TestCase("[Prout] Show;Title 0 - Episode 5 VOSTFR (BDRip 1920x1080 x264 FLAC)", "Show;Title 0", 5, 0, 0)]
[TestCase("[BakedFish] Some Show [Anime] - 01 [720p][AAC].mp4", "Some Show [Anime]", 1, 0, 0)]
[TestCase("[HorribleSubs] Shirobako - 20 [720p].mkv", "Shirobako", 20, 0, 0)]
[TestCase("[Hatsuyuki] Dragon Ball Kai (2014) - 017 (115) [1280x720][B2CFBC0F]", "Dragon Ball Kai (2014)", 17, 0, 0)]
[TestCase("[Hatsuyuki] Dragon Ball Kai (2014) - 018 (116) [1280x720][C4A3B16E]", "Dragon Ball Kai (2014)", 18, 0, 0)]
[TestCase("Dragon Ball Kai (2014) - 39 (137) [v2][720p.HDTV][Unison Fansub]", "Dragon Ball Kai (2014)", 39, 0, 0)]
[TestCase("[HorribleSubs] Eyeshield 21 - 101 [480p].mkv", "Eyeshield 21", 101, 0, 0)]
[TestCase("[Cthuyuu].Taimadou.Gakuen.35.Shiken.Shoutai.-.03.[720p.H264.AAC][8AD82C3A]", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
//[TestCase("Taimadou.Gakuen.35.Shiken.Shoutai.-.03.(1280x720.HEVC.AAC)", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
[TestCase("[Cthuyuu] Taimadou Gakuen 35 Shiken Shoutai - 03 [720p H264 AAC][8AD82C3A]", "Taimadou Gakuen 35 Shiken Shoutai", 3, 0, 0)]
[TestCase("Dragon Ball Super Episode 56 [VOSTFR V2][720p][AAC]-Mystic Z-Team", "Dragon Ball Super", 56, 0, 0)]
[TestCase("[Mystic Z-Team] Dragon Ball Super Episode 69 [VOSTFR_Finale][1080p][AAC].mp4", "Dragon Ball Super", 69, 0, 0)]
[TestCase("[Shark-Raws] Crayon Shin-chan #957 (NBN 1280x720 x264 AAC).mp4", "Crayon Shin-chan", 957, 0, 0)]
[TestCase("Love Rerun EP06 720p x265 AOZ.mp4", "Love Rerun", 6, 0, 0)]
[TestCase("Love Rerun 2018 EP06 720p x265 AOZ.mp4", "Love Rerun 2018", 6, 0, 0)]
[TestCase("Love Rerun 2018 06 720p x265 AOZ.mp4", "Love Rerun 2018", 6, 0, 0)]
[TestCase("Boku No Hero Academia S03 - EP14 VOSTFR [1080p] [HardSub] Yass'Kun", "Boku No Hero Academia S03", 14, 0, 0)]
[TestCase("Boku No Hero Academia S3 - 15 VOSTFR [720p]", "Boku No Hero Academia S3", 15, 0, 0)]
[TestCase("Tokyo Ghoul: RE S2 - Episode 4 VOSTFR (1080p)", "Tokyo Ghoul RE S2", 4, 0, 0)]
[TestCase("To Aru Majutsu no Index III - Episode 5 VOSTFR (1080p)", "To Aru Majutsu no Index III", 5, 0, 0)]
[TestCase("[Prout] Steins;Gate 0 - Episode 5 VOSTFR (BDRip 1920x1080 x264 FLAC)", "Steins;Gate 0", 5, 0, 0)]
[TestCase("[BakedFish] Nakanohito Genome [Jikkyouchuu] - 01 [720p][AAC].mp4", "Nakanohito Genome [Jikkyouchuu]", 1, 0, 0)]
[TestCase("Abc x Abc (2011) - 141 - Magician [KaiDubs] [1080p]", "Abc x Abc (2011)", 141, 0, 0)]
[TestCase("Abc Abc 484 VOSTFR par Abc-Abc (1280*720) - version MQ", "Abc Abc", 484, 0, 0)]
[TestCase("Abc - Abc Abc Abc - 107 VOSTFR par Fansub-Miracle Sharingan (1920x1080) - HQ_Draft", "Abc - Abc Abc Abc", 107, 0, 0)]
[TestCase("Abc Abc Abc Abc Episode 10 VOSTFR (1920x1080) Miracle Sharingan Fansub.MKV - Team - (À suivre)", "Abc Abc Abc Abc", 10, 0, 0)]
[TestCase("[Glenn] Series! 3 - 11 (1080p AAC)[C34B2B3B].mkv", "Series! 3", 11, 0, 0)]
[TestCase("SeriesTitle.E1135.Lasst.den.Mond.am.Himmel.stehen.GERMAN.1080p.WEBRip.x264-Group", "SeriesTitle", 1135, 0, 0)]
[TestCase("[HorribleSubs] Series 100 - 07 [1080p].mkv", "Series 100", 7, 0, 0)]
[TestCase("[HorribleSubs] Series 100 S2 - 07 [1080p].mkv", "Series 100 S2", 7, 0, 0)]
[TestCase("[abc] Adventure Series: 30 [Web][MKV][h264][720p][AAC 2.0][abc]", "Adventure Series:", 30, 0, 0)]
[TestCase("[XKsub] Series Title S2 [05][HEVC-10bit 1080p AAC][CHS&CHT&JPN]", "Series Title S2", 5, 0, 0)]
[TestCase("[Cheetah-Raws] Super Long Anime - 1000 (YTV 1280x720 x264 AAC)", "Super Long Anime", 1000, 0, 0)]
[TestCase("[DameDesuYo] ReZero kara Hajimeru Isekai Seikatsu (Season 2) - 33 (1280x720 10bit EAC3) [42A12A76].mkv", "ReZero kara Hajimeru Isekai Seikatsu", 33, 2, 0)]
[TestCase("[Glenn] Ani ni Tsukeru Kusuri wa Nai! 3 - 11 (1080p AAC)[C34B2B3B].mkv", "Ani ni Tsukeru Kusuri wa Nai! 3", 11, 0, 0)]
[TestCase("Tatort.E1135.Lasst.den.Mond.am.Himmel.stehen.GERMAN.1080p.WEBRip.x264-Group", "Tatort", 1135, 0, 0)]
[TestCase("[HorribleSubs] Abc 100 - 07 [1080p].mkv", "Abc 100", 7, 0, 0)]
[TestCase("[HorribleSubs] Abc 100 S2 - 07 [1080p].mkv", "Abc 100 S2", 7, 0, 0)]
//[TestCase("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{
@@ -115,9 +115,9 @@ namespace NzbDrone.Core.Test.ParserTests
result.FullSeason.Should().BeFalse();
}
[TestCase("[DeadFish] Another Anime Show - 01 - Special [BD][720p][AAC]", "Another Anime Show", 1)]
[TestCase("[DeadFish] Another Anime Show - 01 - OVA [BD][720p][AAC]", "Another Anime Show", 1)]
[TestCase("[DeadFish] Another Anime Show - 01 - OVD [BD][720p][AAC]", "Another Anime Show", 1)]
[TestCase("[DeadFish] Kenzen Robo Daimidaler - 01 - Special [BD][720p][AAC]", "Kenzen Robo Daimidaler", 1)]
[TestCase("[DeadFish] Kenzen Robo Daimidaler - 01 - OVA [BD][720p][AAC]", "Kenzen Robo Daimidaler", 1)]
[TestCase("[DeadFish] Kenzen Robo Daimidaler - 01 - OVD [BD][720p][AAC]", "Kenzen Robo Daimidaler", 1)]
public void should_parse_absolute_specials(string postTitle, string title, int absoluteEpisodeNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);
@@ -130,21 +130,19 @@ namespace NzbDrone.Core.Test.ParserTests
result.Special.Should().BeTrue();
}
[TestCase("[ANBU-AonE]_SeriesTitle_26-27_[F224EF26].avi", "SeriesTitle", 26, 27)]
[TestCase("[Doutei] Some Good, Anime Show - 01-12 [BD][720p-AAC]", "Some Good, Anime Show", 1, 12)]
[TestCase("[ANBU-AonE]_Naruto_26-27_[F224EF26].avi", "Naruto", 26, 27)]
[TestCase("[Doutei] Recently, My Sister is Unusual - 01-12 [BD][720p-AAC]", "Recently, My Sister is Unusual", 1, 12)]
[TestCase("Series Title (2010) - 01-02-03 - Episode Title (1) HDTV-720p", "Series Title (2010)", 1, 3)]
[TestCase("[RlsGrp] Series Title (2010) - S01E01-02-03 - 001-002-003 - Episode Title HDTV-720p v2", "Series Title (2010)", 1, 3)]
[TestCase("[RlsGrp] Series Title (2010) - S01E01-02 - 001-002 - Episode Title HDTV-720p v2", "Series Title (2010)", 1, 2)]
[TestCase("Series Title (2010) - S01E01-02 (001-002) - Episode Title (1) HDTV-720p v2 [RlsGrp]", "Series Title (2010)", 1, 2)]
[TestCase("[HorribleSubs] Some Anime Show!! (01-25) [1080p] (Batch)", "Some Anime Show!!", 1, 25)]
[TestCase("Some Anime Show (2011) Episode 99-100 [1080p] [Dual.Audio] [x265]", "Some Anime Show (2011)", 99, 100)]
[TestCase("Some Anime Show 1-13 (English Dub) [720p]", "Some Anime Show", 1, 13)]
[TestCase("[HorribleSubs] Haikyuu!! (01-25) [1080p] (Batch)", "Haikyuu!!", 1, 25)]
[TestCase("Hunter X Hunter (2011) Episode 99-100 [1080p] [Dual.Audio] [x265]", "Hunter X Hunter (2011)", 99, 100)]
[TestCase("Twin Star Exorcists 1-13 (English Dub) [720p]", "Twin Star Exorcists", 1, 13)]
[TestCase("Series.Title.Ep01-12.Complete.English.AC3.DL.1080p.BluRay.x264", "Series Title", 1, 12)]
[TestCase("[Judas] Some Anime Show 091-123 [1080p][HEVC x265 10bit][Dual-Audio][Multi-Subs]", "Some Anime Show", 91, 123 )]
[TestCase("[Judas] Some Anime Show - 091-123 [1080p][HEVC x265 10bit][Dual-Audio][Multi-Subs]", "Some Anime Show", 91, 123)]
[TestCase("[HorribleSubs] Some Anime Show 01 - 119 [1080p] [Batch]", "Some Anime Show", 1, 119)]
[TestCase("[Erai-raws] Series Title! - 01~10 [1080p][Multiple Subtitle]", "Series Title!", 1, 10)]
[TestCase("[Erai-raws] Series Title! 2 - 01~10 [1080p][Multiple Subtitle]", "Series Title! 2", 1, 10)]
[TestCase("[Judas] Black Clover 091-123 [1080p][HEVC x265 10bit][Dual-Audio][Multi-Subs]", "Black Clover", 91, 123 )]
[TestCase("[Judas] Black Clover - 091-123 [1080p][HEVC x265 10bit][Dual-Audio][Multi-Subs]", "Black Clover", 91, 123)]
[TestCase("[HorribleSubs] Black Clover 01 - 119 [1080p] [Batch]", "Black Clover", 1, 119)]
// [TestCase("", "", 1, 2)]
public void should_parse_multi_episode_absolute_numbers(string postTitle, string title, int firstAbsoluteEpisodeNumber, int lastAbsoluteEpisodeNumber)
{
@@ -157,7 +155,7 @@ namespace NzbDrone.Core.Test.ParserTests
result.FullSeason.Should().BeFalse();
}
[TestCase("[Vivid] Some Anime Show S01 [Web][MKV][h264 10-bit][1080p][AAC 2.0]", "Some Anime Show", 1)]
[TestCase("[Vivid] Living Sky Saga S01 [Web][MKV][h264 10-bit][1080p][AAC 2.0]", "Living Sky Saga", 1)]
public void should_parse_anime_season_packs(string postTitle, string title, int seasonNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);
@@ -168,7 +166,7 @@ namespace NzbDrone.Core.Test.ParserTests
result.SeasonNumber.Should().Be(seasonNumber);
}
[TestCase("[HorribleSubs] Show Slayer - 10.5 [1080p].mkv", "Show Slayer", 10.5)]
[TestCase("[HorribleSubs] Goblin Slayer - 10.5 [1080p].mkv", "Goblin Slayer", 10.5)]
public void should_handle_anime_recap_numbering(string postTitle, string title, double specialEpisodeNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);

View File

@@ -8,21 +8,21 @@ namespace NzbDrone.Core.Test.ParserTests
[TestFixture]
public class AnimeMetadataParserFixture : CoreTest
{
[TestCase("[SubDESU]_Show_Title_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "SubDESU", "6B7FD717")]
[TestCase("[Chihiro]_Show_Title!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Chihiro", "859EEAFA")]
[TestCase("[Underwater]_Show_Title_-_12_(720p)_[5C7BC4F9]", "Underwater", "5C7BC4F9")]
[TestCase("[HorribleSubs]_Show_Title_-_33_[720p]", "HorribleSubs", "")]
[TestCase("[HorribleSubs] Show-Title - 13 [1080p].mkv", "HorribleSubs", "")]
[TestCase("[Doremi].Show.Title.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Show.Title.5.Go.Go!.31[1280x720].[C65D4B1F]", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Show.Title.5.Go.Go!.31.[1280x720].mkv", "Doremi", "")]
[TestCase("[K-F] Series Title 214", "K-F", "")]
[TestCase("[K-F] Series Title S10E14 214", "K-F", "")]
[TestCase("[K-F] Series Title 10x14 214", "K-F", "")]
[TestCase("[K-F] Series Title 214 10x14", "K-F", "")]
[TestCase("Series Title - 031 - The Resolution to Kill [Lunar].avi", "Lunar", "")]
[TestCase("[ACX]Series Title 01 Episode Name [Kosaka] [9C57891E].mkv", "ACX", "9C57891E")]
[TestCase("[S-T-D] Series Title! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "S-T-D", "59B3F2EA")]
[TestCase("[SubDESU]_High_School_DxD_07_(1280x720_x264-AAC)_[6B7FD717]", "SubDESU", "6B7FD717")]
[TestCase("[Chihiro]_Working!!_-_06_[848x480_H.264_AAC][859EEAFA]", "Chihiro", "859EEAFA")]
[TestCase("[Underwater]_Rinne_no_Lagrange_-_12_(720p)_[5C7BC4F9]", "Underwater", "5C7BC4F9")]
[TestCase("[HorribleSubs]_Hunter_X_Hunter_-_33_[720p]", "HorribleSubs", "")]
[TestCase("[HorribleSubs] Tonari no Kaibutsu-kun - 13 [1080p].mkv", "HorribleSubs", "")]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F].mkv", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].[C65D4B1F]", "Doremi", "C65D4B1F")]
[TestCase("[Doremi].Yes.Pretty.Cure.5.Go.Go!.31.[1280x720].mkv", "Doremi", "")]
[TestCase("[K-F] One Piece 214", "K-F", "")]
[TestCase("[K-F] One Piece S10E14 214", "K-F", "")]
[TestCase("[K-F] One Piece 10x14 214", "K-F", "")]
[TestCase("[K-F] One Piece 214 10x14", "K-F", "")]
[TestCase("Bleach - 031 - The Resolution to Kill [Lunar].avi", "Lunar", "")]
[TestCase("[ACX]Hack Sign 01 Role Play [Kosaka] [9C57891E].mkv", "ACX", "9C57891E")]
[TestCase("[S-T-D] Soul Eater Not! - 06 (1280x720 10bit AAC) [59B3F2EA].mkv", "S-T-D", "59B3F2EA")]
public void should_parse_absolute_numbers(string postTitle, string subGroup, string hash)
{
var result = Parser.Parser.ParseTitle(postTitle);

View File

@@ -86,13 +86,13 @@ namespace NzbDrone.Core.Test.ParserTests
success.Should().Be(repetitions);
}
[TestCase("theseriestitle1618finale")]
[TestCase("thebiggestloser1618finale")]
public void should_not_parse_file_name_without_proper_spacing(string fileName)
{
Parser.Parser.ParseTitle(fileName).Should().BeNull();
}
[TestCase("Series Title (2018) Complete 360p HDTV AAC H.264-NEXT")]
[TestCase("Big Forest (2018) Complete 360p HDTV AAC H.264-NEXT")]
public void should_not_parse_invalid_release_name(string fileName)
{
Parser.Parser.ParseTitle(fileName).Should().BeNull();

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