diff --git a/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs
new file mode 100644
index 0000000000..94d7f3cd64
--- /dev/null
+++ b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs
@@ -0,0 +1,118 @@
+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 Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.Library
+{
+ ///
+ /// Test.
+ ///
+ public class ImageFetcherPostScanTask : ILibraryPostScanTask
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IProviderManager _providerManager;
+ private readonly ILogger _logger;
+ private readonly SemaphoreSlim _imageFetcherLock;
+
+ private ConcurrentDictionary _queuedItems;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Some stuff.
+ public ImageFetcherPostScanTask(
+ ILibraryManager libraryManager,
+ IProviderManager providerManager,
+ ILogger logger)
+ {
+ _libraryManager = libraryManager;
+ _providerManager = providerManager;
+ _logger = logger;
+ _queuedItems = new ConcurrentDictionary();
+ _imageFetcherLock = new SemaphoreSlim(1, 1);
+ _libraryManager.ItemAdded += OnLibraryManagerItemAddedOrUpdated;
+ _libraryManager.ItemUpdated += OnLibraryManagerItemAddedOrUpdated;
+ _providerManager.RefreshCompleted += OnProviderManagerRefreshCompleted;
+ }
+
+ ///
+ public async Task Run(IProgress 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;
+ }
+
+ _logger.LogDebug(
+ "Updating remote images for item {ItemId} with media type {ItemMediaType}",
+ queuedItem.item.Id.ToString("N", CultureInfo.InvariantCulture),
+ queuedItem.item.GetType());
+ await _libraryManager.UpdateImagesAsync(queuedItem.item, queuedItem.updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
+
+ _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 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 00282b71a5..74788a320f 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -857,7 +857,21 @@ namespace Emby.Server.Implementations.Library
/// Task{Person}.
public Person GetPerson(string name)
{
- return CreateItemByName(Person.GetPath, name, new DtoOptions(true));
+ var path = Person.GetPath(name);
+ var id = GetItemByNameId(path);
+ if (!(GetItemById(id) is Person item))
+ {
+ item = new Person
+ {
+ Name = name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow,
+ Path = path
+ };
+ }
+
+ return item;
}
///
@@ -1940,19 +1954,9 @@ namespace Emby.Server.Implementations.Library
}
///
- public async Task UpdateItemsAsync(IReadOnlyList items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ public Task UpdateItemsAsync(IReadOnlyList 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 +1987,27 @@ namespace Emby.Server.Implementations.Library
}
}
}
+
+ return Task.CompletedTask;
}
///
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
=> UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
+ public void RunMetadataSavers(IReadOnlyList items, ItemUpdateType updateReason)
+ {
+ foreach (var item in items)
+ {
+ if (item.IsFileProtocol)
+ {
+ ProviderManager.SaveMetadata(item, updateReason);
+ }
+
+ item.DateLastSaved = DateTime.UtcNow;
+ }
+ }
+
///
/// Reports the item removed.
///
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 68126bd8a8..0e99646084 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1433,7 +1433,6 @@ namespace MediaBrowser.Controller.Entities
new List();
var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
- await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh
if (ownedItemsChanged)
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 11542c1cad..b8737b48cd 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -352,11 +352,6 @@ namespace MediaBrowser.Controller.Entities
{
await currentChild.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
- else
- {
- // metadata is up-to-date; make sure DB has correct images dimensions and hash
- await LibraryManager.UpdateImagesAsync(currentChild).ConfigureAwait(false);
- }
continue;
}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 804170d5c9..d329495b9c 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -567,5 +567,7 @@ namespace MediaBrowser.Controller.Library
void AddExternalSubtitleStreams(List streams,
string videoPath,
string[] files);
+
+ void RunMetadataSavers(IReadOnlyList items, ItemUpdateType updateReason);
}
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index f110eafa5a..7ca51f4b5a 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -231,14 +231,14 @@ namespace MediaBrowser.Providers.Manager
private async Task SavePeopleMetadataAsync(List people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
{
+ var personsToSave = new List();
+
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl))
{
- var updateType = ItemUpdateType.MetadataDownload;
-
var saveEntity = false;
var personEntity = LibraryManager.GetPerson(person.Name);
foreach (var id in person.ProviderIds)
@@ -255,15 +255,17 @@ namespace MediaBrowser.Providers.Manager
await AddPersonImageAsync(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false);
saveEntity = true;
- updateType |= ItemUpdateType.ImageUpdate;
}
if (saveEntity)
{
- await personEntity.UpdateToRepositoryAsync(updateType, cancellationToken).ConfigureAwait(false);
+ personsToSave.Add(personEntity);
}
}
}
+
+ LibraryManager.RunMetadataSavers(personsToSave, ItemUpdateType.MetadataDownload);
+ LibraryManager.CreateItems(personsToSave, null, CancellationToken.None);
}
private async Task AddPersonImageAsync(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)