using LightlessSync.API.Data; using LightlessSync.API.Data.Comparer; using LightlessSync.Services; using LightlessSync.Services.Mediator; using LightlessSync.Utils; using LightlessSync.WebAPI; using LightlessSync.WebAPI.Files; using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; /// /// pushes character data to visible pairs /// public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase { private readonly ApiController _apiController; private readonly DalamudUtilService _dalamudUtil; private readonly FileUploadManager _fileTransferManager; private readonly PairLedger _pairLedger; private CharacterData? _lastCreatedData; private CharacterData? _uploadingCharacterData = null; private readonly List _previouslyVisiblePlayers = []; private Task? _fileUploadTask = null; private readonly HashSet _usersToPushDataTo = new(UserDataComparer.Instance); private readonly SemaphoreSlim _pushLock = new(1, 1); private readonly CancellationTokenSource _runtimeCts = new(); public VisibleUserDataDistributor(ILogger logger, ApiController apiController, DalamudUtilService dalamudUtil, PairLedger pairLedger, LightlessMediator mediator, FileUploadManager fileTransferManager) : base(logger, mediator) { _apiController = apiController; _dalamudUtil = dalamudUtil; _pairLedger = pairLedger; _fileTransferManager = fileTransferManager; Mediator.Subscribe(this, (_) => FrameworkOnUpdate()); Mediator.Subscribe(this, (msg) => { var newData = msg.CharacterData; if (_lastCreatedData == null || (!string.Equals(newData.DataHash.Value, _lastCreatedData.DataHash.Value, StringComparison.Ordinal))) { _lastCreatedData = newData; Logger.LogTrace("Storing new data hash {hash}", newData.DataHash.Value); PushToAllVisibleUsers(forced: true); } else { Logger.LogTrace("Data hash {hash} equal to stored data", newData.DataHash.Value); } }); Mediator.Subscribe(this, (_) => PushToAllVisibleUsers()); Mediator.Subscribe(this, (_) => { _fileTransferManager.CancelUpload(); _previouslyVisiblePlayers.Clear(); _usersToPushDataTo.Clear(); _uploadingCharacterData = null; _fileUploadTask = null; }); } protected override void Dispose(bool disposing) { if (disposing) { _runtimeCts.Cancel(); _runtimeCts.Dispose(); } base.Dispose(disposing); } private void PushToAllVisibleUsers(bool forced = false) { foreach (var user in GetVisibleUsers()) { _usersToPushDataTo.Add(user); } if (_usersToPushDataTo.Count > 0) { Logger.LogDebug("Pushing data {hash} for {count} visible players", _lastCreatedData?.DataHash.Value ?? "UNKNOWN", _usersToPushDataTo.Count); PushCharacterData(forced); } } private void FrameworkOnUpdate() { if (!_dalamudUtil.GetIsPlayerPresent() || !_apiController.IsConnected) return; var allVisibleUsers = GetVisibleUsers(); var newVisibleUsers = allVisibleUsers.Except(_previouslyVisiblePlayers, UserDataComparer.Instance).ToList(); _previouslyVisiblePlayers.Clear(); _previouslyVisiblePlayers.AddRange(allVisibleUsers); if (newVisibleUsers.Count == 0) return; Logger.LogDebug("Scheduling character data push of {data} to {users}", _lastCreatedData?.DataHash.Value ?? string.Empty, string.Join(", ", newVisibleUsers.Select(k => k.AliasOrUID))); foreach (var user in newVisibleUsers) { _usersToPushDataTo.Add(user); } PushCharacterData(); } private void PushCharacterData(bool forced = false) { if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return; _ = PushCharacterDataAsync(forced); } private async Task PushCharacterDataAsync(bool forced = false) { await _pushLock.WaitAsync(_runtimeCts.Token).ConfigureAwait(false); try { if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return; var hashChanged = _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash; forced |= hashChanged; if (_fileUploadTask == null || _fileUploadTask.IsCompleted || forced) { _uploadingCharacterData = _lastCreatedData.DeepClone(); var uploadTargets = _usersToPushDataTo.ToList(); Logger.LogDebug("Starting UploadTask for {hash}, Reason: TaskIsNull: {task}, TaskIsCompleted: {taskCpl}, Forced: {frc}", _lastCreatedData.DataHash, _fileUploadTask == null, _fileUploadTask?.IsCompleted ?? false, forced); _fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, uploadTargets); } var dataToSend = await _fileUploadTask.ConfigureAwait(false); var users = _usersToPushDataTo.ToList(); if (users.Count == 0) return; Logger.LogDebug("Pushing {data} to {users}", dataToSend.DataHash, string.Join(", ", users.Select(k => k.AliasOrUID))); await _apiController.PushCharacterData(dataToSend, users).ConfigureAwait(false); _usersToPushDataTo.Clear(); } finally { _pushLock.Release(); } } private List GetVisibleUsers() => [.. _pairLedger.GetVisiblePairs().Select(connection => connection.User)]; }