Integrates history scores into download decision-making by comparing the custom format score of existing history entries with that of incoming releases. Releases are rejected if a higher score already exists in the history, ensuring better quality selections.

Database Migration
NO

Description
The goal is to check the history for the latest entry to determine if a better version already exists.

Let's discuss any necessary changes or potential concerns regarding this pull request here.

#### Attention
I have never touched the Sonarr code before, so please carefully review this for any disadvantages or issues I may have overlooked.

Technically, this method works. I have tested it locally, and I am currently using this version.

Maybe this should be behind a checkbox?

This is parallel to https://github.com/Radarr/Radarr/pull/10924

#### Issues Fixed or Closed by this PR
#5298
This commit is contained in:
skydel0 2025-03-12 23:31:17 +01:00
parent fef00dccf8
commit cb7a0a66c6
3 changed files with 58 additions and 1 deletions

View file

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
@ -51,7 +54,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
_logger.Debug("Checking current status of episode [{0}] in history", episode.Id);
var mostRecent = _historyService.MostRecentForEpisode(episode.Id);
if (mostRecent != null && mostRecent.EventType == EpisodeHistoryEventType.Grabbed)
if (mostRecent == null)
{
continue;
}
if (mostRecent.EventType == EpisodeHistoryEventType.Grabbed)
{
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
@ -116,6 +124,41 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryUpgradesNotAllowed, "{0} grab event in history and Quality Profile '{1}' does not allow upgrades", rejectionSubject, qualityProfile.Name);
}
}
if (subject.Episodes.FirstOrDefault(x => x.Id == episode.Id) is { HasFile: true })
{
EpisodeHistory availableUsableEpisodeHistoryForCustomFormatScore;
var episodeHistoryEventTypeForHistoryComparison = new List<EpisodeHistoryEventType>
{
EpisodeHistoryEventType.Grabbed,
EpisodeHistoryEventType.SeriesFolderImported,
};
if (episodeHistoryEventTypeForHistoryComparison.Contains(mostRecent.EventType))
{
availableUsableEpisodeHistoryForCustomFormatScore = mostRecent;
}
else
{
availableUsableEpisodeHistoryForCustomFormatScore = _historyService.MostRecentForEpisodeInEventCollection(
episode.Id,
new ReadOnlyCollection<EpisodeHistoryEventType>(episodeHistoryEventTypeForHistoryComparison));
}
if (availableUsableEpisodeHistoryForCustomFormatScore == null)
{
continue;
}
var mostRecentCustomFormat = _formatService.ParseCustomFormat(availableUsableEpisodeHistoryForCustomFormatScore, subject.Series);
var mostRecentCustomFormatScore = qualityProfile.CalculateCustomFormatScore(mostRecentCustomFormat);
if (mostRecentCustomFormatScore > subject.CustomFormatScore)
{
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScore, "Quality Profile '{0}' has a higher Custom Format score than the report: {1}", qualityProfile.Name, subject.CustomFormatScore);
}
}
}
return DownloadSpecDecision.Accept();

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@ -20,6 +21,7 @@ namespace NzbDrone.Core.History
void DeleteForSeries(List<int> seriesIds);
List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType);
PagingSpec<EpisodeHistory> GetPaged(PagingSpec<EpisodeHistory> pagingSpec, int[] languages, int[] qualities);
EpisodeHistory MostRecentForEpisodeInEventCollection(int id, ReadOnlyCollection<EpisodeHistoryEventType> episodeHistoryEventTypes);
}
public class HistoryRepository : BasicRepository<EpisodeHistory>, IHistoryRepository
@ -132,6 +134,11 @@ namespace NzbDrone.Core.History
return pagingSpec;
}
public EpisodeHistory MostRecentForEpisodeInEventCollection(int id, ReadOnlyCollection<EpisodeHistoryEventType> episodeHistoryEventTypes)
{
return Query(x => x.EpisodeId == id).Where(x => episodeHistoryEventTypes.Contains(x.EventType)).MaxBy(h => h.Date);
}
private SqlBuilder PagedBuilder(int[] languages, int[] qualities)
{
var builder = Builder()

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using NLog;
@ -27,6 +28,7 @@ namespace NzbDrone.Core.History
List<EpisodeHistory> FindByDownloadId(string downloadId);
string FindDownloadId(EpisodeImportedEvent trackedDownload);
List<EpisodeHistory> Since(DateTime date, EpisodeHistoryEventType? eventType);
EpisodeHistory MostRecentForEpisodeInEventCollection(int id, ReadOnlyCollection<EpisodeHistoryEventType> episodeHistoryEventTypes);
}
public class HistoryService : IHistoryService,
@ -365,5 +367,10 @@ namespace NzbDrone.Core.History
{
return _historyRepository.Since(date, eventType);
}
public EpisodeHistory MostRecentForEpisodeInEventCollection(int id, ReadOnlyCollection<EpisodeHistoryEventType> episodeHistoryEventTypes)
{
return _historyRepository.MostRecentForEpisodeInEventCollection(id, episodeHistoryEventTypes);
}
}
}