From 077fa89717957f871b172ca4b2dc4a178efd3bc5 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 7 Mar 2026 20:12:42 +0100 Subject: Split BaseItemRepository and IItemRepository --- .../Persistence/IItemCountService.cs | 86 +++++++++ .../Persistence/IItemPersistenceService.cs | 47 +++++ .../Persistence/IItemQueryHelpers.cs | 94 +++++++++ .../Persistence/IItemRepository.cs | 211 +++++---------------- .../Persistence/ILinkedChildrenService.cs | 50 +++++ .../Persistence/INextUpService.cs | 33 ++++ 6 files changed, 353 insertions(+), 168 deletions(-) create mode 100644 MediaBrowser.Controller/Persistence/IItemCountService.cs create mode 100644 MediaBrowser.Controller/Persistence/IItemPersistenceService.cs create mode 100644 MediaBrowser.Controller/Persistence/IItemQueryHelpers.cs create mode 100644 MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs create mode 100644 MediaBrowser.Controller/Persistence/INextUpService.cs (limited to 'MediaBrowser.Controller/Persistence') diff --git a/MediaBrowser.Controller/Persistence/IItemCountService.cs b/MediaBrowser.Controller/Persistence/IItemCountService.cs new file mode 100644 index 0000000000..d57f1fc893 --- /dev/null +++ b/MediaBrowser.Controller/Persistence/IItemCountService.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.Persistence; + +/// +/// Provides item counting and played-status query operations. +/// +public interface IItemCountService +{ + /// + /// Gets the count of items matching the filter. + /// + /// The query filter. + /// The item count. + int GetCount(InternalItemsQuery filter); + + /// + /// Gets item counts grouped by type. + /// + /// The query filter. + /// The item counts by type. + ItemCounts GetItemCounts(InternalItemsQuery filter); + + /// + /// Gets item counts for a "by-name" item using an optimized query. + /// + /// The kind of the name item. + /// The ID of the name item. + /// The item kinds to count. + /// A pre-configured query with user access filtering settings. + /// The item counts grouped by type. + ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, InternalItemsQuery accessFilter); + + /// + /// Gets the count of played items that are descendants of the specified ancestor. + /// + /// The query filter containing user access settings. + /// The ancestor item id. + /// The count of played descendant items. + int GetPlayedCount(InternalItemsQuery filter, Guid ancestorId); + + /// + /// Gets the total count of items that are descendants of the specified ancestor. + /// + /// The query filter containing user access settings. + /// The ancestor item id. + /// The total count of descendant items. + int GetTotalCount(InternalItemsQuery filter, Guid ancestorId); + + /// + /// Gets both the played count and total count of descendant items. + /// + /// The query filter containing user access settings. + /// The ancestor item id. + /// A tuple containing (Played count, Total count). + (int Played, int Total) GetPlayedAndTotalCount(InternalItemsQuery filter, Guid ancestorId); + + /// + /// Gets both the played count and total count from linked children. + /// + /// The query filter containing user access settings. + /// The parent item id. + /// A tuple containing (Played count, Total count). + (int Played, int Total) GetPlayedAndTotalCountFromLinkedChildren(InternalItemsQuery filter, Guid parentId); + + /// + /// Batch-fetches played and total counts for multiple folder items. + /// + /// The list of folder item IDs to get counts for. + /// The user for access filtering and played status. + /// Dictionary mapping folder ID to (Played count, Total count). + Dictionary GetPlayedAndTotalCountBatch(IReadOnlyList folderIds, User user); + + /// + /// Batch-fetches child counts for multiple parent folders. + /// + /// The list of parent folder IDs. + /// The user ID for access filtering. + /// Dictionary mapping parent ID to child count. + Dictionary GetChildCountBatch(IReadOnlyList parentIds, Guid? userId); +} diff --git a/MediaBrowser.Controller/Persistence/IItemPersistenceService.cs b/MediaBrowser.Controller/Persistence/IItemPersistenceService.cs new file mode 100644 index 0000000000..37f7194e7a --- /dev/null +++ b/MediaBrowser.Controller/Persistence/IItemPersistenceService.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Persistence; + +/// +/// Provides item persistence operations (save, delete, update). +/// +public interface IItemPersistenceService +{ + /// + /// Deletes items by their IDs. + /// + /// The IDs to delete. + void DeleteItem(params IReadOnlyList ids); + + /// + /// Saves items to the database. + /// + /// The items to save. + /// The cancellation token. + void SaveItems(IReadOnlyList items, CancellationToken cancellationToken); + + /// + /// Saves image info for an item. + /// + /// The item. + /// The cancellation token. + /// A task representing the asynchronous operation. + Task SaveImagesAsync(BaseItem item, CancellationToken cancellationToken = default); + + /// + /// Reattaches user data entries to the correct item. + /// + /// The item. + /// The cancellation token. + /// A task representing the asynchronous operation. + Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken); + + /// + /// Updates inherited values. + /// + void UpdateInheritedValues(); +} diff --git a/MediaBrowser.Controller/Persistence/IItemQueryHelpers.cs b/MediaBrowser.Controller/Persistence/IItemQueryHelpers.cs new file mode 100644 index 0000000000..45fa92c90b --- /dev/null +++ b/MediaBrowser.Controller/Persistence/IItemQueryHelpers.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Persistence; + +/// +/// Provides shared query-building methods used by extracted item services. +/// Implemented by BaseItemRepository. +/// +public interface IItemQueryHelpers +{ + /// + /// Translates an into EF Core filter expressions. + /// + /// The base queryable to filter. + /// The database context. + /// The query filter. + /// The filtered queryable. + IQueryable TranslateQuery( + IQueryable baseQuery, + JellyfinDbContext context, + InternalItemsQuery filter); + + /// + /// Prepares a base query for items from the context. + /// + /// The database context. + /// The query filter. + /// The prepared queryable. + IQueryable PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter); + + /// + /// Applies user access filtering (library access, parental controls, tags) to a query. + /// + /// The database context. + /// The base queryable to filter. + /// The query filter containing access settings. + /// The access-filtered queryable. + IQueryable ApplyAccessFiltering( + JellyfinDbContext context, + IQueryable baseQuery, + InternalItemsQuery filter); + + /// + /// Applies navigation property includes to a query based on filter options. + /// + /// The queryable to apply navigations to. + /// The query filter. + /// The queryable with navigation includes. + IQueryable ApplyNavigations( + IQueryable dbQuery, + InternalItemsQuery filter); + + /// + /// Applies ordering to a query based on filter options. + /// + /// The queryable to order. + /// The query filter. + /// The database context. + /// The ordered queryable. + IQueryable ApplyOrder( + IQueryable query, + InternalItemsQuery filter, + JellyfinDbContext context); + + /// + /// Builds a query for descendants of an ancestor with user access filtering applied. + /// + /// The database context. + /// The query filter. + /// The ancestor item ID. + /// The filtered descendant queryable. + IQueryable BuildAccessFilteredDescendantsQuery( + JellyfinDbContext context, + InternalItemsQuery filter, + Guid ancestorId); + + /// + /// Deserializes a into a . + /// + /// The database entity. + /// Whether to skip JSON deserialization. + /// The deserialized item, or null. + BaseItem? DeserializeBaseItem(BaseItemEntity entity, bool skipDeserialization = false); + + /// + /// Prepares a filter query by adjusting limits and virtual item settings. + /// + /// The query to prepare. + void PrepareFilterQuery(InternalItemsQuery query); +} diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 52250b4058..291916ab25 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -1,18 +1,13 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; -using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType; namespace MediaBrowser.Controller.Persistence; @@ -21,29 +16,6 @@ namespace MediaBrowser.Controller.Persistence; /// public interface IItemRepository { - /// - /// Deletes the item. - /// - /// The identifier to delete. - void DeleteItem(params IReadOnlyList ids); - - /// - /// Saves the items. - /// - /// The items. - /// The cancellation token. - void SaveItems(IReadOnlyList items, CancellationToken cancellationToken); - - Task SaveImagesAsync(BaseItem item, CancellationToken cancellationToken = default); - - /// - /// Reattaches the user data to the item. - /// - /// The item. - /// The cancellation token. - /// A task that represents the asynchronous reattachment operation. - Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken); - /// /// Retrieves the item. /// @@ -80,69 +52,6 @@ public interface IItemRepository /// List<BaseItem>. IReadOnlyList GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType); - /// - /// Gets the list of series presentation keys for next up. - /// - /// The query. - /// The minimum date for a series to have been most recently watched. - /// The list of keys. - IReadOnlyList GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff); - - /// - /// Gets next up episodes for multiple series in a single batched query. - /// Returns the last watched episode, next unwatched episode, specials, and next played episode for each series. - /// - /// The query filter. - /// The series presentation unique keys to query. - /// Whether to include specials (ParentIndexNumber = 0) in the results. - /// Whether to include watched episodes for rewatching mode. - /// A dictionary mapping series key to batch result containing episodes needed for NextUp calculation. - IReadOnlyDictionary GetNextUpEpisodesBatch( - InternalItemsQuery filter, - IReadOnlyList seriesKeys, - bool includeSpecials, - bool includeWatchedForRewatching); - - /// - /// Updates the inherited values. - /// - void UpdateInheritedValues(); - - int GetCount(InternalItemsQuery filter); - - ItemCounts GetItemCounts(InternalItemsQuery filter); - - /// - /// Gets item counts for a "by-name" item (Person, MusicArtist, Genre, MusicGenre, Studio, Year) - /// using an optimized query that starts from the mapping table instead of scanning all BaseItems. - /// - /// The kind of the name item. - /// The ID of the name item. - /// The item kinds to count. - /// A pre-configured query with user access filtering settings. - /// The item counts grouped by type. - ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, InternalItemsQuery accessFilter); - - QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter); - - QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter); - - QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter); - - QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter); - - QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter); - - QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter); - - IReadOnlyList GetMusicGenreNames(); - - IReadOnlyList GetStudioNames(); - - IReadOnlyList GetGenreNames(); - - IReadOnlyList GetAllArtistNames(); - /// /// Checks if an item has been persisted to the database. /// @@ -151,118 +60,84 @@ public interface IItemRepository Task ItemExistsAsync(Guid id); /// - /// Gets a value indicating wherever all children of the requested Id has been played. + /// Gets genres with item counts. /// - /// The userdata to check against. - /// The Top id to check. - /// Whever the check should be done recursive. Warning expensive operation. - /// A value indicating whever all children has been played. - bool GetIsPlayed(User user, Guid id, bool recursive); + /// The query filter. + /// The genres and their item counts. + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter); /// - /// Gets the count of played items that are descendants of the specified ancestor. - /// Uses the AncestorIds table for efficient recursive lookup. - /// Applies user access filtering (library access, parental controls, tags). + /// Gets music genres with item counts. /// - /// The query filter containing user access settings. - /// The ancestor item id. - /// The count of played descendant items. - int GetPlayedCount(InternalItemsQuery filter, Guid ancestorId); + /// The query filter. + /// The music genres and their item counts. + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter); /// - /// Gets the total count of items that are descendants of the specified ancestor. - /// Uses the AncestorIds table for efficient recursive lookup. - /// Applies user access filtering (library access, parental controls, tags). + /// Gets studios with item counts. /// - /// The query filter containing user access settings. - /// The ancestor item id. - /// The total count of descendant items. - int GetTotalCount(InternalItemsQuery filter, Guid ancestorId); + /// The query filter. + /// The studios and their item counts. + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter); /// - /// Gets both the played count and total count of items that are descendants of the specified ancestor. - /// Uses the AncestorIds table for efficient recursive lookup. - /// Applies user access filtering (library access, parental controls, tags). + /// Gets artists with item counts. /// - /// The query filter containing user access settings. - /// The ancestor item id. - /// A tuple containing (Played count, Total count). - (int Played, int Total) GetPlayedAndTotalCount(InternalItemsQuery filter, Guid ancestorId); + /// The query filter. + /// The artists and their item counts. + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter); /// - /// Gets both the played count and total count of items that are linked children of the specified parent. - /// Uses the LinkedChildren table for BoxSets, Playlists, etc. - /// Applies user access filtering (library access, parental controls, tags). + /// Gets album artists with item counts. /// - /// The query filter containing user access settings. - /// The parent item id (BoxSet, Playlist, etc.). - /// A tuple containing (Played count, Total count). - (int Played, int Total) GetPlayedAndTotalCountFromLinkedChildren(InternalItemsQuery filter, Guid parentId); + /// The query filter. + /// The album artists and their item counts. + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter); /// - /// Batch-fetches played and total counts for multiple folder items using the AncestorIds table. - /// This avoids N+1 queries when building DTOs for lists of folder items (Series, Seasons, etc.). - /// Applies user access filtering (parental controls, tags). + /// Gets all artists with item counts. /// - /// The list of folder item IDs to get counts for. - /// The user for access filtering and played status. - /// Dictionary mapping folder ID to (Played count, Total count). - Dictionary GetPlayedAndTotalCountBatch(IReadOnlyList folderIds, User user); + /// The query filter. + /// All artists and their item counts. + QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter); /// - /// Gets the IDs of linked children for the specified parent. + /// Gets all music genre names. /// - /// The parent item ID. - /// Optional child type filter (e.g., LocalAlternateVersion, LinkedAlternateVersion). - /// List of child item IDs. - IReadOnlyList GetLinkedChildrenIds(Guid parentId, int? childType = null); + /// The list of music genre names. + IReadOnlyList GetMusicGenreNames(); /// - /// Gets all artist matches from the db. + /// Gets all studio names. /// - /// The names of the artists. - /// A map of the artist name and the potential matches. - IReadOnlyDictionary FindArtists(IReadOnlyList artistNames); + /// The list of studio names. + IReadOnlyList GetStudioNames(); /// - /// Batch-fetches child counts for multiple parent folders. - /// Returns the count of immediate children (non-recursive) for each parent. + /// Gets all genre names. /// - /// The list of parent folder IDs. - /// The user ID for access filtering. - /// Dictionary mapping parent ID to child count. - Dictionary GetChildCountBatch(IReadOnlyList parentIds, Guid? userId); + /// The list of genre names. + IReadOnlyList GetGenreNames(); /// - /// Gets parent IDs (Playlists/BoxSets) that reference the specified child with LinkedChildType.Manual. + /// Gets all artist names. /// - /// The child item ID. - /// List of parent IDs that reference the child. - IReadOnlyList GetManualLinkedParentIds(Guid childId); + /// The list of artist names. + IReadOnlyList GetAllArtistNames(); /// - /// Gets legacy query filters (Years, Genres, Tags, OfficialRatings) aggregated directly from the database. + /// Gets legacy query filters aggregated from the database. /// /// The query filter. /// Aggregated filter values. QueryFiltersLegacy GetQueryFiltersLegacy(InternalItemsQuery filter); /// - /// Updates LinkedChildren references from one child to another, preserving SortOrder. - /// Handles duplicates: if parent already references toChildId, removes the old reference instead. - /// Used when video versions change to maintain collection integrity. + /// Gets whether all children of the requested item have been played. /// - /// The child ID to re-route from. - /// The child ID to re-route to. - /// List of parent item IDs whose LinkedChildren were modified. - IReadOnlyList RerouteLinkedChildren(Guid fromChildId, Guid toChildId); - - /// - /// Creates or updates a LinkedChild entry linking a parent to a child item. - /// If the link already exists, updates the child type. - /// - /// The parent item ID. - /// The child item ID. - /// The type of linked child relationship. - void UpsertLinkedChild(Guid parentId, Guid childId, LinkedChildType childType); + /// The user to check against. + /// The top item id to check. + /// Whether the check should be done recursively. + /// A value indicating whether all children have been played. + bool GetIsPlayed(User user, Guid id, bool recursive); } diff --git a/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs b/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs new file mode 100644 index 0000000000..d0cddf54a6 --- /dev/null +++ b/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities.Audio; +using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType; + +namespace MediaBrowser.Controller.Persistence; + +/// +/// Provides linked children query and manipulation operations. +/// +public interface ILinkedChildrenService +{ + /// + /// Gets the IDs of linked children for the specified parent. + /// + /// The parent item ID. + /// Optional child type filter. + /// List of child item IDs. + IReadOnlyList GetLinkedChildrenIds(Guid parentId, int? childType = null); + + /// + /// Gets all artist matches from the database. + /// + /// The names of the artists. + /// A map of the artist name and the potential matches. + IReadOnlyDictionary FindArtists(IReadOnlyList artistNames); + + /// + /// Gets parent IDs that reference the specified child with LinkedChildType.Manual. + /// + /// The child item ID. + /// List of parent IDs that reference the child. + IReadOnlyList GetManualLinkedParentIds(Guid childId); + + /// + /// Updates LinkedChildren references from one child to another. + /// + /// The child ID to re-route from. + /// The child ID to re-route to. + /// List of parent item IDs whose LinkedChildren were modified. + IReadOnlyList RerouteLinkedChildren(Guid fromChildId, Guid toChildId); + + /// + /// Creates or updates a LinkedChild entry. + /// + /// The parent item ID. + /// The child item ID. + /// The type of linked child relationship. + void UpsertLinkedChild(Guid parentId, Guid childId, LinkedChildType childType); +} diff --git a/MediaBrowser.Controller/Persistence/INextUpService.cs b/MediaBrowser.Controller/Persistence/INextUpService.cs new file mode 100644 index 0000000000..ade026d0da --- /dev/null +++ b/MediaBrowser.Controller/Persistence/INextUpService.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Persistence; + +/// +/// Provides next-up episode query operations. +/// +public interface INextUpService +{ + /// + /// Gets the list of series presentation keys for next up. + /// + /// The query. + /// The minimum date for a series to have been most recently watched. + /// The list of keys. + IReadOnlyList GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff); + + /// + /// Gets next up episodes for multiple series in a single batched query. + /// + /// The query filter. + /// The series presentation unique keys to query. + /// Whether to include specials. + /// Whether to include watched episodes for rewatching mode. + /// A dictionary mapping series key to batch result. + IReadOnlyDictionary GetNextUpEpisodesBatch( + InternalItemsQuery filter, + IReadOnlyList seriesKeys, + bool includeSpecials, + bool includeWatchedForRewatching); +} -- cgit v1.2.3