using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks.Tasks; /// /// Deletes path references from collections and playlists that no longer exists. /// public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask { private readonly ILocalizationManager _localization; private readonly ICollectionManager _collectionManager; private readonly IPlaylistManager _playlistManager; private readonly ILogger _logger; private readonly IProviderManager _providerManager; private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// The logger. /// The provider manager. /// The filesystem. public CleanupCollectionAndPlaylistPathsTask( ILocalizationManager localization, ICollectionManager collectionManager, IPlaylistManager playlistManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem) { _localization = localization; _collectionManager = collectionManager; _playlistManager = playlistManager; _logger = logger; _providerManager = providerManager; _fileSystem = fileSystem; } /// public string Name => _localization.GetLocalizedString("TaskCleanCollectionsAndPlaylists"); /// public string Key => "CleanCollectionsAndPlaylists"; /// public string Description => _localization.GetLocalizedString("TaskCleanCollectionsAndPlaylistsDescription"); /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); /// public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { var collectionsFolder = await _collectionManager.GetCollectionsFolder(false).ConfigureAwait(false); if (collectionsFolder is null) { _logger.LogDebug("There is no collections folder to be found"); } else { var collections = collectionsFolder.Children.OfType().ToArray(); _logger.LogDebug("Found {CollectionLength} boxsets", collections.Length); for (var index = 0; index < collections.Length; index++) { var collection = collections[index]; _logger.LogDebug("Checking boxset {CollectionName}", collection.Name); CleanupLinkedChildren(collection, cancellationToken); progress.Report(50D / collections.Length * (index + 1)); } } var playlistsFolder = _playlistManager.GetPlaylistsFolder(); if (playlistsFolder is null) { _logger.LogDebug("There is no playlists folder to be found"); return; } var playlists = playlistsFolder.Children.OfType().ToArray(); _logger.LogDebug("Found {PlaylistLength} playlists", playlists.Length); for (var index = 0; index < playlists.Length; index++) { var playlist = playlists[index]; _logger.LogDebug("Checking playlist {PlaylistName}", playlist.Name); CleanupLinkedChildren(playlist, cancellationToken); progress.Report(50D / playlists.Length * (index + 1)); } } private void CleanupLinkedChildren(T folder, CancellationToken cancellationToken) where T : Folder { List? itemsToRemove = null; foreach (var linkedChild in folder.LinkedChildren) { var path = linkedChild.Path; if (Path.HasExtension(path) ? !File.Exists(path) : !Directory.Exists(path)) { _logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, path); (itemsToRemove ??= new List()).Add(linkedChild); } } if (itemsToRemove is not null) { _logger.LogDebug("Updating {FolderName}", folder.Name); folder.LinkedChildren = folder.LinkedChildren.Except(itemsToRemove).ToArray(); folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken); _providerManager.QueueRefresh( folder.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, RefreshPriority.High); } } /// public IEnumerable GetDefaultTriggers() { return new[] { new TaskTriggerInfo() { Type = TaskTriggerInfo.TriggerStartup } }; } }