aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs130
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs76
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs2
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs28
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs2
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs2
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosValidator.cs2
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
});