diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index 17e3e4a7f..f0f799f33 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -154,5 +154,25 @@ namespace NzbDrone.Core.Test //Assert Assert.AreEqual(expectedPath, result.FullName); } + + [Test] + public void DeleteEpisodeFile() + { + //Setup + var episodeFiles = Builder.CreateListOfSize(10).Build(); + + var mocker = new AutoMoqer(); + var database = MockLib.GetEmptyDatabase(true); + mocker.SetConstant(database); + database.InsertMany(episodeFiles); + + //Act + mocker.Resolve().Delete(1); + var result = database.Fetch(); + + //Assert + result.Should().HaveCount(9); + result.Should().NotContain(e => e.EpisodeFileId == 1); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index a46fd83cd..6a531f274 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -2,6 +2,7 @@ using System; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Model; using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Test.Framework; @@ -63,7 +64,6 @@ namespace NzbDrone.Core.Test Assert.AreEqual(episode, result.EpisodeNumbers[0]); } - [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", QualityTypes.DVD)] [TestCase("WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", QualityTypes.DVD)] [TestCase("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", QualityTypes.DVD)] @@ -94,6 +94,8 @@ namespace NzbDrone.Core.Test [TestCase("S07E23 - [WEBDL].mkv ", QualityTypes.WEBDL)] [TestCase("S07E23.mkv ", QualityTypes.HDTV)] [TestCase("S07E23 .avi ", QualityTypes.SDTV)] + [TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", QualityTypes.DVD)] + [TestCase("WEEDS.S03E01-06.DUAL.Bluray.AC3.-HELLYWOOD.avi", QualityTypes.Bluray720p)] public void quality_parse(string postTitle, object quality) { var result = Parser.ParseQuality(postTitle); @@ -122,8 +124,7 @@ namespace NzbDrone.Core.Test Assert.AreEqual(qualityEnum, result.QualityType); } } - - + [Timeout(1000)] [TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", "WEEDS", 3, new[] { 1, 2, 3, 4, 5, 6 }, 6)] [TestCase("Two.and.a.Half.Men.103.104.720p.HDTV.X264-DIMENSION", "Two.and.a.Half.Men", 1, new[] { 3, 4 }, 2)] @@ -164,7 +165,6 @@ namespace NzbDrone.Core.Test Assert.IsNull(result.EpisodeNumbers); } - [TestCase("30.Rock.Season.04.HDTV.XviD-DIMENSION", "30.Rock", 4)] [TestCase("Parks.and.Recreation.S02.720p.x264-DIMENSION", "Parks.and.Recreation", 2)] @@ -176,7 +176,6 @@ namespace NzbDrone.Core.Test Assert.AreEqual(Parser.NormalizeTitle(title), result.CleanTitle); Assert.AreEqual(0, result.EpisodeNumbers.Count); } - [TestCase("Conan", "conan")] [TestCase("The Tonight Show With Jay Leno", "tonightshowwithjayleno")] @@ -189,7 +188,6 @@ namespace NzbDrone.Core.Test Assert.AreEqual(seriesName, result); } - [TestCase(@"c:\test\", @"c:\test")] [TestCase(@"c:\\test\\", @"c:\test")] [TestCase(@"C:\\Test\\", @"C:\Test")] @@ -200,7 +198,6 @@ namespace NzbDrone.Core.Test var result = Parser.NormalizePath(dirty); Assert.AreEqual(clean, result); } - [TestCase("CaPitAl", "capital")] [TestCase("peri.od", "period")] @@ -241,7 +238,6 @@ namespace NzbDrone.Core.Test } - [TestCase("the")] [TestCase("and")] [TestCase("or")] @@ -267,5 +263,83 @@ namespace NzbDrone.Core.Test } } + + [TestCase("Chuck - 4x05 - Title", "Chuck")] + [TestCase("Law & Order - 4x05 - Title", "laworder")] + [TestCase("This Isn't a Valid Post", "")] + public void parse_series_name(string postTitle, string title) + { + var result = Parser.ParseSeriesName(postTitle); + Assert.AreEqual(Parser.NormalizeTitle(title), result); + } + + [TestCase("Chuck - 4x05 - Title [Proper]", true)] + [TestCase("Law & Order - 4x05 - Title", false)] + [TestCase("30.Rock.S04E05.asdjasdj.proper.", true)] + public void parse_proper(string postTitle, bool proper) + { + var result = Parser.ParseProper(postTitle); + Assert.AreEqual(proper, result); + } + + [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", LanguageType.English)] + [TestCase("Castle.2009.S01E14.French.HDTV.XviD-LOL", LanguageType.French)] + [TestCase("Castle.2009.S01E14.Spanish.HDTV.XviD-LOL", LanguageType.Spanish)] + [TestCase("Castle.2009.S01E14.German.HDTV.XviD-LOL", LanguageType.German)] + [TestCase("Castle.2009.S01E14.Germany.HDTV.XviD-LOL", LanguageType.English)] + [TestCase("Castle.2009.S01E14.Italian.HDTV.XviD-LOL", LanguageType.Italian)] + [TestCase("Castle.2009.S01E14.Danish.HDTV.XviD-LOL", LanguageType.Danish)] + [TestCase("Castle.2009.S01E14.Dutch.HDTV.XviD-LOL", LanguageType.Dutch)] + [TestCase("Castle.2009.S01E14.Japanese.HDTV.XviD-LOL", LanguageType.Japanese)] + [TestCase("Castle.2009.S01E14.Cantonese.HDTV.XviD-LOL", LanguageType.Cantonese)] + [TestCase("Castle.2009.S01E14.Mandarin.HDTV.XviD-LOL", LanguageType.Mandarin)] + [TestCase("Castle.2009.S01E14.Korean.HDTV.XviD-LOL", LanguageType.Korean)] + [TestCase("Castle.2009.S01E14.Russian.HDTV.XviD-LOL", LanguageType.Russian)] + [TestCase("Castle.2009.S01E14.Polish.HDTV.XviD-LOL", LanguageType.Polish)] + [TestCase("Castle.2009.S01E14.Vietnamese.HDTV.XviD-LOL", LanguageType.Vietnamese)] + [TestCase("Castle.2009.S01E14.Swedish.HDTV.XviD-LOL", LanguageType.Swedish)] + [TestCase("Castle.2009.S01E14.Norwegian.HDTV.XviD-LOL", LanguageType.Norwegian)] + [TestCase("Castle.2009.S01E14.Finnish.HDTV.XviD-LOL", LanguageType.Finnish)] + [TestCase("Castle.2009.S01E14.Turkish.HDTV.XviD-LOL", LanguageType.Turkish)] + [TestCase("Castle.2009.S01E14.Portuguese.HDTV.XviD-LOL", LanguageType.Portuguese)] + [TestCase("Castle.2009.S01E14.HDTV.XviD-LOL", LanguageType.English)] + public void parse_language(string postTitle, LanguageType language) + { + var result = Parser.ParseLanguage(postTitle); + Assert.AreEqual(language, result); + } + [Test] + [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Path can not be null or empty")] + public void normalize_path_exception_empty() + { + Parser.NormalizePath(""); + } + + [Test] + [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Path can not be null or empty")] + public void normalize_path_exception_null() + { + Parser.NormalizePath(null); + } + + [TestCase("Hawaii Five 0 S01E19 720p WEB DL DD5 1 H 264 NT", "Hawaii Five", 1)] + [TestCase("Chuck.4x05.HDTV.XviD-LOL", "Chuck", 4)] + [TestCase("S03E09 WS PDTV XviD FUtV", "", 3)] + [TestCase("5x10 WS PDTV XviD FUtV", "", 5)] + public void parse_season_info(string postTitle, string seriesName, int seasonNumber) + { + var result = Parser.ParseSeasonInfo(postTitle); + + Assert.AreEqual(Parser.NormalizeTitle(seriesName), result.SeriesTitle); + Assert.AreEqual(seasonNumber, result.SeasonNumber); + } + + [Test] + public void parse_season_info_null() + { + var result = Parser.ParseSeasonInfo("This is not a valid post"); + + Assert.AreEqual(null, result); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/SeriesProviderTest.cs b/NzbDrone.Core.Test/SeriesProviderTest.cs index 2b032aa00..f04eae442 100644 --- a/NzbDrone.Core.Test/SeriesProviderTest.cs +++ b/NzbDrone.Core.Test/SeriesProviderTest.cs @@ -224,7 +224,7 @@ namespace NzbDrone.Core.Test //Act mocker.Resolve(); - var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(true); + var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(); //Assert series.Should().HaveCount(1); @@ -249,7 +249,7 @@ namespace NzbDrone.Core.Test //Act mocker.Resolve(); - var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(true); + var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(); //Assert series.Should().HaveCount(1); @@ -274,7 +274,7 @@ namespace NzbDrone.Core.Test //Act mocker.Resolve(); - var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(true); + var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(); //Assert series.Should().HaveCount(1); @@ -303,7 +303,7 @@ namespace NzbDrone.Core.Test //Act mocker.Resolve(); - var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(true); + var series = mocker.Resolve().GetAllSeriesWithEpisodeCount(); //Assert series.Should().HaveCount(1); diff --git a/NzbDrone.Core/Parser.cs b/NzbDrone.Core/Parser.cs index 6079340af..e5cfb4c45 100644 --- a/NzbDrone.Core/Parser.cs +++ b/NzbDrone.Core/Parser.cs @@ -106,22 +106,15 @@ namespace NzbDrone.Core else { //Try to Parse as a daily show - if (airyear > 0) - { - var airmonth = Convert.ToInt32(match[0].Groups["airmonth"].Value); - var airday = Convert.ToInt32(match[0].Groups["airday"].Value); + var airmonth = Convert.ToInt32(match[0].Groups["airmonth"].Value); + var airday = Convert.ToInt32(match[0].Groups["airday"].Value); - parsedEpisode = new EpisodeParseResult - { - CleanTitle = seriesName, - AirDate = new DateTime(airyear, airmonth, airday), - Language = ParseLanguage(simpleTitle) - }; - } - - //Something went wrong with this one... return null - else - return null; + parsedEpisode = new EpisodeParseResult + { + CleanTitle = seriesName, + AirDate = new DateTime(airyear, airmonth, airday), + Language = ParseLanguage(simpleTitle) + }; } parsedEpisode.Quality = ParseQuality(title); @@ -313,65 +306,67 @@ namespace NzbDrone.Core internal static LanguageType ParseLanguage(string title) { - if (title.ToLower().Contains("english")) + var lowerTitle = title.ToLower(); + + if (lowerTitle.Contains("english")) return LanguageType.English; - if (title.ToLower().Contains("french")) + if (lowerTitle.Contains("french")) return LanguageType.French; - if (title.ToLower().Contains("spanish")) + if (lowerTitle.Contains("spanish")) return LanguageType.Spanish; - if (title.ToLower().Contains("german")) + if (lowerTitle.Contains("german")) { //Make sure it doesn't contain Germany (Since we're not using REGEX for all this) - if (!title.ToLower().Contains("germany")) + if (!lowerTitle.Contains("germany")) return LanguageType.German; } - if (title.ToLower().Contains("italian")) + if (lowerTitle.Contains("italian")) return LanguageType.Italian; - if (title.ToLower().Contains("danish")) + if (lowerTitle.Contains("danish")) return LanguageType.Danish; - if (title.ToLower().Contains("dutch")) + if (lowerTitle.Contains("dutch")) return LanguageType.Dutch; - if (title.ToLower().Contains("japanese")) + if (lowerTitle.Contains("japanese")) return LanguageType.Japanese; - if (title.ToLower().Contains("cantonese")) + if (lowerTitle.Contains("cantonese")) return LanguageType.Cantonese; - if (title.ToLower().Contains("mandarin")) + if (lowerTitle.Contains("mandarin")) return LanguageType.Mandarin; - if (title.ToLower().Contains("korean")) + if (lowerTitle.Contains("korean")) return LanguageType.Korean; - if (title.ToLower().Contains("russian")) + if (lowerTitle.Contains("russian")) return LanguageType.Russian; - if (title.ToLower().Contains("polish")) + if (lowerTitle.Contains("polish")) return LanguageType.Polish; - if (title.ToLower().Contains("vietnamese")) + if (lowerTitle.Contains("vietnamese")) return LanguageType.Vietnamese; - if (title.ToLower().Contains("swedish")) + if (lowerTitle.Contains("swedish")) return LanguageType.Swedish; - if (title.ToLower().Contains("norwegian")) + if (lowerTitle.Contains("norwegian")) return LanguageType.Norwegian; - if (title.ToLower().Contains("finnish")) + if (lowerTitle.Contains("finnish")) return LanguageType.Finnish; - if (title.ToLower().Contains("turkish")) + if (lowerTitle.Contains("turkish")) return LanguageType.Turkish; - if (title.ToLower().Contains("portuguese")) + if (lowerTitle.Contains("portuguese")) return LanguageType.Portuguese; return LanguageType.English; diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index 991a90d15..4ca37b3d7 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -45,7 +45,7 @@ namespace NzbDrone.Core.Providers public virtual void Delete(int episodeFileId) { - _database.Delete(episodeFileId); + _database.Delete(episodeFileId); } public virtual bool Exists(string path) diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 1bde249df..8d0ec18fd 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -42,22 +42,21 @@ namespace NzbDrone.Core.Providers return series; } - public virtual IList GetAllSeriesWithEpisodeCount(bool ignoreSpecials) + public virtual IList GetAllSeriesWithEpisodeCount() { - var seasonNumber = 0; - - if (!ignoreSpecials) - seasonNumber = -1; - var series = _database - .Fetch(@"SELECT Series.*, SUM(CASE WHEN Ignored = 0 THEN 1 ELSE 0 END) AS EpisodeCount, -SUM(CASE WHEN Ignored = 0 AND EpisodeFileId > 0 THEN 1 ELSE 0 END) as EpisodeFileCount, -COUNT (DISTINCT(CASE WHEN SeasonNumber = 0 THEN null ELSE SeasonNumber END)) as SeasonCount, -QualityProfiles.* -FROM Series -INNER JOIN QualityProfiles ON Series.QualityProfileId = QualityProfiles.QualityProfileId -JOIN Episodes ON Series.SeriesId = Episodes.SeriesId -GROUP BY seriesId"); + .Fetch(@"SELECT Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek,Series.AirTimes, + Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder, + SUM(CASE WHEN Ignored = 0 THEN 1 ELSE 0 END) AS EpisodeCount, + SUM(CASE WHEN Episodes.Ignored = 0 AND Episodes.EpisodeFileId > 0 THEN 1 ELSE 0 END) as EpisodeFileCount, + MAX(Episodes.SeasonNumber) as SeasonCount, + QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed + FROM Series + INNER JOIN QualityProfiles ON Series.QualityProfileId = QualityProfiles.QualityProfileId + LEFT JOIN Episodes ON Series.SeriesId = Episodes.SeriesId + GROUP BY Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek,Series.AirTimes, + Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder, + QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed"); return series; } diff --git a/NzbDrone.Web/Controllers/AddSeriesController.cs b/NzbDrone.Web/Controllers/AddSeriesController.cs index 229c909e3..cfd14816d 100644 --- a/NzbDrone.Web/Controllers/AddSeriesController.cs +++ b/NzbDrone.Web/Controllers/AddSeriesController.cs @@ -126,10 +126,10 @@ namespace NzbDrone.Web.Controllers { try { - var path = - rootPath.Replace('|', Path.DirectorySeparatorChar).Replace('^', Path.VolumeSeparatorChar).Replace( - '`', '\'') + - Path.DirectorySeparatorChar + MediaFileProvider.CleanFilename(seriesName); + var path = Path.Combine(rootPath.Replace('|', Path.DirectorySeparatorChar) + .Replace('^', Path.VolumeSeparatorChar) + .Replace('`', '\''), + MediaFileProvider.CleanFilename(seriesName)); //Create the folder for the new series and then Add it _diskProvider.CreateDirectory(path); diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index 725eb7b88..1dbb80a99 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -81,7 +81,7 @@ namespace NzbDrone.Web.Controllers [GridAction] public ActionResult _AjaxSeriesGrid() { - var series = GetSeriesModels(_seriesProvider.GetAllSeriesWithEpisodeCount(true).ToList()); + var series = GetSeriesModels(_seriesProvider.GetAllSeriesWithEpisodeCount()); return View(new GridModel(series)); } @@ -97,7 +97,7 @@ namespace NzbDrone.Web.Controllers _seriesProvider.UpdateSeries(oldSeries); - var series = GetSeriesModels(_seriesProvider.GetAllSeries().ToList()); + var series = GetSeriesModels(_seriesProvider.GetAllSeriesWithEpisodeCount()); return View(new GridModel(series)); } @@ -196,7 +196,7 @@ namespace NzbDrone.Web.Controllers return RedirectToAction("Details", new { seriesId }); } - private List GetSeriesModels(List seriesInDb) + private List GetSeriesModels(IList seriesInDb) { var series = seriesInDb.Select(s => new SeriesModel { @@ -237,6 +237,11 @@ namespace NzbDrone.Web.Controllers episodeQuality = e.EpisodeFile.Quality.ToString(); } + var airDate = String.Empty; + + if (e.AirDate != null) + airDate = e.AirDate.Value.ToShortDateString(); + episodes.Add(new EpisodeModel { EpisodeId = e.EpisodeId, @@ -244,7 +249,7 @@ namespace NzbDrone.Web.Controllers SeasonNumber = e.SeasonNumber, Title = e.Title, Overview = e.Overview, - AirDate = e.AirDate.Value, + AirDate = airDate, Path = episodePath, EpisodeFileId = episodeFileId, Status = e.Status.ToString(), diff --git a/NzbDrone.Web/Models/EpisodeModel.cs b/NzbDrone.Web/Models/EpisodeModel.cs index 5da326f1b..2384031d9 100644 --- a/NzbDrone.Web/Models/EpisodeModel.cs +++ b/NzbDrone.Web/Models/EpisodeModel.cs @@ -13,8 +13,7 @@ namespace NzbDrone.Web.Models public string Overview { get; set; } public string Path { get; set; } public String Status { get; set; } - public DateTime AirDate { get; set; } - + public string AirDate { get; set; } public String Quality { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Views/Series/Details.cshtml b/NzbDrone.Web/Views/Series/Details.cshtml index a2fb68d53..c143650f2 100644 --- a/NzbDrone.Web/Views/Series/Details.cshtml +++ b/NzbDrone.Web/Views/Series/Details.cshtml @@ -61,7 +61,7 @@ columns.Bound(c => c.EpisodeNumber).Width(0).Title("Episode"); columns.Bound(c => c.Title).Title("Title"); - columns.Bound(c => c.AirDate).Format("{0:d}").Width(0); + columns.Bound(c => c.AirDate).Width(0); columns.Bound(c => c.Quality).Width(0); columns.Bound(c => c.Status).Width(0); columns.Bound(o => o.EpisodeId).Title("") @@ -111,7 +111,7 @@ columns.Bound(c => c.EpisodeNumber).Width(10).Title("Episode"); columns.Bound(c => c.Title).Title("Title").Width(10000); - columns.Bound(c => c.AirDate).Format("{0:d}").Width(10); + columns.Bound(c => c.AirDate).Width(10); columns.Bound(c => c.Quality).Width(10); columns.Bound(c => c.Status).Width(10); }) diff --git a/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml b/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml index 7b997f37e..311b3bc96 100644 --- a/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml +++ b/NzbDrone.Web/Views/Settings/EpisodeSorting.cshtml @@ -142,7 +142,7 @@