Преглед изворни кода

Cleanup Tasks and Validators

Shadowghost пре 1 месец
родитељ
комит
5c9f70c375
28 измењених фајлова са 2199 додато и 2241 уклоњено
  1. 34 35
      Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
  2. 78 79
      Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
  3. 107 110
      Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
  4. 34 35
      Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
  5. 75 76
      Emby.Server.Implementations/Library/Validators/GenresValidator.cs
  6. 34 35
      Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
  7. 58 59
      Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
  8. 88 93
      Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
  9. 34 35
      Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
  10. 75 76
      Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
  11. 493 494
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  12. 189 190
      Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
  13. 6 9
      Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
  14. 112 116
      Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
  15. 50 51
      Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
  16. 5 6
      Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs
  17. 101 102
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  18. 65 65
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  19. 84 87
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
  20. 2 2
      Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs
  21. 54 60
      Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
  22. 45 49
      Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
  23. 84 80
      Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
  24. 41 42
      Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
  25. 60 61
      Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
  26. 77 78
      Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
  27. 34 35
      Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
  28. 80 81
      Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs

+ 34 - 35
Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs

@@ -5,45 +5,44 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class ArtistsPostScanTask.
+/// </summary>
+public class ArtistsPostScanTask : ILibraryPostScanTask
 {
     /// <summary>
-    /// Class ArtistsPostScanTask.
+    /// The _library manager.
     /// </summary>
-    public class ArtistsPostScanTask : ILibraryPostScanTask
-    {
-        /// <summary>
-        /// The _library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILogger<ArtistsValidator> _logger;
-        private readonly IItemRepository _itemRepo;
+    private readonly ILibraryManager _libraryManager;
+    private readonly ILogger<ArtistsValidator> _logger;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public ArtistsPostScanTask(
-            ILibraryManager libraryManager,
-            ILogger<ArtistsValidator> logger,
-            IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public ArtistsPostScanTask(
+        ILibraryManager libraryManager,
+        ILogger<ArtistsValidator> logger,
+        IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
-        }
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
     }
 }

+ 78 - 79
Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs

@@ -10,102 +10,101 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class ArtistsValidator.
+/// </summary>
+public class ArtistsValidator
 {
     /// <summary>
-    /// Class ArtistsValidator.
+    /// The library manager.
     /// </summary>
-    public class ArtistsValidator
-    {
-        /// <summary>
-        /// The library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
+    private readonly ILibraryManager _libraryManager;
 
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private readonly ILogger<ArtistsValidator> _logger;
-        private readonly IItemRepository _itemRepo;
+    /// <summary>
+    /// The logger.
+    /// </summary>
+    private readonly ILogger<ArtistsValidator> _logger;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var names = _itemRepo.GetAllArtistNames();
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var names = _itemRepo.GetAllArtistNames();
 
-            var numComplete = 0;
-            var count = names.Count;
+        var numComplete = 0;
+        var count = names.Count;
 
-            foreach (var name in names)
+        foreach (var name in names)
+        {
+            try
             {
-                try
-                {
-                    var item = _libraryManager.GetArtist(name);
-
-                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    // Don't clutter the log
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error refreshing {ArtistName}", name);
-                }
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= count;
-                percent *= 100;
+                var item = _libraryManager.GetArtist(name);
 
-                progress.Report(percent);
+                await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
             }
-
-            var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+            catch (OperationCanceledException)
             {
-                IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
-                IsDeadArtist = true,
-                IsLocked = false
-            }).Cast<MusicArtist>().ToList();
-
-            foreach (var item in deadEntities)
+                // Don't clutter the log
+                throw;
+            }
+            catch (Exception ex)
             {
-                if (!item.IsAccessedByName)
-                {
-                    continue;
-                }
+                _logger.LogError(ex, "Error refreshing {ArtistName}", name);
+            }
 
-                _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
+            numComplete++;
+            double percent = numComplete;
+            percent /= count;
+            percent *= 100;
 
-                _libraryManager.DeleteItem(
-                    item,
-                    new DeleteOptions
-                    {
-                        DeleteFileLocation = false
-                    },
-                    false);
+            progress.Report(percent);
+        }
+
+        var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+        {
+            IncludeItemTypes = [BaseItemKind.MusicArtist],
+            IsDeadArtist = true,
+            IsLocked = false
+        }).Cast<MusicArtist>().ToList();
+
+        foreach (var item in deadEntities)
+        {
+            if (!item.IsAccessedByName)
+            {
+                continue;
             }
 
-            progress.Report(100);
+            _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
+
+            _libraryManager.DeleteItem(
+                item,
+                new DeleteOptions
+                {
+                    DeleteFileLocation = false
+                },
+                false);
         }
+
+        progress.Report(100);
     }
 }

+ 107 - 110
Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs

@@ -9,149 +9,146 @@ using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class CollectionPostScanTask.
+/// </summary>
+public class CollectionPostScanTask : ILibraryPostScanTask
 {
+    private readonly ILibraryManager _libraryManager;
+    private readonly ICollectionManager _collectionManager;
+    private readonly ILogger<CollectionPostScanTask> _logger;
+
     /// <summary>
-    /// Class CollectionPostScanTask.
+    /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class.
     /// </summary>
-    public class CollectionPostScanTask : ILibraryPostScanTask
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="collectionManager">The collection manager.</param>
+    /// <param name="logger">The logger.</param>
+    public CollectionPostScanTask(
+        ILibraryManager libraryManager,
+        ICollectionManager collectionManager,
+        ILogger<CollectionPostScanTask> logger)
     {
-        private readonly ILibraryManager _libraryManager;
-        private readonly ICollectionManager _collectionManager;
-        private readonly ILogger<CollectionPostScanTask> _logger;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="collectionManager">The collection manager.</param>
-        /// <param name="logger">The logger.</param>
-        public CollectionPostScanTask(
-            ILibraryManager libraryManager,
-            ICollectionManager collectionManager,
-            ILogger<CollectionPostScanTask> logger)
-        {
-            _libraryManager = libraryManager;
-            _collectionManager = collectionManager;
-            _logger = logger;
-        }
+        _libraryManager = libraryManager;
+        _collectionManager = collectionManager;
+        _logger = logger;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>();
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>();
 
-            foreach (var library in _libraryManager.RootFolder.Children)
+        foreach (var library in _libraryManager.RootFolder.Children)
+        {
+            if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
             {
-                if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
-                {
-                    continue;
-                }
+                continue;
+            }
 
-                var startIndex = 0;
-                var pagesize = 1000;
+            var startIndex = 0;
+            var pagesize = 1000;
 
-                while (true)
+            while (true)
+            {
+                var movies = _libraryManager.GetItemList(new InternalItemsQuery
                 {
-                    var movies = _libraryManager.GetItemList(new InternalItemsQuery
-                    {
-                        MediaTypes = new[] { MediaType.Video },
-                        IncludeItemTypes = new[] { BaseItemKind.Movie },
-                        IsVirtualItem = false,
-                        OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
-                        Parent = library,
-                        StartIndex = startIndex,
-                        Limit = pagesize,
-                        Recursive = true
-                    });
-
-                    foreach (var m in movies)
+                    MediaTypes = [MediaType.Video],
+                    IncludeItemTypes = [BaseItemKind.Movie],
+                    IsVirtualItem = false,
+                    OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending)],
+                    Parent = library,
+                    StartIndex = startIndex,
+                    Limit = pagesize,
+                    Recursive = true
+                });
+
+                foreach (var m in movies)
+                {
+                    if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
                     {
-                        if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
+                        if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
                         {
-                            if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
-                            {
-                                movieList.Add(movie.Id);
-                            }
-                            else
-                            {
-                                collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id };
-                            }
+                            movieList.Add(movie.Id);
+                        }
+                        else
+                        {
+                            collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id };
                         }
                     }
+                }
 
-                    if (movies.Count < pagesize)
-                    {
-                        break;
-                    }
-
-                    startIndex += pagesize;
+                if (movies.Count < pagesize)
+                {
+                    break;
                 }
+
+                startIndex += pagesize;
             }
+        }
 
-            var numComplete = 0;
-            var count = collectionNameMoviesMap.Count;
+        var numComplete = 0;
+        var count = collectionNameMoviesMap.Count;
 
-            if (count == 0)
-            {
-                progress.Report(100);
-                return;
-            }
+        if (count == 0)
+        {
+            progress.Report(100);
+            return;
+        }
 
-            var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
-            {
-                IncludeItemTypes = new[] { BaseItemKind.BoxSet },
-                CollapseBoxSetItems = false,
-                Recursive = true
-            });
+        var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
+        {
+            IncludeItemTypes = [BaseItemKind.BoxSet],
+            CollapseBoxSetItems = false,
+            Recursive = true
+        });
 
-            foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
+        foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
+        {
+            try
             {
-                try
+                var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet;
+                if (boxSet is null)
                 {
-                    var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet;
-                    if (boxSet is null)
+                    // won't automatically create collection if only one movie in it
+                    if (movieIds.Count >= 2)
                     {
-                        // won't automatically create collection if only one movie in it
-                        if (movieIds.Count >= 2)
+                        boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
                         {
-                            boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
-                            {
-                                Name = collectionName,
-                                IsLocked = true
-                            });
+                            Name = collectionName,
+                            IsLocked = true
+                        }).ConfigureAwait(false);
 
-                            await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
-                        }
+                        await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false);
                     }
-                    else
-                    {
-                        await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
-                    }
-
-                    numComplete++;
-                    double percent = numComplete;
-                    percent /= count;
-                    percent *= 100;
-
-                    progress.Report(percent);
                 }
-                catch (Exception ex)
+                else
                 {
-                    _logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds);
+                    await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false);
                 }
-            }
 
-            progress.Report(100);
+                numComplete++;
+                double percent = numComplete;
+                percent /= count;
+                percent *= 100;
+
+                progress.Report(percent);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds);
+            }
         }
+
+        progress.Report(100);
     }
 }

+ 34 - 35
Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs

@@ -5,45 +5,44 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class GenresPostScanTask.
+/// </summary>
+public class GenresPostScanTask : ILibraryPostScanTask
 {
     /// <summary>
-    /// Class GenresPostScanTask.
+    /// The _library manager.
     /// </summary>
-    public class GenresPostScanTask : ILibraryPostScanTask
-    {
-        /// <summary>
-        /// The _library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILogger<GenresValidator> _logger;
-        private readonly IItemRepository _itemRepo;
+    private readonly ILibraryManager _libraryManager;
+    private readonly ILogger<GenresValidator> _logger;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public GenresPostScanTask(
-            ILibraryManager libraryManager,
-            ILogger<GenresValidator> logger,
-            IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public GenresPostScanTask(
+        ILibraryManager libraryManager,
+        ILogger<GenresValidator> logger,
+        IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
-        }
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
     }
 }

+ 75 - 76
Emby.Server.Implementations/Library/Validators/GenresValidator.cs

@@ -8,97 +8,96 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class GenresValidator.
+/// </summary>
+public class GenresValidator
 {
     /// <summary>
-    /// Class GenresValidator.
+    /// The library manager.
     /// </summary>
-    public class GenresValidator
-    {
-        /// <summary>
-        /// The library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
-        private readonly IItemRepository _itemRepo;
+    private readonly ILibraryManager _libraryManager;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private readonly ILogger<GenresValidator> _logger;
+    /// <summary>
+    /// The logger.
+    /// </summary>
+    private readonly ILogger<GenresValidator> _logger;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="GenresValidator"/> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="GenresValidator"/> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var names = _itemRepo.GetGenreNames();
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var names = _itemRepo.GetGenreNames();
 
-            var numComplete = 0;
-            var count = names.Count;
+        var numComplete = 0;
+        var count = names.Count;
 
-            foreach (var name in names)
+        foreach (var name in names)
+        {
+            try
             {
-                try
-                {
-                    var item = _libraryManager.GetGenre(name);
+                var item = _libraryManager.GetGenre(name);
 
-                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    // Don't clutter the log
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error refreshing {GenreName}", name);
-                }
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= count;
-                percent *= 100;
-
-                progress.Report(percent);
+                await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
             }
-
-            var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+            catch (OperationCanceledException)
             {
-                IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre],
-                IsDeadGenre = true,
-                IsLocked = false
-            });
-
-            foreach (var item in deadEntities)
+                // Don't clutter the log
+                throw;
+            }
+            catch (Exception ex)
             {
-                _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
-
-                _libraryManager.DeleteItem(
-                    item,
-                    new DeleteOptions
-                    {
-                        DeleteFileLocation = false
-                    },
-                    false);
+                _logger.LogError(ex, "Error refreshing {GenreName}", name);
             }
 
-            progress.Report(100);
+            numComplete++;
+            double percent = numComplete;
+            percent /= count;
+            percent *= 100;
+
+            progress.Report(percent);
         }
+
+        var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+        {
+            IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre],
+            IsDeadGenre = 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.DeleteItem(
+                item,
+                new DeleteOptions
+                {
+                    DeleteFileLocation = false
+                },
+                false);
+        }
+
+        progress.Report(100);
     }
 }

+ 34 - 35
Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs

@@ -5,45 +5,44 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class MusicGenresPostScanTask.
+/// </summary>
+public class MusicGenresPostScanTask : ILibraryPostScanTask
 {
     /// <summary>
-    /// Class MusicGenresPostScanTask.
+    /// The library manager.
     /// </summary>
-    public class MusicGenresPostScanTask : ILibraryPostScanTask
-    {
-        /// <summary>
-        /// The library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILogger<MusicGenresValidator> _logger;
-        private readonly IItemRepository _itemRepo;
+    private readonly ILibraryManager _libraryManager;
+    private readonly ILogger<MusicGenresValidator> _logger;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public MusicGenresPostScanTask(
-            ILibraryManager libraryManager,
-            ILogger<MusicGenresValidator> logger,
-            IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public MusicGenresPostScanTask(
+        ILibraryManager libraryManager,
+        ILogger<MusicGenresValidator> logger,
+        IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
-        }
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
     }
 }

+ 58 - 59
Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs

@@ -5,77 +5,76 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class MusicGenresValidator.
+/// </summary>
+public class MusicGenresValidator
 {
     /// <summary>
-    /// Class MusicGenresValidator.
+    /// The library manager.
     /// </summary>
-    public class MusicGenresValidator
-    {
-        /// <summary>
-        /// The library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
+    private readonly ILibraryManager _libraryManager;
 
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private readonly ILogger<MusicGenresValidator> _logger;
-        private readonly IItemRepository _itemRepo;
+    /// <summary>
+    /// The logger.
+    /// </summary>
+    private readonly ILogger<MusicGenresValidator> _logger;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="MusicGenresValidator" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="MusicGenresValidator" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var names = _itemRepo.GetMusicGenreNames();
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var names = _itemRepo.GetMusicGenreNames();
 
-            var numComplete = 0;
-            var count = names.Count;
+        var numComplete = 0;
+        var count = names.Count;
 
-            foreach (var name in names)
+        foreach (var name in names)
+        {
+            try
             {
-                try
-                {
-                    var item = _libraryManager.GetMusicGenre(name);
+                var item = _libraryManager.GetMusicGenre(name);
 
-                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    // Don't clutter the log
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error refreshing {GenreName}", name);
-                }
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= count;
-                percent *= 100;
-
-                progress.Report(percent);
+                await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+            }
+            catch (OperationCanceledException)
+            {
+                // Don't clutter the log
+                throw;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error refreshing {GenreName}", name);
             }
 
-            progress.Report(100);
+            numComplete++;
+            double percent = numComplete;
+            percent /= count;
+            percent *= 100;
+
+            progress.Report(percent);
         }
+
+        progress.Report(100);
     }
 }

+ 88 - 93
Emby.Server.Implementations/Library/Validators/PeopleValidator.cs

@@ -9,119 +9,114 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.IO;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class PeopleValidator.
+/// </summary>
+public class PeopleValidator
 {
     /// <summary>
-    /// Class PeopleValidator.
+    /// The _library manager.
     /// </summary>
-    public class PeopleValidator
+    private readonly ILibraryManager _libraryManager;
+
+    /// <summary>
+    /// The _logger.
+    /// </summary>
+    private readonly ILogger _logger;
+
+    private readonly IFileSystem _fileSystem;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PeopleValidator" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="fileSystem">The file system.</param>
+    public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
     {
-        /// <summary>
-        /// The _library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
-
-        /// <summary>
-        /// The _logger.
-        /// </summary>
-        private readonly ILogger _logger;
-
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PeopleValidator" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="fileSystem">The file system.</param>
-        public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _fileSystem = fileSystem;
-        }
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _fileSystem = fileSystem;
+    }
 
-        /// <summary>
-        /// Validates the people.
-        /// </summary>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="progress">The progress.</param>
-        /// <returns>Task.</returns>
-        public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
-        {
-            var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery());
+    /// <summary>
+    /// Validates the people.
+    /// </summary>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <param name="progress">The progress.</param>
+    /// <returns>Task.</returns>
+    public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
+    {
+        var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery());
 
-            var numComplete = 0;
+        var numComplete = 0;
 
-            var numPeople = people.Count;
+        var numPeople = people.Count;
 
-            _logger.LogDebug("Will refresh {0} people", numPeople);
+        _logger.LogDebug("Will refresh {Amount} people", numPeople);
 
-            foreach (var person in people)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
+        foreach (var person in people)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
 
-                try
-                {
-                    var item = _libraryManager.GetPerson(person);
-                    if (item is null)
-                    {
-                        _logger.LogWarning("Failed to get person: {Name}", person);
-                        continue;
-                    }
-
-                    var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
-                    {
-                        ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
-                        MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
-                    };
-
-                    await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    throw;
-                }
-                catch (Exception ex)
+            try
+            {
+                var item = _libraryManager.GetPerson(person);
+                if (item is null)
                 {
-                    _logger.LogError(ex, "Error validating IBN entry {Person}", person);
+                    _logger.LogWarning("Failed to get person: {Name}", person);
+                    continue;
                 }
 
-                // Update progress
-                numComplete++;
-                double percent = numComplete;
-                percent /= numPeople;
+                var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                {
+                    ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
+                    MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
+                };
 
-                progress.Report(100 * percent);
+                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
             }
-
-            var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+            catch (OperationCanceledException)
             {
-                IncludeItemTypes = [BaseItemKind.Person],
-                IsDeadPerson = true,
-                IsLocked = false
-            });
-
-            foreach (var item in deadEntities)
+                throw;
+            }
+            catch (Exception ex)
             {
-                _logger.LogInformation(
-                    "Deleting dead {2} {0} {1}.",
-                    item.Id.ToString("N", CultureInfo.InvariantCulture),
-                    item.Name,
-                    item.GetType().Name);
-
-                _libraryManager.DeleteItem(
-                    item,
-                    new DeleteOptions
-                    {
-                        DeleteFileLocation = false
-                    },
-                    false);
+                _logger.LogError(ex, "Error validating IBN entry {Person}", person);
             }
 
-            progress.Report(100);
+            // Update progress
+            numComplete++;
+            double percent = numComplete;
+            percent /= numPeople;
 
-            _logger.LogInformation("People validation complete");
+            progress.Report(100 * percent);
         }
+
+        var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+        {
+            IncludeItemTypes = [BaseItemKind.Person],
+            IsDeadPerson = 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.DeleteItem(
+                item,
+                new DeleteOptions
+                {
+                    DeleteFileLocation = false
+                },
+                false);
+        }
+
+        progress.Report(100);
+
+        _logger.LogInformation("People validation complete");
     }
 }

+ 34 - 35
Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs

@@ -5,46 +5,45 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class MusicGenresPostScanTask.
+/// </summary>
+public class StudiosPostScanTask : ILibraryPostScanTask
 {
     /// <summary>
-    /// Class MusicGenresPostScanTask.
+    /// The _library manager.
     /// </summary>
-    public class StudiosPostScanTask : ILibraryPostScanTask
-    {
-        /// <summary>
-        /// The _library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
+    private readonly ILibraryManager _libraryManager;
 
-        private readonly ILogger<StudiosValidator> _logger;
-        private readonly IItemRepository _itemRepo;
+    private readonly ILogger<StudiosValidator> _logger;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public StudiosPostScanTask(
-            ILibraryManager libraryManager,
-            ILogger<StudiosValidator> logger,
-            IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public StudiosPostScanTask(
+        ILibraryManager libraryManager,
+        ILogger<StudiosValidator> logger,
+        IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
-        }
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
     }
 }

+ 75 - 76
Emby.Server.Implementations/Library/Validators/StudiosValidator.cs

@@ -8,98 +8,97 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.Library.Validators
+namespace Emby.Server.Implementations.Library.Validators;
+
+/// <summary>
+/// Class StudiosValidator.
+/// </summary>
+public class StudiosValidator
 {
     /// <summary>
-    /// Class StudiosValidator.
+    /// The library manager.
     /// </summary>
-    public class StudiosValidator
-    {
-        /// <summary>
-        /// The library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
+    private readonly ILibraryManager _libraryManager;
 
-        private readonly IItemRepository _itemRepo;
+    private readonly IItemRepository _itemRepo;
 
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private readonly ILogger<StudiosValidator> _logger;
+    /// <summary>
+    /// The logger.
+    /// </summary>
+    private readonly ILogger<StudiosValidator> _logger;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StudiosValidator" /> class.
-        /// </summary>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="itemRepo">The item repository.</param>
-        public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo)
-        {
-            _libraryManager = libraryManager;
-            _logger = logger;
-            _itemRepo = itemRepo;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="StudiosValidator" /> class.
+    /// </summary>
+    /// <param name="libraryManager">The library manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <param name="itemRepo">The item repository.</param>
+    public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo)
+    {
+        _libraryManager = libraryManager;
+        _logger = logger;
+        _itemRepo = itemRepo;
+    }
 
-        /// <summary>
-        /// Runs the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var names = _itemRepo.GetStudioNames();
+    /// <summary>
+    /// Runs the specified progress.
+    /// </summary>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns>Task.</returns>
+    public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var names = _itemRepo.GetStudioNames();
 
-            var numComplete = 0;
-            var count = names.Count;
+        var numComplete = 0;
+        var count = names.Count;
 
-            foreach (var name in names)
+        foreach (var name in names)
+        {
+            try
             {
-                try
-                {
-                    var item = _libraryManager.GetStudio(name);
+                var item = _libraryManager.GetStudio(name);
 
-                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-                }
-                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);
+                await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
             }
-
-            var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+            catch (OperationCanceledException)
             {
-                IncludeItemTypes = new[] { BaseItemKind.Studio },
-                IsDeadStudio = true,
-                IsLocked = false
-            });
-
-            foreach (var item in deadEntities)
+                // Don't clutter the log
+                throw;
+            }
+            catch (Exception ex)
             {
-                _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
-
-                _libraryManager.DeleteItem(
-                    item,
-                    new DeleteOptions
-                    {
-                        DeleteFileLocation = false
-                    },
-                    false);
+                _logger.LogError(ex, "Error refreshing {StudioName}", name);
             }
 
-            progress.Report(100);
+            numComplete++;
+            double percent = numComplete;
+            percent /= count;
+            percent *= 100;
+
+            progress.Report(percent);
         }
+
+        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.DeleteItem(
+                item,
+                new DeleteOptions
+                {
+                    DeleteFileLocation = false
+                },
+                false);
+        }
+
+        progress.Report(100);
     }
 }

+ 493 - 494
Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -16,663 +16,662 @@ using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks;
+
+/// <summary>
+/// Class ScheduledTaskWorker.
+/// </summary>
+public class ScheduledTaskWorker : IScheduledTaskWorker
 {
+    private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+    private readonly IApplicationPaths _applicationPaths;
+    private readonly ILogger _logger;
+    private readonly ITaskManager _taskManager;
+    private readonly Lock _lastExecutionResultSyncLock = new();
+    private bool _readFromFile;
+    private TaskResult _lastExecutionResult;
+    private Task _currentTask;
+    private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
+    private string _id;
+
     /// <summary>
-    /// Class ScheduledTaskWorker.
+    /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
     /// </summary>
-    public class ScheduledTaskWorker : IScheduledTaskWorker
+    /// <param name="scheduledTask">The scheduled task.</param>
+    /// <param name="applicationPaths">The application paths.</param>
+    /// <param name="taskManager">The task manager.</param>
+    /// <param name="logger">The logger.</param>
+    /// <exception cref="ArgumentNullException">
+    /// scheduledTask
+    /// or
+    /// applicationPaths
+    /// or
+    /// taskManager
+    /// or
+    /// jsonSerializer
+    /// or
+    /// logger.
+    /// </exception>
+    public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
     {
-        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
-        private readonly IApplicationPaths _applicationPaths;
-        private readonly ILogger _logger;
-        private readonly ITaskManager _taskManager;
-        private readonly Lock _lastExecutionResultSyncLock = new();
-        private bool _readFromFile;
-        private TaskResult _lastExecutionResult;
-        private Task _currentTask;
-        private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
-        private string _id;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
-        /// </summary>
-        /// <param name="scheduledTask">The scheduled task.</param>
-        /// <param name="applicationPaths">The application paths.</param>
-        /// <param name="taskManager">The task manager.</param>
-        /// <param name="logger">The logger.</param>
-        /// <exception cref="ArgumentNullException">
-        /// scheduledTask
-        /// or
-        /// applicationPaths
-        /// or
-        /// taskManager
-        /// or
-        /// jsonSerializer
-        /// or
-        /// logger.
-        /// </exception>
-        public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
-        {
-            ArgumentNullException.ThrowIfNull(scheduledTask);
-            ArgumentNullException.ThrowIfNull(applicationPaths);
-            ArgumentNullException.ThrowIfNull(taskManager);
-            ArgumentNullException.ThrowIfNull(logger);
+        ArgumentNullException.ThrowIfNull(scheduledTask);
+        ArgumentNullException.ThrowIfNull(applicationPaths);
+        ArgumentNullException.ThrowIfNull(taskManager);
+        ArgumentNullException.ThrowIfNull(logger);
 
-            ScheduledTask = scheduledTask;
-            _applicationPaths = applicationPaths;
-            _taskManager = taskManager;
-            _logger = logger;
+        ScheduledTask = scheduledTask;
+        _applicationPaths = applicationPaths;
+        _taskManager = taskManager;
+        _logger = logger;
 
-            InitTriggerEvents();
-        }
+        InitTriggerEvents();
+    }
 
-        /// <inheritdoc />
-        public event EventHandler<GenericEventArgs<double>> TaskProgress;
+    /// <inheritdoc />
+    public event EventHandler<GenericEventArgs<double>> TaskProgress;
 
-        /// <inheritdoc />
-        public IScheduledTask ScheduledTask { get; private set; }
+    /// <inheritdoc />
+    public IScheduledTask ScheduledTask { get; private set; }
 
-        /// <inheritdoc />
-        public TaskResult LastExecutionResult
+    /// <inheritdoc />
+    public TaskResult LastExecutionResult
+    {
+        get
         {
-            get
-            {
-                var path = GetHistoryFilePath();
+            var path = GetHistoryFilePath();
 
-                lock (_lastExecutionResultSyncLock)
+            lock (_lastExecutionResultSyncLock)
+            {
+                if (_lastExecutionResult is null && !_readFromFile)
                 {
-                    if (_lastExecutionResult is null && !_readFromFile)
+                    if (File.Exists(path))
                     {
-                        if (File.Exists(path))
+                        var bytes = File.ReadAllBytes(path);
+                        if (bytes.Length > 0)
                         {
-                            var bytes = File.ReadAllBytes(path);
-                            if (bytes.Length > 0)
+                            try
                             {
-                                try
-                                {
-                                    _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(bytes, _jsonOptions);
-                                }
-                                catch (JsonException ex)
-                                {
-                                    _logger.LogError(ex, "Error deserializing {File}", path);
-                                }
+                                _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(bytes, _jsonOptions);
                             }
-                            else
+                            catch (JsonException ex)
                             {
-                                _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
+                                _logger.LogError(ex, "Error deserializing {File}", path);
                             }
                         }
-
-                        _readFromFile = true;
+                        else
+                        {
+                            _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
+                        }
                     }
-                }
 
-                return _lastExecutionResult;
+                    _readFromFile = true;
+                }
             }
 
-            private set
-            {
-                _lastExecutionResult = value;
+            return _lastExecutionResult;
+        }
 
-                var path = GetHistoryFilePath();
-                Directory.CreateDirectory(Path.GetDirectoryName(path));
+        private set
+        {
+            _lastExecutionResult = value;
 
-                lock (_lastExecutionResultSyncLock)
-                {
-                    using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
-                    using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream);
-                    JsonSerializer.Serialize(jsonStream, value, _jsonOptions);
-                }
+            var path = GetHistoryFilePath();
+            Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+            lock (_lastExecutionResultSyncLock)
+            {
+                using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+                using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream);
+                JsonSerializer.Serialize(jsonStream, value, _jsonOptions);
             }
         }
+    }
 
-        /// <inheritdoc />
-        public string Name => ScheduledTask.Name;
+    /// <inheritdoc />
+    public string Name => ScheduledTask.Name;
 
-        /// <inheritdoc />
-        public string Description => ScheduledTask.Description;
+    /// <inheritdoc />
+    public string Description => ScheduledTask.Description;
 
-        /// <inheritdoc />
-        public string Category => ScheduledTask.Category;
+    /// <inheritdoc />
+    public string Category => ScheduledTask.Category;
 
-        /// <summary>
-        /// Gets or sets the current cancellation token.
-        /// </summary>
-        /// <value>The current cancellation token source.</value>
-        private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
+    /// <summary>
+    /// Gets or sets the current cancellation token.
+    /// </summary>
+    /// <value>The current cancellation token source.</value>
+    private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
 
-        /// <summary>
-        /// Gets or sets the current execution start time.
-        /// </summary>
-        /// <value>The current execution start time.</value>
-        private DateTime CurrentExecutionStartTime { get; set; }
+    /// <summary>
+    /// Gets or sets the current execution start time.
+    /// </summary>
+    /// <value>The current execution start time.</value>
+    private DateTime CurrentExecutionStartTime { get; set; }
 
-        /// <inheritdoc />
-        public TaskState State
+    /// <inheritdoc />
+    public TaskState State
+    {
+        get
         {
-            get
+            if (CurrentCancellationTokenSource is not null)
             {
-                if (CurrentCancellationTokenSource is not null)
-                {
-                    return CurrentCancellationTokenSource.IsCancellationRequested
-                               ? TaskState.Cancelling
-                               : TaskState.Running;
-                }
-
-                return TaskState.Idle;
+                return CurrentCancellationTokenSource.IsCancellationRequested
+                            ? TaskState.Cancelling
+                            : TaskState.Running;
             }
+
+            return TaskState.Idle;
         }
+    }
 
-        /// <inheritdoc />
-        public double? CurrentProgress { get; private set; }
+    /// <inheritdoc />
+    public double? CurrentProgress { get; private set; }
 
-        /// <summary>
-        /// Gets or sets the triggers that define when the task will run.
-        /// </summary>
-        /// <value>The triggers.</value>
-        private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
+    /// <summary>
+    /// Gets or sets the triggers that define when the task will run.
+    /// </summary>
+    /// <value>The triggers.</value>
+    private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
+    {
+        get => _triggers;
+        set
         {
-            get => _triggers;
-            set
-            {
-                ArgumentNullException.ThrowIfNull(value);
+            ArgumentNullException.ThrowIfNull(value);
 
-                // Cleanup current triggers
-                if (_triggers is not null)
-                {
-                    DisposeTriggers();
-                }
+            // Cleanup current triggers
+            if (_triggers is not null)
+            {
+                DisposeTriggers();
+            }
 
-                _triggers = value.ToArray();
+            _triggers = value.ToArray();
 
-                ReloadTriggerEvents(false);
-            }
+            ReloadTriggerEvents(false);
         }
+    }
 
-        /// <inheritdoc />
-        public IReadOnlyList<TaskTriggerInfo> Triggers
+    /// <inheritdoc />
+    public IReadOnlyList<TaskTriggerInfo> Triggers
+    {
+        get
         {
-            get
-            {
-                return Array.ConvertAll(InternalTriggers, i => i.Item1);
-            }
+            return Array.ConvertAll(InternalTriggers, i => i.Item1);
+        }
 
-            set
-            {
-                ArgumentNullException.ThrowIfNull(value);
+        set
+        {
+            ArgumentNullException.ThrowIfNull(value);
 
-                // This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly
-                var triggerList = value.Where(i => i is not null).ToArray();
+            // This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly
+            var triggerList = value.Where(i => i is not null).ToArray();
 
-                SaveTriggers(triggerList);
+            SaveTriggers(triggerList);
 
-                InternalTriggers = Array.ConvertAll(triggerList, i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i)));
-            }
+            InternalTriggers = Array.ConvertAll(triggerList, i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i)));
         }
+    }
 
-        /// <inheritdoc />
-        public string Id
+    /// <inheritdoc />
+    public string Id
+    {
+        get
         {
-            get
-            {
-                return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
-            }
+            return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
         }
+    }
 
-        private void InitTriggerEvents()
-        {
-            _triggers = LoadTriggers();
-            ReloadTriggerEvents(true);
-        }
+    private void InitTriggerEvents()
+    {
+        _triggers = LoadTriggers();
+        ReloadTriggerEvents(true);
+    }
 
-        /// <inheritdoc />
-        public void ReloadTriggerEvents()
-        {
-            ReloadTriggerEvents(false);
-        }
+    /// <inheritdoc />
+    public void ReloadTriggerEvents()
+    {
+        ReloadTriggerEvents(false);
+    }
 
-        /// <summary>
-        /// Reloads the trigger events.
-        /// </summary>
-        /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        private void ReloadTriggerEvents(bool isApplicationStartup)
+    /// <summary>
+    /// Reloads the trigger events.
+    /// </summary>
+    /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
+    private void ReloadTriggerEvents(bool isApplicationStartup)
+    {
+        foreach (var triggerInfo in InternalTriggers)
         {
-            foreach (var triggerInfo in InternalTriggers)
-            {
-                var trigger = triggerInfo.Item2;
+            var trigger = triggerInfo.Item2;
 
-                trigger.Stop();
+            trigger.Stop();
 
-                trigger.Triggered -= OnTriggerTriggered;
-                trigger.Triggered += OnTriggerTriggered;
-                trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup);
-            }
+            trigger.Triggered -= OnTriggerTriggered;
+            trigger.Triggered += OnTriggerTriggered;
+            trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup);
         }
+    }
 
-        /// <summary>
-        /// Handles the Triggered event of the trigger control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
-        private async void OnTriggerTriggered(object sender, EventArgs e)
-        {
-            var trigger = (ITaskTrigger)sender;
+    /// <summary>
+    /// Handles the Triggered event of the trigger control.
+    /// </summary>
+    /// <param name="sender">The source of the event.</param>
+    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
+    private async void OnTriggerTriggered(object sender, EventArgs e)
+    {
+        var trigger = (ITaskTrigger)sender;
 
-            if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled)
-            {
-                return;
-            }
+        if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled)
+        {
+            return;
+        }
 
-            _logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name);
+        _logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name);
 
-            trigger.Stop();
+        trigger.Stop();
 
-            _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions);
+        _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions);
 
-            await Task.Delay(1000).ConfigureAwait(false);
+        await Task.Delay(1000).ConfigureAwait(false);
 
-            trigger.Start(LastExecutionResult, _logger, Name, false);
-        }
+        trigger.Start(LastExecutionResult, _logger, Name, false);
+    }
 
-        /// <summary>
-        /// Executes the task.
-        /// </summary>
-        /// <param name="options">Task options.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="InvalidOperationException">Cannot execute a Task that is already running.</exception>
-        public async Task Execute(TaskOptions options)
-        {
-            var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false));
+    /// <summary>
+    /// Executes the task.
+    /// </summary>
+    /// <param name="options">Task options.</param>
+    /// <returns>Task.</returns>
+    /// <exception cref="InvalidOperationException">Cannot execute a Task that is already running.</exception>
+    public async Task Execute(TaskOptions options)
+    {
+        var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false));
 
-            _currentTask = task;
+        _currentTask = task;
 
-            try
-            {
-                await task.ConfigureAwait(false);
-            }
-            finally
-            {
-                _currentTask = null;
-                GC.Collect();
-            }
+        try
+        {
+            await task.ConfigureAwait(false);
         }
-
-        private async Task ExecuteInternal(TaskOptions options)
+        finally
         {
-            // Cancel the current execution, if any
-            if (CurrentCancellationTokenSource is not null)
-            {
-                throw new InvalidOperationException("Cannot execute a Task that is already running");
-            }
-
-            var progress = new Progress<double>();
+            _currentTask = null;
+            GC.Collect();
+        }
+    }
 
-            CurrentCancellationTokenSource = new CancellationTokenSource();
+    private async Task ExecuteInternal(TaskOptions options)
+    {
+        // Cancel the current execution, if any
+        if (CurrentCancellationTokenSource is not null)
+        {
+            throw new InvalidOperationException("Cannot execute a Task that is already running");
+        }
 
-            _logger.LogDebug("Executing {0}", Name);
+        var progress = new Progress<double>();
 
-            ((TaskManager)_taskManager).OnTaskExecuting(this);
+        CurrentCancellationTokenSource = new CancellationTokenSource();
 
-            progress.ProgressChanged += OnProgressChanged;
+        _logger.LogDebug("Executing {0}", Name);
 
-            TaskCompletionStatus status;
-            CurrentExecutionStartTime = DateTime.UtcNow;
+        ((TaskManager)_taskManager).OnTaskExecuting(this);
 
-            Exception failureException = null;
+        progress.ProgressChanged += OnProgressChanged;
 
-            try
-            {
-                if (options is not null && options.MaxRuntimeTicks.HasValue)
-                {
-                    CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value));
-                }
+        TaskCompletionStatus status;
+        CurrentExecutionStartTime = DateTime.UtcNow;
 
-                await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false);
+        Exception failureException = null;
 
-                status = TaskCompletionStatus.Completed;
-            }
-            catch (OperationCanceledException)
+        try
+        {
+            if (options is not null && options.MaxRuntimeTicks.HasValue)
             {
-                status = TaskCompletionStatus.Cancelled;
+                CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value));
             }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error executing Scheduled Task");
 
-                failureException = ex;
-
-                status = TaskCompletionStatus.Failed;
-            }
+            await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false);
 
-            var startTime = CurrentExecutionStartTime;
-            var endTime = DateTime.UtcNow;
+            status = TaskCompletionStatus.Completed;
+        }
+        catch (OperationCanceledException)
+        {
+            status = TaskCompletionStatus.Cancelled;
+        }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Error executing Scheduled Task");
 
-            progress.ProgressChanged -= OnProgressChanged;
-            CurrentCancellationTokenSource.Dispose();
-            CurrentCancellationTokenSource = null;
-            CurrentProgress = null;
+            failureException = ex;
 
-            OnTaskCompleted(startTime, endTime, status, failureException);
+            status = TaskCompletionStatus.Failed;
         }
 
-        /// <summary>
-        /// Progress_s the progress changed.
-        /// </summary>
-        /// <param name="sender">The sender.</param>
-        /// <param name="e">The e.</param>
-        private void OnProgressChanged(object sender, double e)
-        {
-            e = Math.Min(e, 100);
+        var startTime = CurrentExecutionStartTime;
+        var endTime = DateTime.UtcNow;
 
-            CurrentProgress = e;
+        progress.ProgressChanged -= OnProgressChanged;
+        CurrentCancellationTokenSource.Dispose();
+        CurrentCancellationTokenSource = null;
+        CurrentProgress = null;
 
-            TaskProgress?.Invoke(this, new GenericEventArgs<double>(e));
-        }
+        OnTaskCompleted(startTime, endTime, status, failureException);
+    }
 
-        /// <summary>
-        /// Stops the task if it is currently executing.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception>
-        public void Cancel()
-        {
-            if (State != TaskState.Running)
-            {
-                throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state.");
-            }
+    /// <summary>
+    /// Progress_s the progress changed.
+    /// </summary>
+    /// <param name="sender">The sender.</param>
+    /// <param name="e">The e.</param>
+    private void OnProgressChanged(object sender, double e)
+    {
+        e = Math.Min(e, 100);
 
-            CancelIfRunning();
-        }
+        CurrentProgress = e;
 
-        /// <summary>
-        /// Cancels if running.
-        /// </summary>
-        public void CancelIfRunning()
-        {
-            if (State == TaskState.Running)
-            {
-                _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name);
-                CurrentCancellationTokenSource.Cancel();
-            }
-        }
+        TaskProgress?.Invoke(this, new GenericEventArgs<double>(e));
+    }
 
-        /// <summary>
-        /// Gets the scheduled tasks configuration directory.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        private string GetScheduledTasksConfigurationDirectory()
+    /// <summary>
+    /// Stops the task if it is currently executing.
+    /// </summary>
+    /// <exception cref="InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception>
+    public void Cancel()
+    {
+        if (State != TaskState.Running)
         {
-            return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
+            throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state.");
         }
 
-        /// <summary>
-        /// Gets the scheduled tasks data directory.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        private string GetScheduledTasksDataDirectory()
-        {
-            return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks");
-        }
+        CancelIfRunning();
+    }
 
-        /// <summary>
-        /// Gets the history file path.
-        /// </summary>
-        /// <value>The history file path.</value>
-        private string GetHistoryFilePath()
+    /// <summary>
+    /// Cancels if running.
+    /// </summary>
+    public void CancelIfRunning()
+    {
+        if (State == TaskState.Running)
         {
-            return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js");
+            _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name);
+            CurrentCancellationTokenSource.Cancel();
         }
+    }
 
-        /// <summary>
-        /// Gets the configuration file path.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        private string GetConfigurationFilePath()
-        {
-            return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js");
-        }
+    /// <summary>
+    /// Gets the scheduled tasks configuration directory.
+    /// </summary>
+    /// <returns>System.String.</returns>
+    private string GetScheduledTasksConfigurationDirectory()
+    {
+        return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
+    }
 
-        /// <summary>
-        /// Loads the triggers.
-        /// </summary>
-        /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
-        private Tuple<TaskTriggerInfo, ITaskTrigger>[] LoadTriggers()
-        {
-            // This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly
-            var settings = LoadTriggerSettings().Where(i => i is not null);
+    /// <summary>
+    /// Gets the scheduled tasks data directory.
+    /// </summary>
+    /// <returns>System.String.</returns>
+    private string GetScheduledTasksDataDirectory()
+    {
+        return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks");
+    }
 
-            return settings.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
-        }
+    /// <summary>
+    /// Gets the history file path.
+    /// </summary>
+    /// <value>The history file path.</value>
+    private string GetHistoryFilePath()
+    {
+        return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js");
+    }
 
-        private TaskTriggerInfo[] LoadTriggerSettings()
-        {
-            string path = GetConfigurationFilePath();
-            TaskTriggerInfo[] list = null;
-            if (File.Exists(path))
-            {
-                var bytes = File.ReadAllBytes(path);
-                list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(bytes, _jsonOptions);
-            }
+    /// <summary>
+    /// Gets the configuration file path.
+    /// </summary>
+    /// <returns>System.String.</returns>
+    private string GetConfigurationFilePath()
+    {
+        return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js");
+    }
 
-            // Return defaults if file doesn't exist.
-            return list ?? GetDefaultTriggers();
-        }
+    /// <summary>
+    /// Loads the triggers.
+    /// </summary>
+    /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
+    private Tuple<TaskTriggerInfo, ITaskTrigger>[] LoadTriggers()
+    {
+        // This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly
+        var settings = LoadTriggerSettings().Where(i => i is not null);
+
+        return settings.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
+    }
 
-        private TaskTriggerInfo[] GetDefaultTriggers()
+    private TaskTriggerInfo[] LoadTriggerSettings()
+    {
+        string path = GetConfigurationFilePath();
+        TaskTriggerInfo[] list = null;
+        if (File.Exists(path))
         {
-            try
-            {
-                return ScheduledTask.GetDefaultTriggers().ToArray();
-            }
-            catch
-            {
-                return
-                [
-                    new()
-                    {
-                        IntervalTicks = TimeSpan.FromDays(1).Ticks,
-                        Type = TaskTriggerInfoType.IntervalTrigger
-                    }
-                ];
-            }
+            var bytes = File.ReadAllBytes(path);
+            list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(bytes, _jsonOptions);
         }
 
-        /// <summary>
-        /// Saves the triggers.
-        /// </summary>
-        /// <param name="triggers">The triggers.</param>
-        private void SaveTriggers(TaskTriggerInfo[] triggers)
-        {
-            var path = GetConfigurationFilePath();
+        // Return defaults if file doesn't exist.
+        return list ?? GetDefaultTriggers();
+    }
 
-            Directory.CreateDirectory(Path.GetDirectoryName(path));
-            using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
-            using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream);
-            JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions);
+    private TaskTriggerInfo[] GetDefaultTriggers()
+    {
+        try
+        {
+            return ScheduledTask.GetDefaultTriggers().ToArray();
         }
-
-        /// <summary>
-        /// Called when [task completed].
-        /// </summary>
-        /// <param name="startTime">The start time.</param>
-        /// <param name="endTime">The end time.</param>
-        /// <param name="status">The status.</param>
-        /// <param name="ex">The exception.</param>
-        private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex)
+        catch
         {
-            var elapsedTime = endTime - startTime;
+            return
+            [
+                new()
+                {
+                    IntervalTicks = TimeSpan.FromDays(1).Ticks,
+                    Type = TaskTriggerInfoType.IntervalTrigger
+                }
+            ];
+        }
+    }
 
-            _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
+    /// <summary>
+    /// Saves the triggers.
+    /// </summary>
+    /// <param name="triggers">The triggers.</param>
+    private void SaveTriggers(TaskTriggerInfo[] triggers)
+    {
+        var path = GetConfigurationFilePath();
 
-            var result = new TaskResult
-            {
-                StartTimeUtc = startTime,
-                EndTimeUtc = endTime,
-                Status = status,
-                Name = Name,
-                Id = Id
-            };
+        Directory.CreateDirectory(Path.GetDirectoryName(path));
+        using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+        using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream);
+        JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions);
+    }
 
-            result.Key = ScheduledTask.Key;
+    /// <summary>
+    /// Called when [task completed].
+    /// </summary>
+    /// <param name="startTime">The start time.</param>
+    /// <param name="endTime">The end time.</param>
+    /// <param name="status">The status.</param>
+    /// <param name="ex">The exception.</param>
+    private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex)
+    {
+        var elapsedTime = endTime - startTime;
 
-            if (ex is not null)
-            {
-                result.ErrorMessage = ex.Message;
-                result.LongErrorMessage = ex.StackTrace;
-            }
+        _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
 
-            LastExecutionResult = result;
+        var result = new TaskResult
+        {
+            StartTimeUtc = startTime,
+            EndTimeUtc = endTime,
+            Status = status,
+            Name = Name,
+            Id = Id
+        };
 
-            ((TaskManager)_taskManager).OnTaskCompleted(this, result);
-        }
+        result.Key = ScheduledTask.Key;
 
-        /// <inheritdoc />
-        public void Dispose()
+        if (ex is not null)
         {
-            Dispose(true);
-            GC.SuppressFinalize(this);
+            result.ErrorMessage = ex.Message;
+            result.LongErrorMessage = ex.StackTrace;
         }
 
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
+        LastExecutionResult = result;
+
+        ((TaskManager)_taskManager).OnTaskCompleted(this, result);
+    }
+
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        Dispose(true);
+        GC.SuppressFinalize(this);
+    }
+
+    /// <summary>
+    /// Releases unmanaged and - optionally - managed resources.
+    /// </summary>
+    /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+    protected virtual void Dispose(bool dispose)
+    {
+        if (dispose)
         {
-            if (dispose)
-            {
-                DisposeTriggers();
+            DisposeTriggers();
 
-                var wasRunning = State == TaskState.Running;
-                var startTime = CurrentExecutionStartTime;
+            var wasRunning = State == TaskState.Running;
+            var startTime = CurrentExecutionStartTime;
 
-                var token = CurrentCancellationTokenSource;
-                if (token is not null)
+            var token = CurrentCancellationTokenSource;
+            if (token is not null)
+            {
+                try
                 {
-                    try
-                    {
-                        _logger.LogInformation("{Name}: Cancelling", Name);
-                        token.Cancel();
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error calling CancellationToken.Cancel();");
-                    }
+                    _logger.LogInformation("{Name}: Cancelling", Name);
+                    token.Cancel();
                 }
-
-                var task = _currentTask;
-                if (task is not null)
+                catch (Exception ex)
                 {
-                    try
-                    {
-                        _logger.LogInformation("{Name}: Waiting on Task", Name);
-                        var exited = task.Wait(2000);
-
-                        if (exited)
-                        {
-                            _logger.LogInformation("{Name}: Task exited", Name);
-                        }
-                        else
-                        {
-                            _logger.LogInformation("{Name}: Timed out waiting for task to stop", Name);
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error calling Task.WaitAll();");
-                    }
+                    _logger.LogError(ex, "Error calling CancellationToken.Cancel();");
                 }
+            }
 
-                if (token is not null)
+            var task = _currentTask;
+            if (task is not null)
+            {
+                try
                 {
-                    try
+                    _logger.LogInformation("{Name}: Waiting on Task", Name);
+                    var exited = task.Wait(2000);
+
+                    if (exited)
                     {
-                        _logger.LogDebug("{Name}: Disposing CancellationToken", Name);
-                        token.Dispose();
+                        _logger.LogInformation("{Name}: Task exited", Name);
                     }
-                    catch (Exception ex)
+                    else
                     {
-                        _logger.LogError(ex, "Error calling CancellationToken.Dispose();");
+                        _logger.LogInformation("{Name}: Timed out waiting for task to stop", Name);
                     }
                 }
-
-                if (wasRunning)
+                catch (Exception ex)
                 {
-                    OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null);
+                    _logger.LogError(ex, "Error calling Task.WaitAll();");
                 }
             }
-        }
-
-        /// <summary>
-        /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger.
-        /// </summary>
-        /// <param name="info">The info.</param>
-        /// <returns>BaseTaskTrigger.</returns>
-        /// <exception cref="ArgumentException">Invalid trigger type:  + info.Type.</exception>
-        private ITaskTrigger GetTrigger(TaskTriggerInfo info)
-        {
-            var options = new TaskOptions
-            {
-                MaxRuntimeTicks = info.MaxRuntimeTicks
-            };
 
-            if (info.Type == TaskTriggerInfoType.DailyTrigger)
+            if (token is not null)
             {
-                if (!info.TimeOfDayTicks.HasValue)
+                try
                 {
-                    throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
+                    _logger.LogDebug("{Name}: Disposing CancellationToken", Name);
+                    token.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "Error calling CancellationToken.Dispose();");
                 }
-
-                return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options);
             }
 
-            if (info.Type == TaskTriggerInfoType.WeeklyTrigger)
+            if (wasRunning)
             {
-                if (!info.TimeOfDayTicks.HasValue)
-                {
-                    throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
-                }
+                OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null);
+            }
+        }
+    }
 
-                if (!info.DayOfWeek.HasValue)
-                {
-                    throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info));
-                }
+    /// <summary>
+    /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger.
+    /// </summary>
+    /// <param name="info">The info.</param>
+    /// <returns>BaseTaskTrigger.</returns>
+    /// <exception cref="ArgumentException">Invalid trigger type:  + info.Type.</exception>
+    private ITaskTrigger GetTrigger(TaskTriggerInfo info)
+    {
+        var options = new TaskOptions
+        {
+            MaxRuntimeTicks = info.MaxRuntimeTicks
+        };
 
-                return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options);
+        if (info.Type == TaskTriggerInfoType.DailyTrigger)
+        {
+            if (!info.TimeOfDayTicks.HasValue)
+            {
+                throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
             }
 
-            if (info.Type == TaskTriggerInfoType.IntervalTrigger)
+            return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options);
+        }
+
+        if (info.Type == TaskTriggerInfoType.WeeklyTrigger)
+        {
+            if (!info.TimeOfDayTicks.HasValue)
             {
-                if (!info.IntervalTicks.HasValue)
-                {
-                    throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info));
-                }
+                throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
+            }
 
-                return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options);
+            if (!info.DayOfWeek.HasValue)
+            {
+                throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info));
             }
 
-            if (info.Type == TaskTriggerInfoType.StartupTrigger)
+            return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options);
+        }
+
+        if (info.Type == TaskTriggerInfoType.IntervalTrigger)
+        {
+            if (!info.IntervalTicks.HasValue)
             {
-                return new StartupTrigger(options);
+                throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info));
             }
 
-            throw new ArgumentException("Unrecognized trigger type: " + info.Type);
+            return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options);
         }
 
-        /// <summary>
-        /// Disposes each trigger.
-        /// </summary>
-        private void DisposeTriggers()
+        if (info.Type == TaskTriggerInfoType.StartupTrigger)
         {
-            foreach (var triggerInfo in InternalTriggers)
+            return new StartupTrigger(options);
+        }
+
+        throw new ArgumentException("Unrecognized trigger type: " + info.Type);
+    }
+
+    /// <summary>
+    /// Disposes each trigger.
+    /// </summary>
+    private void DisposeTriggers()
+    {
+        foreach (var triggerInfo in InternalTriggers)
+        {
+            var trigger = triggerInfo.Item2;
+            trigger.Triggered -= OnTriggerTriggered;
+            trigger.Stop();
+            if (trigger is IDisposable disposable)
             {
-                var trigger = triggerInfo.Item2;
-                trigger.Triggered -= OnTriggerTriggered;
-                trigger.Stop();
-                if (trigger is IDisposable disposable)
-                {
-                    disposable.Dispose();
-                }
+                disposable.Dispose();
             }
         }
     }

+ 189 - 190
Emby.Server.Implementations/ScheduledTasks/TaskManager.cs

@@ -8,255 +8,254 @@ using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks;
+
+/// <summary>
+/// Class TaskManager.
+/// </summary>
+public class TaskManager : ITaskManager
 {
     /// <summary>
-    /// Class TaskManager.
+    /// The _task queue.
     /// </summary>
-    public class TaskManager : ITaskManager
-    {
-        /// <summary>
-        /// The _task queue.
-        /// </summary>
-        private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
-            new ConcurrentQueue<Tuple<Type, TaskOptions>>();
-
-        private readonly IApplicationPaths _applicationPaths;
-        private readonly ILogger<TaskManager> _logger;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="TaskManager" /> class.
-        /// </summary>
-        /// <param name="applicationPaths">The application paths.</param>
-        /// <param name="logger">The logger.</param>
-        public TaskManager(
-            IApplicationPaths applicationPaths,
-            ILogger<TaskManager> logger)
-        {
-            _applicationPaths = applicationPaths;
-            _logger = logger;
+    private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
+        new ConcurrentQueue<Tuple<Type, TaskOptions>>();
 
-            ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
-        }
+    private readonly IApplicationPaths _applicationPaths;
+    private readonly ILogger<TaskManager> _logger;
 
-        /// <inheritdoc />
-        public event EventHandler<GenericEventArgs<IScheduledTaskWorker>>? TaskExecuting;
+    /// <summary>
+    /// Initializes a new instance of the <see cref="TaskManager" /> class.
+    /// </summary>
+    /// <param name="applicationPaths">The application paths.</param>
+    /// <param name="logger">The logger.</param>
+    public TaskManager(
+        IApplicationPaths applicationPaths,
+        ILogger<TaskManager> logger)
+    {
+        _applicationPaths = applicationPaths;
+        _logger = logger;
 
-        /// <inheritdoc />
-        public event EventHandler<TaskCompletionEventArgs>? TaskCompleted;
+        ScheduledTasks = [];
+    }
 
-        /// <inheritdoc />
-        public IReadOnlyList<IScheduledTaskWorker> ScheduledTasks { get; private set; }
+    /// <inheritdoc />
+    public event EventHandler<GenericEventArgs<IScheduledTaskWorker>>? TaskExecuting;
 
-        /// <inheritdoc />
-        public void CancelIfRunningAndQueue<T>(TaskOptions options)
-            where T : IScheduledTask
-        {
-            var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
-            ((ScheduledTaskWorker)task).CancelIfRunning();
+    /// <inheritdoc />
+    public event EventHandler<TaskCompletionEventArgs>? TaskCompleted;
 
-            QueueScheduledTask<T>(options);
-        }
+    /// <inheritdoc />
+    public IReadOnlyList<IScheduledTaskWorker> ScheduledTasks { get; private set; }
 
-        /// <inheritdoc />
-        public void CancelIfRunningAndQueue<T>()
-               where T : IScheduledTask
-        {
-            CancelIfRunningAndQueue<T>(new TaskOptions());
-        }
+    /// <inheritdoc />
+    public void CancelIfRunningAndQueue<T>(TaskOptions options)
+        where T : IScheduledTask
+    {
+        var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
+        ((ScheduledTaskWorker)task).CancelIfRunning();
 
-        /// <inheritdoc />
-        public void CancelIfRunning<T>()
-                 where T : IScheduledTask
-        {
-            var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
-            ((ScheduledTaskWorker)task).CancelIfRunning();
-        }
+        QueueScheduledTask<T>(options);
+    }
 
-        /// <inheritdoc />
-        public void QueueScheduledTask<T>(TaskOptions options)
+    /// <inheritdoc />
+    public void CancelIfRunningAndQueue<T>()
             where T : IScheduledTask
-        {
-            var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+    {
+        CancelIfRunningAndQueue<T>(new TaskOptions());
+    }
 
-            if (scheduledTask is null)
-            {
-                _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
-            }
-            else
-            {
-                QueueScheduledTask(scheduledTask, options);
-            }
-        }
+    /// <inheritdoc />
+    public void CancelIfRunning<T>()
+                where T : IScheduledTask
+    {
+        var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
+        ((ScheduledTaskWorker)task).CancelIfRunning();
+    }
 
-        /// <inheritdoc />
-        public void QueueScheduledTask<T>()
-            where T : IScheduledTask
-        {
-            QueueScheduledTask<T>(new TaskOptions());
-        }
+    /// <inheritdoc />
+    public void QueueScheduledTask<T>(TaskOptions options)
+        where T : IScheduledTask
+    {
+        var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
 
-        /// <inheritdoc />
-        public void QueueIfNotRunning<T>()
-            where T : IScheduledTask
+        if (scheduledTask is null)
         {
-            var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
-
-            if (task.State != TaskState.Running)
-            {
-                QueueScheduledTask<T>(new TaskOptions());
-            }
+            _logger.LogError("Unable to find scheduled task of type {Type} in QueueScheduledTask.", typeof(T).Name);
         }
-
-        /// <inheritdoc />
-        public void Execute<T>()
-            where T : IScheduledTask
+        else
         {
-            var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+            QueueScheduledTask(scheduledTask, options);
+        }
+    }
 
-            if (scheduledTask is null)
-            {
-                _logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
-            }
-            else
-            {
-                var type = scheduledTask.ScheduledTask.GetType();
+    /// <inheritdoc />
+    public void QueueScheduledTask<T>()
+        where T : IScheduledTask
+    {
+        QueueScheduledTask<T>(new TaskOptions());
+    }
 
-                _logger.LogDebug("Queuing task {0}", type.Name);
+    /// <inheritdoc />
+    public void QueueIfNotRunning<T>()
+        where T : IScheduledTask
+    {
+        var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
 
-                lock (_taskQueue)
-                {
-                    if (scheduledTask.State == TaskState.Idle)
-                    {
-                        Execute(scheduledTask, new TaskOptions());
-                    }
-                }
-            }
+        if (task.State != TaskState.Running)
+        {
+            QueueScheduledTask<T>(new TaskOptions());
         }
+    }
 
-        /// <inheritdoc />
-        public void QueueScheduledTask(IScheduledTask task, TaskOptions options)
-        {
-            var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType());
+    /// <inheritdoc />
+    public void Execute<T>()
+        where T : IScheduledTask
+    {
+        var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
 
-            if (scheduledTask is null)
-            {
-                _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
-            }
-            else
-            {
-                QueueScheduledTask(scheduledTask, options);
-            }
+        if (scheduledTask is null)
+        {
+            _logger.LogError("Unable to find scheduled task of type {Type} in Execute.", typeof(T).Name);
         }
-
-        /// <summary>
-        /// Queues the scheduled task.
-        /// </summary>
-        /// <param name="task">The task.</param>
-        /// <param name="options">The task options.</param>
-        private void QueueScheduledTask(IScheduledTaskWorker task, TaskOptions options)
+        else
         {
-            var type = task.ScheduledTask.GetType();
+            var type = scheduledTask.ScheduledTask.GetType();
 
-            _logger.LogDebug("Queuing task {0}", type.Name);
+            _logger.LogDebug("Queuing task {Name}", type.Name);
 
             lock (_taskQueue)
             {
-                if (task.State == TaskState.Idle)
+                if (scheduledTask.State == TaskState.Idle)
                 {
-                    Execute(task, options);
-                    return;
+                    Execute(scheduledTask, new TaskOptions());
                 }
-
-                _taskQueue.Enqueue(new Tuple<Type, TaskOptions>(type, options));
             }
         }
+    }
 
-        /// <inheritdoc />
-        public void AddTasks(IEnumerable<IScheduledTask> tasks)
-        {
-            var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger));
+    /// <inheritdoc />
+    public void QueueScheduledTask(IScheduledTask task, TaskOptions options)
+    {
+        var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType());
 
-            ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
+        if (scheduledTask is null)
+        {
+            _logger.LogError("Unable to find scheduled task of type {Type} in QueueScheduledTask.", task.GetType().Name);
         }
-
-        /// <inheritdoc />
-        public void Dispose()
+        else
         {
-            Dispose(true);
-            GC.SuppressFinalize(this);
+            QueueScheduledTask(scheduledTask, options);
         }
+    }
+
+    /// <summary>
+    /// Queues the scheduled task.
+    /// </summary>
+    /// <param name="task">The task.</param>
+    /// <param name="options">The task options.</param>
+    private void QueueScheduledTask(IScheduledTaskWorker task, TaskOptions options)
+    {
+        var type = task.ScheduledTask.GetType();
+
+        _logger.LogDebug("Queuing task {Name}", type.Name);
 
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
+        lock (_taskQueue)
         {
-            foreach (var task in ScheduledTasks)
+            if (task.State == TaskState.Idle)
             {
-                task.Dispose();
+                Execute(task, options);
+                return;
             }
-        }
 
-        /// <inheritdoc />
-        public void Cancel(IScheduledTaskWorker task)
-        {
-            ((ScheduledTaskWorker)task).Cancel();
+            _taskQueue.Enqueue(new Tuple<Type, TaskOptions>(type, options));
         }
+    }
 
