EpisodeFile cleanup and deletion fixes

Upgraded episodes will no longer be auto unmonitored
EpsiodeFiles will be removed from db if parsing rules have changed
EpisodeFiles will be removed from db if they are not in their series' folder (or subfolder)
pull/6/head
Mark McDowall 11 years ago
parent 19fc3bad6c
commit 4c1e6e14aa

@ -35,6 +35,7 @@ namespace NzbDrone.Common
bool IsFileLocked(FileInfo file); bool IsFileLocked(FileInfo file);
string GetPathRoot(string path); string GetPathRoot(string path);
void SetPermissions(string filename, string account, FileSystemRights Rights, AccessControlType ControlType); void SetPermissions(string filename, string account, FileSystemRights Rights, AccessControlType ControlType);
bool IsParent(string parentfolder, string subfolder);
} }
public class DiskProvider : IDiskProvider public class DiskProvider : IDiskProvider
@ -383,5 +384,26 @@ namespace NzbDrone.Common
directorySecurity.AddAccessRule(accessRule); directorySecurity.AddAccessRule(accessRule);
directoryInfo.SetAccessControl(directorySecurity); directoryInfo.SetAccessControl(directorySecurity);
} }
public bool IsParent(string parent, string subfolder)
{
parent = parent.TrimEnd(Path.DirectorySeparatorChar);
subfolder = subfolder.TrimEnd(Path.DirectorySeparatorChar);
var diParent = new DirectoryInfo(parent);
var diSubfolder = new DirectoryInfo(subfolder);
while (diSubfolder.Parent != null)
{
if (diSubfolder.Parent.FullName == diParent.FullName)
{
return true;
}
diSubfolder = diSubfolder.Parent;
}
return false;
}
} }
} }

@ -112,7 +112,6 @@ namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
} }
[Test] [Test]
[Explicit]
public void should_return_false_if_exact_path_exists_in_db() public void should_return_false_if_exact_path_exists_in_db()
{ {
Mocker.GetMock<IMediaFileService>() Mocker.GetMock<IMediaFileService>()

@ -14,20 +14,15 @@ namespace NzbDrone.Core.Test.MediaFileTests
{ {
public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService> public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService>
{ {
private void GiveEpisodeFiles(IEnumerable<EpisodeFile> episodeFiles)
{
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.GetFilesBySeries(It.IsAny<int>()))
.Returns(episodeFiles.ToList());
}
private const string DeletedPath = "ANY FILE WITH THIS PATH IS CONSIDERED DELETED!"; private const string DeletedPath = "ANY FILE WITH THIS PATH IS CONSIDERED DELETED!";
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
Mocker.GetMock<ISeriesService>()
.Setup(s => s.GetSeries(It.IsAny<Int32>()))
.Returns(Builder<Series>.CreateNew().Build());
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(e => e.FileExists(It.Is<String>(c => c != DeletedPath))) .Setup(e => e.FileExists(It.Is<String>(c => c != DeletedPath)))
.Returns(true); .Returns(true);
@ -35,6 +30,31 @@ namespace NzbDrone.Core.Test.MediaFileTests
Mocker.GetMock<IEpisodeService>() Mocker.GetMock<IEpisodeService>()
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>())) .Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
.Returns(new List<Episode> {new Episode()}); .Returns(new List<Episode> {new Episode()});
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.IsParent(It.IsAny<String>(), It.IsAny<String>()))
.Returns(true);
}
private void GivenEpisodeFiles(IEnumerable<EpisodeFile> episodeFiles)
{
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.GetFilesBySeries(It.IsAny<int>()))
.Returns(episodeFiles.ToList());
}
private void GivenFilesAreNotAttachedToEpisode()
{
Mocker.GetMock<IEpisodeService>()
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
.Returns(new List<Episode>());
}
private void GivenFileIsNotInSeriesFolder()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.IsParent(It.IsAny<String>(), It.IsAny<String>()))
.Returns(false);
} }
[Test] [Test]
@ -43,7 +63,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(10) var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(10)
.Build(); .Build();
GiveEpisodeFiles(episodeFiles); GivenEpisodeFiles(episodeFiles);
Subject.Execute(new CleanMediaFileDb(0)); Subject.Execute(new CleanMediaFileDb(0));
@ -58,12 +78,11 @@ namespace NzbDrone.Core.Test.MediaFileTests
.With(c => c.Path = DeletedPath) .With(c => c.Path = DeletedPath)
.Build(); .Build();
GiveEpisodeFiles(episodeFiles); GivenEpisodeFiles(episodeFiles);
Subject.Execute(new CleanMediaFileDb(0)); Subject.Execute(new CleanMediaFileDb(0));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<EpisodeFile>(e => e.Path == DeletedPath)), Times.Exactly(2)); Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<EpisodeFile>(e => e.Path == DeletedPath), false), Times.Exactly(2));
} }
[Test] [Test]
@ -74,19 +93,28 @@ namespace NzbDrone.Core.Test.MediaFileTests
.With(c => c.Path = "ExistingPath") .With(c => c.Path = "ExistingPath")
.Build(); .Build();
GiveEpisodeFiles(episodeFiles); GivenEpisodeFiles(episodeFiles);
GivenFilesAreNotAttachedToEpisode(); GivenFilesAreNotAttachedToEpisode();
Subject.Execute(new CleanMediaFileDb(0)); Subject.Execute(new CleanMediaFileDb(0));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>()), Times.Exactly(10)); Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>(), false), Times.Exactly(10));
} }
private void GivenFilesAreNotAttachedToEpisode() [Test]
public void should_delete_files_that_do_not_belong_to_the_series_path()
{ {
Mocker.GetMock<IEpisodeService>() var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(10)
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>())) .Random(10)
.Returns(new List<Episode>()); .With(c => c.Path = "ExistingPath")
.Build();
GivenEpisodeFiles(episodeFiles);
GivenFileIsNotInSeriesFolder();
Subject.Execute(new CleanMediaFileDb(0));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>(), false), Times.Exactly(10));
} }
} }
} }

