diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/LibraryManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/LibraryManager.cs | 207 |
1 files changed, 129 insertions, 78 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 28f7ed659..c8026960d 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2,7 +2,6 @@ #pragma warning disable CA5394 using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -11,6 +10,7 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using BitFaster.Caching.Lru; using Emby.Naming.Common; using Emby.Naming.TV; using Emby.Server.Implementations.Library.Resolvers; @@ -18,8 +18,10 @@ using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks.Tasks; using Emby.Server.Implementations.Sorting; -using Jellyfin.Data.Entities; +using Jellyfin.Data; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -62,7 +64,6 @@ namespace Emby.Server.Implementations.Library private const string ShortcutFileExtension = ".mblink"; private readonly ILogger<LibraryManager> _logger; - private readonly ConcurrentDictionary<Guid, BaseItem> _cache; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; @@ -76,13 +77,16 @@ namespace Emby.Server.Implementations.Library private readonly IItemRepository _itemRepository; private readonly IImageProcessor _imageProcessor; private readonly NamingOptions _namingOptions; + private readonly IPeopleRepository _peopleRepository; private readonly ExtraResolver _extraResolver; + private readonly IPathManager _pathManager; + private readonly FastConcurrentLru<Guid, BaseItem> _cache; /// <summary> /// The _root folder sync lock. /// </summary> - private readonly object _rootFolderSyncLock = new object(); - private readonly object _userRootFolderSyncLock = new object(); + private readonly Lock _rootFolderSyncLock = new(); + private readonly Lock _userRootFolderSyncLock = new(); private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); @@ -112,6 +116,8 @@ namespace Emby.Server.Implementations.Library /// <param name="imageProcessor">The image processor.</param> /// <param name="namingOptions">The naming options.</param> /// <param name="directoryService">The directory service.</param> + /// <param name="peopleRepository">The people repository.</param> + /// <param name="pathManager">The path manager.</param> public LibraryManager( IServerApplicationHost appHost, ILoggerFactory loggerFactory, @@ -127,7 +133,9 @@ namespace Emby.Server.Implementations.Library IItemRepository itemRepository, IImageProcessor imageProcessor, NamingOptions namingOptions, - IDirectoryService directoryService) + IDirectoryService directoryService, + IPeopleRepository peopleRepository, + IPathManager pathManager) { _appHost = appHost; _logger = loggerFactory.CreateLogger<LibraryManager>(); @@ -142,14 +150,17 @@ namespace Emby.Server.Implementations.Library _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; _imageProcessor = imageProcessor; - _cache = new ConcurrentDictionary<Guid, BaseItem>(); - _namingOptions = namingOptions; + _cache = new FastConcurrentLru<Guid, BaseItem>(_configurationManager.Configuration.CacheSize); + + _namingOptions = namingOptions; + _peopleRepository = peopleRepository; + _pathManager = pathManager; _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions, directoryService); _configurationManager.ConfigurationUpdated += ConfigurationUpdated; - RecordConfigurationValues(configurationManager.Configuration); + RecordConfigurationValues(_configurationManager.Configuration); } /// <summary> @@ -197,33 +208,33 @@ namespace Emby.Server.Implementations.Library /// Gets or sets the postscan tasks. /// </summary> /// <value>The postscan tasks.</value> - private ILibraryPostScanTask[] PostscanTasks { get; set; } = Array.Empty<ILibraryPostScanTask>(); + private ILibraryPostScanTask[] PostscanTasks { get; set; } = []; /// <summary> /// Gets or sets the intro providers. /// </summary> /// <value>The intro providers.</value> - private IIntroProvider[] IntroProviders { get; set; } = Array.Empty<IIntroProvider>(); + private IIntroProvider[] IntroProviders { get; set; } = []; /// <summary> /// Gets or sets the list of entity resolution ignore rules. /// </summary> /// <value>The entity resolution ignore rules.</value> - private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = Array.Empty<IResolverIgnoreRule>(); + private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = []; /// <summary> /// Gets or sets the list of currently registered entity resolvers. /// </summary> /// <value>The entity resolvers enumerable.</value> - private IItemResolver[] EntityResolvers { get; set; } = Array.Empty<IItemResolver>(); + private IItemResolver[] EntityResolvers { get; set; } = []; - private IMultiItemResolver[] MultiItemResolvers { get; set; } = Array.Empty<IMultiItemResolver>(); + private IMultiItemResolver[] MultiItemResolvers { get; set; } = []; /// <summary> /// Gets or sets the comparers. /// </summary> /// <value>The comparers.</value> - private IBaseItemComparer[] Comparers { get; set; } = Array.Empty<IBaseItemComparer>(); + private IBaseItemComparer[] Comparers { get; set; } = []; public bool IsScanRunning { get; private set; } @@ -297,7 +308,7 @@ namespace Emby.Server.Implementations.Library } } - _cache[item.Id] = item; + _cache.AddOrUpdate(item.Id, item); } public void DeleteItem(BaseItem item, DeleteOptions options) @@ -356,7 +367,7 @@ namespace Emby.Server.Implementations.Library var children = item.IsFolder ? ((Folder)item).GetRecursiveChildren(false) - : Array.Empty<BaseItem>(); + : []; foreach (var metadataPath in GetMetadataPaths(item, children)) { @@ -451,24 +462,38 @@ namespace Emby.Server.Implementations.Library item.SetParent(null); _itemRepository.DeleteItem(item.Id); + _cache.TryRemove(item.Id, out _); foreach (var child in children) { _itemRepository.DeleteItem(child.Id); + _cache.TryRemove(child.Id, out _); } - _cache.TryRemove(item.Id, out _); - ReportItemRemoved(item, parent); } - private static List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) + private List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) + { + var list = GetInternalMetadataPaths(item); + foreach (var child in children) + { + list.AddRange(GetInternalMetadataPaths(child)); + } + + return list; + } + + private List<string> GetInternalMetadataPaths(BaseItem item) { var list = new List<string> { item.GetInternalMetadataPath() }; - list.AddRange(children.Select(i => i.GetInternalMetadataPath())); + if (item is Video video) + { + list.Add(_pathManager.GetTrickplayDirectory(video)); + } return list; } @@ -589,7 +614,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf); - files = Array.Empty<FileSystemMetadata>(); + files = []; } else { @@ -751,14 +776,7 @@ namespace Emby.Server.Implementations.Library if (folder.Id.IsEmpty()) { - if (string.IsNullOrEmpty(folder.Path)) - { - folder.Id = GetNewItemId(folder.GetType().Name, folder.GetType()); - } - else - { - folder.Id = GetNewItemId(folder.Path, folder.GetType()); - } + folder.Id = GetNewItemId(folder.Path, folder.GetType()); } var dbItem = GetItemById(folder.Id) as BasePluginFolder; @@ -1053,9 +1071,17 @@ namespace Emby.Server.Implementations.Library cancellationToken: cancellationToken).ConfigureAwait(false); // Quickly scan CollectionFolders for changes - foreach (var folder in GetUserRootFolder().Children.OfType<Folder>()) + foreach (var child in GetUserRootFolder().Children.OfType<Folder>()) { - await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false); + // If the user has somehow deleted the collection directory, remove the metadata from the database. + if (child is CollectionFolder collectionFolder && !Directory.Exists(collectionFolder.Path)) + { + _itemRepository.DeleteItem(collectionFolder.Id); + } + else + { + await child.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } } } @@ -1230,7 +1256,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Guid can't be empty", nameof(id)); } - if (_cache.TryGetValue(id, out BaseItem? item)) + if (_cache.TryGet(id, out var item)) { return item; } @@ -1247,7 +1273,7 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public T? GetItemById<T>(Guid id) - where T : BaseItem + where T : BaseItem { var item = GetItemById(id); if (item is T typedItem) @@ -1274,7 +1300,7 @@ namespace Emby.Server.Implementations.Library return ItemIsVisible(item, user) ? item : null; } - public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) { if (query.Recursive && !query.ParentId.IsEmpty()) { @@ -1300,7 +1326,7 @@ namespace Emby.Server.Implementations.Library return itemList; } - public List<BaseItem> GetItemList(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query) { return GetItemList(query, true); } @@ -1324,7 +1350,7 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetCount(query); } - public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) + public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) { SetTopParentIdsOrAncestors(query, parents); @@ -1339,6 +1365,36 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetItemList(query); } + public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery query, IReadOnlyList<BaseItem> parents, CollectionType collectionType) + { + SetTopParentIdsOrAncestors(query, parents); + + if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + } + + return _itemRepository.GetLatestItemList(query, collectionType); + } + + public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents, DateTime dateCutoff) + { + SetTopParentIdsOrAncestors(query, parents); + + if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0) + { + if (query.User is not null) + { + AddUserToQuery(query, query.User); + } + } + + return _itemRepository.GetNextUpSeriesKeys(query, dateCutoff); + } + public QueryResult<BaseItem> QueryItems(InternalItemsQuery query) { if (query.User is not null) @@ -1357,7 +1413,7 @@ namespace Emby.Server.Implementations.Library _itemRepository.GetItemList(query)); } - public List<Guid> GetItemIds(InternalItemsQuery query) + public IReadOnlyList<Guid> GetItemIds(InternalItemsQuery query) { if (query.User is not null) { @@ -1443,7 +1499,7 @@ namespace Emby.Server.Implementations.Library // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); - query.AncestorIds = Array.Empty<Guid>(); + query.AncestorIds = []; // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) @@ -1563,7 +1619,7 @@ namespace Emby.Server.Implementations.Library return GetTopParentIdsForQuery(displayParent, user); } - return Array.Empty<Guid>(); + return []; } if (!view.ParentId.IsEmpty()) @@ -1574,7 +1630,7 @@ namespace Emby.Server.Implementations.Library return GetTopParentIdsForQuery(displayParent, user); } - return Array.Empty<Guid>(); + return []; } // Handle grouping @@ -1589,7 +1645,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => GetTopParentIdsForQuery(i, user)); } - return Array.Empty<Guid>(); + return []; } if (item is CollectionFolder collectionFolder) @@ -1603,7 +1659,7 @@ namespace Emby.Server.Implementations.Library return new[] { topParent.Id }; } - return Array.Empty<Guid>(); + return []; } /// <summary> @@ -1647,7 +1703,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Error getting intros"); - return Enumerable.Empty<IntroInfo>(); + return []; } } @@ -1955,13 +2011,13 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { + _itemRepository.SaveItems(items, cancellationToken); + foreach (var item in items) { await RunMetadataSavers(item, updateReason).ConfigureAwait(false); } - _itemRepository.SaveItems(items, cancellationToken); - if (ItemUpdated is not null) { foreach (var item in items) @@ -2474,8 +2530,11 @@ namespace Emby.Server.Implementations.Library } /// <inheritdoc /> - public int? GetSeasonNumberFromPath(string path) - => SeasonPathParser.Parse(path, true, true).SeasonNumber; + public int? GetSeasonNumberFromPath(string path, Guid? parentId) + { + var parentPath = parentId.HasValue ? GetItemById(parentId.Value)?.ContainingFolderPath : null; + return SeasonPathParser.Parse(path, parentPath, true, true).SeasonNumber; + } /// <inheritdoc /> public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) @@ -2626,15 +2685,6 @@ namespace Emby.Server.Implementations.Library { episode.ParentIndexNumber = season.IndexNumber; } - else - { - /* - Anime series don't generally have a season in their file name, however, - TVDb needs a season to correctly get the metadata. - Hence, a null season needs to be filled with something. */ - // FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified - episode.ParentIndexNumber = 1; - } if (episode.ParentIndexNumber.HasValue) { @@ -2659,7 +2709,7 @@ namespace Emby.Server.Implementations.Library public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { - var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); + var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath); if (ownerVideoInfo is null) { yield break; @@ -2736,12 +2786,12 @@ namespace Emby.Server.Implementations.Library return path; } - public List<PersonInfo> GetPeople(InternalPeopleQuery query) + public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery query) { - return _itemRepository.GetPeople(query); + return _peopleRepository.GetPeople(query); } - public List<PersonInfo> GetPeople(BaseItem item) + public IReadOnlyList<PersonInfo> GetPeople(BaseItem item) { if (item.SupportsPeople) { @@ -2756,12 +2806,12 @@ namespace Emby.Server.Implementations.Library } } - return new List<PersonInfo>(); + return []; } - public List<Person> GetPeopleItems(InternalPeopleQuery query) + public IReadOnlyList<Person> GetPeopleItems(InternalPeopleQuery query) { - return _itemRepository.GetPeopleNames(query) + return _peopleRepository.GetPeopleNames(query) .Select(i => { try @@ -2779,9 +2829,9 @@ namespace Emby.Server.Implementations.Library .ToList()!; // null values are filtered out } - public List<string> GetPeopleNames(InternalPeopleQuery query) + public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query) { - return _itemRepository.GetPeopleNames(query); + return _peopleRepository.GetPeopleNames(query); } public void UpdatePeople(BaseItem item, List<PersonInfo> people) @@ -2790,16 +2840,17 @@ namespace Emby.Server.Implementations.Library } /// <inheritdoc /> - public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken) + public async Task UpdatePeopleAsync(BaseItem item, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken) { if (!item.SupportsPeople) { return; } - _itemRepository.UpdatePeople(item.Id, people); if (people is not null) { + people = people.Where(e => e is not null).ToArray(); + _peopleRepository.UpdatePeople(item.Id, people); await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false); } } @@ -2882,7 +2933,7 @@ namespace Emby.Server.Implementations.Library { var path = Path.Combine(virtualFolderPath, collectionType.ToString()!.ToLowerInvariant() + ".collection"); // Can't be null with legal values? - await File.WriteAllBytesAsync(path, Array.Empty<byte>()).ConfigureAwait(false); + await File.WriteAllBytesAsync(path, []).ConfigureAwait(false); } CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); @@ -2914,14 +2965,13 @@ namespace Emby.Server.Implementations.Library private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken) { - List<BaseItem>? personsToSave = null; - foreach (var person in people) { cancellationToken.ThrowIfCancellationRequested(); var itemUpdateType = ItemUpdateType.MetadataDownload; var saveEntity = false; + var createEntity = false; var personEntity = GetPerson(person.Name); if (personEntity is null) @@ -2938,6 +2988,7 @@ namespace Emby.Server.Implementations.Library personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); saveEntity = true; + createEntity = true; } foreach (var id in person.ProviderIds) @@ -2965,15 +3016,15 @@ namespace Emby.Server.Implementations.Library if (saveEntity) { - (personsToSave ??= new()).Add(personEntity); + if (createEntity) + { + CreateItems([personEntity], null, CancellationToken.None); + } + await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); + CreateItems([personEntity], null, CancellationToken.None); } } - - if (personsToSave is not null) - { - CreateItems(personsToSave, null, CancellationToken.None); - } } private void StartScanInBackground() @@ -3027,7 +3078,7 @@ namespace Emby.Server.Implementations.Library { var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); - libraryOptions.PathInfos = [..libraryOptions.PathInfos, pathInfo]; + libraryOptions.PathInfos = [.. libraryOptions.PathInfos, pathInfo]; SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); |
