using System.Net; using DotBased.Logging; using DotBased.Monads; using Manager.App.Models.System; using Manager.Data.Entities.LibraryContext; using Manager.YouTube; namespace Manager.App.Services.System; public class ClientService(IServiceScopeFactory scopeFactory, ILogger logger) : ExtendedBackgroundService(nameof(ClientService), "Managing YouTube clients", logger, TimeSpan.FromMinutes(10)) { private readonly YouTubeClientCollection _loadedClients = []; private ILibraryService? _libraryService; protected override Task InitializeAsync(CancellationToken stoppingToken) { stoppingToken.Register(CancellationRequested); using var scope = scopeFactory.CreateScope(); _libraryService = scope.ServiceProvider.GetRequiredService(); LogEvent("Initializing service..."); return Task.CompletedTask; } protected override async Task ExecuteServiceAsync(CancellationToken stoppingToken) { LogEvent($"Saving {_loadedClients.Count} loaded client(s)"); foreach (var client in _loadedClients) { await SaveClientAsync(client, cancellationToken: stoppingToken); } } private void CancellationRequested() { foreach (var client in _loadedClients) { client.Dispose(); } } public async Task> GetClientsAsync(string? search, int offset = 0, int limit = 10, CancellationToken cancellationToken = default) { if (_libraryService == null) { return ResultError.Fail("Library service is not initialized!."); } var accountsResult = await _libraryService.GetAccountsAsync(search, offset, limit, cancellationToken); if (!accountsResult.IsSuccess) { return accountsResult.Error ?? ResultError.Fail("Failed to get accounts!"); } var comparedClients = accountsResult.Value.Select(x => new YouTubeClientItem(x) { Id = x.Id, IsLoaded = _loadedClients.Contains(x.Id) }).ToList(); return new ListResultReturn(comparedClients, accountsResult.Total); } public async Task> LoadClientByIdAsync(string id, CancellationToken cancellationToken = default) { if (_loadedClients.TryGetValue(id, out var client)) { return client; } if (_libraryService == null) { return ResultError.Fail("Library service is not initialized!."); } var clientResult = await _libraryService.GetChannelByIdAsync(id, cancellationToken); if (!clientResult.IsSuccess) { return clientResult.Error ?? ResultError.Fail("Failed to load channel from database!"); } var clientAcc = clientResult.Value.ClientAccount; if (clientAcc == null) { return ResultError.Fail("Client account is not initialized!."); } var cookieCollection = new CookieCollection(); foreach (var httpCookie in clientAcc.HttpCookies) { var cookie = new Cookie { Name = httpCookie.Name, Value = httpCookie.Value, Domain = httpCookie.Domain, Path = httpCookie.Path, Secure = httpCookie.Secure, HttpOnly = httpCookie.HttpOnly, Expires = httpCookie.ExpiresUtc ?? DateTime.MinValue }; cookieCollection.Add(cookie); } var ytClientResult = await YouTubeClient.CreateAsync(cookieCollection, clientAcc.UserAgent ?? ""); if (!ytClientResult.IsSuccess) { return ytClientResult; } _loadedClients.Add(ytClientResult.Value); return ytClientResult.Value; } public async Task SaveClientAsync(YouTubeClient client, CancellationToken cancellationToken = default) { if (_libraryService == null) { return ResultError.Fail("Library service is not initialized!."); } if (string.IsNullOrWhiteSpace(client.Id)) { LogEvent("Failed to store client no ID!", LogSeverity.Warning); return ResultError.Fail("Client does not have an ID, cannot save to library database!"); } _loadedClients.Add(client); List httpCookies = []; httpCookies.AddRange(client.CookieContainer.GetAllCookies().Where(c => c.Expires != DateTime.MinValue) .ToList() .Select(cookie => new HttpCookieEntity { ClientId = client.Id, Name = cookie.Name, Value = cookie.Value, Domain = cookie.Domain, Path = cookie.Path, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly, ExpiresUtc = cookie.Expires })); var saveResult = await _libraryService.SaveClientAsync(new ClientAccountEntity { Id = client.Id, UserAgent = client.UserAgent, HttpCookies = httpCookies }, cancellationToken); return saveResult; } }