mirror of
https://github.com/Sonarr/Sonarr
synced 2026-05-08 04:50:56 +02:00
Merge cce09c916a into 4c66cc2c33
This commit is contained in:
commit
1ffbbdc764
2 changed files with 303 additions and 13 deletions
|
|
@ -36,6 +36,182 @@ private void GivenDownloadHistory()
|
|||
});
|
||||
}
|
||||
|
||||
private static DownloadClientDefinition CreateDownloadClient()
|
||||
{
|
||||
return new DownloadClientDefinition()
|
||||
{
|
||||
Id = 1,
|
||||
Protocol = DownloadProtocol.Usenet
|
||||
};
|
||||
}
|
||||
|
||||
private static DownloadClientItem CreateDownloadItem(DownloadItemStatus status)
|
||||
{
|
||||
return new DownloadClientItem()
|
||||
{
|
||||
Title = "TV Series S01E01",
|
||||
DownloadId = "35238",
|
||||
Category = "sonarr",
|
||||
TotalSize = 1000,
|
||||
RemainingSize = 500,
|
||||
Status = status,
|
||||
DownloadClientInfo = new DownloadClientItemClientInfo
|
||||
{
|
||||
Id = 1,
|
||||
Type = "NZBGet",
|
||||
Name = "NZBGet",
|
||||
Protocol = DownloadProtocol.Usenet
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void GivenTrackedDownloadCanBeMapped()
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||
.Returns(new List<EpisodeHistory>());
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
|
||||
.Returns(new RemoteEpisode
|
||||
{
|
||||
Release = new ReleaseInfo { Title = "TV Series S01E01" },
|
||||
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 = 1
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(DownloadItemStatus.Queued)]
|
||||
[TestCase(DownloadItemStatus.Paused)]
|
||||
public void should_reuse_stable_waiting_downloading_tracked_download(DownloadItemStatus status)
|
||||
{
|
||||
GivenTrackedDownloadCanBeMapped();
|
||||
|
||||
var client = CreateDownloadClient();
|
||||
var item = CreateDownloadItem(status);
|
||||
var updatedItem = CreateDownloadItem(status);
|
||||
updatedItem.RemainingSize = 250;
|
||||
|
||||
var trackedDownload = Subject.TrackDownload(client, item);
|
||||
var refreshedTrackedDownload = Subject.TrackDownload(client, updatedItem);
|
||||
|
||||
trackedDownload.State.Should().Be(TrackedDownloadState.Downloading);
|
||||
refreshedTrackedDownload.Should().BeSameAs(trackedDownload);
|
||||
refreshedTrackedDownload.DownloadItem.Should().BeSameAs(updatedItem);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.FindByDownloadId(It.IsAny<string>()), Times.Once());
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_reprocess_when_waiting_download_starts_downloading()
|
||||
{
|
||||
GivenTrackedDownloadCanBeMapped();
|
||||
|
||||
var client = CreateDownloadClient();
|
||||
var item = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
var updatedItem = CreateDownloadItem(DownloadItemStatus.Downloading);
|
||||
|
||||
Subject.TrackDownload(client, item);
|
||||
Subject.TrackDownload(client, updatedItem);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.FindByDownloadId(It.IsAny<string>()), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_reprocess_when_waiting_download_identity_changes()
|
||||
{
|
||||
GivenTrackedDownloadCanBeMapped();
|
||||
|
||||
var client = CreateDownloadClient();
|
||||
var item = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
var updatedItem = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
updatedItem.TotalSize = 2000;
|
||||
|
||||
Subject.TrackDownload(client, item);
|
||||
Subject.TrackDownload(client, updatedItem);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.FindByDownloadId(It.IsAny<string>()), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_reprocess_when_waiting_download_has_warning_status()
|
||||
{
|
||||
GivenTrackedDownloadCanBeMapped();
|
||||
|
||||
var client = CreateDownloadClient();
|
||||
var item = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
var updatedItem = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
updatedItem.RemainingSize = 250;
|
||||
|
||||
var trackedDownload = Subject.TrackDownload(client, item);
|
||||
trackedDownload.Warn("Temporary warning");
|
||||
|
||||
var refreshedTrackedDownload = Subject.TrackDownload(client, updatedItem);
|
||||
|
||||
refreshedTrackedDownload.Should().NotBeSameAs(trackedDownload);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.FindByDownloadId(It.IsAny<string>()), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_reprocess_when_waiting_download_is_not_mapped()
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
|
||||
.Returns(new List<EpisodeHistory>());
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
|
||||
.Returns(new RemoteEpisode
|
||||
{
|
||||
ParsedEpisodeInfo = new ParsedEpisodeInfo
|
||||
{
|
||||
SeriesTitle = "TV Series",
|
||||
SeasonNumber = 1,
|
||||
EpisodeNumbers = new[] { 1 }
|
||||
}
|
||||
});
|
||||
|
||||
var client = CreateDownloadClient();
|
||||
var item = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
var updatedItem = CreateDownloadItem(DownloadItemStatus.Queued);
|
||||
updatedItem.RemainingSize = 250;
|
||||
|
||||
var trackedDownload = Subject.TrackDownload(client, item);
|
||||
var refreshedTrackedDownload = Subject.TrackDownload(client, updatedItem);
|
||||
|
||||
refreshedTrackedDownload.Should().NotBeSameAs(trackedDownload);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.FindByDownloadId(It.IsAny<string>()), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Verify(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_track_downloads_using_the_source_title_if_it_cannot_be_found_using_the_download_title()
|
||||
{
|
||||
|
|
@ -344,6 +520,74 @@ public void should_not_throw_when_processing_deleted_episodes()
|
|||
trackedDownloads.First().RemoteEpisode.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_tracked_download_when_series_edited()
|
||||
{
|
||||
var originalSeries = new Series { Id = 5, TvdbId = 10, Title = "TV Series" };
|
||||
var updatedSeries = new Series { Id = 5, TvdbId = 10, Title = "TV Series Updated" };
|
||||
|
||||
var remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Series = originalSeries,
|
||||
Episodes = new List<Episode> { new Episode { Id = 4 } },
|
||||
ParsedEpisodeInfo = new ParsedEpisodeInfo
|
||||
{
|
||||
SeriesTitle = "TV Series",
|
||||
SeasonNumber = 1,
|
||||
EpisodeNumbers = new[] { 1 }
|
||||
}
|
||||
};
|
||||
|
||||
var updatedRemoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Series = updatedSeries,
|
||||
Episodes = new List<Episode> { new Episode { Id = 4 } },
|
||||
ParsedEpisodeInfo = new ParsedEpisodeInfo
|
||||
{
|
||||
SeriesTitle = "TV Series",
|
||||
SeasonNumber = 1,
|
||||
EpisodeNumbers = new[] { 1 }
|
||||
}
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.SetupSequence(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
|
||||
.Returns(remoteEpisode)
|
||||
.Returns(updatedRemoteEpisode);
|
||||
|
||||
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.Handle(new SeriesEditedEvent(updatedSeries, originalSeries));
|
||||
|
||||
var trackedDownloads = Subject.GetTrackedDownloads();
|
||||
trackedDownloads.Should().HaveCount(1);
|
||||
trackedDownloads.First().RemoteEpisode.Should().BeSameAs(updatedRemoteEpisode);
|
||||
trackedDownloads.First().RemoteEpisode.Series.Title.Should().Be("TV Series Updated");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_throw_when_processing_deleted_series()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public interface ITrackedDownloadService
|
|||
|
||||
public class TrackedDownloadService : ITrackedDownloadService,
|
||||
IHandle<EpisodeInfoRefreshedEvent>,
|
||||
IHandle<SeriesEditedEvent>,
|
||||
IHandle<SeriesAddedEvent>,
|
||||
IHandle<SeriesDeletedEvent>
|
||||
{
|
||||
|
|
@ -90,7 +91,7 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do
|
|||
{
|
||||
var existingItem = Find(downloadItem.DownloadId);
|
||||
|
||||
if (existingItem != null && existingItem.State != TrackedDownloadState.Downloading)
|
||||
if (existingItem != null && CanReuseTrackedDownload(existingItem, downloadItem))
|
||||
{
|
||||
LogItemChange(existingItem, existingItem.DownloadItem, downloadItem);
|
||||
|
||||
|
|
@ -222,6 +223,40 @@ public void UpdateTrackable(List<TrackedDownload> trackedDownloads)
|
|||
}
|
||||
}
|
||||
|
||||
private static bool CanReuseTrackedDownload(TrackedDownload existingItem, DownloadClientItem downloadItem)
|
||||
{
|
||||
if (existingItem.State != TrackedDownloadState.Downloading)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsStableWaitingDownload(downloadItem) &&
|
||||
HasSameDownloadIdentity(existingItem.DownloadItem, downloadItem) &&
|
||||
HasHealthyWaitingCache(existingItem);
|
||||
}
|
||||
|
||||
private static bool IsStableWaitingDownload(DownloadClientItem downloadItem)
|
||||
{
|
||||
return downloadItem.Status == DownloadItemStatus.Queued ||
|
||||
downloadItem.Status == DownloadItemStatus.Paused;
|
||||
}
|
||||
|
||||
private static bool HasHealthyWaitingCache(TrackedDownload existingItem)
|
||||
{
|
||||
return existingItem.Status == TrackedDownloadStatus.Ok &&
|
||||
existingItem.RemoteEpisode?.Series != null &&
|
||||
existingItem.RemoteEpisode.Episodes?.Any() == true;
|
||||
}
|
||||
|
||||
private static bool HasSameDownloadIdentity(DownloadClientItem existingItem, DownloadClientItem downloadItem)
|
||||
{
|
||||
return existingItem.DownloadId == downloadItem.DownloadId &&
|
||||
existingItem.Title == downloadItem.Title &&
|
||||
existingItem.Category == downloadItem.Category &&
|
||||
existingItem.TotalSize == downloadItem.TotalSize &&
|
||||
existingItem.DownloadClientInfo?.Id == downloadItem.DownloadClientInfo?.Id;
|
||||
}
|
||||
|
||||
private void LogItemChange(TrackedDownload trackedDownload, DownloadClientItem existingItem, DownloadClientItem downloadItem)
|
||||
{
|
||||
if (existingItem == null ||
|
||||
|
|
@ -249,6 +284,16 @@ private void UpdateCachedItem(TrackedDownload trackedDownload)
|
|||
_aggregationService.Augment(trackedDownload.RemoteEpisode);
|
||||
}
|
||||
|
||||
private void RefreshCachedItems(List<TrackedDownload> cachedItems)
|
||||
{
|
||||
if (cachedItems.Any())
|
||||
{
|
||||
cachedItems.ForEach(UpdateCachedItem);
|
||||
|
||||
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
|
||||
}
|
||||
}
|
||||
|
||||
private static TrackedDownloadState GetStateFromHistory(DownloadHistoryEventType eventType)
|
||||
{
|
||||
switch (eventType)
|
||||
|
|
@ -289,6 +334,17 @@ public void Handle(EpisodeInfoRefreshedEvent message)
|
|||
}
|
||||
}
|
||||
|
||||
public void Handle(SeriesEditedEvent message)
|
||||
{
|
||||
var cachedItems = _cache.Values
|
||||
.Where(t =>
|
||||
t.RemoteEpisode?.Series != null &&
|
||||
(t.RemoteEpisode.Series.Id == message.Series?.Id || t.RemoteEpisode.Series.TvdbId == message.Series?.TvdbId))
|
||||
.ToList();
|
||||
|
||||
RefreshCachedItems(cachedItems);
|
||||
}
|
||||
|
||||
public void Handle(SeriesAddedEvent message)
|
||||
{
|
||||
var cachedItems = _cache.Values
|
||||
|
|
@ -297,12 +353,7 @@ public void Handle(SeriesAddedEvent message)
|
|||
message.Series?.TvdbId == t.RemoteEpisode.Series.TvdbId)
|
||||
.ToList();
|
||||
|
||||
if (cachedItems.Any())
|
||||
{
|
||||
cachedItems.ForEach(UpdateCachedItem);
|
||||
|
||||
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
|
||||
}
|
||||
RefreshCachedItems(cachedItems);
|
||||
}
|
||||
|
||||
public void Handle(SeriesDeletedEvent message)
|
||||
|
|
@ -313,12 +364,7 @@ public void Handle(SeriesDeletedEvent message)
|
|||
message.Series.Any(s => s.Id == t.RemoteEpisode.Series.Id || s.TvdbId == t.RemoteEpisode.Series.TvdbId))
|
||||
.ToList();
|
||||
|
||||
if (cachedItems.Any())
|
||||
{
|
||||
cachedItems.ForEach(UpdateCachedItem);
|
||||
|
||||
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
|
||||
}
|
||||
RefreshCachedItems(cachedItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue