245 lines
9.2 KiB
C#
Raw Permalink Normal View History

2024-10-25 14:31:50 -04:00
// Copyright (C) 2024 Intro-Skipper contributors <intro-skipper.org>
// SPDX-License-Identifier: GPL-3.0-only.
2024-10-25 14:15:12 -04:00
2022-05-01 00:33:22 -05:00
using System;
using System.Collections.Generic;
2024-03-22 23:41:58 +01:00
using System.Threading;
2022-05-01 00:33:22 -05:00
using System.Threading.Tasks;
using IntroSkipper.Configuration;
using IntroSkipper.Manager;
using IntroSkipper.ScheduledTasks;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
2022-05-01 00:33:22 -05:00
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
2024-10-10 15:49:18 +02:00
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Tasks;
2024-03-22 23:41:58 +01:00
using Microsoft.Extensions.Hosting;
2022-05-01 00:33:22 -05:00
using Microsoft.Extensions.Logging;
namespace IntroSkipper.Services
2022-05-01 00:33:22 -05:00
{
/// <summary>
2024-10-16 16:20:21 +02:00
/// Server entrypoint.
2022-05-01 00:33:22 -05:00
/// </summary>
2024-10-16 16:20:21 +02:00
public sealed class Entrypoint : IHostedService, IDisposable
2022-05-01 00:33:22 -05:00
{
2024-10-16 16:20:21 +02:00
private readonly ITaskManager _taskManager;
private readonly ILibraryManager _libraryManager;
private readonly ILogger<Entrypoint> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager;
2024-10-16 16:20:21 +02:00
private readonly HashSet<Guid> _seasonsToAnalyze = [];
private readonly Timer _queueTimer;
private static readonly SemaphoreSlim _analysisSemaphore = new(1, 1);
2024-10-16 16:20:21 +02:00
private PluginConfiguration _config;
private bool _analyzeAgain;
private static CancellationTokenSource? _cancellationTokenSource;
/// <summary>
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
/// </summary>
/// <param name="libraryManager">Library manager.</param>
/// <param name="taskManager">Task manager.</param>
/// <param name="logger">Logger.</param>
/// <param name="loggerFactory">Logger factory.</param>
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
2024-10-16 16:20:21 +02:00
public Entrypoint(
ILibraryManager libraryManager,
ITaskManager taskManager,
ILogger<Entrypoint> logger,
ILoggerFactory loggerFactory,
MediaSegmentUpdateManager mediaSegmentUpdateManager)
2024-10-16 16:20:21 +02:00
{
_libraryManager = libraryManager;
_taskManager = taskManager;
_logger = logger;
_loggerFactory = loggerFactory;
_mediaSegmentUpdateManager = mediaSegmentUpdateManager;
2024-10-16 16:20:21 +02:00
_config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
_queueTimer = new Timer(
OnTimerCallback,
null,
Timeout.InfiniteTimeSpan,
Timeout.InfiniteTimeSpan);
}
2024-10-16 16:20:21 +02:00
/// <summary>
/// Gets State of the automatic task.
/// </summary>
public static TaskState AutomaticTaskState => _cancellationTokenSource switch
{
null => TaskState.Idle,
{ IsCancellationRequested: true } => TaskState.Cancelling,
_ => TaskState.Running
};
2024-10-16 16:20:21 +02:00
/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken)
{
_libraryManager.ItemAdded += OnItemChanged;
_libraryManager.ItemUpdated += OnItemChanged;
2024-10-16 16:20:21 +02:00
_taskManager.TaskCompleted += OnLibraryRefresh;
Plugin.Instance!.ConfigurationChanged += OnSettingsChanged;
2024-10-16 16:20:21 +02:00
FFmpegWrapper.Logger = _logger;
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
_logger.LogInformation("Running startup enqueue");
new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager).GetMediaItems();
2024-10-16 16:20:21 +02:00
return Task.CompletedTask;
}
2024-10-16 16:20:21 +02:00
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken)
{
_libraryManager.ItemAdded -= OnItemChanged;
_libraryManager.ItemUpdated -= OnItemChanged;
2024-10-16 16:20:21 +02:00
_taskManager.TaskCompleted -= OnLibraryRefresh;
Plugin.Instance!.ConfigurationChanged -= OnSettingsChanged;
2024-10-16 16:20:21 +02:00
_queueTimer.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
2024-10-16 16:20:21 +02:00
/// <summary>
/// Library item was added.
/// </summary>
/// <param name="sender">The sending entity.</param>
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
private void OnItemChanged(object? sender, ItemChangeEventArgs itemChangeEventArgs)
{
Recaps and Previews Support (#357) * Recaps and Previews Support * Add draft UI of preview / recap edit * remove intro/credit tasks * Update configPage.html * rename task * Reorganize settings by relation * More standardized formatting * Some additional formatting * fix a typo * Update configPage.html * Allow missing recap / prview data * More risk to corrupt than benefit * Update TimeStamps.cs * Update PluginConfiguration.cs * Update configPage.html * Update PluginConfiguration.cs * Add chapter regex to settings * Move all UI into UI section * Move ending seconds with similar * Add default * fixes * Update SkipIntroController.cs * Autoskip all segments * Check if adjacent segment * Update AutoSkip.cs * Update AutoSkip.cs * Settings apply to all segment types * Update SegmentProvider * Update configPage.html Whoops * Update Plugin.cs * Update AutoSkip.cs * Let’s call it missing instead * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Move "select" all below list * Clarify button wording * Update configPage.html * Nope, long client list will hide it * Simplify wording * Update QueuedEpisode.cs * fix unit test for ffmpeg7 * Add migration * Restore DataContract * update * Update configPage.html * remove analyzed status * Update AutoSkip.cs * Update configPage.html typo * Store analyzed items in seasoninfo * Update VisualizationController.cs * update * Update IntroSkipperDbContext.cs * Add preview / recap delete * This keeps changing itself * Update SkipIntroController.cs * Rather add it to be removed --------- Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> Co-authored-by: TwistedUmbrellaX <1173913+AbandonedCart@users.noreply.github.com> Co-authored-by: Kilian von Pflugk <github@jumoog.io>
2024-11-21 15:42:55 +01:00
if (_config.AutoDetectIntros &&
itemChangeEventArgs.Item is { LocationType: not LocationType.Virtual } item)
2024-10-16 16:20:21 +02:00
{
Guid? id = item is Episode episode ? episode.SeasonId : (item is Movie movie ? movie.Id : null);
if (id.HasValue)
{
_seasonsToAnalyze.Add(id.Value);
StartTimer();
}
2024-10-16 16:20:21 +02:00
}
}
2024-10-16 16:20:21 +02:00
/// <summary>
/// TaskManager task ended.
/// </summary>
/// <param name="sender">The sending entity.</param>
/// <param name="eventArgs">The <see cref="TaskCompletionEventArgs"/>.</param>
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
{
Recaps and Previews Support (#357) * Recaps and Previews Support * Add draft UI of preview / recap edit * remove intro/credit tasks * Update configPage.html * rename task * Reorganize settings by relation * More standardized formatting * Some additional formatting * fix a typo * Update configPage.html * Allow missing recap / prview data * More risk to corrupt than benefit * Update TimeStamps.cs * Update PluginConfiguration.cs * Update configPage.html * Update PluginConfiguration.cs * Add chapter regex to settings * Move all UI into UI section * Move ending seconds with similar * Add default * fixes * Update SkipIntroController.cs * Autoskip all segments * Check if adjacent segment * Update AutoSkip.cs * Update AutoSkip.cs * Settings apply to all segment types * Update SegmentProvider * Update configPage.html Whoops * Update Plugin.cs * Update AutoSkip.cs * Let’s call it missing instead * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Move "select" all below list * Clarify button wording * Update configPage.html * Nope, long client list will hide it * Simplify wording * Update QueuedEpisode.cs * fix unit test for ffmpeg7 * Add migration * Restore DataContract * update * Update configPage.html * remove analyzed status * Update AutoSkip.cs * Update configPage.html typo * Store analyzed items in seasoninfo * Update VisualizationController.cs * update * Update IntroSkipperDbContext.cs * Add preview / recap delete * This keeps changing itself * Update SkipIntroController.cs * Rather add it to be removed --------- Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> Co-authored-by: TwistedUmbrellaX <1173913+AbandonedCart@users.noreply.github.com> Co-authored-by: Kilian von Pflugk <github@jumoog.io>
2024-11-21 15:42:55 +01:00
if (_config.AutoDetectIntros &&
eventArgs.Result is { Key: "RefreshLibrary", Status: TaskCompletionStatus.Completed } &&
AutomaticTaskState != TaskState.Running)
{
StartTimer();
}
2024-10-16 16:20:21 +02:00
}
private void OnSettingsChanged(object? sender, BasePluginConfiguration e)
{
_config = (PluginConfiguration)e;
Recaps and Previews Support (#357) * Recaps and Previews Support * Add draft UI of preview / recap edit * remove intro/credit tasks * Update configPage.html * rename task * Reorganize settings by relation * More standardized formatting * Some additional formatting * fix a typo * Update configPage.html * Allow missing recap / prview data * More risk to corrupt than benefit * Update TimeStamps.cs * Update PluginConfiguration.cs * Update configPage.html * Update PluginConfiguration.cs * Add chapter regex to settings * Move all UI into UI section * Move ending seconds with similar * Add default * fixes * Update SkipIntroController.cs * Autoskip all segments * Check if adjacent segment * Update AutoSkip.cs * Update AutoSkip.cs * Settings apply to all segment types * Update SegmentProvider * Update configPage.html Whoops * Update Plugin.cs * Update AutoSkip.cs * Let’s call it missing instead * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Move "select" all below list * Clarify button wording * Update configPage.html * Nope, long client list will hide it * Simplify wording * Update QueuedEpisode.cs * fix unit test for ffmpeg7 * Add migration * Restore DataContract * update * Update configPage.html * remove analyzed status * Update AutoSkip.cs * Update configPage.html typo * Store analyzed items in seasoninfo * Update VisualizationController.cs * update * Update IntroSkipperDbContext.cs * Add preview / recap delete * This keeps changing itself * Update SkipIntroController.cs * Rather add it to be removed --------- Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> Co-authored-by: TwistedUmbrellaX <1173913+AbandonedCart@users.noreply.github.com> Co-authored-by: Kilian von Pflugk <github@jumoog.io>
2024-11-21 15:42:55 +01:00
Plugin.Instance!.AnalyzeAgain = true;
}
2024-10-16 16:20:21 +02:00
/// <summary>
/// Start timer to debounce analyzing.
/// </summary>
private void StartTimer()
{
if (AutomaticTaskState == TaskState.Running)
{
2024-10-16 16:20:21 +02:00
_analyzeAgain = true;
}
else if (AutomaticTaskState == TaskState.Idle)
{
_logger.LogDebug("Media Library changed, analyzis will start soon!");
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
}
}
2022-05-01 00:33:22 -05:00
private void OnTimerCallback(object? state) =>
_ = RunAnalysisAsync();
private async Task RunAnalysisAsync()
{
try
{
await PerformAnalysisAsync().ConfigureAwait(false);
}
2024-10-20 10:26:38 +02:00
catch (OperationCanceledException)
{
_logger.LogInformation("Automatic Analysis task cancelled");
}
2024-10-16 16:20:21 +02:00
catch (Exception ex)
{
_logger.LogError(ex, "Error in RunAnalysisAsync");
}
2024-10-16 16:20:21 +02:00
_cancellationTokenSource = null;
}
private async Task PerformAnalysisAsync()
2024-10-16 16:20:21 +02:00
{
await _analysisSemaphore.WaitAsync().ConfigureAwait(false);
try
2024-10-16 16:20:21 +02:00
{
using (_cancellationTokenSource = new CancellationTokenSource())
using (await ScheduledTaskSemaphore.AcquireAsync(_cancellationTokenSource.Token).ConfigureAwait(false))
2024-10-16 16:20:21 +02:00
{
_logger.LogInformation("Initiating automatic analysis task");
var seasonIds = new HashSet<Guid>(_seasonsToAnalyze);
_seasonsToAnalyze.Clear();
_analyzeAgain = false;
Recaps and Previews Support (#357) * Recaps and Previews Support * Add draft UI of preview / recap edit * remove intro/credit tasks * Update configPage.html * rename task * Reorganize settings by relation * More standardized formatting * Some additional formatting * fix a typo * Update configPage.html * Allow missing recap / prview data * More risk to corrupt than benefit * Update TimeStamps.cs * Update PluginConfiguration.cs * Update configPage.html * Update PluginConfiguration.cs * Add chapter regex to settings * Move all UI into UI section * Move ending seconds with similar * Add default * fixes * Update SkipIntroController.cs * Autoskip all segments * Check if adjacent segment * Update AutoSkip.cs * Update AutoSkip.cs * Settings apply to all segment types * Update SegmentProvider * Update configPage.html Whoops * Update Plugin.cs * Update AutoSkip.cs * Let’s call it missing instead * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Update BaseItemAnalyzerTask.cs * Move "select" all below list * Clarify button wording * Update configPage.html * Nope, long client list will hide it * Simplify wording * Update QueuedEpisode.cs * fix unit test for ffmpeg7 * Add migration * Restore DataContract * update * Update configPage.html * remove analyzed status * Update AutoSkip.cs * Update configPage.html typo * Store analyzed items in seasoninfo * Update VisualizationController.cs * update * Update IntroSkipperDbContext.cs * Add preview / recap delete * This keeps changing itself * Update SkipIntroController.cs * Rather add it to be removed --------- Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> Co-authored-by: TwistedUmbrellaX <1173913+AbandonedCart@users.noreply.github.com> Co-authored-by: Kilian von Pflugk <github@jumoog.io>
2024-11-21 15:42:55 +01:00
var analyzer = new BaseItemAnalyzerTask(_loggerFactory.CreateLogger<Entrypoint>(), _loggerFactory, _libraryManager, _mediaSegmentUpdateManager);
await analyzer.AnalyzeItemsAsync(new Progress<double>(), _cancellationTokenSource.Token, seasonIds).ConfigureAwait(false);
if (_analyzeAgain && !_cancellationTokenSource.IsCancellationRequested)
{
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
}
2024-10-16 16:20:21 +02:00
}
}
finally
{
_analysisSemaphore.Release();
}
2024-10-16 16:20:21 +02:00
}
/// <summary>
/// Method to cancel the automatic task.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task CancelAutomaticTaskAsync(CancellationToken cancellationToken)
2024-10-16 16:20:21 +02:00
{
if (_cancellationTokenSource is { IsCancellationRequested: false })
2024-10-16 16:20:21 +02:00
{
try
{
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
2024-10-16 16:20:21 +02:00
}
catch (ObjectDisposedException)
{
_cancellationTokenSource = null;
}
}
await _analysisSemaphore.WaitAsync(TimeSpan.FromSeconds(60), cancellationToken).ConfigureAwait(false);
2024-10-16 16:20:21 +02:00
}
/// <inheritdoc/>
public void Dispose()
{
_queueTimer.Dispose();
_cancellationTokenSource?.Dispose();
_analysisSemaphore.Dispose();
2024-10-16 16:20:21 +02:00
}
2024-03-22 23:41:58 +01:00
}
2022-05-01 00:33:22 -05:00
}