diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index 0ef1a9ffa..9277c8ac7 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Test var fakeSeries = Builder.CreateNew().Build(); var fakeEpisodes = Builder.CreateListOfSize(5) - .WhereAll().Have(e => e.SeriesId = 1).Build(); + .WhereAll().Have(e => e.SeriesId = 1).Have(e => e.EpisodeFileId = 0).Build(); db.InsertMany(fakeEpisodes); @@ -57,8 +57,8 @@ namespace NzbDrone.Core.Test var fakeEpisodes = Builder.CreateNew() .With(e => e.SeriesId = fakeSeries.SeriesId) .With(e => e.EpisodeNumber = 1) - .And(e => e.SeasonNumber = 2).Build(); - + .And(e => e.SeasonNumber = 2) + .With(e => e.EpisodeFileId = 0).Build(); db.Insert(fakeEpisodes); diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index f73608a1e..a02b6c1bc 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -74,5 +74,57 @@ namespace NzbDrone.Core.Test } + [Test] + [TestCase("Law & Order: Criminal Intent - S10E07 - Icarus [HDTV]", "Law & Order- Criminal Intent - S10E07 - Icarus [HDTV]")] + public void CleanFileName(string name, string expectedName) + { + //Act + var result = MediaFileProvider.CleanFilename(name); + + //Assert + Assert.AreEqual(expectedName, result); + } + + [Test] + public void CleanEpisodesWithNonExistantFiles() + { + //Setup + var episodes = Builder.CreateListOfSize(10).Build(); + + var mocker = new AutoMoqer(); + var database = MockLib.GetEmptyDatabase(true); + mocker.SetConstant(database); + database.InsertMany(episodes); + + //Act + mocker.Resolve().CleanEpisodesWithNonExistantFiles(); + var result = database.Fetch(); + + //Assert + result.Should().HaveSameCount(episodes); + result.Should().OnlyContain(e => e.EpisodeFileId == 0); + } + + [Test] + public void DeleteOrphanedEpisodeFiles() + { + //Setup + var episodeFiles = Builder.CreateListOfSize(10).Build(); + var episodes = Builder.CreateListOfSize(5).Build(); + + var mocker = new AutoMoqer(); + var database = MockLib.GetEmptyDatabase(true); + mocker.SetConstant(database); + database.InsertMany(episodes); + database.InsertMany(episodeFiles); + + //Act + mocker.Resolve().DeleteOrphanedEpisodeFiles(); + var result = database.Fetch(); + + //Assert + result.Should().HaveCount(5); + result.Should().OnlyContain(e => e.EpisodeFileId > 0); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/SeriesProviderTest.cs b/NzbDrone.Core.Test/SeriesProviderTest.cs index bced5c070..8e10dfeb6 100644 Binary files a/NzbDrone.Core.Test/SeriesProviderTest.cs and b/NzbDrone.Core.Test/SeriesProviderTest.cs differ diff --git a/NzbDrone.Core.Test/UpcomingEpisodesProviderTest.cs b/NzbDrone.Core.Test/UpcomingEpisodesProviderTest.cs index fdd982e83..3e862686b 100644 --- a/NzbDrone.Core.Test/UpcomingEpisodesProviderTest.cs +++ b/NzbDrone.Core.Test/UpcomingEpisodesProviderTest.cs @@ -1,7 +1,9 @@ // ReSharper disable RedundantUsingDirective using System; +using System.Collections.Generic; using AutoMoq; using FizzWare.NBuilder; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Providers; using NzbDrone.Core.Repository; @@ -18,6 +20,7 @@ namespace NzbDrone.Core.Test private Episode tomorrow; private Episode twoDays; private Episode sevenDays; + private Series series; [SetUp] public new void Setup() @@ -25,33 +28,41 @@ namespace NzbDrone.Core.Test yesterday = Builder.CreateNew() .With(c => c.AirDate = DateTime.Today.AddDays(-1)) .With(c => c.Title = "Yesterday") + .With(c => c.SeriesId = 1) .Build(); today = Builder.CreateNew() .With(c => c.AirDate = DateTime.Today) .With(c => c.Title = "Today") + .With(c => c.SeriesId = 1) .Build(); tomorrow = Builder.CreateNew() .With(c => c.AirDate = DateTime.Today.AddDays(1)) .With(c => c.Title = "Tomorrow") + .With(c => c.SeriesId = 1) .Build(); twoDays = Builder.CreateNew() .With(c => c.AirDate = DateTime.Today.AddDays(2)) .With(c => c.Title = "Two Days") + .With(c => c.SeriesId = 1) .Build(); sevenDays = Builder.CreateNew() .With(c => c.AirDate = DateTime.Today.AddDays(7)) .With(c => c.Title = "Seven Days") + .With(c => c.SeriesId = 1) .Build(); sevenDays = Builder.CreateNew() .With(c => c.AirDate = DateTime.Today.AddDays(8)) .With(c => c.Title = "Eight Days") + .With(c => c.SeriesId = 1) .Build(); + series = Builder.CreateNew().With(s => s.SeriesId = 1).Build(); + base.Setup(); } @@ -68,6 +79,7 @@ namespace NzbDrone.Core.Test database.Insert(tomorrow); database.Insert(twoDays); database.Insert(sevenDays); + database.Insert(series); //Act var result = mocker.Resolve().Yesterday(); @@ -75,6 +87,8 @@ namespace NzbDrone.Core.Test //Assert Assert.AreEqual(1, result.Count); Assert.AreEqual(yesterday.Title, result[0].Title); + result[0].Series.Should().NotBeNull(); + result[0].Series.SeriesId.Should().NotBe(0); } [Test] @@ -90,6 +104,7 @@ namespace NzbDrone.Core.Test database.Insert(tomorrow); database.Insert(twoDays); database.Insert(sevenDays); + database.Insert(series); //Act var result = mocker.Resolve().Today(); @@ -97,6 +112,8 @@ namespace NzbDrone.Core.Test //Assert Assert.AreEqual(1, result.Count); Assert.AreEqual(today.Title, result[0].Title); + result[0].Series.Should().NotBeNull(); + result[0].Series.SeriesId.Should().NotBe(0); } [Test] @@ -112,6 +129,7 @@ namespace NzbDrone.Core.Test database.Insert(tomorrow); database.Insert(twoDays); database.Insert(sevenDays); + database.Insert(series); //Act var result = mocker.Resolve().Tomorrow(); @@ -119,6 +137,8 @@ namespace NzbDrone.Core.Test //Assert Assert.AreEqual(1, result.Count); Assert.AreEqual(tomorrow.Title, result[0].Title); + result[0].Series.Should().NotBeNull(); + result[0].Series.SeriesId.Should().NotBe(0); } [Test] @@ -134,12 +154,17 @@ namespace NzbDrone.Core.Test database.Insert(tomorrow); database.Insert(twoDays); database.Insert(sevenDays); + database.Insert(series); //Act var result = mocker.Resolve().Week(); //Assert Assert.AreEqual(2, result.Count); + result[0].Series.Should().NotBeNull(); + result[0].Series.SeriesId.Should().NotBe(0); + result[1].Series.Should().NotBeNull(); + result[1].Series.SeriesId.Should().NotBe(0); } } } diff --git a/NzbDrone.Core/Helpers/EpisodeRenameHelper.cs b/NzbDrone.Core/Helpers/EpisodeRenameHelper.cs index d02781b55..f401c221a 100644 --- a/NzbDrone.Core/Helpers/EpisodeRenameHelper.cs +++ b/NzbDrone.Core/Helpers/EpisodeRenameHelper.cs @@ -55,17 +55,5 @@ namespace NzbDrone.Core.Helpers return String.Format("{0} - S{1:00}E{2} - {3}", erm.SeriesName, erm.EpisodeFile.Episodes[0].SeasonNumber, epNumberString, epNameString); } - - public static string CleanFilename(string name) - { - string result = name; - string[] badCharacters = {"\\", "/", "<", ">", "?", "*", ":", "|", "\""}; - string[] goodCharacters = {"+", "+", "{", "}", "!", "@", "-", "#", "`"}; - - for (int i = 0; i < badCharacters.Length; i++) - result = result.Replace(badCharacters[i], goodCharacters[i]); - - return result.Trim(); - } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index bb8f9d977..441348f90 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -201,7 +201,9 @@ namespace NzbDrone.Core.Providers /// list of files to verify public virtual void CleanUp(List files) { - //TODO: remove orphaned files. in files table but not linked to from episode table. + _mediaFileProvider.CleanEpisodesWithNonExistantFiles(); + _mediaFileProvider.DeleteOrphanedEpisodeFiles(); + foreach (var episodeFile in files) { if (!_diskProvider.FileExists(episodeFile.Path)) diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 8bdca33be..edc435c2b 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -38,6 +38,7 @@ namespace NzbDrone.Core.Providers var episode = AttachSeries(_database.Fetch(@"SELECT * FROM Episodes LEFT JOIN EpisodeFiles ON Episodes.EpisodeFileId = EpisodeFiles.EpisodeFileId WHERE EpisodeId = @0", id).Single()); + if (episode.EpisodeFileId == 0) episode.EpisodeFile = null; diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index 732d3a18d..9d56da9fe 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -74,6 +74,26 @@ namespace NzbDrone.Core.Providers return new FileInfo(path); } + public virtual void CleanEpisodesWithNonExistantFiles() + { + _database.Execute(@"UPDATE Episodes SET EpisodeFileId = 0 + WHERE EpisodeFileId IN + (SELECT Episodes.EpisodeFileId FROM Episodes + LEFT OUTER JOIN EpisodeFiles + ON Episodes.EpisodeFileId = EpisodeFiles.EpisodeFileId + WHERE Episodes.EpisodeFileId > 0 AND EpisodeFiles.EpisodeFileId IS null)"); + } + + public virtual void DeleteOrphanedEpisodeFiles() + { + _database.Execute(@"DELETE FROM EpisodeFiles + WHERE EpisodeFileId IN + (SELECT EpisodeFiles.EpisodeFileId FROM EpisodeFiles + LEFT OUTER JOIN Episodes + ON EpisodeFiles.EpisodeFileId = Episodes.EpisodeFileId + WHERE Episodes.EpisodeFileId IS null)"); + } + public virtual string GetNewFilename(IList episodes, string seriesTitle, QualityTypes quality) { var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SeparatorStyle); @@ -129,6 +149,18 @@ namespace NzbDrone.Core.Providers result = result.Replace(' ', '.'); Logger.Debug("New File Name is: {0}", result.Trim()); + return CleanFilename(result.Trim()); + } + + public static string CleanFilename(string name) + { + string result = name; + string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; + string[] goodCharacters = { "+", "+", "{", "}", "!", "@", "-", "#", "`" }; + + for (int i = 0; i < badCharacters.Length; i++) + result = result.Replace(badCharacters[i], goodCharacters[i]); + return result.Trim(); } } diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 9a022a14c..161916806 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using NLog; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; using PetaPoco; using TvdbLib.Data; @@ -17,17 +18,15 @@ namespace NzbDrone.Core.Providers private readonly ConfigProvider _configProvider; private readonly TvDbProvider _tvDbProvider; private readonly IDatabase _database; - private readonly QualityProvider _qualityProvider; private readonly SceneMappingProvider _sceneNameMappingProvider; private static readonly Regex TimeRegex = new Regex(@"^(?