[CHANGE] BackgroundServices
This commit is contained in:
@@ -26,8 +26,8 @@ public static class DependencyInjection
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddSingleton<HostedServiceConnector>();
|
||||
builder.Services.AddHostedService<ClientManager>();
|
||||
builder.Services.AddSingleton<BackgroundServiceManager>();
|
||||
builder.Services.AddHostedService<ClientService>();
|
||||
|
||||
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;
|
||||
|
||||
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 CancellationToken _cancellationToken;
|
||||
private ILibraryService? _libraryService;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
protected override async Task ExecuteServiceAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
serviceConnector.RegisterService(this);
|
||||
_cancellationToken = stoppingToken;
|
||||
stoppingToken.Register(CancellationRequested);
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
_libraryService = scope.ServiceProvider.GetRequiredService<ILibraryService>();
|
||||
LogProgress("Initializing service...");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@@ -38,6 +38,12 @@ public sealed class YouTubeClient : IDisposable
|
||||
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)
|
||||
{
|
||||
var client = new YouTubeClient(cookies, userAgent);
|
||||
|
Reference in New Issue
Block a user