Merge pull request #14028 from Shadowghost/cleanup-tasks

Cleanup Tasks and Validators
This commit is contained in:
Bond-009 2025-05-02 14:47:53 +02:00 committed by GitHub
commit b4a58ee13a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 2460 additions and 2502 deletions

View File

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

View File

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

View File

@ -9,149 +9,146 @@ using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; 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> /// <summary>
/// Class CollectionPostScanTask. /// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class.
/// </summary> /// </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; _libraryManager = libraryManager;
private readonly ICollectionManager _collectionManager; _collectionManager = collectionManager;
private readonly ILogger<CollectionPostScanTask> _logger; _logger = logger;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class. /// Runs the specified progress.
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param> /// <param name="progress">The progress.</param>
/// <param name="collectionManager">The collection manager.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="logger">The logger.</param> /// <returns>Task.</returns>
public CollectionPostScanTask( public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
ILibraryManager libraryManager, {
ICollectionManager collectionManager, var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>();
ILogger<CollectionPostScanTask> logger)
foreach (var library in _libraryManager.RootFolder.Children)
{ {
_libraryManager = libraryManager; if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
_collectionManager = collectionManager; {
_logger = logger; 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<Guid> { movie.Id };
}
}
}
if (movies.Count < pagesize)
{
break;
}
startIndex += pagesize;
}
} }
/// <summary> var numComplete = 0;
/// Runs the specified progress. var count = collectionNameMoviesMap.Count;
/// </summary>
/// <param name="progress">The progress.</param> if (count == 0)
/// <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)
{
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<Guid> { 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); 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);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -9,119 +9,114 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators namespace Emby.Server.Implementations.Library.Validators;
/// <summary>
/// Class PeopleValidator.
/// </summary>
public class PeopleValidator
{ {
/// <summary> /// <summary>
/// Class PeopleValidator. /// The _library manager.
/// </summary> /// </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> _libraryManager = libraryManager;
/// The _library manager. _logger = logger;
/// </summary> _fileSystem = fileSystem;
private readonly ILibraryManager _libraryManager; }
/// <summary> /// <summary>
/// The _logger. /// Validates the people.
/// </summary> /// </summary>
private readonly ILogger _logger; /// <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());
private readonly IFileSystem _fileSystem; var numComplete = 0;
/// <summary> var numPeople = people.Count;
/// Initializes a new instance of the <see cref="PeopleValidator" /> class.
/// </summary> _logger.LogDebug("Will refresh {Amount} people", numPeople);
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param> foreach (var person in people)
/// <param name="fileSystem">The file system.</param>
public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
{ {
_libraryManager = libraryManager; cancellationToken.ThrowIfCancellationRequested();
_logger = logger;
_fileSystem = fileSystem;
}
/// <summary> try
/// 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 numPeople = people.Count;
_logger.LogDebug("Will refresh {0} people", numPeople);
foreach (var person in people)
{ {
cancellationToken.ThrowIfCancellationRequested(); var item = _libraryManager.GetPerson(person);
if (item is null)
try
{ {
var item = _libraryManager.GetPerson(person); _logger.LogWarning("Failed to get person: {Name}", person);
if (item is null) continue;
{
_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);
} }
// Update progress var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
numComplete++; {
double percent = numComplete; ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
percent /= numPeople; 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 // Update progress
{ numComplete++;
IncludeItemTypes = [BaseItemKind.Person], double percent = numComplete;
IsDeadPerson = true, percent /= numPeople;
IsLocked = false
});
foreach (var item in deadEntities) progress.Report(100 * percent);
{
_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");
} }
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");
} }
} }

View File

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

View File

