Update 0.1.4 - AutoDetect WIP Debug & Fix UI & Optimization

This commit is contained in:
2025-09-11 22:37:29 +02:00
parent 95d9f65068
commit b79a51748f
10 changed files with 211 additions and 34 deletions

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<AssemblyName>UmbraSync</AssemblyName> <AssemblyName>UmbraSync</AssemblyName>
<RootNamespace>UmbraSync</RootNamespace> <RootNamespace>UmbraSync</RootNamespace>
<Version>0.1.2.0</Version> <Version>0.1.4.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -100,6 +100,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<MareSynchronos.WebAPI.AutoDetect.DiscoveryApiClient>(); collection.AddSingleton<MareSynchronos.WebAPI.AutoDetect.DiscoveryApiClient>();
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectRequestService>(); collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectRequestService>();
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>(); collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>();
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>();
collection.AddSingleton<MarePlugin>(); collection.AddSingleton<MarePlugin>();
collection.AddSingleton<MareProfileManager>(); collection.AddSingleton<MareProfileManager>();
collection.AddSingleton<GameObjectHandlerFactory>(); collection.AddSingleton<GameObjectHandlerFactory>();

View File

@@ -1,6 +1,9 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MareSynchronos.WebAPI.AutoDetect; using MareSynchronos.WebAPI.AutoDetect;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
namespace MareSynchronos.Services.AutoDetect; namespace MareSynchronos.Services.AutoDetect;
@@ -10,13 +13,17 @@ public class AutoDetectRequestService
private readonly DiscoveryConfigProvider _configProvider; private readonly DiscoveryConfigProvider _configProvider;
private readonly DiscoveryApiClient _client; private readonly DiscoveryApiClient _client;
private readonly MareConfigService _configService; private readonly MareConfigService _configService;
private readonly DalamudUtilService _dalamud;
private readonly MareMediator _mediator;
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService) public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService)
{ {
_logger = logger; _logger = logger;
_configProvider = configProvider; _configProvider = configProvider;
_client = client; _client = client;
_configService = configService; _configService = configService;
_mediator = mediator;
_dalamud = dalamudUtilService;
} }
public async Task<bool> SendRequestAsync(string token, CancellationToken ct = default) public async Task<bool> SendRequestAsync(string token, CancellationToken ct = default)
@@ -24,14 +31,34 @@ public class AutoDetectRequestService
if (!_configService.Current.AllowAutoDetectPairRequests) if (!_configService.Current.AllowAutoDetectPairRequests)
{ {
_logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled"); _logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled");
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
return false; return false;
} }
var endpoint = _configProvider.RequestEndpoint; var endpoint = _configProvider.RequestEndpoint;
if (string.IsNullOrEmpty(endpoint)) if (string.IsNullOrEmpty(endpoint))
{ {
_logger.LogDebug("No request endpoint configured"); _logger.LogDebug("No request endpoint configured");
_mediator.Publish(new NotificationMessage("Nearby request failed", "Server does not expose request endpoint.", NotificationType.Error));
return false; return false;
} }
return await _client.SendRequestAsync(endpoint!, token, ct).ConfigureAwait(false); string? displayName = null;
try
{
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
displayName = me?.Name.TextValue;
}
catch { }
_logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint);
var ok = await _client.SendRequestAsync(endpoint!, token, displayName, ct).ConfigureAwait(false);
if (ok)
{
_mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info));
}
else
{
_mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning));
}
return ok;
} }
} }

View File

