[Draft] Update 0.8 (#46)

* move stuff out into file transfer manager

* obnoxious unsupported version text, adjustments to filetransfermanager

* add back file upload transfer progress

* restructure code

* cleanup some more stuff I guess

* downloadids by playername

* individual anim/sound bs

* fix migration stuff, finalize impl of individual sound/anim pause

* fixes with logging stuff

* move download manager to transient

* rework dl ui first iteration

* some refactoring and cleanup

* more code cleanup

* refactoring

* switch to hostbuilder

* some more rework I guess

* more refactoring

* clean up mediator calls and disposal

* fun code cleanup

* push error message when log level is set to anything but information in non-debug builds

* remove notificationservice

* move message to after login

* add download bars to gameworld

* fixes download progress bar

* set gpose ui min and max size

* remove unnecessary usings

* adjustments to reconnection logic

* add options to set visible/offline groups visibility

* add impl of uploading display, transfer list in settings ui

* attempt to fix issues with server selection

* add back download status to compact ui

* make dl bar fixed size based

* some fixes for upload/download handling

* adjust text from Syncing back to Uploading

---------

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
Co-authored-by: Stanley Dimant <stanley.dimant@varian.com>
This commit is contained in:
rootdarkarchon
2023-03-14 19:48:35 +01:00
committed by GitHub
parent 0824ba434b
commit 0c87e84f25
109 changed files with 7323 additions and 6488 deletions

View File

@@ -0,0 +1,90 @@
using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.User;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using System.Text;
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task PushCharacterData(CharacterData data, List<UserData> visibleCharacters)
{
if (!IsConnected) return;
try
{
await PushCharacterDataInternal(data, visibleCharacters.ToList()).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Logger.LogDebug("Upload operation was cancelled");
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error during upload of files");
}
}
public async Task UserAddPair(UserDto user)
{
if (!IsConnected) return;
await _mareHub!.SendAsync(nameof(UserAddPair), user).ConfigureAwait(false);
}
public async Task UserDelete()
{
CheckConnection();
await _mareHub!.SendAsync(nameof(UserDelete)).ConfigureAwait(false);
await CreateConnections().ConfigureAwait(false);
}
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs()
{
return await _mareHub!.InvokeAsync<List<OnlineUserIdentDto>>(nameof(UserGetOnlinePairs)).ConfigureAwait(false);
}
public async Task<List<UserPairDto>> UserGetPairedClients()
{
return await _mareHub!.InvokeAsync<List<UserPairDto>>(nameof(UserGetPairedClients)).ConfigureAwait(false);
}
public async Task UserPushData(UserCharaDataMessageDto dto)
{
try
{
await _mareHub!.InvokeAsync(nameof(UserPushData), dto).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to Push character data");
}
}
public async Task UserRemovePair(UserDto userDto)
{
if (!IsConnected) return;
await _mareHub!.SendAsync(nameof(UserRemovePair), userDto).ConfigureAwait(false);
}
public async Task UserSetPairPermissions(UserPermissionsDto userPermissions)
{
await _mareHub!.SendAsync(nameof(UserSetPairPermissions), userPermissions).ConfigureAwait(false);
}
private async Task PushCharacterDataInternal(CharacterData character, List<UserData> visibleCharacters)
{
Logger.LogInformation("Pushing character data for {hash} to {charas}", character.DataHash.Value, string.Join(", ", visibleCharacters.Select(c => c.AliasOrUID)));
StringBuilder sb = new();
foreach (var kvp in character.FileReplacements.ToList())
{
sb.AppendLine($"FileReplacements for {kvp.Key}: {kvp.Value.Count}");
}
foreach (var item in character.GlamourerData)
{
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
}
Logger.LogDebug("Chara data contained: {nl} {data}", Environment.NewLine, sb.ToString());
await UserPushData(new(visibleCharacters, character)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,297 @@
using Dalamud.Interface.Internal.Notifications;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Dto;
using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Services.Mediator;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public Task Client_DownloadReady(Guid requestId)
{
Logger.LogDebug("Server sent {requestId} ready", requestId);
Mediator.Publish(new DownloadReadyMessage(requestId));
return Task.CompletedTask;
}
public Task Client_GroupChangePermissions(GroupPermissionDto groupPermission)
{
Logger.LogTrace("Client_GroupChangePermissions: {perm}", groupPermission);
ExecuteSafely(() => _pairManager.SetGroupPermissions(groupPermission));
return Task.CompletedTask;
}
public Task Client_GroupDelete(GroupDto groupDto)
{
Logger.LogTrace("Client_GroupDelete: {dto}", groupDto);
ExecuteSafely(() => _pairManager.RemoveGroup(groupDto.Group));
return Task.CompletedTask;
}
public Task Client_GroupPairChangePermissions(GroupPairUserPermissionDto permissionDto)
{
Logger.LogTrace("Client_GroupPairChangePermissions: {perm}", permissionDto);
ExecuteSafely(() =>
{
if (string.Equals(permissionDto.UID, UID, StringComparison.Ordinal)) _pairManager.SetGroupUserPermissions(permissionDto);
else _pairManager.SetGroupPairUserPermissions(permissionDto);
});
return Task.CompletedTask;
}
public Task Client_GroupPairChangeUserInfo(GroupPairUserInfoDto userInfo)
{
Logger.LogTrace("Client_GroupPairChangeUserInfo: {dto}", userInfo);
ExecuteSafely(() =>
{
if (string.Equals(userInfo.UID, UID, StringComparison.Ordinal)) _pairManager.SetGroupStatusInfo(userInfo);
else _pairManager.SetGroupPairStatusInfo(userInfo);
});
return Task.CompletedTask;
}
public Task Client_GroupPairJoined(GroupPairFullInfoDto groupPairInfoDto)
{
Logger.LogTrace("Client_GroupPairJoined: {dto}", groupPairInfoDto);
ExecuteSafely(() => _pairManager.AddGroupPair(groupPairInfoDto));
return Task.CompletedTask;
}
public Task Client_GroupPairLeft(GroupPairDto groupPairDto)
{
Logger.LogTrace("Client_GroupPairLeft: {dto}", groupPairDto);
ExecuteSafely(() => _pairManager.RemoveGroupPair(groupPairDto));
return Task.CompletedTask;
}
public Task Client_GroupSendFullInfo(GroupFullInfoDto groupInfo)
{
Logger.LogTrace("Client_GroupSendFullInfo: {dto}", groupInfo);
ExecuteSafely(() => _pairManager.AddGroup(groupInfo));
return Task.CompletedTask;
}
public Task Client_GroupSendInfo(GroupInfoDto groupInfo)
{
Logger.LogTrace("Client_GroupSendInfo: {dto}", groupInfo);
ExecuteSafely(() => _pairManager.SetGroupInfo(groupInfo));
return Task.CompletedTask;
}
public Task Client_ReceiveServerMessage(MessageSeverity messageSeverity, string message)
{
switch (messageSeverity)
{
case MessageSeverity.Error:
Mediator.Publish(new NotificationMessage("Warning from " + _serverManager.CurrentServer!.ServerName, message, NotificationType.Error, 7500));
break;
case MessageSeverity.Warning:
Mediator.Publish(new NotificationMessage("Warning from " + _serverManager.CurrentServer!.ServerName, message, NotificationType.Warning, 7500));
break;
case MessageSeverity.Information:
if (_doNotNotifyOnNextInfo)
{
_doNotNotifyOnNextInfo = false;
break;
}
Mediator.Publish(new NotificationMessage("Info from " + _serverManager.CurrentServer!.ServerName, message, NotificationType.Info, 5000));
break;
}
return Task.CompletedTask;
}
public Task Client_UpdateSystemInfo(SystemInfoDto systemInfo)
{
SystemInfoDto = systemInfo;
return Task.CompletedTask;
}
public Task Client_UserAddClientPair(UserPairDto dto)
{
Logger.LogDebug("Client_UserAddClientPair: {dto}", dto);
ExecuteSafely(() => _pairManager.AddUserPair(dto));
return Task.CompletedTask;
}
public Task Client_UserReceiveCharacterData(OnlineUserCharaDataDto dataDto)
{
Logger.LogTrace("Client_UserReceiveCharacterData: {user}", dataDto.User);
ExecuteSafely(() => _pairManager.ReceiveCharaData(dataDto));
return Task.CompletedTask;
}
public Task Client_UserReceiveUploadStatus(UserDto dto)
{
Logger.LogTrace("Client_UserReceiveUploadStatus: {dto}", dto);
ExecuteSafely(() => _pairManager.ReceiveUploadStatus(dto));
return Task.CompletedTask;
}
public Task Client_UserRemoveClientPair(UserDto dto)
{
Logger.LogDebug("Client_UserRemoveClientPair: {dto}", dto);
ExecuteSafely(() => _pairManager.RemoveUserPair(dto));
return Task.CompletedTask;
}
public Task Client_UserSendOffline(UserDto dto)
{
Logger.LogDebug("Client_UserSendOffline: {dto}", dto);
ExecuteSafely(() => _pairManager.MarkPairOffline(dto.User));
return Task.CompletedTask;
}
public Task Client_UserSendOnline(OnlineUserIdentDto dto)
{
Logger.LogDebug("Client_UserSendOnline: {dto}", dto);
ExecuteSafely(() => _pairManager.MarkPairOnline(dto));
return Task.CompletedTask;
}
public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto)
{
Logger.LogDebug("Client_UserUpdateOtherPairPermissions: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdatePairPermissions(dto));
return Task.CompletedTask;
}
public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto)
{
Logger.LogDebug("Client_UserUpdateSelfPairPermissions: {dto}", dto);
ExecuteSafely(() => _pairManager.UpdateSelfPairPermissions(dto));
return Task.CompletedTask;
}
public void OnDownloadReady(Action<Guid> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_DownloadReady), act);
}
public void OnGroupChangePermissions(Action<GroupPermissionDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupChangePermissions), act);
}
public void OnGroupDelete(Action<GroupDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupDelete), act);
}
public void OnGroupPairChangePermissions(Action<GroupPairUserPermissionDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairChangePermissions), act);
}
public void OnGroupPairChangeUserInfo(Action<GroupPairUserInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairChangeUserInfo), act);
}
public void OnGroupPairJoined(Action<GroupPairFullInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairJoined), act);
}
public void OnGroupPairLeft(Action<GroupPairDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairLeft), act);
}
public void OnGroupSendFullInfo(Action<GroupFullInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupSendFullInfo), act);
}
public void OnGroupSendInfo(Action<GroupInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupSendInfo), act);
}
public void OnReceiveServerMessage(Action<MessageSeverity, string> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_ReceiveServerMessage), act);
}
public void OnUpdateSystemInfo(Action<SystemInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UpdateSystemInfo), act);
}
public void OnUserAddClientPair(Action<UserPairDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserAddClientPair), act);
}
public void OnUserReceiveCharacterData(Action<OnlineUserCharaDataDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserReceiveCharacterData), act);
}
public void OnUserReceiveUploadStatus(Action<UserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserReceiveUploadStatus), act);
}
public void OnUserRemoveClientPair(Action<UserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserRemoveClientPair), act);
}
public void OnUserSendOffline(Action<UserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserSendOffline), act);
}
public void OnUserSendOnline(Action<OnlineUserIdentDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserSendOnline), act);
}
public void OnUserUpdateOtherPairPermissions(Action<UserPermissionsDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserUpdateOtherPairPermissions), act);
}
public void OnUserUpdateSelfPairPermissions(Action<UserPermissionsDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserUpdateSelfPairPermissions), act);
}
private void ExecuteSafely(Action act)
{
try
{
act();
}
catch (Exception ex)
{
Logger.LogCritical(ex, "Error on executing safely");
}
}
}

