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;
|
2024-04-20 21:12:04 +02:00
|
|
|
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;
|
2024-10-19 23:50:41 +02:00
|
|
|
using IntroSkipper.Configuration;
|
|
|
|
using IntroSkipper.Manager;
|
|
|
|
using IntroSkipper.ScheduledTasks;
|
2024-10-19 22:49:47 +02:00
|
|
|
using MediaBrowser.Controller.Entities.Movies;
|
2024-03-29 16:58:16 +01:00
|
|
|
using MediaBrowser.Controller.Entities.TV;
|
2022-05-01 00:33:22 -05:00
|
|
|
using MediaBrowser.Controller.Library;
|
2024-03-29 16:58:16 +01:00
|
|
|
using MediaBrowser.Model.Entities;
|
2024-10-10 15:49:18 +02:00
|
|
|
using MediaBrowser.Model.Plugins;
|
2024-03-29 16:58:16 +01:00
|
|
|
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;
|
|
|
|
|
2024-10-19 23:50:41 +02:00
|
|
|
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;
|
2024-10-19 22:49:47 +02:00
|
|
|
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager;
|
2024-10-16 16:20:21 +02:00
|
|
|
private readonly HashSet<Guid> _seasonsToAnalyze = [];
|
|
|
|
private readonly Timer _queueTimer;
|
2024-10-19 22:49:47 +02:00
|
|
|
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>
|
2024-10-19 22:49:47 +02:00
|
|
|
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
2024-10-16 16:20:21 +02:00
|
|
|
public Entrypoint(
|
|
|
|
ILibraryManager libraryManager,
|
|
|
|
ITaskManager taskManager,
|
|
|
|
ILogger<Entrypoint> logger,
|
2024-10-19 22:49:47 +02:00
|
|
|
ILoggerFactory loggerFactory,
|
|
|
|
MediaSegmentUpdateManager mediaSegmentUpdateManager)
|
2024-10-16 16:20:21 +02:00
|
|
|
{
|
|
|
|
_libraryManager = libraryManager;
|
|
|
|
_taskManager = taskManager;
|
|
|
|
_logger = logger;
|
|
|
|
_loggerFactory = loggerFactory;
|
2024-10-19 22:49:47 +02:00
|
|
|
_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-03-29 16:58:16 +01:00
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Gets State of the automatic task.
|
|
|
|
/// </summary>
|
2024-10-19 22:49:47 +02:00
|
|
|
public static TaskState AutomaticTaskState => _cancellationTokenSource switch
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
null => TaskState.Idle,
|
|
|
|
{ IsCancellationRequested: true } => TaskState.Cancelling,
|
|
|
|
_ => TaskState.Running
|
|
|
|
};
|
2024-04-10 15:13:37 +02:00
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
/// <inheritdoc />
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
2024-04-10 15:13:37 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
_libraryManager.ItemAdded += OnItemChanged;
|
|
|
|
_libraryManager.ItemUpdated += OnItemChanged;
|
2024-10-16 16:20:21 +02:00
|
|
|
_taskManager.TaskCompleted += OnLibraryRefresh;
|
|
|
|
Plugin.Instance!.ConfigurationChanged += OnSettingsChanged;
|
2024-04-10 15:13:37 +02:00
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
FFmpegWrapper.Logger = _logger;
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-10-19 22:49:47 +02:00
|
|
|
// 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-04-03 17:09:42 +02:00
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
return Task.CompletedTask;
|
2024-03-29 16:58:16 +01:00
|
|
|
}
|
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
/// <inheritdoc />
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
2024-03-29 16:58:16 +01:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
_libraryManager.ItemAdded -= OnItemChanged;
|
|
|
|
_libraryManager.ItemUpdated -= OnItemChanged;
|
2024-10-16 16:20:21 +02:00
|
|
|
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
2024-10-19 22:49:47 +02:00
|
|
|
Plugin.Instance!.ConfigurationChanged -= OnSettingsChanged;
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
_queueTimer.Change(Timeout.Infinite, 0);
|
|
|
|
return Task.CompletedTask;
|
2024-04-03 17:09:42 +02:00
|
|
|
}
|
|
|
|
|
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>
|
2024-10-19 22:49:47 +02:00
|
|
|
private void OnItemChanged(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
2024-03-29 16:58:16 +01:00
|
|
|
{
|
2024-11-21 15:42:55 +01:00
|
|
|
if (_config.AutoDetectIntros &&
|
2024-10-19 22:49:47 +02:00
|
|
|
itemChangeEventArgs.Item is { LocationType: not LocationType.Virtual } item)
|
2024-10-16 16:20:21 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
Guid? id = item is Episode episode ? episode.SeasonId : (item is Movie movie ? movie.Id : null);
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-10-19 22:49:47 +02:00
|
|
|
if (id.HasValue)
|
|
|
|
{
|
|
|
|
_seasonsToAnalyze.Add(id.Value);
|
|
|
|
StartTimer();
|
|
|
|
}
|
2024-10-16 16:20:21 +02:00
|
|
|
}
|
2024-03-29 16:58:16 +01: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)
|
2024-03-29 16:58:16 +01:00
|
|
|
{
|
2024-11-21 15:42:55 +01:00
|
|
|
if (_config.AutoDetectIntros &&
|
2024-10-19 22:49:47 +02:00
|
|
|
eventArgs.Result is { Key: "RefreshLibrary", Status: TaskCompletionStatus.Completed } &&
|
|
|
|
AutomaticTaskState != TaskState.Running)
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
StartTimer();
|
2024-04-13 16:34:58 +02:00
|
|
|
}
|
2024-10-16 16:20:21 +02:00
|
|
|
}
|
2024-04-13 16:34:58 +02:00
|
|
|
|
2024-11-02 18:17:22 +01:00
|
|
|
private void OnSettingsChanged(object? sender, BasePluginConfiguration e)
|
|
|
|
{
|
|
|
|
_config = (PluginConfiguration)e;
|
2024-11-21 15:42:55 +01:00
|
|
|
Plugin.Instance!.AnalyzeAgain = true;
|
2024-11-02 18:17:22 +01:00
|
|
|
}
|
2024-03-30 19:02:18 +01:00
|
|
|
|
2024-10-16 16:20:21 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Start timer to debounce analyzing.
|
|
|
|
/// </summary>
|
|
|
|
private void StartTimer()
|
|
|
|
{
|
|
|
|
if (AutomaticTaskState == TaskState.Running)
|
2024-05-16 19:22:22 +02:00
|
|
|
{
|
2024-10-16 16:20:21 +02:00
|
|
|
_analyzeAgain = true;
|
|
|
|
}
|
|
|
|
else if (AutomaticTaskState == TaskState.Idle)
|
|
|
|
{
|
|
|
|
_logger.LogDebug("Media Library changed, analyzis will start soon!");
|
2024-10-19 22:49:47 +02:00
|
|
|
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
|
2024-05-16 19:22:22 +02:00
|
|
|
}
|
2024-03-30 19:02:18 +01:00
|
|
|
}
|
2022-05-01 00:33:22 -05:00
|
|
|
|
2024-10-19 22:49:47 +02:00
|
|
|
private void OnTimerCallback(object? state) =>
|
|
|
|
_ = RunAnalysisAsync();
|
|
|
|
|
|
|
|
private async Task RunAnalysisAsync()
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
2024-05-16 19:22:22 +02:00
|
|
|
try
|
2024-04-16 18:19:05 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
await PerformAnalysisAsync().ConfigureAwait(false);
|
2024-04-16 18:19:05 +02:00
|
|
|
}
|
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)
|
2024-05-16 19:22:22 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
_logger.LogError(ex, "Error in RunAnalysisAsync");
|
2024-05-16 19:22:22 +02:00
|
|
|
}
|
2024-10-16 16:20:21 +02:00
|
|
|
|
|
|
|
_cancellationTokenSource = null;
|
2024-04-13 16:34:58 +02:00
|
|
|
}
|
2024-05-16 19:22:22 +02:00
|
|
|
|
2024-10-19 22:49:47 +02:00
|
|
|
private async Task PerformAnalysisAsync()
|
2024-10-16 16:20:21 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
await _analysisSemaphore.WaitAsync().ConfigureAwait(false);
|
|
|
|
try
|
2024-10-16 16:20:21 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
using (_cancellationTokenSource = new CancellationTokenSource())
|
|
|
|
using (await ScheduledTaskSemaphore.AcquireAsync(_cancellationTokenSource.Token).ConfigureAwait(false))
|
2024-10-16 16:20:21 +02:00
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
_logger.LogInformation("Initiating automatic analysis task");
|
|
|
|
var seasonIds = new HashSet<Guid>(_seasonsToAnalyze);
|
|
|
|
_seasonsToAnalyze.Clear();
|
|
|
|
_analyzeAgain = false;
|
|
|
|
|
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);
|
2024-10-19 22:49:47 +02:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2024-10-19 22:49:47 +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>
|
2024-10-19 22:49:47 +02:00
|
|
|
/// <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
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
if (_cancellationTokenSource is { IsCancellationRequested: false })
|
2024-10-16 16:20:21 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2024-10-19 22:49:47 +02:00
|
|
|
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
2024-10-16 16:20:21 +02:00
|
|
|
}
|
|
|
|
catch (ObjectDisposedException)
|
|
|
|
{
|
|
|
|
_cancellationTokenSource = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-19 22:49:47 +02:00
|
|
|
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();
|
2024-10-19 22:49:47 +02:00
|
|
|
_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
|
|
|
}
|