diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index aef02ce6bf..9646f13e94 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -144,9 +144,15 @@ namespace Emby.Server.Implementations.EntryPoints .Select(i => { var dto = _userDataManager.GetUserDataDto(i, user); + if (dto is null) + { + return null!; + } + dto.ItemId = i.Id; return dto; }) + .Where(e => e is not null) .ToArray() }; } diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index aec2773e31..371fc22c76 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -224,13 +224,18 @@ namespace Emby.Server.Implementations.Library } /// - public UserItemDataDto GetUserDataDto(BaseItem item, User user) + public UserItemDataDto? GetUserDataDto(BaseItem item, User user) => GetUserDataDto(item, null, user, new DtoOptions()); /// - public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options) + public UserItemDataDto? GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options) { - var userData = GetUserData(user, item) ?? throw new InvalidOperationException("Did not expect UserData to be null."); + var userData = GetUserData(user, item); + if (userData is null) + { + return null; + } + var dto = GetUserItemDataDto(userData); item.FillUserDataDtoValues(dto, userData, itemDto, user, options); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 828bd51740..775d723b0b 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -967,7 +967,7 @@ public class ItemsController : BaseJellyfinApiController [HttpGet("UserItems/{itemId}/UserData")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetItemUserData( + public ActionResult GetItemUserData( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId) { @@ -1005,7 +1005,7 @@ public class ItemsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult GetItemUserDataLegacy( + public ActionResult GetItemUserDataLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) => GetItemUserData(userId, itemId); @@ -1022,7 +1022,7 @@ public class ItemsController : BaseJellyfinApiController [HttpPost("UserItems/{itemId}/UserData")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateItemUserData( + public ActionResult UpdateItemUserData( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId, [FromBody, Required] UpdateUserItemDataDto userDataDto) @@ -1064,7 +1064,7 @@ public class ItemsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult UpdateItemUserDataLegacy( + public ActionResult UpdateItemUserDataLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromBody, Required] UpdateUserItemDataDto userDataDto) diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index 88aa0178f9..292344c9dd 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -513,7 +513,7 @@ public class PlaystateController : BaseJellyfinApiController item.MarkUnplayed(user); } - return _userDataRepository.GetUserDataDto(item, user); + return _userDataRepository.GetUserDataDto(item, user)!; } private PlayMethod ValidatePlayMethod(PlayMethod method, string? playSessionId) diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index b34daba7f3..5330db48bf 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -670,7 +670,7 @@ public class UserLibraryController : BaseJellyfinApiController _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); } - return _userDataRepository.GetUserDataDto(item, user); + return _userDataRepository.GetUserDataDto(item, user)!; } /// @@ -691,6 +691,6 @@ public class UserLibraryController : BaseJellyfinApiController _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); } - return _userDataRepository.GetUserDataDto(item, user); + return _userDataRepository.GetUserDataDto(item, user)!; } } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index aca5c071a3..d862ecf6ce 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -227,8 +227,8 @@ public sealed class BaseItemRepository( .Include(e => e.Provider) .Include(e => e.Images) .Include(e => e.LockedFields); - dbQuery = TranslateQuery(dbQuery, context, filter) - .DistinctBy(e => e.Id); + dbQuery = TranslateQuery(dbQuery, context, filter); + // .DistinctBy(e => e.Id); if (filter.EnableTotalRecordCount) { result.TotalRecordCount = dbQuery.Count(); @@ -1040,7 +1040,7 @@ public sealed class BaseItemRepository( } else { - baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w == e.TopParentId!.Value)); + baseQuery = baseQuery.Where(e => queryTopParentIds.Contains(e.TopParentId!.Value)); } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs index ec0fbddb67..571ac95eba 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs @@ -79,7 +79,7 @@ public class MigrateLibraryDb : IMigrationRoutine stopwatch.Restart(); _logger.LogInformation("Start moving TypedBaseItem."); - var typedBaseItemsQuery = "SELECT guid, type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, Genres, ParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems"; + var typedBaseItemsQuery = "SELECT guid, type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, Genres, ParentId, TopParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems"; dbContext.BaseItems.ExecuteDelete(); var legacyBaseItemWithUserKeys = new Dictionary(); @@ -798,6 +798,11 @@ public class MigrateLibraryDb : IMigrationRoutine entity.ParentId = parentId; } + if (reader.TryGetGuid(index++, out var topParentId)) + { + entity.TopParentId = topParentId; + } + if (reader.TryGetString(index++, out var audioString) && Enum.TryParse(audioString, out var audioType)) { entity.Audio = audioType; diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index b43c62708f..5a2deda66a 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Library /// Item to use. /// User to use. /// User data dto. - UserItemDataDto GetUserDataDto(BaseItem item, User user); + UserItemDataDto? GetUserDataDto(BaseItem item, User user); /// /// Gets the user data dto. @@ -62,7 +62,7 @@ namespace MediaBrowser.Controller.Library /// User to use. /// Dto options to use. /// User data dto. - UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options); + UserItemDataDto? GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options); /// /// Updates playstate for an item and returns true or false indicating if it was played to completion.