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 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<EpisodeService>
{
private Series _series;
private EpisodeFile _episodeFile;
private List<Episode> _episodes;
[SetUp]
public void Setup()
{
_series = Builder<Series>
.CreateNew()
.Build();
_episodeFile = Builder<EpisodeFile>
.CreateNew()
.With(e => e.SeriesId = _series.Id)
.Build();
}
@ -30,6 +38,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeServiceTests
_episodes = Builder<Episode>
.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<Episode>
.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<string>()));
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]

View File

@ -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<EpisodeFileDeletedEvent>,
IHandle<EpisodeFileAddedEvent>,
IHandleAsync<SeriesDeletedEvent>
IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<SeriesScannedEvent>
{
private readonly IEpisodeRepository _episodeRepository;
private readonly IConfigService _configService;
private readonly ICached<HashSet<int>> _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<HashSet<int>>(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<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());
}
}
}
}