From 435ddbab9b61c7b9997163e825eb1e596da88710 Mon Sep 17 00:00:00 2001 From: Mikal Stordal Date: Sun, 12 Sep 2021 20:25:59 +0200 Subject: [PATCH] Hopefully fix the metadata provider this time --- Shokofin/API/ShokoAPIManager.cs | 36 +- Shokofin/Providers/MissingMetadataProvider.cs | 463 ++++++++++++------ Shokofin/Providers/SeriesProvider.cs | 28 +- 3 files changed, 360 insertions(+), 167 deletions(-) diff --git a/Shokofin/API/ShokoAPIManager.cs b/Shokofin/API/ShokoAPIManager.cs index a344376c..8752d504 100644 --- a/Shokofin/API/ShokoAPIManager.cs +++ b/Shokofin/API/ShokoAPIManager.cs @@ -26,11 +26,11 @@ public class ShokoAPIManager private static readonly ConcurrentDictionary SeriesPathToIdDictionary = new ConcurrentDictionary(); - private static ConcurrentDictionary SeriesIdToGroupIdDictionary = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary SeriesIdToGroupIdDictionary = new ConcurrentDictionary(); - private static ConcurrentDictionary EpisodePathToEpisodeIdDirectory = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary EpisodePathToEpisodeIdDirectory = new ConcurrentDictionary(); - private static ConcurrentDictionary EpisodeIdToSeriesIdDictionary = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary EpisodeIdToSeriesIdDictionary = new ConcurrentDictionary(); /// /// Episodes marked as ignored is skipped when adding missing episode metadata. @@ -42,6 +42,8 @@ public class ShokoAPIManager /// private static readonly ConcurrentDictionary> SeriesIdToEpisodeIdDictionery = new ConcurrentDictionary>(); + public static readonly ConcurrentDictionary> LockedIdDictionary = new ConcurrentDictionary>(); + public ShokoAPIManager(ILogger logger, ILibraryManager libraryManager) { Logger = logger; @@ -88,6 +90,28 @@ public string StripMediaFolder(string fullPath) return fullPath.Substring(mediaFolder.Path.Length); } + #endregion + #region Update locks + + public bool TryLockActionForIdOFType(string type, string id, string action = "default") + { + var key = $"{type}:{id}"; + if (!LockedIdDictionary.TryGetValue(key, out var hashSet)) { + LockedIdDictionary.TryAdd(key, new HashSet()); + if (!LockedIdDictionary.TryGetValue(key, out hashSet)) + throw new Exception("Unable to set hash set"); + } + return hashSet.Add(action); + } + + public bool TryUnlockActionForIdOFType(string type, string id, string action = "default") + { + var key = $"{type}:{id}"; + if (!LockedIdDictionary.TryGetValue(key, out var hashSet)) + return false; + return hashSet.Remove(action); + } + #endregion #region Clear @@ -95,6 +119,7 @@ public void Clear() { Logger.LogDebug("Clearing data."); DataCache.Dispose(); + LockedIdDictionary.Clear(); MediaFolderList.Clear(); EpisodeIdToSeriesIdDictionary.Clear(); EpisodePathToEpisodeIdDirectory.Clear(); @@ -439,6 +464,11 @@ public bool TryGetSeriesIdForPath(string path, out string seriesId) return SeriesPathToIdDictionary.TryGetValue(path, out seriesId); } + public bool TryGetGroupIdForSeriesId(string seriesId, out string groupId) + { + return SeriesIdToGroupIdDictionary.TryGetValue(seriesId, out groupId); + } + private async Task CreateSeriesInfo(Series series, string seriesId = null) { if (series == null) diff --git a/Shokofin/Providers/MissingMetadataProvider.cs b/Shokofin/Providers/MissingMetadataProvider.cs index 58b0d836..76d09288 100644 --- a/Shokofin/Providers/MissingMetadataProvider.cs +++ b/Shokofin/Providers/MissingMetadataProvider.cs @@ -43,6 +43,7 @@ public MissingMetadataProvider(ShokoAPIManager apiManager, ILibraryManager libra public Task RunAsync() { + LibraryManager.ItemAdded += OnLibraryManagerItemAdded; LibraryManager.ItemUpdated += OnLibraryManagerItemUpdated; LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved; ProviderManager.RefreshCompleted += OnProviderManagerRefreshComplete; @@ -52,6 +53,7 @@ public Task RunAsync() public void Dispose() { + LibraryManager.ItemAdded -= OnLibraryManagerItemAdded; LibraryManager.ItemUpdated -= OnLibraryManagerItemUpdated; LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved; ProviderManager.RefreshCompleted -= OnProviderManagerRefreshComplete; @@ -93,42 +95,101 @@ private void OnProviderManagerRefreshComplete(object sender, GenericEventArgs !ep.IsVirtualItem)) { + OnLibraryManagerItemUpdated(this, new ItemChangeEventArgs { Item = episode, Parent = season, UpdateReason = ItemUpdateType.None }); + } + } + finally { + ApiManager.TryUnlockActionForIdOFType("season", seasonId, "remove"); + } + + return; + } + case Episode episode: { + // Abort if we're unable to get the shoko episode id + if (!IsEnabledForEpisode(episode, out var episodeId)) return; var query = new InternalItemsQuery { IsVirtualItem = true, - IndexNumber = episode.IndexNumber, - ParentIndexNumber = episode.ParentIndexNumber, - IncludeItemTypes = new [] { episode.GetType().Name }, - Parent = itemChangeEventArgs.Parent, + HasAnyProviderId = { ["Shoko Episode"] = episodeId }, + IncludeItemTypes = new [] { nameof (Episode) }, GroupByPresentationUniqueKey = false, DtoOptions = new DtoOptions(true), }; @@ -139,13 +200,21 @@ private void OnLibraryManagerItemUpdated(object sender, ItemChangeEventArgs item DeleteFileLocation = true, }; + var count = existingVirtualItems.Count; // Remove the virtual season/episode that matches the newly updated item - foreach (var item in existingVirtualItems) + foreach (var item in existingVirtualItems) { + if (episode.IsVirtualItem && System.Guid.Equals(item.Id, episode.Id)) { + count--; + continue; + } + LibraryManager.DeleteItem(item, deleteOptions); - break; + } + Logger.LogInformation("Removed {Count} duplicate episodes for episode {EpisodeName}. (Episode={EpisodeId})", count, episode.Name, episodeId); + + return; } } - } private void OnLibraryManagerItemRemoved(object sender, ItemChangeEventArgs itemChangeEventArgs) @@ -168,7 +237,29 @@ private void OnLibraryManagerItemRemoved(object sender, ItemChangeEventArgs item private bool IsEnabledForSeries(Series series, out string seriesId) { - return (series.ProviderIds.TryGetValue("Shoko Series", out seriesId) || ApiManager.TryGetSeriesIdForPath(series.Path, out seriesId)) && !string.IsNullOrEmpty(seriesId); + if (series.ProviderIds.TryGetValue("Shoko Series", out seriesId) && !string.IsNullOrEmpty(seriesId)) { + return true; + } + + if (ApiManager.TryGetSeriesIdForPath(series.Path, out seriesId)) { + // Set the "Shoko Group" and "Shoko Series" provider ids for the series, since it haven't been set again. It doesn't matter if it's not saved to the database, since we only need it while running the following code. + if (ApiManager.TryGetGroupIdForSeriesId(seriesId, out var groupId)) { + var filterByType = Plugin.Instance.Configuration.FilterOnLibraryTypes ? Ordering.GroupFilterType.Others : Ordering.GroupFilterType.Default; + var groupInfo = ApiManager.GetGroupInfoSync(groupId, filterByType); + seriesId = groupInfo.DefaultSeries.Id; + + SeriesProvider.AddProviderIds(series, seriesId, groupInfo.Id); + } + // Same as above, but only set the "Shoko Series" id. + else { + SeriesProvider.AddProviderIds(series, seriesId); + } + // Make sure the presentation unique is not cached, so we won't reuse the cache key. + series.PresentationUniqueKey = null; + return true; + } + + return false; } private void HandleSeries(Series series) @@ -177,87 +268,108 @@ private void HandleSeries(Series series) if (!IsEnabledForSeries(series, out var seriesId)) return; - // Provide metadata for a series using Shoko's Group feature - if (Plugin.Instance.Configuration.SeriesGrouping == Ordering.GroupType.ShokoGroup) { - var groupInfo = ApiManager.GetGroupInfoForSeriesSync(seriesId, Plugin.Instance.Configuration.FilterOnLibraryTypes ? Ordering.GroupFilterType.Others : Ordering.GroupFilterType.Default); - if (groupInfo == null) { - Logger.LogWarning("Unable to find group info for series. (Series={SeriesID})", seriesId); - return; - } - - // If the series id did not match, then it was too early to try matching it. - if (groupInfo.DefaultSeries.Id != seriesId) { - Logger.LogInformation("Selected series is not the same as the of the default series in the group. Ignoring series. (Series={SeriesId},Group={GroupId})", seriesId, groupInfo.Id); - return; - } - - // Get the existing seasons and episode ids - var (seasons, episodeIds) = GetExistingSeasonsAndEpisodeIds(series); + if (!ApiManager.TryLockActionForIdOFType("series", seriesId)) + return; - // Add missing seasons - foreach (var (seasonNumber, season) in CreateMissingSeasons(groupInfo, series, seasons)) { - seasons.TryAdd(seasonNumber, season); - } + try { - // Handle specials when grouped. - if (seasons.TryGetValue(0, out var zeroSeason)) { - foreach (var seriesInfo in groupInfo.SeriesList) { - foreach (var episodeInfo in seriesInfo.SpecialsList) { - if (episodeIds.Contains(episodeInfo.Id)) - continue; + // Provide metadata for a series using Shoko's Group feature + if (Plugin.Instance.Configuration.SeriesGrouping == Ordering.GroupType.ShokoGroup) { + var groupInfo = ApiManager.GetGroupInfoForSeriesSync(seriesId, Plugin.Instance.Configuration.FilterOnLibraryTypes ? Ordering.GroupFilterType.Others : Ordering.GroupFilterType.Default); + if (groupInfo == null) { + Logger.LogWarning("Unable to find group info for series. (Series={SeriesID})", seriesId); + return; + } - AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, zeroSeason); + // Get the existing seasons and episode ids + var (seasons, episodeIds) = GetExistingSeasonsAndEpisodeIds(series); + + // Add missing seasons + foreach (var (seasonNumber, season) in CreateMissingSeasons(groupInfo, series, seasons)) + seasons.TryAdd(seasonNumber, season); + + // Handle specials when grouped. + if (seasons.TryGetValue(0, out var zeroSeason)) { + var seasonId = $"{seriesId}:0"; + if (ApiManager.TryLockActionForIdOFType("season", seasonId)) { + try { + foreach (var seriesInfo in groupInfo.SeriesList) { + foreach (var episodeInfo in seriesInfo.SpecialsList) { + if (episodeIds.Contains(episodeInfo.Id)) + continue; + + AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, zeroSeason); + } + } + } + finally { + ApiManager.TryUnlockActionForIdOFType("season", seasonId); + } } - } - } - // Add missing episodes - foreach (var (seriesInfo, index) in groupInfo.SeriesList.Select((s, i) => (s, i))) { - var value = index - groupInfo.DefaultSeriesIndex; - var seasonNumber = value < 0 ? value : value + 1; - if (!seasons.TryGetValue(seasonNumber, out var season) || season == null) - continue; + } - foreach (var episodeInfo in seriesInfo.EpisodeList) { - if (episodeIds.Contains(episodeInfo.Id)) + // Add missing episodes + foreach (var (seriesInfo, index) in groupInfo.SeriesList.Select((s, i) => (s, i))) { + var value = index - groupInfo.DefaultSeriesIndex; + var seasonNumber = value < 0 ? value : value + 1; + if (!seasons.TryGetValue(seasonNumber, out var season) || season == null) continue; - AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, season); + var seasonId = $"{seriesId}:{seasonNumber}"; + if (ApiManager.TryLockActionForIdOFType("season", seasonId)) { + try { + foreach (var episodeInfo in seriesInfo.EpisodeList) { + if (episodeIds.Contains(episodeInfo.Id)) + continue; + + AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, season); + } + } + finally { + ApiManager.TryUnlockActionForIdOFType("season", seasonId); + } + } } } - } - // Provide metadata for other series - else { - var seriesInfo = ApiManager.GetSeriesInfoSync(seriesId); - if (seriesInfo == null) { - Logger.LogWarning("Unable to find series info. (Series={SeriesID})", seriesId); - return; - } + // Provide metadata for other series + else { + var seriesInfo = ApiManager.GetSeriesInfoSync(seriesId); + if (seriesInfo == null) { + Logger.LogWarning("Unable to find series info. (Series={SeriesID})", seriesId); + return; + } - // Get the existing seasons and episode ids - var (seasons, episodeIds) = GetExistingSeasonsAndEpisodeIds(series); + // Get the existing seasons and episode ids + var (seasons, episodeIds) = GetExistingSeasonsAndEpisodeIds(series); - // Compute the season numbers for each episode in the series in advance, since we need to filter out the missing seasons - var episodeInfoToSeasonNumberDirectory = seriesInfo.EpisodeList.ToDictionary(e => e, e => Ordering.GetSeasonNumber(null, seriesInfo, e)); + // Compute the season numbers for each episode in the series in advance, since we need to filter out the missing seasons + var episodeInfoToSeasonNumberDirectory = seriesInfo.RawEpisodeList.ToDictionary(e => e, e => Ordering.GetSeasonNumber(null, seriesInfo, e)); - // Add missing seasons - var allKnownSeasonNumbers = episodeInfoToSeasonNumberDirectory.Values.Distinct().ToList(); - foreach (var (seasonNumber, season) in CreateMissingSeasons(series, seasons, allKnownSeasonNumbers)) { - seasons.Add(seasonNumber, season); - } + // Add missing seasons + var allKnownSeasonNumbers = episodeInfoToSeasonNumberDirectory.Values.Distinct().ToList(); + foreach (var (seasonNumber, season) in CreateMissingSeasons(series, seasons, allKnownSeasonNumbers)) + seasons.Add(seasonNumber, season); - // Add missing episodes - foreach (var episodeInfo in seriesInfo.EpisodeList) { - if (episodeIds.Contains(episodeInfo.Id)) - continue; + // Add missing episodes + foreach (var episodeInfo in seriesInfo.RawEpisodeList) { + if (episodeInfo.ExtraType != null) + continue; - var seasonNumber = episodeInfoToSeasonNumberDirectory[episodeInfo]; - if (!seasons.TryGetValue(seasonNumber, out var season) || season == null) - continue; + if (episodeIds.Contains(episodeInfo.Id)) + continue; + + var seasonNumber = episodeInfoToSeasonNumberDirectory[episodeInfo]; + if (!seasons.TryGetValue(seasonNumber, out var season) || season == null) + continue; - AddVirtualEpisode(null, seriesInfo, episodeInfo, season); + AddVirtualEpisode(null, seriesInfo, episodeInfo, season); + } } } + finally { + ApiManager.TryUnlockActionForIdOFType("series", seriesId); + } } private bool IsEnabledForSeason(Season season, out string seriesId) @@ -274,71 +386,81 @@ private void HandleSeason(Season season, Series series, bool deleted = false) if (!IsEnabledForSeason(season, out var seriesId)) return; - var seasonNumber = season.IndexNumber!.Value; - var existingEpisodes = new HashSet(); - // Get a hash-set of existing episodes – both physical and virtual – to exclude when adding new virtual episodes. - foreach (var episode in season.Children.OfType()) - if (IsEnabledForEpisode(episode, out var episodeId)) - existingEpisodes.Add(episodeId); - - Info.GroupInfo groupInfo = null; - Info.SeriesInfo seriesInfo = null; - // Provide metadata for a season using Shoko's Group feature - if (Plugin.Instance.Configuration.SeriesGrouping == Ordering.GroupType.ShokoGroup) { - groupInfo = ApiManager.GetGroupInfoForSeriesSync(seriesId, Plugin.Instance.Configuration.FilterOnLibraryTypes ? Ordering.GroupFilterType.Others : Ordering.GroupFilterType.Default); - if (groupInfo == null) { - Logger.LogWarning("Unable to find group info for series. (Series={SeriesId})", seriesId); + var seasonId = $"{seriesId}:{season.IndexNumber.Value}"; + try { + if (!ApiManager.TryLockActionForIdOFType("season", seasonId)) return; - } - // Handle specials when grouped. - if (seasonNumber == 0) { - if (deleted) - season = AddVirtualSeason(0, series); + var seasonNumber = season.IndexNumber!.Value; + var existingEpisodes = new HashSet(); + // Get a hash-set of existing episodes – both physical and virtual – to exclude when adding new virtual episodes. + foreach (var episode in season.Children.OfType()) + if (IsEnabledForEpisode(episode, out var episodeId)) + existingEpisodes.Add(episodeId); + + Info.GroupInfo groupInfo = null; + Info.SeriesInfo seriesInfo = null; + // Provide metadata for a season using Shoko's Group feature + if (Plugin.Instance.Configuration.SeriesGrouping == Ordering.GroupType.ShokoGroup) { + groupInfo = ApiManager.GetGroupInfoForSeriesSync(seriesId, Plugin.Instance.Configuration.FilterOnLibraryTypes ? Ordering.GroupFilterType.Others : Ordering.GroupFilterType.Default); + if (groupInfo == null) { + Logger.LogWarning("Unable to find group info for series. (Series={SeriesId})", seriesId); + return; + } + + // Handle specials when grouped. + if (seasonNumber == 0) { + if (deleted) + season = AddVirtualSeason(0, series); - foreach (var sI in groupInfo.SeriesList) { - foreach (var episodeInfo in sI.SpecialsList) { - if (existingEpisodes.Contains(episodeInfo.Id)) - continue; + foreach (var sI in groupInfo.SeriesList) { + foreach (var episodeInfo in sI.SpecialsList) { + if (existingEpisodes.Contains(episodeInfo.Id)) + continue; - AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, season); + AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, season); + } } + + return; } - return; - } + seriesInfo = groupInfo.GetSeriesInfoBySeasonNumber(seasonNumber); + if (seriesInfo == null) { + Logger.LogWarning("Unable to find series info for {SeasonNumber} in group for series. (Group={GroupId})", seasonNumber, groupInfo.Id); + return; + } - seriesInfo = groupInfo.GetSeriesInfoBySeasonNumber(seasonNumber); - if (seriesInfo == null) { - Logger.LogWarning("Unable to find series info for {SeasonNumber} in group for series. (Group={GroupId})", seasonNumber, groupInfo.Id); - return; + if (deleted) + season = AddVirtualSeason(seriesInfo, seasonNumber, series); } + // Provide metadata for other seasons + else { + seriesInfo = ApiManager.GetSeriesInfoSync(seriesId); + if (seriesInfo == null) { + Logger.LogWarning("Unable to find series info. (Series={SeriesId})", seriesId); + return; + } - if (deleted) - season = AddVirtualSeason(seriesInfo, seasonNumber, series); - } - // Provide metadata for other seasons - else { - seriesInfo = ApiManager.GetSeriesInfoSync(seriesId); - if (seriesInfo == null) { - Logger.LogWarning("Unable to find series info. (Series={SeriesId})", seriesId); - return; + if (deleted) + season = AddVirtualSeason(seasonNumber, series); } - if (deleted) - season = AddVirtualSeason(seasonNumber, series); - } - - foreach (var episodeInfo in seriesInfo.EpisodeList) { - var episodeParentIndex = Ordering.GetSeasonNumber(groupInfo, seriesInfo, episodeInfo); - if (episodeParentIndex != seasonNumber) - continue; + foreach (var episodeInfo in seriesInfo.EpisodeList) { + var episodeParentIndex = Ordering.GetSeasonNumber(groupInfo, seriesInfo, episodeInfo); + if (episodeParentIndex != seasonNumber) + continue; - if (existingEpisodes.Contains(episodeInfo.Id)) - continue; + if (existingEpisodes.Contains(episodeInfo.Id)) + continue; - AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, season); + AddVirtualEpisode(groupInfo, seriesInfo, episodeInfo, season); + } } + finally { + ApiManager.TryUnlockActionForIdOFType("season", seasonId); + } + } private bool IsEnabledForEpisode(Episode episode, out string episodeId) @@ -388,7 +510,10 @@ private void HandleEpisode(Episode episode) { var missingSeasonNumbers = allSeasonNumbers.Except(existingSeasons.Keys).ToList(); foreach (var seasonNumber in missingSeasonNumbers) { - yield return (seasonNumber, AddVirtualSeason(seasonNumber, series)); + var season = AddVirtualSeason(seasonNumber, series); + if (season == null) + continue; + yield return (seasonNumber, season); } } @@ -403,6 +528,8 @@ private void HandleEpisode(Episode episode) if (s.SpecialsList.Count > 0) hasSpecials = true; var season = AddVirtualSeason(s, seasonNumber, series); + if (season == null) + continue; yield return (seasonNumber, season); } if (hasSpecials && !seasons.ContainsKey(0)) @@ -411,6 +538,19 @@ private void HandleEpisode(Episode episode) private Season AddVirtualSeason(int seasonNumber, Series series) { + var seriesPresentationUniqueKey = series.GetPresentationUniqueKey(); + var searchList = LibraryManager.GetItemList(new InternalItemsQuery { + IncludeItemTypes = new [] { nameof (Season) }, + IndexNumber = seasonNumber, + SeriesPresentationUniqueKey = seriesPresentationUniqueKey, + DtoOptions = new DtoOptions(true), + }, true); + + if (searchList.Count > 0) { + Logger.LogDebug("Season {SeasonName} for Series {SeriesName} was created in another concurrent thread, skipping.", searchList[0].Name, series.Name); + return null; + } + string seasonName; if (seasonNumber == 0) seasonName = LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName; @@ -421,7 +561,7 @@ private Season AddVirtualSeason(int seasonNumber, Series series) Logger.LogInformation("Creating virtual season {SeasonName} entry for {SeriesName}", seasonName, series.Name); - var result = new Season { + var season = new Season { Name = seasonName, IndexNumber = seasonNumber, SortName = seasonName, @@ -437,19 +577,32 @@ private Season AddVirtualSeason(int seasonNumber, Series series) DateLastSaved = DateTime.UtcNow, }; - series.AddChild(result, CancellationToken.None); + series.AddChild(season, CancellationToken.None); - return result; + return season; } private Season AddVirtualSeason(Info.SeriesInfo seriesInfo, int seasonNumber, Series series) { + var seriesPresentationUniqueKey = series.GetPresentationUniqueKey(); + var searchList = LibraryManager.GetItemList(new InternalItemsQuery { + IncludeItemTypes = new [] { nameof (Season) }, + HasAnyProviderId = { ["Shoko Series"] = seriesInfo.Id }, + SeriesPresentationUniqueKey = seriesPresentationUniqueKey, + DtoOptions = new DtoOptions(true), + }, true); + + if (searchList.Count > 0) { + Logger.LogDebug("Season {SeasonName} for Series {SeriesName} was created in another concurrent thread, skipping.", searchList[0].Name, series.Name); + return null; + } + var tags = ApiManager.GetTags(seriesInfo.Id).GetAwaiter().GetResult(); var ( displayTitle, alternateTitle ) = Text.GetSeriesTitles(seriesInfo.AniDB.Titles, seriesInfo.Shoko.Name, series.GetPreferredMetadataLanguage()); var sortTitle = $"S{seasonNumber} - {seriesInfo.Shoko.Name}"; Logger.LogInformation("Adding virtual season {SeasonName} entry for {SeriesName}", displayTitle, series.Name); - var result = new Season { + var season = new Season { Name = displayTitle, OriginalTitle = alternateTitle, IndexNumber = seasonNumber, @@ -467,26 +620,30 @@ private Season AddVirtualSeason(Info.SeriesInfo seriesInfo, int seasonNumber, Se CommunityRating = seriesInfo.AniDB.Rating?.ToFloat(10), SeriesId = series.Id, SeriesName = series.Name, - SeriesPresentationUniqueKey = series.GetPresentationUniqueKey(), + SeriesPresentationUniqueKey = seriesPresentationUniqueKey, DateModified = DateTime.UtcNow, DateLastSaved = DateTime.UtcNow, }; - result.ProviderIds.Add("Shoko Series", seriesInfo.Id); + season.ProviderIds.Add("Shoko Series", seriesInfo.Id); if (Plugin.Instance.Configuration.AddAniDBId) - result.ProviderIds.Add("AniDB", seriesInfo.AniDB.ID.ToString()); + season.ProviderIds.Add("AniDB", seriesInfo.AniDB.ID.ToString()); - series.AddChild(result, CancellationToken.None); + series.AddChild(season, CancellationToken.None); - return result; + return season; } private void AddVirtualEpisode(Info.GroupInfo groupInfo, Info.SeriesInfo seriesInfo, Info.EpisodeInfo episodeInfo, MediaBrowser.Controller.Entities.TV.Season season) { var groupId = groupInfo?.Id ?? null; - var results = LibraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new [] { nameof (Episode) }, HasAnyProviderId = { ["Shoko Episode"] = episodeInfo.Id }, DtoOptions = new DtoOptions(true) }, true); - - if (results.Count > 0) { - Logger.LogWarning("A virtual or physical episode entry already exists. Ignoreing. (Episode={EpisodeId},Series={SeriesId},Group={GroupId})", episodeInfo.Id, seriesInfo.Id, groupId); + var searchList = LibraryManager.GetItemList(new InternalItemsQuery { + IncludeItemTypes = new [] { nameof (Episode) }, + HasAnyProviderId = { ["Shoko Episode"] = episodeInfo.Id }, + DtoOptions = new DtoOptions(true) + }, true); + + if (searchList.Count > 0) { + Logger.LogDebug("A virtual or physical episode entry already exists. Ignoreing. (Episode={EpisodeId},Series={SeriesId},Group={GroupId})", episodeInfo.Id, seriesInfo.Id, groupId); return; } diff --git a/Shokofin/Providers/SeriesProvider.cs b/Shokofin/Providers/SeriesProvider.cs index f729a34c..a530d831 100644 --- a/Shokofin/Providers/SeriesProvider.cs +++ b/Shokofin/Providers/SeriesProvider.cs @@ -71,11 +71,7 @@ private async Task> GetDefaultMetadata(SeriesInfo info, C Tags = tags, CommunityRating = series.AniDB.Rating.ToFloat(10), }; - result.Item.SetProviderId("Shoko Series", series.Id); - if (Plugin.Instance.Configuration.AddAniDBId) - result.Item.SetProviderId("AniDB", series.AniDB.ID.ToString()); - if (Plugin.Instance.Configuration.SeriesGrouping == Ordering.GroupType.MergeFriendly && !string.IsNullOrEmpty(series.TvDBId)) - result.Item.SetProviderId(MetadataProvider.Tvdb, series.TvDBId); + AddProviderIds(result.Item, series.Id, null, series.AniDB.ID.ToString(), Plugin.Instance.Configuration.SeriesGrouping == Ordering.GroupType.MergeFriendly ? series.TvDBId : null); result.HasMetadata = true; @@ -112,12 +108,7 @@ private async Task> GetShokoGroupedMetadata(SeriesInfo in Tags = tags, CommunityRating = series.AniDB.Rating.ToFloat(10), }; - // NOTE: This next line will remain here till they fix the series merging for providers outside the MetadataProvider enum. - result.Item.SetProviderId(MetadataProvider.Imdb, $"INVALID-BUT-DO-NOT-TOUCH:{series.Id}"); - result.Item.SetProviderId("Shoko Series", series.Id); - result.Item.SetProviderId("Shoko Group", group.Id); - if (Plugin.Instance.Configuration.AddAniDBId) - result.Item.SetProviderId("AniDB", series.AniDB.ID.ToString()); + AddProviderIds(result.Item, series.Id, group.Id, series.AniDB.ID.ToString(), null); result.HasMetadata = true; @@ -128,6 +119,21 @@ private async Task> GetShokoGroupedMetadata(SeriesInfo in return result; } + public static void AddProviderIds(Series series, string seriesId, string groupId = null, string aniDbId = null, string tvDbId = null) + { + // NOTE: These next two lines will remain here till _someone_ fix the series merging for providers other then TvDB and ImDB in Jellyfin. + if (string.IsNullOrEmpty(tvDbId)) + series.SetProviderId(MetadataProvider.Imdb, $"INVALID-BUT-DO-NOT-TOUCH:{seriesId}"); + + series.SetProviderId("Shoko Series", seriesId); + if (!string.IsNullOrEmpty(groupId)) + series.SetProviderId("Shoko Group", groupId); + if (Plugin.Instance.Configuration.AddAniDBId && !string.IsNullOrEmpty(aniDbId)) + series.SetProviderId("AniDB", aniDbId); + if (!string.IsNullOrEmpty(tvDbId)) + series.SetProviderId(MetadataProvider.Tvdb, tvDbId); + } + public async Task> GetSearchResults(SeriesInfo info, CancellationToken cancellationToken) { try {