From 4679c51b96bc775b7b43d95df1745eb4b45e184f Mon Sep 17 00:00:00 2001 From: Mikal Stordal Date: Tue, 16 Jul 2024 01:52:08 +0200 Subject: [PATCH] fix: improve logic in the episode user data migration task --- Shokofin/Sync/SyncExtensions.cs | 69 +++++++++++++++++--- Shokofin/Tasks/MigrateEpisodeUserDataTask.cs | 58 ++++++---------- 2 files changed, 80 insertions(+), 47 deletions(-) diff --git a/Shokofin/Sync/SyncExtensions.cs b/Shokofin/Sync/SyncExtensions.cs index ee0badac..81777bed 100644 --- a/Shokofin/Sync/SyncExtensions.cs +++ b/Shokofin/Sync/SyncExtensions.cs @@ -22,19 +22,68 @@ public static File.UserStats ToFileUserStats(this UserItemData userData) }; } - public static void CopyFrom(this UserItemData userData, UserItemData otherUserData) + public static bool CopyFrom(this UserItemData userData, UserItemData otherUserData) { - userData.LastPlayedDate = otherUserData.LastPlayedDate; - userData.IsFavorite = otherUserData.IsFavorite; - userData.AudioStreamIndex = otherUserData.AudioStreamIndex; - userData.Likes = otherUserData.Likes; - userData.PlaybackPositionTicks = otherUserData.PlaybackPositionTicks; - userData.PlayCount = otherUserData.PlayCount; - userData.Played = otherUserData.Played; - userData.Rating = otherUserData.Rating; - userData.SubtitleStreamIndex = otherUserData.SubtitleStreamIndex; + var updated = false; + + if (!userData.Rating.HasValue && otherUserData.Rating.HasValue || userData.Rating.HasValue && otherUserData.Rating.HasValue && userData.Rating != otherUserData.Rating) + { + userData.Rating = otherUserData.Rating; + updated = true; + } + + if (userData.PlaybackPositionTicks != otherUserData.PlaybackPositionTicks) + { + userData.PlaybackPositionTicks = otherUserData.PlaybackPositionTicks; + updated = true; + } + + if (userData.PlayCount != otherUserData.PlayCount) + { + userData.PlayCount = otherUserData.PlayCount; + updated = true; + } + + if (!userData.IsFavorite != otherUserData.IsFavorite) + { + userData.IsFavorite = otherUserData.IsFavorite; + updated = true; + } + + if (!userData.LastPlayedDate.HasValue && otherUserData.LastPlayedDate.HasValue || userData.LastPlayedDate.HasValue && otherUserData.LastPlayedDate.HasValue && userData.LastPlayedDate < otherUserData.LastPlayedDate) + { + userData.LastPlayedDate = otherUserData.LastPlayedDate; + updated = true; + } + + if (userData.Played != otherUserData.Played) + { + userData.Played = otherUserData.Played; + updated = true; + } + + if (!userData.AudioStreamIndex.HasValue && otherUserData.AudioStreamIndex.HasValue || userData.AudioStreamIndex.HasValue && otherUserData.AudioStreamIndex.HasValue && userData.AudioStreamIndex != otherUserData.AudioStreamIndex) + { + userData.AudioStreamIndex = otherUserData.AudioStreamIndex; + updated = true; + } + + if (!userData.SubtitleStreamIndex.HasValue && otherUserData.SubtitleStreamIndex.HasValue || userData.SubtitleStreamIndex.HasValue && otherUserData.SubtitleStreamIndex.HasValue && userData.SubtitleStreamIndex != otherUserData.SubtitleStreamIndex) + { + userData.SubtitleStreamIndex = otherUserData.SubtitleStreamIndex; + updated = true; + } + + if (!userData.Likes.HasValue && otherUserData.Likes.HasValue || userData.Likes.HasValue && otherUserData.Likes.HasValue && userData.Likes != otherUserData.Likes) + { + userData.Likes = otherUserData.Likes; + updated = true; + } + + return updated; } + public static UserItemData MergeWithFileUserStats(this UserItemData userData, File.UserStats userStats) { userData.Played = userStats.LastWatchedAt.HasValue; diff --git a/Shokofin/Tasks/MigrateEpisodeUserDataTask.cs b/Shokofin/Tasks/MigrateEpisodeUserDataTask.cs index ea49f4eb..0fac08ab 100644 --- a/Shokofin/Tasks/MigrateEpisodeUserDataTask.cs +++ b/Shokofin/Tasks/MigrateEpisodeUserDataTask.cs @@ -48,21 +48,17 @@ public class MigrateEpisodeUserDataTask : IScheduledTask, IConfigurableScheduled private readonly ILibraryManager LibraryManager; - private readonly IIdLookup Lookup; - public MigrateEpisodeUserDataTask( ILogger logger, IUserDataManager userDataManager, IUserManager userManager, - ILibraryManager libraryManager, - IIdLookup lookup + ILibraryManager libraryManager ) { Logger = logger; UserDataManager = userDataManager; UserManager = userManager; LibraryManager = libraryManager; - Lookup = lookup; } public IEnumerable GetDefaultTriggers() @@ -71,7 +67,7 @@ public IEnumerable GetDefaultTriggers() public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { var foundEpisodeCount = 0; - var seriesDict = new Dictionary episodes)>(); + var seriesDict = new Dictionary episodes)>(); var users = UserManager.Users.ToList(); var allEpisodes = LibraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.Episode], @@ -86,37 +82,33 @@ public Task ExecuteAsync(IProgress progress, CancellationToken cancellat }) .OfType() .ToList(); - Logger.LogInformation("Attempting to migrate user watch data across {EpisodeCount} episodes and {UserCount} users.", allEpisodes.Count, users.Count); + Logger.LogDebug("Attempting to migrate user watch data across {EpisodeCount} episodes and {UserCount} users.", allEpisodes.Count, users.Count); foreach (var episode in allEpisodes) { cancellationToken.ThrowIfCancellationRequested(); if (!episode.ParentIndexNumber.HasValue || !episode.IndexNumber.HasValue || - !Lookup.TryGetFileIdFor(episode, out var fileId) || episode.Series is not Series series) + !episode.TryGetProviderId(ShokoFileId.Name, out var fileId) || + episode.Series is not Series series || !series.TryGetProviderId(ShokoSeriesId.Name, out var seriesId)) continue; - if (!seriesDict.TryGetValue(series.Id, out var tuple)) - seriesDict[series.Id] = tuple = (series, []); + + if (!seriesDict.TryGetValue(seriesId, out var tuple)) + seriesDict[seriesId] = tuple = (series, []); tuple.episodes.Add(episode); foundEpisodeCount++; } - Logger.LogInformation("Found {SeriesCount} series and {EpisodeCount} episodes across {AllEpisodeCount} initial episodes to search for user watch data.", foundEpisodeCount, allEpisodes.Count); + Logger.LogInformation("Found {SeriesCount} series and {EpisodeCount} episodes across {AllEpisodeCount} total episodes to search for user watch data to migrate.", seriesDict.Count, foundEpisodeCount, allEpisodes.Count); var savedCount = 0; var numComplete = 0; var numTotal = foundEpisodeCount * users.Count; var userDataDict = users.ToDictionary(user => user, user => (UserDataManager.GetAllUserData(user.Id).DistinctBy(data => data.Key).ToDictionary(data => data.Key), new List())); var userDataToRemove = new List(); - foreach (var (series, episodes) in seriesDict.Values) { + foreach (var (seriesId, (series, episodes)) in seriesDict) { cancellationToken.ThrowIfCancellationRequested(); - if (!Lookup.TryGetSeriesIdFor(series, out var seriesId)) - continue; - SeriesProvider.AddProviderIds(series, seriesId); var seriesUserKeys = series.GetUserDataKeys(); - if (seriesUserKeys.Count > 1) - seriesUserKeys = seriesUserKeys.TakeLast(1).ToList(); - // 10.9 post-4.1 id format var primaryKey = seriesUserKeys.First(); var keysToSearch = seriesUserKeys.Skip(1) @@ -125,37 +117,29 @@ public Task ExecuteAsync(IProgress progress, CancellationToken cancellat // 10.8 id format .Prepend($"INVALID-BUT-DO-NOT-TOUCH:{seriesId}") .ToList(); - Logger.LogInformation("Migrating user watch data for series {SeriesName}. (Series={SeriesId},Primary={PrimaryKey},Search={SearchKeys})", series.Name, seriesId, primaryKey, keysToSearch); + Logger.LogTrace("Migrating user watch data for series {SeriesName}. (Series={SeriesId},Primary={PrimaryKey},Search={SearchKeys})", series.Name, seriesId, primaryKey, keysToSearch); foreach (var episode in episodes) { cancellationToken.ThrowIfCancellationRequested(); - + if (!episode.TryGetProviderId(ShokoFileId.Name, out var fileId)) continue; var suffix = episode.ParentIndexNumber!.Value.ToString("000", CultureInfo.InvariantCulture) + episode.IndexNumber!.Value.ToString("000", CultureInfo.InvariantCulture); var videoUserDataKeys = (episode as Video).GetUserDataKeys(); - var episodeKeyToUse = primaryKey + suffix; - var episodeKeysToSearch = keysToSearch.Select(key => key + suffix).Concat(videoUserDataKeys).ToList(); - Logger.LogInformation("Migrating user watch data for season {SeasonNumber}, episode {EpisodeNumber} - {EpisodeName}. (Series={SeriesId},File={FileId},Primary={PrimaryKey},Search={SearchKeys})", episode.ParentIndexNumber, episode.IndexNumber, episode.Name, seriesId, fileId, episodeKeyToUse, episodeKeysToSearch); + var episodeKeysToSearch = keysToSearch.Select(key => key + suffix).Prepend(primaryKey + suffix).Concat(videoUserDataKeys).ToList(); + Logger.LogTrace("Migrating user watch data for season {SeasonNumber}, episode {EpisodeNumber} - {EpisodeName}. (Series={SeriesId},File={FileId},Search={SearchKeys})", episode.ParentIndexNumber, episode.IndexNumber, episode.Name, seriesId, fileId, episodeKeysToSearch); foreach (var (user, (dataDict, dataList)) in userDataDict) { var userData = UserDataManager.GetUserData(user, episode); - if (dataDict.TryGetValue(episodeKeyToUse, out var primaryUserData)) { - Logger.LogInformation("Found user data to migrate. (Key={SearchKey})", episodeKeyToUse); - userData.CopyFrom(primaryUserData); - dataList.Add(userData); - savedCount++; - } - else { - foreach (var secondaryKey in episodeKeysToSearch) { - if (!dataDict.TryGetValue(episodeKeyToUse, out var secondaryUserData)) - continue; + foreach (var searchKey in episodeKeysToSearch) { + if (!dataDict.TryGetValue(searchKey, out var searchUserData)) + continue; - Logger.LogInformation("Found user data to migrate. (Key={SearchKey})", secondaryKey); - userData.CopyFrom(secondaryUserData); + if (userData.CopyFrom(searchUserData)) { + Logger.LogInformation("Found user data to migrate. (Series={SeriesId},File={FileId},Search={SearchKeys},Key={SearchKey},User={UserId})", seriesId, fileId, episodeKeysToSearch, searchKey, user.Id); dataList.Add(userData); savedCount++; - break; } + break; } numComplete++; @@ -170,7 +154,7 @@ public Task ExecuteAsync(IProgress progress, CancellationToken cancellat // Last attempt to cancel before we save all the changes. cancellationToken.ThrowIfCancellationRequested(); - Logger.LogInformation("Saving {UserDataCount} user watch data entries across {UserCount} users", savedCount, users.Count); + Logger.LogDebug("Saving {UserDataCount} user watch data entries across {UserCount} users", savedCount, users.Count); foreach (var (user, (dataDict, dataList)) in userDataDict) { if (dataList.Count is 0) continue;