mirror of
https://github.com/Sonarr/Sonarr.git
synced 2025-02-02 11:34:39 +02:00
parent
e70d92f670
commit
9b617af713
@ -13,6 +13,12 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
|
|||||||
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
|
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
|
||||||
import NamingConnector from './Naming/NamingConnector';
|
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 = [
|
const rescanAfterRefreshOptions = [
|
||||||
{ key: 'always', value: 'Always' },
|
{ key: 'always', value: 'Always' },
|
||||||
{ key: 'afterManual', value: 'After Manual Refresh' },
|
{ key: 'afterManual', value: 'After Manual Refresh' },
|
||||||
@ -116,6 +122,23 @@ class MediaManagement extends Component {
|
|||||||
<FieldSet
|
<FieldSet
|
||||||
legend="Importing"
|
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 &&
|
isMono &&
|
||||||
<FormGroup
|
<FormGroup
|
||||||
|
@ -2,7 +2,10 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
@ -81,5 +84,66 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
|||||||
|
|
||||||
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
|
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.MediaFiles;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Common.Http.Proxy;
|
using NzbDrone.Common.Http.Proxy;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Security;
|
using NzbDrone.Core.Security;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Configuration
|
namespace NzbDrone.Core.Configuration
|
||||||
@ -223,6 +224,13 @@ namespace NzbDrone.Core.Configuration
|
|||||||
set { SetValue("RescanAfterRefresh", value); }
|
set { SetValue("RescanAfterRefresh", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EpisodeTitleRequiredType EpisodeTitleRequired
|
||||||
|
{
|
||||||
|
get { return GetValueEnum("EpisodeTitleRequired", EpisodeTitleRequiredType.Always); }
|
||||||
|
|
||||||
|
set { SetValue("EpisodeTitleRequired", value); }
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetPermissionsLinux
|
public bool SetPermissionsLinux
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Common.Http.Proxy;
|
using NzbDrone.Common.Http.Proxy;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Security;
|
using NzbDrone.Core.Security;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Configuration
|
namespace NzbDrone.Core.Configuration
|
||||||
@ -35,6 +36,8 @@ namespace NzbDrone.Core.Configuration
|
|||||||
bool ImportExtraFiles { get; set; }
|
bool ImportExtraFiles { get; set; }
|
||||||
string ExtraFileExtensions { get; set; }
|
string ExtraFileExtensions { get; set; }
|
||||||
RescanAfterRefreshType RescanAfterRefresh { get; set; }
|
RescanAfterRefreshType RescanAfterRefresh { get; set; }
|
||||||
|
EpisodeTitleRequiredType EpisodeTitleRequired { get; set; }
|
||||||
|
|
||||||
|
|
||||||
//Permissions (Media Management)
|
//Permissions (Media Management)
|
||||||
bool SetPermissionsLinux { get; set; }
|
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;
|
||||||
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
{
|
{
|
||||||
public class EpisodeTitleSpecification : IImportDecisionEngineSpecification
|
public class EpisodeTitleSpecification : IImportDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
private readonly IBuildFileNames _buildFileNames;
|
private readonly IBuildFileNames _buildFileNames;
|
||||||
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public EpisodeTitleSpecification(IBuildFileNames buildFileNames, Logger logger)
|
public EpisodeTitleSpecification(IConfigService configService,
|
||||||
|
IBuildFileNames buildFileNames,
|
||||||
|
IEpisodeService episodeService,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
|
_configService = configService;
|
||||||
_buildFileNames = buildFileNames;
|
_buildFileNames = buildFileNames;
|
||||||
|
_episodeService = episodeService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
|
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))
|
if (!_buildFileNames.RequiresEpisodeTitle(localEpisode.Series, localEpisode.Episodes))
|
||||||
{
|
{
|
||||||
_logger.Debug("File name format does not require episode title, skipping check");
|
_logger.Debug("File name format does not require episode title, skipping check");
|
||||||
return Decision.Accept();
|
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 airDateUtc = episode.AirDateUtc;
|
||||||
var title = episode.Title;
|
var title = episode.Title;
|
||||||
|
@ -783,6 +783,7 @@
|
|||||||
<Compile Include="Languages\LanguageComparer.cs" />
|
<Compile Include="Languages\LanguageComparer.cs" />
|
||||||
<Compile Include="Languages\LanguagesBelowCutoff.cs" />
|
<Compile Include="Languages\LanguagesBelowCutoff.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguage.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguage.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\EpisodeTitleRequiredType.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\AbsoluteEpisodeNumberSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\AbsoluteEpisodeNumberSpecification.cs" />
|
||||||
<Compile Include="Notifications\Discord\Discord.cs" />
|
<Compile Include="Notifications\Discord\Discord.cs" />
|
||||||
<Compile Include="Notifications\Discord\DiscordColors.cs" />
|
<Compile Include="Notifications\Discord\DiscordColors.cs" />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using Sonarr.Http.REST;
|
using Sonarr.Http.REST;
|
||||||
|
|
||||||
namespace Sonarr.Api.V3.Config
|
namespace Sonarr.Api.V3.Config
|
||||||
@ -20,6 +21,7 @@ namespace Sonarr.Api.V3.Config
|
|||||||
public string ChownUser { get; set; }
|
public string ChownUser { get; set; }
|
||||||
public string ChownGroup { get; set; }
|
public string ChownGroup { get; set; }
|
||||||
|
|
||||||
|
public EpisodeTitleRequiredType EpisodeTitleRequired { get; set; }
|
||||||
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||||
public bool CopyUsingHardlinks { get; set; }
|
public bool CopyUsingHardlinks { get; set; }
|
||||||
public bool ImportExtraFiles { get; set; }
|
public bool ImportExtraFiles { get; set; }
|
||||||
@ -47,6 +49,7 @@ namespace Sonarr.Api.V3.Config
|
|||||||
ChownUser = model.ChownUser,
|
ChownUser = model.ChownUser,
|
||||||
ChownGroup = model.ChownGroup,
|
ChownGroup = model.ChownGroup,
|
||||||
|
|
||||||
|
EpisodeTitleRequired = model.EpisodeTitleRequired,
|
||||||
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
|
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
|
||||||
CopyUsingHardlinks = model.CopyUsingHardlinks,
|
CopyUsingHardlinks = model.CopyUsingHardlinks,
|
||||||
ImportExtraFiles = model.ImportExtraFiles,
|
ImportExtraFiles = model.ImportExtraFiles,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user