This commit is contained in:
Mika Cohen 2026-04-23 21:15:39 +00:00 committed by GitHub
commit f1416a2394
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 284 additions and 25 deletions

View file

@ -40,6 +40,178 @@ 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 = "A Movie 1998",
DownloadId = "35238",
Category = "radarr",
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<MovieHistory>());
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), null))
.Returns(new RemoteMovie
{
Release = new ReleaseInfo { Title = "A Movie 1998" },
Movie = new Movie() { Id = 3 },
ParsedMovieInfo = new ParsedMovieInfo()
{
MovieTitles = new List<string> { "A Movie" },
Year = 1998
}
});
}
[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<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), 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<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), 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<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), 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<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), 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<MovieHistory>());
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), null))
.Returns(new RemoteMovie
{
ParsedMovieInfo = new ParsedMovieInfo
{
MovieTitles = new List<string> { "A Movie" },
Year = 1998
}
});
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<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), null), Times.Exactly(2));
}
[Test]
public void should_track_downloads_using_the_source_title_if_it_cannot_be_found_using_the_download_title()
{
@ -214,6 +386,70 @@ public void should_unmap_tracked_download_if_movie_deleted()
trackedDownloads.First().RemoteMovie.Should().BeNull();
}
[Test]
public void should_update_tracked_download_when_movie_edited()
{
var originalMovie = new Movie { Id = 3, TmdbId = 10, Title = "A Movie" };
var updatedMovie = new Movie { Id = 3, TmdbId = 10, Title = "A Movie Updated" };
var remoteMovie = new RemoteMovie
{
Movie = originalMovie,
ParsedMovieInfo = new ParsedMovieInfo
{
MovieTitles = { "A Movie" },
Year = 1998
}
};
var updatedRemoteMovie = new RemoteMovie
{
Movie = updatedMovie,
ParsedMovieInfo = new ParsedMovieInfo
{
MovieTitles = { "A Movie" },
Year = 1998
}
};
Mocker.GetMock<IParsingService>()
.SetupSequence(s => s.Map(It.IsAny<ParsedMovieInfo>(), It.IsAny<string>(), It.IsAny<int>(), null))
.Returns(remoteMovie)
.Returns(updatedRemoteMovie);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<MovieHistory>());
var client = new DownloadClientDefinition
{
Id = 1,
Protocol = DownloadProtocol.Torrent
};
var item = new DownloadClientItem
{
Title = "A Movie 1998",
DownloadId = "12345",
DownloadClientInfo = new DownloadClientItemClientInfo
{
Id = 1,
Type = "Blackhole",
Name = "Blackhole Client",
Protocol = DownloadProtocol.Torrent
}
};
Subject.TrackDownload(client, item);
Subject.Handle(new MovieEditedEvent(updatedMovie, originalMovie));
var trackedDownloads = Subject.GetTrackedDownloads();
trackedDownloads.Should().HaveCount(1);
trackedDownloads.First().RemoteMovie.Should().BeSameAs(updatedRemoteMovie);
trackedDownloads.First().RemoteMovie.Movie.Title.Should().Be("A Movie Updated");
}
[Test]
public void should_not_throw_when_processing_deleted_movie()
{

View file

@ -96,7 +96,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);
@ -221,6 +221,39 @@ 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.RemoteMovie?.Movie != null;
}
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 UpdateCachedItem(TrackedDownload trackedDownload)
{
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(trackedDownload.DownloadItem.Title);
@ -230,6 +263,16 @@ private void UpdateCachedItem(TrackedDownload trackedDownload)
_aggregationService.Augment(trackedDownload.RemoteMovie);
}
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)
@ -271,12 +314,7 @@ public void Handle(MovieAddedEvent message)
message.Movie?.TmdbId == t.RemoteMovie.Movie.TmdbId)
.ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
RefreshCachedItems(cachedItems);
}
public void Handle(MovieEditedEvent message)
@ -287,12 +325,7 @@ public void Handle(MovieEditedEvent message)
(t.RemoteMovie.Movie.Id == message.Movie?.Id || t.RemoteMovie.Movie.TmdbId == message.Movie?.TmdbId))
.ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
RefreshCachedItems(cachedItems);
}
public void Handle(MoviesBulkEditedEvent message)
@ -303,12 +336,7 @@ public void Handle(MoviesBulkEditedEvent message)
message.Movies.Any(m => m.Id == t.RemoteMovie.Movie.Id || m.TmdbId == t.RemoteMovie.Movie.TmdbId))
.ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
RefreshCachedItems(cachedItems);
}
public void Handle(MoviesDeletedEvent message)
@ -319,12 +347,7 @@ public void Handle(MoviesDeletedEvent message)
message.Movies.Any(m => m.Id == t.RemoteMovie.Movie.Id || m.TmdbId == t.RemoteMovie.Movie.TmdbId))
.ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
RefreshCachedItems(cachedItems);
}
}
}