@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
Subject.UpgradeEpisodeFile(_episodeFile, _localEpisode); Subject.UpgradeEpisodeFile(_episodeFile, _localEpisode);
Mocker.GetMock<IMediaFileService>().Verify(v => v.Delete(It.IsAny<EpisodeFile>()), Times.Once()); Mocker.GetMock<IMediaFileService>().Verify(v => v.Delete(It.IsAny<EpisodeFile>(), true), Times.Once());
} }
} }
} }

@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests
{ {
GivenSingleEpisodeFile(); GivenSingleEpisodeFile();
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile)); Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
Mocker.GetMock<IEpisodeRepository>() Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.Update(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Once()); .Verify(v => v.Update(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Once());
@ -69,14 +69,14 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests
{ {
GivenMultiEpisodeFile(); GivenMultiEpisodeFile();
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile)); Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
Mocker.GetMock<IEpisodeRepository>() Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.Update(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Exactly(2)); .Verify(v => v.Update(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Exactly(2));
} }
[Test] [Test]
public void should_set_monitored_to_false_if_autoUnmonitor_is_true() public void should_set_monitored_to_false_if_autoUnmonitor_is_true_and_is_not_for_an_upgrade()
{ {
GivenSingleEpisodeFile(); GivenSingleEpisodeFile();
@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests
.SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes) .SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes)
.Returns(true); .Returns(true);
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile)); Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
Mocker.GetMock<IEpisodeRepository>() Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == false)), Times.Once()); .Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == false)), Times.Once());
@ -99,7 +99,22 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeProviderTests
.SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes) .SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes)
.Returns(false); .Returns(false);
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile)); Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == true)), Times.Once());
}
[Test]
public void should_leave_monitored_to_true_if_autoUnmonitor_is_true_and_is_for_an_upgrade()
{
GivenSingleEpisodeFile();
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes)
.Returns(true);
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, true));
Mocker.GetMock<IEpisodeRepository>() Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == true)), Times.Once()); .Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == true)), Times.Once());

@ -20,11 +20,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
public bool IsSatisfiedBy(LocalEpisode localEpisode) public bool IsSatisfiedBy(LocalEpisode localEpisode)
{ {
// if (_mediaFileService.Exists(localEpisode.Path)) if (_mediaFileService.Exists(localEpisode.Path))
// { {
// _logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path); _logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path);
// return false; return false;
// } }
var existingFiles = localEpisode.Episodes.Where(e => e.EpisodeFileId > 0).Select(e => e.EpisodeFile.Value); var existingFiles = localEpisode.Episodes.Where(e => e.EpisodeFileId > 0).Select(e => e.EpisodeFile.Value);
@ -36,9 +36,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path); _logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path);
return false; return false;
} }
_logger.Trace("Existing filename: {0} size: {1}", Path.GetFileName(existingFile.Path), existingFile.Size);
_logger.Trace("New filename: {0} size: {1}", Path.GetFileName(localEpisode.Path), localEpisode.Size);
} }
return true; return true;

@ -1,14 +1,17 @@
using NzbDrone.Common.Messaging; using System;
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.MediaFiles.Events namespace NzbDrone.Core.MediaFiles.Events
{ {
public class EpisodeFileDeletedEvent : IEvent public class EpisodeFileDeletedEvent : IEvent
{ {
public EpisodeFile EpisodeFile { get; private set; } public EpisodeFile EpisodeFile { get; private set; }
public Boolean ForUpgrade { get; private set; }
public EpisodeFileDeletedEvent(EpisodeFile episodeFile) public EpisodeFileDeletedEvent(EpisodeFile episodeFile, Boolean forUpgrade)
{ {
EpisodeFile = episodeFile; EpisodeFile = episodeFile;
ForUpgrade = forUpgrade;
} }
} }
} }

