New: Configurable Specials folder format

pull/3166/head
Taloth Saldono 5 years ago
parent 39ea2dd32f
commit 628ab85de4

@ -85,6 +85,16 @@ class Naming extends Component {
});
}
onSpecialsFolderNamingModalOpenClick = () => {
this.setState({
isNamingModalOpen: true,
namingModalOptions: {
name: 'specialsFolderFormat',
season: true
}
});
}
onNamingModalClose = () => {
this.setState({ isNamingModalOpen: false });
}
@ -130,6 +140,8 @@ class Naming extends Component {
const seriesFolderFormatErrors = [];
const seasonFolderFormatHelpTexts = [];
const seasonFolderFormatErrors = [];
const specialsFolderFormatHelpTexts = [];
const specialsFolderFormatErrors = [];
if (examplesPopulated) {
if (examples.singleEpisodeExample) {
@ -173,6 +185,12 @@ class Naming extends Component {
} else {
seasonFolderFormatErrors.push({ message: 'Invalid Format' });
}
if (examples.specialsFolderExample) {
specialsFolderFormatHelpTexts.push(`Example: ${examples.specialsFolderExample}`);
} else {
specialsFolderFormatErrors.push({ message: 'Invalid Format' });
}
}
return (
@ -297,6 +315,24 @@ class Naming extends Component {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Specials Folder Format</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
type={inputTypes.TEXT}
name="specialsFolderFormat"
buttons={<FormInputButton onPress={this.onSpecialsFolderNamingModalOpenClick}>?</FormInputButton>}
onChange={onInputChange}
{...settings.specialsFolderFormat}
helpTexts={specialsFolderFormatHelpTexts}
errors={[...specialsFolderFormatErrors, ...settings.specialsFolderFormat.errors]}
/>
</FormGroup>
<FormGroup>
<FormLabel>Multi-Episode Style</FormLabel>

@ -41,6 +41,7 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
SharedValidator.RuleFor(c => c.SpecialsFolderFormat).ValidSpecialsFolderFormat();
}
private void UpdateNamingConfig(NamingConfigResource resource)
@ -109,6 +110,10 @@ namespace NzbDrone.Api.Config
? "Invalid format"
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
sampleResource.SpecialsFolderExample = nameSpec.SpecialsFolderFormat.IsNullOrWhiteSpace()
? "Invalid format"
: _filenameSampleService.GetSpecialsFolderSample(nameSpec);
return sampleResource.AsResponse();
}

@ -13,6 +13,7 @@ namespace NzbDrone.Api.Config
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public string SpecialsFolderFormat { get; set; }
public bool IncludeSeriesTitle { get; set; }
public bool IncludeEpisodeTitle { get; set; }
public bool IncludeQuality { get; set; }
@ -36,7 +37,8 @@ namespace NzbDrone.Api.Config
DailyEpisodeFormat = model.DailyEpisodeFormat,
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
SeriesFolderFormat = model.SeriesFolderFormat,
SeasonFolderFormat = model.SeasonFolderFormat
SeasonFolderFormat = model.SeasonFolderFormat,
SpecialsFolderFormat = model.SpecialsFolderFormat
//IncludeSeriesTitle
//IncludeEpisodeTitle
//IncludeQuality
@ -69,7 +71,8 @@ namespace NzbDrone.Api.Config
DailyEpisodeFormat = resource.DailyEpisodeFormat,
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
SeriesFolderFormat = resource.SeriesFolderFormat,
SeasonFolderFormat = resource.SeasonFolderFormat
SeasonFolderFormat = resource.SeasonFolderFormat,
SpecialsFolderFormat = resource.SpecialsFolderFormat
};
}
}

@ -9,5 +9,6 @@
public string AnimeMultiEpisodeExample { get; set; }
public string SeriesFolderExample { get; set; }
public string SeasonFolderExample { get; set; }
public string SpecialsFolderExample { get; set; }
}
}

@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
[TestCase("30 Rock - S01E05 - Episode Title", 1, false, "Season {season:00}", @"C:\Test\30 Rock\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S01E05 - Episode Title", 1, false, "Season {season}", @"C:\Test\30 Rock\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S01E05 - Episode Title", 1, true, "ReallyUglySeasonFolder {season}", @"C:\Test\30 Rock\ReallyUglySeasonFolder 1\30 Rock - S01E05 - Episode Title.mkv")]
[TestCase("30 Rock - S00E05 - Episode Title", 0, true, "Season {season}", @"C:\Test\30 Rock\Specials\30 Rock - S00E05 - Episode Title.mkv")]
[TestCase("30 Rock - S00E05 - Episode Title", 0, true, "Season {season}", @"C:\Test\30 Rock\MySpecials\30 Rock - S00E05 - Episode Title.mkv")]
public void CalculateFilePath_SeasonFolder_SingleNumber(string filename, int seasonNumber, bool useSeasonFolder, string seasonFolderFormat, string expectedPath)
{
var fakeSeries = Builder<Series>.CreateNew()
@ -39,6 +39,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Build();
namingConfig.SeasonFolderFormat = seasonFolderFormat;
namingConfig.SpecialsFolderFormat = "MySpecials";
Subject.BuildFilePath(fakeSeries, seasonNumber, filename, ".mkv").Should().Be(expectedPath.AsOsAgnostic());
}

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(134)]
public class add_specials_folder_format : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("NamingConfig").AddColumn("SpecialsFolderFormat").AsString().Nullable();
Execute.WithConnection(ConvertConfig);
}
private void ConvertConfig(IDbConnection conn, IDbTransaction tran)
{
var defaultFormat = "Specials";
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE NamingConfig SET SpecialsFolderFormat = ?";
updateCmd.AddParameter(defaultFormat);
updateCmd.ExecuteNonQuery();
}
}
}
}

