From ef6f342a54c9c6c8713b650c4ad60005af600ade Mon Sep 17 00:00:00 2001 From: John Corser Date: Sun, 17 May 2026 10:28:40 -0400 Subject: [PATCH] Use IItemTypeLookup and QueryPartitionHelpers Address review feedback: - Replace typeof(Person).FullName with IItemTypeLookup.BaseItemKindNames - Replace foreach+ToListAsync with PartitionEagerAsync for batched iteration with built-in progress reporting - Check HasImage/HasOverview on the loaded domain Person object instead of projecting from the DB query --- .../Tasks/PeopleValidationTask.cs | 102 +++++++++++------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index bdda7937fd..dff9a473af 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; @@ -27,6 +29,7 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask private readonly IDbContextFactory _dbContextFactory; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; + private readonly IItemTypeLookup _itemTypeLookup; /// /// Initializes a new instance of the class. @@ -36,18 +39,21 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public PeopleValidationTask( ILibraryManager libraryManager, ILocalizationManager localization, IDbContextFactory dbContextFactory, IFileSystem fileSystem, - ILogger logger) + ILogger logger, + IItemTypeLookup itemTypeLookup) { _libraryManager = libraryManager; _localization = localization; _dbContextFactory = dbContextFactory; _fileSystem = fileSystem; _logger = logger; + _itemTypeLookup = itemTypeLookup; } /// @@ -169,61 +175,51 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask private async Task RefreshPeopleImagesAsync(IProgress progress, CancellationToken cancellationToken) { var thirtyDaysAgo = DateTime.UtcNow.AddDays(-30); - var personTypeName = typeof(Person).FullName!; + var personTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]; var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); await using (context.ConfigureAwait(false)) { - var people = await context.BaseItems + const int PartitionSize = 100; + + var numPeople = await context.BaseItems .AsNoTracking() .Where(b => b.Type == personTypeName) .Where(b => b.DateLastRefreshed == null || b.DateLastRefreshed < thirtyDaysAgo) .Where(b => !b.Images!.Any(i => i.ImageType == ImageInfoImageType.Primary) || string.IsNullOrEmpty(b.Overview)) - .Select(b => new - { - b.Id, - HasImage = b.Images!.Any(i => i.ImageType == ImageInfoImageType.Primary), - HasOverview = !string.IsNullOrEmpty(b.Overview) - }) - .ToListAsync(cancellationToken) + .CountAsync(cancellationToken) .ConfigureAwait(false); - var numPeople = people.Count; - var numComplete = 0; - var numRefreshed = 0; - _logger.LogDebug("Found {Count} people needing image/overview refresh", numPeople); - foreach (var entry in people) + if (numPeople == 0) { - cancellationToken.ThrowIfCancellationRequested(); + progress.Report(100); + return; + } - try + var numComplete = 0; + var numRefreshed = 0; + + await foreach (var entry in context.BaseItems + .AsNoTracking() + .Where(b => b.Type == personTypeName) + .Where(b => b.DateLastRefreshed == null || b.DateLastRefreshed < thirtyDaysAgo) + .Where(b => + !b.Images!.Any(i => i.ImageType == ImageInfoImageType.Primary) || + string.IsNullOrEmpty(b.Overview)) + .OrderBy(b => b.Id) + .WithPartitionProgress(partition => _logger.LogDebug("Processing people partition {Partition}", partition)) + .PartitionEagerAsync(PartitionSize, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + if (await RefreshPersonAsync(entry.Id, cancellationToken).ConfigureAwait(false)) { - if (_libraryManager.GetItemById(entry.Id) is not Person item) - { - continue; - } - - var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - ImageRefreshMode = entry.HasImage ? MetadataRefreshMode.ValidationOnly : MetadataRefreshMode.Default, - MetadataRefreshMode = entry.HasOverview ? MetadataRefreshMode.ValidationOnly : MetadataRefreshMode.Default - }; - - await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); numRefreshed++; } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error refreshing images for person {PersonId}", entry.Id); - } numComplete++; progress.Report(100.0 * numComplete / numPeople); @@ -232,4 +228,36 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask _logger.LogInformation("Refreshed metadata for {Count} people missing images or overview", numRefreshed); } } + + private async Task RefreshPersonAsync(Guid personId, CancellationToken cancellationToken) + { + try + { + if (_libraryManager.GetItemById(personId) is not Person item) + { + return false; + } + + var hasImage = item.HasImage(MediaBrowser.Model.Entities.ImageType.Primary); + var hasOverview = !string.IsNullOrEmpty(item.Overview); + + var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ImageRefreshMode = hasImage ? MetadataRefreshMode.ValidationOnly : MetadataRefreshMode.Default, + MetadataRefreshMode = hasOverview ? MetadataRefreshMode.ValidationOnly : MetadataRefreshMode.Default + }; + + await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); + return true; + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error refreshing images for person {PersonId}", personId); + return false; + } + } }