View File

@@ -0,0 +1,115 @@
using MareSynchronos.API.Dto.Group;
using MareSynchronos.WebAPI.SignalR.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task GroupBanUser(GroupPairDto dto, string reason)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupBanUser), dto, reason).ConfigureAwait(false);
}
public async Task GroupChangeGroupPermissionState(GroupPermissionDto dto)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupChangeGroupPermissionState), dto).ConfigureAwait(false);
}
public async Task GroupChangeIndividualPermissionState(GroupPairUserPermissionDto dto)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupChangeIndividualPermissionState), dto).ConfigureAwait(false);
}
public async Task GroupChangeOwnership(GroupPairDto groupPair)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupChangeOwnership), groupPair).ConfigureAwait(false);
}
public async Task<bool> GroupChangePassword(GroupPasswordDto groupPassword)
{
CheckConnection();
return await _mareHub!.InvokeAsync<bool>(nameof(GroupChangePassword), groupPassword).ConfigureAwait(false);
}
public async Task GroupClear(GroupDto group)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
}
public async Task<GroupPasswordDto> GroupCreate()
{
CheckConnection();
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate)).ConfigureAwait(false);
}
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
{
CheckConnection();
return await _mareHub!.InvokeAsync<List<string>>(nameof(GroupCreateTempInvite), group, amount).ConfigureAwait(false);
}
public async Task GroupDelete(GroupDto group)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupDelete), group).ConfigureAwait(false);
}
public async Task<List<BannedGroupUserDto>> GroupGetBannedUsers(GroupDto group)
{
CheckConnection();
return await _mareHub!.InvokeAsync<List<BannedGroupUserDto>>(nameof(GroupGetBannedUsers), group).ConfigureAwait(false);
}
public async Task<bool> GroupJoin(GroupPasswordDto passwordedGroup)
{
CheckConnection();
return await _mareHub!.InvokeAsync<bool>(nameof(GroupJoin), passwordedGroup).ConfigureAwait(false);
}
public async Task GroupLeave(GroupDto group)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupLeave), group).ConfigureAwait(false);
}
public async Task GroupRemoveUser(GroupPairDto groupPair)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupRemoveUser), groupPair).ConfigureAwait(false);
}
public async Task GroupSetUserInfo(GroupPairUserInfoDto groupPair)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupSetUserInfo), groupPair).ConfigureAwait(false);
}
public async Task<List<GroupFullInfoDto>> GroupsGetAll()
{
CheckConnection();
return await _mareHub!.InvokeAsync<List<GroupFullInfoDto>>(nameof(GroupsGetAll)).ConfigureAwait(false);
}
public async Task<List<GroupPairFullInfoDto>> GroupsGetUsersInGroup(GroupDto group)
{
CheckConnection();
return await _mareHub!.InvokeAsync<List<GroupPairFullInfoDto>>(nameof(GroupsGetUsersInGroup), group).ConfigureAwait(false);
}
public async Task GroupUnbanUser(GroupPairDto groupPair)
{
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupUnbanUser), groupPair).ConfigureAwait(false);
}
private void CheckConnection()
{
if (ServerState is not (ServerState.Connected or ServerState.Connecting or ServerState.Reconnecting)) throw new InvalidDataException("Not connected");
}
}