@@ -50,7 +50,7 @@ public class DiscoveryConfigProvider
root.NearbyDiscovery?.Hydrate(); root.NearbyDiscovery?.Hydrate();
_config = root; _config = root;
_lastLoad = DateTimeOffset.UtcNow; _lastLoad = DateTimeOffset.UtcNow;
_logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}", NearbyEnabled); _logger.LogInformation("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -25,6 +25,9 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
private bool _loggedLocalOnly; private bool _loggedLocalOnly;
private int _lastLocalCount = -1; private int _lastLocalCount = -1;
private int _lastMatchCount = -1; private int _lastMatchCount = -1;
private bool _loggedConfigReady;
private string? _lastSnapshotSig;
private volatile bool _isConnected;
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator, public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService, MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
@@ -44,7 +47,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
_loopCts = new CancellationTokenSource(); _loopCts = new CancellationTokenSource();
_mediator.Subscribe<ConnectedMessage>(this, _ => _configProvider.TryLoadFromStapled()); _mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
_ = Task.Run(() => Loop(_loopCts.Token)); _ = Task.Run(() => Loop(_loopCts.Token));
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -65,7 +69,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
{ {
try try
{ {
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn) if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected)
{ {
await Task.Delay(1000, ct).ConfigureAwait(false); await Task.Delay(1000, ct).ConfigureAwait(false);
continue; continue;
@@ -79,6 +83,11 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false); await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false);
} }
} }
else if (!_loggedConfigReady && _configProvider.NearbyEnabled)
{
_loggedConfigReady = true;
_logger.LogInformation("Nearby: well-known loaded and enabled; refresh={refresh}s, expires={exp}", _configProvider.RefreshSec, _configProvider.SaltExpiresAt);
}
var entries = await GetLocalNearbyAsync().ConfigureAwait(false); var entries = await GetLocalNearbyAsync().ConfigureAwait(false);
// Log when local count changes (including 0) to indicate activity // Log when local count changes (including 0) to indicate activity
@@ -95,7 +104,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
try try
{ {
var saltHex = Convert.ToHexString(_configProvider.Salt!); var saltHex = Convert.ToHexString(_configProvider.Salt!);
// map hash->index for result matching and reuse for publish // map hash->index for result matching
Dictionary<string, int> hashToIndex = new(StringComparer.Ordinal); Dictionary<string, int> hashToIndex = new(StringComparer.Ordinal);
List<string> hashes = new(entries.Count); List<string> hashes = new(entries.Count);
foreach (var (entry, idx) in entries.Select((e, i) => (e, i))) foreach (var (entry, idx) in entries.Select((e, i) => (e, i)))
@@ -105,35 +114,63 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
hashes.Add(h); hashes.Add(h);
} }
// Publish local snapshot if endpoint is available (deduplicated) // Debug snapshot once per change
try
{
var snapSig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256();
if (!string.Equals(snapSig, _lastSnapshotSig, StringComparison.Ordinal))
{
_lastSnapshotSig = snapSig;
var sample = entries.Take(5).Select(e =>
{
var hh = (saltHex + e.Name + e.WorldId.ToString()).GetHash256();
var shortH = hh.Length > 8 ? hh[..8] : hh;
return $"{e.Name}({e.WorldId})->{shortH}";
});
var saltShort = saltHex.Length > 8 ? saltHex[..8] : saltHex;
_logger.LogInformation("Nearby snapshot: {count} entries; salt={saltShort}…; samples=[{samples}]",
entries.Count, saltShort, string.Join(", ", sample));
}
}
catch { }
// Publish OUR presence (own hash) if endpoint is available (deduplicated)
if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint)) if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint))
{ {
string? displayName = null; string? displayName = null;
string? selfHash = null;
try try
{ {
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false); var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
if (me != null) if (me != null)
{ {
displayName = me.Name.TextValue; displayName = me.Name.TextValue;
ushort meWorld = 0;
if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc)
meWorld = (ushort)mePc.HomeWorld.RowId;
_logger.LogInformation("Nearby self ident: {name} ({world})", displayName, meWorld);
selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
} }
} }
catch { /* ignore */ } catch { /* ignore */ }
if (hashes.Count > 0) if (!string.IsNullOrEmpty(selfHash))
{ {
var sig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256(); var sig = selfHash!;
if (!string.Equals(sig, _lastPublishedSignature, StringComparison.Ordinal)) if (!string.Equals(sig, _lastPublishedSignature, StringComparison.Ordinal))
{ {
_lastPublishedSignature = sig; _lastPublishedSignature = sig;
_logger.LogDebug("Nearby publish: {count} hashes (updated)", hashes.Count); var shortSelf = selfHash!.Length > 8 ? selfHash[..8] : selfHash;
_ = _api.PublishAsync(_configProvider.PublishEndpoint!, hashes, displayName, ct); _logger.LogInformation("Nearby publish: self presence updated (hash={hash})", shortSelf);
var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct).ConfigureAwait(false);
_logger.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed");
} }
else else
{ {
_logger.LogDebug("Nearby publish skipped (no changes)"); _logger.LogDebug("Nearby publish skipped (no changes)");
} }
} }
// else: no local entries; skip publish silently // else: no self character available; skip publish silently
} }
// Query for matches if endpoint is available // Query for matches if endpoint is available
@@ -160,6 +197,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
} }
} }
} }
_logger.LogInformation("Nearby: server returned {count} matches", allMatches.Count);
// Log change in number of Umbra matches // Log change in number of Umbra matches
int matchCount = entries.Count(e => e.IsMatch); int matchCount = entries.Count(e => e.IsMatch);
@@ -206,7 +244,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
var localPos = local?.Position ?? Vector3.Zero; var localPos = local?.Position ?? Vector3.Zero;
int maxDist = Math.Clamp(_config.Current.AutoDetectMaxDistanceMeters, 5, 100); int maxDist = Math.Clamp(_config.Current.AutoDetectMaxDistanceMeters, 5, 100);
for (int i = 0; i < 200; i += 2) int limit = Math.Min(200, _objectTable.Length);
for (int i = 0; i < limit; i++)
{ {
var obj = await _dalamud.RunOnFrameworkThread(() => _objectTable[i]).ConfigureAwait(false); var obj = await _dalamud.RunOnFrameworkThread(() => _objectTable[i]).ConfigureAwait(false);
if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
@@ -215,7 +254,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position); float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position);
if (!float.IsNaN(dist) && dist > maxDist) continue; if (!float.IsNaN(dist) && dist > maxDist) continue;
string name = obj.Name.ToString(); string name = obj.Name.TextValue;
ushort worldId = 0; ushort worldId = 0;
if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc) if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc)
worldId = (ushort)pc.HomeWorld.RowId; worldId = (ushort)pc.HomeWorld.RowId;

