[CHANGE] BackgroundServices
This commit is contained in:
@@ -26,8 +26,8 @@ public static class DependencyInjection
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddSingleton<HostedServiceConnector>();
|
builder.Services.AddSingleton<BackgroundServiceManager>();
|
||||||
builder.Services.AddHostedService<ClientManager>();
|
builder.Services.AddHostedService<ClientService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<ILibraryService, LibraryService>();
|
builder.Services.AddScoped<ILibraryService, LibraryService>();
|
||||||
}
|
}
|
||||||
|
56
Manager.App/Services/CircularBuffer.cs
Normal file
56
Manager.App/Services/CircularBuffer.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace Manager.App.Services;
|
||||||
|
|
||||||
|
public class CircularBuffer <T>
|
||||||
|
{
|
||||||
|
private readonly T[] _buffer;
|
||||||
|
private readonly Channel<T> _channel;
|
||||||
|
|
||||||
|
public int Capacity { get; }
|
||||||
|
public int Head { get; private set; }
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
public CircularBuffer(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
Capacity = capacity;
|
||||||
|
_buffer = new T[Capacity];
|
||||||
|
_channel = Channel.CreateUnbounded<T>(new UnboundedChannelOptions
|
||||||
|
{
|
||||||
|
SingleReader = false,
|
||||||
|
SingleWriter = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
_buffer[Head] = item;
|
||||||
|
Head = (Head + 1) % _buffer.Length;
|
||||||
|
|
||||||
|
if (Count < _buffer.Length)
|
||||||
|
{
|
||||||
|
Count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_channel.Writer.TryWrite(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> Items
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
yield return _buffer[(Head - Count + i + _buffer.Length) % _buffer.Length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<T> GetStreamAsync() => _channel.Reader.ReadAllAsync();
|
||||||
|
}
|
97
Manager.App/Services/ExtendedBackgroundService.cs
Normal file
97
Manager.App/Services/ExtendedBackgroundService.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using DotBased.Logging;
|
||||||
|
using Manager.App.Services.System;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
|
namespace Manager.App.Services;
|
||||||
|
|
||||||
|
public abstract class ExtendedBackgroundService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ManualResetEventSlim _resetEvent = new(true);
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
public ServiceState State { get; private set; } = ServiceState.Stopped;
|
||||||
|
public CircularBuffer<ServiceProgress> ProgressLog { get; } = new(500);
|
||||||
|
public string Name { get; }
|
||||||
|
public TimeSpan ExecuteInterval { get; set; }
|
||||||
|
|
||||||
|
public ExtendedBackgroundService(string name, ILogger logger, BackgroundServiceManager manager, TimeSpan? executeInterval = null)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
_logger = logger;
|
||||||
|
manager.RegisterService(this);
|
||||||
|
ExecuteInterval = executeInterval ?? TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
State = ServiceState.Running;
|
||||||
|
_logger.LogInformation("Starting background service: {ServiceName}", Name);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_resetEvent.Wait(stoppingToken);
|
||||||
|
await Task.Delay(ExecuteInterval, stoppingToken);
|
||||||
|
await ExecuteServiceAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (e is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
State = ServiceState.Faulted;
|
||||||
|
_logger.LogError(e,"Background service {ServiceName} faulted!", Name);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
_logger.LogInformation(e,"Service {ServiceName} received cancellation", Name);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
State = ServiceState.Stopped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void LogProgress(string message, LogSeverity severity = LogSeverity.Info) => ProgressLog.Add(new ServiceProgress(message, DateTime.UtcNow, severity));
|
||||||
|
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
if (State == ServiceState.Running)
|
||||||
|
{
|
||||||
|
_resetEvent.Reset();
|
||||||
|
State = ServiceState.Paused;
|
||||||
|
_logger.LogInformation("Pauses service: {ServiceName}", Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume()
|
||||||
|
{
|
||||||
|
if (State == ServiceState.Paused)
|
||||||
|
{
|
||||||
|
_resetEvent.Set();
|
||||||
|
State = ServiceState.Running;
|
||||||
|
_logger.LogInformation("Resumes service: {ServiceName}", Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task ExecuteServiceAsync(CancellationToken stoppingToken);
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is ExtendedBackgroundService bgService && bgService.Name.Equals(Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Name.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ServiceState
|
||||||
|
{
|
||||||
|
Stopped,
|
||||||
|
Faulted,
|
||||||
|
Running,
|
||||||
|
Paused
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ServiceProgress(string Message, DateTime StartTime, LogSeverity Severity);
|
23
Manager.App/Services/System/BackgroundServiceManager.cs
Normal file
23
Manager.App/Services/System/BackgroundServiceManager.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Manager.App.Models.System;
|
||||||
|
|
||||||
|
namespace Manager.App.Services.System;
|
||||||
|
|
||||||
|
public class BackgroundServiceManager
|
||||||
|
{
|
||||||
|
private readonly HashSet<ExtendedBackgroundService> _backgroundServices = [];
|
||||||
|
|
||||||
|
public void RegisterService(ExtendedBackgroundService service)
|
||||||
|
{
|
||||||
|
_backgroundServices.Add(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListResult<ExtendedBackgroundService> GetServices(string serviceName, int total = 20, int skip = 0)
|
||||||
|
{
|
||||||
|
var filtered = string.IsNullOrWhiteSpace(serviceName) ? _backgroundServices.ToArray() : _backgroundServices.Where(x => x.Name.Equals(serviceName, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
|
||||||
|
var results = filtered.OrderDescending()
|
||||||
|
.Skip(skip)
|
||||||
|
.Take(total);
|
||||||
|
return new ListResultReturn<ExtendedBackgroundService>(results.ToList(), filtered.Length);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,19 +6,19 @@ using Manager.YouTube;
|
|||||||
|
|
||||||
namespace Manager.App.Services.System;
|
namespace Manager.App.Services.System;
|
||||||
|
|
||||||
public class ClientManager(IServiceScopeFactory scopeFactory, HostedServiceConnector serviceConnector) : BackgroundService
|
public class ClientService(IServiceScopeFactory scopeFactory, ILogger<ClientService> logger, BackgroundServiceManager serviceManager) : ExtendedBackgroundService("ClientService", logger, serviceManager)
|
||||||
{
|
{
|
||||||
private readonly List<YouTubeClient> _clients = [];
|
private readonly List<YouTubeClient> _clients = [];
|
||||||
private CancellationToken _cancellationToken;
|
private CancellationToken _cancellationToken;
|
||||||
private ILibraryService? _libraryService;
|
private ILibraryService? _libraryService;
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteServiceAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
serviceConnector.RegisterService(this);
|
|
||||||
_cancellationToken = stoppingToken;
|
_cancellationToken = stoppingToken;
|
||||||
stoppingToken.Register(CancellationRequested);
|
stoppingToken.Register(CancellationRequested);
|
||||||
using var scope = scopeFactory.CreateScope();
|
using var scope = scopeFactory.CreateScope();
|
||||||
_libraryService = scope.ServiceProvider.GetRequiredService<ILibraryService>();
|
_libraryService = scope.ServiceProvider.GetRequiredService<ILibraryService>();
|
||||||
|
LogProgress("Initializing service...");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CancellationRequested()
|
private void CancellationRequested()
|
@@ -1,11 +0,0 @@
|
|||||||
namespace Manager.App.Services.System;
|
|
||||||
|
|
||||||
public class HostedServiceConnector
|
|
||||||
{
|
|
||||||
private readonly List<IHostedService> _hostedServices = [];
|
|
||||||
|
|
||||||
public void RegisterService(IHostedService service)
|
|
||||||
{
|
|
||||||
_hostedServices.Add(service);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -20,7 +20,7 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
public List<string> DatasyncIds { get; } = [];
|
public List<string> DatasyncIds { get; } = [];
|
||||||
public Cookie? SapisidCookie => CookieContainer.GetAllCookies()["SAPISID"];
|
public Cookie? SapisidCookie => CookieContainer.GetAllCookies()["SAPISID"];
|
||||||
public HttpClient HttpClient { get; }
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
private YouTubeClient(CookieCollection cookies, string userAgent)
|
private YouTubeClient(CookieCollection cookies, string userAgent)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(userAgent))
|
if (string.IsNullOrWhiteSpace(userAgent))
|
||||||
@@ -38,6 +38,12 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
HttpClient = new HttpClient(GetHttpClientHandler());
|
HttpClient = new HttpClient(GetHttpClientHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the given cookies and fetch client state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cookies">The cookies to use for making requests. Empty collection for anonymous requests.</param>
|
||||||
|
/// <param name="userAgent">The user agent to use for the requests. Only WEB client is supported.</param>
|
||||||
|
/// <returns></returns>
|
||||||
public static async Task<Result<YouTubeClient>> CreateAsync(CookieCollection cookies, string userAgent)
|
public static async Task<Result<YouTubeClient>> CreateAsync(CookieCollection cookies, string userAgent)
|
||||||
{
|
{
|
||||||
var client = new YouTubeClient(cookies, userAgent);
|
var client = new YouTubeClient(cookies, userAgent);
|
||||||
|
Reference in New Issue
Block a user