@ -8,98 +8,97 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators namespace Emby.Server.Implementations.Library.Validators;
/// <summary>
/// Class StudiosValidator.
/// </summary>
public class StudiosValidator
{ {
/// <summary> /// <summary>
/// Class StudiosValidator. /// The library manager.
/// </summary> /// </summary>
public class StudiosValidator private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
/// <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)
{ {
/// <summary> _libraryManager = libraryManager;
/// The library manager. _logger = logger;
/// </summary> _itemRepo = itemRepo;
private readonly ILibraryManager _libraryManager; }
private readonly IItemRepository _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> var numComplete = 0;
/// The logger. var count = names.Count;
/// </summary>
private readonly ILogger<StudiosValidator> _logger;
/// <summary> foreach (var name in names)
/// 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; try
_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();
var numComplete = 0;
var count = names.Count;
foreach (var name in names)
{ {
try var item = _libraryManager.GetStudio(name);
{
var item = _libraryManager.GetStudio(name);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// Don't clutter the log // Don't clutter the log
throw; throw;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error refreshing {StudioName}", name); _logger.LogError(ex, "Error refreshing {StudioName}", name);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
} }
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery numComplete++;
{ double percent = numComplete;
IncludeItemTypes = new[] { BaseItemKind.Studio }, percent /= count;
IsDeadStudio = true, percent *= 100;
IsLocked = false
});
foreach (var item in deadEntities) progress.Report(percent);
{
_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);
} }
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);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,93 +9,93 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks; 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> /// <summary>
/// Deletes old log files. /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
/// </summary> /// </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; _configurationManager = configurationManager;
private readonly IFileSystem _fileSystem; _fileSystem = fileSystem;
private readonly ILocalizationManager _localization; _localization = localization;
}
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class. public string Name => _localization.GetLocalizedString("TaskCleanLogs");
/// </summary>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> /// <inheritdoc />
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> public string Description => string.Format(
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> CultureInfo.InvariantCulture,
public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) _localization.GetLocalizedString("TaskCleanLogsDescription"),
_configurationManager.CommonConfiguration.LogFileRetentionDays);
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
/// <inheritdoc />
public string Key => "CleanLogFiles";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
yield return new TaskTriggerInfo
{ {
_configurationManager = configurationManager; Type = TaskTriggerInfoType.IntervalTrigger,
_fileSystem = fileSystem; IntervalTicks = TimeSpan.FromHours(24).Ticks
_localization = localization; };
}
/// <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();
var index = 0;
foreach (var file in filesToDelete)
{
double percent = index / (double)filesToDelete.Count;
progress.Report(100 * percent);
cancellationToken.ThrowIfCancellationRequested();
_fileSystem.DeleteFile(file.FullName);
index++;
} }
/// <inheritdoc /> progress.Report(100);
public string Name => _localization.GetLocalizedString("TaskCleanLogs");
/// <inheritdoc /> return Task.CompletedTask;
public string Description => string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("TaskCleanLogsDescription"),
_configurationManager.CommonConfiguration.LogFileRetentionDays);
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
/// <inheritdoc />
public string Key => "CleanLogFiles";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return
[
new TaskTriggerInfo { 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);
// 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;
foreach (var file in filesToDelete)
{
double percent = index / (double)filesToDelete.Count;
progress.Report(100 * percent);
cancellationToken.ThrowIfCancellationRequested();
_fileSystem.DeleteFile(file.FullName);
index++;
}
progress.Report(100);
return Task.CompletedTask;
}
} }
} }

View File

@ -10,118 +10,115 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; 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> /// <summary>
/// Deletes all transcoding temp files. /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class.
/// </summary> /// </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; _logger = logger;
private readonly IConfigurationManager _configurationManager; _fileSystem = fileSystem;
private readonly IFileSystem _fileSystem; _configurationManager = configurationManager;
private readonly ILocalizationManager _localization; _localization = localization;
}
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class. public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{DeleteTranscodeFileTask}"/> interface.</param> /// <inheritdoc />
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> /// <inheritdoc />
public DeleteTranscodeFileTask( public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
ILogger<DeleteTranscodeFileTask> logger,
IFileSystem fileSystem, /// <inheritdoc />
IConfigurationManager configurationManager, public string Key => "DeleteTranscodeFiles";
ILocalizationManager localization)
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
yield return new TaskTriggerInfo
{ {
_logger = logger; Type = TaskTriggerInfoType.StartupTrigger
_fileSystem = fileSystem; };
_configurationManager = configurationManager;
_localization = localization; yield return new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromHours(24).Ticks
};
}
/// <inheritdoc />
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
var minDateModified = DateTime.UtcNow.AddDays(-1);
progress.Report(50);
DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken);
return Task.CompletedTask;
}
/// <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();
var index = 0;
foreach (var file in filesToDelete)
{
double percent = index;
percent /= filesToDelete.Count;
progress.Report(100 * percent);
cancellationToken.ThrowIfCancellationRequested();
FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
index++;
} }
/// <inheritdoc /> FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
/// <inheritdoc /> progress.Report(100);
public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
/// <inheritdoc />
public string Key => "DeleteTranscodeFiles";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return
[
new TaskTriggerInfo
{
Type = TaskTriggerInfoType.StartupTrigger
},
new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromHours(24).Ticks
}
];
}
/// <inheritdoc />
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
var minDateModified = DateTime.UtcNow.AddDays(-1);
progress.Report(50);
DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken);
return Task.CompletedTask;
}
/// <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();
var index = 0;
foreach (var file in filesToDelete)
{
double percent = index;
percent /= filesToDelete.Count;
progress.Report(100 * percent);
cancellationToken.ThrowIfCancellationRequested();
FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
index++;
}
FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
progress.Report(100);
}
} }
} }