View File

@@ -0,0 +1,70 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using MareSynchronos.Services.Mediator;
using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Services.AutoDetect;
public sealed class NearbyPendingService : IMediatorSubscriber
{
private readonly ILogger<NearbyPendingService> _logger;
private readonly MareMediator _mediator;
private readonly ApiController _api;
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal);
private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api)
{
_logger = logger;
_mediator = mediator;
_api = api;
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
}
public MareMediator Mediator => _mediator;
public IReadOnlyDictionary<string, string> Pending => _pending;
private void OnNotification(NotificationMessage msg)
{
// Watch info messages for Nearby request pattern
if (msg.Type != MareSynchronos.MareConfiguration.Models.NotificationType.Info) return;
var m = ReqRegex.Match(msg.Message);
if (!m.Success) return;
var uid = m.Groups["uid"].Value;
if (string.IsNullOrEmpty(uid)) return;
// Try to extract name as everything before space and '['
var name = msg.Message;
try
{
var idx = msg.Message.IndexOf(':');
if (idx >= 0) name = msg.Message[(idx + 1)..].Trim();
var br = name.LastIndexOf('[');
if (br > 0) name = name[..br].Trim();
}
catch { name = uid; }
_pending[uid] = name;
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
}
public void Remove(string uid)
{
_pending.TryRemove(uid, out _);
}
public async Task<bool> AcceptAsync(string uid)
{
try
{
await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false);
_pending.TryRemove(uid, out _);
return true;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "NearbyPending: accept failed for {uid}", uid);
return false;
}
}
}

View File

