diff options
| author | JPVenson <github@jpb.email> | 2025-09-16 21:08:04 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-16 13:08:04 -0600 |
| commit | a0b3e2b071509f440db10768f6f8984c7ea382d6 (patch) | |
| tree | 3f0244dc6002796b98f573f4570eb02aa248282b /Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | |
| parent | 2618a5fba23432c89882bf343f481f4248ae7ab3 (diff) | |
Optimize internal querying of UserData, other fixes (#14795)
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 104 |
1 files changed, 82 insertions, 22 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 1b55d2dc7..30de711ea 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -111,13 +111,15 @@ public sealed class BaseItemRepository var date = (DateTime?)DateTime.UtcNow; + var relatedItems = TraverseHirachyDown(id, context).ToArray(); + // Remove any UserData entries for the placeholder item that would conflict with the UserData // being detached from the item being deleted. This is necessary because, during an update, // UserData may be reattached to a new entry, but some entries can be left behind. // Ensures there are no duplicate UserId/CustomDataKey combinations for the placeholder. context.UserData .Join( - context.UserData.Where(e => e.ItemId == id), + context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId), placeholder => new { placeholder.UserId, placeholder.CustomDataKey }, userData => new { userData.UserId, userData.CustomDataKey }, (placeholder, userData) => placeholder) @@ -125,29 +127,31 @@ public sealed class BaseItemRepository .ExecuteDelete(); // Detach all user watch data - context.UserData.Where(e => e.ItemId == id) + context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId) .ExecuteUpdate(e => e .SetProperty(f => f.RetentionDate, date) .SetProperty(f => f.ItemId, PlaceholderId)); - context.AncestorIds.Where(e => e.ItemId == id || e.ParentItemId == id).ExecuteDelete(); - context.AttachmentStreamInfos.Where(e => e.ItemId == id).ExecuteDelete(); - context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete(); - context.BaseItemMetadataFields.Where(e => e.ItemId == id).ExecuteDelete(); - context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete(); - context.BaseItemTrailerTypes.Where(e => e.ItemId == id).ExecuteDelete(); - context.BaseItems.Where(e => e.Id == id).ExecuteDelete(); - context.Chapters.Where(e => e.ItemId == id).ExecuteDelete(); - context.CustomItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete(); - context.ItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete(); + context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ParentItemId).ExecuteDelete(); + context.AttachmentStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemImageInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemMetadataFields.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemProviders.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItemTrailerTypes.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.BaseItems.WhereOneOrMany(relatedItems, e => e.Id).ExecuteDelete(); + context.Chapters.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.CustomItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.ItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete(); - context.ItemValuesMap.Where(e => e.ItemId == id).ExecuteDelete(); - context.KeyframeData.Where(e => e.ItemId == id).ExecuteDelete(); - context.MediaSegments.Where(e => e.ItemId == id).ExecuteDelete(); - context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete(); - context.PeopleBaseItemMap.Where(e => e.ItemId == id).ExecuteDelete(); - context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete(); - context.TrickplayInfos.Where(e => e.ItemId == id).ExecuteDelete(); + context.ItemValuesMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.KeyframeData.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.MediaSegments.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.MediaStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + var query = context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).Select(f => f.PeopleId).Distinct().ToArray(); + context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); + context.Peoples.WhereOneOrMany(query, e => e.Id).Where(e => e.BaseItems!.Count == 0).ExecuteDelete(); + context.TrickplayInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); context.SaveChanges(); transaction.Commit(); } @@ -434,7 +438,8 @@ public sealed class BaseItemRepository dbQuery = dbQuery.AsSingleQuery() .Include(e => e.TrailerTypes) .Include(e => e.Provider) - .Include(e => e.LockedFields); + .Include(e => e.LockedFields) + .Include(e => e.UserData); if (filter.DtoOptions.EnableImages) { @@ -745,8 +750,9 @@ public sealed class BaseItemRepository /// <param name="entity">The entity.</param> /// <param name="dto">The dto base instance.</param> /// <param name="appHost">The Application server Host.</param> + /// <param name="logger">The applogger.</param> /// <returns>The dto to map.</returns> - public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost) + public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost, ILogger logger) { dto.Id = entity.Id; dto.ParentId = entity.ParentId.GetValueOrDefault(); @@ -791,6 +797,8 @@ public sealed class BaseItemRepository dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty); dto.Width = entity.Width.GetValueOrDefault(); dto.Height = entity.Height.GetValueOrDefault(); + dto.UserData = entity.UserData; + if (entity.Provider is not null) { dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue); @@ -1144,7 +1152,7 @@ public sealed class BaseItemRepository dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialize unknown type."); } - return Map(baseItemEntity, dto, appHost); + return Map(baseItemEntity, dto, appHost, logger); } private QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyList<ItemValueType> itemValueTypes, string returnType) @@ -2449,4 +2457,56 @@ public sealed class BaseItemRepository return await dbContext.BaseItems.AnyAsync(f => f.Id == id).ConfigureAwait(false); } } + + /// <inheritdoc/> + public bool GetIsPlayed(User user, Guid id, bool recursive) + { + using var dbContext = _dbProvider.CreateDbContext(); + + if (recursive) + { + var folderList = TraverseHirachyDown(id, dbContext, item => (item.IsFolder || item.IsVirtualItem)); + + return dbContext.BaseItems + .Where(e => folderList.Contains(e.ParentId!.Value) && !e.IsFolder && !e.IsVirtualItem) + .All(f => f.UserData!.Any(e => e.UserId == user.Id && e.Played)); + } + + return dbContext.BaseItems.Where(e => e.ParentId == id).All(f => f.UserData!.Any(e => e.UserId == user.Id && e.Played)); + } + + private static HashSet<Guid> TraverseHirachyDown(Guid parentId, JellyfinDbContext dbContext, Expression<Func<BaseItemEntity, bool>>? filter = null) + { + var folderStack = new HashSet<Guid>() + { + parentId + }; + var folderList = new HashSet<Guid>() + { + parentId + }; + + while (folderStack.Count != 0) + { + var items = folderStack.ToArray(); + folderStack.Clear(); + var query = dbContext.BaseItems + .WhereOneOrMany(items, e => e.ParentId!.Value); + + if (filter != null) + { + query = query.Where(filter); + } + + foreach (var item in query.Select(e => e.Id).ToArray()) + { + if (folderList.Add(item)) + { + folderStack.Add(item); + } + } + } + + return folderList; + } } |