-        /// <inheritdoc />
-        public Task Execute(IScheduledTaskWorker task, TaskOptions options)
-        {
-            return ((ScheduledTaskWorker)task).Execute(options);
-        }
+    /// <inheritdoc />
+    public void AddTasks(IEnumerable<IScheduledTask> tasks)
+    {
+        var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger));
+
+        ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
+    }
+
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        Dispose(true);
+        GC.SuppressFinalize(this);
+    }
 
-        /// <summary>
-        /// Called when [task executing].
-        /// </summary>
-        /// <param name="task">The task.</param>
-        internal void OnTaskExecuting(IScheduledTaskWorker task)
+    /// <summary>
+    /// Releases unmanaged and - optionally - managed resources.
+    /// </summary>
+    /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+    protected virtual void Dispose(bool dispose)
+    {
+        foreach (var task in ScheduledTasks)
         {
-            TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker>(task));
+            task.Dispose();
         }
+    }
 
-        /// <summary>
-        /// Called when [task completed].
-        /// </summary>
-        /// <param name="task">The task.</param>
-        /// <param name="result">The result.</param>
-        internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result)
-        {
-            TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result));
+    /// <inheritdoc />
+    public void Cancel(IScheduledTaskWorker task)
+    {
+        ((ScheduledTaskWorker)task).Cancel();
+    }
 
-            ExecuteQueuedTasks();
-        }
+    /// <inheritdoc />
+    public Task Execute(IScheduledTaskWorker task, TaskOptions options)
+    {
+        return ((ScheduledTaskWorker)task).Execute(options);
+    }
+
+    /// <summary>
+    /// Called when [task executing].
+    /// </summary>
+    /// <param name="task">The task.</param>
+    internal void OnTaskExecuting(IScheduledTaskWorker task)
+    {
+        TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker>(task));
+    }
+
+    /// <summary>
+    /// Called when [task completed].
+    /// </summary>
+    /// <param name="task">The task.</param>
+    /// <param name="result">The result.</param>
+    internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result)
+    {
+        TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result));
+
+        ExecuteQueuedTasks();
+    }
 
-        /// <summary>
-        /// Executes the queued tasks.
-        /// </summary>
-        private void ExecuteQueuedTasks()
+    /// <summary>
+    /// Executes the queued tasks.
+    /// </summary>
+    private void ExecuteQueuedTasks()
+    {
+        lock (_taskQueue)
         {
-            lock (_taskQueue)
-            {
-                var list = new List<Tuple<Type, TaskOptions>>();
+            var list = new List<Tuple<Type, TaskOptions>>();
 
-                while (_taskQueue.TryDequeue(out var item))
+            while (_taskQueue.TryDequeue(out var item))
+            {
+                if (list.All(i => i.Item1 != item.Item1))
                 {
-                    if (list.All(i => i.Item1 != item.Item1))
-                    {
-                        list.Add(item);
-                    }
+                    list.Add(item);
                 }
+            }
 
-                foreach (var enqueuedType in list)
-                {
-                    var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
+            foreach (var enqueuedType in list)
+            {
+                var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
 
-                    if (scheduledTask.State == TaskState.Idle)
-                    {
-                        Execute(scheduledTask, enqueuedType.Item2);
-                    }
+                if (scheduledTask.State == TaskState.Idle)
+                {
+                    Execute(scheduledTask, enqueuedType.Item2);
                 }
             }
         }

+ 6 - 9
Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs

@@ -156,14 +156,11 @@ public partial class AudioNormalizationTask : IScheduledTask
     /// <inheritdoc />
     public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
     {
-        return
-        [
-            new TaskTriggerInfo
-            {
-                Type = TaskTriggerInfoType.IntervalTrigger,
-                IntervalTicks = TimeSpan.FromHours(24).Ticks
-            }
-        ];
+        yield return new TaskTriggerInfo
+        {
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(24).Ticks
+        };
     }
 
     private async Task<float?> CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken)
@@ -194,7 +191,7 @@ public partial class AudioNormalizationTask : IScheduledTask
 
             using var reader = process.StandardError;
             float? lufs = null;
-            await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
+            await foreach (var line in reader.ReadAllLinesAsync(cancellationToken).ConfigureAwait(false))
             {
                 Match match = LUFSRegex().Match(line);
                 if (match.Success)

+ 112 - 116
Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs

@@ -17,155 +17,151 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Class ChapterImagesTask.
+/// </summary>
+public class ChapterImagesTask : IScheduledTask
 {
+    private readonly ILogger<ChapterImagesTask> _logger;
+    private readonly ILibraryManager _libraryManager;
+    private readonly IApplicationPaths _appPaths;
+    private readonly IChapterManager _chapterManager;
+    private readonly IFileSystem _fileSystem;
+    private readonly ILocalizationManager _localization;
+
     /// <summary>
-    /// Class ChapterImagesTask.
+    /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
     /// </summary>
-    public class ChapterImagesTask : IScheduledTask
+    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+    /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+    /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
+    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public ChapterImagesTask(
+        ILogger<ChapterImagesTask> logger,
+        ILibraryManager libraryManager,
+        IApplicationPaths appPaths,
+        IChapterManager chapterManager,
+        IFileSystem fileSystem,
+        ILocalizationManager localization)
     {
-        private readonly ILogger<ChapterImagesTask> _logger;
-        private readonly ILibraryManager _libraryManager;
-        private readonly IApplicationPaths _appPaths;
-        private readonly IChapterManager _chapterManager;
-        private readonly IFileSystem _fileSystem;
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
-        /// </summary>
-        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
-        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
-        /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
-        /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
-        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public ChapterImagesTask(
-            ILogger<ChapterImagesTask> logger,
-            ILibraryManager libraryManager,
-            IApplicationPaths appPaths,
-            IChapterManager chapterManager,
-            IFileSystem fileSystem,
-            ILocalizationManager localization)
-        {
-            _logger = logger;
-            _libraryManager = libraryManager;
-            _appPaths = appPaths;
-            _chapterManager = chapterManager;
-            _fileSystem = fileSystem;
-            _localization = localization;
-        }
+        _logger = logger;
+        _libraryManager = libraryManager;
+        _appPaths = appPaths;
+        _chapterManager = chapterManager;
+        _fileSystem = fileSystem;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
 
-        /// <inheritdoc />
-        public string Key => "RefreshChapterImages";
+    /// <inheritdoc />
+    public string Key => "RefreshChapterImages";
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            return
-            [
-                new TaskTriggerInfo
-                {
-                    Type = TaskTriggerInfoType.DailyTrigger,
-                    TimeOfDayTicks = TimeSpan.FromHours(2).Ticks,
-                    MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks
-                }
-            ];
-        }
+            Type = TaskTriggerInfoType.DailyTrigger,
+            TimeOfDayTicks = TimeSpan.FromHours(2).Ticks,
+            MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks
+        };
+    }
 
-        /// <inheritdoc />
-        public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    /// <inheritdoc />
+    public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var videos = _libraryManager.GetItemList(new InternalItemsQuery
         {
-            var videos = _libraryManager.GetItemList(new InternalItemsQuery
+            MediaTypes = [MediaType.Video],
+            IsFolder = false,
+            Recursive = true,
+            DtoOptions = new DtoOptions(false)
             {
-                MediaTypes = [MediaType.Video],
-                IsFolder = false,
-                Recursive = true,
-                DtoOptions = new DtoOptions(false)
-                {
-                    EnableImages = false
-                },
-                SourceTypes = [SourceType.Library],
-                IsVirtualItem = false
-            })
-            .OfType<Video>()
-            .ToList();
+                EnableImages = false
+            },
+            SourceTypes = [SourceType.Library],
+            IsVirtualItem = false
+        })
+        .OfType<Video>()
+        .ToList();
 
-            var numComplete = 0;
+        var numComplete = 0;
 
-            var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt");
+        var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt");
 
-            List<string> previouslyFailedImages;
+        List<string> previouslyFailedImages;
 
-            if (File.Exists(failHistoryPath))
+        if (File.Exists(failHistoryPath))
+        {
+            try
             {
-                try
-                {
-                    previouslyFailedImages = (await File.ReadAllTextAsync(failHistoryPath, cancellationToken).ConfigureAwait(false))
-                        .Split('|', StringSplitOptions.RemoveEmptyEntries)
-                        .ToList();
-                }
-                catch (IOException)
-                {
-                    previouslyFailedImages = [];
-                }
+                previouslyFailedImages = (await File.ReadAllTextAsync(failHistoryPath, cancellationToken).ConfigureAwait(false))
+                    .Split('|', StringSplitOptions.RemoveEmptyEntries)
+                    .ToList();
             }
-            else
+            catch (IOException)
             {
                 previouslyFailedImages = [];
             }
+        }
+        else
+        {
+            previouslyFailedImages = [];
+        }
 
-            var directoryService = new DirectoryService(_fileSystem);
-
-            foreach (var video in videos)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
+        var directoryService = new DirectoryService(_fileSystem);
 
-                var key = video.Path + video.DateModified.Ticks;
+        foreach (var video in videos)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
 
-                var extract = !previouslyFailedImages.Contains(key, StringComparison.OrdinalIgnoreCase);
+            var key = video.Path + video.DateModified.Ticks;
 
-                try
-                {
-                    var chapters = _chapterManager.GetChapters(video.Id);
+            var extract = !previouslyFailedImages.Contains(key, StringComparison.OrdinalIgnoreCase);
 
-                    var success = await _chapterManager.RefreshChapterImages(video, directoryService, chapters, extract, true, cancellationToken).ConfigureAwait(false);
+            try
+            {
+                var chapters = _chapterManager.GetChapters(video.Id);
 
-                    if (!success)
-                    {
-                        previouslyFailedImages.Add(key);
+                var success = await _chapterManager.RefreshChapterImages(video, directoryService, chapters, extract, true, cancellationToken).ConfigureAwait(false);
 
-                        var parentPath = Path.GetDirectoryName(failHistoryPath);
-                        if (parentPath is not null)
-                        {
-                            Directory.CreateDirectory(parentPath);
-                        }
+                if (!success)
+                {
+                    previouslyFailedImages.Add(key);
 
-                        string text = string.Join('|', previouslyFailedImages);
-                        await File.WriteAllTextAsync(failHistoryPath, text, cancellationToken).ConfigureAwait(false);
+                    var parentPath = Path.GetDirectoryName(failHistoryPath);
+                    if (parentPath is not null)
+                    {
+                        Directory.CreateDirectory(parentPath);
                     }
 
-                    numComplete++;
-                    double percent = numComplete;
-                    percent /= videos.Count;
-
-                    progress.Report(100 * percent);
-                }
-                catch (ObjectDisposedException ex)
-                {
-                    // TODO Investigate and properly fix.
-                    _logger.LogError(ex, "Object Disposed");
-                    break;
+                    string text = string.Join('|', previouslyFailedImages);
+                    await File.WriteAllTextAsync(failHistoryPath, text, cancellationToken).ConfigureAwait(false);
                 }
+
+                numComplete++;
+                double percent = numComplete;
+                percent /= videos.Count;
+
+                progress.Report(100 * percent);
+            }
+            catch (ObjectDisposedException ex)
+            {
+                // TODO Investigate and properly fix.
+                _logger.LogError(ex, "Object Disposed");
+                break;
             }
         }
     }

+ 50 - 51
Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs

@@ -7,71 +7,70 @@ using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Tasks;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Deletes old activity log entries.
+/// </summary>
+public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask
 {
+    private readonly ILocalizationManager _localization;
+    private readonly IActivityManager _activityManager;
+    private readonly IServerConfigurationManager _serverConfigurationManager;
+
     /// <summary>
-    /// Deletes old activity log entries.
+    /// Initializes a new instance of the <see cref="CleanActivityLogTask"/> class.
     /// </summary>
-    public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
+    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+    public CleanActivityLogTask(
+        ILocalizationManager localization,
+        IActivityManager activityManager,
+        IServerConfigurationManager serverConfigurationManager)
     {
-        private readonly ILocalizationManager _localization;
-        private readonly IActivityManager _activityManager;
-        private readonly IServerConfigurationManager _serverConfigurationManager;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="CleanActivityLogTask"/> class.
-        /// </summary>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
-        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
-        public CleanActivityLogTask(
-            ILocalizationManager localization,
-            IActivityManager activityManager,
-            IServerConfigurationManager serverConfigurationManager)
-        {
-            _localization = localization;
-            _activityManager = activityManager;
-            _serverConfigurationManager = serverConfigurationManager;
-        }
+        _localization = localization;
+        _activityManager = activityManager;
+        _serverConfigurationManager = serverConfigurationManager;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskCleanActivityLog");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskCleanActivityLog");
 
-        /// <inheritdoc />
-        public string Key => "CleanActivityLog";
+    /// <inheritdoc />
+    public string Key => "CleanActivityLog";
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <inheritdoc />
-        public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    /// <inheritdoc />
+    public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
+        if (!retentionDays.HasValue || retentionDays < 0)
         {
-            var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
-            if (!retentionDays.HasValue || retentionDays < 0)
-            {
-                throw new InvalidOperationException($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
-            }
-
-            var startDate = DateTime.UtcNow.AddDays(-retentionDays.Value);
-            return _activityManager.CleanAsync(startDate);
+            throw new InvalidOperationException($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
         }
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
-        {
-            return [];
-        }
+        var startDate = DateTime.UtcNow.AddDays(-retentionDays.Value);
+        return _activityManager.CleanAsync(startDate);
+    }
+
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        return [];
     }
 }

+ 5 - 6
Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs

@@ -27,7 +27,6 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
     private readonly IPlaylistManager _playlistManager;
     private readonly ILogger<CleanupCollectionAndPlaylistPathsTask> _logger;
     private readonly IProviderManager _providerManager;
-    private readonly IFileSystem _fileSystem;
 
     /// <summary>
     /// Initializes a new instance of the <see cref="CleanupCollectionAndPlaylistPathsTask"/> class.
@@ -37,21 +36,18 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
     /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param>
     /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
     /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
-    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
     public CleanupCollectionAndPlaylistPathsTask(
         ILocalizationManager localization,
         ICollectionManager collectionManager,
         IPlaylistManager playlistManager,
         ILogger<CleanupCollectionAndPlaylistPathsTask> logger,
-        IProviderManager providerManager,
-        IFileSystem fileSystem)
+        IProviderManager providerManager)
     {
         _localization = localization;
         _collectionManager = collectionManager;
         _playlistManager = playlistManager;
         _logger = logger;
         _providerManager = providerManager;
-        _fileSystem = fileSystem;
     }
 
     /// <inheritdoc />
