From 606140832db6c644ee8d158186919a9ef5199c2b Mon Sep 17 00:00:00 2001 From: Keivan Date: Sun, 3 Oct 2010 18:00:50 -0700 Subject: [PATCH] Refactored SeriesController. Updated Post title parse. --- NzbDrone.Core.Test/SeriesTest.cs | 81 +++++------------- NzbDrone.Core/Providers/ISeriesProvider.cs | 9 +- NzbDrone.Core/Providers/ITvDbProvider.cs | 1 + NzbDrone.Core/Providers/SeriesProvider.cs | 83 +++++++------------ NzbDrone.Core/Providers/TvDbProvider.cs | 62 +++++++++++++- NzbDrone.Web/Content/style.css | 1 + .../Controllers/SettingsController.cs | 1 - 7 files changed, 110 insertions(+), 128 deletions(-) diff --git a/NzbDrone.Core.Test/SeriesTest.cs b/NzbDrone.Core.Test/SeriesTest.cs index 1815bc773..b74edaa40 100644 --- a/NzbDrone.Core.Test/SeriesTest.cs +++ b/NzbDrone.Core.Test/SeriesTest.cs @@ -23,87 +23,44 @@ namespace NzbDrone.Core.Test public class SeriesTest { [Test] - [Ignore("Can't get it to work")] - [Description("This test will confirm that a folder will be skipped if it has been resolved to a series already assigned to another folder")] - public void skip_same_series_diffrent_folder() + public void Map_path_to_series() { - var tvDbId = 1234; - //Arrange - var moqData = new Mock(); - var moqTvdb = new Mock(); - - //setup db to return a fake series - Series fakeSeries = Builder.CreateNew() - .With(f => f.TvdbId = tvDbId) - .Build(); - - moqData.Setup(f => f.Exists(c => c.TvdbId == tvDbId)). - Returns(true); - - //setup tvdb to return the same show, - IList fakeSearchResult = Builder.CreateListOfSize(4).WhereTheFirst(1).Has(f => f.Id = tvDbId).Build(); - TvdbSeries fakeTvDbSeries = Builder.CreateNew() - .With(f => f.Id = tvDbId) - .Build(); - - moqTvdb.Setup(f => f.GetSeries(It.IsAny(), It.IsAny())).Returns(fakeTvDbSeries); - moqTvdb.Setup(f => f.SearchSeries(It.IsAny())). - Returns(fakeSearchResult); - - var kernel = new MockingKernel(); - kernel.Bind().ToConstant(moqData.Object); - kernel.Bind().ToConstant(moqTvdb.Object); - kernel.Bind().ToConstant(MockLib.StandardConfig); - kernel.Bind().ToConstant(MockLib.StandardDisk); - kernel.Bind().To(); - - - //Act - var seriesController = kernel.Get(); - seriesController.SyncSeriesWithDisk(); - - //Assert - //Verify that the show was added to the database only once. - moqData.Verify(c => c.Add(It.IsAny()), Times.Once()); - } - - - [Test] - [Row(0)] - [Row(1)] - [Row(2)] - [Row(3)] - public void register_series_with_match(int matchPosition) - { TvdbSeries fakeSeries = Builder.CreateNew().With(f => f.SeriesName = "The Simpsons").Build(); - var fakeSearch = Builder.CreateListOfSize(4).Build(); - fakeSearch[matchPosition].Id = fakeSeries.Id; - fakeSearch[matchPosition].SeriesName = fakeSeries.SeriesName; + var fakeSearch = Builder.CreateNew().Build(); + fakeSearch.Id = fakeSeries.Id; + fakeSearch.SeriesName = fakeSeries.SeriesName; - - //Arrange var moqData = new Mock(); var moqTvdb = new Mock(); moqData.Setup(f => f.Exists(c => c.TvdbId == It.IsAny())).Returns(false); - moqTvdb.Setup(f => f.SearchSeries(It.IsAny())).Returns(fakeSearch); + moqTvdb.Setup(f => f.GetSeries(It.IsAny())).Returns(fakeSearch); moqTvdb.Setup(f => f.GetSeries(fakeSeries.Id, It.IsAny())).Returns(fakeSeries); var kernel = new MockingKernel(); kernel.Bind().ToConstant(moqData.Object); kernel.Bind().ToConstant(moqTvdb.Object); kernel.Bind().To(); - //Act var seriesController = kernel.Get(); - seriesController.RegisterSeries(@"D:\TV Shows\The Simpsons"); + var mappedSeries = seriesController.MapPathToSeries(@"D:\TV Shows\The Simpsons"); //Assert - //Verify that the show was added to the database only once. - moqData.Verify(c => c.Add(It.Is(d => d.TvdbId == fakeSeries.Id)), Times.Once()); + Assert.AreEqual(fakeSeries, mappedSeries); + } + + [Test] + [Row(new object[] { "That's Life - 2x03 -The Devil and Miss DeLucca", "That's Life" })] + [Row(new object[] { "Van.Duin.Op.Zn.Best.S02E05.DUTCH.WS.PDTV.XViD-DiFFERENT", "Van Duin Op Zn Best" })] + [Row(new object[] { "Dollhouse.S02E06.The.Left.Hand.720p.BluRay.x264-SiNNERS", "Dollhouse" })] + [Row(new object[] { "Heroes.S02.COMPLETE.German.PROPER.DVDRip.XviD-Prim3time", "Heroes" })] + public void Test_Parse_Success(string postTitle, string title) + { + var result = SeriesProvider.ParseTitle(postTitle); + Assert.AreEqual(title, result, postTitle); } @@ -142,7 +99,7 @@ namespace NzbDrone.Core.Test [Row(new object[] { "Simpsons The", "Simpsons", true })] public void Name_match_test(string a, string b, bool match) { - bool result = SeriesProvider.IsTitleMatch(a, b); + bool result = TvDbProvider.IsTitleMatch(a, b); Assert.AreEqual(match, result, "{0} , {1}", a, b); } diff --git a/NzbDrone.Core/Providers/ISeriesProvider.cs b/NzbDrone.Core/Providers/ISeriesProvider.cs index 694f9b3ea..89ec8e783 100644 --- a/NzbDrone.Core/Providers/ISeriesProvider.cs +++ b/NzbDrone.Core/Providers/ISeriesProvider.cs @@ -12,13 +12,6 @@ namespace NzbDrone.Core.Providers Series GetSeries(long tvdbId); void SyncSeriesWithDisk(); - /// - /// Parses a post title - /// - /// Title of the report - /// TVDB id of the series this report belongs to - long Parse(string postTitle); - /// /// Determines if a series is being actively watched. /// @@ -26,7 +19,7 @@ namespace NzbDrone.Core.Providers /// Whether or not the show is monitored bool IsMonitored(long id); - bool RegisterSeries(string path); + TvdbSeries MapPathToSeries(string path); void RegisterSeries(string path, TvdbSeries series); List GetUnmappedFolders(); } diff --git a/NzbDrone.Core/Providers/ITvDbProvider.cs b/NzbDrone.Core/Providers/ITvDbProvider.cs index 4315401b9..ca43e27be 100644 --- a/NzbDrone.Core/Providers/ITvDbProvider.cs +++ b/NzbDrone.Core/Providers/ITvDbProvider.cs @@ -7,5 +7,6 @@ namespace NzbDrone.Core.Providers { IList SearchSeries(string name); TvdbSeries GetSeries(int id, TvdbLanguage language); + TvdbSearchResult GetSeries(string title); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 9e72c9ede..f0e86e082 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -15,7 +15,8 @@ namespace NzbDrone.Core.Providers { //TODO: Remove parsing of rest of tv show info we just need the show name - private static readonly Regex CleanUpRegex = new Regex(@"((\s|^)the(\s|$))|((\s|^)and(\s|$))|[^a-z]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + //Trims all white spaces and separators from the end of the title. + private static readonly Regex CleanTitleRegex = new Regex(@"[\s.][^a-z]*$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ParseRegex = new Regex(@"(?.*) (?: s(?\d+)e(?\d+)-?e(?\d+) @@ -38,6 +39,7 @@ namespace NzbDrone.Core.Providers private readonly IRepository _sonioRepo; private readonly ITvDbProvider _tvDb; private static readonly Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + private static readonly Regex CleanUpRegex = new Regex(@"((\s|^)the(\s|$))|((\s|^)and(\s|$))|[^a-z]", RegexOptions.IgnoreCase | RegexOptions.Compiled); public SeriesProvider(IDiskProvider diskProvider, IConfigProvider configProvider, IRepository dataRepository, ITvDbProvider tvDbProvider) { @@ -70,20 +72,18 @@ namespace NzbDrone.Core.Providers } /// - /// Parses a post title + /// Parses series name out of a post title /// /// Title of the report - /// TVDB id of the series this report belongs to - public long Parse(string postTitle) + /// Name series this report belongs to + public static string ParseTitle(string postTitle) { var match = ParseRegex.Match(postTitle); if (!match.Success) throw new ArgumentException(String.Format("Title doesn't match any know patterns. [{0}]", postTitle)); - //TODO: title should be mapped to a proper Series object. with tvdbId and everything even if it is not in the db or being tracked. - - throw new NotImplementedException(); + return CleanTitleRegex.Replace(match.Groups["showName"].Value, String.Empty).Replace(".", " "); } public void SyncSeriesWithDisk() @@ -94,7 +94,23 @@ namespace NzbDrone.Core.Providers foreach (string seriesFolder in GetUnmappedFolders()) { Logger.Info("Folder '{0}' isn't mapped to a series in the database. Trying to map it.'", seriesFolder); - RegisterSeries(seriesFolder); + var mappedSeries = MapPathToSeries(seriesFolder); + + if (mappedSeries == null) + { + Logger.Warn("Unable to find a matching series for '{0}'", seriesFolder); + break; + } + + if (!_sonioRepo.Exists(s => s.TvdbId == mappedSeries.Id)) + { + RegisterSeries(seriesFolder, mappedSeries); + } + else + { + Logger.Warn("Folder '{0}' mapped to '{1}' which is already another folder assigned to it.'", seriesFolder, mappedSeries.SeriesName); + } + } } @@ -113,27 +129,15 @@ namespace NzbDrone.Core.Providers return results; } - public bool RegisterSeries(string path) + public TvdbSeries MapPathToSeries(string path) { var seriesPath = new DirectoryInfo(path); - var searchResults = _tvDb.SearchSeries(seriesPath.Name); - Logger.Debug("Search for '{0}' returned {1} results", searchResults.Count); + var searchResults = _tvDb.GetSeries(seriesPath.Name); - if (searchResults.Count == 0) - return false; + if (searchResults == null) + return null; - foreach (var tvdbSearchResult in searchResults) - { - TvdbSearchResult result = tvdbSearchResult; - if (IsTitleMatch(seriesPath.Name, result.SeriesName) && !_sonioRepo.Exists(c => c.TvdbId == result.Id)) - { - RegisterSeries(path, _tvDb.GetSeries(result.Id, result.Language)); - return true; - } - } - - Logger.Info("Unable to fine a match for {0}", seriesPath.Name); - return false; + return _tvDb.GetSeries(searchResults.Id, searchResults.Language); } @@ -157,37 +161,8 @@ namespace NzbDrone.Core.Providers #region Static Helpers - /// - /// Determines whether a title in a search result is equal to the title searched for. - /// - /// Name of the directory. - /// The TVDB title. - /// - /// true if the titles are found to be same; otherwise, false. - /// - public static bool IsTitleMatch(string directoryName, string tvdbTitle) - { - - var result = false; - - if (String.IsNullOrEmpty(directoryName)) - throw new ArgumentException("directoryName"); - if (String.IsNullOrEmpty(tvdbTitle)) - throw new ArgumentException("tvdbTitle"); - - if (String.Equals(directoryName, tvdbTitle, StringComparison.CurrentCultureIgnoreCase)) - { - result = true; - } - else if (String.Equals(CleanUpRegex.Replace(directoryName, ""), CleanUpRegex.Replace(tvdbTitle, ""), StringComparison.InvariantCultureIgnoreCase)) - result = true; - - Logger.Debug("Match between '{0}' and '{1}' was {2}", tvdbTitle, directoryName, result); - return result; - } - #endregion } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/TvDbProvider.cs b/NzbDrone.Core/Providers/TvDbProvider.cs index 5f02aff0e..18fd67d90 100644 --- a/NzbDrone.Core/Providers/TvDbProvider.cs +++ b/NzbDrone.Core/Providers/TvDbProvider.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; +using NLog; using TvdbLib; using TvdbLib.Cache; using TvdbLib.Data; @@ -8,6 +11,9 @@ namespace NzbDrone.Core.Providers { public class TvDbProvider : ITvDbProvider { + private static readonly Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + private static readonly Regex CleanUpRegex = new Regex(@"((\s|^)the(\s|$))|((\s|^)and(\s|$))|[^a-z]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private const string TVDB_APIKEY = "5D2D188E86E07F4F"; private readonly TvdbHandler _handler; @@ -18,16 +24,66 @@ namespace NzbDrone.Core.Providers #region ITvDbProvider Members - public IList SearchSeries(string name) + public IList SearchSeries(string title) { - return _handler.SearchSeries(name); + Logger.Debug("Searching TVDB for '{0}'", title); + var result = new List(); + + foreach (var tvdbSearchResult in _handler.SearchSeries(title)) + { + if (IsTitleMatch(tvdbSearchResult.SeriesName, title)) + { + result.Add(tvdbSearchResult); + } + } + + Logger.Debug("Search for '{0}' returned {1} results", title); + return result; + } + + + public TvdbSearchResult GetSeries(string title) + { + return SearchSeries(title)[0]; } public TvdbSeries GetSeries(int id, TvdbLanguage language) { + Logger.Debug("Fetching seriesId'{0}' - '{1}' from tvdb", id, language); return _handler.GetSeries(id, language, true, false, false); } + /// + /// Determines whether a title in a search result is equal to the title searched for. + /// + /// Name of the directory. + /// The TVDB title. + /// + /// true if the titles are found to be same; otherwise, false. + /// + public static bool IsTitleMatch(string directoryName, string tvdbTitle) + { + Logger.Debug("Trying to match '{0}' and '{1}'", tvdbTitle, directoryName); + + var result = false; + + if (String.IsNullOrEmpty(directoryName)) + throw new ArgumentException("directoryName"); + if (String.IsNullOrEmpty(tvdbTitle)) + throw new ArgumentException("tvdbTitle"); + + if (String.Equals(directoryName, tvdbTitle, StringComparison.CurrentCultureIgnoreCase)) + { + result = true; + } + else if (String.Equals(CleanUpRegex.Replace(directoryName, ""), CleanUpRegex.Replace(tvdbTitle, ""), StringComparison.InvariantCultureIgnoreCase)) + result = true; + + Logger.Debug("Match between '{0}' and '{1}' was {2}", tvdbTitle, directoryName, result); + + return result; + } + #endregion } } \ No newline at end of file diff --git a/NzbDrone.Web/Content/style.css b/NzbDrone.Web/Content/style.css index 815bbad81..a56e1a7de 100644 --- a/NzbDrone.Web/Content/style.css +++ b/NzbDrone.Web/Content/style.css @@ -12,6 +12,7 @@ body font-family: Segoe UI, Tahoma, Geneva, sans-serif; font-size: 11px; color: #3C3C3C; + background-attachment: fixed; } h1, h2, h3 diff --git a/NzbDrone.Web/Controllers/SettingsController.cs b/NzbDrone.Web/Controllers/SettingsController.cs index cff25a70b..37679c56a 100644 --- a/NzbDrone.Web/Controllers/SettingsController.cs +++ b/NzbDrone.Web/Controllers/SettingsController.cs @@ -32,7 +32,6 @@ namespace NzbDrone.Web.Controllers _configProvider.SeriesRoot = model.TvFolder; //return RedirectToAction("index"); } - return View(model); }