Files
LightlessClient/LightlessSync/Services/PairRequestService.cs

233 lines
7.2 KiB
C#

using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
namespace LightlessSync.Services;
public sealed class PairRequestService : DisposableMediatorSubscriberBase
{
private readonly DalamudUtilService _dalamudUtil;
private readonly PairManager _pairManager;
private readonly Lazy<WebAPI.ApiController> _apiController;
private readonly Lock _syncRoot = new();
private readonly List<PairRequestEntry> _requests = [];
private static readonly TimeSpan _expiration = TimeSpan.FromMinutes(5);
public PairRequestService(
ILogger<PairRequestService> logger,
LightlessMediator mediator,
DalamudUtilService dalamudUtil,
PairManager pairManager,
Lazy<WebAPI.ApiController> apiController)
: base(logger, mediator)
{
_dalamudUtil = dalamudUtil;
_pairManager = pairManager;
_apiController = apiController;
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, _ =>
{
bool removed;
lock (_syncRoot)
{
removed = CleanupExpiredUnsafe();
}
if (removed)
{
Mediator.Publish(new PairRequestsUpdatedMessage());
}
});
}
public PairRequestDisplay RegisterIncomingRequest(string hashedCid, string messageTemplate)
{
if (string.IsNullOrWhiteSpace(hashedCid))
{
hashedCid = string.Empty;
}
messageTemplate ??= string.Empty;
PairRequestEntry entry = new(hashedCid, messageTemplate, DateTime.UtcNow);
lock (_syncRoot)
{
CleanupExpiredUnsafe();
var index = _requests.FindIndex(r => string.Equals(r.HashedCid, hashedCid, StringComparison.Ordinal));
if (index >= 0)
{
_requests[index] = entry;
}
else
{
_requests.Add(entry);
}
}
var display = _dalamudUtil.IsOnFrameworkThread
? ToDisplay(entry)
: _dalamudUtil.RunOnFrameworkThread(() => ToDisplay(entry)).GetAwaiter().GetResult();
Mediator.Publish(new PairRequestsUpdatedMessage());
return display;
}
public IReadOnlyList<PairRequestDisplay> GetActiveRequests()
{
List<PairRequestEntry> entries;
lock (_syncRoot)
{
CleanupExpiredUnsafe();
entries = _requests
.OrderByDescending(r => r.ReceivedAt)
.ToList();
}
return _dalamudUtil.IsOnFrameworkThread
? entries.Select(ToDisplay).ToList()
: _dalamudUtil.RunOnFrameworkThread(() => entries.Select(ToDisplay).ToList()).GetAwaiter().GetResult();
}
public bool RemoveRequest(string hashedCid)
{
bool removed;
lock (_syncRoot)
{
removed = _requests.RemoveAll(r => string.Equals(r.HashedCid, hashedCid, StringComparison.Ordinal)) > 0;
}
if (removed)
{
Mediator.Publish(new PairRequestsUpdatedMessage());
}
return removed;
}
public bool HasPendingRequests()
{
lock (_syncRoot)
{
CleanupExpiredUnsafe();
return _requests.Count > 0;
}
}
private PairRequestDisplay ToDisplay(PairRequestEntry entry)
{
var displayName = ResolveDisplayName(entry.HashedCid);
var message = FormatMessage(entry.MessageTemplate, displayName);
return new PairRequestDisplay(entry.HashedCid, displayName, message, entry.ReceivedAt);
}
private string ResolveDisplayName(string hashedCid)
{
if (string.IsNullOrWhiteSpace(hashedCid))
{
return string.Empty;
}
var (name, address) = _dalamudUtil.FindPlayerByNameHash(hashedCid);
if (!string.IsNullOrWhiteSpace(name))
{
var worldName = _dalamudUtil.GetWorldNameFromPlayerAddress(address);
return !string.IsNullOrWhiteSpace(worldName)
? $"{name} @ {worldName}"
: name;
}
var pair = _pairManager
.GetOnlineUserPairs()
.FirstOrDefault(p => string.Equals(p.Ident, hashedCid, StringComparison.Ordinal));
if (pair != null)
{
if (!string.IsNullOrWhiteSpace(pair.PlayerName))
{
return pair.PlayerName;
}
if (!string.IsNullOrWhiteSpace(pair.UserData.AliasOrUID))
{
return pair.UserData.AliasOrUID;
}
}
return string.Empty;
}
private static string FormatMessage(string template, string displayName)
{
var safeName = string.IsNullOrWhiteSpace(displayName) ? "Someone" : displayName;
template ??= string.Empty;
const string placeholder = "{DisplayName}";
if (!string.IsNullOrEmpty(template) && template.Contains(placeholder, StringComparison.Ordinal))
{
return template.Replace(placeholder, safeName, StringComparison.Ordinal);
}
if (!string.IsNullOrWhiteSpace(template))
{
return $"{safeName}: {template}";
}
return $"{safeName} sent you a pair request.";
}
private bool CleanupExpiredUnsafe()
{
if (_requests.Count == 0)
{
return false;
}
var now = DateTime.UtcNow;
return _requests.RemoveAll(r => now - r.ReceivedAt > _expiration) > 0;
}
public void AcceptPairRequest(string hashedCid, string displayName)
{
_ = Task.Run(async () =>
{
try
{
await _apiController.Value.TryPairWithContentId(hashedCid).ConfigureAwait(false);
RemoveRequest(hashedCid);
var displayText = string.IsNullOrEmpty(displayName) ? hashedCid : displayName;
Mediator.Publish(new NotificationMessage(
"Pair request accepted",
$"Sent a pair request back to {displayText}.",
NotificationType.Info,
TimeSpan.FromSeconds(3)));
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to accept pair request for {HashedCid}", hashedCid);
Mediator.Publish(new NotificationMessage(
"Failed to Accept Pair Request",
ex.Message,
NotificationType.Error,
TimeSpan.FromSeconds(5)));
}
});
}
public void DeclinePairRequest(string hashedCid, string displayName)
{
RemoveRequest(hashedCid);
Mediator.Publish(new NotificationMessage("Pair request declined",
"Declined " + displayName + "'s pending pair request.",
NotificationType.Info,
TimeSpan.FromSeconds(3)));
Logger.LogDebug("Declined pair request from {HashedCid}", hashedCid);
}
private record struct PairRequestEntry(string HashedCid, string MessageTemplate, DateTime ReceivedAt);
public readonly record struct PairRequestDisplay(string HashedCid, string DisplayName, string Message, DateTime ReceivedAt);
}