using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using MareSynchronos.API.Data; using MareSynchronos.API.Dto.Group; using MareSynchronos.Services.Mediator; using MareSynchronos.WebAPI; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace MareSynchronos.Services.AutoDetect; public sealed class SyncshellDiscoveryService : IHostedService, IMediatorSubscriber { private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly ApiController _apiController; private readonly SemaphoreSlim _refreshSemaphore = new(1, 1); private readonly object _entriesLock = new(); private List _entries = []; private string? _lastError; private bool _isRefreshing; public SyncshellDiscoveryService(ILogger logger, MareMediator mediator, ApiController apiController) { _logger = logger; _mediator = mediator; _apiController = apiController; } public MareMediator Mediator => _mediator; public IReadOnlyList Entries { get { lock (_entriesLock) { return _entries.ToList(); } } } public bool IsRefreshing => _isRefreshing; public string? LastError => _lastError; public async Task JoinAsync(string gid, CancellationToken ct) { try { return await _apiController.SyncshellDiscoveryJoin(new GroupDto(new GroupData(gid))).ConfigureAwait(false); } catch (Exception ex) { _logger.LogWarning(ex, "Join syncshell discovery failed for {gid}", gid); return false; } } public async Task GetStateAsync(string gid, CancellationToken ct) { try { return await _apiController.SyncshellDiscoveryGetState(new GroupDto(new GroupData(gid))).ConfigureAwait(false); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to fetch syncshell discovery state for {gid}", gid); return null; } } public async Task SetVisibilityAsync(string gid, bool visible, CancellationToken ct) { try { var request = new SyncshellDiscoveryVisibilityRequestDto { GID = gid, AutoDetectVisible = visible, }; var success = await _apiController.SyncshellDiscoverySetVisibility(request).ConfigureAwait(false); if (!success) return false; var state = await GetStateAsync(gid, ct).ConfigureAwait(false); if (state != null) { _mediator.Publish(new SyncshellAutoDetectStateChanged(state.GID, state.AutoDetectVisible, state.PasswordTemporarilyDisabled)); } return true; } catch (Exception ex) { _logger.LogWarning(ex, "Failed to set syncshell visibility for {gid}", gid); return false; } } public async Task RefreshAsync(CancellationToken ct) { if (!await _refreshSemaphore.WaitAsync(0, ct).ConfigureAwait(false)) { return; } try { _isRefreshing = true; var discovered = await _apiController.SyncshellDiscoveryList().ConfigureAwait(false); lock (_entriesLock) { _entries = discovered ?? []; } _lastError = null; _mediator.Publish(new SyncshellDiscoveryUpdated(Entries.ToList())); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to refresh syncshell discovery list"); _lastError = ex.Message; } finally { _isRefreshing = false; _refreshSemaphore.Release(); } } public Task StartAsync(CancellationToken cancellationToken) { _mediator.Subscribe(this, msg => { _ = RefreshAsync(CancellationToken.None); }); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _mediator.UnsubscribeAll(this); return Task.CompletedTask; } }