@ -13,7 +13,7 @@ namespace NzbDrone.Core.MediaFiles
{ {
EpisodeFile Add(EpisodeFile episodeFile); EpisodeFile Add(EpisodeFile episodeFile);
void Update(EpisodeFile episodeFile); void Update(EpisodeFile episodeFile);
void Delete(EpisodeFile episodeFile); void Delete(EpisodeFile episodeFile, bool forUpgrade = false);
bool Exists(string path); bool Exists(string path);
EpisodeFile GetFileByPath(string path); EpisodeFile GetFileByPath(string path);
List<EpisodeFile> GetFilesBySeries(int seriesId); List<EpisodeFile> GetFilesBySeries(int seriesId);
@ -46,10 +46,11 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileRepository.Update(episodeFile); _mediaFileRepository.Update(episodeFile);
} }
public void Delete(EpisodeFile episodeFile) public void Delete(EpisodeFile episodeFile, bool forUpgrade = false)
{ {
_mediaFileRepository.Delete(episodeFile); _mediaFileRepository.Delete(episodeFile);
_messageAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile));
_messageAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, forUpgrade));
} }
public bool Exists(string path) public bool Exists(string path)

@ -4,6 +4,7 @@ using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
@ -14,19 +15,29 @@ namespace NzbDrone.Core.MediaFiles
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly ISeriesService _seriesService;
private readonly IParsingService _parsingService;
private readonly Logger _logger; private readonly Logger _logger;
public MediaFileTableCleanupService(IMediaFileService mediaFileService, IDiskProvider diskProvider, IEpisodeService episodeService, Logger logger) public MediaFileTableCleanupService(IMediaFileService mediaFileService,
IDiskProvider diskProvider,
IEpisodeService episodeService,
ISeriesService seriesService,
IParsingService parsingService,
Logger logger)
{ {
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_episodeService = episodeService; _episodeService = episodeService;
_seriesService = seriesService;
_parsingService = parsingService;
_logger = logger; _logger = logger;
} }
public void Execute(CleanMediaFileDb message) public void Execute(CleanMediaFileDb message)
{ {
var seriesFile = _mediaFileService.GetFilesBySeries(message.SeriesId); var seriesFile = _mediaFileService.GetFilesBySeries(message.SeriesId);
var series = _seriesService.GetSeries(message.SeriesId);
foreach (var episodeFile in seriesFile) foreach (var episodeFile in seriesFile)
{ {
@ -34,14 +45,34 @@ namespace NzbDrone.Core.MediaFiles
{ {
if (!_diskProvider.FileExists(episodeFile.Path)) if (!_diskProvider.FileExists(episodeFile.Path))
{ {
_logger.Trace("File [{0}] no longer exists on disk. removing from db", episodeFile.Path); _logger.Trace("File [{0}] no longer exists on disk, removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile); _mediaFileService.Delete(episodeFile);
continue;
} }
if (!_episodeService.GetEpisodesByFileId(episodeFile.Id).Any()) if (!_diskProvider.IsParent(series.Path, episodeFile.Path))
{ {
_logger.Trace("File [{0}] is not assigned to any episodes. removing from db", episodeFile.Path); _logger.Trace("File [{0}] does not belong to this series, removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile); _mediaFileService.Delete(episodeFile);
continue;
}
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
if (!episodes.Any())
{
_logger.Trace("File [{0}] is not assigned to any episodes, removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile);
continue;
}
var localEpsiode = _parsingService.GetEpisodes(episodeFile.Path, series);
if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count)
{
_logger.Trace("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile);
continue;
} }
} }
catch (Exception ex) catch (Exception ex)

@ -40,7 +40,7 @@ namespace NzbDrone.Core.MediaFiles
_logger.Trace("Removing existing episode file: {0}", file); _logger.Trace("Removing existing episode file: {0}", file);
_recycleBinProvider.DeleteFile(file.Path); _recycleBinProvider.DeleteFile(file.Path);
_mediaFileService.Delete(file); _mediaFileService.Delete(file, true);
} }
_logger.Trace("Moving episode file: {0}", episodeFile); _logger.Trace("Moving episode file: {0}", episodeFile);

@ -183,7 +183,7 @@ namespace NzbDrone.Core.Tv
_logger.Trace("Detaching episode {0} from file.", episode.Id); _logger.Trace("Detaching episode {0} from file.", episode.Id);
episode.EpisodeFileId = 0; episode.EpisodeFileId = 0;
if (_configService.AutoUnmonitorPreviouslyDownloadedEpisodes) if (!message.ForUpgrade && _configService.AutoUnmonitorPreviouslyDownloadedEpisodes)
{ {
episode.Monitored = false; episode.Monitored = false;
} }

Loading…
Cancel
Save