Fixed: Errors loading queue after episodes in series are removed

Closes #3565
This commit is contained in:
Mark McDowall 2021-02-07 20:25:44 -08:00
parent 54a267d860
commit 6c324c8a1c
4 changed files with 236 additions and 4 deletions

View File

@ -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<Episode> { new Episode { Id = 4 } },
ParsedEpisodeInfo = new ParsedEpisodeInfo()
{
SeriesTitle = "TV Series",
SeasonNumber = 1,
EpisodeNumbers = new[] { 1 }
},
MappedSeasonNumber = 0
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(remoteEpisode);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<EpisodeHistory>());
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<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List<Episode>(), new List<Episode>(), 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<Episode> { new Episode { Id = 4 } },
ParsedEpisodeInfo = new ParsedEpisodeInfo()
{
SeriesTitle = "TV Series",
SeasonNumber = 1,
EpisodeNumbers = new[] { 1 }
},
MappedSeasonNumber = 0
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<EpisodeHistory>());
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<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List<Episode>(), new List<Episode>(), 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<Episode> { new Episode { Id = 4 } },
ParsedEpisodeInfo = new ParsedEpisodeInfo()
{
SeriesTitle = "TV Series",
SeasonNumber = 1,
EpisodeNumbers = new[] { 1 }
},
MappedSeasonNumber = 0
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<EpisodeHistory>());
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<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), 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();
}
}
}

View File

@ -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<TrackedDownload> trackedDownloads);
}
public class TrackedDownloadService : ITrackedDownloadService
public class TrackedDownloadService : ITrackedDownloadService,
IHandle<EpisodeInfoRefreshedEvent>,
IHandle<SeriesDeletedEvent>
{
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()));
}
}
}
}

View File

@ -9,12 +9,13 @@ namespace NzbDrone.Core.Tv.Events
public Series Series { get; set; }
public ReadOnlyCollection<Episode> Added { get; private set; }
public ReadOnlyCollection<Episode> Updated { get; private set; }
public ReadOnlyCollection<Episode> Removed { get; private set; }
public EpisodeInfoRefreshedEvent(Series series, IList<Episode> added, IList<Episode> updated)
public EpisodeInfoRefreshedEvent(Series series, IList<Episode> added, IList<Episode> updated, IList<Episode> removed)
{
Series = series;
Added = new ReadOnlyCollection<Episode>(added);
Updated = new ReadOnlyCollection<Episode>(updated);
Removed = new ReadOnlyCollection<Episode>(removed);
}
}
}

View File

@ -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)
{