This commit is contained in:
2025-11-25 07:14:59 +09:00
parent 9c794137c1
commit ef592032b3
111 changed files with 20622 additions and 3476 deletions

View File

@@ -1,5 +1,6 @@
using LightlessSync.API.Data;
using LightlessSync.API.Data;
using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Chat;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using Microsoft.AspNetCore.SignalR.Client;
@@ -41,6 +42,30 @@ public partial class ApiController
await _lightlessHub!.SendAsync(nameof(TryPairWithContentId), otherCid).ConfigureAwait(false);
}
public async Task UpdateChatPresence(ChatPresenceUpdateDto presence)
{
if (!IsConnected || _lightlessHub is null) return;
await _lightlessHub.InvokeAsync(nameof(UpdateChatPresence), presence).ConfigureAwait(false);
}
public async Task SendChatMessage(ChatSendRequestDto request)
{
if (!IsConnected || _lightlessHub is null) return;
await _lightlessHub.InvokeAsync(nameof(SendChatMessage), request).ConfigureAwait(false);
}
public async Task ReportChatMessage(ChatReportSubmitDto request)
{
if (!IsConnected || _lightlessHub is null) return;
await _lightlessHub.InvokeAsync(nameof(ReportChatMessage), request).ConfigureAwait(false);
}
public async Task<ChatParticipantResolveResultDto?> ResolveChatParticipant(ChatParticipantResolveRequestDto request)
{
if (!IsConnected || _lightlessHub is null) return null;
return await _lightlessHub.InvokeAsync<ChatParticipantResolveResultDto?>(nameof(ResolveChatParticipant), request).ConfigureAwait(false);
}
public async Task SetBroadcastStatus(bool enabled, GroupBroadcastRequestDto? groupDto = null)
{
CheckConnection();
@@ -88,6 +113,12 @@ public partial class ApiController
return await _lightlessHub!.InvokeAsync<UserProfileDto>(nameof(UserGetProfile), dto).ConfigureAwait(false);
}
public async Task<UserProfileDto?> UserGetLightfinderProfile(string hashedCid)
{
if (!IsConnected) return null;
return await _lightlessHub!.InvokeAsync<UserProfileDto?>(nameof(UserGetLightfinderProfile), hashedCid).ConfigureAwait(false);
}
public async Task UserPushData(UserCharaDataMessageDto dto)
{
try

View File

@@ -1,7 +1,9 @@
using System;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Dto;
using LightlessSync.API.Dto.CharaData;
using LightlessSync.API.Dto.Chat;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using LightlessSync.LightlessConfiguration.Models;
@@ -24,21 +26,21 @@ public partial class ApiController
public Task Client_GroupChangePermissions(GroupPermissionDto groupPermission)
{
Logger.LogTrace("Client_GroupChangePermissions: {perm}", groupPermission);
ExecuteSafely(() => _pairManager.SetGroupPermissions(groupPermission));
ExecuteSafely(() => _pairCoordinator.HandleGroupChangePermissions(groupPermission));
return Task.CompletedTask;
}
public Task Client_GroupChangeUserPairPermissions(GroupPairUserPermissionDto dto)
{
Logger.LogDebug("Client_GroupChangeUserPairPermissions: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdateGroupPairPermissions(dto));
ExecuteSafely(() => _pairCoordinator.HandleGroupPairPermissions(dto));
return Task.CompletedTask;
}
public Task Client_GroupDelete(GroupDto groupDto)
{
Logger.LogTrace("Client_GroupDelete: {dto}", groupDto);
ExecuteSafely(() => _pairManager.RemoveGroup(groupDto.Group));
ExecuteSafely(() => _pairCoordinator.HandleGroupRemoved(groupDto));
return Task.CompletedTask;
}
@@ -47,8 +49,8 @@ public partial class ApiController
Logger.LogTrace("Client_GroupPairChangeUserInfo: {dto}", userInfo);
ExecuteSafely(() =>
{
if (string.Equals(userInfo.UID, UID, StringComparison.Ordinal)) _pairManager.SetGroupStatusInfo(userInfo);
else _pairManager.SetGroupPairStatusInfo(userInfo);
var isSelf = string.Equals(userInfo.UID, UID, StringComparison.Ordinal);
_pairCoordinator.HandleGroupPairStatus(userInfo, isSelf);
});
return Task.CompletedTask;
}
@@ -56,28 +58,28 @@ public partial class ApiController
public Task Client_GroupPairJoined(GroupPairFullInfoDto groupPairInfoDto)
{
Logger.LogTrace("Client_GroupPairJoined: {dto}", groupPairInfoDto);
ExecuteSafely(() => _pairManager.AddGroupPair(groupPairInfoDto));
ExecuteSafely(() => _pairCoordinator.HandleGroupPairJoined(groupPairInfoDto));
return Task.CompletedTask;
}
public Task Client_GroupPairLeft(GroupPairDto groupPairDto)
{
Logger.LogTrace("Client_GroupPairLeft: {dto}", groupPairDto);
ExecuteSafely(() => _pairManager.RemoveGroupPair(groupPairDto));
ExecuteSafely(() => _pairCoordinator.HandleGroupPairLeft(groupPairDto));
return Task.CompletedTask;
}
public Task Client_GroupSendFullInfo(GroupFullInfoDto groupInfo)
{
Logger.LogTrace("Client_GroupSendFullInfo: {dto}", groupInfo);
ExecuteSafely(() => _pairManager.AddGroup(groupInfo));
ExecuteSafely(() => _pairCoordinator.HandleGroupFullInfo(groupInfo));
return Task.CompletedTask;
}
public Task Client_GroupSendInfo(GroupInfoDto groupInfo)
{
Logger.LogTrace("Client_GroupSendInfo: {dto}", groupInfo);
ExecuteSafely(() => _pairManager.SetGroupInfo(groupInfo));
ExecuteSafely(() => _pairCoordinator.HandleGroupInfoUpdate(groupInfo));
return Task.CompletedTask;
}
@@ -129,52 +131,62 @@ public partial class ApiController
return Task.CompletedTask;
}
public Task Client_ChatReceive(ChatMessageDto message)
{
Logger.LogTrace("Client_ChatReceive: {@channel}", message.Channel);
ExecuteSafely(() => ChatMessageReceived?.Invoke(message));
return Task.CompletedTask;
}
public Task Client_UpdateUserIndividualPairStatusDto(UserIndividualPairStatusDto dto)
{
Logger.LogDebug("Client_UpdateUserIndividualPairStatusDto: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdateIndividualPairStatus(dto));
ExecuteSafely(() => _pairCoordinator.HandleUserStatus(dto));
return Task.CompletedTask;
}
public Task Client_UserAddClientPair(UserPairDto dto)
{
Logger.LogDebug("Client_UserAddClientPair: {dto}", dto);
ExecuteSafely(() => _pairManager.AddUserPair(dto, addToLastAddedUser: true));
ExecuteSafely(() => _pairCoordinator.HandleUserAddPair(dto, addToLastAddedUser: true));
return Task.CompletedTask;
}
public Task Client_UserReceiveCharacterData(OnlineUserCharaDataDto dataDto)
{
Logger.LogTrace("Client_UserReceiveCharacterData: {user}", dataDto.User);
ExecuteSafely(() => _pairManager.ReceiveCharaData(dataDto));
ExecuteSafely(() => _pairCoordinator.HandleCharacterData(dataDto));
return Task.CompletedTask;
}
public Task Client_UserReceiveUploadStatus(UserDto dto)
{
Logger.LogTrace("Client_UserReceiveUploadStatus: {dto}", dto);
ExecuteSafely(() => _pairManager.ReceiveUploadStatus(dto));
ExecuteSafely(() =>
{
_pairCoordinator.HandleUploadStatus(dto);
});
return Task.CompletedTask;
}
public Task Client_UserRemoveClientPair(UserDto dto)
{
Logger.LogDebug("Client_UserRemoveClientPair: {dto}", dto);
ExecuteSafely(() => _pairManager.RemoveUserPair(dto));
ExecuteSafely(() => _pairCoordinator.HandleUserRemovePair(dto));
return Task.CompletedTask;
}
public Task Client_UserSendOffline(UserDto dto)
{
Logger.LogDebug("Client_UserSendOffline: {dto}", dto);
ExecuteSafely(() => _pairManager.MarkPairOffline(dto.User));
ExecuteSafely(() => _pairCoordinator.HandleUserOffline(dto.User));
return Task.CompletedTask;
}
public Task Client_UserSendOnline(OnlineUserIdentDto dto)
{
Logger.LogDebug("Client_UserSendOnline: {dto}", dto);
ExecuteSafely(() => _pairManager.MarkPairOnline(dto));
ExecuteSafely(() => _pairCoordinator.HandleUserOnline(dto, sendNotification: true));
return Task.CompletedTask;
}
@@ -188,7 +200,7 @@ public partial class ApiController
public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto)
{
Logger.LogDebug("Client_UserUpdateOtherPairPermissions: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdatePairPermissions(dto));
ExecuteSafely(() => _pairCoordinator.HandleUserPermissions(dto));
return Task.CompletedTask;
}
@@ -209,7 +221,7 @@ public partial class ApiController
public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto)
{
Logger.LogDebug("Client_UserUpdateSelfPairPermissions: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdateSelfPairPermissions(dto));
ExecuteSafely(() => _pairCoordinator.HandleSelfPermissions(dto));
return Task.CompletedTask;
}

View File

@@ -1,9 +1,14 @@
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Utility;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Chat;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.Chat;
using LightlessSync.API.Dto.User;
using LightlessSync.API.SignalR;
using LightlessSync.LightlessConfiguration;
@@ -16,7 +21,6 @@ using LightlessSync.WebAPI.SignalR;
using LightlessSync.WebAPI.SignalR.Utils;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using System.Reflection;
namespace LightlessSync.WebAPI;
@@ -28,7 +32,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
private readonly DalamudUtilService _dalamudUtil;
private readonly HubFactory _hubFactory;
private readonly PairManager _pairManager;
private readonly PairCoordinator _pairCoordinator;
private readonly PairRequestService _pairRequestService;
private readonly ServerConfigurationManager _serverManager;
private readonly TokenProvider _tokenProvider;
@@ -42,14 +46,17 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
private HubConnection? _lightlessHub;
private ServerState _serverState;
private CensusUpdateMessage? _lastCensus;
private IReadOnlyList<ZoneChatChannelInfoDto> _zoneChatChannels = Array.Empty<ZoneChatChannelInfoDto>();
private IReadOnlyList<GroupChatChannelInfoDto> _groupChatChannels = Array.Empty<GroupChatChannelInfoDto>();
private event Action<ChatMessageDto>? ChatMessageReceived;
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
PairManager pairManager, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator,
PairCoordinator pairCoordinator, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator,
TokenProvider tokenProvider, LightlessConfigService lightlessConfigService, NotificationService lightlessNotificationService) : base(logger, mediator)
{
_hubFactory = hubFactory;
_dalamudUtil = dalamudUtil;
_pairManager = pairManager;
_pairCoordinator = pairCoordinator;
_pairRequestService = pairRequestService;
_serverManager = serverManager;
_tokenProvider = tokenProvider;
@@ -61,7 +68,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
Mediator.Subscribe<HubClosedMessage>(this, (msg) => LightlessHubOnClosed(msg.Exception));
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = LightlessHubOnReconnectedAsync());
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => LightlessHubOnReconnecting(msg.Exception));
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePauseAsync(msg.UserData));
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePauseAsync(msg.Pair));
Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg);
Mediator.Subscribe<PauseMessage>(this, (msg) => _ = PauseAsync(msg.UserData));
@@ -106,15 +113,65 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
public SystemInfoDto SystemInfoDto { get; private set; } = new();
public IReadOnlyList<ZoneChatChannelInfoDto> ZoneChatChannels => _zoneChatChannels;
public IReadOnlyList<GroupChatChannelInfoDto> GroupChatChannels => _groupChatChannels;
public string UID => _connectionDto?.User.UID ?? string.Empty;
public event Action? OnConnected;
public async Task<bool> CheckClientHealth()
{
return await _lightlessHub!.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
var hub = _lightlessHub;
if (hub is null || !IsConnected)
{
return false;
}
try
{
return await hub.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogDebug(ex, "Client health check failed.");
return false;
}
}
public async Task RefreshChatChannelsAsync()
{
if (_lightlessHub is null || !IsConnected)
return;
await Task.WhenAll(GetZoneChatChannelsAsync(), GetGroupChatChannelsAsync()).ConfigureAwait(false);
}
public async Task<IReadOnlyList<ZoneChatChannelInfoDto>> GetZoneChatChannelsAsync()
{
if (_lightlessHub is null || !IsConnected)
return _zoneChatChannels;
var channels = await _lightlessHub.InvokeAsync<IReadOnlyList<ZoneChatChannelInfoDto>>("GetZoneChatChannels").ConfigureAwait(false);
_zoneChatChannels = channels;
return channels;
}
public async Task<IReadOnlyList<GroupChatChannelInfoDto>> GetGroupChatChannelsAsync()
{
if (_lightlessHub is null || !IsConnected)
return _groupChatChannels;
var channels = await _lightlessHub.InvokeAsync<IReadOnlyList<GroupChatChannelInfoDto>>("GetGroupChatChannels").ConfigureAwait(false);
_groupChatChannels = channels;
return channels;
}
Task<IReadOnlyList<ZoneChatChannelInfoDto>> ILightlessHub.GetZoneChatChannels()
=> _lightlessHub!.InvokeAsync<IReadOnlyList<ZoneChatChannelInfoDto>>("GetZoneChatChannels");
Task<IReadOnlyList<GroupChatChannelInfoDto>> ILightlessHub.GetGroupChatChannels()
=> _lightlessHub!.InvokeAsync<IReadOnlyList<GroupChatChannelInfoDto>>("GetGroupChatChannels");
public async Task CreateConnectionsAsync()
{
if (!_serverManager.ShownCensusPopup)
@@ -337,35 +394,86 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
private bool _naggedAboutLod = false;
public Task CyclePauseAsync(UserData userData)
public Task CyclePauseAsync(Pair pair)
{
CancellationTokenSource cts = new();
cts.CancelAfter(TimeSpan.FromSeconds(5));
ArgumentNullException.ThrowIfNull(pair);
return CyclePauseAsync(pair.UniqueIdent);
}
public Task CyclePauseAsync(PairUniqueIdentifier ident)
{
var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
_ = Task.Run(async () =>
{
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
var perm = pair.UserPair!.OwnPermissions;
perm.SetPaused(paused: true);
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
// wait until it's changed
while (pair.UserPair!.OwnPermissions != perm)
var token = timeoutCts.Token;
try
{
await Task.Delay(250, cts.Token).ConfigureAwait(false);
Logger.LogTrace("Waiting for permissions change for {data}", userData);
if (!_pairCoordinator.Ledger.TryGetEntry(ident, out var entry) || entry is null)
{
Logger.LogWarning("CyclePauseAsync: pair {uid} not found in ledger", ident.UserId);
return;
}
var originalPermissions = entry.SelfPermissions;
var targetPermissions = originalPermissions;
targetPermissions.SetPaused(!originalPermissions.IsPaused());
await UserSetPairPermissions(new UserPermissionsDto(entry.User, targetPermissions)).ConfigureAwait(false);
var applied = false;
while (!token.IsCancellationRequested)
{
if (_pairCoordinator.Ledger.TryGetEntry(ident, out var updated) && updated is not null)
{
if (updated.SelfPermissions == targetPermissions)
{
applied = true;
entry = updated;
break;
}
}
await Task.Delay(250, token).ConfigureAwait(false);
Logger.LogTrace("Waiting for permissions change for {uid}", ident.UserId);
}
if (!applied)
{
Logger.LogWarning("CyclePauseAsync timed out waiting for pause acknowledgement for {uid}", ident.UserId);
return;
}
Logger.LogDebug("CyclePauseAsync toggled paused for {uid} to {state}", ident.UserId, targetPermissions.IsPaused());
}
perm.SetPaused(paused: false);
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
}, cts.Token).ContinueWith((t) => cts.Dispose());
catch (OperationCanceledException)
{
Logger.LogDebug("CyclePauseAsync cancelled for {uid}", ident.UserId);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "CyclePauseAsync failed for {uid}", ident.UserId);
}
finally
{
timeoutCts.Dispose();
}
}, CancellationToken.None);
return Task.CompletedTask;
}
public async Task PauseAsync(UserData userData)
{
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
var perm = pair.UserPair!.OwnPermissions;
perm.SetPaused(paused: true);
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
var pairIdent = new PairUniqueIdentifier(userData.UID);
if (!_pairCoordinator.Ledger.TryGetEntry(pairIdent, out var entry) || entry is null)
{
Logger.LogWarning("PauseAsync: pair {uid} not found in ledger", userData.UID);
return;
}
var permissions = entry.SelfPermissions;
permissions.SetPaused(paused: true);
await UserSetPairPermissions(new UserPermissionsDto(userData, permissions)).ConfigureAwait(false);
}
public Task<ConnectionDto> GetConnectionDto() => GetConnectionDtoAsync(true);
@@ -388,8 +496,13 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
private async Task ClientHealthCheckAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested && _lightlessHub != null)
while (!ct.IsCancellationRequested)
{
if (_lightlessHub is null)
{
break;
}
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
Logger.LogDebug("Checking Client Health State");
@@ -455,6 +568,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
_lightlessHub.On(nameof(Client_ChatReceive), (Func<ChatMessageDto, Task>)Client_ChatReceive);
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto));
@@ -470,18 +584,36 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
_initialized = true;
}
private readonly HashSet<Action<ChatMessageDto>> _chatHandlers = new();
public void RegisterChatMessageHandler(Action<ChatMessageDto> handler)
{
if (_chatHandlers.Add(handler))
{
ChatMessageReceived += handler;
}
}
public void UnregisterChatMessageHandler(Action<ChatMessageDto> handler)
{
if (_chatHandlers.Remove(handler))
{
ChatMessageReceived -= handler;
}
}
private async Task LoadIninitialPairsAsync()
{
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
{
Logger.LogDebug("Group: {entry}", entry);
_pairManager.AddGroup(entry);
_pairCoordinator.HandleGroupFullInfo(entry);
}
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
{
Logger.LogDebug("Individual Pair: {userPair}", userPair);
_pairManager.AddUserPair(userPair);
_pairCoordinator.HandleUserAddPair(userPair);
}
}
@@ -498,7 +630,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
foreach (var entry in await UserGetOnlinePairs(dto).ConfigureAwait(false))
{
Logger.LogDebug("Pair online: {pair}", entry);
_pairManager.MarkPairOnline(entry, sendNotif: false);
_pairCoordinator.HandleUserOnline(entry, sendNotification: false);
}
}
@@ -602,49 +734,12 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
Mediator.Publish(new DisconnectedMessage());
_lightlessHub = null;
_connectionDto = null;
_zoneChatChannels = Array.Empty<ZoneChatChannelInfoDto>();
_groupChatChannels = Array.Empty<GroupChatChannelInfoDto>();
}
ServerState = state;
}
public Task<UserProfileDto?> UserGetLightfinderProfile(string hashedCid)
{
throw new NotImplementedException();
}
public Task UpdateChatPresence(ChatPresenceUpdateDto presence)
{
throw new NotImplementedException();
}
public Task Client_ChatReceive(ChatMessageDto message)
{
throw new NotImplementedException();
}
public Task<IReadOnlyList<ZoneChatChannelInfoDto>> GetZoneChatChannels()
{
throw new NotImplementedException();
}
public Task<IReadOnlyList<GroupChatChannelInfoDto>> GetGroupChatChannels()
{
throw new NotImplementedException();
}
public Task SendChatMessage(ChatSendRequestDto request)
{
throw new NotImplementedException();
}
public Task ReportChatMessage(ChatReportSubmitDto request)
{
throw new NotImplementedException();
}
public Task<ChatParticipantResolveResultDto?> ResolveChatParticipant(ChatParticipantResolveRequestDto request)
{
throw new NotImplementedException();
}
}
#pragma warning restore MA0040
#pragma warning restore MA0040