New: Do not automatically unmonitor episodes renamed outside of Sonarr

Closes #6584
This commit is contained in:
Mark McDowall 2024-03-02 17:10:32 -08:00
parent 653963a247
commit fa4c11a943
2 changed files with 108 additions and 32 deletions

View File

@ -1,9 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@ -14,14 +16,20 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
[TestFixture] [TestFixture]
public class HandleEpisodeFileDeletedFixture : CoreTest<EpisodeService> public class HandleEpisodeFileDeletedFixture : CoreTest<EpisodeService>
{ {
private Series _series;
private EpisodeFile _episodeFile; private EpisodeFile _episodeFile;
private List<Episode> _episodes; private List<Episode> _episodes;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
_series = Builder<Series>
.CreateNew()
.Build();
_episodeFile = Builder<EpisodeFile> _episodeFile = Builder<EpisodeFile>
.CreateNew() .CreateNew()
.With(e => e.SeriesId = _series.Id)
.Build(); .Build();
} }
@ -30,6 +38,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
_episodes = Builder<Episode> _episodes = Builder<Episode>
.CreateListOfSize(1) .CreateListOfSize(1)
.All() .All()
.With(e => e.SeriesId = _series.Id)
.With(e => e.Monitored = true) .With(e => e.Monitored = true)
.Build() .Build()
.ToList(); .ToList();
@ -44,6 +53,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
_episodes = Builder<Episode> _episodes = Builder<Episode>
.CreateListOfSize(2) .CreateListOfSize(2)
.All() .All()
.With(e => e.SeriesId = _series.Id)
.With(e => e.Monitored = true) .With(e => e.Monitored = true)
.Build() .Build()
.ToList(); .ToList();
@ -85,9 +95,31 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
.Returns(true); .Returns(true);
Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, DeleteMediaFileReason.MissingFromDisk)); Subject.Handle(new EpisodeFileDeletedEvent(_episodeFile, DeleteMediaFileReason.MissingFromDisk));
Subject.HandleAsync(new SeriesScannedEvent(_series, new List<string>()));
Mocker.GetMock<IEpisodeRepository>() Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.ClearFileId(It.IsAny<Episode>(), true), Times.Once()); .Verify(v => v.SetMonitored(It.IsAny<IEnumerable<int>>(), 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<List<Episode>>(_episodes);
Mocker.GetMock<IConfigService>()
.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<string>()));
Mocker.GetMock<IEpisodeRepository>()
.Verify(v => v.SetMonitored(It.IsAny<IEnumerable<int>>(), false), Times.Never());
} }
[Test] [Test]

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
@ -42,16 +43,19 @@ namespace NzbDrone.Core.Tv
public class EpisodeService : IEpisodeService, public class EpisodeService : IEpisodeService,
IHandle<EpisodeFileDeletedEvent>, IHandle<EpisodeFileDeletedEvent>,
IHandle<EpisodeFileAddedEvent>, IHandle<EpisodeFileAddedEvent>,
IHandleAsync<SeriesDeletedEvent> IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<SeriesScannedEvent>
{ {
private readonly IEpisodeRepository _episodeRepository; private readonly IEpisodeRepository _episodeRepository;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ICached<HashSet<int>> _cache;
private readonly Logger _logger; private readonly Logger _logger;
public EpisodeService(IEpisodeRepository episodeRepository, IConfigService configService, Logger logger) public EpisodeService(IEpisodeRepository episodeRepository, IConfigService configService, ICacheManager cacheManager, Logger logger)
{ {
_episodeRepository = episodeRepository; _episodeRepository = episodeRepository;
_configService = configService; _configService = configService;
_cache = cacheManager.GetCache<HashSet<int>>(GetType());
_logger = logger; _logger = logger;
} }
@ -215,34 +219,6 @@ namespace NzbDrone.Core.Tv
_episodeRepository.DeleteMany(episodes); _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) private Episode FindOneByAirDate(int seriesId, string date, int? part)
{ {
var episodes = _episodeRepository.Find(seriesId, date); 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}"); 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<int>());
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());
}
}
} }
} }