From e280897bc7f5c55d11714b0c25e7220477c18886 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 19 Mar 2022 12:57:00 -0700 Subject: [PATCH] Fixed: Automatic import of releases when file is not matched to series Closes #4935 --- ...add_additional_info_to_pending_releases.cs | 14 ++++++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 1 + .../Download/CompletedDownloadService.cs | 9 +++-- .../Download/Pending/PendingRelease.cs | 6 ++++ .../Download/Pending/PendingReleaseService.cs | 11 +++--- src/NzbDrone.Core/History/EpisodeHistory.cs | 1 + src/NzbDrone.Core/History/HistoryService.cs | 1 + .../Parser/Model/FindSeriesResult.cs | 24 +++++++++++++ .../Parser/Model/RemoteEpisode.cs | 1 + src/NzbDrone.Core/Parser/ParsingService.cs | 34 ++++++++++++++----- 10 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/168_add_additional_info_to_pending_releases.cs create mode 100644 src/NzbDrone.Core/Parser/Model/FindSeriesResult.cs diff --git a/src/NzbDrone.Core/Datastore/Migration/168_add_additional_info_to_pending_releases.cs b/src/NzbDrone.Core/Datastore/Migration/168_add_additional_info_to_pending_releases.cs new file mode 100644 index 000000000..965a03f63 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/168_add_additional_info_to_pending_releases.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(168)] + public class add_additional_info_to_pending_releases : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("PendingReleases").AddColumn("AdditionalInfo").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 3fb430fcf..0eee0b275 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -177,6 +177,7 @@ namespace NzbDrone.Core.Datastore MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new LanguageIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(ParsedEpisodeInfo), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter()); + MapRepository.Instance.RegisterTypeConverter(typeof(PendingReleaseAdditionalInfo), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(HashSet), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(Guid), new GuidConverter()); diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 888706b41..e8db376b3 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -13,6 +13,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Download @@ -97,9 +98,13 @@ namespace NzbDrone.Core.Download return; } - trackedDownload.Warn("Found matching series via grab history, but release title doesn't match series title. Automatic import is not possible."); + Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.SERIES_MATCH_TYPE, SeriesMatchType.Unknown.ToString()), out SeriesMatchType seriesMatchType); - return; + if (seriesMatchType == SeriesMatchType.Id) + { + trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible."); + return; + } } trackedDownload.State = TrackedDownloadState.ImportPending; diff --git a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs index 93c75a669..cdfc0b0e8 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs @@ -12,8 +12,14 @@ namespace NzbDrone.Core.Download.Pending public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } public ReleaseInfo Release { get; set; } public PendingReleaseReason Reason { get; set; } + public PendingReleaseAdditionalInfo AdditionalInfo { get; set; } //Not persisted public RemoteEpisode RemoteEpisode { get; set; } } + + public class PendingReleaseAdditionalInfo + { + public SeriesMatchType SeriesMatchType { get; set; } + } } diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index 59267fd7e..0b03c3e8c 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -291,16 +291,15 @@ namespace NzbDrone.Core.Download.Pending // Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up) if (series == null) return null; - release.RemoteEpisode = new RemoteEpisode { Series = series, + SeriesMatchType = release.AdditionalInfo?.SeriesMatchType ?? SeriesMatchType.Unknown, ParsedEpisodeInfo = release.ParsedEpisodeInfo, Release = release.Release }; - RemoteEpisode knownRemoteEpisode; - if (knownRemoteEpisodes != null && knownRemoteEpisodes.TryGetValue(release.Release.Title, out knownRemoteEpisode)) + if (knownRemoteEpisodes != null && knownRemoteEpisodes.TryGetValue(release.Release.Title, out var knownRemoteEpisode)) { release.RemoteEpisode.MappedSeasonNumber = knownRemoteEpisode.MappedSeasonNumber; release.RemoteEpisode.Episodes = knownRemoteEpisode.Episodes; @@ -333,7 +332,11 @@ namespace NzbDrone.Core.Download.Pending Release = decision.RemoteEpisode.Release, Title = decision.RemoteEpisode.Release.Title, Added = DateTime.UtcNow, - Reason = reason + Reason = reason, + AdditionalInfo = new PendingReleaseAdditionalInfo + { + SeriesMatchType = decision.RemoteEpisode.SeriesMatchType + } }); _eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent()); diff --git a/src/NzbDrone.Core/History/EpisodeHistory.cs b/src/NzbDrone.Core/History/EpisodeHistory.cs index d6016cbde..15dba719c 100644 --- a/src/NzbDrone.Core/History/EpisodeHistory.cs +++ b/src/NzbDrone.Core/History/EpisodeHistory.cs @@ -10,6 +10,7 @@ namespace NzbDrone.Core.History public class EpisodeHistory : ModelBase { public const string DOWNLOAD_CLIENT = "downloadClient"; + public const string SERIES_MATCH_TYPE = "seriesMatchType"; public EpisodeHistory() { diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index ed582be56..1adfb7fb9 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -169,6 +169,7 @@ namespace NzbDrone.Core.History history.Data.Add("TvRageId", message.Episode.Release.TvRageId.ToString()); history.Data.Add("Protocol", ((int)message.Episode.Release.DownloadProtocol).ToString()); history.Data.Add("PreferredWordScore", message.Episode.PreferredWordScore.ToString()); + history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString()); if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Parser/Model/FindSeriesResult.cs b/src/NzbDrone.Core/Parser/Model/FindSeriesResult.cs new file mode 100644 index 000000000..1ca61c62b --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/FindSeriesResult.cs @@ -0,0 +1,24 @@ +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Parser.Model +{ + public class FindSeriesResult + { + public Series Series { get; set; } + public SeriesMatchType MatchType { get; set; } + + public FindSeriesResult(Series series, SeriesMatchType matchType) + { + Series = series; + MatchType = matchType; + } + } + + public enum SeriesMatchType + { + Unknown = 0, + Title = 1, + Alias = 2, + Id = 3 + } +} diff --git a/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs b/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs index 7441b76a7..6bf8b7bc5 100644 --- a/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.Parser.Model public bool DownloadAllowed { get; set; } public TorrentSeedConfiguration SeedConfiguration { get; set; } public int PreferredWordScore { get; set; } + public SeriesMatchType SeriesMatchType { get; set; } public RemoteEpisode() { diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index b6ae87f26..2d337990e 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -164,7 +164,13 @@ namespace NzbDrone.Core.Parser if (series == null) { - series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, sceneMapping, searchCriteria); + var seriesMatch = FindSeries(parsedEpisodeInfo, tvdbId, tvRageId, sceneMapping, searchCriteria); + + if (seriesMatch != null) + { + series = seriesMatch.Series; + remoteEpisode.SeriesMatchType = seriesMatch.MatchType; + } } if (series != null) @@ -328,7 +334,7 @@ namespace NzbDrone.Core.Parser return null; } - private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SceneMapping sceneMapping, SearchCriteriaBase searchCriteria) + private FindSeriesResult FindSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SceneMapping sceneMapping, SearchCriteriaBase searchCriteria) { Series series = null; @@ -336,7 +342,7 @@ namespace NzbDrone.Core.Parser { if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMapping.TvdbId) { - return searchCriteria.Series; + return new FindSeriesResult(searchCriteria.Series, SeriesMatchType.Alias); } series = _seriesService.FindByTvdbId(sceneMapping.TvdbId); @@ -347,14 +353,14 @@ namespace NzbDrone.Core.Parser return null; } - return series; + return new FindSeriesResult(series, SeriesMatchType.Alias); } if (searchCriteria != null) { if (searchCriteria.Series.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle()) { - return searchCriteria.Series; + return new FindSeriesResult(searchCriteria.Series, SeriesMatchType.Title); } if (tvdbId > 0 && tvdbId == searchCriteria.Series.TvdbId) @@ -366,7 +372,7 @@ namespace NzbDrone.Core.Parser .WriteSentryWarn("TvdbIdMatch", tvdbId.ToString(), parsedEpisodeInfo.SeriesTitle) .Write(); - return searchCriteria.Series; + return new FindSeriesResult(searchCriteria.Series, SeriesMatchType.Id); } if (tvRageId > 0 && tvRageId == searchCriteria.Series.TvRageId) @@ -378,20 +384,28 @@ namespace NzbDrone.Core.Parser .WriteSentryWarn("TvRageIdMatch", tvRageId.ToString(), parsedEpisodeInfo.SeriesTitle) .Write(); - return searchCriteria.Series; + return new FindSeriesResult(searchCriteria.Series, SeriesMatchType.Id); } } + var matchType = SeriesMatchType.Unknown; series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); + if (series != null) + { + matchType = SeriesMatchType.Title; + } + if (series == null && parsedEpisodeInfo.SeriesTitleInfo.AllTitles != null) { series = GetSeriesByAllTitles(parsedEpisodeInfo); + matchType = SeriesMatchType.Title; } if (series == null && parsedEpisodeInfo.SeriesTitleInfo.Year > 0) { series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, parsedEpisodeInfo.SeriesTitleInfo.Year); + matchType = SeriesMatchType.Title; } if (series == null && tvdbId > 0) @@ -406,6 +420,8 @@ namespace NzbDrone.Core.Parser .Property("ParsedEpisodeInfo", parsedEpisodeInfo) .WriteSentryWarn("TvdbIdMatch", tvdbId.ToString(), parsedEpisodeInfo.SeriesTitle) .Write(); + + matchType = SeriesMatchType.Id; } } @@ -421,6 +437,8 @@ namespace NzbDrone.Core.Parser .Property("ParsedEpisodeInfo", parsedEpisodeInfo) .WriteSentryWarn("TvRageIdMatch", tvRageId.ToString(), parsedEpisodeInfo.SeriesTitle) .Write(); + + matchType = SeriesMatchType.Id; } } @@ -430,7 +448,7 @@ namespace NzbDrone.Core.Parser return null; } - return series; + return new FindSeriesResult(series, matchType); } private Episode GetDailyEpisode(Series series, string airDate, int? part, SearchCriteriaBase searchCriteria)