@ -136,6 +136,7 @@
<Compile Include="Configuration\InvalidConfigFileException.cs" />
<Compile Include="Configuration\RescanAfterRefreshType.cs" />
<Compile Include="Configuration\ResetApiKeyCommand.cs" />
<Compile Include="Datastore\Migration\134_add_specials_folder_format.cs" />
<Compile Include="Datastore\Migration\132_add_download_client_priority.cs" />
<Compile Include="Datastore\Migration\131_download_propers_config.cs" />
<Compile Include="Datastore\Migration\130_episode_last_searched_time.cs" />

@ -174,18 +174,11 @@ namespace NzbDrone.Core.Organizer
if (series.SeasonFolder)
{
if (seasonNumber == 0)
{
path = Path.Combine(path, "Specials");
}
else
{
var seasonFolder = GetSeasonFolder(series, seasonNumber);
var seasonFolder = GetSeasonFolder(series, seasonNumber);
seasonFolder = CleanFileName(seasonFolder);
seasonFolder = CleanFileName(seasonFolder);
path = Path.Combine(path, seasonFolder);
}
path = Path.Combine(path, seasonFolder);
}
return path;
@ -266,7 +259,9 @@ namespace NzbDrone.Core.Organizer
AddIdTokens(tokenHandlers, series);
AddSeasonTokens(tokenHandlers, seasonNumber);
var folderName = ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig);
var format = seasonNumber == 0 ? namingConfig.SpecialsFolderFormat : namingConfig.SeasonFolderFormat;
var folderName = ReplaceTokens(format, tokenHandlers, namingConfig);
return CleanFolderName(folderName);
}

@ -15,6 +15,7 @@ namespace NzbDrone.Core.Organizer
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
string GetSeriesFolderSample(NamingConfig nameSpec);
string GetSeasonFolderSample(NamingConfig nameSpec);
string GetSpecialsFolderSample(NamingConfig nameSpec);
}
public class FileNameSampleService : IFilenameSampleService
@ -245,6 +246,11 @@ namespace NzbDrone.Core.Organizer
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
}
public string GetSpecialsFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetSeasonFolder(_standardSeries, 0, nameSpec);
}
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
{
try

@ -41,6 +41,11 @@ namespace NzbDrone.Core.Organizer
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
}
public static IRuleBuilderOptions<T, string> ValidSpecialsFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.SetValidator(new NotEmptyValidator(null));
}
}
public class ValidStandardEpisodeFormatValidator : PropertyValidator

@ -13,7 +13,8 @@ namespace NzbDrone.Core.Organizer
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
SeriesFolderFormat = "{Series Title}",
SeasonFolderFormat = "Season {season}"
SeasonFolderFormat = "Season {season}",
SpecialsFolderFormat = "Specials"
};
public bool RenameEpisodes { get; set; }
@ -24,5 +25,6 @@ namespace NzbDrone.Core.Organizer
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public string SpecialsFolderFormat { get; set; }
}
}

@ -41,6 +41,7 @@ namespace Sonarr.Api.V3.Config
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
SharedValidator.RuleFor(c => c.SpecialsFolderFormat).ValidSpecialsFolderFormat();
}
private void UpdateNamingConfig(NamingConfigResource resource)
@ -114,6 +115,10 @@ namespace Sonarr.Api.V3.Config
? null
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
sampleResource.SpecialsFolderExample = nameSpec.SpecialsFolderFormat.IsNullOrWhiteSpace()
? null
: _filenameSampleService.GetSpecialsFolderSample(nameSpec);
return sampleResource.AsResponse();
}

@ -12,6 +12,7 @@ namespace Sonarr.Api.V3.Config
public string AnimeEpisodeFormat { get; set; }
public string SeriesFolderFormat { get; set; }
public string SeasonFolderFormat { get; set; }
public string SpecialsFolderFormat { get; set; }
public bool IncludeSeriesTitle { get; set; }
public bool IncludeEpisodeTitle { get; set; }
public bool IncludeQuality { get; set; }

@ -11,6 +11,7 @@ namespace Sonarr.Api.V3.Config
public string AnimeMultiEpisodeExample { get; set; }
public string SeriesFolderExample { get; set; }
public string SeasonFolderExample { get; set; }
public string SpecialsFolderExample { get; set; }
}
public static class NamingConfigResourceMapper
@ -28,7 +29,8 @@ namespace Sonarr.Api.V3.Config
DailyEpisodeFormat = model.DailyEpisodeFormat,
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
SeriesFolderFormat = model.SeriesFolderFormat,
SeasonFolderFormat = model.SeasonFolderFormat
SeasonFolderFormat = model.SeasonFolderFormat,
SpecialsFolderFormat = model.SpecialsFolderFormat
//IncludeSeriesTitle
//IncludeEpisodeTitle
//IncludeQuality
@ -61,7 +63,8 @@ namespace Sonarr.Api.V3.Config
DailyEpisodeFormat = resource.DailyEpisodeFormat,
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
SeriesFolderFormat = resource.SeriesFolderFormat,
SeasonFolderFormat = resource.SeasonFolderFormat
SeasonFolderFormat = resource.SeasonFolderFormat,
SpecialsFolderFormat = resource.SpecialsFolderFormat
};
}
}

Loading…
Cancel
Save