diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
index d51f9aaa79..a31d5eccac 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
+++ b/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;
+
+///
+/// Class ArtistsPostScanTask.
+///
+public class ArtistsPostScanTask : ILibraryPostScanTask
{
///
- /// Class ArtistsPostScanTask.
+ /// The _library manager.
///
- public class ArtistsPostScanTask : ILibraryPostScanTask
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public ArtistsPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger logger,
+ IItemRepository itemRepo)
{
- ///
- /// The _library manager.
- ///
- private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
- private readonly IItemRepository _itemRepo;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public ArtistsPostScanTask(
- ILibraryManager libraryManager,
- ILogger logger,
- IItemRepository itemRepo)
- {
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
- }
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 7591e8391f..7cc851b73b 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/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;
+
+///
+/// Class ArtistsValidator.
+///
+public class ArtistsValidator
{
///
- /// Class ArtistsValidator.
+ /// The library manager.
///
- public class ArtistsValidator
+ private readonly ILibraryManager _libraryManager;
+
+ ///
+ /// The logger.
+ ///
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
- ///
- /// The library manager.
- ///
- private readonly ILibraryManager _libraryManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- ///
- /// The logger.
- ///
- private readonly ILogger _logger;
- private readonly IItemRepository _itemRepo;
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public async Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ var names = _itemRepo.GetAllArtistNames();
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ var numComplete = 0;
+ var count = names.Count;
+
+ foreach (var name in names)
{
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public async Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- var names = _itemRepo.GetAllArtistNames();
-
- var numComplete = 0;
- var count = names.Count;
-
- foreach (var name in names)
+ try
{
- try
- {
- var item = _libraryManager.GetArtist(name);
+ 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;
-
- 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 {ArtistName}", name);
}
- var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+ numComplete++;
+ double percent = numComplete;
+ percent /= count;
+ percent *= 100;
+
+ progress.Report(percent);
+ }
+
+ var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = [BaseItemKind.MusicArtist],
+ IsDeadArtist = true,
+ IsLocked = false
+ }).Cast().ToList();
+
+ foreach (var item in deadEntities)
+ {
+ if (!item.IsAccessedByName)
{
- IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
- IsDeadArtist = true,
- IsLocked = false
- }).Cast().ToList();
-
- foreach (var item in deadEntities)
- {
- if (!item.IsAccessedByName)
- {
- continue;
- }
-
- _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);
+ 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);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
index 337b1afdd4..38631e0de8 100644
--- a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
+++ b/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;
+
+///
+/// Class CollectionPostScanTask.
+///
+public class CollectionPostScanTask : ILibraryPostScanTask
{
+ private readonly ILibraryManager _libraryManager;
+ private readonly ICollectionManager _collectionManager;
+ private readonly ILogger _logger;
+
///
- /// Class CollectionPostScanTask.
+ /// Initializes a new instance of the class.
///
- public class CollectionPostScanTask : ILibraryPostScanTask
+ /// The library manager.
+ /// The collection manager.
+ /// The logger.
+ public CollectionPostScanTask(
+ ILibraryManager libraryManager,
+ ICollectionManager collectionManager,
+ ILogger logger)
{
- private readonly ILibraryManager _libraryManager;
- private readonly ICollectionManager _collectionManager;
- private readonly ILogger _logger;
+ _libraryManager = libraryManager;
+ _collectionManager = collectionManager;
+ _logger = logger;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The collection manager.
- /// The logger.
- public CollectionPostScanTask(
- ILibraryManager libraryManager,
- ICollectionManager collectionManager,
- ILogger logger)
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public async Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ var collectionNameMoviesMap = new Dictionary>();
+
+ foreach (var library in _libraryManager.RootFolder.Children)
{
- _libraryManager = libraryManager;
- _collectionManager = collectionManager;
- _logger = logger;
+ if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
+ {
+ continue;
+ }
+
+ var startIndex = 0;
+ var pagesize = 1000;
+
+ while (true)
+ {
+ var movies = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ 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 (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
+ {
+ movieList.Add(movie.Id);
+ }
+ else
+ {
+ collectionNameMoviesMap[movie.CollectionName] = new HashSet { movie.Id };
+ }
+ }
+ }
+
+ if (movies.Count < pagesize)
+ {
+ break;
+ }
+
+ startIndex += pagesize;
+ }
}
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public async Task Run(IProgress progress, CancellationToken cancellationToken)
+ var numComplete = 0;
+ var count = collectionNameMoviesMap.Count;
+
+ if (count == 0)
{
- var collectionNameMoviesMap = new Dictionary>();
-
- foreach (var library in _libraryManager.RootFolder.Children)
- {
- if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
- {
- continue;
- }
-
- var startIndex = 0;
- var pagesize = 1000;
-
- while (true)
- {
- 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)
- {
- if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
- {
- if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
- {
- movieList.Add(movie.Id);
- }
- else
- {
- collectionNameMoviesMap[movie.CollectionName] = new HashSet { movie.Id };
- }
- }
- }
-
- if (movies.Count < pagesize)
- {
- break;
- }
-
- startIndex += pagesize;
- }
- }
-
- var numComplete = 0;
- var count = collectionNameMoviesMap.Count;
-
- if (count == 0)
- {
- progress.Report(100);
- return;
- }
-
- var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { BaseItemKind.BoxSet },
- CollapseBoxSetItems = false,
- Recursive = true
- });
-
- foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
- {
- try
- {
- 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)
- {
- boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
- {
- Name = collectionName,
- IsLocked = true
- });
-
- await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
- }
- }
- else
- {
- await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
- }
-
- 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);
+ return;
}
+
+ var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = [BaseItemKind.BoxSet],
+ CollapseBoxSetItems = false,
+ Recursive = true
+ });
+
+ foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
+ {
+ try
+ {
+ 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)
+ {
+ boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
+ {
+ Name = collectionName,
+ IsLocked = true
+ }).ConfigureAwait(false);
+
+ await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds).ConfigureAwait(false);
+ }
+
+ 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);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
index d21d2887b0..5097e0073d 100644
--- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
+++ b/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;
+
+///
+/// Class GenresPostScanTask.
+///
+public class GenresPostScanTask : ILibraryPostScanTask
{
///
- /// Class GenresPostScanTask.
+ /// The _library manager.
///
- public class GenresPostScanTask : ILibraryPostScanTask
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public GenresPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger logger,
+ IItemRepository itemRepo)
{
- ///
- /// The _library manager.
- ///
- private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
- private readonly IItemRepository _itemRepo;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public GenresPostScanTask(
- ILibraryManager libraryManager,
- ILogger logger,
- IItemRepository itemRepo)
- {
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
- }
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
index 364770fcdc..fbfc9f7d54 100644
--- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
+++ b/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;
+
+///
+/// Class GenresValidator.
+///
+public class GenresValidator
{
///
- /// Class GenresValidator.
+ /// The library manager.
///
- public class GenresValidator
+ private readonly ILibraryManager _libraryManager;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// The logger.
+ ///
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
- ///
- /// The library manager.
- ///
- private readonly ILibraryManager _libraryManager;
- private readonly IItemRepository _itemRepo;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- ///
- /// The logger.
- ///
- private readonly ILogger _logger;
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public async Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ var names = _itemRepo.GetGenreNames();
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ var numComplete = 0;
+ var count = names.Count;
+
+ foreach (var name in names)
{
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public async Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- var names = _itemRepo.GetGenreNames();
-
- var numComplete = 0;
- var count = names.Count;
-
- 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);
+ }
+ catch (OperationCanceledException)
+ {
+ // Don't clutter the log
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error refreshing {GenreName}", name);
}
- var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre],
- IsDeadGenre = true,
- IsLocked = false
- });
+ numComplete++;
+ double percent = numComplete;
+ percent /= count;
+ percent *= 100;
- 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);
+ 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);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
index be119866b1..76658a81b5 100644
--- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
+++ b/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;
+
+///
+/// Class MusicGenresPostScanTask.
+///
+public class MusicGenresPostScanTask : ILibraryPostScanTask
{
///
- /// Class MusicGenresPostScanTask.
+ /// The library manager.
///
- public class MusicGenresPostScanTask : ILibraryPostScanTask
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public MusicGenresPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger logger,
+ IItemRepository itemRepo)
{
- ///
- /// The library manager.
- ///
- private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
- private readonly IItemRepository _itemRepo;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public MusicGenresPostScanTask(
- ILibraryManager libraryManager,
- ILogger logger,
- IItemRepository itemRepo)
- {
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
- }
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
index 1ecf4c87c9..6203bce2bc 100644
--- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
+++ b/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;
+
+///
+/// Class MusicGenresValidator.
+///
+public class MusicGenresValidator
{
///
- /// Class MusicGenresValidator.
+ /// The library manager.
///
- public class MusicGenresValidator
+ private readonly ILibraryManager _libraryManager;
+
+ ///
+ /// The logger.
+ ///
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
- ///
- /// The library manager.
- ///
- private readonly ILibraryManager _libraryManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- ///
- /// The logger.
- ///
- private readonly ILogger _logger;
- private readonly IItemRepository _itemRepo;
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public async Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ var names = _itemRepo.GetMusicGenreNames();
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ var numComplete = 0;
+ var count = names.Count;
+
+ foreach (var name in names)
{
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public async Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- var names = _itemRepo.GetMusicGenreNames();
-
- var numComplete = 0;
- var count = names.Count;
-
- 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);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index 725b8f76c7..b7fd24fa5c 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/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;
+
+///
+/// Class PeopleValidator.
+///
+public class PeopleValidator
{
///
- /// Class PeopleValidator.
+ /// The _library manager.
///
- public class PeopleValidator
+ private readonly ILibraryManager _libraryManager;
+
+ ///
+ /// The _logger.
+ ///
+ private readonly ILogger _logger;
+
+ private readonly IFileSystem _fileSystem;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The file system.
+ public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
{
- ///
- /// The _library manager.
- ///
- private readonly ILibraryManager _libraryManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _fileSystem = fileSystem;
+ }
- ///
- /// The _logger.
- ///
- private readonly ILogger _logger;
+ ///
+ /// Validates the people.
+ ///
+ /// The cancellation token.
+ /// The progress.
+ /// Task.
+ public async Task ValidatePeople(CancellationToken cancellationToken, IProgress progress)
+ {
+ var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery());
- private readonly IFileSystem _fileSystem;
+ var numComplete = 0;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The file system.
- public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
+ var numPeople = people.Count;
+
+ _logger.LogDebug("Will refresh {Amount} people", numPeople);
+
+ foreach (var person in people)
{
- _libraryManager = libraryManager;
- _logger = logger;
- _fileSystem = fileSystem;
- }
+ cancellationToken.ThrowIfCancellationRequested();
- ///
- /// Validates the people.
- ///
- /// The cancellation token.
- /// The progress.
- /// Task.
- public async Task ValidatePeople(CancellationToken cancellationToken, IProgress progress)
- {
- var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery());
-
- var numComplete = 0;
-
- var numPeople = people.Count;
-
- _logger.LogDebug("Will refresh {0} people", numPeople);
-
- foreach (var person in people)
+ try
{
- cancellationToken.ThrowIfCancellationRequested();
-
- try
+ var item = _libraryManager.GetPerson(person);
+ if (item is null)
{
- 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)
- {
- _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);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating IBN entry {Person}", person);
}
- var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = [BaseItemKind.Person],
- IsDeadPerson = true,
- IsLocked = false
- });
+ // Update progress
+ numComplete++;
+ double percent = numComplete;
+ percent /= numPeople;
- foreach (var item in deadEntities)
- {
- _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);
- }
-
- progress.Report(100);
-
- _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");
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
index c682b156b8..67c56c104d 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
+++ b/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;
+
+///
+/// Class MusicGenresPostScanTask.
+///
+public class StudiosPostScanTask : ILibraryPostScanTask
{
///
- /// Class MusicGenresPostScanTask.
+ /// The _library manager.
///
- public class StudiosPostScanTask : ILibraryPostScanTask
+ private readonly ILibraryManager _libraryManager;
+
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public StudiosPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger logger,
+ IItemRepository itemRepo)
{
- ///
- /// The _library manager.
- ///
- private readonly ILibraryManager _libraryManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- private readonly ILogger _logger;
- private readonly IItemRepository _itemRepo;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public StudiosPostScanTask(
- ILibraryManager libraryManager,
- ILogger logger,
- IItemRepository itemRepo)
- {
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
- }
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index 26bc49c1f0..5b87e4d9d0 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/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;
+
+///
+/// Class StudiosValidator.
+///
+public class StudiosValidator
{
///
- /// Class StudiosValidator.
+ /// The library manager.
///
- public class StudiosValidator
+ private readonly ILibraryManager _libraryManager;
+
+ private readonly IItemRepository _itemRepo;
+
+ ///
+ /// The logger.
+ ///
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The library manager.
+ /// The logger.
+ /// The item repository.
+ public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
- ///
- /// The library manager.
- ///
- private readonly ILibraryManager _libraryManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ }
- private readonly IItemRepository _itemRepo;
+ ///
+ /// Runs the specified progress.
+ ///
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ public async Task Run(IProgress progress, CancellationToken cancellationToken)
+ {
+ var names = _itemRepo.GetStudioNames();
- ///
- /// The logger.
- ///
- private readonly ILogger _logger;
+ var numComplete = 0;
+ var count = names.Count;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The library manager.
- /// The logger.
- /// The item repository.
- public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ foreach (var name in names)
{
- _libraryManager = libraryManager;
- _logger = logger;
- _itemRepo = itemRepo;
- }
-
- ///
- /// Runs the specified progress.
- ///
- /// The progress.
- /// The cancellation token.
- /// Task.
- public async Task Run(IProgress progress, CancellationToken cancellationToken)
- {
- var names = _itemRepo.GetStudioNames();
-
- var numComplete = 0;
- var count = names.Count;
-
- 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);
+ }
+ catch (OperationCanceledException)
+ {
+ // Don't clutter the log
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error refreshing {StudioName}", name);
}
- var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { BaseItemKind.Studio },
- IsDeadStudio = true,
- IsLocked = false
- });
+ numComplete++;
+ double percent = numComplete;
+ percent /= count;
+ percent *= 100;
- 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);
+ 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);
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 985f0a8f85..24f554981a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/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;
+
+///
+/// Class ScheduledTaskWorker.
+///
+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[] _triggers;
+ private string _id;
+
///
- /// Class ScheduledTaskWorker.
+ /// Initializes a new instance of the class.
///
- public class ScheduledTaskWorker : IScheduledTaskWorker
+ /// The scheduled task.
+ /// The application paths.
+ /// The task manager.
+ /// The logger.
+ ///
+ /// scheduledTask
+ /// or
+ /// applicationPaths
+ /// or
+ /// taskManager
+ /// or
+ /// jsonSerializer
+ /// or
+ /// logger.
+ ///
+ 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[] _triggers;
- private string _id;
+ ArgumentNullException.ThrowIfNull(scheduledTask);
+ ArgumentNullException.ThrowIfNull(applicationPaths);
+ ArgumentNullException.ThrowIfNull(taskManager);
+ ArgumentNullException.ThrowIfNull(logger);
- ///
- /// Initializes a new instance of the class.
- ///
- /// The scheduled task.
- /// The application paths.
- /// The task manager.
- /// The logger.
- ///
- /// scheduledTask
- /// or
- /// applicationPaths
- /// or
- /// taskManager
- /// or
- /// jsonSerializer
- /// or
- /// logger.
- ///
- public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
+ ScheduledTask = scheduledTask;
+ _applicationPaths = applicationPaths;
+ _taskManager = taskManager;
+ _logger = logger;
+
+ InitTriggerEvents();
+ }
+
+ ///
+ public event EventHandler> TaskProgress;
+
+ ///
+ public IScheduledTask ScheduledTask { get; private set; }
+
+ ///
+ public TaskResult LastExecutionResult
+ {
+ get
{
- ArgumentNullException.ThrowIfNull(scheduledTask);
- ArgumentNullException.ThrowIfNull(applicationPaths);
- ArgumentNullException.ThrowIfNull(taskManager);
- ArgumentNullException.ThrowIfNull(logger);
+ var path = GetHistoryFilePath();
- ScheduledTask = scheduledTask;
- _applicationPaths = applicationPaths;
- _taskManager = taskManager;
- _logger = logger;
-
- InitTriggerEvents();
- }
-
- ///
- public event EventHandler> TaskProgress;
-
- ///
- public IScheduledTask ScheduledTask { get; private set; }
-
- ///
- public TaskResult LastExecutionResult
- {
- get
+ lock (_lastExecutionResultSyncLock)
{
- var path = GetHistoryFilePath();
-
- 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(bytes, _jsonOptions);
- }
- catch (JsonException ex)
- {
- _logger.LogError(ex, "Error deserializing {File}", path);
- }
+ _lastExecutionResult = JsonSerializer.Deserialize(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;
- }
- }
-
- return _lastExecutionResult;
- }
-
- private set
- {
- _lastExecutionResult = value;
-
- 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);
- }
- }
- }
-
- ///
- public string Name => ScheduledTask.Name;
-
- ///
- public string Description => ScheduledTask.Description;
-
- ///
- public string Category => ScheduledTask.Category;
-
- ///
- /// Gets or sets the current cancellation token.
- ///
- /// The current cancellation token source.
- private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
-
- ///
- /// Gets or sets the current execution start time.
- ///
- /// The current execution start time.
- private DateTime CurrentExecutionStartTime { get; set; }
-
- ///
- public TaskState State
- {
- get
- {
- if (CurrentCancellationTokenSource is not null)
- {
- return CurrentCancellationTokenSource.IsCancellationRequested
- ? TaskState.Cancelling
- : TaskState.Running;
- }
-
- return TaskState.Idle;
- }
- }
-
- ///
- public double? CurrentProgress { get; private set; }
-
- ///
- /// Gets or sets the triggers that define when the task will run.
- ///
- /// The triggers.
- private Tuple[] InternalTriggers
- {
- get => _triggers;
- set
- {
- ArgumentNullException.ThrowIfNull(value);
-
- // Cleanup current triggers
- if (_triggers is not null)
- {
- DisposeTriggers();
- }
-
- _triggers = value.ToArray();
-
- ReloadTriggerEvents(false);
- }
- }
-
- ///
- public IReadOnlyList Triggers
- {
- get
- {
- return Array.ConvertAll(InternalTriggers, i => i.Item1);
- }
-
- 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();
-
- SaveTriggers(triggerList);
-
- InternalTriggers = Array.ConvertAll(triggerList, i => new Tuple(i, GetTrigger(i)));
- }
- }
-
- ///
- public string Id
- {
- get
- {
- return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- }
- }
-
- private void InitTriggerEvents()
- {
- _triggers = LoadTriggers();
- ReloadTriggerEvents(true);
- }
-
- ///
- public void ReloadTriggerEvents()
- {
- ReloadTriggerEvents(false);
- }
-
- ///
- /// Reloads the trigger events.
- ///
- /// if set to true [is application startup].
- private void ReloadTriggerEvents(bool isApplicationStartup)
- {
- foreach (var triggerInfo in InternalTriggers)
- {
- var trigger = triggerInfo.Item2;
-
- trigger.Stop();
-
- trigger.Triggered -= OnTriggerTriggered;
- trigger.Triggered += OnTriggerTriggered;
- trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup);
- }
- }
-
- ///
- /// Handles the Triggered event of the trigger control.
- ///
- /// The source of the event.
- /// The instance containing the event data.
- private async void OnTriggerTriggered(object sender, EventArgs e)
- {
- var trigger = (ITaskTrigger)sender;
-
- if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled)
- {
- return;
- }
-
- _logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name);
-
- trigger.Stop();
-
- _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions);
-
- await Task.Delay(1000).ConfigureAwait(false);
-
- trigger.Start(LastExecutionResult, _logger, Name, false);
- }
-
- ///
- /// Executes the task.
- ///
- /// Task options.
- /// Task.
- /// Cannot execute a Task that is already running.
- public async Task Execute(TaskOptions options)
- {
- var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false));
-
- _currentTask = task;
-
- try
- {
- await task.ConfigureAwait(false);
- }
- finally
- {
- _currentTask = null;
- GC.Collect();
- }
- }
-
- 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");
- }
-
- var progress = new Progress();
-
- CurrentCancellationTokenSource = new CancellationTokenSource();
-
- _logger.LogDebug("Executing {0}", Name);
-
- ((TaskManager)_taskManager).OnTaskExecuting(this);
-
- progress.ProgressChanged += OnProgressChanged;
-
- TaskCompletionStatus status;
- CurrentExecutionStartTime = DateTime.UtcNow;
-
- Exception failureException = null;
-
- try
- {
- if (options is not null && options.MaxRuntimeTicks.HasValue)
- {
- CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value));
- }
-
- await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false);
-
- status = TaskCompletionStatus.Completed;
- }
- catch (OperationCanceledException)
- {
- status = TaskCompletionStatus.Cancelled;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error executing Scheduled Task");
-
- failureException = ex;
-
- status = TaskCompletionStatus.Failed;
- }
-
- var startTime = CurrentExecutionStartTime;
- var endTime = DateTime.UtcNow;
-
- progress.ProgressChanged -= OnProgressChanged;
- CurrentCancellationTokenSource.Dispose();
- CurrentCancellationTokenSource = null;
- CurrentProgress = null;
-
- OnTaskCompleted(startTime, endTime, status, failureException);
- }
-
- ///
- /// Progress_s the progress changed.
- ///
- /// The sender.
- /// The e.
- private void OnProgressChanged(object sender, double e)
- {
- e = Math.Min(e, 100);
-
- CurrentProgress = e;
-
- TaskProgress?.Invoke(this, new GenericEventArgs(e));
- }
-
- ///
- /// Stops the task if it is currently executing.
- ///
- /// Cannot cancel a Task unless it is in the Running state.
- public void Cancel()
- {
- if (State != TaskState.Running)
- {
- throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state.");
- }
-
- CancelIfRunning();
- }
-
- ///
- /// Cancels if running.
- ///
- public void CancelIfRunning()
- {
- if (State == TaskState.Running)
- {
- _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name);
- CurrentCancellationTokenSource.Cancel();
- }
- }
-
- ///
- /// Gets the scheduled tasks configuration directory.
- ///
- /// System.String.
- private string GetScheduledTasksConfigurationDirectory()
- {
- return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
- }
-
- ///
- /// Gets the scheduled tasks data directory.
- ///
- /// System.String.
- private string GetScheduledTasksDataDirectory()
- {
- return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks");
- }
-
- ///
- /// Gets the history file path.
- ///
- /// The history file path.
- private string GetHistoryFilePath()
- {
- return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js");
- }
-
- ///
- /// Gets the configuration file path.
- ///
- /// System.String.
- private string GetConfigurationFilePath()
- {
- return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js");
- }
-
- ///
- /// Loads the triggers.
- ///
- /// IEnumerable{BaseTaskTrigger}.
- private Tuple[] 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(i, GetTrigger(i))).ToArray();
- }
-
- private TaskTriggerInfo[] LoadTriggerSettings()
- {
- string path = GetConfigurationFilePath();
- TaskTriggerInfo[] list = null;
- if (File.Exists(path))
- {
- var bytes = File.ReadAllBytes(path);
- list = JsonSerializer.Deserialize(bytes, _jsonOptions);
- }
-
- // Return defaults if file doesn't exist.
- return list ?? GetDefaultTriggers();
- }
-
- private TaskTriggerInfo[] GetDefaultTriggers()
- {
- try
- {
- return ScheduledTask.GetDefaultTriggers().ToArray();
- }
- catch
- {
- return
- [
- new()
- {
- IntervalTicks = TimeSpan.FromDays(1).Ticks,
- Type = TaskTriggerInfoType.IntervalTrigger
- }
- ];
- }
- }
-
- ///
- /// Saves the triggers.
- ///
- /// The triggers.
- private void SaveTriggers(TaskTriggerInfo[] triggers)
- {
- var path = GetConfigurationFilePath();
-
- 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);
- }
-
- ///
- /// Called when [task completed].
- ///
- /// The start time.
- /// The end time.
- /// The status.
- /// The exception.
- private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex)
- {
- var elapsedTime = endTime - startTime;
-
- _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
-
- var result = new TaskResult
- {
- StartTimeUtc = startTime,
- EndTimeUtc = endTime,
- Status = status,
- Name = Name,
- Id = Id
- };
-
- result.Key = ScheduledTask.Key;
-
- if (ex is not null)
- {
- result.ErrorMessage = ex.Message;
- result.LongErrorMessage = ex.StackTrace;
- }
-
- LastExecutionResult = result;
-
- ((TaskManager)_taskManager).OnTaskCompleted(this, result);
- }
-
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- DisposeTriggers();
-
- var wasRunning = State == TaskState.Running;
- var startTime = CurrentExecutionStartTime;
-
- var token = CurrentCancellationTokenSource;
- if (token is not null)
- {
- try
- {
- _logger.LogInformation("{Name}: Cancelling", Name);
- token.Cancel();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error calling CancellationToken.Cancel();");
- }
- }
-
- var task = _currentTask;
- if (task is not null)
- {
- 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);
+ _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
}
}
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error calling Task.WaitAll();");
- }
- }
- if (token is not null)
- {
- try
- {
- _logger.LogDebug("{Name}: Disposing CancellationToken", Name);
- token.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error calling CancellationToken.Dispose();");
- }
- }
-
- if (wasRunning)
- {
- OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null);
+ _readFromFile = true;
}
}
+
+ return _lastExecutionResult;
}
- ///
- /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger.
- ///
- /// The info.
- /// BaseTaskTrigger.
- /// Invalid trigger type: + info.Type.
- private ITaskTrigger GetTrigger(TaskTriggerInfo info)
+ private set
{
- var options = new TaskOptions
- {
- MaxRuntimeTicks = info.MaxRuntimeTicks
- };
+ _lastExecutionResult = value;
- if (info.Type == TaskTriggerInfoType.DailyTrigger)
- {
- if (!info.TimeOfDayTicks.HasValue)
- {
- throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
- }
+ var path = GetHistoryFilePath();
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
- return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options);
+ 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);
+ }
+ }
+ }
+
+ ///
+ public string Name => ScheduledTask.Name;
+
+ ///
+ public string Description => ScheduledTask.Description;
+
+ ///
+ public string Category => ScheduledTask.Category;
+
+ ///
+ /// Gets or sets the current cancellation token.
+ ///
+ /// The current cancellation token source.
+ private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
+
+ ///
+ /// Gets or sets the current execution start time.
+ ///
+ /// The current execution start time.
+ private DateTime CurrentExecutionStartTime { get; set; }
+
+ ///
+ public TaskState State
+ {
+ get
+ {
+ if (CurrentCancellationTokenSource is not null)
+ {
+ return CurrentCancellationTokenSource.IsCancellationRequested
+ ? TaskState.Cancelling
+ : TaskState.Running;
}
- if (info.Type == TaskTriggerInfoType.WeeklyTrigger)
+ return TaskState.Idle;
+ }
+ }
+
+ ///
+ public double? CurrentProgress { get; private set; }
+
+ ///
+ /// Gets or sets the triggers that define when the task will run.
+ ///
+ /// The triggers.
+ private Tuple[] InternalTriggers
+ {
+ get => _triggers;
+ set
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ // Cleanup current triggers
+ if (_triggers is not null)
{
- if (!info.TimeOfDayTicks.HasValue)
- {
- throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
- }
-
- if (!info.DayOfWeek.HasValue)
- {
- throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info));
- }
-
- return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options);
+ DisposeTriggers();
}
- if (info.Type == TaskTriggerInfoType.IntervalTrigger)
- {
- if (!info.IntervalTicks.HasValue)
- {
- throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info));
- }
+ _triggers = value.ToArray();
- return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options);
- }
+ ReloadTriggerEvents(false);
+ }
+ }
- if (info.Type == TaskTriggerInfoType.StartupTrigger)
- {
- return new StartupTrigger(options);
- }
-
- throw new ArgumentException("Unrecognized trigger type: " + info.Type);
+ ///
+ public IReadOnlyList Triggers
+ {
+ get
+ {
+ return Array.ConvertAll(InternalTriggers, i => i.Item1);
}
- ///
- /// Disposes each trigger.
- ///
- private void DisposeTriggers()
+ set
{
- foreach (var triggerInfo in InternalTriggers)
+ 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();
+
+ SaveTriggers(triggerList);
+
+ InternalTriggers = Array.ConvertAll(triggerList, i => new Tuple(i, GetTrigger(i)));
+ }
+ }
+
+ ///
+ public string Id
+ {
+ get
+ {
+ return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ }
+ }
+
+ private void InitTriggerEvents()
+ {
+ _triggers = LoadTriggers();
+ ReloadTriggerEvents(true);
+ }
+
+ ///
+ public void ReloadTriggerEvents()
+ {
+ ReloadTriggerEvents(false);
+ }
+
+ ///
+ /// Reloads the trigger events.
+ ///
+ /// if set to true [is application startup].
+ private void ReloadTriggerEvents(bool isApplicationStartup)
+ {
+ foreach (var triggerInfo in InternalTriggers)
+ {
+ var trigger = triggerInfo.Item2;
+
+ trigger.Stop();
+
+ trigger.Triggered -= OnTriggerTriggered;
+ trigger.Triggered += OnTriggerTriggered;
+ trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup);
+ }
+ }
+
+ ///
+ /// Handles the Triggered event of the trigger control.
+ ///
+ /// The source of the event.
+ /// The instance containing the event data.
+ private async void OnTriggerTriggered(object sender, EventArgs e)
+ {
+ var trigger = (ITaskTrigger)sender;
+
+ if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled)
+ {
+ return;
+ }
+
+ _logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name);
+
+ trigger.Stop();
+
+ _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions);
+
+ await Task.Delay(1000).ConfigureAwait(false);
+
+ trigger.Start(LastExecutionResult, _logger, Name, false);
+ }
+
+ ///
+ /// Executes the task.
+ ///
+ /// Task options.
+ /// Task.
+ /// Cannot execute a Task that is already running.
+ public async Task Execute(TaskOptions options)
+ {
+ var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false));
+
+ _currentTask = task;
+
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ finally
+ {
+ _currentTask = null;
+ GC.Collect();
+ }
+ }
+
+ 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");
+ }
+
+ var progress = new Progress();
+
+ CurrentCancellationTokenSource = new CancellationTokenSource();
+
+ _logger.LogDebug("Executing {0}", Name);
+
+ ((TaskManager)_taskManager).OnTaskExecuting(this);
+
+ progress.ProgressChanged += OnProgressChanged;
+
+ TaskCompletionStatus status;
+ CurrentExecutionStartTime = DateTime.UtcNow;
+
+ Exception failureException = null;
+
+ try
+ {
+ if (options is not null && options.MaxRuntimeTicks.HasValue)
{
- var trigger = triggerInfo.Item2;
- trigger.Triggered -= OnTriggerTriggered;
- trigger.Stop();
- if (trigger is IDisposable disposable)
+ CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value));
+ }
+
+ await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false);
+
+ status = TaskCompletionStatus.Completed;
+ }
+ catch (OperationCanceledException)
+ {
+ status = TaskCompletionStatus.Cancelled;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error executing Scheduled Task");
+
+ failureException = ex;
+
+ status = TaskCompletionStatus.Failed;
+ }
+
+ var startTime = CurrentExecutionStartTime;
+ var endTime = DateTime.UtcNow;
+
+ progress.ProgressChanged -= OnProgressChanged;
+ CurrentCancellationTokenSource.Dispose();
+ CurrentCancellationTokenSource = null;
+ CurrentProgress = null;
+
+ OnTaskCompleted(startTime, endTime, status, failureException);
+ }
+
+ ///
+ /// Progress_s the progress changed.
+ ///
+ /// The sender.
+ /// The e.
+ private void OnProgressChanged(object sender, double e)
+ {
+ e = Math.Min(e, 100);
+
+ CurrentProgress = e;
+
+ TaskProgress?.Invoke(this, new GenericEventArgs(e));
+ }
+
+ ///
+ /// Stops the task if it is currently executing.
+ ///
+ /// Cannot cancel a Task unless it is in the Running state.
+ public void Cancel()
+ {
+ if (State != TaskState.Running)
+ {
+ throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state.");
+ }
+
+ CancelIfRunning();
+ }
+
+ ///
+ /// Cancels if running.
+ ///
+ public void CancelIfRunning()
+ {
+ if (State == TaskState.Running)
+ {
+ _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name);
+ CurrentCancellationTokenSource.Cancel();
+ }
+ }
+
+ ///
+ /// Gets the scheduled tasks configuration directory.
+ ///
+ /// System.String.
+ private string GetScheduledTasksConfigurationDirectory()
+ {
+ return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
+ }
+
+ ///
+ /// Gets the scheduled tasks data directory.
+ ///
+ /// System.String.
+ private string GetScheduledTasksDataDirectory()
+ {
+ return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks");
+ }
+
+ ///
+ /// Gets the history file path.
+ ///
+ /// The history file path.
+ private string GetHistoryFilePath()
+ {
+ return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js");
+ }
+
+ ///
+ /// Gets the configuration file path.
+ ///
+ /// System.String.
+ private string GetConfigurationFilePath()
+ {
+ return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js");
+ }
+
+ ///
+ /// Loads the triggers.
+ ///
+ /// IEnumerable{BaseTaskTrigger}.
+ private Tuple[] 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(i, GetTrigger(i))).ToArray();
+ }
+
+ private TaskTriggerInfo[] LoadTriggerSettings()
+ {
+ string path = GetConfigurationFilePath();
+ TaskTriggerInfo[] list = null;
+ if (File.Exists(path))
+ {
+ var bytes = File.ReadAllBytes(path);
+ list = JsonSerializer.Deserialize(bytes, _jsonOptions);
+ }
+
+ // Return defaults if file doesn't exist.
+ return list ?? GetDefaultTriggers();
+ }
+
+ private TaskTriggerInfo[] GetDefaultTriggers()
+ {
+ try
+ {
+ return ScheduledTask.GetDefaultTriggers().ToArray();
+ }
+ catch
+ {
+ return
+ [
+ new()
{
- disposable.Dispose();
+ IntervalTicks = TimeSpan.FromDays(1).Ticks,
+ Type = TaskTriggerInfoType.IntervalTrigger
}
+ ];
+ }
+ }
+
+ ///
+ /// Saves the triggers.
+ ///
+ /// The triggers.
+ private void SaveTriggers(TaskTriggerInfo[] triggers)
+ {
+ var path = GetConfigurationFilePath();
+
+ 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);
+ }
+
+ ///
+ /// Called when [task completed].
+ ///
+ /// The start time.
+ /// The end time.
+ /// The status.
+ /// The exception.
+ private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex)
+ {
+ var elapsedTime = endTime - startTime;
+
+ _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
+
+ var result = new TaskResult
+ {
+ StartTimeUtc = startTime,
+ EndTimeUtc = endTime,
+ Status = status,
+ Name = Name,
+ Id = Id
+ };
+
+ result.Key = ScheduledTask.Key;
+
+ if (ex is not null)
+ {
+ result.ErrorMessage = ex.Message;
+ result.LongErrorMessage = ex.StackTrace;
+ }
+
+ LastExecutionResult = result;
+
+ ((TaskManager)_taskManager).OnTaskCompleted(this, result);
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ DisposeTriggers();
+
+ var wasRunning = State == TaskState.Running;
+ var startTime = CurrentExecutionStartTime;
+
+ var token = CurrentCancellationTokenSource;
+ if (token is not null)
+ {
+ try
+ {
+ _logger.LogInformation("{Name}: Cancelling", Name);
+ token.Cancel();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error calling CancellationToken.Cancel();");
+ }
+ }
+
+ var task = _currentTask;
+ if (task is not null)
+ {
+ 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();");
+ }
+ }
+
+ if (token is not null)
+ {
+ try
+ {
+ _logger.LogDebug("{Name}: Disposing CancellationToken", Name);
+ token.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error calling CancellationToken.Dispose();");
+ }
+ }
+
+ if (wasRunning)
+ {
+ OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null);
+ }
+ }
+ }
+
+ ///
+ /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger.
+ ///
+ /// The info.
+ /// BaseTaskTrigger.
+ /// Invalid trigger type: + info.Type.
+ private ITaskTrigger GetTrigger(TaskTriggerInfo info)
+ {
+ var options = new TaskOptions
+ {
+ MaxRuntimeTicks = info.MaxRuntimeTicks
+ };
+
+ if (info.Type == TaskTriggerInfoType.DailyTrigger)
+ {
+ if (!info.TimeOfDayTicks.HasValue)
+ {
+ throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
+ }
+
+ return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options);
+ }
+
+ if (info.Type == TaskTriggerInfoType.WeeklyTrigger)
+ {
+ if (!info.TimeOfDayTicks.HasValue)
+ {
+ throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info));
+ }
+
+ if (!info.DayOfWeek.HasValue)
+ {
+ throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info));
+ }
+
+ return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options);
+ }
+
+ if (info.Type == TaskTriggerInfoType.IntervalTrigger)
+ {
+ if (!info.IntervalTicks.HasValue)
+ {
+ throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info));
+ }
+
+ return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options);
+ }
+
+ if (info.Type == TaskTriggerInfoType.StartupTrigger)
+ {
+ return new StartupTrigger(options);
+ }
+
+ throw new ArgumentException("Unrecognized trigger type: " + info.Type);
+ }
+
+ ///
+ /// Disposes each trigger.
+ ///
+ private void DisposeTriggers()
+ {
+ foreach (var triggerInfo in InternalTriggers)
+ {
+ var trigger = triggerInfo.Item2;
+ trigger.Triggered -= OnTriggerTriggered;
+ trigger.Stop();
+ if (trigger is IDisposable disposable)
+ {
+ disposable.Dispose();
}
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
index a5e4104ffe..4ec2c9c786 100644
--- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
+++ b/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;
+
+///
+/// Class TaskManager.
+///
+public class TaskManager : ITaskManager
{
///
- /// Class TaskManager.
+ /// The _task queue.
///
- public class TaskManager : ITaskManager
+ private readonly ConcurrentQueue> _taskQueue =
+ new ConcurrentQueue>();
+
+ private readonly IApplicationPaths _applicationPaths;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The application paths.
+ /// The logger.
+ public TaskManager(
+ IApplicationPaths applicationPaths,
+ ILogger logger)
{
- ///
- /// The _task queue.
- ///
- private readonly ConcurrentQueue> _taskQueue =
- new ConcurrentQueue>();
+ _applicationPaths = applicationPaths;
+ _logger = logger;
- private readonly IApplicationPaths _applicationPaths;
- private readonly ILogger _logger;
+ ScheduledTasks = [];
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The application paths.
- /// The logger.
- public TaskManager(
- IApplicationPaths applicationPaths,
- ILogger logger)
- {
- _applicationPaths = applicationPaths;
- _logger = logger;
+ ///
+ public event EventHandler>? TaskExecuting;
- ScheduledTasks = Array.Empty();
- }
+ ///
+ public event EventHandler? TaskCompleted;
- ///
- public event EventHandler>? TaskExecuting;
+ ///
+ public IReadOnlyList ScheduledTasks { get; private set; }
- ///
- public event EventHandler? TaskCompleted;
+ ///
+ public void CancelIfRunningAndQueue(TaskOptions options)
+ where T : IScheduledTask
+ {
+ var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
+ ((ScheduledTaskWorker)task).CancelIfRunning();
- ///
- public IReadOnlyList ScheduledTasks { get; private set; }
+ QueueScheduledTask(options);
+ }
- ///
- public void CancelIfRunningAndQueue(TaskOptions options)
+ ///
+ public void CancelIfRunningAndQueue()
where T : IScheduledTask
+ {
+ CancelIfRunningAndQueue(new TaskOptions());
+ }
+
+ ///
+ public void CancelIfRunning()
+ where T : IScheduledTask
+ {
+ var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
+ ((ScheduledTaskWorker)task).CancelIfRunning();
+ }
+
+ ///
+ public void QueueScheduledTask(TaskOptions options)
+ where T : IScheduledTask
+ {
+ var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+
+ if (scheduledTask is null)
{
- var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
- ((ScheduledTaskWorker)task).CancelIfRunning();
-
- QueueScheduledTask(options);
+ _logger.LogError("Unable to find scheduled task of type {Type} in QueueScheduledTask.", typeof(T).Name);
}
-
- ///
- public void CancelIfRunningAndQueue()
- where T : IScheduledTask
+ else
{
- CancelIfRunningAndQueue(new TaskOptions());
+ QueueScheduledTask(scheduledTask, options);
}
+ }
- ///
- public void CancelIfRunning()
- where T : IScheduledTask
- {
- var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
- ((ScheduledTaskWorker)task).CancelIfRunning();
- }
+ ///
+ public void QueueScheduledTask()
+ where T : IScheduledTask
+ {
+ QueueScheduledTask(new TaskOptions());
+ }
- ///
- public void QueueScheduledTask(TaskOptions options)
- where T : IScheduledTask
- {
- var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+ ///
+ public void QueueIfNotRunning()
+ where T : IScheduledTask
+ {
+ var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
- if (scheduledTask is null)
- {
- _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
- }
- else
- {
- QueueScheduledTask(scheduledTask, options);
- }
- }
-
- ///
- public void QueueScheduledTask()
- where T : IScheduledTask
+ if (task.State != TaskState.Running)
{
QueueScheduledTask(new TaskOptions());
}
+ }
- ///
- public void QueueIfNotRunning()
- where T : IScheduledTask
+ ///
+ public void Execute()
+ where T : IScheduledTask
+ {
+ var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+
+ if (scheduledTask is null)
{
- var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
-
- if (task.State != TaskState.Running)
- {
- QueueScheduledTask(new TaskOptions());
- }
+ _logger.LogError("Unable to find scheduled task of type {Type} in Execute.", typeof(T).Name);
}
-
- ///
- public void Execute()
- where T : IScheduledTask
+ else
{
- var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+ var type = scheduledTask.ScheduledTask.GetType();
- 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();
-
- _logger.LogDebug("Queuing task {0}", type.Name);
-
- lock (_taskQueue)
- {
- if (scheduledTask.State == TaskState.Idle)
- {
- Execute(scheduledTask, new TaskOptions());
- }
- }
- }
- }
-
- ///
- public void QueueScheduledTask(IScheduledTask task, TaskOptions options)
- {
- var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType());
-
- if (scheduledTask is null)
- {
- _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
- }
- else
- {
- QueueScheduledTask(scheduledTask, options);
- }
- }
-
- ///
- /// Queues the scheduled task.
- ///
- /// The task.
- /// The task options.
- private void QueueScheduledTask(IScheduledTaskWorker task, TaskOptions options)
- {
- var type = task.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, options));
}
}
+ }
- ///
- public void AddTasks(IEnumerable tasks)
+ ///
+ public void QueueScheduledTask(IScheduledTask task, TaskOptions options)
+ {
+ var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType());
+
+ if (scheduledTask is null)
{
- var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger));
-
- ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
+ _logger.LogError("Unable to find scheduled task of type {Type} in QueueScheduledTask.", task.GetType().Name);
}
-
- ///
- public void Dispose()
+ else
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ QueueScheduledTask(scheduledTask, options);
}
+ }
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool dispose)
+ ///
+ /// Queues the scheduled task.
+ ///
+ /// The task.
+ /// The task options.
+ private void QueueScheduledTask(IScheduledTaskWorker task, TaskOptions options)
+ {
+ var type = task.ScheduledTask.GetType();
+
+ _logger.LogDebug("Queuing task {Name}", type.Name);
+
+ lock (_taskQueue)
{
- foreach (var task in ScheduledTasks)
+ if (task.State == TaskState.Idle)
{
- task.Dispose();
+ Execute(task, options);
+ return;
}
+
+ _taskQueue.Enqueue(new Tuple(type, options));
}
+ }
- ///
- public void Cancel(IScheduledTaskWorker task)
+ ///
+ public void AddTasks(IEnumerable tasks)
+ {
+ var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger));
+
+ ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool dispose)
+ {
+ foreach (var task in ScheduledTasks)
{
- ((ScheduledTaskWorker)task).Cancel();
+ task.Dispose();
}
+ }
- ///
- public Task Execute(IScheduledTaskWorker task, TaskOptions options)
+ ///
+ public void Cancel(IScheduledTaskWorker task)
+ {
+ ((ScheduledTaskWorker)task).Cancel();
+ }
+
+ ///
+ public Task Execute(IScheduledTaskWorker task, TaskOptions options)
+ {
+ return ((ScheduledTaskWorker)task).Execute(options);
+ }
+
+ ///
+ /// Called when [task executing].
+ ///
+ /// The task.
+ internal void OnTaskExecuting(IScheduledTaskWorker task)
+ {
+ TaskExecuting?.Invoke(this, new GenericEventArgs(task));
+ }
+
+ ///
+ /// Called when [task completed].
+ ///
+ /// The task.
+ /// The result.
+ internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result)
+ {
+ TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result));
+
+ ExecuteQueuedTasks();
+ }
+
+ ///
+ /// Executes the queued tasks.
+ ///
+ private void ExecuteQueuedTasks()
+ {
+ lock (_taskQueue)
{
- return ((ScheduledTaskWorker)task).Execute(options);
- }
+ var list = new List>();
- ///
- /// Called when [task executing].
- ///
- /// The task.
- internal void OnTaskExecuting(IScheduledTaskWorker task)
- {
- TaskExecuting?.Invoke(this, new GenericEventArgs(task));
- }
-
- ///
- /// Called when [task completed].
- ///
- /// The task.
- /// The result.
- internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result)
- {
- TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result));
-
- ExecuteQueuedTasks();
- }
-
- ///
- /// Executes the queued tasks.
- ///
- private void ExecuteQueuedTasks()
- {
- lock (_taskQueue)
+ while (_taskQueue.TryDequeue(out var item))
{
- var list = new List>();
-
- 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)
+ foreach (var enqueuedType in list)
+ {
+ var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
+
+ if (scheduledTask.State == TaskState.Idle)
{
- var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
-
- if (scheduledTask.State == TaskState.Idle)
- {
- Execute(scheduledTask, enqueuedType.Item2);
- }
+ Execute(scheduledTask, enqueuedType.Item2);
}
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
index 8d1d509ff7..ef005bfaa5 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
@@ -156,14 +156,11 @@ public partial class AudioNormalizationTask : IScheduledTask
///
public IEnumerable 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 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)
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index b76fdeeb04..f81309560e 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/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;
+
+///
+/// Class ChapterImagesTask.
+///
+public class ChapterImagesTask : IScheduledTask
{
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IChapterManager _chapterManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
+
///
- /// Class ChapterImagesTask.
+ /// Initializes a new instance of the class.
///
- public class ChapterImagesTask : IScheduledTask
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public ChapterImagesTask(
+ ILogger logger,
+ ILibraryManager libraryManager,
+ IApplicationPaths appPaths,
+ IChapterManager chapterManager,
+ IFileSystem fileSystem,
+ ILocalizationManager localization)
{
- private readonly ILogger _logger;
- private readonly ILibraryManager _libraryManager;
- private readonly IApplicationPaths _appPaths;
- private readonly IChapterManager _chapterManager;
- private readonly IFileSystem _fileSystem;
- private readonly ILocalizationManager _localization;
+ _logger = logger;
+ _libraryManager = libraryManager;
+ _appPaths = appPaths;
+ _chapterManager = chapterManager;
+ _fileSystem = fileSystem;
+ _localization = localization;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- public ChapterImagesTask(
- ILogger logger,
- ILibraryManager libraryManager,
- IApplicationPaths appPaths,
- IChapterManager chapterManager,
- IFileSystem fileSystem,
- ILocalizationManager localization)
+ ///
+ public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
+
+ ///
+ public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
+
+ ///
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+
+ ///
+ public string Key => "RefreshChapterImages";
+
+ ///
+ public IEnumerable GetDefaultTriggers()
+ {
+ yield return new TaskTriggerInfo
{
- _logger = logger;
- _libraryManager = libraryManager;
- _appPaths = appPaths;
- _chapterManager = chapterManager;
- _fileSystem = fileSystem;
- _localization = localization;
- }
+ Type = TaskTriggerInfoType.DailyTrigger,
+ TimeOfDayTicks = TimeSpan.FromHours(2).Ticks,
+ MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks
+ };
+ }
- ///
- public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
-
- ///
- public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
-
- ///
- public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
-
- ///
- public string Key => "RefreshChapterImages";
-
- ///
- public IEnumerable GetDefaultTriggers()
+ ///
+ public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
+ {
+ var videos = _libraryManager.GetItemList(new InternalItemsQuery
{
- return
- [
- new TaskTriggerInfo
- {
- Type = TaskTriggerInfoType.DailyTrigger,
- TimeOfDayTicks = TimeSpan.FromHours(2).Ticks,
- MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks
- }
- ];
- }
-
- ///
- public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
- {
- 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