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

Compare commits

..

1 Commits

Author SHA1 Message Date
Robin Dadswell
bf8d21cd52 initial additions 2021-12-07 16:37:59 +00:00
266 changed files with 1923 additions and 3706 deletions

View File

@@ -7,13 +7,13 @@ variables:
outputFolder: './_output'
artifactsFolder: './_artifacts'
testsFolder: './_tests'
majorVersion: '4.0.2'
majorVersion: '4.0.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.101'
dotnetVersion: '6.0.100'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger:

View File

@@ -120,7 +120,7 @@ class Blocklist extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label={translate('RemoveSelected')}
label="Remove Selected"
iconName={icons.REMOVE}
isDisabled={!selectedIds.length}
isSpinning={isRemoving}

View File

@@ -25,7 +25,6 @@ function HistoryDetails(props) {
releaseGroup,
nzbInfoUrl,
downloadClient,
downloadClientName,
downloadId,
age,
ageHours,
@@ -33,8 +32,6 @@ function HistoryDetails(props) {
publishedDate
} = data;
const downloadClientNameInfo = downloadClientName ?? downloadClient;
return (
<DescriptionList>
<DescriptionListItem
@@ -74,12 +71,11 @@ function HistoryDetails(props) {
}
{
downloadClientNameInfo ?
!!downloadClient &&
<DescriptionListItem
title={translate('DownloadClient')}
data={downloadClientNameInfo}
/> :
null
data={downloadClient}
/>
}
{

View File

@@ -161,7 +161,7 @@ class AddNewMovie extends Component {
{translate('YouCanAlsoSearch')}
</div>
<div>
<Link to="https://wiki.servarr.com/radarr/faq#why-can-i-not-add-a-new-movie-to-radarr">
<Link to="https://wiki.servarr.com/radarr/faq#why-cant-i-add-a-new-movie-to-radarr">
{translate('CantFindMovie')}
</Link>
</div>

View File

@@ -183,7 +183,7 @@ class AddNewMovieSearchResult extends Component {
<div>
<Label size={sizes.LARGE}>
<HeartRating
ratings={ratings}
rating={ratings.value}
iconSize={13}
/>
</Label>

View File

@@ -43,15 +43,15 @@ class CalendarEvent extends Component {
const link = `/movie/${titleSlug}`;
const eventType = [];
if (inCinemas && moment(date).isSame(moment(inCinemas), 'day')) {
if (moment(date).isSame(moment(inCinemas), 'day')) {
eventType.push('Cinemas');
}
if (physicalRelease && moment(date).isSame(moment(physicalRelease), 'day')) {
if (moment(date).isSame(moment(physicalRelease), 'day')) {
eventType.push('Physical');
}
if (digitalRelease && moment(date).isSame(moment(digitalRelease), 'day')) {
if (moment(date).isSame(moment(digitalRelease), 'day')) {
eventType.push('Digital');
}

View File

@@ -166,9 +166,7 @@ class FilterBuilderModalContent extends Component {
</div>
</div>
<div className={styles.label}>
{translate('Filters')}
</div>
<div className={styles.label}>Filters</div>
<div className={styles.rows}>
{

View File

@@ -6,7 +6,8 @@ import SelectInput from './SelectInput';
const availabilityOptions = [
{ key: 'announced', value: translate('Announced') },
{ key: 'inCinemas', value: translate('InCinemas') },
{ key: 'released', value: translate('Released') }
{ key: 'released', value: translate('Released') },
{ key: 'preDB', value: translate('PreDB') }
];
function AvailabilitySelectInput(props) {

View File

@@ -1,100 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.downloadClients,
(state, { includeAny }) => includeAny,
(state, { protocol }) => protocol,
(downloadClients, includeAny, protocolFilter) => {
const {
isFetching,
isPopulated,
error,
items
} = downloadClients;
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return {
key: downloadClient.id,
value: downloadClient.name
};
});
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
});
}
return {
isFetching,
isPopulated,
error,
values
};
}
);
}
const mapDispatchToProps = {
dispatchFetchDownloadClients: fetchDownloadClients
};
class DownloadClientSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchDownloadClients();
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
DownloadClientSelectInputConnector.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeAny: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchDownloadClients: PropTypes.func.isRequired
};
DownloadClientSelectInputConnector.defaultProps = {
includeAny: false,
protocol: 'torrent'
};
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);

View File

@@ -8,7 +8,6 @@ import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector';
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
@@ -74,9 +73,6 @@ function getComponent(type) {
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInputConnector;

View File

@@ -1,5 +1,4 @@
.image {
align-content: center;
.heart {
margin-right: 5px;
vertical-align: -0.125em;
color: $themeRed;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

View File

@@ -1,58 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './RottenTomatoRating.css';
class RottenTomatoRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const rtRotten = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQ1LjE4NSA0NDQuNjg0Yy03OS4zNjkgNC4xNjctOTUuNTg3LTg2LjY1Mi0xMjYuNzI2LTg2LjAwNi0xMy4yNjguMjc5LTIzLjcyNiAxNC4xNTEtMTkuMTMzIDMwLjMyIDIuNTI1IDguODg4IDkuNTMgMjEuOTIzIDEzLjk0NCAzMC4wMTEgMTUuNTcgMjguNTQ0LTcuNDQ3IDYwLjg0NS0zNC4zODMgNjMuNTc3LTQ0Ljc2IDQuNTQtNjMuNDMzLTIxLjQyNi02Mi4yNzgtNDguMDA3IDEuMy0yOS44NCAyNi42LTYwLjMzMS42NS03My4zMDUtMjcuMTk0LTEzLjU5Ny00OS4zMDEgMzkuNTcyLTc1LjMyNSA1MS40MzktMjMuNTUzIDEwLjc0MS01Ni4yNDggMi40MTMtNjcuODcyLTIzLjc0MS04LjE2NC0xOC4zNzktNi42OC01My43NjggMjkuNjctNjcuMjcgMjIuNzA2LTguNDMzIDczLjMwNSAxMS4wMjkgNzUuOS0xMy42MjMgMi45OTItMjguNDE2LTUzLjE1NS0zMC44MTItNzAuMDYtMzcuNjI2LTI5LjkxMi0xMi4wNTUtNDcuNTY3LTM3Ljg1LTMzLjczNC02NS41MjIgMTAuMzc4LTIwLjc1NyA0MC45MTUtMjkuMjAzIDY0LjIyMy0yMC4xMSAyNy45MjIgMTAuODkyIDMyLjQwNCAzOS44NTMgNDYuNzEgNTEuODk3IDEyLjMyNCAxMC4zOCAyOS4xOSAxMS42OCA0MC4yMiA0LjU0MyA4LjEzNS01LjI2NSAxMC44NDMtMTYuODI4IDcuNzc0LTI3LjM5LTQuMDctMTQuMDIzLTE0Ljg3NS0yMi43NzMtMjUuNDE1LTMxLjM0Ni0xOC43NTgtMTUuMjQ5LTQ1LjI0LTI4LjM2LTI5LjIyMi02OS45ODMgMTMuMTMtMzQuMTEgNTEuNjQyLTM1LjM0IDUxLjY0Mi0zNS4zNCAxNS4zLTEuNzIgMjkuMDAyIDIuOSA0MC4xNjcgMTIuODc1IDE0LjkyNyAxMy4zMzUgMTcuODM0IDMxLjE2IDE1LjMzNiA1MC4xNzYtMi4yODMgMTcuMzU4LTguNDI2IDMyLjU2LTExLjYzIDQ5Ljc1OS0zLjcxNyAxOS45NjYgNi45NTQgNDAuMDg2IDI3LjI0OSA0MC44NjkgMjYuNjk0IDEuMDMxIDM0LjY5OC0xOS40ODYgMzcuOTY0LTMyLjQ5MiA0Ljc4Mi0xOS4wMjggMTEuMDU4LTM2LjY5NCAyOC43MTgtNDcuODIgMjUuMzQ2LTE1Ljk3IDYwLjU1Mi0xMi40NyA3Ni44ODYgMTguMjIyIDEyLjkyIDI0LjI4NCA4Ljc3MiA1Ny43MTUtMTEuMDQ3IDc1Ljk3LTguODkyIDguMTg4LTE5LjU4NCAxMS4wNzUtMzEuMTQ4IDExLjE1Ni0xNi41ODUuMTE3LTMzLjE2Mi0uMjktNDguNTU2IDcuNDcxLTEwLjQ4IDUuMjgxLTE1LjA0NyAxMy44ODgtMTUuMDQ1IDI1LjQyMyAwIDExLjI0MiA1Ljg1MyAxOC41ODUgMTUuMzM2IDIzLjM2MyAxNy44NiA5LjAwMyAzNy41NzcgMTAuODQzIDU2Ljg3MSAxNC4yMjIgMjcuOTggNC45IDUyLjU4MSAxNC43NTUgNjguMzc1IDQwLjcyLjE0Mi4yMjguMjguNDU4LjQxNS42OSAxOC4xMzkgMzAuNzQxLS44MzEgNzUuMDA1LTM2LjQ3NiA3Ni44NzgiIGZpbGw9IiMwQUM4NTUiLz48L3N2Zz4=';
const rtFresh = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNDc4LjI5IDI5Ni45OGMtMy45OS02My45NjYtMzYuNTItMTExLjgyLTg1LjQ2OC0xMzguNTggMC4yNzggMS41Ni0xLjEwOSAzLjUwOC0yLjY4OCAyLjgxOC0zMi4wMTYtMTQuMDA2LTg2LjMyOCAzMS4zMi0xMjQuMjggNy41ODQgMC4yODUgOC41MTktMS4zNzggNTAuMDcyLTU5LjkxNCA1Mi40ODMtMS4zODIgMC4wNTYtMi4xNDItMS4zNTUtMS4yNjgtMi4zNTQgNy44MjgtOC45MjkgMTUuNzMyLTMxLjUzNSA0LjM2Ny00My41ODYtMjQuMzM4IDIxLjgxLTM4LjQ3MiAzMC4wMTctODUuMTM4IDE5LjE4Ni0yOS44NzggMzEuMjQxLTQ2LjgwOSA3NC00My40ODUgMTI3LjI2IDYuNzggMTA4Ljc0IDEwOC42MyAxNzAuODkgMjExLjE5IDE2NC40OSAxMDIuNTYtNi4zOTUgMTkzLjQ3LTgwLjU3MiAxODYuNjgtMTg5LjMxIiBmaWxsPSIjRkEzMjBBIi8+PHBhdGggZD0iTTI5MS4zNzUgMTMyLjI5M2MyMS4wNzUtNS4wMjMgODEuNjkzLS40OSAxMDEuMTE0IDI1LjI3NCAxLjE2NiAxLjU0NS0uNDc1IDQuNDY4LTIuMzU1IDMuNjQ4LTMyLjAxNi0xNC4wMDYtODYuMzI4IDMxLjMyLTEyNC4yODIgNy41ODQuMjg1IDguNTE5LTEuMzc4IDUwLjA3Mi01OS45MTQgNTIuNDgzLTEuMzgyLjA1Ni0yLjE0Mi0xLjM1NS0xLjI2OC0yLjM1NCA3LjgyOC04LjkyOSAxNS43My0zMS41MzUgNC4zNjctNDMuNTg2LTI2LjUxMiAyMy43NTgtNDAuODg0IDMxLjM5Mi05OC40MjYgMTUuODM4LTEuODgzLS41MDgtMS4yNDEtMy41MzUuNzYyLTQuMjk4IDEwLjg3Ni00LjE1NyAzNS41MTUtMjIuMzYxIDU4LjgyNC0zMC4zODUgNC40MzgtMS41MjYgOC44NjItMi43MSAxMy4xOC0zLjQtMjUuNjY1LTIuMjkzLTM3LjIzNS01Ljg2Mi01My41NTktMy40LTEuNzg5LjI3LTMuMDA0LTEuODEzLTEuODk1LTMuMjQxIDIxLjk5NS0yOC4zMzIgNjIuNTEzLTM2Ljg4OCA4Ny41MTItMjEuODM3LTE1LjQxLTE5LjA5NC0yNy40OC0zNC4zMjEtMjcuNDgtMzQuMzIxbDI4LjYwMS0xNi4yNDZzMTEuODE3IDI2LjQgMjAuNDE0IDQ1LjYxNGMyMS4yNzUtMzEuNDM1IDYwLjg2LTM0LjMzNiA3Ny41ODUtMTIuMDMzLjk5MiAxLjMyNi0uMDQ1IDMuMjEtMS43MDIgMy4xNzEtMTMuNjEyLS4zMzEtMjEuMTA3IDEyLjA1LTIxLjY3NSAyMS40NjZsLjE5Ny4wMjMiIGZpbGw9IiMwMDkxMkQiLz48L3N2Zz4=';
const rating = ratings.rottenTomatoes;
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value}%`;
}
return (
<span>
{
!hideIcon &&
<img
className={styles.image}
src={rating.value > 50 ? rtFresh : rtRotten}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
RottenTomatoRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
RottenTomatoRating.defaultProps = {
iconSize: 14
};
export default RottenTomatoRating;

View File

@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}

View File

@@ -1,57 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './TmdbRating.css';
class TmdbRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const tmdbImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTAuMjQgODEuNTIiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeTE9IjQwLjc2IiB4Mj0iMTkwLjI0IiB5Mj0iNDAuNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNlYTEiLz48c3RvcCBvZmZzZXQ9Ii41NiIgc3RvcC1jb2xvcj0iIzNjYmVjOSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwYjNlNSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xMDUuNjcgMzYuMDZoNjYuOWExNy42NyAxNy42NyAwIDAwMTcuNjctMTcuNjZBMTcuNjcgMTcuNjcgMCAwMDE3Mi41Ny43M2gtNjYuOUExNy42NyAxNy42NyAwIDAwODggMTguNGExNy42NyAxNy42NyAwIDAwMTcuNjcgMTcuNjZ6bS04OCA0NWg3Ni45YTE3LjY3IDE3LjY3IDAgMDAxNy42Ny0xNy42NiAxNy42NyAxNy42NyAwIDAwLTE3LjY3LTE3LjY3aC03Ni45QTE3LjY3IDE3LjY3IDAgMDAwIDYzLjRhMTcuNjcgMTcuNjcgMCAwMDE3LjY3IDE3LjY2em0tNy4yNi00NS42NGg3LjhWNi45MmgxMC4xVjBoLTI4djYuOWgxMC4xem0yOC4xIDBoNy44VjguMjVoLjFsOSAyNy4xNWg2bDkuMy0yNy4xNWguMVYzNS40aDcuOFYwSDY2Ljc2bC04LjIgMjMuMWgtLjFMNTAuMzEgMGgtMTEuOHptMTEzLjkyIDIwLjI1YTE1LjA3IDE1LjA3IDAgMDAtNC41Mi01LjUyIDE4LjU3IDE4LjU3IDAgMDAtNi42OC0zLjA4IDMzLjU0IDMzLjU0IDAgMDAtOC4wNy0xaC0xMS43djM1LjRoMTIuNzVhMjQuNTggMjQuNTggMCAwMDcuNTUtMS4xNSAxOS4zNCAxOS4zNCAwIDAwNi4zNS0zLjMyIDE2LjI3IDE2LjI3IDAgMDA0LjM3LTUuNSAxNi45MSAxNi45MSAwIDAwMS42My03LjU4IDE4LjUgMTguNSAwIDAwLTEuNjgtOC4yNXpNMTQ1IDY4LjZhOC44IDguOCAwIDAxLTIuNjQgMy40IDEwLjcgMTAuNyAwIDAxLTQgMS44MiAyMS41NyAyMS41NyAwIDAxLTUgLjU1aC00LjA1di0yMWg0LjZhMTcgMTcgMCAwMTQuNjcuNjMgMTEuNjYgMTEuNjYgMCAwMTMuODggMS44N0E5LjE0IDkuMTQgMCAwMTE0NSA1OWE5Ljg3IDkuODcgMCAwMTEgNC41MiAxMS44OSAxMS44OSAwIDAxLTEgNS4wOHptNDQuNjMtLjEzYTggOCAwIDAwLTEuNTgtMi42MiA4LjM4IDguMzggMCAwMC0yLjQyLTEuODUgMTAuMzEgMTAuMzEgMCAwMC0zLjE3LTF2LS4xYTkuMjIgOS4yMiAwIDAwNC40Mi0yLjgyIDcuNDMgNy40MyAwIDAwMS42OC01IDguNDIgOC40MiAwIDAwLTEuMTUtNC42NSA4LjA5IDguMDkgMCAwMC0zLTIuNzIgMTIuNTYgMTIuNTYgMCAwMC00LjE4LTEuMyAzMi44NCAzMi44NCAwIDAwLTQuNjItLjMzaC0xMy4ydjM1LjRoMTQuNWEyMi40MSAyMi40MSAwIDAwNC43Mi0uNSAxMy41MyAxMy41MyAwIDAwNC4yOC0xLjY1IDkuNDIgOS40MiAwIDAwMy4xLTMgOC41MiA4LjUyIDAgMDAxLjItNC42OCA5LjM5IDkuMzkgMCAwMC0uNTUtMy4xOHptLTE5LjQyLTE1Ljc1aDUuM2ExMCAxMCAwIDAxMS44NS4xOCA2LjE4IDYuMTggMCAwMTEuNy41NyAzLjM5IDMuMzkgMCAwMTEuMjIgMS4xMyAzLjIyIDMuMjIgMCAwMS40OCAxLjgyIDMuNjMgMy42MyAwIDAxLS40MyAxLjggMy40IDMuNCAwIDAxLTEuMTIgMS4yIDQuOTIgNC45MiAwIDAxLTEuNTguNjUgNy41MSA3LjUxIDAgMDEtMS43Ny4yaC01LjY1em0xMS43MiAyMGEzLjkgMy45IDAgMDEtMS4yMiAxLjMgNC42NCA0LjY0IDAgMDEtMS42OC43IDguMTggOC4xOCAwIDAxLTEuODIuMmgtN3YtOGg1LjlhMTUuMzUgMTUuMzUgMCAwMTIgLjE1IDguNDcgOC40NyAwIDAxMi4wNS41NSA0IDQgMCAwMTEuNTcgMS4xOCAzLjExIDMuMTEgMCAwMS42MyAyIDMuNzEgMy43MSAwIDAxLS40MyAxLjkyeiIgZmlsbD0idXJsKCNhKSIvPjwvc3ZnPg==';
const rating = ratings.tmdb;
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value * 10}%`;
}
return (
<span title={`${rating.votes} votes`}>
{
!hideIcon &&
<img
className={styles.image}
src={tmdbImage}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
TmdbRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
TmdbRating.defaultProps = {
iconSize: 14
};
export default TmdbRating;

View File

@@ -73,7 +73,7 @@ function getInfoRowProps(row, props) {
return {
title: translate('Ratings'),
iconName: icons.HEART,
label: `${props.ratings.tmdb.value * 10}%`
label: `${props.ratings.value * 10}%`
};
}

View File

@@ -112,7 +112,7 @@ function DiscoverMoviePosterInfo(props) {
return (
<div className={styles.info}>
<HeartRating
ratings={ratings}
rating={ratings.value}
/>
</div>
);
@@ -129,7 +129,7 @@ DiscoverMoviePosterInfo.propTypes = {
digitalRelease: PropTypes.string,
physicalRelease: PropTypes.string,
runtime: PropTypes.number,
ratings: PropTypes.arrayOf(PropTypes.object).isRequired,
ratings: PropTypes.object,
sortKey: PropTypes.string.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -246,7 +246,7 @@ class DiscoverMovieRow extends Component {
className={styles[name]}
>
<HeartRating
ratings={ratings}
rating={ratings.value}
/>
</VirtualTableRowCell>
);
@@ -373,7 +373,7 @@ DiscoverMovieRow.propTypes = {
digitalRelease: PropTypes.string,
runtime: PropTypes.number,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
ratings: PropTypes.arrayOf(PropTypes.object).isRequired,
ratings: PropTypes.object.isRequired,
certification: PropTypes.string,
collection: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -13,7 +13,6 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
@@ -36,7 +35,6 @@ export const all = [
PASSWORD,
PATH,
QUALITY_PROFILE_SELECT,
DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT,

View File

@@ -19,7 +19,6 @@ import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
@@ -41,11 +40,6 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{
name: 'quality',
label: translate('Quality'),
@@ -89,7 +83,6 @@ const SELECT = 'select';
const MOVIE = 'movie';
const LANGUAGE = 'language';
const QUALITY = 'quality';
const RELEASE_GROUP = 'releaseGroup';
class InteractiveImportModalContent extends Component {
@@ -209,11 +202,10 @@ class InteractiveImportModalContent extends Component {
const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems'));
const bulkSelectOptions = [
{ key: SELECT, value: translate('SelectDotDot'), disabled: true },
{
key: SELECT, value: translate('SelectDotDot'), disabled: true },
{ key: LANGUAGE, value: translate('SelectLanguage') },
{ key: QUALITY, value: translate('SelectQuality') },
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
];
{ key: QUALITY, value: translate('SelectQuality') }];
if (allowMovieChange) {
bulkSelectOptions.splice(1, 0, {
@@ -380,13 +372,6 @@ class InteractiveImportModalContent extends Component {
real={false}
onModalClose={this.onSelectModalClose}
/>
<SelectReleaseGroupModal
isOpen={selectModalOpen === RELEASE_GROUP}
ids={selectedIds}
releaseGroup=""
onModalClose={this.onSelectModalClose}
/>
</ModalContent>
);
}

View File

@@ -110,8 +110,7 @@ class InteractiveImportModalContentConnector extends Component {
const {
movie,
quality,
languages,
releaseGroup
languages
} = item;
if (!movie) {
@@ -133,7 +132,6 @@ class InteractiveImportModalContentConnector extends Component {
path: item.path,
folderName: item.folderName,
movieId: movie.id,
releaseGroup,
quality,
languages,
downloadId: this.props.downloadId

View File

@@ -11,7 +11,6 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import formatBytes from 'Utilities/Number/formatBytes';
@@ -29,7 +28,6 @@ class InteractiveImportRow extends Component {
this.state = {
isSelectMovieModalOpen: false,
isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false,
isSelectLanguageModalOpen: false
};
@@ -105,10 +103,6 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectMovieModalOpen: true });
}
onSelectReleaseGroupPress = () => {
this.setState({ isSelectReleaseGroupModalOpen: true });
}
onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true });
}
@@ -122,11 +116,6 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed);
}
onSelectReleaseGroupModalClose = (changed) => {
this.setState({ isSelectReleaseGroupModalOpen: false });
this.selectRowAfterChange(changed);
}
onSelectQualityModalClose = (changed) => {
this.setState({ isSelectQualityModalOpen: false });
this.selectRowAfterChange(changed);
@@ -148,7 +137,6 @@ class InteractiveImportRow extends Component {
movie,
quality,
languages,
releaseGroup,
size,
rejections,
isReprocessing,
@@ -159,8 +147,7 @@ class InteractiveImportRow extends Component {
const {
isSelectMovieModalOpen,
isSelectQualityModalOpen,
isSelectLanguageModalOpen,
isSelectReleaseGroupModalOpen
isSelectLanguageModalOpen
} = this.state;
const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : '';
@@ -168,7 +155,6 @@ class InteractiveImportRow extends Component {
const showMoviePlaceholder = isSelected && !movie;
const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !languages && !isReprocessing;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
return (
<TableRow>
@@ -195,17 +181,6 @@ class InteractiveImportRow extends Component {
}
</TableRowCellButton>
<TableRowCellButton
title={translate('ClickToChangeReleaseGroup')}
onPress={this.onSelectReleaseGroupPress}
>
{
showReleaseGroupPlaceholder ?
<InteractiveImportRowCellPlaceholder /> :
releaseGroup
}
</TableRowCellButton>
<TableRowCellButton
className={styles.quality}
title={translate('ClickToChangeQuality')}
@@ -293,13 +268,6 @@ class InteractiveImportRow extends Component {
onModalClose={this.onSelectMovieModalClose}
/>
<SelectReleaseGroupModal
isOpen={isSelectReleaseGroupModalOpen}
ids={[id]}
releaseGroup={releaseGroup ?? ''}
onModalClose={this.onSelectReleaseGroupModalClose}
/>
<SelectQualityModal
isOpen={isSelectQualityModalOpen}
ids={[id]}
@@ -328,7 +296,6 @@ InteractiveImportRow.propTypes = {
movie: PropTypes.object,
quality: PropTypes.object,
languages: PropTypes.arrayOf(PropTypes.object),
releaseGroup: PropTypes.string,
size: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
isReprocessing: PropTypes.bool,

View File

@@ -128,7 +128,7 @@ class SelectLanguageModalContent extends Component {
kind={kinds.SUCCESS}
onPress={this.onLanguageSelect}
>
{translate('SelectLanguages')}
{translate('SelectLanguges')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -1,37 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import SelectReleaseGroupModalContentConnector from './SelectReleaseGroupModalContentConnector';
class SelectReleaseGroupModal extends Component {
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<SelectReleaseGroupModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
SelectReleaseGroupModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectReleaseGroupModal;

View File

@@ -1,99 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class SelectReleaseGroupModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
const {
releaseGroup
} = props;
this.state = {
releaseGroup
};
}
//
// Listeners
onReleaseGroupChange = ({ value }) => {
this.setState({ releaseGroup: value });
}
onReleaseGroupSelect = () => {
this.props.onReleaseGroupSelect(this.state);
}
//
// Render
render() {
const {
onModalClose
} = this.props;
const {
releaseGroup
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('ManualImportSetReleaseGroup')}
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="releaseGroup"
value={releaseGroup}
onChange={this.onReleaseGroupChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.SUCCESS}
onPress={this.onReleaseGroupSelect}
>
{translate('SetReleaseGroup')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
SelectReleaseGroupModalContent.propTypes = {
releaseGroup: PropTypes.string.isRequired,
onReleaseGroupSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectReleaseGroupModalContent;

View File

@@ -1,54 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import SelectReleaseGroupModalContent from './SelectReleaseGroupModalContent';
const mapDispatchToProps = {
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
};
class SelectReleaseGroupModalContentConnector extends Component {
//
// Listeners
onReleaseGroupSelect = ({ releaseGroup }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchReprocessInteractiveImportItems
} = this.props;
dispatchUpdateInteractiveImportItems({
ids,
releaseGroup
});
dispatchReprocessInteractiveImportItems({ ids });
this.props.onModalClose(true);
}
//
// Render
render() {
return (
<SelectReleaseGroupModalContent
{...this.props}
onReleaseGroupSelect={this.onReleaseGroupSelect}
/>
);
}
}
SelectReleaseGroupModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(null, mapDispatchToProps)(SelectReleaseGroupModalContentConnector);

View File

@@ -155,7 +155,7 @@ DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.number.isRequired,
sizeOnDisk: PropTypes.string.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -5,7 +5,7 @@
.header {
position: relative;
width: 100%;
height: 375px;
height: 350px;
}
.errorMessage {
@@ -41,8 +41,8 @@
.poster {
flex-shrink: 0;
margin-right: 35px;
width: 217px;
height: 319px;
width: 200px;
height: 294px;
}
.info {

View File

@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import TextTruncate from 'react-text-truncate';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import InfoLabel from 'Components/InfoLabel';
import IconButton from 'Components/Link/IconButton';
import Marquee from 'Components/Marquee';
@@ -16,8 +16,6 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
@@ -451,6 +449,17 @@ class MovieDetails extends Component {
</span>
}
{
!!ratings &&
<span className={styles.rating}>
<HeartRating
rating={ratings.value}
iconSize={20}
hideHeart={isSmallScreen}
/>
</span>
}
{
<span className={styles.links}>
<Tooltip
@@ -492,36 +501,6 @@ class MovieDetails extends Component {
</div>
</div>
<div className={styles.details}>
{
!!ratings.tmdb &&
<span className={styles.rating}>
<TmdbRating
ratings={ratings}
iconSize={20}
/>
</span>
}
{
!!ratings.imdb &&
<span className={styles.rating}>
<ImdbRating
ratings={ratings}
iconSize={20}
/>
</span>
}
{
!!ratings.rottenTomatoes &&
<span className={styles.rating}>
<RottenTomatoRating
ratings={ratings}
iconSize={20}
/>
</span>
}
</div>
<div className={styles.detailsLabels}>
<InfoLabel
className={styles.detailsInfoLabel}

View File

@@ -67,7 +67,8 @@ class MovieHistoryRow extends Component {
data,
isMarkingAsFailed,
shortDateFormat,
timeFormat
timeFormat,
onMarkAsFailedPress
} = this.props;
const {
@@ -142,7 +143,7 @@ class MovieHistoryRow extends Component {
isMarkingAsFailed={isMarkingAsFailed}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
onMarkAsFailedPress={this.onMarkAsFailedPress}
onMarkAsFailedPress={onMarkAsFailedPress}
onModalClose={this.onDetailsModalClose}
/>
</TableRow>

View File

@@ -101,15 +101,6 @@ function MovieIndexSortMenu(props) {
{translate('DigitalRelease')}
</SortMenuItem>
<SortMenuItem
name="ratings"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Ratings')}
</SortMenuItem>
<SortMenuItem
name="path"
sortKey={sortKey}

View File

@@ -332,7 +332,7 @@ class MovieIndexRow extends Component {
className={styles[name]}
>
<HeartRating
ratings={ratings}
rating={ratings.value}
/>
</VirtualTableRowCell>
);

View File

@@ -6,7 +6,7 @@ export function getMovieStatusDetails(status) {
let statusDetails = {
icon: icons.ANNOUNCED,
title: translate('Announced'),
message: translate('AnnouncedMsg')
message: translate('AnnoucedMsg')
};
if (status === 'deleted') {

View File

@@ -4,11 +4,6 @@
margin-right: auto;
}
.rightButtons {
justify-content: flex-end;
margin-right: auto;
}
.addSpecification {
composes: customFormat from '~./CustomFormat.css';

View File

@@ -198,25 +198,26 @@ class EditCustomFormatModalContent extends Component {
</div>
</ModalBody>
<ModalFooter>
<div className={styles.rightButtons}>
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteCustomFormatPress}
>
{translate('Delete')}
</Button>
}
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteCustomFormatPress}
>
{translate('Delete')}
</Button>
}
<Button
className={styles.deleteButton}
onPress={this.onImportPress}
>
{translate('Import')}
</Button>
</div>
{
!id &&
<Button
className={styles.deleteButton}
onPress={this.onImportPress}
>
{translate('Import')}
</Button>
}
<Button
onPress={onModalClose}

View File

@@ -51,15 +51,9 @@ class RemotePathMappings extends Component {
{...otherProps}
>
<div className={styles.remotePathMappingsHeader}>
<div className={styles.host}>
{translate('Host')}
</div>
<div className={styles.path}>
{translate('RemotePath')}
</div>
<div className={styles.path}>
{translate('LocalPath')}
</div>
<div className={styles.host}>Host</div>
<div className={styles.path}>Remote Path</div>
<div className={styles.path}>Local Path</div>
</div>
<div>

View File

@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector';
import EditImportExclusionModalContentConnector from './EditImportExclusionModalContentConnector';
function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditImportListExclusionModalContentConnector
<EditImportExclusionModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@@ -19,9 +19,9 @@ function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
);
}
EditImportListExclusionModal.propTypes = {
EditImportExclusionModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditImportListExclusionModal;
export default EditImportExclusionModal;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditImportListExclusionModal from './EditImportListExclusionModal';
import EditImportExclusionModal from './EditImportExclusionModal';
function mapStateToProps() {
return {};
@@ -12,7 +12,7 @@ const mapDispatchToProps = {
clearPendingChanges
};
class EditImportListExclusionModalConnector extends Component {
class EditImportExclusionModalConnector extends Component {
//
// Listeners
@@ -27,7 +27,7 @@ class EditImportListExclusionModalConnector extends Component {
render() {
return (
<EditImportListExclusionModal
<EditImportExclusionModal
{...this.props}
onModalClose={this.onModalClose}
/>
@@ -35,9 +35,9 @@ class EditImportListExclusionModalConnector extends Component {
}
}
EditImportListExclusionModalConnector.propTypes = {
EditImportExclusionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(EditImportListExclusionModalConnector);
export default connect(mapStateToProps, mapDispatchToProps)(EditImportExclusionModalConnector);

View File

@@ -13,9 +13,9 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditImportListExclusionModalContent.css';
import styles from './EditImportExclusionModalContent.css';
function EditImportListExclusionModalContent(props) {
function EditImportExclusionModalContent(props) {
const {
id,
isFetching,
@@ -130,7 +130,7 @@ function EditImportListExclusionModalContent(props) {
);
}
EditImportListExclusionModalContent.propTypes = {
EditImportExclusionModalContent.propTypes = {
id: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
@@ -143,4 +143,4 @@ EditImportListExclusionModalContent.propTypes = {
onDeleteImportExclusionPress: PropTypes.func
};
export default EditImportListExclusionModalContent;
export default EditImportExclusionModalContent;

View File

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
import EditImportExclusionModalContent from './EditImportExclusionModalContent';
const newImportExclusion = {
movieTitle: '',
@@ -97,7 +97,7 @@ class EditImportExclusionModalContentConnector extends Component {
render() {
return (
<EditImportListExclusionModalContent
<EditImportExclusionModalContent
{...this.props}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}

View File

@@ -8,14 +8,12 @@
}
.movieTitle {
@add-mixin truncate;
flex: 0 0 600px;
flex: 0 0 400px;
}
.tmdbId,
.movieYear {
flex: 0 0 70px;
flex: 0 0 200px;
}
.actions {

View File

@@ -6,10 +6,10 @@ import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportListExclusion.css';
import EditImportExclusionModalConnector from './EditImportExclusionModalConnector';
import styles from './ImportExclusion.css';
class ImportListExclusion extends Component {
class ImportExclusion extends Component {
//
// Lifecycle
@@ -78,7 +78,7 @@ class ImportListExclusion extends Component {
</Link>
</div>
<EditImportListExclusionModalConnector
<EditImportExclusionModalConnector
id={id}
isOpen={this.state.isEditImportExclusionModalOpen}
onModalClose={this.onEditImportExclusionModalClose}
@@ -99,7 +99,7 @@ class ImportListExclusion extends Component {
}
}
ImportListExclusion.propTypes = {
ImportExclusion.propTypes = {
id: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired,
@@ -107,9 +107,9 @@ ImportListExclusion.propTypes = {
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};
ImportListExclusion.defaultProps = {
ImportExclusion.defaultProps = {
// The drag preview will not connect the drag handle.
connectDragSource: (node) => node
};
export default ImportListExclusion;
export default ImportExclusion;

View File

@@ -1,16 +1,16 @@
.importListExclusionsHeader {
.importExclusionsHeader {
display: flex;
margin-bottom: 10px;
font-weight: bold;
}
.title {
flex: 0 0 600px;
flex: 0 0 400px;
}
.tmdbId,
.movieYear {
flex: 0 0 70px;
flex: 0 0 200px;
}
.addImportExclusion {

View File

@@ -6,11 +6,11 @@ import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion';
import styles from './ImportListExclusions.css';
import EditImportExclusionModalConnector from './EditImportExclusionModalConnector';
import ImportExclusion from './ImportExclusion';
import styles from './ImportExclusions.css';
class ImportListExclusions extends Component {
class ImportExclusions extends Component {
//
// Lifecycle
@@ -50,23 +50,17 @@ class ImportListExclusions extends Component {
errorMessage={translate('UnableToLoadListExclusions')}
{...otherProps}
>
<div className={styles.importListExclusionsHeader}>
<div className={styles.tmdbId}>
TMDb Id
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
<div className={styles.importExclusionsHeader}>
<div className={styles.tmdbId}>TMDB Id</div>
<div className={styles.title}>Title</div>
<div className={styles.movieYear}>Year</div>
</div>
<div>
{
items.map((item, index) => {
return (
<ImportListExclusion
<ImportExclusion
key={item.id}
{...item}
{...otherProps}
@@ -87,7 +81,7 @@ class ImportListExclusions extends Component {
</Link>
</div>
<EditImportListExclusionModalConnector
<EditImportExclusionModalConnector
isOpen={this.state.isAddImportExclusionModalOpen}
onModalClose={this.onModalClose}
/>
@@ -98,11 +92,11 @@ class ImportListExclusions extends Component {
}
}
ImportListExclusions.propTypes = {
ImportExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
};
export default ImportListExclusions;
export default ImportExclusions;

View File

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteImportExclusion, fetchImportExclusions } from 'Store/Actions/settingsActions';
import ImportListExclusions from './ImportListExclusions';
import ImportExclusions from './ImportExclusions';
function createMapStateToProps() {
return createSelector(
@@ -42,7 +42,7 @@ class ImportExclusionsConnector extends Component {
render() {
return (
<ImportListExclusions
<ImportExclusions
{...this.state}
{...this.props}
onConfirmDeleteImportExclusion={this.onConfirmDeleteImportExclusion}

View File

@@ -7,7 +7,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
import ImportExclusionsConnector from './ImportExclusions/ImportExclusionsConnector';
import ImportListsConnector from './ImportLists/ImportListsConnector';
import ImportListOptionsConnector from './Options/ImportListOptionsConnector';
@@ -86,7 +86,7 @@ class ImportListSettings extends Component {
onChildStateChange={this.onChildStateChange}
/>
<ImportListExclusionsConnector />
<ImportExclusionsConnector />
</PageContentBody>
</PageContent>

View File

@@ -45,9 +45,7 @@ function EditIndexerModalContent(props) {
supportsSearch,
tags,
fields,
priority,
protocol,
downloadClientId
priority
} = item;
return (
@@ -156,25 +154,8 @@ function EditIndexerModalContent(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('DownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
name="downloadClientId"
helpText={translate('IndexerDownloadClientHelpText')}
{...downloadClientId}
includeAny={true}
protocol={protocol.value}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormLabel>Tags</FormLabel>
<FormInputGroup
type={inputTypes.TAG}

View File

@@ -85,7 +85,7 @@ function IndexerOptions(props) {
type={inputTypes.CHECK}
name="preferIndexerFlags"
helpText={translate('PreferIndexerFlagsHelpText')}
helpLink="https://wiki.servarr.com/radarr/settings#indexer-flags"
helpLink="https://wiki.servarr.com/Definitions#Indexer_Flags"
onChange={onInputChange}
{...settings.preferIndexerFlags}
/>

View File

@@ -147,8 +147,7 @@ class NamingModal extends Component {
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' },
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' }
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }
];
const releaseGroupTokens = [

View File

@@ -63,7 +63,6 @@ class Notification extends Component {
onMovieFileDelete,
onMovieFileDeleteForUpgrade,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
@@ -71,8 +70,7 @@ class Notification extends Component {
supportsOnMovieDelete,
supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade,
supportsOnHealthIssue,
supportsOnApplicationUpdate
supportsOnHealthIssue
} = this.props;
return (
@@ -125,14 +123,6 @@ class Notification extends Component {
null
}
{
supportsOnApplicationUpdate && onApplicationUpdate ?
<Label kind={kinds.SUCCESS}>
{translate('OnApplicationUpdate')}
</Label> :
null
}
{
supportsOnMovieDelete && onMovieDelete ?
<Label kind={kinds.SUCCESS}>
@@ -158,7 +148,7 @@ class Notification extends Component {
}
{
!onGrab && !onDownload && !onRename && !onHealthIssue && !onApplicationUpdate && !onMovieDelete && !onMovieFileDelete ?
!onGrab && !onDownload && !onRename && !onHealthIssue && !onMovieDelete && !onMovieFileDelete ?
<Label
kind={kinds.DISABLED}
outline={true}
@@ -200,7 +190,6 @@ Notification.propTypes = {
onMovieFileDelete: PropTypes.bool.isRequired,
onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired,
supportsOnMovieDelete: PropTypes.bool.isRequired,
@@ -209,7 +198,6 @@ Notification.propTypes = {
supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -23,7 +23,6 @@ function NotificationEventItems(props) {
onMovieFileDelete,
onMovieFileDeleteForUpgrade,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
@@ -31,7 +30,6 @@ function NotificationEventItems(props) {
supportsOnMovieDelete,
supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade,
supportsOnApplicationUpdate,
supportsOnHealthIssue,
includeHealthWarnings
} = item;
@@ -152,17 +150,6 @@ function NotificationEventItems(props) {
/>
</div>
}
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onApplicationUpdate"
helpText={translate('OnApplicationUpdateHelpText')}
isDisabled={!supportsOnApplicationUpdate.value}
{...onApplicationUpdate}
onChange={onInputChange}
/>
</div>
</div>
</div>
</FormGroup>

View File

@@ -82,18 +82,10 @@ class DelayProfiles extends Component {
>
<div>
<div className={styles.delayProfilesHeader}>
<div className={styles.column}>
{translate('Protocol')}
</div>
<div className={styles.column}>
{translate('UsenetDelay')}
</div>
<div className={styles.column}>
{translate('TorrentDelay')}
</div>
<div className={styles.tags}>
{translate('Tags')}
</div>
<div className={styles.column}>Protocol</div>
<div className={styles.column}>Usenet Delay</div>
<div className={styles.column}>Torrent Delay</div>
<div className={styles.tags}>Tags</div>
</div>
<div className={styles.delayProfiles}>

View File

@@ -25,15 +25,9 @@ class QualityDefinitions extends Component {
{...otherProps}
>
<div className={styles.header}>
<div className={styles.quality}>
{translate('Quality')}
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.sizeLimit}>
{translate('SizeLimit')}
</div>
<div className={styles.quality}>Quality</div>
<div className={styles.title}>Title</div>
<div className={styles.sizeLimit}>Size Limit</div>
{
advancedSettings ?

View File

@@ -152,7 +152,7 @@ function TagDetailsModalContent(props) {
{
indexers.length ?
<FieldSet legend={translate('Indexers')}>
<FieldSet legend="Indexers">
{
indexers.map((item) => {
return (

View File

@@ -14,6 +14,7 @@ function createRemoveItemHandler(section, url) {
const ajaxOptions = {
url: `${url}/${id}?${$.param(queryParams, true)}`,
dataType: 'text',
method: 'DELETE'
};

View File

@@ -109,7 +109,6 @@ export default {
selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete;
selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete;
selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema;
});

View File

@@ -200,7 +200,7 @@ export const defaultState = {
ratings: function(item) {
const { ratings = {} } = item;
return ratings.tmdb? ratings.tmdb.value : 0;
return ratings.value;
}
},
@@ -330,23 +330,8 @@ export const defaultState = {
valueType: filterBuilderValueTypes.MINIMUM_AVAILABILITY
},
{
name: 'tmdbRating',
label: translate('TmdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'tmdbVotes',
label: translate('TmdbVotes'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),
name: 'ratings',
label: 'Rating',
type: filterBuilderTypes.NUMBER
},
{

View File

@@ -148,10 +148,9 @@ export const actionHandlers = handleThunks({
return {
id,
path: item.path,
movieId: item.movie ? item.movie.id : undefined,
movieId: item.movie.id,
quality: item.quality,
languages: item.languages,
releaseGroup: item.releaseGroup,
downloadId: item.downloadId
};
});

View File

@@ -131,38 +131,10 @@ export const filterPredicates = {
return dateFilterPredicate(item.digitalRelease, filterValue, type);
},
tmdbRating: function(item, filterValue, type) {
ratings: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.tmdb ? item.ratings.tmdb.value : 0;
return predicate(rating * 10, filterValue);
},
tmdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.tmdb ? item.ratings.tmdb.votes : 0;
return predicate(rating, filterValue);
},
imdbRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
console.log(item.ratings);
const rating = item.ratings.imdb ? item.ratings.imdb.value : 0;
return predicate(rating, filterValue);
},
imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.imdb ? item.ratings.imdb.votes : 0;
return predicate(rating, filterValue);
return predicate(item.ratings.value * 10, filterValue);
},
qualityCutoffNotMet: function(item) {

View File

@@ -209,7 +209,7 @@ export const defaultState = {
ratings: function(item) {
const { ratings = {} } = item;
return ratings.tmdb? ratings.tmdb.value : 0;
return ratings.value;
}
},
@@ -357,23 +357,8 @@ export const defaultState = {
}
},
{
name: 'tmdbRating',
label: translate('TmdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'tmdbVotes',
label: translate('TmdbVotes'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),
name: 'ratings',
label: translate('Ratings'),
type: filterBuilderTypes.NUMBER
},
{

View File

@@ -27,7 +27,7 @@ const paged = `${section}.paged`;
export const defaultState = {
options: {
includeUnknownMovieItems: true
includeUnknownMovieItems: false
},
status: {

View File

@@ -100,7 +100,7 @@ export default function createSentryMiddleware() {
return;
}
const dsn = isProduction ? 'https://7794f2858478485ea337fb5535624fbd@sentry.servarr.com/12' :
const dsn = isProduction ? 'https://b0fb75c38ef4487dbf742f79c4ba62d2@sentry.servarr.com/12' :
'https://da610619280249f891ec3ee306906793@sentry.servarr.com/13';
sentry.init({

View File

@@ -19,7 +19,6 @@ function getInternalLink(source) {
case 'IndexerRssCheck':
case 'IndexerSearchCheck':
case 'IndexerStatusCheck':
case 'IndexerJackettAllCheck':
case 'IndexerLongTermStatusCheck':
return (
<IconButton

View File

@@ -30,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "6.0.1",
"@microsoft/signalr": "6.0.0",
"@sentry/browser": "6.13.2",
"@sentry/integrations": "6.13.2",
"classnames": "2.3.1",

View File

@@ -146,7 +146,7 @@ namespace NzbDrone.Common.Test.Http
var request = new HttpRequest($"https://expired.badssl.com");
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
ExceptionVerification.ExpectedErrors(1);
ExceptionVerification.ExpectedErrors(2);
}
[Test]

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Common.EnvironmentInfo
private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null)
public RuntimeInfo(IHostLifetime hostLifetime, Logger logger)
{
_logger = logger;

View File

@@ -171,26 +171,5 @@ namespace NzbDrone.Common.Extensions
{
return source.Contains(value, StringComparer.InvariantCultureIgnoreCase);
}
public static string EncodeRFC3986(this string value)
{
// From Twitterizer http://www.twitterizer.net/
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
var encoded = Uri.EscapeDataString(value);
return Regex
.Replace(encoded, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper())
.Replace("(", "%28")
.Replace(")", "%29")
.Replace("$", "%24")
.Replace("!", "%21")
.Replace("*", "%2A")
.Replace("'", "%27")
.Replace("%7E", "~");
}
}
}

View File

@@ -1,12 +0,0 @@
using System.Net;
namespace NzbDrone.Common.Http
{
public class BasicNetworkCredential : NetworkCredential
{
public BasicNetworkCredential(string user, string pass)
: base(user, pass)
{
}
}
}

View File

@@ -4,9 +4,9 @@ using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -26,21 +26,22 @@ namespace NzbDrone.Common.Http.Dispatchers
private readonly ICertificateValidationService _certificateValidationService;
private readonly IUserAgentBuilder _userAgentBuilder;
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
private readonly ICached<CredentialCache> _credentialCache;
private readonly Logger _logger;
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
ICreateManagedWebProxy createManagedWebProxy,
ICertificateValidationService certificateValidationService,
IUserAgentBuilder userAgentBuilder,
ICacheManager cacheManager)
ICacheManager cacheManager,
Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy;
_certificateValidationService = certificateValidationService;
_userAgentBuilder = userAgentBuilder;
_logger = logger;
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
}
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
@@ -66,26 +67,6 @@ namespace NzbDrone.Common.Http.Dispatchers
cts.CancelAfter(TimeSpan.FromSeconds(100));
}
if (request.Credentials != null)
{
if (request.Credentials is BasicNetworkCredential bc)
{
// Manually set header to avoid initial challenge response
var authInfo = bc.UserName + ":" + bc.Password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
requestMessage.Headers.Add("Authorization", "Basic " + authInfo);
}
else if (request.Credentials is NetworkCredential nc)
{
var creds = GetCredentialCache();
foreach (var authtype in new[] { "Basic", "Digest" })
{
creds.Remove((Uri)request.Url, authtype);
creds.Add((Uri)request.Url, authtype, nc);
}
}
}
if (request.ContentData != null)
{
requestMessage.Content = new ByteArrayContent(request.ContentData);
@@ -98,32 +79,49 @@ namespace NzbDrone.Common.Http.Dispatchers
var httpClient = GetClient(request.Url);
using var responseMessage = httpClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
HttpResponseMessage responseMessage;
try
{
byte[] data = null;
try
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
responseMessage.Content.CopyTo(request.ResponseStream, null, cts.Token);
}
else
{
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
responseMessage = httpClient.Send(requestMessage, cts.Token);
}
catch (HttpRequestException e)
{
_logger.Error(e, "HttpClient error");
throw;
}
byte[] data = null;
using (var responseStream = responseMessage.Content.ReadAsStream())
{
if (responseStream != null && responseStream != Stream.Null)
{
try
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
// A target ResponseStream was specified, write to that instead.
// But only on the OK status code, since we don't want to write failures and redirects.
responseStream.CopyTo(request.ResponseStream);
}
else
{
data = responseStream.ToBytes();
}
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
}
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
return new HttpResponse(request, new HttpHeader(headers), data, responseMessage.StatusCode);
}
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
@@ -142,8 +140,6 @@ namespace NzbDrone.Common.Http.Dispatchers
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Brotli,
UseCookies = false, // sic - we don't want to use a shared cookie container
AllowAutoRedirect = false,
Credentials = GetCredentialCache(),
PreAuthenticate = true,
MaxConnectionsPerServer = 12,
ConnectCallback = onConnect,
SslOptions = new SslClientAuthenticationOptions
@@ -228,11 +224,6 @@ namespace NzbDrone.Common.Http.Dispatchers
headers.Add(header, value);
}
private CredentialCache GetCredentialCache()
{
return _credentialCache.Get("credentialCache", () => new CredentialCache());
}
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.

View File

@@ -38,7 +38,6 @@ namespace NzbDrone.Common.Http
public HttpHeader Headers { get; set; }
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public ICredentials Credentials { get; set; }
public bool SuppressHttpError { get; set; }
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
public bool UseSimplifiedUserAgent { get; set; }
@@ -90,5 +89,12 @@ namespace NzbDrone.Common.Http
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data);
}
public void AddBasicAuthentication(string username, string password)
{
var authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"));
Headers.Set("Authorization", "Basic " + authInfo);
}
}
}

View File

@@ -26,9 +26,10 @@ namespace NzbDrone.Common.Http
public bool ConnectionKeepAlive { get; set; }
public TimeSpan RateLimit { get; set; }
public bool LogResponseContent { get; set; }
public ICredentials NetworkCredential { get; set; }
public NetworkCredential NetworkCredential { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public List<HttpFormData> FormData { get; private set; }
public Action<HttpRequest> PostProcess { get; set; }
public HttpRequestBuilder(string baseUrl)
@@ -108,7 +109,13 @@ namespace NzbDrone.Common.Http
request.ConnectionKeepAlive = ConnectionKeepAlive;
request.RateLimit = RateLimit;
request.LogResponseContent = LogResponseContent;
request.Credentials = NetworkCredential;
if (NetworkCredential != null)
{
var authInfo = NetworkCredential.UserName + ":" + NetworkCredential.Password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers.Set("Authorization", "Basic " + authInfo);
}
foreach (var header in Headers)
{

View File

@@ -1,103 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Common.Http
{
public class XmlRpcRequestBuilder : HttpRequestBuilder
{
public static string XmlRpcContentType = "text/xml";
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(XmlRpcRequestBuilder));
public string XmlMethod { get; private set; }
public List<object> XmlParameters { get; private set; }
public XmlRpcRequestBuilder(string baseUrl)
: base(baseUrl)
{
Method = HttpMethod.Post;
XmlParameters = new List<object>();
}
public XmlRpcRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
: this(BuildBaseUrl(useHttps, host, port, urlBase))
{
}
public override HttpRequestBuilder Clone()
{
var clone = base.Clone() as XmlRpcRequestBuilder;
clone.XmlParameters = new List<object>(XmlParameters);
return clone;
}
public XmlRpcRequestBuilder Call(string method, params object[] parameters)
{
var clone = Clone() as XmlRpcRequestBuilder;
clone.XmlMethod = method;
clone.XmlParameters = parameters.ToList();
return clone;
}
protected override void Apply(HttpRequest request)
{
base.Apply(request);
request.Headers.ContentType = XmlRpcContentType;
var methodCallElements = new List<XElement> { new XElement("methodName", XmlMethod) };
if (XmlParameters.Any())
{
var argElements = XmlParameters.Select(x => new XElement("param", ConvertParameter(x))).ToList();
var paramsElement = new XElement("params", argElements);
methodCallElements.Add(paramsElement);
}
var message = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("methodCall", methodCallElements));
var body = message.ToString();
Logger.Debug($"Executing remote method: {XmlMethod}");
Logger.Trace($"methodCall {XmlMethod} body:\n{body}");
request.SetContent(body);
}
private static XElement ConvertParameter(object value)
{
XElement data;
if (value is string s)
{
data = new XElement("string", s);
}
else if (value is List<string> l)
{
data = new XElement("array", new XElement("data", l.Select(x => new XElement("value", new XElement("string", x)))));
}
else if (value is int i)
{
data = new XElement("int", i);
}
else if (value is byte[] bytes)
{
data = new XElement("base64", Convert.ToBase64String(bytes));
}
else
{
throw new InvalidOperationException($"Unhandled argument type {value.GetType().Name}");
}
return new XElement("value", data);
}
}
}

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Common.Instrumentation
else
{
dsn = RuntimeInfo.IsProduction
? "https://26668106d708406b9ddf5a2bda34fcbb@sentry.servarr.com/9"
? "https://39b572f7f3f04899b2c3254c7ac126d0@sentry.servarr.com/9"
: "https://998b4673d4c849ccb5277b5966ed5bc2@sentry.servarr.com/10";
}

View File

@@ -12,7 +12,7 @@
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="3.11.1" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.1" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />

View File

@@ -1,71 +0,0 @@
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using FluentAssertions;
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 migrate_discord_from_slackFixture : MigrationTest<migrate_discord_from_slack>
{
private readonly JsonSerializerOptions _serializerSettings;
public migrate_discord_from_slackFixture()
{
_serializerSettings = new JsonSerializerOptions
{
AllowTrailingCommas = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
PropertyNameCaseInsensitive = true,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
_serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true));
}
[Test]
public void should_replace_old_url()
{
var webhookUrl = "https://discord.com/api/webhooks/922499153416847361/f9CAcD5i_E_-0AoPfMVa8igVK8h271HpJDbd6euUrPh9KonWlMCziLOSMmD-2SQ4CHmX/slack";
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
Name = "SlackDiscord",
Implementation = "Slack",
Settings = new SlackNotificationSettings201
{
Icon = "TestURL",
Username = "TestUsername",
WebHookUrl = webhookUrl
}.ToJson(),
ConfigContract = "SlackSettings",
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnRename = true,
OnHealthIssue = true,
OnMovieDelete = true,
OnMovieFileDelete = true,
OnMovieFileDeleteForUpgrade = true,
IncludeHealthWarnings = true
});
});
var items = db.Query<NotificationEntity201>("SELECT Id,ConfigContract,Implementation,Name,Settings FROM Notifications");
items.Should().HaveCount(1);
items.First().ConfigContract.Should().Be("DiscordSettings");
var settings = JsonSerializer.Deserialize<DiscordNotificationSettings201>(items.First().Settings, _serializerSettings);
settings.Avatar.Should().Be("TestURL");
settings.Username.Should().Be("TestUsername");
settings.WebHookUrl.Should().Be(webhookUrl.Replace("/slack", ""));
}
}
}

View File

@@ -1,91 +0,0 @@
using System;
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 remove_predbFixture : MigrationTest<remove_predb>
{
[Test]
public void should_change_min_avail_from_predb_on_list()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("ImportLists").Row(new
{
Enabled = 1,
EnableAuto = 1,
RootFolderPath = "D:\\Movies",
ProfileId = 1,
MinimumAvailability = 4,
ShouldMonitor = 1,
Name = "IMDB List",
Implementation = "RadarrLists",
Settings = new RadarrListSettings169
{
APIURL = "https://api.radarr.video/v2",
Path = "/imdb/list?listId=ls000199717",
}.ToJson(),
ConfigContract = "RadarrSettings"
});
});
var items = db.Query<ListDefinition201>("SELECT Id, MinimumAvailability FROM ImportLists");
items.Should().HaveCount(1);
items.First().MinimumAvailability.Should().Be(3);
}
[Test]
public void should_change_min_avail_from_predb_on_movie()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Movies").Row(new
{
Monitored = true,
Title = "Title",
CleanTitle = "CleanTitle",
Status = 3,
MinimumAvailability = 4,
Images = new[] { new { CoverType = "Poster" } }.ToJson(),
Recommendations = new[] { 1 }.ToJson(),
HasPreDBEntry = false,
Runtime = 90,
OriginalLanguage = 1,
ProfileId = 1,
MovieFileId = 0,
Path = string.Format("/Movies/{0}", "Title"),
TitleSlug = 123456,
TmdbId = 132456,
Added = DateTime.UtcNow,
LastInfoSync = DateTime.UtcNow,
});
});
var items = db.Query<Movie201>("SELECT Id, MinimumAvailability FROM Movies");
items.Should().HaveCount(1);
items.First().MinimumAvailability.Should().Be(3);
}
}
public class ListDefinition201
{
public int Id { get; set; }
public int MinimumAvailability { get; set; }
}
public class Movie201
{
public int Id { get; set; }
public int MinimumAvailability { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
using System.Data;
using System.Data;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
@@ -38,7 +38,6 @@ namespace NzbDrone.Core.Test.Datastore.SqliteSchemaDumperTests
result.Name.Should().Be(tableName);
result.Columns.Count.Should().Be(1);
result.Columns.First().Name.Should().Be(columnName);
result.Columns.First().IsIdentity.Should().BeTrue();
}
[TestCase(@"CREATE INDEX TestIndex ON TestTable (MyId)", "TestIndex", "TestTable", "MyId")]

View File

@@ -1,11 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
@@ -68,17 +67,6 @@ namespace NzbDrone.Core.Test.Download
return mock;
}
private void WithTorrentIndexer(int downloadClientId)
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Find(It.IsAny<int>()))
.Returns(Builder<IndexerDefinition>
.CreateNew()
.With(v => v.Id = _nextId++)
.With(v => v.DownloadClientId = downloadClientId)
.Build());
}
private void GivenBlockedClient(int id)
{
_blockedProviders.Add(new DownloadClientStatus
@@ -235,39 +223,5 @@ namespace NzbDrone.Core.Test.Download
client3.Definition.Id.Should().Be(2);
client4.Definition.Id.Should().Be(3);
}
[Test]
public void should_always_choose_indexer_client()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentIndexer(3);
var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent, 1);
client1.Definition.Id.Should().Be(3);
client2.Definition.Id.Should().Be(3);
client3.Definition.Id.Should().Be(3);
client4.Definition.Id.Should().Be(3);
client5.Definition.Id.Should().Be(3);
}
[Test]
public void should_fail_to_choose_client_when_indexer_reference_does_not_exist()
{
WithUsenetClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentClient();
WithTorrentIndexer(5);
Assert.Throws<DownloadClientUnavailableException>(() => Subject.GetDownloadClient(DownloadProtocol.Torrent, 1));
}
}
}

View File

@@ -31,8 +31,8 @@ namespace NzbDrone.Core.Test.Download
.Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClient(It.IsAny<DownloadProtocol>(), It.IsAny<int>()))
.Returns<DownloadProtocol, int>((v, i) => _downloadClients.FirstOrDefault(d => d.Protocol == v));
.Setup(v => v.GetDownloadClient(It.IsAny<DownloadProtocol>()))
.Returns<DownloadProtocol>(v => _downloadClients.FirstOrDefault(d => d.Protocol == v));
var releaseInfo = Builder<ReleaseInfo>.CreateNew()
.With(v => v.DownloadProtocol = DownloadProtocol.Usenet)

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.Resolve<ConfigService>(), TestLogger));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>(), TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(Array.Empty<IHttpRequestInterceptor>(), Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), TestLogger));
Mocker.SetConstant<IRadarrCloudRequestBuilder>(new RadarrCloudRequestBuilder());
}

View File

@@ -1,74 +0,0 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Torznab;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class IndexerJackettAllCheckFixture : CoreTest<IndexerJackettAllCheck>
{
private List<IndexerDefinition> _indexers = new List<IndexerDefinition>();
private IndexerDefinition _definition;
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.All())
.Returns(_indexers);
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
}
private void GivenIndexer(string baseUrl, string apiPath)
{
var torznabSettings = new TorznabSettings
{
BaseUrl = baseUrl,
ApiPath = apiPath
};
_definition = new IndexerDefinition
{
Name = "Indexer",
ConfigContract = "TorznabSettings",
Settings = torznabSettings
};
_indexers.Add(_definition);
}
[Test]
public void should_not_return_error_when_no_indexers()
{
Subject.Check().ShouldBeOk();
}
[TestCase("http://localhost:9117/", "api")]
public void should_not_return_error_when_no_jackett_all_indexers(string baseUrl, string apiPath)
{
GivenIndexer(baseUrl, apiPath);
Subject.Check().ShouldBeOk();
}
[TestCase("http://localhost:9117/torznab/all/api", "api")]
[TestCase("http://localhost:9117/api/v2.0/indexers/all/results/torznab", "api")]
[TestCase("http://localhost:9117/", "/torznab/all/api")]
[TestCase("http://localhost:9117/", "/api/v2.0/indexers/all/results/torznab")]
public void should_return_warning_if_any_jackett_all_indexer_exists(string baseUrl, string apiPath)
{
GivenIndexer(baseUrl, apiPath);
Subject.Check().ShouldBeWarning();
}
}
}

View File

@@ -176,7 +176,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_ok_on_movie_imported_event()
{
GivenFolderExists(_downloadRootPath);
var importEvent = new MovieImportedEvent(new LocalMovie(), new MovieFile(), new List<MovieFile>(), true, new DownloadClientItem());
var importEvent = new MovieImportedEvent(new LocalMovie(), new MovieFile(), true, new DownloadClientItem(), _downloadItem.DownloadId);
Subject.Check(importEvent).ShouldBeOk();
}

View File

@@ -19,10 +19,6 @@ namespace NzbDrone.Core.Test.HealthCheck
Mocker.SetConstant<IEnumerable<IProvideHealthCheck>>(new[] { _healthCheck });
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.GetMock<IServerSideNotificationService>()
.Setup(v => v.GetServerChecks())
.Returns(new List<Core.HealthCheck.HealthCheck>());
}
[Test]

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.HistoryTests
DownloadId = "abcd"
};
Subject.Handle(new MovieImportedEvent(localMovie, movieFile, new List<MovieFile>(), true, downloadClientItem));
Subject.Handle(new MovieImportedEvent(localMovie, movieFile, true, downloadClientItem, "abcd"));
Mocker.GetMock<IHistoryRepository>()
.Verify(v => v.Insert(It.Is<MovieHistory>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localMovie.Path))));

View File

@@ -204,48 +204,5 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
pageTier2.Url.Query.Should().NotContain("imdbid=0076759");
pageTier2.Url.Query.Should().Contain("q=");
}
[Test]
public void should_encode_raw_title()
{
_capabilities.SupportedMovieSearchParameters = new[] { "q" };
_capabilities.TextSearchEngine = "raw";
MovieSearchCriteria movieRawSearchCriteria = new MovieSearchCriteria
{
Movie = new Movies.Movie { Title = "Some Movie & Title: Words", Year = 2021, TmdbId = 123 },
SceneTitles = new List<string> { "Some Movie & Title: Words" }
};
var results = Subject.GetSearchRequests(movieRawSearchCriteria);
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("q=Some%20Movie%20%26%20Title%3A%20Words");
page.Url.Query.Should().NotContain(" & ");
page.Url.Query.Should().Contain("%26");
}
[Test]
public void should_use_clean_title_and_encode()
{
_capabilities.SupportedMovieSearchParameters = new[] { "q" };
_capabilities.TextSearchEngine = "sphinx";
MovieSearchCriteria movieRawSearchCriteria = new MovieSearchCriteria
{
Movie = new Movies.Movie { Title = "Some Movie & Title: Words", Year = 2021, TmdbId = 123 },
SceneTitles = new List<string> { "Some Movie & Title: Words" }
};
var results = Subject.GetSearchRequests(movieRawSearchCriteria);
var page = results.GetTier(0).First().First();
page.Url.Query.Should().Contain("q=Some%20Movie%20and%20Title%20Words%202021");
page.Url.Query.Should().Contain("and");
page.Url.Query.Should().NotContain(" & ");
page.Url.Query.Should().NotContain("%26");
}
}
}

View File

@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using FizzWare.NBuilder;
using FluentAssertions;
using FluentValidation.Results;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
@@ -13,7 +10,6 @@ using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Indexers.Torznab;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
{
@@ -35,11 +31,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
}
};
_caps = new NewznabCapabilities
{
Categories = Builder<NewznabCategory>.CreateListOfSize(1).All().With(t => t.Id = 1).Build().ToList()
};
_caps = new NewznabCapabilities();
Mocker.GetMock<INewznabCapabilitiesProvider>()
.Setup(v => v.GetCapabilities(It.IsAny<NewznabSettings>()))
.Returns(_caps);
@@ -142,50 +134,5 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
Subject.PageSize.Should().Be(25);
}
[TestCase("http://localhost:9117/", "/api")]
public void url_and_api_not_jackett_all(string baseUrl, string apiPath)
{
var setting = new TorznabSettings()
{
BaseUrl = baseUrl,
ApiPath = apiPath
};
setting.Validate().IsValid.Should().BeTrue();
}
[TestCase("http://localhost:9117/torznab/all/api")]
[TestCase("http://localhost:9117/api/v2.0/indexers/all/results/torznab")]
public void jackett_all_url_should_not_validate(string baseUrl)
{
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
(Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl;
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
result.HasWarnings.Should().BeTrue();
}
[TestCase("/torznab/all/api")]
[TestCase("/api/v2.0/indexers/all/results/torznab")]
public void jackett_all_api_should_not_validate(string apiPath)
{
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
(Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath;
var result = new NzbDroneValidationResult(Subject.Test());
result.IsValid.Should().BeTrue();
result.HasWarnings.Should().BeTrue();
}
}
}

View File

@@ -43,10 +43,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { 28, Language.Thai },
new object[] { 29, Language.Bulgarian },
new object[] { 30, Language.PortugueseBR },
new object[] { 31, Language.Arabic },
new object[] { 32, Language.Ukrainian },
new object[] { 33, Language.Persian },
new object[] { 34, Language.Bengali },
new object[] { 31, Language.Arabic }
};
public static object[] ToIntCases =
@@ -84,10 +81,7 @@ namespace NzbDrone.Core.Test.Languages
new object[] { Language.Thai, 28 },
new object[] { Language.Bulgarian, 29 },
new object[] { Language.PortugueseBR, 30 },
new object[] { Language.Arabic, 31 },
new object[] { Language.Ukrainian, 32 },
new object[] { Language.Persian, 33 },
new object[] { Language.Bengali, 34 },
new object[] { Language.Arabic, 31 }
};
[Test]

View File

@@ -20,9 +20,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
[TestCase("wmv2, WMV2", "Droned.wmv", "WMV")]
[TestCase("mpeg4, XVID", "", "XviD")]
[TestCase("mpeg4, DIV3", "spsm.dvdrip.divx.avi'.", "DivX")]
[TestCase("msmpeg4, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
[TestCase("msmpeg4v2, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
[TestCase("msmpeg4v3, DIV3", "Exit the Dragon, Enter the Tiger (1976) 360p MPEG Audio.avi", "DivX")]
[TestCase("vp6, 4", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
[TestCase("vp7, VP70", "Sweet Seymour.avi", "VP7")]
[TestCase("vp8, V_VP8", "Dick.mkv", "VP8")]

View File

@@ -1,31 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
{
[TestFixture]
public class FormatVideoDynamicRangeTypeFixture : TestBase
{
[TestCase(HdrFormat.None, "")]
[TestCase(HdrFormat.Hlg10, "HLG")]
[TestCase(HdrFormat.Pq10, "PQ")]
[TestCase(HdrFormat.Hdr10, "HDR10")]
[TestCase(HdrFormat.Hdr10Plus, "HDR10Plus")]
[TestCase(HdrFormat.DolbyVision, "DV")]
[TestCase(HdrFormat.DolbyVisionHdr10, "DV HDR10")]
[TestCase(HdrFormat.DolbyVisionHlg, "DV HLG")]
[TestCase(HdrFormat.DolbyVisionSdr, "DV SDR")]
public void should_format_video_dynamic_range_type(HdrFormat format, string expectedVideoDynamicRangeType)
{
var mediaInfo = new MediaInfoModel
{
VideoHdrFormat = format,
SchemaRevision = 9
};
MediaInfoFormatter.FormatVideoDynamicRangeType(mediaInfo).Should().Be(expectedVideoDynamicRangeType);
}
}
}

View File

@@ -102,38 +102,24 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
info.VideoTransferCharacteristics.Should().Be("bt709");
}
[TestCase(8, "", "", "", null, HdrFormat.None)]
[TestCase(10, "", "", "", null, HdrFormat.None)]
[TestCase(10, "bt709", "bt709", "", null, HdrFormat.None)]
[TestCase(8, "bt2020", "smpte2084", "", null, HdrFormat.None)]
[TestCase(10, "bt2020", "bt2020-10", "", null, HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "arib-std-b67", "", null, HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "smpte2084", "", null, HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", null, HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", null, HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", null, HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", null, HdrFormat.Hdr10Plus)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", null, HdrFormat.DolbyVision)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 1, HdrFormat.DolbyVisionHdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 2, HdrFormat.DolbyVisionSdr)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", 4, HdrFormat.DolbyVisionHlg)]
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, int? doviConfigId, HdrFormat expected)
[TestCase(8, "", "", "", HdrFormat.None)]
[TestCase(10, "", "", "", HdrFormat.None)]
[TestCase(10, "bt709", "bt709", "", HdrFormat.None)]
[TestCase(8, "bt2020", "smpte2084", "", HdrFormat.None)]
[TestCase(10, "bt2020", "bt2020-10", "", HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "arib-std-b67", "", HdrFormat.Hlg10)]
[TestCase(10, "bt2020", "smpte2084", "", HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.SideData", HdrFormat.Pq10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.MasteringDisplayMetadata", HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.ContentLightLevelMetadata", HdrFormat.Hdr10)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.HdrDynamicMetadataSpmte2094", HdrFormat.Hdr10Plus)]
[TestCase(10, "bt2020", "smpte2084", "FFMpegCore.DoviConfigurationRecordSideData", HdrFormat.DolbyVision)]
public void should_detect_hdr_correctly(int bitDepth, string colourPrimaries, string transferFunction, string sideDataTypes, HdrFormat expected)
{
var assembly = Assembly.GetAssembly(typeof(FFProbe));
var types = sideDataTypes.Split(",").Select(x => x.Trim()).ToList();
var sideData = types.Where(x => x.IsNotNullOrWhiteSpace()).Select(x => assembly.CreateInstance(x)).Cast<SideData>().ToList();
if (doviConfigId.HasValue)
{
sideData.ForEach(x =>
{
if (x.GetType().Name == "DoviConfigurationRecordSideData")
{
((DoviConfigurationRecordSideData)x).DvBlSignalCompatibilityId = doviConfigId.Value;
}
});
}
var result = VideoFileInfoReader.GetHdrFormat(bitDepth, colourPrimaries, transferFunction, sideData);
result.Should().Be(expected);

View File

@@ -78,11 +78,6 @@ namespace NzbDrone.Core.Test.NotificationTests
{
TestLogger.Info("OnHealthIssue was called");
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
TestLogger.Info("OnApplicationUpdate was called");
}
}
private class TestNotificationWithNoEvents : NotificationBase<TestSetting>
@@ -121,7 +116,6 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnMovieFileDelete.Should().BeTrue();
notification.SupportsOnMovieFileDeleteForUpgrade.Should().BeTrue();
notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnApplicationUpdate.Should().BeTrue();
}
[Test]
@@ -137,7 +131,6 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnMovieFileDelete.Should().BeFalse();
notification.SupportsOnMovieFileDeleteForUpgrade.Should().BeFalse();
notification.SupportsOnHealthIssue.Should().BeFalse();
notification.SupportsOnApplicationUpdate.Should().BeFalse();
}
}
}

View File

@@ -51,6 +51,15 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().Contain(Language.English);
}
[TestCase("Movie.Title.1982.Ger.Eng.AC3.DL.BDRip.x264-iNCEPTiON")]
public void should_parse_language_english_german(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
result.Languages.Should().Contain(Language.German);
result.Languages.Should().Contain(Language.English);
}
[TestCase("Movie.Title.1994.Spanish.1080p.XviD-LOL")]
[TestCase("Movie Title (2020)[BDRemux AVC 1080p][E-AC3 DD Plus 5.1 Castellano-Inglés Subs]")]
[TestCase("Movie Title (2020) [UHDRemux2160p HDR][DTS-HD MA 5.1 AC3 5.1 Castellano - True-HD 7.1 Atmos Inglés Subs]")]
@@ -63,7 +72,6 @@ namespace NzbDrone.Core.Test.ParserTests
}
[TestCase("Movie.Title.1994.German.1080p.XviD-LOL")]
[TestCase("Movie.Title.2016.Ger.Dub.AAC.1080p.WebDL.x264-TKP21")]
public void should_parse_language_german(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
@@ -153,7 +161,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.1994.Bulgarian.1080p.XviD-LOL")]
[TestCase("Movie.Title.1994.BGAUDIO.1080p.XviD-LOL")]
[TestCase("Movie.Title.1994.BG.AUDIO.1080p.XviD-LOL")]
public void should_parse_language_bulgarian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
@@ -307,40 +314,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Arabic);
}
[TestCase("Movie.Title [1989, BDRip] MVO + DVO + UKR (MVO) + Sub")]
[TestCase("Movie.Title (2006) BDRemux 1080p 2xUkr | Sub Ukr")]
[TestCase("Movie.Title [1984, BDRip 720p] MVO + MVO + Dub + AVO + 3xUkr")]
[TestCase("Movie.Title.2019.UKRAINIAN.WEBRip.x264-VXT")]
public void should_parse_language_ukrainian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
result.Languages.Should().BeEquivalentTo(Language.Ukrainian);
}
[TestCase("Movie.Title [1937, BDRip 1080p] Dub UKR/Eng + Sub rus")]
[TestCase("Movie.Title.[2003.BDRemux.1080p].Dub.MVO.(2xUkr/Fra).Sub.(Rus/Fra)")]
public void should_parse_language_ukrainian_multi(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
result.Languages.Should().Contain(Language.Ukrainian);
}
[TestCase("Movie.Title.2019.PERSIAN.WEBRip.x264-VXT")]
public void should_parse_language_persian(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Languages.Should().BeEquivalentTo(Language.Persian);
}
[TestCase("Movie.Title.2019.BENGALI.WEBRip.x264-VXT")]
public void should_parse_language_bengali(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Languages.Should().BeEquivalentTo(Language.Bengali);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -23,7 +23,6 @@ namespace NzbDrone.Core.Test.ParserTests
* Superman.-.The.Man.of.Steel.1994-06.34.hybrid.DreamGirl-Novus-HD
* Superman.-.The.Man.of.Steel.1994-05.33.hybrid.DreamGirl-Novus-HD
* Constantine S1-E1-WEB-DL-1080p-NZBgeek
* [TestCase("Valana la Movie FRENCH BluRay 720p 2016 kjhlj", "Valana la Movie")] Removed 2021-12-19 as this / the regex for this was breaking all movies w/ french in title
*/
[Test]
@@ -50,6 +49,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.The.Final.Chapter.2016", "Movie The Final Chapter")]
[TestCase("Der.Movie.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", "Der Movie James")]
[TestCase("Movie.German.DL.AC3.Dubbed..BluRay.x264-PsO", "Movie")]
[TestCase("Valana la Movie FRENCH BluRay 720p 2016 kjhlj", "Valana la Movie")]
[TestCase("Valana la Movie TRUEFRENCH BluRay 720p 2016 kjhlj", "Valana la Movie")]
[TestCase("Mission Movie: Rogue Movie (2015)<29>[XviD - Ita Ac3 - SoftSub Ita]azione, spionaggio, thriller *Prima Visione* Team mulnic Tom Cruise", "Mission Movie Rogue Movie")]
[TestCase("Movie.Movie.2000.FRENCH..BluRay.-AiRLiNE", "Movie Movie")]
@@ -61,7 +61,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("World.Movie.Z.2.EXTENDED.2013.German.DL.1080p.BluRay.AVC-XANOR", "World Movie Z 2")]
[TestCase("G.I.Movie.Movie.2013.THEATRiCAL.COMPLETE.BLURAY-GLiMMER", "G.I. Movie Movie")]
[TestCase("www.Torrenting.org - Movie.2008.720p.X264-DIMENSION", "Movie")]
[TestCase("The.French.Movie.2013.720p.BluRay.x264 - ROUGH[PublicHD]", "The French Movie")]
public void should_parse_movie_title(string postTitle, string title)
{
Parser.Parser.ParseMovieTitle(postTitle).PrimaryMovieTitle.Should().Be(title);
@@ -200,7 +199,6 @@ namespace NzbDrone.Core.Test.ParserTests
}
[TestCase("The.Italian.Movie.2025.720p.BluRay.X264-AMIABLE")]
[TestCase("The.French.Movie.2013.720p.BluRay.x264 - ROUGH[PublicHD]")]
public void should_not_parse_wrong_language_in_title(string postTitle)
{
var parsed = Parser.Parser.ParseMovieTitle(postTitle, true);

View File

@@ -119,7 +119,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Name.S01E08.Tourmaline.Nepal.720p.HDTV.x264-DHD", false)]
[TestCase("Movie.Name.US.S12E17.HR.WS.PDTV.X264-DIMENSION", false)]
[TestCase("Movie.Name.The.Lost.Pilots.Movie.HR.WS.PDTV.x264-DHD", false)]
[TestCase("Movie.Name.The.Lost.Pilots.Movie.HR.WS.PDTV.x264-DHD-Remux.mkv", false)]
public void should_parse_hdtv720p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.TV, proper, Resolution.R720p);
@@ -187,7 +186,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[HorribleSubs] Movie Title! 2018 [Web][MKV][h264][1080p][AAC 2.0][Softsubs (HorribleSubs)]", false)]
[TestCase("Movie.Title.2020.MULTi.1080p.WEB.H264-ALLDAYiN (S:285/L:11)", false)]
[TestCase("Movie Title (2020) MULTi WEB 1080p x264-JiHEFF (S:317/L:28)", false)]
[TestCase("Movie.Titles.2020.1080p.NF.WEB.DD2.0.x264-SNEAkY", false)]
public void should_parse_webdl1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R1080p);
@@ -258,8 +256,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie Name 2005 1080p UHD BluRay DD+7.1 x264-LoRD.mkv", false)]
[TestCase("Movie.Name.2011.1080p.UHD.BluRay.DD5.1.HDR.x265-CtrlHD.mkv", false)]
[TestCase("Movie.Name.2016.German.DTS.DL.1080p.UHDBD.x265-TDO.mkv", false)]
[TestCase("Movie.Name.2021.1080p.BDLight.x265-AVCDVD", false)]
[TestCase("Random.Title.2010.1080p.HD.DVD.AVC.DDP.5.1-GRouP", false)]
public void should_parse_bluray1080p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.BLURAY, proper, Resolution.R1080p);
@@ -276,8 +272,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2019.2160p.MBLURAY.x264-MBLURAYFANS.mkv", false)]
[TestCase("Movie.Title.2017.2160p.MBluRay.x264-TREBLE.mkv", false)]
[TestCase("Movie.Name.2020.German.UHDBD.2160p.HDR10.HEVC.EAC3.DL-pmHD.mkv", false)]
[TestCase("Movie.Title.2014.2160p.UHD.BluRay.X265-IAMABLE.mkv", false)]
[TestCase("Movie.Title.2014.2160p.BDRip.AAC.7.1.HDR10.x265.10bit-Markll", false)]
public void should_parse_bluray2160p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Source.BLURAY, proper, Resolution.R2160p);
@@ -431,8 +425,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "KORSUBS")]
[TestCase("Movie Title 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
[TestCase("Movie.Title.2017.720p.SUBBED.HDRip.V2.XViD-26k.avi", "Generic Hardcoded Subs")]
[TestCase("Movie Title! 2018 [Web][MKV][h264][480p][AAC 2.0][Softsubs]", null)]
[TestCase("Movie Title! 2019 [HorribleSubs][Web][MKV][h264][848x480][AAC 2.0][Softsubs(HorribleSubs)]", null)]
public void should_parse_hardcoded_subs(string postTitle, string sub)
{
QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub);

View File

@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Test.UpdateTests
[Test]
public void finds_update_when_version_lower()
{
NotBsd();
UseRealHttp();
Subject.GetLatestUpdate("develop", new Version(3, 0)).Should().NotBeNull();
}
@@ -42,6 +43,8 @@ namespace NzbDrone.Core.Test.UpdateTests
[Test]
public void should_get_recent_updates()
{
NotBsd();
const string branch = "nightly";
UseRealHttp();
var recent = Subject.GetRecentUpdates(branch, new Version(3, 0), null);

View File

@@ -17,7 +17,6 @@ namespace NzbDrone.Core.Datastore
IEnumerable<TModel> All();
int Count();
TModel Get(int id);
TModel Find(int id);
TModel Insert(TModel model);
TModel Update(TModel model);
TModel Upsert(TModel model);
@@ -100,13 +99,6 @@ namespace NzbDrone.Core.Datastore
return model;
}
public TModel Find(int id)
{
var model = Query(c => c.Id == id).SingleOrDefault();
return model;
}
public IEnumerable<TModel> Get(IEnumerable<int> ids)
{
if (!ids.Any())

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