rework lightfinder for new api

This commit is contained in:
azyges
2025-10-09 07:33:49 +09:00
parent 86107acf12
commit f01229a97f
11 changed files with 531 additions and 164 deletions

View File

@@ -7,6 +7,7 @@ using LightlessSync.WebAPI;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
namespace LightlessSync.Services;
public class BroadcastService : IHostedService, IMediatorSubscriber
@@ -16,9 +17,11 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
private readonly LightlessMediator _mediator;
private readonly LightlessConfigService _config;
private readonly DalamudUtilService _dalamudUtil;
private CancellationTokenSource? _lightfinderCancelTokens;
private Action? _connectedHandler;
public LightlessMediator Mediator => _mediator;
public bool IsLightFinderAvailable { get; private set; } = true;
public bool IsLightFinderAvailable { get; private set; } = false;
public bool IsBroadcasting => _config.Current.BroadcastEnabled;
private bool _syncedOnStartup = false;
@@ -57,24 +60,125 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
await action().ConfigureAwait(false);
}
public async Task StartAsync(CancellationToken cancellationToken)
private async Task<string?> GetLocalHashedCidAsync(string context)
{
try
{
var cid = await _dalamudUtil.GetCIDAsync().ConfigureAwait(false);
return cid.ToString().GetHash256();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to resolve CID for {Context}", context);
return null;
}
}
private void ApplyBroadcastDisabled(bool forcePublish = false)
{
bool wasEnabled = _config.Current.BroadcastEnabled;
bool hadExpiry = _config.Current.BroadcastTtl != DateTime.MinValue;
bool hadRemaining = _remainingTtl.HasValue;
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
if (wasEnabled || hadExpiry)
_config.Save();
_remainingTtl = null;
_waitingForTtlFetch = false;
_syncedOnStartup = false;
if (forcePublish || wasEnabled || hadRemaining)
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
}
private bool TryApplyBroadcastEnabled(TimeSpan? ttl, string context)
{
if (ttl is not { } validTtl || validTtl <= TimeSpan.Zero)
{
_logger.LogWarning("Lightfinder enable skipped ({Context}): invalid TTL ({TTL})", context, ttl);
return false;
}
bool wasEnabled = _config.Current.BroadcastEnabled;
TimeSpan? previousRemaining = _remainingTtl;
DateTime previousExpiry = _config.Current.BroadcastTtl;
var newExpiry = DateTime.UtcNow + validTtl;
_config.Current.BroadcastEnabled = true;
_config.Current.BroadcastTtl = newExpiry;
if (!wasEnabled || previousExpiry != newExpiry)
_config.Save();
_remainingTtl = validTtl;
_waitingForTtlFetch = false;
if (!wasEnabled || previousRemaining != validTtl)
_mediator.Publish(new BroadcastStatusChangedMessage(true, validTtl));
_logger.LogInformation("Lightfinder broadcast enabled ({Context}), TTL: {TTL}", context, validTtl);
return true;
}
private void HandleLightfinderUnavailable(string message, Exception? ex = null)
{
if (ex != null)
_logger.LogWarning(ex, message);
else
_logger.LogWarning(message);
IsLightFinderAvailable = false;
ApplyBroadcastDisabled(forcePublish: true);
}
private void OnDisconnected()
{
IsLightFinderAvailable = false;
ApplyBroadcastDisabled(forcePublish: true);
_logger.LogDebug("Cleared Lightfinder state due to disconnect.");
}
public Task StartAsync(CancellationToken cancellationToken)
{
_mediator.Subscribe<EnableBroadcastMessage>(this, OnEnableBroadcast);
_mediator.Subscribe<BroadcastStatusChangedMessage>(this, OnBroadcastStatusChanged);
_mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick);
_mediator.Subscribe<DisconnectedMessage>(this, _ => OnDisconnected());
_apiController.OnConnected += () => _ = CheckLightfinderSupportAsync(cancellationToken);
//_ = CheckLightfinderSupportAsync(cancellationToken);
IsLightFinderAvailable = false;
_lightfinderCancelTokens?.Cancel();
_lightfinderCancelTokens?.Dispose();
_lightfinderCancelTokens = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_connectedHandler = () => _ = CheckLightfinderSupportAsync(_lightfinderCancelTokens.Token);
_apiController.OnConnected += _connectedHandler;
if (_apiController.IsConnected)
_ = CheckLightfinderSupportAsync(_lightfinderCancelTokens.Token);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_lightfinderCancelTokens?.Cancel();
_lightfinderCancelTokens?.Dispose();
_lightfinderCancelTokens = null;
if (_connectedHandler is not null)
{
_apiController.OnConnected -= _connectedHandler;
_connectedHandler = null;
}
_mediator.UnsubscribeAll(this);
_apiController.OnConnected -= () => _ = CheckLightfinderSupportAsync(cancellationToken);
return Task.CompletedTask;
}
// need to rework this, this is cooked
private async Task CheckLightfinderSupportAsync(CancellationToken cancellationToken)
{
try
@@ -85,25 +189,54 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
if (cancellationToken.IsCancellationRequested)
return;
var dummy = "0".PadLeft(64, '0');
var hashedCid = await GetLocalHashedCidAsync("Lightfinder state check").ConfigureAwait(false);
if (string.IsNullOrEmpty(hashedCid))
return;
await _apiController.IsUserBroadcasting(dummy).ConfigureAwait(false);
await _apiController.SetBroadcastStatus(dummy, true, null).ConfigureAwait(false);
await _apiController.GetBroadcastTtl(dummy).ConfigureAwait(false);
await _apiController.AreUsersBroadcasting([dummy]).ConfigureAwait(false);
BroadcastStatusInfoDto? status = null;
try
{
status = await _apiController.IsUserBroadcasting(hashedCid).ConfigureAwait(false);
}
catch (HubException ex) when (ex.Message.Contains("Method does not exist", StringComparison.OrdinalIgnoreCase))
{
HandleLightfinderUnavailable("Lightfinder unavailable on server (required method missing).", ex);
}
if (!IsLightFinderAvailable)
_logger.LogInformation("Lightfinder is available.");
IsLightFinderAvailable = true;
_logger.LogInformation("Lightfinder is available.");
}
catch (HubException ex) when (ex.Message.Contains("Method does not exist"))
{
_logger.LogWarning("Lightfinder unavailable: required method missing.");
IsLightFinderAvailable = false;
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
bool isBroadcasting = status?.IsBroadcasting == true;
TimeSpan? ttl = status?.TTL;
if (isBroadcasting)
{
if (ttl is not { } remaining || remaining <= TimeSpan.Zero)
ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
if (TryApplyBroadcastEnabled(ttl, "server handshake"))
{
_syncedOnStartup = true;
}
else
{
isBroadcasting = false;
}
}
if (!isBroadcasting)
{
ApplyBroadcastDisabled(forcePublish: true);
_logger.LogInformation("Lightfinder is available but no active broadcast was found.");
}
if (_config.Current.LightfinderAutoEnableOnConnect && !isBroadcasting)
{
_logger.LogInformation("Auto-enabling Lightfinder broadcast after reconnect.");
_mediator.Publish(new EnableBroadcastMessage(hashedCid, true));
}
}
catch (OperationCanceledException)
{
@@ -111,14 +244,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Lightfinder check failed.");
IsLightFinderAvailable = false;
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
HandleLightfinderUnavailable("Lightfinder check failed.", ex);
}
}
@@ -139,46 +265,38 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
};
}
await _apiController.SetBroadcastStatus(msg.HashedCid, msg.Enabled, groupDto).ConfigureAwait(false);
await _apiController.SetBroadcastStatus(msg.Enabled, groupDto).ConfigureAwait(false);
_logger.LogDebug("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid);
if (!msg.Enabled)
{
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
ApplyBroadcastDisabled(forcePublish: true);
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Disabled Lightfinder for Player: {msg.HashedCid}")));
return;
}
_waitingForTtlFetch = true;
TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
if (ttl is { } remaining && remaining > TimeSpan.Zero)
try
{
_config.Current.BroadcastTtl = DateTime.UtcNow + remaining;
_config.Current.BroadcastEnabled = true;
_config.Save();
TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
_logger.LogDebug("Fetched TTL from server: {TTL}", remaining);
_mediator.Publish(new BroadcastStatusChangedMessage(true, remaining));
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}")));
if (TryApplyBroadcastEnabled(ttl, "client request"))
{
_logger.LogDebug("Fetched TTL from server: {TTL}", ttl);
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}")));
}
else
{
ApplyBroadcastDisabled(forcePublish: true);
_logger.LogWarning("No valid TTL returned after enabling broadcast. Disabling.");
}
}
else
finally
{
_logger.LogWarning("No valid TTL returned after enabling broadcast. Disabling.");
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
_waitingForTtlFetch = false;
}
_waitingForTtlFetch = false;
}
catch (Exception ex)
{
@@ -219,17 +337,24 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
return result;
}
public async Task<TimeSpan?> GetBroadcastTtlAsync(string cid)
public async Task<TimeSpan?> GetBroadcastTtlAsync(string? cidForLog = null)
{
TimeSpan? ttl = null;
await RequireConnectionAsync(nameof(GetBroadcastTtlAsync), async () => {
try
{
ttl = await _apiController.GetBroadcastTtl(cid).ConfigureAwait(false);
ttl = await _apiController.GetBroadcastTtl().ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch broadcast TTL for {cid}", cid);
if (cidForLog is { Length: > 0 })
{
_logger.LogWarning(ex, "Failed to fetch broadcast TTL for {Cid}", cidForLog);
}
else
{
_logger.LogWarning(ex, "Failed to fetch broadcast TTL");
}
}
}).ConfigureAwait(false);
return ttl;
@@ -281,7 +406,12 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
return;
}
var hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
var hashedCid = await GetLocalHashedCidAsync(nameof(ToggleBroadcast)).ConfigureAwait(false);
if (string.IsNullOrEmpty(hashedCid))
{
_logger.LogWarning("ToggleBroadcast - unable to resolve CID.");
return;
}
try
{
@@ -321,31 +451,31 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
await RequireConnectionAsync(nameof(OnTick), async () => {
if (!_syncedOnStartup && _config.Current.BroadcastEnabled)
{
_syncedOnStartup = true;
try
{
string hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
if (ttl is { }
remaining && remaining > TimeSpan.Zero)
var hashedCid = await GetLocalHashedCidAsync("startup TTL refresh").ConfigureAwait(false);
if (string.IsNullOrEmpty(hashedCid))
{
_config.Current.BroadcastTtl = DateTime.UtcNow + remaining;
_config.Current.BroadcastEnabled = true;
_config.Save();
_logger.LogDebug("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining);
_logger.LogDebug("Skipping TTL refresh; hashed CID unavailable.");
return;
}
TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
if (TryApplyBroadcastEnabled(ttl, "startup TTL refresh"))
{
_syncedOnStartup = true;
_logger.LogDebug("Refreshed broadcast TTL from server on first OnTick: {TTL}", ttl);
}
else
{
_logger.LogWarning("No valid TTL found on OnTick. Disabling broadcast state.");
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
ApplyBroadcastDisabled(forcePublish: true);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to refresh TTL in OnTick");
_syncedOnStartup = false;
}
}
if (_config.Current.BroadcastEnabled)
@@ -362,10 +492,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
if (_remainingTtl == null)
{
_logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally.");
_config.Current.BroadcastEnabled = false;
_config.Current.BroadcastTtl = DateTime.MinValue;
_config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
ApplyBroadcastDisabled(forcePublish: true);
}
}
else