@@ -44,6 +44,7 @@ public class CompactUi : WindowMediatorSubscriberBase
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private readonly Stopwatch _timeout = new(); private readonly Stopwatch _timeout = new();
private readonly CharaDataManager _charaDataManager; private readonly CharaDataManager _charaDataManager;
private readonly Services.AutoDetect.NearbyPendingService _nearbyPending;
private readonly UidDisplayHandler _uidDisplayHandler; private readonly UidDisplayHandler _uidDisplayHandler;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private bool _buttonState; private bool _buttonState;
@@ -62,6 +63,7 @@ public class CompactUi : WindowMediatorSubscriberBase
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService, public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler, CharaDataManager charaDataManager, ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler, CharaDataManager charaDataManager,
Services.AutoDetect.NearbyPendingService nearbyPendingService,
PerformanceCollectorService performanceCollectorService) PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "###UmbraSyncMainUI", performanceCollectorService) : base(logger, mediator, "###UmbraSyncMainUI", performanceCollectorService)
{ {
@@ -73,6 +75,7 @@ public class CompactUi : WindowMediatorSubscriberBase
_fileTransferManager = fileTransferManager; _fileTransferManager = fileTransferManager;
_uidDisplayHandler = uidDisplayHandler; _uidDisplayHandler = uidDisplayHandler;
_charaDataManager = charaDataManager; _charaDataManager = charaDataManager;
_nearbyPending = nearbyPendingService;
var tagHandler = new TagHandler(_serverManager); var tagHandler = new TagHandler(_serverManager);
_groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager); _groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager);
@@ -180,14 +183,6 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
ImGui.Separator(); ImGui.Separator();
using (ImRaii.PushId("transfers")) DrawTransfers(); using (ImRaii.PushId("transfers")) DrawTransfers();
using (ImRaii.PushId("autosync"))
{
ImGui.SameLine();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2))
{
Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
}
}
TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight; TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight;
using (ImRaii.PushId("group-user-popup")) _selectPairsForGroupUi.Draw(_pairManager.DirectPairs); using (ImRaii.PushId("group-user-popup")) _selectPairsForGroupUi.Draw(_pairManager.DirectPairs);
using (ImRaii.PushId("grouping-popup")) _selectGroupForPairUi.Draw(); using (ImRaii.PushId("grouping-popup")) _selectGroupForPairUi.Draw();
@@ -396,6 +391,16 @@ public class CompactUi : WindowMediatorSubscriberBase
: $"Nearby ({nearbyCount} Players)"); : $"Nearby ({nearbyCount} Players)");
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen; if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen;
// Header action button to open Nearby window
var btnWidth = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, "Nearby");
var headerRight = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
ImGui.SameLine();
ImGui.SetCursorPosX(headerRight - btnWidth);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", btnWidth))
{
Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
}
if (_nearbyOpen) if (_nearbyOpen)
{ {
ImGui.Indent(); ImGui.Indent();
@@ -407,6 +412,34 @@ public class CompactUi : WindowMediatorSubscriberBase
{ {
UiSharedService.ColorTextWrapped("Open Nearby for details.", ImGuiColors.DalamudGrey3); UiSharedService.ColorTextWrapped("Open Nearby for details.", ImGuiColors.DalamudGrey3);
} }
// Pending Nearby requests (Accept / Dismiss)
try
{
var inbox = _nearbyPending;
if (inbox != null && inbox.Pending.Count > 0)
{
ImGuiHelpers.ScaledDummy(6);
_uiSharedService.BigText("Requests");
foreach (var kv in inbox.Pending)
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted($"{kv.Value} [{kv.Key}]");
ImGui.SameLine();
if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
{
_ = inbox.AcceptAsync(kv.Key);
}
UiSharedService.AttachToolTip("Accept and add as pair");
ImGui.SameLine();
if (_uiSharedService.IconButton(FontAwesomeIcon.Times))
{
inbox.Remove(kv.Key);
}
UiSharedService.AttachToolTip("Dismiss request");
}
}
}
catch { }
ImGui.Unindent(); ImGui.Unindent();
ImGui.Separator(); ImGui.Separator();
} }
@@ -530,21 +563,20 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted(downloadText); ImGui.TextUnformatted(downloadText);
} }
var bottomButtonWidth = (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2; // Space for three equal-width buttons laid out precisely (avoid overlap/wrap)
var spacing = ImGui.GetStyle().ItemSpacing.X;
var bottomButtonWidth = (WindowContentWidth - spacing) / 2f;
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Character Analysis", bottomButtonWidth)) if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Character Analysis", bottomButtonWidth))
{ {
Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
} }
ImGui.SameLine(); ImGui.SameLine();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Running, "Character Data Hub", bottomButtonWidth)) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Running, "Character Data Hub", bottomButtonWidth))
{ {
Mediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi))); Mediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi)));
} }
ImGuiHelpers.ScaledDummy(2);
ImGui.SameLine();
} }
private void DrawUIDHeader() private void DrawUIDHeader()