View File

@@ -0,0 +1,350 @@
using MareSynchronos.API.Routes;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using MareSynchronos.API.Dto;
using MareSynchronos.API.SignalR;
using Dalamud.Utility;
using System.Reflection;
using MareSynchronos.WebAPI.SignalR.Utils;
using MareSynchronos.WebAPI.SignalR;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Services;
namespace MareSynchronos.WebAPI;
public sealed partial class ApiController : DisposableMediatorSubscriberBase, IMareHubClient
{
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
public const string MainServiceUri = "wss://maresynchronos.com";
private readonly DalamudUtilService _dalamudUtil;
private readonly HubFactory _hubFactory;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverManager;
private CancellationTokenSource _connectionCancellationTokenSource;
private ConnectionDto? _connectionDto;
private bool _doNotNotifyOnNextInfo = false;
private CancellationTokenSource? _healthCheckTokenSource = new();
private bool _initialized;
private HubConnection? _mareHub;
private ServerState _serverState;
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
PairManager pairManager, ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator)
{
_hubFactory = hubFactory;
_dalamudUtil = dalamudUtil;
_pairManager = pairManager;
_serverManager = serverManager;
_connectionCancellationTokenSource = new CancellationTokenSource();
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
Mediator.Subscribe<HubClosedMessage>(this, (msg) => MareHubOnClosed(msg.Exception));
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = Task.Run(MareHubOnReconnected));
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception));
ServerState = ServerState.Offline;
if (_dalamudUtil.IsLoggedIn)
{
DalamudUtilOnLogIn();
}
}
public string AuthFailureMessage { get; private set; } = string.Empty;
public Version CurrentClientVersion => _connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0);
public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty;
public bool IsConnected => ServerState == ServerState.Connected;
public bool IsCurrentVersion => (Assembly.GetExecutingAssembly().GetName().Version ?? new Version(0, 0, 0, 0)) >= (_connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0, 0));
public int OnlineUsers => SystemInfoDto.OnlineUsers;
public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected;
public ServerInfo ServerInfo => _connectionDto?.ServerInfo ?? new ServerInfo();
public ServerState ServerState
{
get => _serverState;
private set
{
Logger.LogDebug("New ServerState: {value}, prev ServerState: {_serverState}", value, _serverState);
_serverState = value;
}
}
public SystemInfoDto SystemInfoDto { get; private set; } = new();
public string UID => _connectionDto?.User.UID ?? string.Empty;
public async Task<bool> CheckClientHealth()
{
return await _mareHub!.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
}
public async Task CreateConnections(bool forceGetToken = false)
{
Logger.LogDebug("CreateConnections called");
if (_serverManager.CurrentServer?.FullPause ?? true)
{
Logger.LogInformation("Not recreating Connection, paused");
_connectionDto = null;
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
_connectionCancellationTokenSource.Cancel();
return;
}
var secretKey = _serverManager.GetSecretKey();
if (secretKey.IsNullOrEmpty())
{
Logger.LogWarning("No secret key set for current character");
_connectionDto = null;
await StopConnection(ServerState.NoSecretKey).ConfigureAwait(false);
_connectionCancellationTokenSource.Cancel();
return;
}
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
Logger.LogInformation("Recreating Connection");
_connectionCancellationTokenSource.Cancel();
_connectionCancellationTokenSource = new CancellationTokenSource();
var token = _connectionCancellationTokenSource.Token;
while (ServerState is not ServerState.Connected && !token.IsCancellationRequested)
{
AuthFailureMessage = string.Empty;
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
ServerState = ServerState.Connecting;
try
{
Logger.LogDebug("Building connection");
if (_serverManager.GetToken() == null || forceGetToken)
{
Logger.LogDebug("Requesting new JWT");
using HttpClient httpClient = new();
var postUri = MareAuth.AuthFullPath(new Uri(_serverManager.CurrentApiUrl
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
var auth = secretKey.GetHash256();
var result = await httpClient.PostAsync(postUri, new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("auth", auth),
new KeyValuePair<string, string>("charaIdent", _dalamudUtil.PlayerNameHashed),
})).ConfigureAwait(false);
AuthFailureMessage = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
result.EnsureSuccessStatusCode();
_serverManager.SaveToken(await result.Content.ReadAsStringAsync().ConfigureAwait(false));
Logger.LogDebug("JWT Success");
}
while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested)
{
Logger.LogDebug("Player not loaded in yet, waiting");
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
}
if (token.IsCancellationRequested) break;
_mareHub = _hubFactory.GetOrCreate();
await _mareHub.StartAsync(token).ConfigureAwait(false);
await InitializeData().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
ServerState = ServerState.Connected;
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
{
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
return;
}
}
catch (HttpRequestException ex)
{
Logger.LogWarning(ex, "HttpRequestException on Connection");
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await StopConnection(ServerState.Unauthorized).ConfigureAwait(false);
return;
}
ServerState = ServerState.Reconnecting;
Logger.LogInformation("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Exception on Connection");
Logger.LogInformation("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token).ConfigureAwait(false);
}
}
}
public async Task<ConnectionDto> GetConnectionDto()
{
var dto = await _mareHub!.InvokeAsync<ConnectionDto>(nameof(GetConnectionDto)).ConfigureAwait(false);
Mediator.Publish(new ConnectedMessage(dto));
return dto;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_healthCheckTokenSource?.Cancel();
Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
_connectionCancellationTokenSource?.Cancel();
}
private async Task ClientHealthCheck(CancellationToken ct)
{
while (!ct.IsCancellationRequested && _mareHub != null)
{
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
_ = await CheckClientHealth().ConfigureAwait(false);
Logger.LogDebug("Checked Client Health State");
}
}
private void DalamudUtilOnLogIn()
{
Task.Run(() => CreateConnections(forceGetToken: true));
}
private void DalamudUtilOnLogOut()
{
Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
ServerState = ServerState.Offline;
}
private async Task InitializeData()
{
if (_mareHub == null) return;
Logger.LogDebug("Initializing data");
OnDownloadReady((guid) => Client_DownloadReady(guid));
OnReceiveServerMessage((sev, msg) => Client_ReceiveServerMessage(sev, msg));
OnUpdateSystemInfo((dto) => Client_UpdateSystemInfo(dto));
OnUserSendOffline((dto) => Client_UserSendOffline(dto));
OnUserAddClientPair((dto) => Client_UserAddClientPair(dto));
OnUserReceiveCharacterData((dto) => Client_UserReceiveCharacterData(dto));
OnUserRemoveClientPair(dto => Client_UserRemoveClientPair(dto));
OnUserSendOnline(dto => Client_UserSendOnline(dto));
OnUserUpdateOtherPairPermissions(dto => Client_UserUpdateOtherPairPermissions(dto));
OnUserUpdateSelfPairPermissions(dto => Client_UserUpdateSelfPairPermissions(dto));
OnUserReceiveUploadStatus(dto => Client_UserReceiveUploadStatus(dto));
OnGroupChangePermissions((dto) => Client_GroupChangePermissions(dto));
OnGroupDelete((dto) => Client_GroupDelete(dto));
OnGroupPairChangePermissions((dto) => Client_GroupPairChangePermissions(dto));
OnGroupPairChangeUserInfo((dto) => Client_GroupPairChangeUserInfo(dto));
OnGroupPairJoined((dto) => Client_GroupPairJoined(dto));
OnGroupPairLeft((dto) => Client_GroupPairLeft(dto));
OnGroupSendFullInfo((dto) => Client_GroupSendFullInfo(dto));
OnGroupSendInfo((dto) => Client_GroupSendInfo(dto));
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
{
Logger.LogDebug("Individual Pair: {userPair}", userPair);
_pairManager.AddUserPair(userPair, addToLastAddedUser: false);
}
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
{
Logger.LogDebug("Group: {entry}", entry);
_pairManager.AddGroup(entry);
}
foreach (var group in _pairManager.GroupPairs.Keys)
{
var users = await GroupsGetUsersInGroup(group).ConfigureAwait(false);
foreach (var user in users)
{
Logger.LogDebug("Group Pair: {user}", user);
_pairManager.AddGroupPair(user);
}
}
foreach (var entry in await UserGetOnlinePairs().ConfigureAwait(false))
{
_pairManager.MarkPairOnline(entry, sendNotif: false);
}
_healthCheckTokenSource?.Cancel();
_healthCheckTokenSource?.Dispose();
_healthCheckTokenSource = new CancellationTokenSource();
_ = ClientHealthCheck(_healthCheckTokenSource.Token);
_initialized = true;
}
private void MareHubOnClosed(Exception? arg)
{
_healthCheckTokenSource?.Cancel();
Mediator.Publish(new DisconnectedMessage());
_pairManager.ClearPairs();
ServerState = ServerState.Offline;
if (arg != null)
{
Logger.LogWarning(arg, "Connection closed");
}
else
{
Logger.LogInformation("Connection closed");
}
}
private async Task MareHubOnReconnected()
{
ServerState = ServerState.Connecting;
try
{
await InitializeData().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
{
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
return;
}
ServerState = ServerState.Connected;
}
catch (Exception ex)
{
Logger.LogCritical(ex, "Failure to obtain data after reconnection");
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
}
}
private void MareHubOnReconnecting(Exception? arg)
{
_doNotNotifyOnNextInfo = true;
_healthCheckTokenSource?.Cancel();
ServerState = ServerState.Reconnecting;
Logger.LogWarning(arg, "Connection closed... Reconnecting");
}
private async Task StopConnection(ServerState state)
{
ServerState = state;
if (_mareHub is not null)
{
_initialized = false;
_healthCheckTokenSource?.Cancel();
Logger.LogInformation("Stopping existing connection");
await _hubFactory.DisposeHubAsync().ConfigureAwait(false);
Mediator.Publish(new DisconnectedMessage());
_mareHub = null;
_connectionDto = null;
}
}
}

