diff --git a/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs b/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs
index a11893b..cd62f98 100644
--- a/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs
+++ b/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs
@@ -2,6 +2,9 @@ using LightlessSync.API.Data;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// performance metrics for each pair handler
+///
public interface IPairPerformanceSubject
{
string Ident { get; }
diff --git a/LightlessSync/PlayerData/Pairs/OptionalPluginWarning.cs b/LightlessSync/PlayerData/Pairs/OptionalPluginWarning.cs
deleted file mode 100644
index a5c5eff..0000000
--- a/LightlessSync/PlayerData/Pairs/OptionalPluginWarning.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace LightlessSync.PlayerData.Pairs;
-
-public record OptionalPluginWarning
-{
- public bool ShownHeelsWarning { get; set; } = false;
- public bool ShownCustomizePlusWarning { get; set; } = false;
- public bool ShownHonorificWarning { get; set; } = false;
- public bool ShownMoodlesWarning { get; set; } = false;
- public bool ShowPetNicknamesWarning { get; set; } = false;
-}
\ No newline at end of file
diff --git a/LightlessSync/PlayerData/Pairs/Pair.cs b/LightlessSync/PlayerData/Pairs/Pair.cs
index 7709b06..87933ac 100644
--- a/LightlessSync/PlayerData/Pairs/Pair.cs
+++ b/LightlessSync/PlayerData/Pairs/Pair.cs
@@ -14,6 +14,9 @@ using LightlessSync.WebAPI;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// ui wrapper around a pair connection
+///
public class Pair
{
private readonly PairLedger _pairLedger;
diff --git a/LightlessSync/PlayerData/Pairs/PairCoordinator.Groups.cs b/LightlessSync/PlayerData/Pairs/PairCoordinator.Groups.cs
new file mode 100644
index 0000000..7bdfc23
--- /dev/null
+++ b/LightlessSync/PlayerData/Pairs/PairCoordinator.Groups.cs
@@ -0,0 +1,136 @@
+using LightlessSync.API.Dto.Group;
+using Microsoft.Extensions.Logging;
+
+namespace LightlessSync.PlayerData.Pairs;
+
+///
+/// handles group related pair events
+///
+public sealed partial class PairCoordinator
+{
+ public void HandleGroupChangePermissions(GroupPermissionDto dto)
+ {
+ var result = _pairManager.UpdateGroupPermissions(dto);
+ if (!result.Success)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update permissions for group {GroupId}: {Error}", dto.Group.GID, result.Error);
+ }
+ return;
+ }
+
+ PublishPairDataChanged(groupChanged: true);
+ }
+
+ public void HandleGroupFullInfo(GroupFullInfoDto dto)
+ {
+ var result = _pairManager.AddGroup(dto);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to add group {GroupId}: {Error}", dto.Group.GID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged(groupChanged: true);
+ }
+
+ public void HandleGroupPairJoined(GroupPairFullInfoDto dto)
+ {
+ var result = _pairManager.AddOrUpdateGroupPair(dto);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to add group pair {Uid}/{Group}: {Error}", dto.User.UID, dto.Group.GID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged(groupChanged: true);
+ }
+
+ public void HandleGroupPairLeft(GroupPairDto dto)
+ {
+ var deregistration = _pairManager.RemoveGroupPair(dto);
+ if (deregistration.Success && deregistration.Value is { } registration && registration.CharacterIdent is not null)
+ {
+ _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true);
+ }
+ else if (!deregistration.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("RemoveGroupPair failed for {Uid}: {Error}", dto.User.UID, deregistration.Error);
+ }
+
+ if (deregistration.Success)
+ {
+ PublishPairDataChanged(groupChanged: true);
+ }
+ }
+
+ public void HandleGroupRemoved(GroupDto dto)
+ {
+ var removalResult = _pairManager.RemoveGroup(dto.Group.GID);
+ if (removalResult.Success)
+ {
+ foreach (var registration in removalResult.Value)
+ {
+ if (registration.CharacterIdent is not null)
+ {
+ _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true);
+ }
+ }
+ }
+ else if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to remove group {Group}: {Error}", dto.Group.GID, removalResult.Error);
+ }
+
+ if (removalResult.Success)
+ {
+ PublishPairDataChanged(groupChanged: true);
+ }
+ }
+
+ public void HandleGroupInfoUpdate(GroupInfoDto dto)
+ {
+ var result = _pairManager.UpdateGroupInfo(dto);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update group info for {Group}: {Error}", dto.Group.GID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged(groupChanged: true);
+ }
+
+ public void HandleGroupPairPermissions(GroupPairUserPermissionDto dto)
+ {
+ var result = _pairManager.UpdateGroupPairPermissions(dto);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update group pair permissions for {Group}: {Error}", dto.Group.GID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged(groupChanged: true);
+ }
+
+ public void HandleGroupPairStatus(GroupPairUserInfoDto dto, bool isSelf)
+ {
+ PairOperationResult result;
+ if (isSelf)
+ {
+ result = _pairManager.UpdateGroupStatus(dto);
+ }
+ else
+ {
+ result = _pairManager.UpdateGroupPairStatus(dto);
+ }
+
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update group status for {Group}:{Uid}: {Error}", dto.GID, dto.UID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged(groupChanged: true);
+ }
+}
diff --git a/LightlessSync/PlayerData/Pairs/PairCoordinator.Users.cs b/LightlessSync/PlayerData/Pairs/PairCoordinator.Users.cs
new file mode 100644
index 0000000..5925663
--- /dev/null
+++ b/LightlessSync/PlayerData/Pairs/PairCoordinator.Users.cs
@@ -0,0 +1,303 @@
+using LightlessSync.API.Data;
+using LightlessSync.API.Data.Enum;
+using LightlessSync.API.Data.Extensions;
+using LightlessSync.API.Dto.User;
+using LightlessSync.Services.Events;
+using LightlessSync.Services.Mediator;
+using Microsoft.Extensions.Logging;
+
+namespace LightlessSync.PlayerData.Pairs;
+
+///
+/// handles user pair events
+///
+public sealed partial class PairCoordinator
+{
+ public void HandleUserAddPair(UserPairDto dto, bool addToLastAddedUser = true)
+ {
+ var result = _pairManager.AddOrUpdateIndividual(dto, addToLastAddedUser);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to add/update pair {Uid}: {Error}", dto.User.UID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged();
+ }
+
+ public void HandleUserAddPair(UserFullPairDto dto)
+ {
+ var result = _pairManager.AddOrUpdateIndividual(dto);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to add/update full pair {Uid}: {Error}", dto.User.UID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged();
+ }
+
+ public void HandleUserRemovePair(UserDto dto)
+ {
+ var removal = _pairManager.RemoveIndividual(dto);
+ if (removal.Success && removal.Value is { } registration && registration.CharacterIdent is not null)
+ {
+ _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true);
+ }
+ else if (!removal.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("RemoveIndividual failed for {Uid}: {Error}", dto.User.UID, removal.Error);
+ }
+
+ if (removal.Success)
+ {
+ _pendingCharacterData.TryRemove(dto.User.UID, out _);
+ PublishPairDataChanged();
+ }
+ }
+
+ public void HandleUserStatus(UserIndividualPairStatusDto dto)
+ {
+ var result = _pairManager.SetIndividualStatus(dto);
+ if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update individual pair status for {Uid}: {Error}", dto.User.UID, result.Error);
+ return;
+ }
+
+ PublishPairDataChanged();
+ }
+
+ public void HandleUserOnline(OnlineUserIdentDto dto, bool sendNotification)
+ {
+ var wasOnline = false;
+ PairConnection? previousConnection = null;
+ if (_pairManager.TryGetPair(dto.User.UID, out var existingConnection))
+ {
+ previousConnection = existingConnection;
+ wasOnline = existingConnection.IsOnline;
+ }
+
+ var registrationResult = _pairManager.MarkOnline(dto);
+ if (!registrationResult.Success)
+ {
+ _logger.LogDebug("MarkOnline failed for {Uid}: {Error}", dto.User.UID, registrationResult.Error);
+ return;
+ }
+
+ var registration = registrationResult.Value;
+ if (registration.CharacterIdent is null)
+ {
+ _logger.LogDebug("Online registration for {Uid} missing ident.", dto.User.UID);
+ }
+ else
+ {
+ var handlerResult = _handlerRegistry.RegisterOnlinePair(registration);
+ if (!handlerResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("RegisterOnlinePair failed for {Uid}: {Error}", dto.User.UID, handlerResult.Error);
+ }
+ }
+
+ var connectionResult = _pairManager.GetPair(dto.User.UID);
+ var connection = connectionResult.Success ? connectionResult.Value : previousConnection;
+ if (connection is not null)
+ {
+ _mediator.Publish(new ClearProfileUserDataMessage(connection.User));
+ }
+ else
+ {
+ _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
+ }
+
+ if (!wasOnline)
+ {
+ NotifyUserOnline(connection, sendNotification);
+ }
+
+ if (registration.CharacterIdent is not null &&
+ _pendingCharacterData.TryRemove(dto.User.UID, out var pendingData))
+ {
+ var pendingRegistration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), registration.CharacterIdent);
+ var pendingApply = _handlerRegistry.ApplyCharacterData(pendingRegistration, pendingData);
+ if (!pendingApply.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Applying pending character data for {Uid} failed: {Error}", dto.User.UID, pendingApply.Error);
+ }
+ }
+
+ PublishPairDataChanged();
+ }
+
+ public void HandleUserOffline(UserData user)
+ {
+ var registrationResult = _pairManager.MarkOffline(user);
+ if (registrationResult.Success)
+ {
+ _pendingCharacterData.TryRemove(user.UID, out _);
+ if (registrationResult.Value.CharacterIdent is not null)
+ {
+ _ = _handlerRegistry.DeregisterOfflinePair(registrationResult.Value);
+ }
+
+ _mediator.Publish(new ClearProfileUserDataMessage(user));
+ PublishPairDataChanged();
+ }
+ else if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("MarkOffline failed for {Uid}: {Error}", user.UID, registrationResult.Error);
+ }
+ }
+
+ public void HandleUserPermissions(UserPermissionsDto dto)
+ {
+ var pairResult = _pairManager.GetPair(dto.User.UID);
+ if (!pairResult.Success)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Permission update received for unknown pair {Uid}", dto.User.UID);
+ }
+ return;
+ }
+
+ var connection = pairResult.Value;
+ var previous = connection.OtherToSelfPermissions;
+
+ var updateResult = _pairManager.UpdateOtherPermissions(dto);
+ if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error);
+ return;
+ }
+
+ PublishPairDataChanged();
+
+ if (previous.IsPaused() != dto.Permissions.IsPaused())
+ {
+ _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
+
+ if (connection.Ident is not null)
+ {
+ var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused());
+ if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error);
+ }
+ }
+ }
+
+ if (!connection.IsPaused && connection.Ident is not null)
+ {
+ ReapplyLastKnownData(dto.User.UID, connection.Ident);
+ }
+ }
+
+ public void HandleSelfPermissions(UserPermissionsDto dto)
+ {
+ var pairResult = _pairManager.GetPair(dto.User.UID);
+ if (!pairResult.Success)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Self permission update received for unknown pair {Uid}", dto.User.UID);
+ }
+ return;
+ }
+
+ var connection = pairResult.Value;
+ var previous = connection.SelfToOtherPermissions;
+
+ var updateResult = _pairManager.UpdateSelfPermissions(dto);
+ if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update self permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error);
+ return;
+ }
+
+ PublishPairDataChanged();
+
+ if (previous.IsPaused() != dto.Permissions.IsPaused())
+ {
+ _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
+
+ if (connection.Ident is not null)
+ {
+ var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused());
+ if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error);
+ }
+ }
+ }
+
+ if (!connection.IsPaused && connection.Ident is not null)
+ {
+ ReapplyLastKnownData(dto.User.UID, connection.Ident);
+ }
+ }
+
+ public void HandleUploadStatus(UserDto dto)
+ {
+ var pairResult = _pairManager.GetPair(dto.User.UID);
+ if (!pairResult.Success)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Upload status received for unknown pair {Uid}", dto.User.UID);
+ }
+ return;
+ }
+
+ var connection = pairResult.Value;
+ if (connection.Ident is null)
+ {
+ return;
+ }
+
+ var setResult = _handlerRegistry.SetUploading(new PairUniqueIdentifier(dto.User.UID), connection.Ident, true);
+ if (!setResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Failed to set uploading for {Uid}: {Error}", dto.User.UID, setResult.Error);
+ }
+ }
+
+ public void HandleCharacterData(OnlineUserCharaDataDto dto)
+ {
+ var pairResult = _pairManager.GetPair(dto.User.UID);
+ if (!pairResult.Success)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Character data received for unknown pair {Uid}, queued for later.", dto.User.UID);
+ }
+ _pendingCharacterData[dto.User.UID] = dto;
+ return;
+ }
+
+ var connection = pairResult.Value;
+ _mediator.Publish(new EventMessage(new Event(connection.User, nameof(PairCoordinator), EventSeverity.Informational, "Received Character Data")));
+ if (connection.Ident is null)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Character data received for {Uid} without ident, queued for later.", dto.User.UID);
+ }
+ _pendingCharacterData[dto.User.UID] = dto;
+ return;
+ }
+
+ _pendingCharacterData.TryRemove(dto.User.UID, out _);
+ var registration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident);
+ var applyResult = _handlerRegistry.ApplyCharacterData(registration, dto);
+ if (!applyResult.Success && _logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("ApplyCharacterData queued for {Uid}: {Error}", dto.User.UID, applyResult.Error);
+ }
+ }
+
+ public void HandleProfile(UserDto dto)
+ {
+ _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
+ }
+}
diff --git a/LightlessSync/PlayerData/Pairs/PairCoordinator.cs b/LightlessSync/PlayerData/Pairs/PairCoordinator.cs
index ddc4adb..b7af0cd 100644
--- a/LightlessSync/PlayerData/Pairs/PairCoordinator.cs
+++ b/LightlessSync/PlayerData/Pairs/PairCoordinator.cs
@@ -1,21 +1,19 @@
using System;
using System.Collections.Concurrent;
-using LightlessSync.API.Data;
-using LightlessSync.API.Data.Enum;
-using LightlessSync.API.Data.Extensions;
-using LightlessSync.API.Dto.CharaData;
-using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Models;
-using LightlessSync.Services.Mediator;
using LightlessSync.Services.Events;
+using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using Microsoft.Extensions.Logging;
namespace LightlessSync.PlayerData.Pairs;
-public sealed class PairCoordinator : MediatorSubscriberBase
+///
+/// wires mediator events into the pair system
+///
+public sealed partial class PairCoordinator : MediatorSubscriberBase
{
private readonly ILogger _logger;
private readonly LightlessConfigService _configService;
@@ -107,45 +105,6 @@ public sealed class PairCoordinator : MediatorSubscriberBase
}
}
- public void HandleGroupChangePermissions(GroupPermissionDto dto)
- {
- var result = _pairManager.UpdateGroupPermissions(dto);
- if (!result.Success)
- {
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update permissions for group {GroupId}: {Error}", dto.Group.GID, result.Error);
- }
- return;
- }
-
- PublishPairDataChanged(groupChanged: true);
- }
-
- public void HandleGroupFullInfo(GroupFullInfoDto dto)
- {
- var result = _pairManager.AddGroup(dto);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to add group {GroupId}: {Error}", dto.Group.GID, result.Error);
- return;
- }
-
- PublishPairDataChanged(groupChanged: true);
- }
-
- public void HandleGroupPairJoined(GroupPairFullInfoDto dto)
- {
- var result = _pairManager.AddOrUpdateGroupPair(dto);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to add group pair {Uid}/{Group}: {Error}", dto.User.UID, dto.Group.GID, result.Error);
- return;
- }
-
- PublishPairDataChanged(groupChanged: true);
- }
-
private void HandleActiveServerChange(string serverUrl)
{
if (_logger.IsEnabled(LogLevel.Debug))
@@ -175,379 +134,4 @@ public sealed class PairCoordinator : MediatorSubscriberBase
_mediator.Publish(new ClearProfileGroupDataMessage());
PublishPairDataChanged(groupChanged: true);
}
-
- public void HandleGroupPairLeft(GroupPairDto dto)
- {
- var deregistration = _pairManager.RemoveGroupPair(dto);
- if (deregistration.Success && deregistration.Value is { } registration && registration.CharacterIdent is not null)
- {
- _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true);
- }
- else if (!deregistration.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("RemoveGroupPair failed for {Uid}: {Error}", dto.User.UID, deregistration.Error);
- }
-
- if (deregistration.Success)
- {
- PublishPairDataChanged(groupChanged: true);
- }
- }
-
- public void HandleGroupRemoved(GroupDto dto)
- {
- var removalResult = _pairManager.RemoveGroup(dto.Group.GID);
- if (removalResult.Success)
- {
- foreach (var registration in removalResult.Value)
- {
- if (registration.CharacterIdent is not null)
- {
- _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true);
- }
- }
- }
- else if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to remove group {Group}: {Error}", dto.Group.GID, removalResult.Error);
- }
-
- if (removalResult.Success)
- {
- PublishPairDataChanged(groupChanged: true);
- }
- }
-
- public void HandleGroupInfoUpdate(GroupInfoDto dto)
- {
- var result = _pairManager.UpdateGroupInfo(dto);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update group info for {Group}: {Error}", dto.Group.GID, result.Error);
- return;
- }
-
- PublishPairDataChanged(groupChanged: true);
- }
-
- public void HandleGroupPairPermissions(GroupPairUserPermissionDto dto)
- {
- var result = _pairManager.UpdateGroupPairPermissions(dto);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update group pair permissions for {Group}: {Error}", dto.Group.GID, result.Error);
- return;
- }
-
- PublishPairDataChanged(groupChanged: true);
- }
-
- public void HandleGroupPairStatus(GroupPairUserInfoDto dto, bool isSelf)
- {
- PairOperationResult result;
- if (isSelf)
- {
- result = _pairManager.UpdateGroupStatus(dto);
- }
- else
- {
- result = _pairManager.UpdateGroupPairStatus(dto);
- }
-
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update group status for {Group}:{Uid}: {Error}", dto.GID, dto.UID, result.Error);
- return;
- }
-
- PublishPairDataChanged(groupChanged: true);
- }
-
- public void HandleUserAddPair(UserPairDto dto, bool addToLastAddedUser = true)
- {
- var result = _pairManager.AddOrUpdateIndividual(dto, addToLastAddedUser);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to add/update pair {Uid}: {Error}", dto.User.UID, result.Error);
- return;
- }
-
- PublishPairDataChanged();
- }
-
- public void HandleUserAddPair(UserFullPairDto dto)
- {
- var result = _pairManager.AddOrUpdateIndividual(dto);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to add/update full pair {Uid}: {Error}", dto.User.UID, result.Error);
- return;
- }
-
- PublishPairDataChanged();
- }
-
- public void HandleUserRemovePair(UserDto dto)
- {
- var removal = _pairManager.RemoveIndividual(dto);
- if (removal.Success && removal.Value is { } registration && registration.CharacterIdent is not null)
- {
- _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true);
- }
- else if (!removal.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("RemoveIndividual failed for {Uid}: {Error}", dto.User.UID, removal.Error);
- }
-
- if (removal.Success)
- {
- _pendingCharacterData.TryRemove(dto.User.UID, out _);
- PublishPairDataChanged();
- }
- }
-
- public void HandleUserStatus(UserIndividualPairStatusDto dto)
- {
- var result = _pairManager.SetIndividualStatus(dto);
- if (!result.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update individual pair status for {Uid}: {Error}", dto.User.UID, result.Error);
- return;
- }
-
- PublishPairDataChanged();
- }
-
- public void HandleUserOnline(OnlineUserIdentDto dto, bool sendNotification)
- {
- var wasOnline = false;
- PairConnection? previousConnection = null;
- if (_pairManager.TryGetPair(dto.User.UID, out var existingConnection))
- {
- previousConnection = existingConnection;
- wasOnline = existingConnection.IsOnline;
- }
-
- var registrationResult = _pairManager.MarkOnline(dto);
- if (!registrationResult.Success)
- {
- _logger.LogDebug("MarkOnline failed for {Uid}: {Error}", dto.User.UID, registrationResult.Error);
- return;
- }
-
- var registration = registrationResult.Value;
- if (registration.CharacterIdent is null)
- {
- _logger.LogDebug("Online registration for {Uid} missing ident.", dto.User.UID);
- }
- else
- {
- var handlerResult = _handlerRegistry.RegisterOnlinePair(registration);
- if (!handlerResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("RegisterOnlinePair failed for {Uid}: {Error}", dto.User.UID, handlerResult.Error);
- }
- }
-
- var connectionResult = _pairManager.GetPair(dto.User.UID);
- var connection = connectionResult.Success ? connectionResult.Value : previousConnection;
- if (connection is not null)
- {
- _mediator.Publish(new ClearProfileUserDataMessage(connection.User));
- }
- else
- {
- _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
- }
-
- if (!wasOnline)
- {
- NotifyUserOnline(connection, sendNotification);
- }
-
- if (registration.CharacterIdent is not null &&
- _pendingCharacterData.TryRemove(dto.User.UID, out var pendingData))
- {
- var pendingRegistration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), registration.CharacterIdent);
- var pendingApply = _handlerRegistry.ApplyCharacterData(pendingRegistration, pendingData);
- if (!pendingApply.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Applying pending character data for {Uid} failed: {Error}", dto.User.UID, pendingApply.Error);
- }
- }
-
- PublishPairDataChanged();
- }
-
- public void HandleUserOffline(UserData user)
- {
- var registrationResult = _pairManager.MarkOffline(user);
- if (registrationResult.Success)
- {
- _pendingCharacterData.TryRemove(user.UID, out _);
- if (registrationResult.Value.CharacterIdent is not null)
- {
- _ = _handlerRegistry.DeregisterOfflinePair(registrationResult.Value);
- }
-
- _mediator.Publish(new ClearProfileUserDataMessage(user));
- PublishPairDataChanged();
- }
- else if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("MarkOffline failed for {Uid}: {Error}", user.UID, registrationResult.Error);
- }
- }
-
- public void HandleUserPermissions(UserPermissionsDto dto)
- {
- var pairResult = _pairManager.GetPair(dto.User.UID);
- if (!pairResult.Success)
- {
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Permission update received for unknown pair {Uid}", dto.User.UID);
- }
- return;
- }
-
- var connection = pairResult.Value;
- var previous = connection.OtherToSelfPermissions;
-
- var updateResult = _pairManager.UpdateOtherPermissions(dto);
- if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error);
- return;
- }
-
- PublishPairDataChanged();
-
- if (previous.IsPaused() != dto.Permissions.IsPaused())
- {
- _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
-
- if (connection.Ident is not null)
- {
- var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused());
- if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error);
- }
- }
- }
-
- if (!connection.IsPaused && connection.Ident is not null)
- {
- ReapplyLastKnownData(dto.User.UID, connection.Ident);
- }
- }
-
- public void HandleSelfPermissions(UserPermissionsDto dto)
- {
- var pairResult = _pairManager.GetPair(dto.User.UID);
- if (!pairResult.Success)
- {
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Self permission update received for unknown pair {Uid}", dto.User.UID);
- }
- return;
- }
-
- var connection = pairResult.Value;
- var previous = connection.SelfToOtherPermissions;
-
- var updateResult = _pairManager.UpdateSelfPermissions(dto);
- if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update self permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error);
- return;
- }
-
- PublishPairDataChanged();
-
- if (previous.IsPaused() != dto.Permissions.IsPaused())
- {
- _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
-
- if (connection.Ident is not null)
- {
- var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused());
- if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error);
- }
- }
- }
-
- if (!connection.IsPaused && connection.Ident is not null)
- {
- ReapplyLastKnownData(dto.User.UID, connection.Ident);
- }
- }
-
- public void HandleUploadStatus(UserDto dto)
- {
- var pairResult = _pairManager.GetPair(dto.User.UID);
- if (!pairResult.Success)
- {
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Upload status received for unknown pair {Uid}", dto.User.UID);
- }
- return;
- }
-
- var connection = pairResult.Value;
- if (connection.Ident is null)
- {
- return;
- }
-
- var setResult = _handlerRegistry.SetUploading(new PairUniqueIdentifier(dto.User.UID), connection.Ident, true);
- if (!setResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Failed to set uploading for {Uid}: {Error}", dto.User.UID, setResult.Error);
- }
- }
-
- public void HandleCharacterData(OnlineUserCharaDataDto dto)
- {
- var pairResult = _pairManager.GetPair(dto.User.UID);
- if (!pairResult.Success)
- {
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Character data received for unknown pair {Uid}, queued for later.", dto.User.UID);
- }
- _pendingCharacterData[dto.User.UID] = dto;
- return;
- }
-
- var connection = pairResult.Value;
- _mediator.Publish(new EventMessage(new Event(connection.User, nameof(PairCoordinator), EventSeverity.Informational, "Received Character Data")));
- if (connection.Ident is null)
- {
- if (_logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("Character data received for {Uid} without ident, queued for later.", dto.User.UID);
- }
- _pendingCharacterData[dto.User.UID] = dto;
- return;
- }
-
- _pendingCharacterData.TryRemove(dto.User.UID, out _);
- var registration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident);
- var applyResult = _handlerRegistry.ApplyCharacterData(registration, dto);
- if (!applyResult.Success && _logger.IsEnabled(LogLevel.Debug))
- {
- _logger.LogDebug("ApplyCharacterData queued for {Uid}: {Error}", dto.User.UID, applyResult.Error);
- }
- }
-
- public void HandleProfile(UserDto dto)
- {
- _mediator.Publish(new ClearProfileUserDataMessage(dto.User));
- }
}
diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs
index 2114c35..6950186 100644
--- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs
+++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs
@@ -28,6 +28,9 @@ using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// orchestrates the lifecycle of a paired character
+///
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
{
string Ident { get; }
diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs b/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs
index 6c43119..48d3c9e 100644
--- a/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs
+++ b/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
using System.Threading.Tasks;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
@@ -11,12 +10,14 @@ using Microsoft.Extensions.Logging;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// creates, tracks, and removes pair handlers
+///
public sealed class PairHandlerRegistry : IDisposable
{
private readonly object _gate = new();
- private readonly Dictionary _identToHandler = new(StringComparer.Ordinal);
- private readonly Dictionary> _handlerToPairs = new();
- private readonly Dictionary _waitingRequests = new(StringComparer.Ordinal);
+ private readonly Dictionary _entriesByIdent = new(StringComparer.Ordinal);
+ private readonly Dictionary _entriesByHandler = new();
private readonly IPairHandlerAdapterFactory _handlerFactory;
private readonly PairManager _pairManager;
@@ -24,7 +25,6 @@ public sealed class PairHandlerRegistry : IDisposable
private readonly ILogger _logger;
private readonly TimeSpan _deletionGracePeriod = TimeSpan.FromMinutes(5);
- private readonly TimeSpan _waitForHandlerGracePeriod = TimeSpan.FromMinutes(2);
public PairHandlerRegistry(
IPairHandlerAdapterFactory handlerFactory,
@@ -42,7 +42,7 @@ public sealed class PairHandlerRegistry : IDisposable
{
lock (_gate)
{
- return _handlerToPairs.Keys.Count(handler => handler.IsVisible);
+ return _entriesByHandler.Keys.Count(handler => handler.IsVisible);
}
}
@@ -50,7 +50,7 @@ public sealed class PairHandlerRegistry : IDisposable
{
lock (_gate)
{
- return _identToHandler.TryGetValue(ident, out var handler) && handler.IsVisible;
+ return _entriesByIdent.TryGetValue(ident, out var entry) && entry.Handler.IsVisible;
}
}
@@ -64,16 +64,10 @@ public sealed class PairHandlerRegistry : IDisposable
IPairHandlerAdapter handler;
lock (_gate)
{
- handler = GetOrAddHandler(registration.CharacterIdent);
+ var entry = GetOrCreateEntry(registration.CharacterIdent);
+ handler = entry.Handler;
handler.ScheduledForDeletion = false;
-
- if (!_handlerToPairs.TryGetValue(handler, out var set))
- {
- set = new HashSet();
- _handlerToPairs[handler] = set;
- }
-
- set.Add(registration.PairIdent);
+ entry.AddPair(registration.PairIdent);
}
ApplyPauseStateForHandler(handler);
@@ -109,25 +103,23 @@ public sealed class PairHandlerRegistry : IDisposable
lock (_gate)
{
- if (!_identToHandler.TryGetValue(registration.CharacterIdent, out handler))
+ if (!_entriesByIdent.TryGetValue(registration.CharacterIdent, out var entry))
{
return PairOperationResult.Fail($"Ident {registration.CharacterIdent} not registered.");
}
- if (_handlerToPairs.TryGetValue(handler, out var set))
+ handler = entry.Handler;
+ entry.RemovePair(registration.PairIdent);
+ if (entry.PairCount == 0)
{
- set.Remove(registration.PairIdent);
- if (set.Count == 0)
+ if (forceDisposal)
{
- if (forceDisposal)
- {
- shouldDisposeImmediately = true;
- }
- else
- {
- shouldScheduleRemoval = true;
- handler.ScheduledForDeletion = true;
- }
+ shouldDisposeImmediately = true;
+ }
+ else
+ {
+ shouldScheduleRemoval = true;
+ handler.ScheduledForDeletion = true;
}
}
}
@@ -154,13 +146,7 @@ public sealed class PairHandlerRegistry : IDisposable
return PairOperationResult.Fail($"Character data received without ident for {registration.PairIdent.UserId}.");
}
- IPairHandlerAdapter? handler;
- lock (_gate)
- {
- _identToHandler.TryGetValue(registration.CharacterIdent, out handler);
- }
-
- if (handler is null)
+ if (!TryGetHandler(registration.CharacterIdent, out var handler) || handler is null)
{
var registerResult = RegisterOnlinePair(registration);
if (!registerResult.Success)
@@ -168,30 +154,19 @@ public sealed class PairHandlerRegistry : IDisposable
return PairOperationResult.Fail(registerResult.Error);
}
- lock (_gate)
+ if (!TryGetHandler(registration.CharacterIdent, out handler) || handler is null)
{
- _identToHandler.TryGetValue(registration.CharacterIdent, out handler);
+ return PairOperationResult.Fail($"Handler not ready for {registration.PairIdent.UserId}.");
}
}
- if (handler is null)
- {
- return PairOperationResult.Fail($"Handler not ready for {registration.PairIdent.UserId}.");
- }
-
handler.ApplyData(dto.CharaData);
return PairOperationResult.Ok();
}
public PairOperationResult ApplyLastReceivedData(PairUniqueIdentifier pairIdent, string ident, bool forced = false)
{
- IPairHandlerAdapter? handler;
- lock (_gate)
- {
- _identToHandler.TryGetValue(ident, out handler);
- }
-
- if (handler is null)
+ if (!TryGetHandler(ident, out var handler) || handler is null)
{
return PairOperationResult.Fail($"Cannot reapply data: handler for {pairIdent.UserId} not found.");
}
@@ -202,13 +177,7 @@ public sealed class PairHandlerRegistry : IDisposable
public PairOperationResult SetUploading(PairUniqueIdentifier pairIdent, string ident, bool uploading)
{
- IPairHandlerAdapter? handler;
- lock (_gate)
- {
- _identToHandler.TryGetValue(ident, out handler);
- }
-
- if (handler is null)
+ if (!TryGetHandler(ident, out var handler) || handler is null)
{
return PairOperationResult.Fail($"Cannot set uploading for {pairIdent.UserId}: handler not found.");
}
@@ -219,44 +188,31 @@ public sealed class PairHandlerRegistry : IDisposable
public PairOperationResult SetPausedState(PairUniqueIdentifier pairIdent, string ident, bool paused)
{
- IPairHandlerAdapter? handler;
- lock (_gate)
- {
- _identToHandler.TryGetValue(ident, out handler);
- }
-
- if (handler is null)
+ if (!TryGetHandler(ident, out var handler) || handler is null)
{
return PairOperationResult.Fail($"Cannot update pause state for {pairIdent.UserId}: handler not found.");
}
_ = paused; // value reflected in pair manager already
- // Recalculate pause state against all registered pairs to ensure consistency across contexts.
ApplyPauseStateForHandler(handler);
return PairOperationResult.Ok();
}
public PairOperationResult> GetPairConnections(string ident)
{
- IPairHandlerAdapter? handler;
- HashSet? identifiers = null;
-
+ PairHandlerEntry? entry;
lock (_gate)
{
- _identToHandler.TryGetValue(ident, out handler);
- if (handler is not null)
- {
- _handlerToPairs.TryGetValue(handler, out identifiers);
- }
+ _entriesByIdent.TryGetValue(ident, out entry);
}
- if (handler is null || identifiers is null)
+ if (entry is null)
{
return PairOperationResult>.Fail($"No handler registered for {ident}.");
}
var list = new List<(PairUniqueIdentifier, PairConnection)>();
- foreach (var pairIdent in identifiers)
+ foreach (var pairIdent in entry.SnapshotPairs())
{
var result = _pairManager.GetPair(pairIdent.UserId);
if (result.Success)
@@ -279,8 +235,8 @@ public sealed class PairHandlerRegistry : IDisposable
{
lock (_gate)
{
- var success = _identToHandler.TryGetValue(ident, out var resolved);
- handler = resolved;
+ var success = _entriesByIdent.TryGetValue(ident, out var entry);
+ handler = entry?.Handler;
return success;
}
}
@@ -289,7 +245,7 @@ public sealed class PairHandlerRegistry : IDisposable
{
lock (_gate)
{
- return _identToHandler.Values.Distinct().ToList();
+ return _entriesByHandler.Keys.ToList();
}
}
@@ -297,9 +253,9 @@ public sealed class PairHandlerRegistry : IDisposable
{
lock (_gate)
{
- if (_handlerToPairs.TryGetValue(handler, out var pairs))
+ if (_entriesByHandler.TryGetValue(handler, out var entry))
{
- return pairs.ToList();
+ return entry.SnapshotPairs();
}
}
@@ -330,17 +286,9 @@ public sealed class PairHandlerRegistry : IDisposable
List handlers;
lock (_gate)
{
- handlers = _identToHandler.Values.Distinct().ToList();
- _identToHandler.Clear();
- _handlerToPairs.Clear();
-
- foreach (var pending in _waitingRequests.Values)
- {
- pending.Cancel();
- pending.Dispose();
- }
-
- _waitingRequests.Clear();
+ handlers = _entriesByHandler.Keys.ToList();
+ _entriesByIdent.Clear();
+ _entriesByHandler.Clear();
}
foreach (var handler in handlers)
@@ -364,14 +312,9 @@ public sealed class PairHandlerRegistry : IDisposable
List handlers;
lock (_gate)
{
- handlers = _identToHandler.Values.Distinct().ToList();
- _identToHandler.Clear();
- _handlerToPairs.Clear();
- foreach (var kv in _waitingRequests.Values)
- {
- kv.Cancel();
- }
- _waitingRequests.Clear();
+ handlers = _entriesByHandler.Keys.ToList();
+ _entriesByIdent.Clear();
+ _entriesByHandler.Clear();
}
foreach (var handler in handlers)
@@ -380,46 +323,23 @@ public sealed class PairHandlerRegistry : IDisposable
}
}
- private IPairHandlerAdapter GetOrAddHandler(string ident)
+ private PairHandlerEntry GetOrCreateEntry(string ident)
{
- if (_identToHandler.TryGetValue(ident, out var handler))
+ if (_entriesByIdent.TryGetValue(ident, out var entry))
{
- return handler;
+ return entry;
}
- handler = _handlerFactory.Create(ident);
- _identToHandler[ident] = handler;
- _handlerToPairs[handler] = new HashSet();
- return handler;
- }
-
- private void EnsureInitialized(IPairHandlerAdapter handler)
- {
- if (handler.Initialized)
- {
- return;
- }
-
- try
- {
- handler.Initialize();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Failed to initialize handler for {Ident}", handler.Ident);
- }
+ var handler = _handlerFactory.Create(ident);
+ entry = new PairHandlerEntry(ident, handler);
+ _entriesByIdent[ident] = entry;
+ _entriesByHandler[handler] = entry;
+ return entry;
}
private async Task RemoveAfterGracePeriodAsync(IPairHandlerAdapter handler)
{
- try
- {
- await Task.Delay(_deletionGracePeriod).ConfigureAwait(false);
- }
- catch (TaskCanceledException)
- {
- return;
- }
+ await Task.Delay(_deletionGracePeriod).ConfigureAwait(false);
if (TryFinalizeHandlerRemoval(handler))
{
@@ -431,63 +351,15 @@ public sealed class PairHandlerRegistry : IDisposable
{
lock (_gate)
{
- if (!_handlerToPairs.TryGetValue(handler, out var set) || set.Count > 0)
+ if (!_entriesByHandler.TryGetValue(handler, out var entry) || entry.HasPairs)
{
handler.ScheduledForDeletion = false;
return false;
}
- _handlerToPairs.Remove(handler);
- _identToHandler.Remove(handler.Ident);
-
- if (_waitingRequests.TryGetValue(handler.Ident, out var cts))
- {
- cts.Cancel();
- cts.Dispose();
- _waitingRequests.Remove(handler.Ident);
- }
-
+ _entriesByHandler.Remove(handler);
+ _entriesByIdent.Remove(entry.Ident);
return true;
}
}
-
- private async Task WaitThenApplyDataAsync(PairRegistration registration, OnlineUserCharaDataDto dto, CancellationTokenSource cts)
- {
- var token = cts.Token;
- try
- {
- while (!token.IsCancellationRequested)
- {
- IPairHandlerAdapter? handler;
- lock (_gate)
- {
- _identToHandler.TryGetValue(registration.CharacterIdent!, out handler);
- }
-
- if (handler is not null && handler.Initialized)
- {
- handler.ApplyData(dto.CharaData);
- break;
- }
-
- await Task.Delay(TimeSpan.FromMilliseconds(500), token).ConfigureAwait(false);
- }
- }
- catch (OperationCanceledException)
- {
- // expected
- }
- finally
- {
- lock (_gate)
- {
- if (_waitingRequests.TryGetValue(registration.CharacterIdent!, out var existing) && existing == cts)
- {
- _waitingRequests.Remove(registration.CharacterIdent!);
- }
- }
-
- cts.Dispose();
- }
- }
}
diff --git a/LightlessSync/PlayerData/Pairs/PairLedger.cs b/LightlessSync/PlayerData/Pairs/PairLedger.cs
index 1e0e359..1593a7c 100644
--- a/LightlessSync/PlayerData/Pairs/PairLedger.cs
+++ b/LightlessSync/PlayerData/Pairs/PairLedger.cs
@@ -12,6 +12,9 @@ using Microsoft.Extensions.Logging;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// keeps pair info for ui and reapplication
+///
public sealed class PairLedger : DisposableMediatorSubscriberBase
{
private readonly PairManager _pairManager;
diff --git a/LightlessSync/PlayerData/Pairs/PairManager.cs b/LightlessSync/PlayerData/Pairs/PairManager.cs
index adbe5b8..95525b3 100644
--- a/LightlessSync/PlayerData/Pairs/PairManager.cs
+++ b/LightlessSync/PlayerData/Pairs/PairManager.cs
@@ -9,6 +9,9 @@ using LightlessSync.API.Dto.User;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// in memory state for pairs, groups, and syncshells
+///
public sealed class PairManager
{
private readonly object _gate = new();
diff --git a/LightlessSync/PlayerData/Pairs/PairState.cs b/LightlessSync/PlayerData/Pairs/PairModels.cs
similarity index 69%
rename from LightlessSync/PlayerData/Pairs/PairState.cs
rename to LightlessSync/PlayerData/Pairs/PairModels.cs
index 0e2a508..015a2a8 100644
--- a/LightlessSync/PlayerData/Pairs/PairState.cs
+++ b/LightlessSync/PlayerData/Pairs/PairModels.cs
@@ -7,42 +7,27 @@ using LightlessSync.API.Dto.Group;
namespace LightlessSync.PlayerData.Pairs;
-public readonly struct PairOperationResult
+///
+/// core models for the pair system
+///
+public sealed class PairState
{
- private PairOperationResult(bool success, string? error)
- {
- Success = success;
- Error = error;
- }
+ public CharacterData? CharacterData { get; set; }
+ public Guid? TemporaryCollectionId { get; set; }
- public bool Success { get; }
- public string? Error { get; }
-
- public static PairOperationResult Ok() => new(true, null);
-
- public static PairOperationResult Fail(string error) => new(false, error);
+ public bool IsEmpty => CharacterData is null && (TemporaryCollectionId is null || TemporaryCollectionId == Guid.Empty);
}
-public readonly struct PairOperationResult
-{
- private PairOperationResult(bool success, T value, string? error)
- {
- Success = success;
- Value = value;
- Error = error;
- }
-
- public bool Success { get; }
- public T Value { get; }
- public string? Error { get; }
-
- public static PairOperationResult Ok(T value) => new(true, value, null);
-
- public static PairOperationResult Fail(string error) => new(false, default!, error);
-}
+public readonly record struct PairUniqueIdentifier(string UserId);
+///
+/// link between a pair id and character ident
+///
public sealed record PairRegistration(PairUniqueIdentifier PairIdent, string? CharacterIdent);
+///
+/// per group membership info for a pair
+///
public sealed class GroupPairRelationship
{
public GroupPairRelationship(string groupId, GroupPairUserInfo? info)
@@ -60,6 +45,9 @@ public sealed class GroupPairRelationship
}
}
+///
+/// runtime view of a single pair connection
+///
public sealed class PairConnection
{
public PairConnection(UserData user)
@@ -121,6 +109,9 @@ public sealed class PairConnection
}
}
+///
+/// syncshell metadata plus member connections
+///
public sealed class Syncshell
{
public Syncshell(GroupFullInfoDto dto)
@@ -138,12 +129,94 @@ public sealed class Syncshell
}
}
-public sealed class PairState
+///
+/// simple success/failure result
+///
+public readonly struct PairOperationResult
{
- public CharacterData? CharacterData { get; set; }
- public Guid? TemporaryCollectionId { get; set; }
+ private PairOperationResult(bool success, string? error)
+ {
+ Success = success;
+ Error = error;
+ }
- public bool IsEmpty => CharacterData is null && (TemporaryCollectionId is null || TemporaryCollectionId == Guid.Empty);
+ public bool Success { get; }
+ public string? Error { get; }
+
+ public static PairOperationResult Ok() => new(true, null);
+
+ public static PairOperationResult Fail(string error) => new(false, error);
}
-public readonly record struct PairUniqueIdentifier(string UserId);
+///
+/// typed success/failure result
+///
+public readonly struct PairOperationResult
+{
+ private PairOperationResult(bool success, T value, string? error)
+ {
+ Success = success;
+ Value = value;
+ Error = error;
+ }
+
+ public bool Success { get; }
+ public T Value { get; }
+ public string? Error { get; }
+
+ public static PairOperationResult Ok(T value) => new(true, value, null);
+
+ public static PairOperationResult Fail(string error) => new(false, default!, error);
+}
+
+///
+/// state of which optional plugin warnings were shown
+///
+public record OptionalPluginWarning
+{
+ public bool ShownHeelsWarning { get; set; } = false;
+ public bool ShownCustomizePlusWarning { get; set; } = false;
+ public bool ShownHonorificWarning { get; set; } = false;
+ public bool ShownMoodlesWarning { get; set; } = false;
+ public bool ShowPetNicknamesWarning { get; set; } = false;
+}
+
+///
+/// tracks the handler registered pairs for an ident
+///
+internal sealed class PairHandlerEntry
+{
+ private readonly HashSet _pairs = new();
+
+ public PairHandlerEntry(string ident, IPairHandlerAdapter handler)
+ {
+ Ident = ident;
+ Handler = handler;
+ }
+
+ public string Ident { get; }
+ public IPairHandlerAdapter Handler { get; }
+
+ public bool HasPairs => _pairs.Count > 0;
+ public int PairCount => _pairs.Count;
+
+ public void AddPair(PairUniqueIdentifier pair)
+ {
+ _pairs.Add(pair);
+ }
+
+ public bool RemovePair(PairUniqueIdentifier pair)
+ {
+ return _pairs.Remove(pair);
+ }
+
+ public IReadOnlyCollection SnapshotPairs()
+ {
+ if (_pairs.Count == 0)
+ {
+ return Array.Empty();
+ }
+
+ return _pairs.ToArray();
+ }
+}
diff --git a/LightlessSync/PlayerData/Pairs/PairStateCache.cs b/LightlessSync/PlayerData/Pairs/PairStateCache.cs
index 67e8c8c..b6a7872 100644
--- a/LightlessSync/PlayerData/Pairs/PairStateCache.cs
+++ b/LightlessSync/PlayerData/Pairs/PairStateCache.cs
@@ -6,6 +6,9 @@ using LightlessSync.Utils;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// cache for character/pair data and penumbra collections
+///
public sealed class PairStateCache
{
private readonly ConcurrentDictionary _cache = new(StringComparer.Ordinal);
diff --git a/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs b/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs
index 1840813..805bc26 100644
--- a/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs
+++ b/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs
@@ -14,6 +14,9 @@ using Microsoft.Extensions.Logging;
namespace LightlessSync.PlayerData.Pairs;
+///
+/// pushes character data to visible pairs
+///
public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase
{
private readonly ApiController _apiController;