From 1e878a88fd2002e631ea53a69688ba541691f129 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 16 Jan 2014 20:41:23 -0800 Subject: [PATCH 1/7] ImdbId and Title Slug are now nullable --- .../AlterFixture.cs | 3 - .../EpisodeParseResultTest.cs | 258 ------------------ .../NzbDrone.Core.Test.csproj | 1 - .../Migration/034_remove_series_contraints.cs | 14 + .../Migration/Framework/SqliteAlter.cs | 42 ++- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 6 files changed, 56 insertions(+), 263 deletions(-) delete mode 100644 src/NzbDrone.Core.Test/EpisodeParseResultTest.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/034_remove_series_contraints.cs diff --git a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs index 7e1510259..aa07e1028 100644 --- a/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/SQLiteMigrationHelperTests/AlterFixture.cs @@ -97,10 +97,8 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests var columns = _subject.GetColumns("QualitySizes"); var indexes = _subject.GetIndexes("QualitySizes"); - _subject.CreateTable("QualityB", columns.Values, indexes); - var newIndexes = _subject.GetIndexes("QualityB"); newIndexes.Should().HaveSameCount(indexes); @@ -122,6 +120,5 @@ namespace NzbDrone.Core.Test.Datastore.SQLiteMigrationHelperTests newColumns.Values.Should().HaveSameCount(columns.Values); newIndexes.Should().Contain(i=>i.Column == "AirTime"); } - } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/EpisodeParseResultTest.cs b/src/NzbDrone.Core.Test/EpisodeParseResultTest.cs deleted file mode 100644 index 4d14a77af..000000000 --- a/src/NzbDrone.Core.Test/EpisodeParseResultTest.cs +++ /dev/null @@ -1,258 +0,0 @@ -/* -using System; -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Model; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test -{ - [TestFixture] - - public class EpisodeParseResultTest : CoreTest - { - [Test] - public void tostring_single_season_episode() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.EpisodeNumbers = new List { 3 }; - parseResult.FullSeason = false; - parseResult.AirDate = null; - parseResult.Quality = new QualityModel(Quality.HDTV720p, false); - - - parseResult.ToString().Should().Be("My Series - S12E03 HDTV-720p"); - } - - [Test] - public void tostring_single_season_episode_proper() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.EpisodeNumbers = new List { 3 }; - parseResult.FullSeason = false; - parseResult.AirDate = null; - parseResult.Quality = new QualityModel(Quality.HDTV720p, true); - - - parseResult.ToString().Should().Be("My Series - S12E03 HDTV-720p [proper]"); - } - - [Test] - public void tostring_multi_season_episode() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.EpisodeNumbers = new List { 3, 4, 5 }; - parseResult.FullSeason = false; - parseResult.AirDate = null; - parseResult.Quality = new QualityModel(Quality.HDTV720p, false); - - - parseResult.ToString().Should().Be("My Series - S12E03-04-05 HDTV-720p"); - } - - [Test] - public void tostring_multi_season_episode_proper() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.EpisodeNumbers = new List { 3, 4, 5 }; - parseResult.FullSeason = false; - parseResult.AirDate = null; - parseResult.Quality = new QualityModel(Quality.HDTV720p, true); - - - parseResult.ToString().Should().Be("My Series - S12E03-04-05 HDTV-720p [proper]"); - } - - - [Test] - public void tostring_full_season() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.FullSeason = true; - parseResult.AirDate = null; - parseResult.Quality = new QualityModel(Quality.HDTV720p, false); - - - parseResult.ToString().Should().Be("My Series - Season 12 HDTV-720p"); - } - - - [Test] - public void tostring_full_season_proper() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.FullSeason = true; - parseResult.AirDate = null; - parseResult.Quality = new QualityModel(Quality.HDTV720p, true); - - - parseResult.ToString().Should().Be("My Series - Season 12 HDTV-720p [proper]"); - } - - [Test] - public void tostring_daily_show() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.FullSeason = true; - parseResult.AirDate = new DateTime(2010, 12, 30); - parseResult.Quality = new QualityModel(Quality.HDTV720p, false); - - - parseResult.ToString().Should().Be("My Series - 2010-12-30 HDTV-720p"); - } - - [Test] - public void tostring_daily_show_proper() - { - var parseResult = new RemoteEpisode(); - parseResult.SeriesTitle = "My Series"; - parseResult.SeasonNumber = 12; - parseResult.FullSeason = true; - parseResult.AirDate = new DateTime(2010, 12, 30); - parseResult.Quality = new QualityModel(Quality.HDTV720p, true); - - - parseResult.ToString().Should().Be("My Series - 2010-12-30 HDTV-720p [proper]"); - } - - - - public static readonly object[] SabNamingCases = - { - new object[] { 1, new[] { 2 }, "My Episode Title", Quality.DVD, false, "My Series Name - 1x02 - My Episode Title [DVD]" }, - new object[] { 1, new[] { 2 }, "My Episode Title", Quality.DVD, true, "My Series Name - 1x02 - My Episode Title [DVD] [Proper]" }, - new object[] { 1, new[] { 2 }, "", Quality.DVD, true, "My Series Name - 1x02 - [DVD] [Proper]" }, - new object[] { 1, new[] { 2, 4 }, "My Episode Title", Quality.HDTV720p, false, "My Series Name - 1x02-1x04 - My Episode Title [HDTV-720p]" }, - new object[] { 1, new[] { 2, 4 }, "My Episode Title", Quality.HDTV720p, true, "My Series Name - 1x02-1x04 - My Episode Title [HDTV-720p] [Proper]" }, - new object[] { 1, new[] { 2, 4 }, "", Quality.HDTV720p, true, "My Series Name - 1x02-1x04 - [HDTV-720p] [Proper]" }, - }; - - - [Test, TestCaseSource("SabNamingCases")] - public void create_proper_sab_titles(int seasons, int[] episodes, string title, Quality quality, bool proper, string expected) - { - var series = Builder.CreateNew() - .With(c => c.Title = "My Series Name") - .Build(); - - var fakeEpisodes = new List(); - - foreach (var episode in episodes) - fakeEpisodes.Add(Builder - .CreateNew() - .With(e => e.EpisodeNumber = episode) - .With(e => e.Title = title) - .Build()); - - var parsResult = new RemoteEpisode() - { - AirDate = DateTime.Now, - EpisodeNumbers = episodes.ToList(), - Quality = new QualityModel(quality, proper), - SeasonNumber = seasons, - Series = series, - EpisodeTitle = title, - Episodes = fakeEpisodes - }; - - parsResult.GetDownloadTitle().Should().Be(expected); - } - - [TestCase(true, Result = "My Series Name - Season 1 [Bluray720p] [Proper]")] - [TestCase(false, Result = "My Series Name - Season 1 [Bluray720p]")] - public string create_proper_sab_season_title(bool proper) - { - var series = Builder.CreateNew() - .With(c => c.Title = "My Series Name") - .Build(); - - var parsResult = new RemoteEpisode() - { - AirDate = DateTime.Now, - Quality = new QualityModel(Quality.Bluray720p, proper), - SeasonNumber = 1, - Series = series, - EpisodeTitle = "My Episode Title", - FullSeason = true - }; - - return parsResult.GetDownloadTitle(); - } - - [TestCase(true, Result = "My Series Name - 2011-12-01 - My Episode Title [Bluray720p] [Proper]")] - [TestCase(false, Result = "My Series Name - 2011-12-01 - My Episode Title [Bluray720p]")] - public string create_proper_sab_daily_titles(bool proper) - { - var series = Builder.CreateNew() - .With(c => c.SeriesType = SeriesTypes.Daily) - .With(c => c.Title = "My Series Name") - .Build(); - - var episode = Builder.CreateNew() - .With(e => e.Title = "My Episode Title") - .Build(); - - var parsResult = new RemoteEpisode - { - AirDate = new DateTime(2011, 12, 1), - Quality = new QualityModel(Quality.Bluray720p, proper), - Series = series, - EpisodeTitle = "My Episode Title", - Episodes = new List { episode } - }; - - return parsResult.GetDownloadTitle(); - } - - [Test] - public void should_not_repeat_the_same_episode_title() - { - var series = Builder.CreateNew() - .With(c => c.Title = "My Series Name") - .Build(); - - var fakeEpisodes = Builder.CreateListOfSize(2) - .All() - .With(e => e.SeasonNumber = 5) - .TheFirst(1) - .With(e => e.Title = "My Episode Title (1)") - .TheLast(1) - .With(e => e.Title = "My Episode Title (2)") - .Build(); - - var parsResult = new RemoteEpisode - { - AirDate = DateTime.Now, - EpisodeNumbers = new List { 10, 11 }, - Quality = new QualityModel(Quality.HDTV720p, false), - SeasonNumber = 35, - Series = series, - Episodes = fakeEpisodes - }; - - parsResult.GetDownloadTitle().Should().Be("My Series Name - 5x01-5x02 - My Episode Title [HDTV-720p]"); - } - - } -} -*/ diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index d1da02111..f4a352f2e 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -185,7 +185,6 @@ - diff --git a/src/NzbDrone.Core/Datastore/Migration/034_remove_series_contraints.cs b/src/NzbDrone.Core/Datastore/Migration/034_remove_series_contraints.cs new file mode 100644 index 000000000..7874e9c38 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/034_remove_series_contraints.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(34)] + public class remove_series_contraints : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + SqLiteAlter.Nullify("Series", new[] { "ImdbId", "TitleSlug" }); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs index c22670588..c8d423a1c 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/SqliteAlter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; namespace NzbDrone.Core.Datastore.Migration.Framework @@ -7,6 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework { void DropColumns(string tableName, IEnumerable columns); void AddIndexes(string tableName, params SQLiteIndex[] indexes); + void Nullify(string tableName, IEnumerable columns); } public class SQLiteAlter : ISQLiteAlter @@ -50,6 +52,44 @@ namespace NzbDrone.Core.Datastore.Migration.Framework } } + public void Nullify(string tableName, IEnumerable columns) + { + using (var transaction = _sqLiteMigrationHelper.BeginTransaction()) + { + var originalColumns = _sqLiteMigrationHelper.GetColumns(tableName); + var originalIndexes = _sqLiteMigrationHelper.GetIndexes(tableName); + + var newColumns = originalColumns.Select(c => + { + if (!columns.Contains(c.Key)) + { + return c.Value; + } + + if (!c.Value.Schema.Contains("NOT NULL") && c.Value.Schema.Contains("NULL")) + { + return c.Value; + } + + if (c.Value.Schema.Contains("NOT NULL")) + { + c.Value.Schema = c.Value.Schema.Replace("NOT NULL", "NULL"); + return c.Value; + } + + c.Value.Schema += " NULL"; + + return c.Value; + }).ToList(); + + var newIndexes = originalIndexes; + + CreateTable(tableName, newColumns, newIndexes); + + transaction.Commit(); + } + } + private void CreateTable(string tableName, List newColumns, IEnumerable newIndexes) { var tempTableName = tableName + "_temp"; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 7967454d8..4567bc73a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -189,6 +189,7 @@ + From 7c6605c02bce57cbc956872a0784f721a2881140 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 16 Jan 2014 23:23:34 -0800 Subject: [PATCH 2/7] New: Original Title can be used in file names --- .../OrganizerTests/GetNewFilenameFixture.cs | 13 +++++++++++++ src/NzbDrone.Core/Organizer/FileNameBuilder.cs | 4 ++-- .../MediaManagement/Naming/NamingViewTemplate.html | 2 ++ .../Naming/Partials/OriginalTitleNamingPartial.html | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/UI/Settings/MediaManagement/Naming/Partials/OriginalTitleNamingPartial.html diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs index 749dff288..b67e571ec 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs @@ -353,5 +353,18 @@ namespace NzbDrone.Core.Test.OrganizerTests Subject.BuildFilename(new List { _episode1 }, _series, _episodeFile) .Should().Be(_episodeFile.ReleaseGroup); } + + [Test] + public void should_be_able_to_use_orginal_title() + { + _series.Title = "30 Rock"; + _namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Title}"; + + _episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL"; + _episodeFile.Path = @"C:\Test\TV\30 Rock - S01E01 - Test"; + + Subject.BuildFilename(new List { _episode1 }, _series, _episodeFile) + .Should().Be("30 Rock - 30.Rock.S01E01.xvid-LOL"); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 004ff68ac..4a1abfed5 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text.RegularExpressions; using NLog; using NzbDrone.Common.Cache; -using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Tv; @@ -86,7 +85,8 @@ namespace NzbDrone.Core.Organizer var tokenValues = new Dictionary(FilenameBuilderTokenEqualityComparer.Instance) { - {"{Series Title}", series.Title} + {"{Series Title}", series.Title}, + {"Original Title", episodeFile.SceneName} }; tokenValues.Add("{Release Group}", episodeFile.ReleaseGroup); diff --git a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html index e7b2e4f61..33586db4a 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html +++ b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html @@ -42,6 +42,7 @@ {{> EpisodeTitleNamingPartial}} {{> QualityTitleNamingPartial}} {{> ReleaseGroupNamingPartial}} + {{> OriginalTitleNamingPartial}} {{> SeparatorNamingPartial}} @@ -71,6 +72,7 @@ {{> EpisodeTitleNamingPartial}} {{> QualityTitleNamingPartial}} {{> ReleaseGroupNamingPartial}} + {{> OriginalTitleNamingPartial}} {{> SeparatorNamingPartial}} diff --git a/src/UI/Settings/MediaManagement/Naming/Partials/OriginalTitleNamingPartial.html b/src/UI/Settings/MediaManagement/Naming/Partials/OriginalTitleNamingPartial.html new file mode 100644 index 000000000..cef96b894 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Partials/OriginalTitleNamingPartial.html @@ -0,0 +1 @@ +
  • Original Title
  • \ No newline at end of file From b5b9fababbdddf8954e417ae2d8fc390979d5110 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 16 Jan 2014 23:52:28 -0800 Subject: [PATCH 3/7] New: Series Folder format now configurable (used when adding series only) --- .../Config/NamingConfigResource.cs | 2 +- .../NzbDrone.Core.Test.csproj | 1 + .../OrganizerTests/GetSeriesFolderFixture.cs | 33 +++++++++++++++++++ ...d_series_folder_format_to_naming_config.cs | 16 +++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + .../Organizer/FileNameBuilder.cs | 13 ++++++++ src/NzbDrone.Core/Organizer/NamingConfig.cs | 2 ++ src/NzbDrone.Core/Tv/SeriesService.cs | 8 ++--- .../Naming/NamingViewTemplate.html | 18 ++++++++++ 9 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/035_add_series_folder_format_to_naming_config.cs diff --git a/src/NzbDrone.Api/Config/NamingConfigResource.cs b/src/NzbDrone.Api/Config/NamingConfigResource.cs index 9ca779dac..1e5ab16e3 100644 --- a/src/NzbDrone.Api/Config/NamingConfigResource.cs +++ b/src/NzbDrone.Api/Config/NamingConfigResource.cs @@ -9,8 +9,8 @@ namespace NzbDrone.Api.Config public Int32 MultiEpisodeStyle { get; set; } public string StandardEpisodeFormat { get; set; } public string DailyEpisodeFormat { get; set; } + public string SeriesFolderFormat { get; set; } public string SeasonFolderFormat { get; set; } - public bool IncludeSeriesTitle { get; set; } public bool IncludeEpisodeTitle { get; set; } public bool IncludeQuality { get; set; } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index f4a352f2e..dd0723b87 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -176,6 +176,7 @@ + diff --git a/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs new file mode 100644 index 000000000..abe7bf78b --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/GetSeriesFolderFixture.cs @@ -0,0 +1,33 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.OrganizerTests +{ + [TestFixture] + + public class GetSeriesFolderFixture : CoreTest + { + private NamingConfig namingConfig; + + [SetUp] + public void Setup() + { + namingConfig = new NamingConfig(); + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(namingConfig); + } + + [TestCase("30 Rock", "{Series Title}", "30 Rock")] + [TestCase("30 Rock", "{Series.Title}", "30.Rock")] + [TestCase("24/7 Road to the NHL Winter Classic", "{Series Title}", "24+7 Road to the NHL Winter Classic")] + public void should_use_seriesFolderFormat_to_build_folder_name(string seriesTitle, string format, string expected) + { + namingConfig.SeriesFolderFormat = format; + + Subject.GetSeriesFolder(seriesTitle).Should().Be(expected); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/035_add_series_folder_format_to_naming_config.cs b/src/NzbDrone.Core/Datastore/Migration/035_add_series_folder_format_to_naming_config.cs new file mode 100644 index 000000000..9423a54f0 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/035_add_series_folder_format_to_naming_config.cs @@ -0,0 +1,16 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(35)] + public class add_series_folder_format_to_naming_config : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("NamingConfig").AddColumn("SeriesFolderFormat").AsString().Nullable(); + + Execute.Sql("UPDATE NamingConfig SET SeriesFolderFormat = '{Series Title}'"); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 4567bc73a..5dbf4fd48 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -190,6 +190,7 @@ + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 4a1abfed5..905b73456 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Organizer string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); + string GetSeriesFolder(string seriesTitle); } public class FileNameBuilder : IBuildFileNames @@ -151,6 +152,7 @@ namespace NzbDrone.Core.Organizer public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) { string path = series.Path; + if (series.SeasonFolder) { string seasonFolder; @@ -222,6 +224,17 @@ namespace NzbDrone.Core.Organizer return basicNamingConfig; } + public string GetSeriesFolder(string seriesTitle) + { + seriesTitle = CleanFilename(seriesTitle); + + var nameSpec = _namingConfigService.GetConfig(); + var tokenValues = new Dictionary(FilenameBuilderTokenEqualityComparer.Instance); + tokenValues.Add("{Series Title}", seriesTitle); + + return ReplaceTokens(nameSpec.SeriesFolderFormat, tokenValues); + } + public static string CleanFilename(string name) { string result = name; diff --git a/src/NzbDrone.Core/Organizer/NamingConfig.cs b/src/NzbDrone.Core/Organizer/NamingConfig.cs index 7a537e3ce..fcfcdd940 100644 --- a/src/NzbDrone.Core/Organizer/NamingConfig.cs +++ b/src/NzbDrone.Core/Organizer/NamingConfig.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.Organizer MultiEpisodeStyle = 0, StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Title}", DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Title}", + SeriesFolderFormat = "{Series Title}", SeasonFolderFormat = "Season {season}" }; } @@ -23,6 +24,7 @@ namespace NzbDrone.Core.Organizer public int MultiEpisodeStyle { get; set; } public string StandardEpisodeFormat { get; set; } public string DailyEpisodeFormat { get; set; } + public string SeriesFolderFormat { get; set; } public string SeasonFolderFormat { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs index 18b67a732..5387f3704 100644 --- a/src/NzbDrone.Core/Tv/SeriesService.cs +++ b/src/NzbDrone.Core/Tv/SeriesService.cs @@ -31,24 +31,24 @@ namespace NzbDrone.Core.Tv public class SeriesService : ISeriesService { private readonly ISeriesRepository _seriesRepository; - private readonly IConfigService _configService; private readonly IEventAggregator _eventAggregator; private readonly ISceneMappingService _sceneMappingService; private readonly IEpisodeService _episodeService; + private readonly IBuildFileNames _fileNameBuilder; private readonly Logger _logger; public SeriesService(ISeriesRepository seriesRepository, - IConfigService configServiceService, IEventAggregator eventAggregator, ISceneMappingService sceneMappingService, IEpisodeService episodeService, + IBuildFileNames fileNameBuilder, Logger logger) { _seriesRepository = seriesRepository; - _configService = configServiceService; _eventAggregator = eventAggregator; _sceneMappingService = sceneMappingService; _episodeService = episodeService; + _fileNameBuilder = fileNameBuilder; _logger = logger; } @@ -63,7 +63,7 @@ namespace NzbDrone.Core.Tv if (String.IsNullOrWhiteSpace(newSeries.Path)) { - var folderName = FileNameBuilder.CleanFilename(newSeries.Title); + var folderName = _fileNameBuilder.GetSeriesFolder(newSeries.Title); newSeries.Path = Path.Combine(newSeries.RootFolderPath, folderName); } diff --git a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html index 33586db4a..af3839918 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html +++ b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html @@ -98,6 +98,24 @@ +
    + + +
    +
    + +
    + + +
    +
    +
    +
    +
    From 0b2865a97c1caa2e54c7b2ea29dec9bf9cc10666 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 17 Jan 2014 08:14:58 -0800 Subject: [PATCH 4/7] Fixed broken add series test --- .../SeriesServiceTests/AddSeriesFixture.cs | Bin 1805 -> 2001 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/AddSeriesFixture.cs index ab33ff0724940e12dfd3ee923903605e37a0cf98..a7eabef0623c35039fc484015f58e1a90deadeb6 100644 GIT binary patch delta 174 zcmeC>yU0HwiPOI*Juxq{Dz#{%cP3N4Z+>!iYLT9MY6*yCTRM3p9!qZ0!_^L3)Ezi!xJ--STr%Qj0Xw60=hw0(v2tB{`{@*v$w6 Rno%T@SB$BC^J1nf766YgG_(K! delta 15 Wcmcb}-^({4X=76m)8-21NEQGwrUifi From 43a2f39106d27e265d7c08c8f16a8041633becc4 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 17 Jan 2014 08:27:49 -0800 Subject: [PATCH 5/7] Added help text to Series Folder Format. Fixed styling of format helper buttons --- src/UI/Content/form.less | 6 ++++++ .../Settings/MediaManagement/Naming/NamingViewTemplate.html | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/UI/Content/form.less b/src/UI/Content/form.less index 3d9e5267c..3296620df 100644 --- a/src/UI/Content/form.less +++ b/src/UI/Content/form.less @@ -21,6 +21,12 @@ margin-bottom : 0; vertical-align : middle; } + + .btn { + i { + margin-right: 0px; + } + } } } diff --git a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html index af3839918..00908678c 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html +++ b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html @@ -98,7 +98,7 @@
    -
    +
    @@ -113,6 +113,9 @@
    + + +
    From 7bb8a9db5fb0d190a0ef32abf76e23b2c6574c78 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 17 Jan 2014 10:43:15 -0800 Subject: [PATCH 6/7] Validation and integration test for SeriesFolderFormat --- src/NzbDrone.Api/Config/NamingModule.cs | 2 +- src/NzbDrone.Core/Organizer/FileNameBuilder.cs | 3 +++ src/NzbDrone.Core/Organizer/FileNameValidation.cs | 6 ++++++ src/NzbDrone.Integration.Test/NamingConfigTests.cs | 11 +++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Api/Config/NamingModule.cs b/src/NzbDrone.Api/Config/NamingModule.cs index 1f0b39fac..f340f5ab9 100644 --- a/src/NzbDrone.Api/Config/NamingModule.cs +++ b/src/NzbDrone.Api/Config/NamingModule.cs @@ -4,7 +4,6 @@ using System.Linq; using FluentValidation; using FluentValidation.Results; using Nancy.Responses; -using NzbDrone.Api.REST; using NzbDrone.Core.Organizer; using Nancy.ModelBinding; using NzbDrone.Api.Mapping; @@ -39,6 +38,7 @@ namespace NzbDrone.Api.Config SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 3); SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat(); SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat(); + SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat(); } private void UpdateNamingConfig(NamingConfigResource resource) diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 905b73456..fa156552a 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -39,6 +39,9 @@ namespace NzbDrone.Core.Organizer public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?\s|\.|-|_)Title\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + public FileNameBuilder(INamingConfigService namingConfigService, ICacheManger cacheManger, Logger logger) diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index 0eceb9564..9a291e05c 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -17,6 +17,12 @@ namespace NzbDrone.Core.Organizer ruleBuilder.SetValidator(new NotEmptyValidator(null)); return ruleBuilder.SetValidator(new ValidDailyEpisodeFormatValidator()); } + + public static IRuleBuilderOptions ValidSeriesFolderFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.SeriesTitleRegex)).WithMessage("Must contain series title"); + } } public class ValidDailyEpisodeFormatValidator : PropertyValidator diff --git a/src/NzbDrone.Integration.Test/NamingConfigTests.cs b/src/NzbDrone.Integration.Test/NamingConfigTests.cs index 3f351bb68..5dc9a2fb0 100644 --- a/src/NzbDrone.Integration.Test/NamingConfigTests.cs +++ b/src/NzbDrone.Integration.Test/NamingConfigTests.cs @@ -96,5 +96,16 @@ namespace NzbDrone.Integration.Test var errors = NamingConfig.InvalidPut(config); errors.Should().NotBeEmpty(); } + + [Test] + public void should_get_bad_request_if_series_folder_format_does_not_contain_series_title() + { + var config = NamingConfig.GetSingle(); + config.RenameEpisodes = true; + config.SeriesFolderFormat = "This and That"; + + var errors = NamingConfig.InvalidPut(config); + errors.Should().NotBeEmpty(); + } } } \ No newline at end of file From 7d46f4d64d58ba8faae39d0462af41f746d23eb6 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 21 Jan 2014 21:35:01 -0800 Subject: [PATCH 7/7] Logging destination filename when importing/renaming files. --- src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs | 4 ++++ src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index 7f435fa86..0c08c7595 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -41,6 +41,8 @@ namespace NzbDrone.Core.MediaFiles var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id); var newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile); var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); + + _logger.Trace("Renaming episode file: {0} to {1}", episodeFile, filePath); MoveFile(episodeFile, series, filePath); return filePath; @@ -50,6 +52,8 @@ namespace NzbDrone.Core.MediaFiles { var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile); var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); + + _logger.Trace("Moving episode file: {0} to {1}", episodeFile, filePath); MoveFile(episodeFile, localEpisode.Series, filePath); return filePath; diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index 6a299d511..494c4ba10 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -53,7 +53,6 @@ namespace NzbDrone.Core.MediaFiles _mediaFileService.Delete(file, true); } - _logger.Trace("Moving episode file: {0}", episodeFile); moveFileResult.Path = _episodeFileMover.MoveEpisodeFile(episodeFile, localEpisode); return moveFileResult;