View File

@@ -0,0 +1,117 @@
using MareSynchronos.API.SignalR;
using MareSynchronos.Interop;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.WebAPI.SignalR.Utils;
using MessagePack;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.WebAPI.SignalR;
public class HubFactory : MediatorSubscriberBase
{
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly MareConfigService _configService;
private HubConnection? _instance;
private bool _isDisposed = false;
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator, ServerConfigurationManager serverConfigurationManager, MareConfigService configService) : base(logger, mediator)
{
_serverConfigurationManager = serverConfigurationManager;
_configService = configService;
}
private HubConnection BuildHubConnection()
{
Logger.LogDebug("Building new HubConnection");
_instance = new HubConnectionBuilder()
.WithUrl(_serverConfigurationManager.CurrentApiUrl + IMareHub.Path, options =>
{
options.Headers.Add("Authorization", "Bearer " + _serverConfigurationManager.GetToken());
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
})
.AddMessagePackProtocol(opt =>
{
var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance,
BuiltinResolver.Instance,
AttributeFormatterResolver.Instance,
// replace enum resolver
DynamicEnumAsStringResolver.Instance,
DynamicGenericResolver.Instance,
DynamicUnionResolver.Instance,
DynamicObjectResolver.Instance,
PrimitiveObjectResolver.Instance,
// final fallback(last priority)
StandardResolver.Instance);
opt.SerializerOptions =
MessagePackSerializerOptions.Standard
.WithCompression(MessagePackCompression.Lz4Block)
.WithResolver(resolver);
})
.WithAutomaticReconnect(new ForeverRetryPolicy(Mediator))
.ConfigureLogging(a =>
{
a.ClearProviders().AddProvider(new DalamudLoggingProvider(_configService));
a.SetMinimumLevel(LogLevel.Information);
})
.Build();
_instance.Closed += HubOnClosed;
_instance.Reconnecting += HubOnReconnecting;
_instance.Reconnected += HubOnReconnected;
_isDisposed = false;
return _instance;
}
private Task HubOnReconnected(string? arg)
{
Mediator.Publish(new HubReconnectedMessage(arg));
return Task.CompletedTask;
}
private Task HubOnReconnecting(Exception? arg)
{
Mediator.Publish(new HubReconnectingMessage(arg));
return Task.CompletedTask;
}
private Task HubOnClosed(Exception? arg)
{
Mediator.Publish(new HubClosedMessage(arg));
return Task.CompletedTask;
}
public HubConnection GetOrCreate()
{
if (!_isDisposed && _instance != null) return _instance;
return BuildHubConnection();
}
public async Task DisposeHubAsync()
{
if (_instance == null || _isDisposed) return;
Logger.LogDebug("Disposing current HubConnection");
_isDisposed = true;
_instance.Closed -= HubOnClosed;
_instance.Reconnecting -= HubOnReconnecting;
_instance.Reconnected -= HubOnReconnected;
await _instance.StopAsync().ConfigureAwait(false);
await _instance.DisposeAsync().ConfigureAwait(false);
_instance = null;
}
}

