From 6c324c8a1c9979d1a85824e63d37eaf984fc2680 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 7 Feb 2021 20:25:44 -0800 Subject: [PATCH] Fixed: Errors loading queue after episodes in series are removed Closes #3565 --- .../TrackedDownloadServiceFixture.cs | 181 ++++++++++++++++++ .../TrackedDownloadService.cs | 52 ++++- .../Tv/Events/EpisodeInfoRefreshedEvent.cs | 5 +- src/NzbDrone.Core/Tv/RefreshEpisodeService.cs | 2 +- 4 files changed, 236 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs index fcb7110c0..7097ccb72 100644 --- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Core.Indexers; using System.Linq; +using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.Test.Download.TrackedDownloads { @@ -144,5 +145,185 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(0); trackedDownload.RemoteEpisode.MappedSeasonNumber.Should().Be(0); } + + [Test] + public void should_unmap_tracked_download_if_episode_deleted() + { + GivenDownloadHistory(); + + var remoteEpisode = new RemoteEpisode + { + Series = new Series() { Id = 5 }, + Episodes = new List { new Episode { Id = 4 } }, + ParsedEpisodeInfo = new ParsedEpisodeInfo() + { + SeriesTitle = "TV Series", + SeasonNumber = 1, + EpisodeNumbers = new[] { 1 } + }, + MappedSeasonNumber = 0 + }; + + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(remoteEpisode); + + Mocker.GetMock() + .Setup(s => s.FindByDownloadId(It.IsAny())) + .Returns(new List()); + + + var client = new DownloadClientDefinition() + { + Id = 1, + Protocol = DownloadProtocol.Torrent + }; + + var item = new DownloadClientItem() + { + Title = "TV Series - S01E01", + DownloadId = "12345", + DownloadClientInfo = new DownloadClientItemClientInfo + { + Id = 1, + Type = "Blackhole", + Name = "Blackhole Client", + Protocol = DownloadProtocol.Torrent + } + }; + + Subject.TrackDownload(client, item); + Subject.GetTrackedDownloads().Should().HaveCount(1); + + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(default(RemoteEpisode)); + + Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List(), new List(), remoteEpisode.Episodes)); + + var trackedDownloads = Subject.GetTrackedDownloads(); + trackedDownloads.Should().HaveCount(1); + trackedDownloads.First().RemoteEpisode.Should().BeNull(); + } + + [Test] + public void should_not_throw_when_processing_deleted_episodes() + { + GivenDownloadHistory(); + + var remoteEpisode = new RemoteEpisode + { + Series = new Series() { Id = 5 }, + Episodes = new List { new Episode { Id = 4 } }, + ParsedEpisodeInfo = new ParsedEpisodeInfo() + { + SeriesTitle = "TV Series", + SeasonNumber = 1, + EpisodeNumbers = new[] { 1 } + }, + MappedSeasonNumber = 0 + }; + + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(default(RemoteEpisode)); + + Mocker.GetMock() + .Setup(s => s.FindByDownloadId(It.IsAny())) + .Returns(new List()); + + + var client = new DownloadClientDefinition() + { + Id = 1, + Protocol = DownloadProtocol.Torrent + }; + + var item = new DownloadClientItem() + { + Title = "TV Series - S01E01", + DownloadId = "12345", + DownloadClientInfo = new DownloadClientItemClientInfo + { + Id = 1, + Type = "Blackhole", + Name = "Blackhole Client", + Protocol = DownloadProtocol.Torrent + } + }; + + Subject.TrackDownload(client, item); + Subject.GetTrackedDownloads().Should().HaveCount(1); + + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(default(RemoteEpisode)); + + Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List(), new List(), remoteEpisode.Episodes)); + + var trackedDownloads = Subject.GetTrackedDownloads(); + trackedDownloads.Should().HaveCount(1); + trackedDownloads.First().RemoteEpisode.Should().BeNull(); + } + + [Test] + public void should_not_throw_when_processing_deleted_series() + { + GivenDownloadHistory(); + + var remoteEpisode = new RemoteEpisode + { + Series = new Series() { Id = 5 }, + Episodes = new List { new Episode { Id = 4 } }, + ParsedEpisodeInfo = new ParsedEpisodeInfo() + { + SeriesTitle = "TV Series", + SeasonNumber = 1, + EpisodeNumbers = new[] { 1 } + }, + MappedSeasonNumber = 0 + }; + + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(default(RemoteEpisode)); + + Mocker.GetMock() + .Setup(s => s.FindByDownloadId(It.IsAny())) + .Returns(new List()); + + + var client = new DownloadClientDefinition() + { + Id = 1, + Protocol = DownloadProtocol.Torrent + }; + + var item = new DownloadClientItem() + { + Title = "TV Series - S01E01", + DownloadId = "12345", + DownloadClientInfo = new DownloadClientItemClientInfo + { + Id = 1, + Type = "Blackhole", + Name = "Blackhole Client", + Protocol = DownloadProtocol.Torrent + } + }; + + Subject.TrackDownload(client, item); + Subject.GetTrackedDownloads().Should().HaveCount(1); + + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(default(RemoteEpisode)); + + Subject.Handle(new SeriesDeletedEvent(remoteEpisode.Series, true, true)); + + var trackedDownloads = Subject.GetTrackedDownloads(); + trackedDownloads.Should().HaveCount(1); + trackedDownloads.First().RemoteEpisode.Should().BeNull(); + } } } diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index a0dc6788f..f98ce880d 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -8,6 +8,7 @@ using NzbDrone.Core.Download.History; using NzbDrone.Core.History; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; +using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.Download.TrackedDownloads { @@ -21,7 +22,9 @@ namespace NzbDrone.Core.Download.TrackedDownloads void UpdateTrackable(List trackedDownloads); } - public class TrackedDownloadService : ITrackedDownloadService + public class TrackedDownloadService : ITrackedDownloadService, + IHandle, + IHandle { private readonly IParsingService _parsingService; private readonly IHistoryService _historyService; @@ -187,6 +190,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads } } + private void UpdateCachedItem(TrackedDownload trackedDownload) + { + var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title); + + trackedDownload.RemoteEpisode = parsedEpisodeInfo == null ? null :_parsingService.Map(parsedEpisodeInfo, 0, 0); + } + private static TrackedDownloadState GetStateFromHistory(DownloadHistoryEventType eventType) { switch (eventType) @@ -201,5 +211,45 @@ namespace NzbDrone.Core.Download.TrackedDownloads return TrackedDownloadState.Downloading; } } + + public void Handle(EpisodeInfoRefreshedEvent message) + { + var needsToUpdate = false; + + foreach (var episode in message.Removed) + { + var cachedItems = _cache.Values.Where(t => + t.RemoteEpisode?.Episodes != null && + t.RemoteEpisode.Episodes.Any(e => e.Id == episode.Id)) + .ToList(); + + if (cachedItems.Any()) + { + needsToUpdate = true; + } + + cachedItems.ForEach(UpdateCachedItem); + } + + if (needsToUpdate) + { + _eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads())); + } + } + + public void Handle(SeriesDeletedEvent message) + { + var cachedItems = _cache.Values.Where(t => + t.RemoteEpisode?.Series != null && + t.RemoteEpisode.Series.Id == message.Series.Id) + .ToList(); + + if (cachedItems.Any()) + { + cachedItems.ForEach(UpdateCachedItem); + + _eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads())); + } + } } } diff --git a/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs b/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs index 4eded3b79..d991af5c0 100644 --- a/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs +++ b/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs @@ -9,12 +9,13 @@ namespace NzbDrone.Core.Tv.Events public Series Series { get; set; } public ReadOnlyCollection Added { get; private set; } public ReadOnlyCollection Updated { get; private set; } + public ReadOnlyCollection Removed { get; private set; } - public EpisodeInfoRefreshedEvent(Series series, IList added, IList updated) + public EpisodeInfoRefreshedEvent(Series series, IList added, IList updated, IList removed) { Series = series; Added = new ReadOnlyCollection(added); - Updated = new ReadOnlyCollection(updated); + Removed = new ReadOnlyCollection(removed); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs index 795ad1671..8ed4d9d21 100644 --- a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -99,7 +99,7 @@ namespace NzbDrone.Core.Tv _episodeService.UpdateMany(updateList); _episodeService.InsertMany(newList); - _eventAggregator.PublishEvent(new EpisodeInfoRefreshedEvent(series, newList, updateList)); + _eventAggregator.PublishEvent(new EpisodeInfoRefreshedEvent(series, newList, updateList, existingEpisodes)); if (failCount != 0) {