View File

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

View File

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

View File

@ -6,68 +6,64 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks; 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> /// <summary>
/// Class PeopleValidationTask. /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
/// </summary> /// </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; _libraryManager = libraryManager;
private readonly ILocalizationManager _localization; _localization = localization;
}
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="PeopleValidationTask" /> class. public string Name => _localization.GetLocalizedString("TaskRefreshPeople");
/// </summary>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <inheritdoc />
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription");
public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization)
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public string Key => "RefreshPeople";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => 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()
{
yield return new TaskTriggerInfo
{ {
_libraryManager = libraryManager; Type = TaskTriggerInfoType.IntervalTrigger,
_localization = localization; IntervalTicks = TimeSpan.FromDays(7).Ticks
} };
}
/// <inheritdoc /> /// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskRefreshPeople"); public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
/// <inheritdoc /> return _libraryManager.ValidatePeopleAsync(progress, cancellationToken);
public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription");
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
/// <inheritdoc />
public string Key => "RefreshPeople";
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => 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()
{
return new[]
{
new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromDays(7).Ticks
}
};
}
/// <inheritdoc />
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
return _libraryManager.ValidatePeopleAsync(progress, cancellationToken);
}
} }
} }

View File

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

View File

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

View File

@ -3,85 +3,84 @@ using System.Threading;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; 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> /// <summary>
/// Represents a task trigger that fires everyday. /// Initializes a new instance of the <see cref="DailyTrigger"/> class.
/// </summary> /// </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; _timeOfDay = timeOfDay;
private Timer? _timer; TaskOptions = taskOptions;
private bool _disposed = false; }
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="DailyTrigger"/> class. public event EventHandler<EventArgs>? Triggered;
/// </summary>
/// <param name="timeofDay">The time of day to trigger the task to run.</param> /// <inheritdoc />
/// <param name="taskOptions">The options of this task.</param> public TaskOptions TaskOptions { get; }
public DailyTrigger(TimeSpan timeofDay, TaskOptions taskOptions)
/// <inheritdoc />
public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
var now = DateTime.Now;
var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date;
triggerDate = triggerDate.Add(_timeOfDay);
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);
_timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <inheritdoc />
public void Stop()
{
DisposeTimer();
}
/// <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);
}
/// <inheritdoc />
public void Dispose()
{
if (_disposed)
{ {
_timeOfDay = timeofDay; return;
TaskOptions = taskOptions;
} }
/// <inheritdoc /> DisposeTimer();
public event EventHandler<EventArgs>? Triggered;
/// <inheritdoc /> _disposed = true;
public TaskOptions TaskOptions { get; }
/// <inheritdoc />
public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
var now = DateTime.Now;
var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date;
triggerDate = triggerDate.Add(_timeOfDay);
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);
_timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <inheritdoc />
public void Stop()
{
DisposeTimer();
}
/// <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);
}
/// <inheritdoc />
public void Dispose()
{
if (_disposed)
{
return;
}
DisposeTimer();
_disposed = true;
}
} }
} }

View File

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

View File

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

View File

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