598 lines
20 KiB
C#
598 lines
20 KiB
C#
using CinemaLib.API;
|
|
using Jellyfin.Database.Implementations.Entities;
|
|
using Jellyfin.Database.Implementations.Enums;
|
|
using Jellyfin.Data.Enums;
|
|
using MediaBrowser.Controller.Dto;
|
|
using MediaBrowser.Controller.Entities;
|
|
using MediaBrowser.Controller.Entities.Audio;
|
|
using MediaBrowser.Controller.Entities.TV;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Controller.Persistence;
|
|
using MediaBrowser.Controller.Providers;
|
|
using MediaBrowser.Controller.Resolvers;
|
|
using MediaBrowser.Controller.Sorting;
|
|
using MediaBrowser.Model.Configuration;
|
|
using MediaBrowser.Model.Dto;
|
|
using MediaBrowser.Model.Entities;
|
|
using MediaBrowser.Model.IO;
|
|
using MediaBrowser.Model.Querying;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Jellyfin.Database.Implementations;
|
|
|
|
namespace Jellyfin.Plugin.Cinema;
|
|
|
|
sealed class CinemaLibraryManager : ILibraryManager
|
|
{
|
|
private readonly ILibraryManager _inner;
|
|
private readonly IDbContextFactory<JellyfinDbContext> _userData;
|
|
|
|
public CinemaLibraryManager(CinemaInnerLibraryManager innerLibraryManager, IDbContextFactory<JellyfinDbContext> userData, IServiceProvider svc)
|
|
{
|
|
if (innerLibraryManager == null || svc == null)
|
|
throw new ArgumentNullException();
|
|
|
|
ILibraryManager? inner = svc.GetService(innerLibraryManager.InnerType) as ILibraryManager;
|
|
if (inner == null)
|
|
throw new InvalidOperationException("Original LibraryManager service not found.");
|
|
this._inner = inner;
|
|
this._userData = userData;
|
|
}
|
|
|
|
#region ILibraryManager Members
|
|
|
|
public AggregateFolder RootFolder => _inner.RootFolder;
|
|
|
|
public bool IsScanRunning => _inner.IsScanRunning;
|
|
|
|
public event EventHandler<ItemChangeEventArgs>? ItemAdded
|
|
{
|
|
add => _inner.ItemAdded += value;
|
|
remove => _inner.ItemAdded -= value;
|
|
}
|
|
public event EventHandler<ItemChangeEventArgs>? ItemUpdated
|
|
{
|
|
add => _inner.ItemUpdated += value;
|
|
remove => _inner.ItemUpdated -= value;
|
|
}
|
|
public event EventHandler<ItemChangeEventArgs>? ItemRemoved
|
|
{
|
|
add => _inner.ItemRemoved += value;
|
|
remove => _inner.ItemRemoved -= value;
|
|
}
|
|
|
|
public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
|
|
{
|
|
_inner.AddMediaPath(virtualFolderName, mediaPath);
|
|
}
|
|
|
|
public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, IEnumerable<ILibraryPostScanTask> postscanTasks)
|
|
{
|
|
_inner.AddParts(rules, resolvers, introProviders, itemComparers, postscanTasks);
|
|
}
|
|
|
|
public Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary)
|
|
{
|
|
return _inner.AddVirtualFolder(name, collectionType, options, refreshLibrary);
|
|
}
|
|
|
|
public Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex, bool removeOnFailure = true)
|
|
{
|
|
return _inner.ConvertImageToLocal(item, image, imageIndex, removeOnFailure);
|
|
}
|
|
|
|
public void CreateItem(BaseItem item, BaseItem? parent)
|
|
{
|
|
_inner.CreateItem(item, parent);
|
|
}
|
|
|
|
public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
|
|
{
|
|
_inner.CreateItems(items, parent, cancellationToken);
|
|
}
|
|
|
|
public void DeleteItem(BaseItem item, DeleteOptions options)
|
|
{
|
|
_inner.DeleteItem(item, options);
|
|
}
|
|
|
|
public void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem)
|
|
{
|
|
_inner.DeleteItem(item, options, notifyParentItem);
|
|
}
|
|
|
|
public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem)
|
|
{
|
|
_inner.DeleteItem(item, options, parent, notifyParentItem);
|
|
}
|
|
|
|
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
|
{
|
|
return _inner.FillMissingEpisodeNumbersFromPath(episode, forceRefresh);
|
|
}
|
|
|
|
public BaseItem? FindByPath(string path, bool? isFolder)
|
|
{
|
|
return _inner.FindByPath(path, isFolder);
|
|
}
|
|
|
|
public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
|
{
|
|
return _inner.FindExtras(owner, fileSystemChildren, directoryService);
|
|
}
|
|
|
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetAlbumArtists(query);
|
|
}
|
|
|
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetAllArtists(query);
|
|
}
|
|
|
|
public MusicArtist GetArtist(string name)
|
|
{
|
|
return _inner.GetArtist(name);
|
|
}
|
|
|
|
public MusicArtist GetArtist(string name, DtoOptions options)
|
|
{
|
|
return _inner.GetArtist(name, options);
|
|
}
|
|
|
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetArtists(query);
|
|
}
|
|
|
|
public List<Folder> GetCollectionFolders(BaseItem item)
|
|
{
|
|
return _inner.GetCollectionFolders(item);
|
|
}
|
|
|
|
public List<Folder> GetCollectionFolders(BaseItem item, IEnumerable<Folder> allUserRootChildren)
|
|
{
|
|
return _inner.GetCollectionFolders(item, allUserRootChildren);
|
|
}
|
|
|
|
public CollectionType? GetConfiguredContentType(BaseItem item)
|
|
{
|
|
return _inner.GetConfiguredContentType(item);
|
|
}
|
|
|
|
public CollectionType? GetConfiguredContentType(string path)
|
|
{
|
|
return _inner.GetConfiguredContentType(path);
|
|
}
|
|
|
|
public CollectionType? GetContentType(BaseItem item)
|
|
{
|
|
return _inner.GetContentType(item);
|
|
}
|
|
|
|
public int GetCount(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetCount(query);
|
|
}
|
|
|
|
public Genre GetGenre(string name)
|
|
{
|
|
return _inner.GetGenre(name);
|
|
}
|
|
|
|
public Guid GetGenreId(string name)
|
|
{
|
|
return _inner.GetGenreId(name);
|
|
}
|
|
|
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetGenres(query);
|
|
}
|
|
|
|
public CollectionType? GetInheritedContentType(BaseItem item)
|
|
{
|
|
return _inner.GetInheritedContentType(item);
|
|
}
|
|
|
|
public Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
|
|
{
|
|
return _inner.GetIntros(item, user);
|
|
}
|
|
|
|
public BaseItem? GetItemById(Guid id)
|
|
{
|
|
return _inner.GetItemById(id);
|
|
}
|
|
|
|
public T? GetItemById<T>(Guid id) where T : BaseItem
|
|
{
|
|
return _inner.GetItemById<T>(id);
|
|
}
|
|
|
|
public T? GetItemById<T>(Guid id, Guid userId) where T : BaseItem
|
|
{
|
|
return _inner.GetItemById<T>(id, userId);
|
|
}
|
|
|
|
public T? GetItemById<T>(Guid id, User? user) where T : BaseItem
|
|
{
|
|
return _inner.GetItemById<T>(id, user);
|
|
}
|
|
|
|
public IReadOnlyList<Guid> GetItemIds(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetItemIds(query);
|
|
}
|
|
|
|
public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query)
|
|
{
|
|
string? serieOrSeasonIdS;
|
|
Guid serieOrSeasonId;
|
|
BaseItem? serieOrSeason;
|
|
if (query.IncludeItemTypes == null
|
|
|| query.IncludeItemTypes.Length != 1
|
|
|| query.IncludeItemTypes[0] != BaseItemKind.Episode
|
|
|| (serieOrSeasonIdS = query.AncestorWithPresentationUniqueKey ?? query.SeriesPresentationUniqueKey) == null
|
|
|| !Guid.TryParse(serieOrSeasonIdS, out serieOrSeasonId)
|
|
|| (serieOrSeason = _inner.GetItemById(serieOrSeasonId)) == null
|
|
|| !CinemaQueryExtensions.IsCinemaExternalId(serieOrSeason.ExternalId))
|
|
return _inner.GetItemList(query);
|
|
|
|
// HACK: Necessary until GetEpisodes is virtual
|
|
switch (serieOrSeason)
|
|
{
|
|
case CinemaSeason season: return new List<BaseItem>(season.GetItemList(query));
|
|
case CinemaTvSeries series:
|
|
List<BaseItem> result = new List<BaseItem>();
|
|
foreach (BaseItem i in series.GetItemList(new InternalItemsQuery()))
|
|
{
|
|
switch (i)
|
|
{
|
|
case CinemaEpisode episode: result.Add(episode); break;
|
|
case CinemaSeason season: result.AddRange(season.GetItemList(query)); break;
|
|
}
|
|
}
|
|
return result;
|
|
default: return _inner.GetItemList(query);
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
|
|
{
|
|
return _inner.GetItemList(query, allowExternalContent);
|
|
}
|
|
|
|
public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
|
|
{
|
|
return _inner.GetItemList(query, parents);
|
|
}
|
|
|
|
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
|
|
{
|
|
QueryResult<BaseItem> result = _inner.GetItemsResult(query);
|
|
if (query.ParentId != Guid.Empty)
|
|
// Not a search at the root so do not involve our root folders
|
|
return result;
|
|
|
|
List<BaseItem> resultL = new List<BaseItem>();
|
|
HashSet<Guid> innerAdded = new HashSet<Guid>();
|
|
if (query.OrderBy.FirstOrDefault().OrderBy == ItemSortBy.DatePlayed && query.User != null && query.MediaTypes.Contains(MediaType.Video))
|
|
{
|
|
// Get Resume play items
|
|
UserData[]? resumePlay;
|
|
Guid userId = query.User.Id;
|
|
using (JellyfinDbContext context = _userData.CreateDbContext())
|
|
resumePlay = context.UserData.AsNoTracking()
|
|
.Where(e => e.UserId.Equals(userId))
|
|
.OrderByDescending(x => x.Played)
|
|
.Skip(query.StartIndex ?? 0)
|
|
.Take(query.Limit ?? 20)
|
|
.ToArray();
|
|
|
|
foreach (UserData i in resumePlay)
|
|
{
|
|
// Note: All Cinema items override GetUserDataKeys and return ExternalId
|
|
string? csId;
|
|
if (CinemaQueryExtensions.TryGetCinemaIdFromExternalId(i.CustomDataKey, out csId))
|
|
{
|
|
// First try to get already in-memory item
|
|
Guid itemId = CinemaQueryExtensions.GetMediaItemId(csId, null);
|
|
BaseItem? item = _inner.GetItemById(itemId);
|
|
if (item == null)
|
|
{
|
|
// Fetch it from cinema
|
|
MediaSource? ms = Metadata.DetailAsync(csId, default).GetAwaiter().GetResult();
|
|
CinemaQueryExtensions.TryCreateMediaItem<Folder>(ms, csId, null, false, out item);
|
|
}
|
|
if (item != null)
|
|
resultL.Add(item);
|
|
}
|
|
else
|
|
{
|
|
// Try to append item from the inner result
|
|
if (Guid.TryParse(i.CustomDataKey, out Guid key))
|
|
foreach (BaseItem j in result.Items)
|
|
if (j.Id == key)
|
|
{
|
|
resultL.Add(j);
|
|
innerAdded.Add(key);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Append the not-seen inner items
|
|
foreach (BaseItem i in result.Items)
|
|
if (!innerAdded.Contains(i.Id))
|
|
resultL.Add(i);
|
|
}
|
|
else
|
|
{
|
|
// Add our root folders to the search
|
|
resultL.AddRange(result.Items);
|
|
foreach (BaseItem i in GetUserRootFolder().Children)
|
|
// Prevent duplicates as content in Anime is also elsewhere
|
|
if (i is CinemaRootFolder root && i is not CinemaAnimeFolder)
|
|
{
|
|
var b = root.GetItemList(query);
|
|
if (b != null)
|
|
resultL.AddRange(b);
|
|
}
|
|
}
|
|
|
|
return new QueryResult<BaseItem>() { Items = resultL };
|
|
}
|
|
|
|
public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery query, IReadOnlyList<BaseItem> parents, CollectionType collectionType)
|
|
{
|
|
return _inner.GetLatestItemList(query, parents, collectionType);
|
|
}
|
|
|
|
public LibraryOptions GetLibraryOptions(BaseItem item)
|
|
{
|
|
return _inner.GetLibraryOptions(item);
|
|
}
|
|
|
|
public MusicGenre GetMusicGenre(string name)
|
|
{
|
|
return _inner.GetMusicGenre(name);
|
|
}
|
|
|
|
public Guid GetMusicGenreId(string name)
|
|
{
|
|
return _inner.GetMusicGenreId(name);
|
|
}
|
|
|
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
|
{
|
|
return _inner.GetMusicGenres(query);
|
|
}
|
|
|
|
public UserView GetNamedView(User user, string name, Guid parentId, CollectionType? viewType, string sortName)
|
|
{
|
|
return _inner.GetNamedView(user, name, parentId, viewType, sortName);
|
|
}
|
|
|
|
public UserView GetNamedView(User user, string name, CollectionType? viewType, string sortName)
|
|
{
|
|
return _inner.GetNamedView(user, name, viewType, sortName);
|
|
}
|
|
|
|
public UserView GetNamedView(string name, CollectionType viewType, string sortName)
|
|
{
|
|
return _inner.GetNamedView(name, viewType, sortName);
|
|
}
|
|
|
|
public UserView GetNamedView(string name, Guid parentId, CollectionType? viewType, string sortName, string uniqueId)
|
|
{
|
|
return _inner.GetNamedView(name, parentId, viewType, sortName, uniqueId);
|
|
}
|
|
|
|
public Guid GetNewItemId(string key, Type type)
|
|
{
|
|
return _inner.GetNewItemId(key, type);
|
|
}
|
|
|
|
public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents, DateTime dateCutoff)
|
|
{
|
|
return _inner.GetNextUpSeriesKeys(query, parents, dateCutoff);
|
|
}
|
|
|
|
public BaseItem GetParentItem(Guid? parentId, Guid? userId)
|
|
{
|
|
return _inner.GetParentItem(parentId, userId);
|
|
}
|
|
|
|
public string GetPathAfterNetworkSubstitution(string path, BaseItem? ownerItem = null)
|
|
{
|
|
return _inner.GetPathAfterNetworkSubstitution(path, ownerItem);
|
|
}
|
|
|
|
public IReadOnlyList<PersonInfo> GetPeople(BaseItem item)
|
|
{
|
|
return _inner.GetPeople(item);
|
|
}
|
|
|
|
public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery query)
|
|
{
|
|
return _inner.GetPeople(query);
|
|
}
|
|
|
|
public IReadOnlyList<Person> GetPeopleItems(InternalPeopleQuery query)
|
|
{
|
|
return _inner.GetPeopleItems(query);
|
|
}
|
|
|
|
public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query)
|
|
{
|
|
return _inner.GetPeopleNames(query);
|
|
}
|
|
|
|
public Person? GetPerson(string name)
|
|
{
|
|
return _inner.GetPerson(name);
|
|
}
|
|
|
|
public int? GetSeasonNumberFromPath(string path, Guid? parentId)
|
|
{
|
|
return _inner.GetSeasonNumberFromPath(path, parentId);
|
|
}
|
|
|
|
public UserView GetShadowView(BaseItem parent, CollectionType? viewType, string sortName)
|
|
{
|
|
return _inner.GetShadowView(parent, viewType, sortName);
|
|
}
|
|
|
|
public Studio GetStudio(string name)
|
|
{
|
|
return _inner.GetStudio(name);
|
|
}
|
|
|
|
public Guid GetStudioId(string name)
|
|
{
|
|
return _inner.GetStudioId(name);
|
|
}
|
|
|
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
|
|
{
|
|
return GetStudios(query);
|
|
}
|
|
|
|
public Folder GetUserRootFolder()
|
|
{
|
|
return _inner.GetUserRootFolder();
|
|
}
|
|
|
|
public List<VirtualFolderInfo> GetVirtualFolders()
|
|
{
|
|
return _inner.GetVirtualFolders();
|
|
}
|
|
|
|
public List<VirtualFolderInfo> GetVirtualFolders(bool includeRefreshState)
|
|
{
|
|
return _inner.GetVirtualFolders(includeRefreshState);
|
|
}
|
|
|
|
public Year GetYear(int value)
|
|
{
|
|
return _inner.GetYear(value);
|
|
}
|
|
|
|
public bool IgnoreFile(FileSystemMetadata file, BaseItem parent)
|
|
{
|
|
return _inner.IgnoreFile(file, parent);
|
|
}
|
|
|
|
public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
|
|
{
|
|
return _inner.NormalizeRootPathList(paths);
|
|
}
|
|
|
|
public ItemLookupInfo ParseName(string name)
|
|
{
|
|
return _inner.ParseName(name);
|
|
}
|
|
|
|
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
|
|
{
|
|
return _inner.QueryItems(query);
|
|
}
|
|
|
|
public void QueueLibraryScan()
|
|
{
|
|
_inner.QueueLibraryScan();
|
|
}
|
|
|
|
public void RegisterItem(BaseItem item)
|
|
{
|
|
_inner.RegisterItem(item);
|
|
}
|
|
|
|
public void RemoveMediaPath(string virtualFolderName, string mediaPath)
|
|
{
|
|
_inner.RemoveMediaPath(virtualFolderName, mediaPath);
|
|
}
|
|
|
|
public Task RemoveVirtualFolder(string name, bool refreshLibrary)
|
|
{
|
|
return _inner.RemoveVirtualFolder(name, refreshLibrary);
|
|
}
|
|
|
|
public BaseItem? ResolvePath(FileSystemMetadata fileInfo, Folder? parent = null, IDirectoryService? directoryService = null)
|
|
{
|
|
return _inner.ResolvePath(fileInfo, parent, directoryService);
|
|
}
|
|
|
|
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, CollectionType? collectionType = null)
|
|
{
|
|
return _inner.ResolvePaths(files, directoryService, parent, libraryOptions, collectionType);
|
|
}
|
|
|
|
public BaseItem RetrieveItem(Guid id)
|
|
{
|
|
return _inner.RetrieveItem(id);
|
|
}
|
|
|
|
public Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
|
|
{
|
|
return _inner.RunMetadataSavers(item, updateReason);
|
|
}
|
|
|
|
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder)
|
|
{
|
|
return _inner.Sort(items, user, sortBy, sortOrder);
|
|
}
|
|
|
|
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy)
|
|
{
|
|
return _inner.Sort(items, user, orderBy);
|
|
}
|
|
|
|
public Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false)
|
|
{
|
|
return _inner.UpdateImagesAsync(item, forceUpdate);
|
|
}
|
|
|
|
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
|
{
|
|
return _inner.UpdateItemAsync(item, parent, updateReason, cancellationToken);
|
|
}
|
|
|
|
public Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
|
{
|
|
return _inner.UpdateItemsAsync(items, parent, updateReason, cancellationToken);
|
|
}
|
|
|
|
public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
|
|
{
|
|
_inner.UpdateMediaPath(virtualFolderName, mediaPath);
|
|
}
|
|
|
|
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
|
|
{
|
|
_inner.UpdatePeople(item, people);
|
|
}
|
|
|
|
public Task UpdatePeopleAsync(BaseItem item, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken)
|
|
{
|
|
return _inner.UpdatePeopleAsync(item, people, cancellationToken);
|
|
}
|
|
|
|
public Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken)
|
|
{
|
|
return _inner.ValidateMediaLibrary(progress, cancellationToken);
|
|
}
|
|
|
|
public Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
|
{
|
|
return _inner.ValidatePeopleAsync(progress, cancellationToken);
|
|
}
|
|
|
|
public Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false)
|
|
{
|
|
return _inner.ValidateTopLibraryFolders(cancellationToken, removeRoot);
|
|
}
|
|
|
|
#endregion
|
|
} |