diff --git a/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/HandleEpisodeFileDeletedFixture.cs b/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/HandleEpisodeFileDeletedFixture.cs index 6f90c9716..fe8e521b3 100644 --- a/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/HandleEpisodeFileDeletedFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/EpisodeServiceTests/HandleEpisodeFileDeletedFixture.cs @@ -1,9 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using Moq; using NUnit.Framework; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Test.Framework; @@ -14,14 +16,20 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests [TestFixture] public class HandleEpisodeFileDeletedFixture : CoreTest { + private Series _series; private EpisodeFile _episodeFile; private List _episodes; [SetUp] public void Setup() { + _series = Builder + .CreateNew() + .Build(); + _episodeFile = Builder .CreateNew() + .With(e => e.SeriesId = _series.Id) .Build(); } @@ -30,6 +38,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests _episodes = Builder .CreateListOfSize(1) .All() + .With(e => e.SeriesId = _series.Id) .With(e => e.Monitored = true) .Build() .ToList(); @@ -44,6 +53,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests _episodes = Builder .CreateListOfSize(2) .All() + .With(e => e.SeriesId = _series.Id) .With(e => e.Monitored = true) .Build() .ToList(); @@ -85,9 +95,31 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests .Returns(true); Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, DeleteMediaFileReason.MissingFromDisk)); + Subject.HandleAsync(new SeriesScannedEvent(_series, new List())); Mocker.GetMock() - .Verify(v => v.ClearFileId(It.IsAny(), true), Times.Once()); + .Verify(v => v.SetMonitored(It.IsAny>(), false), Times.Once()); + } + + [Test] + public void should_leave_monitored_if_autoUnmonitor_is_true_and_missing_episode_is_replaced() + { + GivenSingleEpisodeFile(); + + var newEpisodeFile = _episodeFile.JsonClone(); + newEpisodeFile.Id = 123; + newEpisodeFile.Episodes = new LazyLoaded>(_episodes); + + Mocker.GetMock() + .SetupGet(s => s.AutoUnmonitorPreviouslyDownloadedEpisodes) + .Returns(true); + + Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, DeleteMediaFileReason.MissingFromDisk)); + Subject.Handle(new EpisodeFileAddedEvent(newEpisodeFile)); + Subject.HandleAsync(new SeriesScannedEvent(_series, new List())); + + Mocker.GetMock() + .Verify(v => v.SetMonitored(It.IsAny>(), false), Times.Never()); } [Test] diff --git a/src/NzbDrone.Core/Tv/EpisodeService.cs b/src/NzbDrone.Core/Tv/EpisodeService.cs index 241037799..20dad0582 100644 --- a/src/NzbDrone.Core/Tv/EpisodeService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common.Cache; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; @@ -42,16 +43,19 @@ namespace NzbDrone.Core.Tv public class EpisodeService : IEpisodeService, IHandle, IHandle, - IHandleAsync + IHandleAsync, + IHandleAsync { private readonly IEpisodeRepository _episodeRepository; private readonly IConfigService _configService; + private readonly ICached> _cache; private readonly Logger _logger; - public EpisodeService(IEpisodeRepository episodeRepository, IConfigService configService, Logger logger) + public EpisodeService(IEpisodeRepository episodeRepository, IConfigService configService, ICacheManager cacheManager, Logger logger) { _episodeRepository = episodeRepository; _configService = configService; + _cache = cacheManager.GetCache>(GetType()); _logger = logger; } @@ -215,34 +219,6 @@ namespace NzbDrone.Core.Tv _episodeRepository.DeleteMany(episodes); } - public void HandleAsync(SeriesDeletedEvent message) - { - var episodes = _episodeRepository.GetEpisodesBySeriesIds(message.Series.Select(s => s.Id).ToList()); - _episodeRepository.DeleteMany(episodes); - } - - public void Handle(EpisodeFileDeletedEvent message) - { - foreach (var episode in GetEpisodesByFileId(message.EpisodeFile.Id)) - { - _logger.Debug("Detaching episode {0} from file.", episode.Id); - - var unmonitorForReason = message.Reason != DeleteMediaFileReason.Upgrade && - message.Reason != DeleteMediaFileReason.ManualOverride; - - _episodeRepository.ClearFileId(episode, unmonitorForReason && _configService.AutoUnmonitorPreviouslyDownloadedEpisodes); - } - } - - public void Handle(EpisodeFileAddedEvent message) - { - foreach (var episode in message.EpisodeFile.Episodes.Value) - { - _episodeRepository.SetFileId(episode, message.EpisodeFile.Id); - _logger.Debug("Linking [{0}] > [{1}]", message.EpisodeFile.RelativePath, episode); - } - } - private Episode FindOneByAirDate(int seriesId, string date, int? part) { var episodes = _episodeRepository.Find(seriesId, date); @@ -277,5 +253,73 @@ namespace NzbDrone.Core.Tv throw new InvalidOperationException($"Multiple episodes with the same air date found. Date: {date}"); } + + public void Handle(EpisodeFileDeletedEvent message) + { + foreach (var episode in GetEpisodesByFileId(message.EpisodeFile.Id)) + { + _logger.Debug("Detaching episode {0} from file.", episode.Id); + + var unmonitorEpisodes = _configService.AutoUnmonitorPreviouslyDownloadedEpisodes; + + var unmonitorForReason = message.Reason != DeleteMediaFileReason.Upgrade && + message.Reason != DeleteMediaFileReason.ManualOverride && + message.Reason != DeleteMediaFileReason.MissingFromDisk; + + // If episode is being unlinked because it's missing from disk store it for + if (message.Reason == DeleteMediaFileReason.MissingFromDisk && unmonitorEpisodes) + { + lock (_cache) + { + var ids = _cache.Get(episode.SeriesId.ToString(), () => new HashSet()); + + ids.Add(episode.Id); + } + } + + _episodeRepository.ClearFileId(episode, unmonitorForReason && unmonitorEpisodes); + } + } + + public void Handle(EpisodeFileAddedEvent message) + { + foreach (var episode in message.EpisodeFile.Episodes.Value) + { + _episodeRepository.SetFileId(episode, message.EpisodeFile.Id); + + lock (_cache) + { + var ids = _cache.Find(episode.SeriesId.ToString()); + + if (ids?.Contains(episode.Id) == true) + { + ids.Remove(episode.Id); + } + } + + _logger.Debug("Linking [{0}] > [{1}]", message.EpisodeFile.RelativePath, episode); + } + } + + public void HandleAsync(SeriesDeletedEvent message) + { + var episodes = _episodeRepository.GetEpisodesBySeriesIds(message.Series.Select(s => s.Id).ToList()); + _episodeRepository.DeleteMany(episodes); + } + + public void HandleAsync(SeriesScannedEvent message) + { + lock (_cache) + { + var ids = _cache.Find(message.Series.Id.ToString()); + + if (ids?.Any() == true) + { + _episodeRepository.SetMonitored(ids, false); + } + + _cache.Remove(message.Series.Id.ToString()); + } + } } }