using System; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Validators; /// /// Class StudiosValidator. /// public class StudiosValidator { /// /// The library manager. /// private readonly ILibraryManager _libraryManager; private readonly IItemRepository _itemRepo; /// /// The logger. /// private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The library manager. /// The logger. /// The item repository. public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; _itemRepo = itemRepo; } /// /// Runs the specified progress. /// /// The progress. /// The cancellation token. /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { var names = _itemRepo.GetStudioNames(); var existingStudioIds = _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.Studio] }).ToHashSet(); var existingStudios = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.Studio] }).Cast() .GroupBy(s => s.Name, StringComparer.OrdinalIgnoreCase) .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); var numComplete = 0; var count = names.Count; var refreshed = 0; foreach (var name in names) { try { Studio? item = null; if (existingStudios.TryGetValue(name, out var existingStudio)) { item = existingStudio; } // Fall back to GetStudio if not found (creates new item if needed) item ??= _libraryManager.GetStudio(name); if (!existingStudioIds.Contains(item.Id)) { await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); refreshed++; } } catch (OperationCanceledException) { // Don't clutter the log throw; } catch (Exception ex) { _logger.LogError(ex, "Error refreshing {StudioName}", name); } numComplete++; double percent = numComplete; percent /= count; percent *= 100; progress.Report(percent); } _logger.LogInformation("Refreshed metadata for {RefreshedCount} new studios out of {TotalCount} total", refreshed, count); var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.Studio], IsDeadStudio = true, IsLocked = false }); foreach (var item in deadEntities) { _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); } _libraryManager.DeleteItemsUnsafeFast(deadEntities, deleteSourceFiles: true); progress.Report(100); } }