Skip to content

Commit

Permalink
fix: improve logic in the episode user data migration task
Browse files Browse the repository at this point in the history
  • Loading branch information
revam committed Jul 15, 2024
1 parent eada3b4 commit 4679c51
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 47 deletions.
69 changes: 59 additions & 10 deletions Shokofin/Sync/SyncExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
58 changes: 21 additions & 37 deletions Shokofin/Tasks/MigrateEpisodeUserDataTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,17 @@ public class MigrateEpisodeUserDataTask : IScheduledTask, IConfigurableScheduled

private readonly ILibraryManager LibraryManager;

private readonly IIdLookup Lookup;

public MigrateEpisodeUserDataTask(
ILogger<MigrateEpisodeUserDataTask> logger,
IUserDataManager userDataManager,
IUserManager userManager,
ILibraryManager libraryManager,
IIdLookup lookup
ILibraryManager libraryManager
)
{
Logger = logger;
UserDataManager = userDataManager;
UserManager = userManager;
LibraryManager = libraryManager;
Lookup = lookup;
}

public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
Expand All @@ -71,7 +67,7 @@ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
var foundEpisodeCount = 0;
var seriesDict = new Dictionary<Guid, (Series series, List<Episode> episodes)>();
var seriesDict = new Dictionary<string, (Series series, List<Episode> episodes)>();
var users = UserManager.Users.ToList();
var allEpisodes = LibraryManager.GetItemList(new InternalItemsQuery {
IncludeItemTypes = [BaseItemKind.Episode],
Expand All @@ -86,37 +82,33 @@ public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellat
})
.OfType<Episode>()
.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<UserItemData>()));
var userDataToRemove = new List<UserItemData>();
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)
Expand All @@ -125,37 +117,29 @@ public Task ExecuteAsync(IProgress<double> 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++;
Expand All @@ -170,7 +154,7 @@ public Task ExecuteAsync(IProgress<double> 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;
Expand Down

0 comments on commit 4679c51

Please sign in to comment.