@@ -135,6 +131,9 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
     /// <inheritdoc />
     public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
     {
-        return [new TaskTriggerInfo() { Type = TaskTriggerInfoType.StartupTrigger }];
+        yield return new TaskTriggerInfo
+        {
+            Type = TaskTriggerInfoType.StartupTrigger,
+        };
     }
 }

+ 101 - 102
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -11,134 +11,133 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Deletes old cache files.
+/// </summary>
+public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask
 {
     /// <summary>
-    /// Deletes old cache files.
+    /// Gets or sets the application paths.
+    /// </summary>
+    /// <value>The application paths.</value>
+    private readonly IApplicationPaths _applicationPaths;
+    private readonly ILogger<DeleteCacheFileTask> _logger;
+    private readonly IFileSystem _fileSystem;
+    private readonly ILocalizationManager _localization;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
     /// </summary>
-    public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public DeleteCacheFileTask(
+        IApplicationPaths appPaths,
+        ILogger<DeleteCacheFileTask> logger,
+        IFileSystem fileSystem,
+        ILocalizationManager localization)
     {
-        /// <summary>
-        /// Gets or sets the application paths.
-        /// </summary>
-        /// <value>The application paths.</value>
-        private readonly IApplicationPaths _applicationPaths;
-        private readonly ILogger<DeleteCacheFileTask> _logger;
-        private readonly IFileSystem _fileSystem;
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
-        /// </summary>
-        /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
-        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
-        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public DeleteCacheFileTask(
-            IApplicationPaths appPaths,
-            ILogger<DeleteCacheFileTask> logger,
-            IFileSystem fileSystem,
-            ILocalizationManager localization)
-        {
-            _applicationPaths = appPaths;
-            _logger = logger;
-            _fileSystem = fileSystem;
-            _localization = localization;
-        }
+        _applicationPaths = appPaths;
+        _logger = logger;
+        _fileSystem = fileSystem;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskCleanCache");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskCleanCache");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
 
-        /// <inheritdoc />
-        public string Key => "DeleteCacheFiles";
+    /// <inheritdoc />
+    public string Key => "DeleteCacheFiles";
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            return
-            [
-                // Every so often
-                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
-            ];
-        }
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(24).Ticks
+        };
+    }
 
-        /// <inheritdoc />
-        public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    /// <inheritdoc />
+    public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var minDateModified = DateTime.UtcNow.AddDays(-30);
+
+        try
+        {
+            DeleteCacheFilesFromDirectory(_applicationPaths.CachePath, minDateModified, progress, cancellationToken);
+        }
+        catch (DirectoryNotFoundException)
         {
-            var minDateModified = DateTime.UtcNow.AddDays(-30);
-
-            try
-            {
-                DeleteCacheFilesFromDirectory(_applicationPaths.CachePath, minDateModified, progress, cancellationToken);
-            }
-            catch (DirectoryNotFoundException)
-            {
-                // No biggie here. Nothing to delete
-            }
-
-            progress.Report(90);
-
-            minDateModified = DateTime.UtcNow.AddDays(-1);
-
-            try
-            {
-                DeleteCacheFilesFromDirectory(_applicationPaths.TempDirectory, minDateModified, progress, cancellationToken);
-            }
-            catch (DirectoryNotFoundException)
-            {
-                // No biggie here. Nothing to delete
-            }
-
-            return Task.CompletedTask;
+            // No biggie here. Nothing to delete
         }
 
-        /// <summary>
-        /// Deletes the cache files from directory with a last write time less than a given date.
-        /// </summary>
-        /// <param name="directory">The directory.</param>
-        /// <param name="minDateModified">The min date modified.</param>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The task cancellation token.</param>
-        private void DeleteCacheFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken)
+        progress.Report(90);
+
+        minDateModified = DateTime.UtcNow.AddDays(-1);
+
+        try
         {
-            var filesToDelete = _fileSystem.GetFiles(directory, true)
-                .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
-                .ToList();
+            DeleteCacheFilesFromDirectory(_applicationPaths.TempDirectory, minDateModified, progress, cancellationToken);
+        }
+        catch (DirectoryNotFoundException)
+        {
+            // No biggie here. Nothing to delete
+        }
 
-            var index = 0;
+        return Task.CompletedTask;
+    }
 
-            foreach (var file in filesToDelete)
-            {
-                double percent = index;
-                percent /= filesToDelete.Count;
+    /// <summary>
+    /// Deletes the cache files from directory with a last write time less than a given date.
+    /// </summary>
+    /// <param name="directory">The directory.</param>
+    /// <param name="minDateModified">The min date modified.</param>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The task cancellation token.</param>
+    private void DeleteCacheFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var filesToDelete = _fileSystem.GetFiles(directory, true)
+            .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
+            .ToList();
 
-                progress.Report(100 * percent);
+        var index = 0;
 
-                cancellationToken.ThrowIfCancellationRequested();
+        foreach (var file in filesToDelete)
+        {
+            double percent = index;
+            percent /= filesToDelete.Count;
 
-                FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
+            progress.Report(100 * percent);
 
-                index++;
-            }
+            cancellationToken.ThrowIfCancellationRequested();
 
-            FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
+            FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
 
-            progress.Report(100);
+            index++;
         }
+
+        FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
+
+        progress.Report(100);
     }
 }

+ 65 - 65
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -9,93 +9,93 @@ using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Deletes old log files.
+/// </summary>
+public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
 {
+    private readonly IConfigurationManager _configurationManager;
+    private readonly IFileSystem _fileSystem;
+    private readonly ILocalizationManager _localization;
+
     /// <summary>
-    /// Deletes old log files.
+    /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
     /// </summary>
-    public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization)
     {
-        private readonly IConfigurationManager _configurationManager;
-        private readonly IFileSystem _fileSystem;
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
-        /// </summary>
-        /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
-        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization)
-        {
-            _configurationManager = configurationManager;
-            _fileSystem = fileSystem;
-            _localization = localization;
-        }
+        _configurationManager = configurationManager;
+        _fileSystem = fileSystem;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskCleanLogs");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskCleanLogs");
 
-        /// <inheritdoc />
-        public string Description => string.Format(
-            CultureInfo.InvariantCulture,
-            _localization.GetLocalizedString("TaskCleanLogsDescription"),
-            _configurationManager.CommonConfiguration.LogFileRetentionDays);
+    /// <inheritdoc />
+    public string Description => string.Format(
+        CultureInfo.InvariantCulture,
+        _localization.GetLocalizedString("TaskCleanLogsDescription"),
+        _configurationManager.CommonConfiguration.LogFileRetentionDays);
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
 
-        /// <inheritdoc />
-        public string Key => "CleanLogFiles";
+    /// <inheritdoc />
+    public string Key => "CleanLogFiles";
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            return
-            [
-                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
-            ];
-        }
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(24).Ticks
+        };
+    }
 
-        /// <inheritdoc />
-        public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            // Delete log files more than n days old
-            var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays);
+    /// <inheritdoc />
+    public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        // Delete log files more than n days old
+        var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays);
 
-            // Only delete files that serilog doesn't manage (anything that doesn't start with 'log_'
-            var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, true)
-                .Where(f => !f.Name.StartsWith("log_", StringComparison.Ordinal)
-                            && _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
-                .ToList();
+        // Only delete files that serilog doesn't manage (anything that doesn't start with 'log_'
+        var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, true)
+            .Where(f => !f.Name.StartsWith("log_", StringComparison.Ordinal)
+                        && _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
+            .ToList();
 
-            var index = 0;
+        var index = 0;
 
-            foreach (var file in filesToDelete)
-            {
-                double percent = index / (double)filesToDelete.Count;
+        foreach (var file in filesToDelete)
+        {
+            double percent = index / (double)filesToDelete.Count;
 
-                progress.Report(100 * percent);
+            progress.Report(100 * percent);
 
-                cancellationToken.ThrowIfCancellationRequested();
+            cancellationToken.ThrowIfCancellationRequested();
 
-                _fileSystem.DeleteFile(file.FullName);
+            _fileSystem.DeleteFile(file.FullName);
 
-                index++;
-            }
+            index++;
+        }
 
-            progress.Report(100);
+        progress.Report(100);
 
-            return Task.CompletedTask;
-        }
+        return Task.CompletedTask;
     }
 }

+ 84 - 87
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs

@@ -10,118 +10,115 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Deletes all transcoding temp files.
+/// </summary>
+public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
 {
+    private readonly ILogger<DeleteTranscodeFileTask> _logger;
+    private readonly IConfigurationManager _configurationManager;
+    private readonly IFileSystem _fileSystem;
+    private readonly ILocalizationManager _localization;
+
     /// <summary>
-    /// Deletes all transcoding temp files.
+    /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class.
     /// </summary>
-    public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="logger">Instance of the <see cref="ILogger{DeleteTranscodeFileTask}"/> interface.</param>
+    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+    /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public DeleteTranscodeFileTask(
+        ILogger<DeleteTranscodeFileTask> logger,
+        IFileSystem fileSystem,
+        IConfigurationManager configurationManager,
+        ILocalizationManager localization)
     {
-        private readonly ILogger<DeleteTranscodeFileTask> _logger;
-        private readonly IConfigurationManager _configurationManager;
-        private readonly IFileSystem _fileSystem;
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class.
-        /// </summary>
-        /// <param name="logger">Instance of the <see cref="ILogger{DeleteTranscodeFileTask}"/> interface.</param>
-        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
-        /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public DeleteTranscodeFileTask(
-            ILogger<DeleteTranscodeFileTask> logger,
-            IFileSystem fileSystem,
-            IConfigurationManager configurationManager,
-            ILocalizationManager localization)
-        {
-            _logger = logger;
-            _fileSystem = fileSystem;
-            _configurationManager = configurationManager;
-            _localization = localization;
-        }
+        _logger = logger;
+        _fileSystem = fileSystem;
+        _configurationManager = configurationManager;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
 
-        /// <inheritdoc />
-        public string Key => "DeleteTranscodeFiles";
+    /// <inheritdoc />
+    public string Key => "DeleteTranscodeFiles";
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            return
-            [
-                new TaskTriggerInfo
-                {
-                    Type = TaskTriggerInfoType.StartupTrigger
-                },
-                new TaskTriggerInfo
-                {
-                    Type = TaskTriggerInfoType.IntervalTrigger,
-                    IntervalTicks = TimeSpan.FromHours(24).Ticks
-                }
-            ];
-        }
+            Type = TaskTriggerInfoType.StartupTrigger
+        };
 
-        /// <inheritdoc />
-        public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+        yield return new TaskTriggerInfo
         {
-            var minDateModified = DateTime.UtcNow.AddDays(-1);
-            progress.Report(50);
-
-            DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken);
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(24).Ticks
+        };
+    }
 
