diff options
Diffstat (limited to 'Emby.Server.Implementations/Library')
10 files changed, 208 insertions, 42 deletions
diff --git a/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs new file mode 100644 index 000000000..d4e790c9a --- /dev/null +++ b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Net; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + /// <summary> + /// A library post scan/refresh task for pre-fetching remote images. + /// </summary> + public class ImageFetcherPostScanTask : ILibraryPostScanTask + { + private readonly ILibraryManager _libraryManager; + private readonly IProviderManager _providerManager; + private readonly ILogger<ImageFetcherPostScanTask> _logger; + private readonly SemaphoreSlim _imageFetcherLock; + + private ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)> _queuedItems; + + /// <summary> + /// Initializes a new instance of the <see cref="ImageFetcherPostScanTask"/> class. + /// </summary> + /// <param name="libraryManager">An instance of <see cref="ILibraryManager"/>.</param> + /// <param name="providerManager">An instance of <see cref="IProviderManager"/>.</param> + /// <param name="logger">An instance of <see cref="ILogger{ImageFetcherPostScanTask}"/>.</param> + public ImageFetcherPostScanTask( + ILibraryManager libraryManager, + IProviderManager providerManager, + ILogger<ImageFetcherPostScanTask> logger) + { + _libraryManager = libraryManager; + _providerManager = providerManager; + _logger = logger; + _queuedItems = new ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)>(); + _imageFetcherLock = new SemaphoreSlim(1, 1); + _libraryManager.ItemAdded += OnLibraryManagerItemAddedOrUpdated; + _libraryManager.ItemUpdated += OnLibraryManagerItemAddedOrUpdated; + _providerManager.RefreshCompleted += OnProviderManagerRefreshCompleted; + } + + /// <inheritdoc /> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + // Sometimes a library scan will cause this to run twice if there's an item refresh going on. + await _imageFetcherLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var now = DateTime.UtcNow; + var itemGuids = _queuedItems.Keys.ToList(); + + for (var i = 0; i < itemGuids.Count; i++) + { + if (!_queuedItems.TryGetValue(itemGuids[i], out var queuedItem)) + { + continue; + } + + var itemId = queuedItem.item.Id.ToString("N", CultureInfo.InvariantCulture); + var itemType = queuedItem.item.GetType(); + _logger.LogDebug( + "Updating remote images for item {ItemId} with media type {ItemMediaType}", + itemId, + itemType); + try + { + await _libraryManager.UpdateImagesAsync(queuedItem.item, queuedItem.updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to fetch images for {Type} item with id {ItemId}", itemType, itemId); + } + + _queuedItems.TryRemove(queuedItem.item.Id, out _); + } + + if (itemGuids.Count > 0) + { + _logger.LogInformation( + "Finished updating/pre-fetching {NumberOfImages} images. Elapsed time: {TimeElapsed}s.", + itemGuids.Count.ToString(CultureInfo.InvariantCulture), + (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + } + else + { + _logger.LogDebug("No images were updated."); + } + } + finally + { + _imageFetcherLock.Release(); + } + } + + private void OnLibraryManagerItemAddedOrUpdated(object sender, ItemChangeEventArgs itemChangeEventArgs) + { + if (!_queuedItems.ContainsKey(itemChangeEventArgs.Item.Id) && itemChangeEventArgs.Item.ImageInfos.Length > 0) + { + _queuedItems.AddOrUpdate( + itemChangeEventArgs.Item.Id, + (itemChangeEventArgs.Item, itemChangeEventArgs.UpdateReason), + (key, existingValue) => existingValue); + } + } + + private void OnProviderManagerRefreshCompleted(object sender, GenericEventArgs<BaseItem> e) + { + if (!_queuedItems.ContainsKey(e.Argument.Id) && e.Argument.ImageInfos.Length > 0) + { + _queuedItems.AddOrUpdate( + e.Argument.Id, + (e.Argument, ItemUpdateType.None), + (key, existingValue) => existingValue); + } + + // The RefreshCompleted event is a bit awkward in that it seems to _only_ be fired on + // the item that was refreshed regardless of children refreshes. So we take it as a signal + // that the refresh is entirely completed. + Run(null, CancellationToken.None).GetAwaiter().GetResult(); + } + } +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 00282b71a..013781258 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Emby.Naming.Audio; @@ -857,7 +858,21 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{Person}.</returns> public Person GetPerson(string name) { - return CreateItemByName<Person>(Person.GetPath, name, new DtoOptions(true)); + var path = Person.GetPath(name); + var id = GetItemByNameId<Person>(path); + if (!(GetItemById(id) is Person item)) + { + item = new Person + { + Name = name, + Id = id, + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + Path = path + }; + } + + return item; } /// <summary> @@ -1502,7 +1517,7 @@ namespace Emby.Server.Implementations.Library { if (query.AncestorIds.Length == 0 && query.ParentId.Equals(Guid.Empty) && - query.ChannelIds.Length == 0 && + query.ChannelIds.Count == 0 && query.TopParentIds.Length == 0 && string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) && @@ -1940,19 +1955,9 @@ namespace Emby.Server.Implementations.Library } /// <inheritdoc /> - public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + public Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - foreach (var item in items) - { - if (item.IsFileProtocol) - { - ProviderManager.SaveMetadata(item, updateReason); - } - - item.DateLastSaved = DateTime.UtcNow; - - await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false); - } + RunMetadataSavers(items, updateReason); _itemRepository.SaveItems(items, cancellationToken); @@ -1983,12 +1988,27 @@ namespace Emby.Server.Implementations.Library } } } + + return Task.CompletedTask; } /// <inheritdoc /> public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken); + public void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason) + { + foreach (var item in items) + { + if (item.IsFileProtocol) + { + ProviderManager.SaveMetadata(item, updateReason); + } + + item.DateLastSaved = DateTime.UtcNow; + } + } + /// <summary> /// Reports the item removed. /// </summary> @@ -2440,6 +2460,21 @@ namespace Emby.Server.Implementations.Library new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); } + public BaseItem GetParentItem(string parentId, Guid? userId) + { + if (!string.IsNullOrEmpty(parentId)) + { + return GetItemById(new Guid(parentId)); + } + + if (userId.HasValue && userId != Guid.Empty) + { + return GetUserRootFolder(); + } + + return RootFolder; + } + /// <inheritdoc /> public bool IsVideoFile(string path) { @@ -2470,9 +2505,10 @@ namespace Emby.Server.Implementations.Library var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; + // TODO nullable - what are we trying to do there with empty episodeInfo? var episodeInfo = episode.IsFileProtocol - ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo() - : new Naming.TV.EpisodeInfo(); + ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path) + : new Naming.TV.EpisodeInfo(episode.Path); try { @@ -2561,12 +2597,12 @@ namespace Emby.Server.Implementations.Library if (!episode.IndexNumberEnd.HasValue || forceRefresh) { - if (episode.IndexNumberEnd != episodeInfo.EndingEpsiodeNumber) + if (episode.IndexNumberEnd != episodeInfo.EndingEpisodeNumber) { changed = true; } - episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; + episode.IndexNumberEnd = episodeInfo.EndingEpisodeNumber; } if (!episode.ParentIndexNumber.HasValue || forceRefresh) @@ -2690,7 +2726,7 @@ namespace Emby.Server.Implementations.Library var videos = videoListResolver.Resolve(fileSystemChildren); - var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase)); + var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase)); if (currentVideo != null) { @@ -2892,7 +2928,7 @@ namespace Emby.Server.Implementations.Library return item.GetImageInfo(image.Type, imageIndex); } - catch (HttpException ex) + catch (HttpRequestException ex) { if (ex.StatusCode.HasValue && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden)) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 376a15570..928f5f88e 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -849,7 +849,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Key can't be empty.", nameof(key)); } - var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2); + var keys = key.Split(LiveStreamIdDelimeter, 2); var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 179e0ed98..28fa06239 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.Library private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, string[] languagePreferences) { - // Give some preferance to external text subs for better performance + // Give some preference to external text subs for better performance return streams.Where(i => i.Type == type) .OrderBy(i => { diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 877fdec86..658c53f28 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Library var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(Audio).Name }, + IncludeItemTypes = new[] { nameof(Audio) }, DtoOptions = dtoOptions }) .Cast<Audio>() @@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.Library { return _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(Audio).Name }, + IncludeItemTypes = new[] { nameof(Audio) }, GenreIds = genreIds.ToArray(), diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 70be52411..2c4497c69 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -201,7 +201,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio continue; } - var firstMedia = resolvedItem.Files.First(); + var firstMedia = resolvedItem.Files[0]; var libraryItem = new T { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 9a69bce0e..c850e3a08 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -87,61 +87,61 @@ namespace Emby.Server.Implementations.Library var excludeItemTypes = query.ExcludeItemTypes.ToList(); var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList(); - excludeItemTypes.Add(typeof(Year).Name); - excludeItemTypes.Add(typeof(Folder).Name); + excludeItemTypes.Add(nameof(Year)); + excludeItemTypes.Add(nameof(Folder)); if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, typeof(Genre).Name); - AddIfMissing(includeItemTypes, typeof(MusicGenre).Name); + AddIfMissing(includeItemTypes, nameof(Genre)); + AddIfMissing(includeItemTypes, nameof(MusicGenre)); } } else { - AddIfMissing(excludeItemTypes, typeof(Genre).Name); - AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name); + AddIfMissing(excludeItemTypes, nameof(Genre)); + AddIfMissing(excludeItemTypes, nameof(MusicGenre)); } if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, typeof(Person).Name); + AddIfMissing(includeItemTypes, nameof(Person)); } } else { - AddIfMissing(excludeItemTypes, typeof(Person).Name); + AddIfMissing(excludeItemTypes, nameof(Person)); } if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, typeof(Studio).Name); + AddIfMissing(includeItemTypes, nameof(Studio)); } } else { - AddIfMissing(excludeItemTypes, typeof(Studio).Name); + AddIfMissing(excludeItemTypes, nameof(Studio)); } if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, typeof(MusicArtist).Name); + AddIfMissing(includeItemTypes, nameof(MusicArtist)); } } else { - AddIfMissing(excludeItemTypes, typeof(MusicArtist).Name); + AddIfMissing(excludeItemTypes, nameof(MusicArtist)); } - AddIfMissing(excludeItemTypes, typeof(CollectionFolder).Name); - AddIfMissing(excludeItemTypes, typeof(Folder).Name); + AddIfMissing(excludeItemTypes, nameof(CollectionFolder)); + AddIfMissing(excludeItemTypes, nameof(Folder)); var mediaTypes = query.MediaTypes.ToList(); if (includeItemTypes.Count > 0) diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index d4c8c35e6..f9a3e2c64 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(MusicArtist).Name }, + IncludeItemTypes = new[] { nameof(MusicArtist) }, IsDeadArtist = true, IsLocked = false }).Cast<MusicArtist>().ToList(); diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 8275c873a..8739a9e1b 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Library.Validators var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Person).Name }, + IncludeItemTypes = new[] { nameof(Person) }, IsDeadPerson = true, IsLocked = false }); diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index ca35adfff..9a8c5f39d 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Validators var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(Studio).Name }, + IncludeItemTypes = new[] { nameof(Studio) }, IsDeadStudio = true, IsLocked = false }); |
