mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
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)
This commit is contained in:
parent
19fc3bad6c
commit
4c1e6e14aa
@ -35,6 +35,7 @@ public interface IDiskProvider
|
||||
bool IsFileLocked(FileInfo file);
|
||||
string GetPathRoot(string path);
|
||||
void SetPermissions(string filename, string account, FileSystemRights Rights, AccessControlType ControlType);
|
||||
bool IsParent(string parentfolder, string subfolder);
|
||||
}
|
||||
|
||||
public class DiskProvider : IDiskProvider
|
||||
@ -383,5 +384,26 @@ public void SetPermissions(string filename, string account, FileSystemRights rig
|
||||
directorySecurity.AddAccessRule(accessRule);
|
||||
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 @@ public void should_return_true_if_file_names_are_different()
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void should_return_false_if_exact_path_exists_in_db()
|
||||
{
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
|
@ -14,27 +14,47 @@ namespace NzbDrone.Core.Test.MediaFileTests
|
||||
{
|
||||
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!";
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Setup(s => s.GetSeries(It.IsAny<Int32>()))
|
||||
.Returns(Builder<Series>.CreateNew().Build());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(e => e.FileExists(It.Is<String>(c => c != DeletedPath)))
|
||||
.Returns(true);
|
||||
.Setup(e => e.FileExists(It.Is<String>(c => c != DeletedPath)))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
|
||||
.Returns(new List<Episode> { new Episode() });
|
||||
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
|
||||
.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]
|
||||
@ -43,7 +63,7 @@ public void should_skip_files_that_exist_in_disk()
|
||||
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(10)
|
||||
.Build();
|
||||
|
||||
GiveEpisodeFiles(episodeFiles);
|
||||
GivenEpisodeFiles(episodeFiles);
|
||||
|
||||
Subject.Execute(new CleanMediaFileDb(0));
|
||||
|
||||
@ -58,12 +78,11 @@ public void should_delete_none_existing_files()
|
||||
.With(c => c.Path = DeletedPath)
|
||||
.Build();
|
||||
|
||||
GiveEpisodeFiles(episodeFiles);
|
||||
GivenEpisodeFiles(episodeFiles);
|
||||
|
||||
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]
|
||||
@ -74,19 +93,28 @@ public void should_delete_files_that_dont_belong_to_any_episodes()
|
||||
.With(c => c.Path = "ExistingPath")
|
||||
.Build();
|
||||
|
||||
GiveEpisodeFiles(episodeFiles);
|
||||
GivenEpisodeFiles(episodeFiles);
|
||||
GivenFilesAreNotAttachedToEpisode();
|
||||
|
||||
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>()
|
||||
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
|
||||
.Returns(new List<Episode>());
|
||||
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(10)
|
||||
.Random(10)
|
||||
.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 @@ public void should_delete_episode_file_from_database()
|
||||
|
||||
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 @@ public void should_set_EpisodeFileId_to_zero()
|
||||
{
|
||||
GivenSingleEpisodeFile();
|
||||
|
||||
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile));
|
||||
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
|
||||
|
||||
Mocker.GetMock<IEpisodeRepository>()
|
||||
.Verify(v => v.Update(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Once());
|
||||
@ -69,14 +69,14 @@ public void should_update_each_episode_for_file()
|
||||
{
|
||||
GivenMultiEpisodeFile();
|
||||
|
||||
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile));
|
||||
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
|
||||
|
||||
Mocker.GetMock<IEpisodeRepository>()
|
||||
.Verify(v => v.Update(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[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();
|
||||
|
||||
@ -84,7 +84,7 @@ public void should_set_monitored_to_false_if_autoUnmonitor_is_true()
|
||||
.SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes)
|
||||
.Returns(true);
|
||||
|
||||
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile));
|
||||
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, false));
|
||||
|
||||
Mocker.GetMock<IEpisodeRepository>()
|
||||
.Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == false)), Times.Once());
|
||||
@ -99,7 +99,22 @@ public void should_leave_monitored_to_true_if_autoUnmonitor_is_false()
|
||||
.SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes)
|
||||
.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>()
|
||||
.Verify(v => v.Update(It.Is<Episode>(e => e.Monitored == true)), Times.Once());
|
||||
|
@ -20,11 +20,11 @@ public NotExistingFileSpecification(IMediaFileService mediaFileService, Logger l
|
||||
|
||||
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
{
|
||||
// if (_mediaFileService.Exists(localEpisode.Path))
|
||||
// {
|
||||
// _logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path);
|
||||
// return false;
|
||||
// }
|
||||
if (_mediaFileService.Exists(localEpisode.Path))
|
||||
{
|
||||
_logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
var existingFiles = localEpisode.Episodes.Where(e => e.EpisodeFileId > 0).Select(e => e.EpisodeFile.Value);
|
||||
|
||||
@ -36,9 +36,6 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
_logger.Trace("File is a match for an existing episode file: {0}", localEpisode.Path);
|
||||
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;
|
||||
|
@ -1,14 +1,17 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using System;
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public class EpisodeFileDeletedEvent : IEvent
|
||||
{
|
||||
public EpisodeFile EpisodeFile { get; private set; }
|
||||
public Boolean ForUpgrade { get; private set; }
|
||||
|
||||
public EpisodeFileDeletedEvent(EpisodeFile episodeFile)
|
||||
public EpisodeFileDeletedEvent(EpisodeFile episodeFile, Boolean forUpgrade)
|
||||
{
|
||||
EpisodeFile = episodeFile;
|
||||
ForUpgrade = forUpgrade;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ public interface IMediaFileService
|
||||
{
|
||||
EpisodeFile Add(EpisodeFile episodeFile);
|
||||
void Update(EpisodeFile episodeFile);
|
||||
void Delete(EpisodeFile episodeFile);
|
||||
void Delete(EpisodeFile episodeFile, bool forUpgrade = false);
|
||||
bool Exists(string path);
|
||||
EpisodeFile GetFileByPath(string path);
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
@ -46,10 +46,11 @@ public void Update(EpisodeFile episodeFile)
|
||||
_mediaFileRepository.Update(episodeFile);
|
||||
}
|
||||
|
||||
public void Delete(EpisodeFile episodeFile)
|
||||
public void Delete(EpisodeFile episodeFile, bool forUpgrade = false)
|
||||
{
|
||||
_mediaFileRepository.Delete(episodeFile);
|
||||
_messageAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile));
|
||||
|
||||
_messageAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, forUpgrade));
|
||||
}
|
||||
|
||||
public bool Exists(string path)
|
||||
|
@ -4,6 +4,7 @@
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
@ -14,19 +15,29 @@ public class MediaFileTableCleanupService : IExecute<CleanMediaFileDb>
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IParsingService _parsingService;
|
||||
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;
|
||||
_diskProvider = diskProvider;
|
||||
_episodeService = episodeService;
|
||||
_seriesService = seriesService;
|
||||
_parsingService = parsingService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Execute(CleanMediaFileDb message)
|
||||
{
|
||||
var seriesFile = _mediaFileService.GetFilesBySeries(message.SeriesId);
|
||||
var series = _seriesService.GetSeries(message.SeriesId);
|
||||
|
||||
foreach (var episodeFile in seriesFile)
|
||||
{
|
||||
@ -34,14 +45,34 @@ public void Execute(CleanMediaFileDb message)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
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)
|
||||
|
@ -40,7 +40,7 @@ public EpisodeFile UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode loca
|
||||
_logger.Trace("Removing existing episode file: {0}", file);
|
||||
|
||||
_recycleBinProvider.DeleteFile(file.Path);
|
||||
_mediaFileService.Delete(file);
|
||||
_mediaFileService.Delete(file, true);
|
||||
}
|
||||
|
||||
_logger.Trace("Moving episode file: {0}", episodeFile);
|
||||
|
@ -183,7 +183,7 @@ public void Handle(EpisodeFileDeletedEvent message)
|
||||
_logger.Trace("Detaching episode {0} from file.", episode.Id);
|
||||
episode.EpisodeFileId = 0;
|
||||
|
||||
if (_configService.AutoUnmonitorPreviouslyDownloadedEpisodes)
|
||||
if (!message.ForUpgrade && _configService.AutoUnmonitorPreviouslyDownloadedEpisodes)
|
||||
{
|
||||
episode.Monitored = false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user