-            return Task.CompletedTask;
-        }
+    /// <inheritdoc />
+    public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var minDateModified = DateTime.UtcNow.AddDays(-1);
+        progress.Report(50);
 
-        /// <summary>
-        /// Deletes the transcoded temp files from directory with a last write time less than a given date.
-        /// </summary>
-        /// <param name="directory">The directory.</param>
-        /// <param name="minDateModified">The min date modified.</param>
-        /// <param name="progress">The progress.</param>
-        /// <param name="cancellationToken">The task cancellation token.</param>
-        private void DeleteTempFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var filesToDelete = _fileSystem.GetFiles(directory, true)
-                .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
-                .ToList();
+        DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken);
 
-            var index = 0;
+        return Task.CompletedTask;
+    }
 
-            foreach (var file in filesToDelete)
-            {
-                double percent = index;
-                percent /= filesToDelete.Count;
+    /// <summary>
+    /// Deletes the transcoded temp files from directory with a last write time less than a given date.
+    /// </summary>
+    /// <param name="directory">The directory.</param>
+    /// <param name="minDateModified">The min date modified.</param>
+    /// <param name="progress">The progress.</param>
+    /// <param name="cancellationToken">The task cancellation token.</param>
+    private void DeleteTempFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        var filesToDelete = _fileSystem.GetFiles(directory, true)
+            .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
+            .ToList();
 
-                progress.Report(100 * percent);
+        var index = 0;
 
-                cancellationToken.ThrowIfCancellationRequested();
+        foreach (var file in filesToDelete)
+        {
+            double percent = index;
+            percent /= filesToDelete.Count;
 
-                FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
+            progress.Report(100 * percent);
 
-                index++;
-            }
+            cancellationToken.ThrowIfCancellationRequested();
 
-            FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
+            FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
 
-            progress.Report(100);
+            index++;
         }
+
+        FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
+
+        progress.Report(100);
     }
 }

+ 2 - 2
Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs

@@ -62,11 +62,11 @@ public class MediaSegmentExtractionTask : IScheduledTask
 
         var query = new InternalItemsQuery
         {
-            MediaTypes = new[] { MediaType.Video, MediaType.Audio },
+            MediaTypes = [MediaType.Video, MediaType.Audio],
             IsVirtualItem = false,
             IncludeItemTypes = _itemTypes,
             DtoOptions = new DtoOptions(true),
-            SourceTypes = new[] { SourceType.Library },
+            SourceTypes = [SourceType.Library],
             Recursive = true,
             Limit = pagesize
         };

+ 54 - 60
Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs

@@ -5,84 +5,78 @@ using System.Threading.Tasks;
 using Jellyfin.Database.Implementations;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Tasks;
-using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Optimizes Jellyfin's database by issuing a VACUUM command.
+/// </summary>
+public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask
 {
+    private readonly ILogger<OptimizeDatabaseTask> _logger;
+    private readonly ILocalizationManager _localization;
+    private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider;
+
     /// <summary>
-    /// Optimizes Jellyfin's database by issuing a VACUUM command.
+    /// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class.
     /// </summary>
-    public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    /// <param name="jellyfinDatabaseProvider">Instance of the JellyfinDatabaseProvider that can be used for provider specific operations.</param>
+    public OptimizeDatabaseTask(
+        ILogger<OptimizeDatabaseTask> logger,
+        ILocalizationManager localization,
+        IJellyfinDatabaseProvider jellyfinDatabaseProvider)
     {
-        private readonly ILogger<OptimizeDatabaseTask> _logger;
-        private readonly ILocalizationManager _localization;
-        private readonly IDbContextFactory<JellyfinDbContext> _provider;
-        private readonly IJellyfinDatabaseProvider _jellyfinDatabaseProvider;
+        _logger = logger;
+        _localization = localization;
+        _jellyfinDatabaseProvider = jellyfinDatabaseProvider;
+    }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class.
-        /// </summary>
-        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        /// <param name="provider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
-        /// <param name="jellyfinDatabaseProvider">Instance of the JellyfinDatabaseProvider that can be used for provider specific operations.</param>
-        public OptimizeDatabaseTask(
-            ILogger<OptimizeDatabaseTask> logger,
-            ILocalizationManager localization,
-            IDbContextFactory<JellyfinDbContext> provider,
-            IJellyfinDatabaseProvider jellyfinDatabaseProvider)
-        {
-            _logger = logger;
-            _localization = localization;
-            _provider = provider;
-            _jellyfinDatabaseProvider = jellyfinDatabaseProvider;
-        }
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskOptimizeDatabase");
+
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskOptimizeDatabaseDescription");
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskOptimizeDatabase");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskOptimizeDatabaseDescription");
+    /// <inheritdoc />
+    public string Key => "OptimizeDatabaseTask";
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public string Key => "OptimizeDatabaseTask";
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
+        {
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(24).Ticks
+        };
+    }
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        _logger.LogInformation("Optimizing and vacuuming jellyfin.db...");
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+        try
         {
-            return
-            [
-                // Every so often
-                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
-            ];
+            await _jellyfinDatabaseProvider.RunScheduledOptimisation(cancellationToken).ConfigureAwait(false);
         }
-
-        /// <inheritdoc />
-        public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+        catch (Exception e)
         {
-            _logger.LogInformation("Optimizing and vacuuming jellyfin.db...");
-
-            try
-            {
-                await _jellyfinDatabaseProvider.RunScheduledOptimisation(cancellationToken).ConfigureAwait(false);
-            }
-            catch (Exception e)
-            {
-                _logger.LogError(e, "Error while optimizing jellyfin.db");
-            }
+            _logger.LogError(e, "Error while optimizing jellyfin.db");
         }
     }
 }

+ 45 - 49
Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs

@@ -6,68 +6,64 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Tasks;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Class PeopleValidationTask.
+/// </summary>
+public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask
 {
+    private readonly ILibraryManager _libraryManager;
+    private readonly ILocalizationManager _localization;
+
     /// <summary>
-    /// Class PeopleValidationTask.
+    /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
     /// </summary>
-    public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization)
     {
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization)
-        {
-            _libraryManager = libraryManager;
-            _localization = localization;
-        }
+        _libraryManager = libraryManager;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskRefreshPeople");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskRefreshPeople");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
 
-        /// <inheritdoc />
-        public string Key => "RefreshPeople";
+    /// <inheritdoc />
+    public string Key => "RefreshPeople";
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <summary>
-        /// Creates the triggers that define when the task will run.
-        /// </summary>
-        /// <returns>An <see cref="IEnumerable{TaskTriggerInfo}"/> containing the default trigger infos for this task.</returns>
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <summary>
+    /// Creates the triggers that define when the task will run.
+    /// </summary>
+    /// <returns>An <see cref="IEnumerable{TaskTriggerInfo}"/> containing the default trigger infos for this task.</returns>
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            return new[]
-            {
-                new TaskTriggerInfo
-                {
-                    Type = TaskTriggerInfoType.IntervalTrigger,
-                    IntervalTicks = TimeSpan.FromDays(7).Ticks
-                }
-            };
-        }
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromDays(7).Ticks
+        };
+    }
 
-        /// <inheritdoc />
-        public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            return _libraryManager.ValidatePeopleAsync(progress, cancellationToken);
-        }
+    /// <inheritdoc />
+    public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        return _libraryManager.ValidatePeopleAsync(progress, cancellationToken);
     }
 }

+ 84 - 80
Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs

@@ -10,111 +10,115 @@ using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Plugin Update Task.
+/// </summary>
+public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
 {
+    private readonly ILogger<PluginUpdateTask> _logger;
+
+    private readonly IInstallationManager _installationManager;
+    private readonly ILocalizationManager _localization;
+
     /// <summary>
-    /// Plugin Update Task.
+    /// Initializes a new instance of the <see cref="PluginUpdateTask" /> class.
     /// </summary>
-    public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
+    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+    /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public PluginUpdateTask(ILogger<PluginUpdateTask> logger, IInstallationManager installationManager, ILocalizationManager localization)
     {
-        private readonly ILogger<PluginUpdateTask> _logger;
-
-        private readonly IInstallationManager _installationManager;
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PluginUpdateTask" /> class.
-        /// </summary>
-        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
-        /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public PluginUpdateTask(ILogger<PluginUpdateTask> logger, IInstallationManager installationManager, ILocalizationManager localization)
-        {
-            _logger = logger;
-            _installationManager = installationManager;
-            _localization = localization;
-        }
+        _logger = logger;
+        _installationManager = installationManager;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
 
-        /// <inheritdoc />
-        public string Key => "PluginUpdates";
+    /// <inheritdoc />
+    public string Key => "PluginUpdates";
 
-        /// <inheritdoc />
-        public bool IsHidden => false;
+    /// <inheritdoc />
+    public bool IsHidden => false;
 
-        /// <inheritdoc />
-        public bool IsEnabled => true;
+    /// <inheritdoc />
+    public bool IsEnabled => true;
 
-        /// <inheritdoc />
-        public bool IsLogged => true;
+    /// <inheritdoc />
+    public bool IsLogged => true;
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            // At startup
-            yield return new TaskTriggerInfo { Type = TaskTriggerInfoType.StartupTrigger };
-
-            // Every so often
-            yield return new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks };
-        }
+            Type = TaskTriggerInfoType.StartupTrigger
+        };
 
-        /// <inheritdoc />
-        public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+        yield return new TaskTriggerInfo
         {
-            progress.Report(0);
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(24).Ticks
+        };
+    }
 
-            var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
-            var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
+    /// <inheritdoc />
+    public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        progress.Report(0);
 
-            progress.Report(10);
+        var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
+        var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
 
-            var numComplete = 0;
+        progress.Report(10);
 
-            foreach (var package in packagesToInstall)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
+        var numComplete = 0;
 
-                try
-                {
-                    await _installationManager.InstallPackage(package, cancellationToken).ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    // InstallPackage has its own inner cancellation token, so only throw this if it's ours
-                    if (cancellationToken.IsCancellationRequested)
-                    {
-                        throw;
-                    }
-                }
-                catch (HttpRequestException ex)
-                {
-                    _logger.LogError(ex, "Error downloading {0}", package.Name);
-                }
-                catch (IOException ex)
-                {
-                    _logger.LogError(ex, "Error updating {0}", package.Name);
-                }
-                catch (InvalidDataException ex)
-                {
-                    _logger.LogError(ex, "Error updating {0}", package.Name);
-                }
+        foreach (var package in packagesToInstall)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
 
-                // Update progress
-                lock (progress)
+            try
+            {
+                await _installationManager.InstallPackage(package, cancellationToken).ConfigureAwait(false);
+            }
+            catch (OperationCanceledException)
+            {
+                // InstallPackage has its own inner cancellation token, so only throw this if it's ours
+                if (cancellationToken.IsCancellationRequested)
                 {
-                    progress.Report((90.0 * ++numComplete / packagesToInstall.Count) + 10);
+                    throw;
                 }
             }
+            catch (HttpRequestException ex)
+            {
+                _logger.LogError(ex, "Error downloading {Name}", package.Name);
+            }
+            catch (IOException ex)
+            {
+                _logger.LogError(ex, "Error updating {Name}", package.Name);
+            }
+            catch (InvalidDataException ex)
+            {
+                _logger.LogError(ex, "Error updating {Name}", package.Name);
+            }
 
-            progress.Report(100);
+            // Update progress
+            lock (progress)
+            {
+                progress.Report((90.0 * ++numComplete / packagesToInstall.Count) + 10);
+            }
         }
+
+        progress.Report(100);
     }
 }

+ 41 - 42
Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs

@@ -7,60 +7,59 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Tasks;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Tasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Class RefreshMediaLibraryTask.
+/// </summary>
+public class RefreshMediaLibraryTask : IScheduledTask
 {
     /// <summary>
-    /// Class RefreshMediaLibraryTask.
+    /// The _library manager.
     /// </summary>
-    public class RefreshMediaLibraryTask : IScheduledTask
-    {
-        /// <summary>
-        /// The _library manager.
-        /// </summary>
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILocalizationManager _localization;
+    private readonly ILibraryManager _libraryManager;
+    private readonly ILocalizationManager _localization;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class.
-        /// </summary>
-        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
-        /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization)
-        {
-            _libraryManager = libraryManager;
-            _localization = localization;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class.
+    /// </summary>
+    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+    public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization)
+    {
+        _libraryManager = libraryManager;
+        _localization = localization;
+    }
 
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskRefreshLibrary");
+    /// <inheritdoc />
+    public string Name => _localization.GetLocalizedString("TaskRefreshLibrary");
 
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription");
+    /// <inheritdoc />
+    public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription");
 
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+    /// <inheritdoc />
+    public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
 
-        /// <inheritdoc />
-        public string Key => "RefreshLibrary";
+    /// <inheritdoc />
+    public string Key => "RefreshLibrary";
 
-        /// <inheritdoc />
-        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    /// <inheritdoc />
+    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+    {
+        yield return new TaskTriggerInfo
         {
-            yield return new TaskTriggerInfo
-            {
-                Type = TaskTriggerInfoType.IntervalTrigger,
-                IntervalTicks = TimeSpan.FromHours(12).Ticks
-            };
-        }
+            Type = TaskTriggerInfoType.IntervalTrigger,
+            IntervalTicks = TimeSpan.FromHours(12).Ticks
+        };
+    }
 
-        /// <inheritdoc />
-        public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
+    /// <inheritdoc />
+    public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+    {
+        cancellationToken.ThrowIfCancellationRequested();
 
-            progress.Report(0);
+        progress.Report(0);
 
-            return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
-        }
+        return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
     }
 }

+ 60 - 61
Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs

@@ -3,85 +3,84 @@ using System.Threading;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Triggers
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers;
+
+/// <summary>
+/// Represents a task trigger that fires everyday.
+/// </summary>
+public sealed class DailyTrigger : ITaskTrigger, IDisposable
 {
+    private readonly TimeSpan _timeOfDay;
+    private Timer? _timer;
+    private bool _disposed;
+
     /// <summary>
-    /// Represents a task trigger that fires everyday.
+    /// Initializes a new instance of the <see cref="DailyTrigger"/> class.
     /// </summary>
-    public sealed class DailyTrigger : ITaskTrigger, IDisposable
+    /// <param name="timeOfDay">The time of day to trigger the task to run.</param>
+    /// <param name="taskOptions">The options of this task.</param>
+    public DailyTrigger(TimeSpan timeOfDay, TaskOptions taskOptions)
     {
-        private readonly TimeSpan _timeOfDay;
-        private Timer? _timer;
-        private bool _disposed = false;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DailyTrigger"/> class.
-        /// </summary>
-        /// <param name="timeofDay">The time of day to trigger the task to run.</param>
-        /// <param name="taskOptions">The options of this task.</param>
-        public DailyTrigger(TimeSpan timeofDay, TaskOptions taskOptions)
-        {
-            _timeOfDay = timeofDay;
-            TaskOptions = taskOptions;
-        }
+        _timeOfDay = timeOfDay;
+        TaskOptions = taskOptions;
+    }
 
-        /// <inheritdoc />
-        public event EventHandler<EventArgs>? Triggered;
+    /// <inheritdoc />
+    public event EventHandler<EventArgs>? Triggered;
 
-        /// <inheritdoc />
-        public TaskOptions TaskOptions { get; }
+    /// <inheritdoc />
+    public TaskOptions TaskOptions { get; }
 
-        /// <inheritdoc />
-        public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
-        {
-            DisposeTimer();
+    /// <inheritdoc />
+    public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+    {
+        DisposeTimer();
 
-            var now = DateTime.Now;
+        var now = DateTime.Now;
 
-            var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date;
-            triggerDate = triggerDate.Add(_timeOfDay);
+        var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date;
+        triggerDate = triggerDate.Add(_timeOfDay);
 
-            var dueTime = triggerDate - now;
+        var dueTime = triggerDate - now;
 
-            logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime);
+        logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime);
 
-            _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
-        }
+        _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
+    }
 
-        /// <inheritdoc />
-        public void Stop()
-        {
-            DisposeTimer();
-        }
+    /// <inheritdoc />
+    public void Stop()
+    {
+        DisposeTimer();
+    }
 
-        /// <summary>
-        /// Disposes the timer.
-        /// </summary>
-        private void DisposeTimer()
-        {
-            _timer?.Dispose();
-            _timer = null;
-        }
+    /// <summary>
+    /// Disposes the timer.
+    /// </summary>
+    private void DisposeTimer()
+    {
+        _timer?.Dispose();
+        _timer = null;
+    }
 
-        /// <summary>
-        /// Called when [triggered].
-        /// </summary>
-        private void OnTriggered()
-        {
-            Triggered?.Invoke(this, EventArgs.Empty);
-        }
+    /// <summary>
+    /// Called when [triggered].
+    /// </summary>
+    private void OnTriggered()
+    {
+        Triggered?.Invoke(this, EventArgs.Empty);
+    }
 
-        /// <inheritdoc />
-        public void Dispose()
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
         {
-            if (_disposed)
-            {
-                return;
-            }
+            return;
+        }
 
-            DisposeTimer();
+        DisposeTimer();
 
-            _disposed = true;
-        }
+        _disposed = true;
     }
 }

+ 77 - 78
Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs

@@ -4,104 +4,103 @@ using System.Threading;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Triggers
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers;
+
+/// <summary>
+/// Represents a task trigger that runs repeatedly on an interval.
+/// </summary>
+public sealed class IntervalTrigger : ITaskTrigger, IDisposable
 {
+    private readonly TimeSpan _interval;
+    private DateTime _lastStartDate;
+    private Timer? _timer;
+    private bool _disposed;
+
     /// <summary>
-    /// Represents a task trigger that runs repeatedly on an interval.
+    /// Initializes a new instance of the <see cref="IntervalTrigger"/> class.
     /// </summary>
-    public sealed class IntervalTrigger : ITaskTrigger, IDisposable
+    /// <param name="interval">The interval.</param>
+    /// <param name="taskOptions">The options of this task.</param>
+    public IntervalTrigger(TimeSpan interval, TaskOptions taskOptions)
     {
-        private readonly TimeSpan _interval;
-        private DateTime _lastStartDate;
-        private Timer? _timer;
-        private bool _disposed = false;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="IntervalTrigger"/> class.
-        /// </summary>
-        /// <param name="interval">The interval.</param>
-        /// <param name="taskOptions">The options of this task.</param>
-        public IntervalTrigger(TimeSpan interval, TaskOptions taskOptions)
-        {
-            _interval = interval;
-            TaskOptions = taskOptions;
-        }
+        _interval = interval;
+        TaskOptions = taskOptions;
+    }
+
+    /// <inheritdoc />
+    public event EventHandler<EventArgs>? Triggered;
 
-        /// <inheritdoc />
-        public event EventHandler<EventArgs>? Triggered;
+    /// <inheritdoc />
+    public TaskOptions TaskOptions { get; }
+
+    /// <inheritdoc />
+    public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+    {
+        DisposeTimer();
 
-        /// <inheritdoc />
-        public TaskOptions TaskOptions { get; }
+        DateTime now = DateTime.UtcNow;
+        DateTime triggerDate;
 
-        /// <inheritdoc />
-        public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+        if (lastResult is null)
         {
-            DisposeTimer();
-
-            DateTime now = DateTime.UtcNow;
-            DateTime triggerDate;
-
-            if (lastResult is null)
-            {
-                // Task has never been completed before
-                triggerDate = now.AddHours(1);
-            }
-            else
-            {
-                triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate, now.AddMinutes(1) }.Max().Add(_interval);
-            }
-
-            var dueTime = triggerDate - now;
-            var maxDueTime = TimeSpan.FromDays(7);
-
-            if (dueTime > maxDueTime)
-            {
-                dueTime = maxDueTime;
-            }
-
-            _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
+            // Task has never been completed before
+            triggerDate = now.AddHours(1);
         }
-
-        /// <inheritdoc />
-        public void Stop()
+        else
         {
-            DisposeTimer();
+            triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate, now.AddMinutes(1) }.Max().Add(_interval);
         }
 
-        /// <summary>
-        /// Disposes the timer.
-        /// </summary>
-        private void DisposeTimer()
+        var dueTime = triggerDate - now;
+        var maxDueTime = TimeSpan.FromDays(7);
+
+        if (dueTime > maxDueTime)
         {
-            _timer?.Dispose();
-            _timer = null;
+            dueTime = maxDueTime;
         }
 
-        /// <summary>
-        /// Called when [triggered].
-        /// </summary>
-        private void OnTriggered()
-        {
-            DisposeTimer();
+        _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
+    }
+
+    /// <inheritdoc />
+    public void Stop()
+    {
+        DisposeTimer();
+    }
 
-            if (Triggered is not null)
-            {
-                _lastStartDate = DateTime.UtcNow;
-                Triggered(this, EventArgs.Empty);
-            }
+    /// <summary>
+    /// Disposes the timer.
+    /// </summary>
+    private void DisposeTimer()
+    {
+        _timer?.Dispose();
+        _timer = null;
+    }
+
+    /// <summary>
+    /// Called when [triggered].
+    /// </summary>
+    private void OnTriggered()
+    {
+        DisposeTimer();
+
+        if (Triggered is not null)
+        {
+            _lastStartDate = DateTime.UtcNow;
+            Triggered(this, EventArgs.Empty);
         }
+    }
 
-        /// <inheritdoc />
-        public void Dispose()
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
         {
-            if (_disposed)
-            {
-                return;
-            }
+            return;
+        }
 
-            DisposeTimer();
+        DisposeTimer();
 
-            _disposed = true;
-        }
+        _disposed = true;
     }
 }

+ 34 - 35
Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs

@@ -3,52 +3,51 @@ using System.Threading.Tasks;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Triggers
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers;
+
+/// <summary>
+/// Class StartupTaskTrigger.
+/// </summary>
+public sealed class StartupTrigger : ITaskTrigger
 {
+    private const int DelayMs = 3000;
+
     /// <summary>
-    /// Class StartupTaskTrigger.
+    /// Initializes a new instance of the <see cref="StartupTrigger"/> class.
     /// </summary>
-    public sealed class StartupTrigger : ITaskTrigger
+    /// <param name="taskOptions">The options of this task.</param>
+    public StartupTrigger(TaskOptions taskOptions)
     {
-        private const int DelayMs = 3000;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StartupTrigger"/> class.
-        /// </summary>
-        /// <param name="taskOptions">The options of this task.</param>
-        public StartupTrigger(TaskOptions taskOptions)
-        {
-            TaskOptions = taskOptions;
-        }
+        TaskOptions = taskOptions;
+    }
 
-        /// <inheritdoc />
-        public event EventHandler<EventArgs>? Triggered;
+    /// <inheritdoc />
+    public event EventHandler<EventArgs>? Triggered;
 
-        /// <inheritdoc />
-        public TaskOptions TaskOptions { get; }
+    /// <inheritdoc />
+    public TaskOptions TaskOptions { get; }
 
-        /// <inheritdoc />
-        public async void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+    /// <inheritdoc />
+    public async void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+    {
+        if (isApplicationStartup)
         {
-            if (isApplicationStartup)
-            {
-                await Task.Delay(DelayMs).ConfigureAwait(false);
+            await Task.Delay(DelayMs).ConfigureAwait(false);
 
-                OnTriggered();
-            }
+            OnTriggered();
         }
+    }
 
-        /// <inheritdoc />
-        public void Stop()
-        {
-        }
+    /// <inheritdoc />
+    public void Stop()
+    {
+    }
 
-        /// <summary>
-        /// Called when [triggered].
-        /// </summary>
-        private void OnTriggered()
-        {
-            Triggered?.Invoke(this, EventArgs.Empty);
-        }
+    /// <summary>
+    /// Called when [triggered].
+    /// </summary>
+    private void OnTriggered()
+    {
+        Triggered?.Invoke(this, EventArgs.Empty);
     }
 }

+ 80 - 81
Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs

@@ -3,108 +3,107 @@ using System.Threading;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.ScheduledTasks.Triggers
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers;
+
+/// <summary>
+/// Represents a task trigger that fires on a weekly basis.
+/// </summary>
+public sealed class WeeklyTrigger : ITaskTrigger, IDisposable
 {
+    private readonly TimeSpan _timeOfDay;
+    private readonly DayOfWeek _dayOfWeek;
+    private Timer? _timer;
+    private bool _disposed;
+
     /// <summary>
-    /// Represents a task trigger that fires on a weekly basis.
+    /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class.
     /// </summary>
-    public sealed class WeeklyTrigger : ITaskTrigger, IDisposable
+    /// <param name="timeOfDay">The time of day to trigger the task to run.</param>
+    /// <param name="dayOfWeek">The day of week.</param>
+    /// <param name="taskOptions">The options of this task.</param>
+    public WeeklyTrigger(TimeSpan timeOfDay, DayOfWeek dayOfWeek, TaskOptions taskOptions)
     {
-        private readonly TimeSpan _timeOfDay;
-        private readonly DayOfWeek _dayOfWeek;
-        private Timer? _timer;
-        private bool _disposed;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class.
-        /// </summary>
-        /// <param name="timeofDay">The time of day to trigger the task to run.</param>
-        /// <param name="dayOfWeek">The day of week.</param>
-        /// <param name="taskOptions">The options of this task.</param>
-        public WeeklyTrigger(TimeSpan timeofDay, DayOfWeek dayOfWeek, TaskOptions taskOptions)
-        {
-            _timeOfDay = timeofDay;
-            _dayOfWeek = dayOfWeek;
-            TaskOptions = taskOptions;
-        }
+        _timeOfDay = timeOfDay;
+        _dayOfWeek = dayOfWeek;
+        TaskOptions = taskOptions;
+    }
 
-        /// <inheritdoc />
-        public event EventHandler<EventArgs>? Triggered;
+    /// <inheritdoc />
+    public event EventHandler<EventArgs>? Triggered;
 
-        /// <inheritdoc />
-        public TaskOptions TaskOptions { get; }
+    /// <inheritdoc />
+    public TaskOptions TaskOptions { get; }
 
-        /// <inheritdoc />
-        public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
-        {
-            DisposeTimer();
+    /// <inheritdoc />
+    public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+    {
+        DisposeTimer();
 
-            var triggerDate = GetNextTriggerDateTime();
+        var triggerDate = GetNextTriggerDateTime();
 
-            _timer = new Timer(_ => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1));
-        }
+        _timer = new Timer(_ => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1));
+    }
 
-        /// <summary>
-        /// Gets the next trigger date time.
-        /// </summary>
-        /// <returns>DateTime.</returns>
-        private DateTime GetNextTriggerDateTime()
+    /// <summary>
+    /// Gets the next trigger date time.
+    /// </summary>
+    /// <returns>DateTime.</returns>
+    private DateTime GetNextTriggerDateTime()
+    {
+        var now = DateTime.Now;
+
+        // If it's on the same day
+        if (now.DayOfWeek == _dayOfWeek)
         {
-            var now = DateTime.Now;
+            // It's either later today, or a week from now
+            return now.TimeOfDay < _timeOfDay ? now.Date.Add(_timeOfDay) : now.Date.AddDays(7).Add(_timeOfDay);
+        }
 
-            // If it's on the same day
-            if (now.DayOfWeek == _dayOfWeek)
-            {
-                // It's either later today, or a week from now
-                return now.TimeOfDay < _timeOfDay ? now.Date.Add(_timeOfDay) : now.Date.AddDays(7).Add(_timeOfDay);
-            }
+        var triggerDate = now.Date;
 
-            var triggerDate = now.Date;
+        // Walk the date forward until we get to the trigger day
+        while (triggerDate.DayOfWeek != _dayOfWeek)
+        {
+            triggerDate = triggerDate.AddDays(1);
+        }
 
-            // Walk the date forward until we get to the trigger day
-            while (triggerDate.DayOfWeek != _dayOfWeek)
-            {
-                triggerDate = triggerDate.AddDays(1);
-            }
+        // Return the trigger date plus the time offset
+        return triggerDate.Add(_timeOfDay);
+    }
 
-            // Return the trigger date plus the time offset
-            return triggerDate.Add(_timeOfDay);
-        }
+    /// <inheritdoc />
+    public void Stop()
+    {
+        DisposeTimer();
+    }
 
-        /// <inheritdoc />
-        public void Stop()
-        {
-            DisposeTimer();
-        }
+    /// <summary>
+    /// Disposes the timer.
+    /// </summary>
+    private void DisposeTimer()
+    {
+        _timer?.Dispose();
+        _timer = null;
+    }
 
-        /// <summary>
-        /// Disposes the timer.
-        /// </summary>
-        private void DisposeTimer()
-        {
-            _timer?.Dispose();
-            _timer = null;
-        }
+    /// <summary>
+    /// Called when [triggered].
+    /// </summary>
+    private void OnTriggered()
+    {
+        Triggered?.Invoke(this, EventArgs.Empty);
+    }
 
-        /// <summary>
-        /// Called when [triggered].
-        /// </summary>
-        private void OnTriggered()
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
         {
-            Triggered?.Invoke(this, EventArgs.Empty);
+            return;
         }
 
-        /// <inheritdoc />
-        public void Dispose()
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            DisposeTimer();
+        DisposeTimer();
 
-            _disposed = true;
-        }
+        _disposed = true;
     }
 }