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))) { _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, (_) => _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); } 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 = _pairManager.GetVisibleUsers(); var newVisibleUsers = allVisibleUsers.Except(_previouslyVisiblePlayers).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; _ = 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, forced); _fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, [.. _usersToPushDataTo]); } if (_fileUploadTask != null) { var dataToSend = await _fileUploadTask.ConfigureAwait(false); await _pushDataSemaphore.WaitAsync(_runtimeCts.Token).ConfigureAwait(false); try { if (_usersToPushDataTo.Count == 0) 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(); } } }); } }