parent
c76364a891
commit
9350f6a04c
@ -0,0 +1,37 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import SelectLanguageModalContentConnector from './SelectLanguageModalContentConnector';
|
||||||
|
|
||||||
|
class SelectLanguageModal extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<SelectLanguageModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectLanguageModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectLanguageModal;
|
@ -0,0 +1,87 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { inputTypes } from 'Helpers/Props';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
|
||||||
|
function SelectLanguageModalContent(props) {
|
||||||
|
const {
|
||||||
|
languageId,
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
onModalClose,
|
||||||
|
onLanguageSelect
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const languageOptions = items.map(( language ) => {
|
||||||
|
return {
|
||||||
|
key: language.id,
|
||||||
|
value: language.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
Manual Import - Select Language
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div>Unable to load languages</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error &&
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Language</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="language"
|
||||||
|
value={languageId}
|
||||||
|
values={languageOptions}
|
||||||
|
onChange={onLanguageSelect}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectLanguageModalContent.propTypes = {
|
||||||
|
languageId: PropTypes.number.isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onLanguageSelect: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectLanguageModalContent;
|
@ -0,0 +1,88 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { fetchLanguages } from 'Store/Actions/settingsActions';
|
||||||
|
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||||
|
import SelectLanguageModalContent from './SelectLanguageModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.languages,
|
||||||
|
(languages) => {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
} = languages;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchLanguages: fetchLanguages,
|
||||||
|
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectLanguageModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
if (!this.props.isPopulated) {
|
||||||
|
this.props.dispatchFetchLanguages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onLanguageSelect = ({ value }) => {
|
||||||
|
const languageId = parseInt(value);
|
||||||
|
|
||||||
|
const language = _.find(this.props.items,
|
||||||
|
(item) => item.id === languageId);
|
||||||
|
|
||||||
|
this.props.dispatchUpdateInteractiveImportItems({
|
||||||
|
ids: this.props.ids,
|
||||||
|
language
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onModalClose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectLanguageModalContent
|
||||||
|
{...this.props}
|
||||||
|
onLanguageSelect={this.onLanguageSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectLanguageModalContentConnector.propTypes = {
|
||||||
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||||
|
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(SelectLanguageModalContentConnector);
|
@ -1,4 +1,4 @@
|
|||||||
.series {
|
.movie {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid $borderColor;
|
||||||
}
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import { createThunk } from 'Store/thunks';
|
||||||
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
const section = 'settings.languages';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchLanguages = createThunk(FETCH_LANGUAGES);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Details
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
defaultState: {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
actionHandlers: {
|
||||||
|
[FETCH_LANGUAGES]: createFetchHandler(section, '/language')
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
reducers: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(155)]
|
||||||
|
public class add_update_allowed_quality_profile : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Profiles").AddColumn("UpgradeAllowed").AsInt32().Nullable();
|
||||||
|
|
||||||
|
// Set upgrade allowed for existing profiles (default will be false for new profiles)
|
||||||
|
Update.Table("Profiles").Set(new { UpgradeAllowed = true }).AllRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
{
|
||||||
|
public class AlreadyImportedSpecification : IDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly IHistoryService _historyService;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public AlreadyImportedSpecification(IHistoryService historyService,
|
||||||
|
IConfigService configService,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_historyService = historyService;
|
||||||
|
_configService = configService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||||
|
public RejectionType Type => RejectionType.Permanent;
|
||||||
|
|
||||||
|
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||||
|
{
|
||||||
|
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||||
|
|
||||||
|
if (!cdhEnabled)
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping already imported check because CDH is disabled");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var movie = subject.Movie;
|
||||||
|
|
||||||
|
_logger.Debug("Performing already imported check on report");
|
||||||
|
if (movie != null)
|
||||||
|
{
|
||||||
|
if (!movie.HasFile)
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping already imported check for episode without file");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var historyForEpisode = _historyService.GetByMovieId(movie.Id, null);
|
||||||
|
var lastGrabbed = historyForEpisode.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
if (lastGrabbed == null)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported = historyForEpisode.FirstOrDefault(h =>
|
||||||
|
h.EventType == HistoryEventType.DownloadFolderImported &&
|
||||||
|
h.DownloadId == lastGrabbed.DownloadId);
|
||||||
|
|
||||||
|
if (imported == null)
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is really only a guard against redownloading the same release over
|
||||||
|
// and over when the grabbed and imported qualities do not match, if they do
|
||||||
|
// match skip this check.
|
||||||
|
if (lastGrabbed.Quality.Equals(imported.Quality))
|
||||||
|
{
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
var release = subject.Release;
|
||||||
|
|
||||||
|
if (release.DownloadProtocol == DownloadProtocol.Torrent)
|
||||||
|
{
|
||||||
|
var torrentInfo = release as TorrentInfo;
|
||||||
|
|
||||||
|
if (torrentInfo?.InfoHash != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId)
|
||||||
|
{
|
||||||
|
_logger.Debug("Has same torrent hash as a grabbed and imported release");
|
||||||
|
return Decision.Reject("Has same torrent hash as a grabbed and imported release");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only based on title because a release with the same title on another indexer/released at
|
||||||
|
// a different time very likely has the exact same content and we don't need to also try it.
|
||||||
|
|
||||||
|
if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.Debug("Has same release name as a grabbed and imported release");
|
||||||
|
return Decision.Reject("Has same release name as a grabbed and imported release");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using NzbDrone.Core.Movies;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
|
||||||
{
|
|
||||||
public class AnimeVersionUpgradeSpecification : IDecisionEngineSpecification
|
|
||||||
{
|
|
||||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public AnimeVersionUpgradeSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger)
|
|
||||||
{
|
|
||||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RejectionType Type => RejectionType.Permanent;
|
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
{
|
||||||
|
public class BlockedIndexerSpecification : IDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly IIndexerStatusService _indexerStatusService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
private readonly ICachedDictionary<IndexerStatus> _blockedIndexerCache;
|
||||||
|
|
||||||
|
public BlockedIndexerSpecification(IIndexerStatusService indexerStatusService, ICacheManager cacheManager, Logger logger)
|
||||||
|
{
|
||||||
|
_indexerStatusService = indexerStatusService;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_blockedIndexerCache = cacheManager.GetCacheDictionary(GetType(), "blocked", FetchBlockedIndexer, TimeSpan.FromSeconds(15));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||||
|
public RejectionType Type => RejectionType.Temporary;
|
||||||
|
|
||||||
|
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||||
|
{
|
||||||
|
var status = _blockedIndexerCache.Find(subject.Release.IndexerId.ToString());
|
||||||
|
if (status != null)
|
||||||
|
{
|
||||||
|
return Decision.Reject($"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, IndexerStatus> FetchBlockedIndexer()
|
||||||
|
{
|
||||||
|
return _indexerStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
{
|
{
|
||||||
public interface IDecisionEngineSpecification
|
public interface IDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
RejectionType Type { get; }
|
RejectionType Type { get; }
|
||||||
|
|
||||||
|
SpecificationPriority Priority { get; }
|
||||||
|
|
||||||
Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria);
|
Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,25 +0,0 @@
|
|||||||
using System;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using NzbDrone.Core.Movies;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
|
||||||
{
|
|
||||||
public class DailyEpisodeMatchSpecification : IDecisionEngineSpecification
|
|
||||||
{
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public DailyEpisodeMatchSpecification(Logger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RejectionType Type => RejectionType.Permanent;
|
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,51 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
{
|
||||||
|
public class UpgradeAllowedSpecification : IDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly UpgradableSpecification _upgradableSpecification;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification, Logger logger)
|
||||||
|
{
|
||||||
|
_upgradableSpecification = upgradableSpecification;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||||
|
public RejectionType Type => RejectionType.Permanent;
|
||||||
|
|
||||||
|
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||||
|
{
|
||||||
|
var qualityProfile = subject.Movie.Profile.Value;
|
||||||
|
|
||||||
|
if (subject.Movie.MovieFileId != 0)
|
||||||
|
{
|
||||||
|
var file = subject.Movie.MovieFile;
|
||||||
|
|
||||||
|
if (file == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("File is no longer available, skipping this file.");
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality);
|
||||||
|
|
||||||
|
if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile,
|
||||||
|
file.Quality,
|
||||||
|
subject.ParsedMovieInfo.Quality))
|
||||||
|
{
|
||||||
|
_logger.Debug("Upgrading is not allowed by the quality profile");
|
||||||
|
|
||||||
|
return Decision.Reject("Existing file and the Quality profile does not allow upgrades");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.MediaFiles.MovieImport.Manual;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using Radarr.Http;
|
||||||
|
using Radarr.Http.Extensions;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V2.ManualImport
|
||||||
|
{
|
||||||
|
public class ManualImportModule : RadarrRestModule<ManualImportResource>
|
||||||
|
{
|
||||||
|
private readonly IManualImportService _manualImportService;
|
||||||
|
|
||||||
|
public ManualImportModule(IManualImportService manualImportService)
|
||||||
|
: base("/manualimport")
|
||||||
|
{
|
||||||
|
_manualImportService = manualImportService;
|
||||||
|
|
||||||
|
GetResourceAll = GetMediaFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ManualImportResource> GetMediaFiles()
|
||||||
|
{
|
||||||
|
var folder = (string)Request.Query.folder;
|
||||||
|
var downloadId = (string)Request.Query.downloadId;
|
||||||
|
var filterExistingFiles = Request.GetBooleanQueryParameter("filterExistingFiles", true);
|
||||||
|
|
||||||
|
return _manualImportService.GetMediaFiles(folder, downloadId, filterExistingFiles).ToResource().Select(AddQualityWeight).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ManualImportResource AddQualityWeight(ManualImportResource item)
|
||||||
|
{
|
||||||
|
if (item.Quality != null)
|
||||||
|
{
|
||||||
|
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
|
||||||
|
item.QualityWeight += item.Quality.Revision.Real * 10;
|
||||||
|
item.QualityWeight += item.Quality.Revision.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Common.Crypto;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
|
using NzbDrone.Core.MediaFiles.MovieImport.Manual;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using Radarr.Api.V2.Movies;
|
||||||
|
using Radarr.Http.REST;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V2.ManualImport
|
||||||
|
{
|
||||||
|
public class ManualImportResource : RestResource
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public string RelativePath { get; set; }
|
||||||
|
public string FolderName { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public MovieResource Movie { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public List<Language> Languages { get; set; }
|
||||||
|
public int QualityWeight { get; set; }
|
||||||
|
public string DownloadId { get; set; }
|
||||||
|
public IEnumerable<Rejection> Rejections { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ManualImportResourceMapper
|
||||||
|
{
|
||||||
|
public static ManualImportResource ToResource(this ManualImportItem model)
|
||||||
|
{
|
||||||
|
if (model == null) return null;
|
||||||
|
|
||||||
|
return new ManualImportResource
|
||||||
|
{
|
||||||
|
Id = HashConverter.GetHashInt31(model.Path),
|
||||||
|
Path = model.Path,
|
||||||
|
RelativePath = model.RelativePath,
|
||||||
|
// FolderName = model.FolderName,
|
||||||
|
Name = model.Name,
|
||||||
|
Size = model.Size,
|
||||||
|
Movie = model.Movie.ToResource(),
|
||||||
|
Quality = model.Quality,
|
||||||
|
Languages = model.Languages,
|
||||||
|
//QualityWeight
|
||||||
|
DownloadId = model.DownloadId,
|
||||||
|
Rejections = model.Rejections
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ManualImportResource> ToResource(this IEnumerable<ManualImportItem> models)
|
||||||
|
{
|
||||||
|
return models.Select(ToResource).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue