Compare commits
3 Commits
d183803390
...
03631cd0c8
Author | SHA1 | Date | |
---|---|---|---|
|
03631cd0c8 | ||
|
9ff4fcded2 | ||
|
2593d02a73 |
@@ -16,7 +16,7 @@
|
|||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
</MudAppBar>
|
</MudAppBar>
|
||||||
<div style="margin: 20px">
|
<div style="display: flex; flex-direction: column; flex: 1; padding: 20px; min-height: 0;">
|
||||||
@Body
|
@Body
|
||||||
</div>
|
</div>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<CascadingValue Value="this">
|
<CascadingValue Value="this">
|
||||||
<MudLayout>
|
<MudLayout>
|
||||||
<MudMainContent>
|
<MudMainContent Style="display: flex; flex-direction: column; height: 100vh;">
|
||||||
@Body
|
@Body
|
||||||
</MudMainContent>
|
</MudMainContent>
|
||||||
</MudLayout>
|
</MudLayout>
|
||||||
|
@@ -5,4 +5,5 @@
|
|||||||
<MudNavLink Href="/Library" Icon="@Icons.Material.Filled.LocalLibrary" Match="NavLinkMatch.All">Library</MudNavLink>
|
<MudNavLink Href="/Library" Icon="@Icons.Material.Filled.LocalLibrary" Match="NavLinkMatch.All">Library</MudNavLink>
|
||||||
<MudNavLink Href="/Playlists" Icon="@Icons.Material.Filled.ViewList" Match="NavLinkMatch.All">Playlists</MudNavLink>
|
<MudNavLink Href="/Playlists" Icon="@Icons.Material.Filled.ViewList" Match="NavLinkMatch.All">Playlists</MudNavLink>
|
||||||
<MudNavLink Href="/Development" Icon="@Icons.Material.Filled.DeveloperMode" Match="NavLinkMatch.All">Development</MudNavLink>
|
<MudNavLink Href="/Development" Icon="@Icons.Material.Filled.DeveloperMode" Match="NavLinkMatch.All">Development</MudNavLink>
|
||||||
|
<MudNavLink Href="/Services" Icon="@Icons.Material.Filled.MiscellaneousServices" Match="NavLinkMatch.All">Services</MudNavLink>
|
||||||
</MudNavMenu>
|
</MudNavMenu>
|
90
Manager.App/Components/Pages/Services.razor
Normal file
90
Manager.App/Components/Pages/Services.razor
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@page "/Services"
|
||||||
|
@using Manager.App.Services.System
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
@inject BackgroundServiceManager ServiceManager
|
||||||
|
|
||||||
|
<title>Services</title>
|
||||||
|
|
||||||
|
<MudDataGrid T="ExtendedBackgroundService" Items="@_backgroundServices" Filterable QuickFilter="@QuickFilter">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudText Typo="Typo.h6">Services</MudText>
|
||||||
|
<MudSpacer/>
|
||||||
|
<MudTextField T="string" @bind-Value="@_searchText" Immediate
|
||||||
|
Placeholder="Search" Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium"/>
|
||||||
|
</ToolBarContent>
|
||||||
|
<Columns>
|
||||||
|
<PropertyColumn Property="x => x.Name" Title="Service"/>
|
||||||
|
<PropertyColumn Property="x => x.Description" Title="Description"/>
|
||||||
|
<PropertyColumn Property="x => x.State" Title="Status"/>
|
||||||
|
<PropertyColumn Property="x => x.ExecuteInterval" Title="Execute interval"/>
|
||||||
|
<TemplateColumn>
|
||||||
|
<CellTemplate>
|
||||||
|
<MudStack Row Spacing="2">
|
||||||
|
<MudButton Disabled="@(context.Item?.State == ServiceState.Paused)"
|
||||||
|
OnClick="@(() => { context.Item?.Pause(); })" Variant="Variant.Outlined">Pause
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Disabled="@(context.Item?.State == ServiceState.Running)"
|
||||||
|
OnClick="@(() => { context.Item?.Resume(); })" Variant="Variant.Outlined">Resume
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
<PagerContent>
|
||||||
|
<MudDataGridPager T="ExtendedBackgroundService"/>
|
||||||
|
</PagerContent>
|
||||||
|
</MudDataGrid>
|
||||||
|
|
||||||
|
<MudPaper Elevation="0" Class="mt-3" Style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
|
||||||
|
<MudStack Class="ml-2 mb-2" Spacing="1">
|
||||||
|
<MudText Typo="Typo.h5">Service events</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@($"{_serviceEvents.Count}/{VisibleEventCapacity} events")</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<div class="console-container">
|
||||||
|
@foreach (var serviceEvent in _serviceEvents)
|
||||||
|
{
|
||||||
|
<div class="log-line">
|
||||||
|
<span>@serviceEvent.Date</span>
|
||||||
|
<span>|</span>
|
||||||
|
<span class="@GetLogClass(serviceEvent)">@serviceEvent.Severity</span>
|
||||||
|
<span>|</span>
|
||||||
|
<span style="color: #4d69f1">@serviceEvent.Source</span>
|
||||||
|
<span>-</span>
|
||||||
|
<span style="color: #d4d4d4">@serviceEvent.Message</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.console-container {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #9c9898;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line {
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-info {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-warning {
|
||||||
|
color: #dcdcaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-error {
|
||||||
|
color: #f44747;
|
||||||
|
}
|
||||||
|
</style>
|
67
Manager.App/Components/Pages/Services.razor.cs
Normal file
67
Manager.App/Components/Pages/Services.razor.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using DotBased.Logging;
|
||||||
|
using Manager.App.Extensions;
|
||||||
|
using Manager.App.Services;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Manager.App.Components.Pages;
|
||||||
|
|
||||||
|
public partial class Services : ComponentBase
|
||||||
|
{
|
||||||
|
private const int VisibleEventCapacity = 500;
|
||||||
|
private string _searchText = "";
|
||||||
|
private List<ExtendedBackgroundService> _backgroundServices = [];
|
||||||
|
|
||||||
|
private List<ServiceEvent> _serviceEvents = [];
|
||||||
|
private CancellationTokenSource _cts = new();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_backgroundServices = ServiceManager.GetServices();
|
||||||
|
_ = Task.Run(() => ReadEventStreamsAsync(_cts.Token));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Func<ExtendedBackgroundService, bool> QuickFilter
|
||||||
|
=> x => string.IsNullOrWhiteSpace(_searchText) || $"{x.Name} {x.Description} {x.State} {x.ExecuteInterval}".Contains(_searchText);
|
||||||
|
|
||||||
|
private async Task ReadEventStreamsAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
if (_backgroundServices.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalToGet = VisibleEventCapacity / _backgroundServices.Count;
|
||||||
|
_serviceEvents.AddRange(_backgroundServices.SelectMany(x => x.ProgressEvents.Items.TakeLast(totalToGet)));
|
||||||
|
|
||||||
|
var asyncEnumerators = _backgroundServices.Select(x => x.ProgressEvents.GetStreamAsync());
|
||||||
|
await foreach (var serviceEvent in AsyncEnumerableExtensions.Merge(asyncEnumerators, token))
|
||||||
|
{
|
||||||
|
if (!_serviceEvents.Contains(serviceEvent))
|
||||||
|
{
|
||||||
|
_serviceEvents.Add(serviceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_serviceEvents.Count > VisibleEventCapacity)
|
||||||
|
{
|
||||||
|
_serviceEvents.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLogClass(ServiceEvent serviceEvent) =>
|
||||||
|
serviceEvent.Severity switch
|
||||||
|
{
|
||||||
|
LogSeverity.Info => "log-info",
|
||||||
|
LogSeverity.Warning => "log-warning",
|
||||||
|
LogSeverity.Error => "log-error",
|
||||||
|
_ => "log-info"
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
@@ -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>();
|
||||||
}
|
}
|
||||||
|
48
Manager.App/Extensions/AsyncEnumerableExtensions.cs
Normal file
48
Manager.App/Extensions/AsyncEnumerableExtensions.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace Manager.App.Extensions;
|
||||||
|
|
||||||
|
public static class AsyncEnumerableExtensions
|
||||||
|
{
|
||||||
|
public static async IAsyncEnumerable<T> Merge<T>(IEnumerable<IAsyncEnumerable<T>> sources, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var channel = Channel.CreateUnbounded<T>( new UnboundedChannelOptions { SingleReader = true, SingleWriter = false });
|
||||||
|
|
||||||
|
var writerTasks = sources.Select(source => Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await foreach (var item in source.WithCancellation(cancellationToken))
|
||||||
|
{
|
||||||
|
await channel.Writer.WriteAsync(item, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
channel.Writer.TryComplete(ex);
|
||||||
|
}
|
||||||
|
}, cancellationToken)).ToArray();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.WhenAll(writerTasks);
|
||||||
|
channel.Writer.TryComplete();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
channel.Writer.TryComplete();
|
||||||
|
}
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
|
await foreach (var item in channel.Reader.ReadAllAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
Manager.App/Services/CircularBuffer.cs
Normal file
63
Manager.App/Services/CircularBuffer.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace Manager.App.Services;
|
||||||
|
|
||||||
|
public class CircularBuffer <T>
|
||||||
|
{
|
||||||
|
private readonly T[] _buffer;
|
||||||
|
private readonly Channel<T> _channel;
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_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++)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
yield return _buffer[(Head - Count + i + _buffer.Length) % _buffer.Length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<T> GetStreamAsync() => _channel.Reader.ReadAllAsync();
|
||||||
|
}
|
106
Manager.App/Services/ExtendedBackgroundService.cs
Normal file
106
Manager.App/Services/ExtendedBackgroundService.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using DotBased.Logging;
|
||||||
|
using Manager.App.Services.System;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
|
namespace Manager.App.Services;
|
||||||
|
|
||||||
|
public abstract class ExtendedBackgroundService : BackgroundService
|
||||||
|
{
|
||||||
|
private TaskCompletionSource _resumeSignal = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
public ServiceState State { get; private set; } = ServiceState.Stopped;
|
||||||
|
public CircularBuffer<ServiceEvent> ProgressEvents { get; } = new(500);
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public TimeSpan ExecuteInterval { get; set; }
|
||||||
|
|
||||||
|
public ExtendedBackgroundService(string name, string description, ILogger logger, BackgroundServiceManager manager, TimeSpan? executeInterval = null)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
_logger = logger;
|
||||||
|
manager.RegisterService(this);
|
||||||
|
ExecuteInterval = executeInterval ?? TimeSpan.FromMinutes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
State = ServiceState.Running;
|
||||||
|
_logger.LogInformation("Initializing background service: {ServiceName}", Name);
|
||||||
|
await InitializeAsync(stoppingToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Running background service: {ServiceName}", Name);
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (State == ServiceState.Paused)
|
||||||
|
{
|
||||||
|
_resumeSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
await _resumeSignal.Task.WaitAsync(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 LogEvent(string message, LogSeverity severity = LogSeverity.Info) => ProgressEvents.Add(new ServiceEvent(Name, message, DateTime.UtcNow, severity));
|
||||||
|
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
if (State == ServiceState.Running)
|
||||||
|
{
|
||||||
|
State = ServiceState.Paused;
|
||||||
|
_logger.LogInformation("Pauses service: {ServiceName}", Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume()
|
||||||
|
{
|
||||||
|
if (State == ServiceState.Paused)
|
||||||
|
{
|
||||||
|
State = ServiceState.Running;
|
||||||
|
_resumeSignal.TrySetResult();
|
||||||
|
_logger.LogInformation("Resumed service: {ServiceName}", Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task InitializeAsync(CancellationToken stoppingToken);
|
||||||
|
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 ServiceEvent(string Source, string Message, DateTime Date, LogSeverity Severity);
|
16
Manager.App/Services/System/BackgroundServiceManager.cs
Normal file
16
Manager.App/Services/System/BackgroundServiceManager.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Manager.App.Services.System;
|
||||||
|
|
||||||
|
public class BackgroundServiceManager
|
||||||
|
{
|
||||||
|
private readonly HashSet<ExtendedBackgroundService> _backgroundServices = [];
|
||||||
|
|
||||||
|
public void RegisterService(ExtendedBackgroundService service)
|
||||||
|
{
|
||||||
|
_backgroundServices.Add(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ExtendedBackgroundService> GetServices()
|
||||||
|
{
|
||||||
|
return _backgroundServices.ToList();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using DotBased.Logging;
|
||||||
using DotBased.Monads;
|
using DotBased.Monads;
|
||||||
using Manager.App.Models.Library;
|
using Manager.App.Models.Library;
|
||||||
using Manager.Data.Entities.LibraryContext;
|
using Manager.Data.Entities.LibraryContext;
|
||||||
@@ -6,19 +7,28 @@ 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", "Managing YouTube clients", logger, serviceManager, TimeSpan.FromMilliseconds(100))
|
||||||
{
|
{
|
||||||
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 InitializeAsync(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>();
|
||||||
|
LogEvent("Initializing service...");
|
||||||
|
Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteServiceAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
LogEvent("Sending event...");
|
||||||
|
LogEvent("Sending warning event...", LogSeverity.Warning);
|
||||||
|
LogEvent("Sending error event...", LogSeverity.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,11 +3,11 @@
|
|||||||
"Logging": {
|
"Logging": {
|
||||||
"Severity": "Debug",
|
"Severity": "Debug",
|
||||||
"SeverityFilters":{
|
"SeverityFilters":{
|
||||||
"Microsoft": "Debug",
|
"Microsoft": "Info",
|
||||||
"Microsoft.Hosting.Lifetime": "Debug",
|
"Microsoft.Hosting.Lifetime": "Debug",
|
||||||
"Microsoft.AspNetCore": "Warning",
|
"Microsoft.AspNetCore": "Warning",
|
||||||
"Microsoft.AspNetCore.Authentication": "Debug",
|
"Microsoft.AspNetCore.Authentication": "Debug",
|
||||||
"MudBlazor": "Debug"
|
"MudBlazor": "Info"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -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