View File

@@ -0,0 +1,39 @@
using Dalamud.Interface.Internal.Notifications;
using MareSynchronos.Services.Mediator;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI.SignalR.Utils;
public class ForeverRetryPolicy : IRetryPolicy
{
private readonly MareMediator _mediator;
private bool _sentDisconnected = false;
public ForeverRetryPolicy(MareMediator mediator)
{
_mediator = mediator;
}
public TimeSpan? NextRetryDelay(RetryContext retryContext)
{
TimeSpan timeToWait = TimeSpan.FromSeconds(new Random().Next(10, 20));
if (retryContext.PreviousRetryCount == 0)
{
_sentDisconnected = false;
timeToWait = TimeSpan.FromSeconds(3);
}
else if (retryContext.PreviousRetryCount == 1) timeToWait = TimeSpan.FromSeconds(5);
else if (retryContext.PreviousRetryCount == 2) timeToWait = TimeSpan.FromSeconds(10);
else
{
if (!_sentDisconnected)
{
_mediator.Publish(new NotificationMessage("Connection lost", "Connection lost to server", NotificationType.Warning, 5000));
_mediator.Publish(new DisconnectedMessage());
}
_sentDisconnected = true;
}
return timeToWait;
}
}

View File

@@ -0,0 +1,14 @@
namespace MareSynchronos.WebAPI.SignalR.Utils;
public enum ServerState
{
Offline,
Connecting,
Reconnecting,
Disconnected,
Connected,
Unauthorized,
VersionMisMatch,
RateLimited,
NoSecretKey,
}