diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
index 1c6985c99..47077c163 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
@@ -25,6 +25,7 @@ import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
+import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css';
@@ -51,6 +52,11 @@ const columns = [
label: 'Episode(s)',
isVisible: true
},
+ {
+ name: 'releaseGroup',
+ label: 'Release Group',
+ isVisible: true
+ },
{
name: 'quality',
label: 'Quality',
@@ -94,8 +100,9 @@ const SELECT = 'select';
const SERIES = 'series';
const SEASON = 'season';
const EPISODE = 'episode';
-const LANGUAGE = 'language';
+const RELEASE_GROUP = 'releaseGroup';
const QUALITY = 'quality';
+const LANGUAGE = 'language';
class InteractiveImportModalContent extends Component {
@@ -231,8 +238,9 @@ class InteractiveImportModalContent extends Component {
{ key: SELECT, value: 'Select...', disabled: true },
{ key: SEASON, value: 'Select Season' },
{ key: EPISODE, value: 'Select Episode(s)' },
- { key: LANGUAGE, value: 'Select Language' },
- { key: QUALITY, value: 'Select Quality' }
+ { key: QUALITY, value: 'Select Quality' },
+ { key: RELEASE_GROUP, value: 'Select Release Group' },
+ { key: LANGUAGE, value: 'Select Language' }
];
if (allowSeriesChange) {
@@ -400,6 +408,13 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose}
/>
+
+
e.id),
+ releaseGroup,
quality,
language,
downloadId: this.props.downloadId
diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js
index a9f244f80..7b3b1c0b4 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js
@@ -16,6 +16,7 @@ import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
+import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
import styles from './InteractiveImportRow.css';
@@ -32,6 +33,7 @@ class InteractiveImportRow extends Component {
isSelectSeriesModalOpen: false,
isSelectSeasonModalOpen: false,
isSelectEpisodeModalOpen: false,
+ isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false,
isSelectLanguageModalOpen: false
};
@@ -125,6 +127,10 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectEpisodeModalOpen: true });
}
+ onSelectReleaseGroupPress = () => {
+ this.setState({ isSelectReleaseGroupModalOpen: true });
+ }
+
onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true });
}
@@ -148,6 +154,11 @@ 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);
@@ -171,6 +182,7 @@ class InteractiveImportRow extends Component {
episodes,
quality,
language,
+ releaseGroup,
size,
rejections,
isReprocessing,
@@ -182,6 +194,7 @@ class InteractiveImportRow extends Component {
isSelectSeriesModalOpen,
isSelectSeasonModalOpen,
isSelectEpisodeModalOpen,
+ isSelectReleaseGroupModalOpen,
isSelectQualityModalOpen,
isSelectLanguageModalOpen
} = this.state;
@@ -202,6 +215,7 @@ class InteractiveImportRow extends Component {
const showSeriesPlaceholder = isSelected && !series;
const showSeasonNumberPlaceholder = isSelected && !!series && isNaN(seasonNumber) && !isReprocessing;
const showEpisodeNumbersPlaceholder = isSelected && Number.isInteger(seasonNumber) && !episodes.length;
+ const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !language;
@@ -246,7 +260,6 @@ class InteractiveImportRow extends Component {
/> : null
}
-
+
+ {
+ showReleaseGroupPlaceholder ?
+ :
+ releaseGroup
+ }
+
+
+
+
+
+
+ );
+ }
+}
+
+SelectReleaseGroupModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default SelectReleaseGroupModal;
diff --git a/frontend/src/InteractiveImport/ReleaseGroup/SelectReleaseGroupModalContent.js b/frontend/src/InteractiveImport/ReleaseGroup/SelectReleaseGroupModalContent.js
new file mode 100644
index 000000000..a345e2a06
--- /dev/null
+++ b/frontend/src/InteractiveImport/ReleaseGroup/SelectReleaseGroupModalContent.js
@@ -0,0 +1,98 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { inputTypes, kinds } from 'Helpers/Props';
+import Button from 'Components/Link/Button';
+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';
+
+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 (
+
+
+ Manual Import - Set Release Group
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+SelectReleaseGroupModalContent.propTypes = {
+ releaseGroup: PropTypes.string.isRequired,
+ onReleaseGroupSelect: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default SelectReleaseGroupModalContent;
diff --git a/frontend/src/InteractiveImport/ReleaseGroup/SelectReleaseGroupModalContentConnector.js b/frontend/src/InteractiveImport/ReleaseGroup/SelectReleaseGroupModalContentConnector.js
new file mode 100644
index 000000000..17277bf7f
--- /dev/null
+++ b/frontend/src/InteractiveImport/ReleaseGroup/SelectReleaseGroupModalContentConnector.js
@@ -0,0 +1,54 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { updateInteractiveImportItems, reprocessInteractiveImportItems } 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 (
+
+ );
+ }
+}
+
+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);
diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js
index f967cae84..ee6b3f058 100644
--- a/frontend/src/Store/Actions/interactiveImportActions.js
+++ b/frontend/src/Store/Actions/interactiveImportActions.js
@@ -175,6 +175,7 @@ export const actionHandlers = handleThunks({
episodeIds: (item.episodes || []).map((e) => e.id),
quality: item.quality,
language: item.language,
+ releaseGroup: item.releaseGroup,
downloadId: item.downloadId
};
});
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs
index 849627d19..557276771 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs
@@ -14,6 +14,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List EpisodeIds { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
+ public string ReleaseGroup { get; set; }
public string DownloadId { get; set; }
public bool Equals(ManualImportFile other)
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs
index d9990483d..70926e8d2 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs
@@ -18,6 +18,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List Episodes { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
+ public string ReleaseGroup { get; set; }
public string DownloadId { get; set; }
public IEnumerable Rejections { get; set; }
}
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs
index 253fcbfcc..000ebdc38 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public interface IManualImportService
{
List GetMediaFiles(string path, string downloadId, int? seriesId, bool filterExistingFiles);
- ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, QualityModel quality, Language language);
+ ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, string releaseGroup, QualityModel quality, Language language);
}
public class ManualImportService : IExecute, IManualImportService
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
return ProcessFolder(path, path, downloadId, seriesId, filterExistingFiles);
}
- public ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, QualityModel quality, Language language)
+ public ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, int? seasonNumber, List episodeIds, string releaseGroup, QualityModel quality, Language language)
{
var rootFolder = Path.GetDirectoryName(path);
var series = _seriesService.GetSeries(seriesId);
@@ -115,6 +115,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
SceneSource = SceneSource(series, rootFolder),
ExistingFile = series.Path.IsParentPath(path),
Size = _diskProvider.GetFileSize(path),
+ ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup,
Language = language == Language.Unknown ? LanguageParser.ParseLanguage(path) : language,
Quality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality
};
@@ -141,6 +142,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
SceneSource = SceneSource(series, rootFolder),
ExistingFile = series.Path.IsParentPath(path),
Size = _diskProvider.GetFileSize(path),
+ ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup,
Language = language == Language.Unknown ? LanguageParser.ParseLanguage(path) : language,
Quality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality
};
@@ -250,7 +252,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{
var localEpisode = new LocalEpisode();
localEpisode.Path = file;
+ localEpisode.ReleaseGroup = Parser.Parser.ParseReleaseGroup(file);
localEpisode.Quality = QualityParser.ParseQuality(file);
+ localEpisode.ReleaseGroup = Parser.Parser.ParseReleaseGroup(file);
localEpisode.Language = LanguageParser.ParseLanguage(file);
localEpisode.Size = _diskProvider.GetFileSize(file);
@@ -350,6 +354,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
}
}
+ item.ReleaseGroup = decision.LocalEpisode.ReleaseGroup;
item.Quality = decision.LocalEpisode.Quality;
item.Language = decision.LocalEpisode.Language;
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
@@ -382,6 +387,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
Episodes = episodes,
FileEpisodeInfo = fileEpisodeInfo,
Path = file.Path,
+ ReleaseGroup = file.ReleaseGroup,
Quality = file.Quality,
Language = file.Language,
Series = series,
@@ -405,6 +411,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
// Apply the user-chosen values.
localEpisode.Series = series;
localEpisode.Episodes = episodes;
+ localEpisode.ReleaseGroup = file.ReleaseGroup;
localEpisode.Quality = file.Quality;
localEpisode.Language = file.Language;
diff --git a/src/Sonarr.Api.V3/ManualImport/ManualImportModule.cs b/src/Sonarr.Api.V3/ManualImport/ManualImportModule.cs
index b560969af..ebcb1a30c 100644
--- a/src/Sonarr.Api.V3/ManualImport/ManualImportModule.cs
+++ b/src/Sonarr.Api.V3/ManualImport/ManualImportModule.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
+using NzbDrone.Common.Extensions;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Qualities;
@@ -39,7 +40,7 @@ namespace Sonarr.Api.V3.ManualImport
foreach (var item in items)
{
- var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.SeasonNumber, item.EpisodeIds ?? new List(), item.Quality, item.Language);
+ var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.SeasonNumber, item.EpisodeIds ?? new List(), item.ReleaseGroup, item.Quality, item.Language);
item.SeasonNumber = processedItem.SeasonNumber;
item.Episodes = processedItem.Episodes.ToResource();
@@ -56,6 +57,11 @@ namespace Sonarr.Api.V3.ManualImport
item.Quality = processedItem.Quality;
}
+ if (item.ReleaseGroup.IsNotNullOrWhiteSpace())
+ {
+ item.ReleaseGroup = processedItem.ReleaseGroup;
+ }
+
// Clear episode IDs in favour of the full episode
item.EpisodeIds = null;
}
diff --git a/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs b/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs
index 0ebe3a464..bab8f5589 100644
--- a/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs
+++ b/src/Sonarr.Api.V3/ManualImport/ManualImportReprocessResource.cs
@@ -16,6 +16,7 @@ namespace Sonarr.Api.V3.ManualImport
public List EpisodeIds { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
+ public string ReleaseGroup { get; set; }
public string DownloadId { get; set; }
public IEnumerable Rejections { get; set; }
diff --git a/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs b/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs
index 593e98cee..ea6318545 100644
--- a/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs
+++ b/src/Sonarr.Api.V3/ManualImport/ManualImportResource.cs
@@ -21,6 +21,7 @@ namespace Sonarr.Api.V3.ManualImport
public SeriesResource Series { get; set; }
public int? SeasonNumber { get; set; }
public List Episodes { get; set; }
+ public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
public int QualityWeight { get; set; }
@@ -45,6 +46,7 @@ namespace Sonarr.Api.V3.ManualImport
Series = model.Series.ToResource(),
SeasonNumber = model.SeasonNumber,
Episodes = model.Episodes.ToResource(),
+ ReleaseGroup = model.ReleaseGroup,
Quality = model.Quality,
Language = model.Language,
//QualityWeight