New: Option to opt out of TBA episode title import delays

Closes #3086
pull/3105/head
Mark McDowall 6 years ago
parent e70d92f670
commit 9b617af713

@ -13,6 +13,12 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
import NamingConnector from './Naming/NamingConnector';
const episodeTitleRequiredOptions = [
{ key: 'always', value: 'Always' },
{ key: 'bulkSeasonReleases', value: 'Only for Bulk Season Releases' },
{ key: 'never', value: 'Never' }
];
const rescanAfterRefreshOptions = [
{ key: 'always', value: 'Always' },
{ key: 'afterManual', value: 'After Manual Refresh' },
@ -116,6 +122,23 @@ class MediaManagement extends Component {
<FieldSet
legend="Importing"
>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.SMALL}
>
<FormLabel>Episode Title Required</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="episodeTitleRequired"
helpText="Prevent importing for up to 24 hours if the episode title is in the naming format and the episode title is TBA"
values={episodeTitleRequiredOptions}
onChange={onInputChange}
{...settings.episodeTitleRequired}
/>
</FormGroup>
{
isMono &&
<FormGroup

@ -2,7 +2,10 @@ using System;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -81,5 +84,66 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_accept_when_episode_title_is_never_required()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.EpisodeTitleRequired)
.Returns(EpisodeTitleRequiredType.Never);
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_accept_if_episode_title_is_required_for_bulk_season_releases_and_not_bulk_season()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.EpisodeTitleRequired)
.Returns(EpisodeTitleRequiredType.BulkSeasonReleases);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), It.IsAny<int>()))
.Returns(Builder<Episode>.CreateListOfSize(5).BuildList());
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_accept_if_episode_title_is_required_for_bulk_season_releases()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.EpisodeTitleRequired)
.Returns(EpisodeTitleRequiredType.BulkSeasonReleases);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), It.IsAny<int>()))
.Returns(Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.AirDateUtc == _localEpisode.Episodes.First().AirDateUtc)
.BuildList());
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_reject_if_episode_title_is_required_for_bulk_season_releases_and_it_is_mising()
{
_localEpisode.Episodes.First().Title = "TBA";
Mocker.GetMock<IConfigService>()
.Setup(s => s.EpisodeTitleRequired)
.Returns(EpisodeTitleRequiredType.BulkSeasonReleases);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodesBySeason(It.IsAny<int>(), It.IsAny<int>()))
.Returns(Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.AirDateUtc = _localEpisode.Episodes.First().AirDateUtc)
.BuildList());
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeFalse();
}
}
}

@ -8,6 +8,7 @@ using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Common.Http.Proxy;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Security;
namespace NzbDrone.Core.Configuration
@ -223,6 +224,13 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RescanAfterRefresh", value); }
}
public EpisodeTitleRequiredType EpisodeTitleRequired
{
get { return GetValueEnum("EpisodeTitleRequired", EpisodeTitleRequiredType.Always); }
set { SetValue("EpisodeTitleRequired", value); }
}
public bool SetPermissionsLinux
{
get { return GetValueBoolean("SetPermissionsLinux", false); }

@ -1,6 +1,7 @@
using System.Collections.Generic;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Common.Http.Proxy;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Security;
namespace NzbDrone.Core.Configuration
@ -35,6 +36,8 @@ namespace NzbDrone.Core.Configuration
bool ImportExtraFiles { get; set; }
string ExtraFileExtensions { get; set; }
RescanAfterRefreshType RescanAfterRefresh { get; set; }
EpisodeTitleRequiredType EpisodeTitleRequired { get; set; }
//Permissions (Media Management)
bool SetPermissionsLinux { get; set; }

@ -0,0 +1,9 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
public enum EpisodeTitleRequiredType
{
Always = 0,
BulkSeasonReleases = 1,
Never = 2
}
}

@ -1,32 +1,69 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
public class EpisodeTitleSpecification : IImportDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly IBuildFileNames _buildFileNames;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public EpisodeTitleSpecification(IBuildFileNames buildFileNames, Logger logger)
public EpisodeTitleSpecification(IConfigService configService,
IBuildFileNames buildFileNames,
IEpisodeService episodeService,
Logger logger)
{
_configService = configService;
_buildFileNames = buildFileNames;
_episodeService = episodeService;
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
var episodeTitleRequired = _configService.EpisodeTitleRequired;
if (episodeTitleRequired == EpisodeTitleRequiredType.Never)
{
_logger.Debug("Episode titles are never required, skipping check");
return Decision.Accept();
}
if (!_buildFileNames.RequiresEpisodeTitle(localEpisode.Series, localEpisode.Episodes))
{
_logger.Debug("File name format does not require episode title, skipping check");
return Decision.Accept();
}
foreach (var episode in localEpisode.Episodes)
var episodes = localEpisode.Episodes;
var firstEpisode = episodes.First();
var episodesInSeason = _episodeService.GetEpisodesBySeason(firstEpisode.SeriesId, firstEpisode.EpisodeNumber);
var allEpisodesOnTheSameDay = firstEpisode.AirDateUtc.HasValue && episodes.All(e =>
e.AirDateUtc.HasValue &&
e.AirDateUtc.Value == firstEpisode.AirDateUtc.Value);
if (episodeTitleRequired == EpisodeTitleRequiredType.BulkSeasonReleases &&
allEpisodesOnTheSameDay &&
episodesInSeason.Count(e => e.AirDateUtc.HasValue &&
e.AirDateUtc.Value == firstEpisode.AirDateUtc.Value
) < 4
)
{
_logger.Debug("Episode title only required for bulk season releases");
return Decision.Accept();
}
foreach (var episode in episodes)
{
var airDateUtc = episode.AirDateUtc;
var title = episode.Title;

@ -783,6 +783,7 @@
<Compile Include="Languages\LanguageComparer.cs" />
<Compile Include="Languages\LanguagesBelowCutoff.cs" />
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguage.cs" />
<Compile Include="MediaFiles\EpisodeImport\EpisodeTitleRequiredType.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\AbsoluteEpisodeNumberSpecification.cs" />
<Compile Include="Notifications\Discord\Discord.cs" />
<Compile Include="Notifications\Discord\DiscordColors.cs" />

@ -1,5 +1,6 @@
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using Sonarr.Http.REST;
namespace Sonarr.Api.V3.Config
@ -20,6 +21,7 @@ namespace Sonarr.Api.V3.Config
public string ChownUser { get; set; }
public string ChownGroup { get; set; }
public EpisodeTitleRequiredType EpisodeTitleRequired { get; set; }
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
public bool CopyUsingHardlinks { get; set; }
public bool ImportExtraFiles { get; set; }
@ -47,6 +49,7 @@ namespace Sonarr.Api.V3.Config
ChownUser = model.ChownUser,
ChownGroup = model.ChownGroup,
EpisodeTitleRequired = model.EpisodeTitleRequired,
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
CopyUsingHardlinks = model.CopyUsingHardlinks,
ImportExtraFiles = model.ImportExtraFiles,

Loading…
Cancel
Save