using MareSynchronos.API.Data; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using MareSynchronos.WebAPI; using MareSynchronos.WebAPI.Files; using Microsoft.Extensions.Logging; namespace MareSynchronos.PlayerData.Pairs; public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase { private readonly ApiController _apiController; private readonly DalamudUtilService _dalamudUtil; private readonly FileUploadManager _fileTransferManager; private readonly PairManager _pairManager; private CharacterData? _lastCreatedData; private CharacterData? _uploadingCharacterData = null; private readonly List _previouslyVisiblePlayers = []; private Task? _fileUploadTask = null; private readonly HashSet _usersToPushDataTo = []; private readonly SemaphoreSlim _pushDataSemaphore = new(1, 1); private readonly CancellationTokenSource _runtimeCts = new(); public VisibleUserDataDistributor(ILogger logger, ApiController apiController, DalamudUtilService dalamudUtil, PairManager pairManager, MareMediator mediator, FileUploadManager fileTransferManager) : base(logger, mediator) { _apiController = apiController; _dalamudUtil = dalamudUtil; _pairManager = pairManager; _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))) { Logger.LogDebug("Pushing data for visible players"); _lastCreatedData = newData; PushToAllVisibleUsers(forced: true); } else { Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value); } }); Mediator.Subscribe(this, (_) => PushToAllVisibleUsers()); Mediator.Subscribe(this, (_) => _previouslyVisiblePlayers.Clear()); } 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 _pairManager.GetVisibleUsers()) { _usersToPushDataTo.Add(user); } PushCharacterData(forced); } private void FrameworkOnUpdate() { if (!_dalamudUtil.GetIsPlayerPresent() || !_apiController.IsConnected) return; var allVisibleUsers = _pairManager.GetVisibleUsers(); var newVisibleUsers = allVisibleUsers.Except(_previouslyVisiblePlayers).ToList(); _previouslyVisiblePlayers.Clear(); _previouslyVisiblePlayers.AddRange(allVisibleUsers); if (newVisibleUsers.Count == 0) return; Logger.LogTrace("Has new visible players, pushing character data to {users}", 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) return; _ = Task.Run(async () => { forced |= _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash; if (_fileUploadTask == null || (_fileUploadTask?.IsCompleted ?? false) || forced) { _uploadingCharacterData = _lastCreatedData.DeepClone(); Logger.LogDebug("Starting UploadTask for {hash}, Reason: TaskIsNull: {task}, TaskIsCompleted: {taskCpl}, Forced: {frc}", _lastCreatedData.DataHash, _fileUploadTask == null, _fileUploadTask?.IsCompleted ?? false, false); _fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, [.. _usersToPushDataTo]); } if (_fileUploadTask != null) { var dataToSend = await _fileUploadTask.ConfigureAwait(false); await _pushDataSemaphore.WaitAsync(_runtimeCts.Token).ConfigureAwait(false); try { if (!_usersToPushDataTo.Any()) return; Logger.LogDebug("Pushing {data} to {users}", dataToSend.DataHash, string.Join(", ", _usersToPushDataTo.Select(k => k.AliasOrUID))); await _apiController.PushCharacterData(dataToSend, [.. _usersToPushDataTo]).ConfigureAwait(false); _usersToPushDataTo.Clear(); } finally { _pushDataSemaphore.Release(); } } }); } }