View File

@@ -212,7 +212,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
ImGui.Separator(); ImGui.Separator();
_uiShared.BigText("Nearby"); _uiShared.BigText("AutoDetect");
bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery; bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery;
if (ImGui.Checkbox("Enable Nearby detection (beta)", ref enableDiscovery)) if (ImGui.Checkbox("Enable Nearby detection (beta)", ref enableDiscovery))
{ {

View File

@@ -8,7 +8,7 @@
"Tags": [ "Tags": [
"customization" "customization"
], ],
"IconUrl": "https://repo.umbra-sync.net/logo.png", "IconUrl": "https://repo.umbra-sync.net/images/logo.png",
"RepoUrl": "https://repo.umbra-sync.net/plugin.json", "RepoUrl": "https://repo.umbra-sync.net/plugin.json",
"CanUnloadAsync": true "CanUnloadAsync": true
} }

View File

@@ -11,6 +11,7 @@ public class DiscoveryApiClient
private readonly ILogger<DiscoveryApiClient> _logger; private readonly ILogger<DiscoveryApiClient> _logger;
private readonly TokenProvider _tokenProvider; private readonly TokenProvider _tokenProvider;
private readonly HttpClient _httpClient = new(); private readonly HttpClient _httpClient = new();
private static readonly JsonSerializerOptions JsonOpt = new() { PropertyNameCaseInsensitive = true };
public DiscoveryApiClient(ILogger<DiscoveryApiClient> logger, TokenProvider tokenProvider) public DiscoveryApiClient(ILogger<DiscoveryApiClient> logger, TokenProvider tokenProvider)
{ {
@@ -32,7 +33,7 @@ public class DiscoveryApiClient
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
resp.EnsureSuccessStatusCode(); resp.EnsureSuccessStatusCode();
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
var result = JsonSerializer.Deserialize<List<ServerMatch>>(json) ?? []; var result = JsonSerializer.Deserialize<List<ServerMatch>>(json, JsonOpt) ?? [];
return result; return result;
} }
catch (Exception ex) catch (Exception ex)
@@ -42,7 +43,7 @@ public class DiscoveryApiClient
} }
} }
public async Task<bool> SendRequestAsync(string endpoint, string token, CancellationToken ct) public async Task<bool> SendRequestAsync(string endpoint, string token, string? displayName, CancellationToken ct)
{ {
try try
{ {
@@ -50,10 +51,17 @@ public class DiscoveryApiClient
if (string.IsNullOrEmpty(jwt)) return false; if (string.IsNullOrEmpty(jwt)) return false;
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint); using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
var body = JsonSerializer.Serialize(new { token }); var body = JsonSerializer.Serialize(new { token, displayName });
req.Content = new StringContent(body, Encoding.UTF8, "application/json"); req.Content = new StringContent(body, Encoding.UTF8, "application/json");
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
return resp.IsSuccessStatusCode; if (!resp.IsSuccessStatusCode)
{
string txt = string.Empty;
try { txt = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } catch { }
_logger.LogWarning("Discovery request failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt);
return false;
}
return true;
} }
catch (Exception ex) catch (Exception ex)
{ {