using MareSynchronos.MareConfiguration; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.WebAPI.Files.Models; using Microsoft.Extensions.Logging; using System.Net.Http.Headers; using System.Net.Http.Json; namespace MareSynchronos.WebAPI.Files; public class FileTransferOrchestrator : DisposableMediatorSubscriberBase { private readonly HttpClient _httpClient; private readonly MareConfigService _mareConfig; private readonly object _semaphoreModificationLock = new(); private readonly ServerConfigurationManager _serverManager; private int _availableDownloadSlots; private SemaphoreSlim _downloadSemaphore; public FileTransferOrchestrator(ILogger logger, MareConfigService mareConfig, ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator) { _mareConfig = mareConfig; _serverManager = serverManager; _httpClient = new(); _availableDownloadSlots = mareConfig.Current.ParallelDownloads; _downloadSemaphore = new(_availableDownloadSlots); Mediator.Subscribe(this, (msg) => { FilesCdnUri = msg.Connection.ServerInfo.FileServerAddress; }); Mediator.Subscribe(this, (msg) => { FilesCdnUri = null; }); } public Uri? FilesCdnUri { private set; get; } public List ForbiddenTransfers { get; } = new(); public bool IsInitialized => FilesCdnUri != null; public void ReleaseDownloadSlot() { _downloadSemaphore.Release(); } public async Task SendRequestAsync(HttpMethod method, Uri uri, CancellationToken? ct = null) { using var requestMessage = new HttpRequestMessage(method, uri); return await SendRequestInternalAsync(requestMessage, ct).ConfigureAwait(false); } public async Task SendRequestAsync(HttpMethod method, Uri uri, T content, CancellationToken ct) where T : class { using var requestMessage = new HttpRequestMessage(method, uri); requestMessage.Content = JsonContent.Create(content); return await SendRequestInternalAsync(requestMessage, ct).ConfigureAwait(false); } public async Task SendRequestStreamAsync(HttpMethod method, Uri uri, ProgressableStreamContent content, CancellationToken ct) { using var requestMessage = new HttpRequestMessage(method, uri); requestMessage.Content = content; return await SendRequestInternalAsync(requestMessage, ct).ConfigureAwait(false); } public async Task WaitForDownloadSlotAsync(CancellationToken token) { lock (_semaphoreModificationLock) { if (_availableDownloadSlots != _mareConfig.Current.ParallelDownloads && _availableDownloadSlots == _downloadSemaphore.CurrentCount) { _availableDownloadSlots = _mareConfig.Current.ParallelDownloads; _downloadSemaphore = new(_availableDownloadSlots); } } await _downloadSemaphore.WaitAsync(token).ConfigureAwait(false); } private async Task SendRequestInternalAsync(HttpRequestMessage requestMessage, CancellationToken? ct = null) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _serverManager.GetToken()); if (requestMessage.Content != null && requestMessage.Content is not StreamContent) { var content = await ((JsonContent)requestMessage.Content).ReadAsStringAsync().ConfigureAwait(false); Logger.LogDebug("Sending {method} to {uri} (Content: {content})", requestMessage.Method, requestMessage.RequestUri, content); } else { Logger.LogDebug("Sending {method} to {uri}", requestMessage.Method, requestMessage.RequestUri); } try { if (ct != null) return await _httpClient.SendAsync(requestMessage, ct.Value).ConfigureAwait(false); return await _httpClient.SendAsync(requestMessage).ConfigureAwait(false); } catch (Exception ex) { Logger.LogCritical(ex, "Error during SendRequestInternal for {uri}", requestMessage.RequestUri); throw; } } }