using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Dalamud.Logging; using MareSynchronos.API; using MareSynchronos.Utils; using MareSynchronos.WebAPI.Utils; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR.Client; namespace MareSynchronos.WebAPI { public delegate void VoidDelegate(); public delegate void SimpleStringDelegate(string str); public enum ServerState { Offline, Disconnected, Connected, Unauthorized, VersionMisMatch, NoAccount } public partial class ApiController : IDisposable { public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)"; public const string MainServiceUri = "wss://v2202207178628194299.powersrv.de:6871"; public readonly int[] SupportedServerVersions = { API.API.Version }; private readonly Configuration _pluginConfiguration; private readonly DalamudUtil _dalamudUtil; private CancellationTokenSource _connectionCancellationTokenSource; private HubConnection? _fileHub; private HubConnection? _connectionHub; private HubConnection? _adminHub; private CancellationTokenSource? _uploadCancellationTokenSource; private HubConnection? _userHub; private ConnectionDto? _connectionDto; public SystemInfoDto SystemInfoDto { get; private set; } = new(); public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false); public bool IsAdmin => _connectionDto?.IsAdmin ?? false; public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil) { Logger.Verbose("Creating " + nameof(ApiController)); _pluginConfiguration = pluginConfiguration; _dalamudUtil = dalamudUtil; _connectionCancellationTokenSource = new CancellationTokenSource(); _dalamudUtil.LogIn += DalamudUtilOnLogIn; _dalamudUtil.LogOut += DalamudUtilOnLogOut; if (_dalamudUtil.IsLoggedIn) { DalamudUtilOnLogIn(); } } private void DalamudUtilOnLogOut() { Task.Run(async () => await StopAllConnections(_connectionCancellationTokenSource.Token)); } private void DalamudUtilOnLogIn() { Task.Run(CreateConnections); } public event EventHandler? CharacterReceived; public event VoidDelegate? RegisterFinalized; public event VoidDelegate? Connected; public event VoidDelegate? Disconnected; public event SimpleStringDelegate? PairedClientOffline; public event SimpleStringDelegate? PairedClientOnline; public event SimpleStringDelegate? PairedWithOther; public event SimpleStringDelegate? UnpairedFromOther; public List CurrentDownloads { get; } = new(); public List CurrentUploads { get; } = new(); public List ForbiddenTransfers { get; } = new(); public List AdminBannedUsers { get; private set; } = new(); public List AdminForbiddenFiles { get; private set; } = new(); public bool IsConnected => ServerState == ServerState.Connected; public bool IsDownloading => CurrentDownloads.Count > 0; public bool IsUploading => CurrentUploads.Count > 0; public List PairedClients { get; set; } = new(); public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? _pluginConfiguration.ClientSecret[ApiUri] : "-"; public bool ServerAlive => (_connectionHub?.State ?? HubConnectionState.Disconnected) == HubConnectionState.Connected; public Dictionary ServerDictionary => new Dictionary() { { MainServiceUri, MainServer } } .Concat(_pluginConfiguration.CustomServerList) .ToDictionary(k => k.Key, k => k.Value); public string UID => _connectionDto?.UID ?? string.Empty; private string ApiUri => _pluginConfiguration.ApiUri; public int OnlineUsers => SystemInfoDto.OnlineUsers; public ServerState ServerState { get { var supportedByServer = SupportedServerVersions.Contains(_connectionDto?.ServerVersion ?? 0); bool hasUid = !string.IsNullOrEmpty(UID); if (_pluginConfiguration.FullPause) return ServerState.Disconnected; if (!ServerAlive) return ServerState.Offline; if (!hasUid && _pluginConfiguration.ClientSecret.ContainsKey(ApiUri)) return ServerState.Unauthorized; if (!supportedByServer) return ServerState.VersionMisMatch; if (supportedByServer && hasUid) return ServerState.Connected; return ServerState.NoAccount; } } public async Task CreateConnections() { Logger.Verbose("Recreating Connection"); await StopAllConnections(_connectionCancellationTokenSource.Token); _connectionCancellationTokenSource.Cancel(); _connectionCancellationTokenSource = new CancellationTokenSource(); var token = _connectionCancellationTokenSource.Token; while (ServerState is not ServerState.Connected && !token.IsCancellationRequested) { await StopAllConnections(_connectionCancellationTokenSource.Token); try { Logger.Debug("Building connection"); while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested) { Logger.Debug("Player not loaded in yet, waiting"); await Task.Delay(TimeSpan.FromSeconds(1), token); } if (token.IsCancellationRequested) break; _connectionHub = BuildHubConnection(ConnectionHubAPI.Path); _userHub = BuildHubConnection(UserHubAPI.Path); _fileHub = BuildHubConnection(FilesHubAPI.Path); _adminHub = BuildHubConnection(AdminHubAPI.Path); await _connectionHub.StartAsync(token); await _userHub.StartAsync(token); await _fileHub.StartAsync(token); await _adminHub.StartAsync(token); _connectionHub.On(ConnectionHubAPI.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto); if (_pluginConfiguration.FullPause) { _connectionDto = null; return; } _connectionDto = await _connectionHub.InvokeAsync(ConnectionHubAPI.InvokeHeartbeat, token); if (ServerState is ServerState.Connected) // user is authorized && server is legit { Logger.Debug("Initializing data"); _userHub.On(UserHubAPI.OnUpdateClientPairs, UpdateLocalClientPairsCallback); _userHub.On(UserHubAPI.OnReceiveCharacterData, ReceiveCharacterDataCallback); _userHub.On(UserHubAPI.OnRemoveOnlinePairedPlayer, (s) => PairedClientOffline?.Invoke(s)); _userHub.On(UserHubAPI.OnAddOnlinePairedPlayer, (s) => PairedClientOnline?.Invoke(s)); _adminHub.On(AdminHubAPI.OnForcedReconnect, UserForcedReconnectCallback); PairedClients = await _userHub!.InvokeAsync>(UserHubAPI.InvokeGetPairedClients, token); _connectionHub.Closed += ConnectionHubOnClosed; _connectionHub.Reconnected += ConnectionHubOnReconnected; _connectionHub.Reconnecting += ConnectionHubOnReconnecting; if (IsModerator) { AdminForbiddenFiles = await _adminHub.InvokeAsync>(AdminHubAPI.InvokeGetForbiddenFiles, token); AdminBannedUsers = await _adminHub.InvokeAsync>(AdminHubAPI.InvokeGetBannedUsers, token); _adminHub.On(AdminHubAPI.OnUpdateOrAddBannedUser, UpdateOrAddBannedUserCallback); _adminHub.On(AdminHubAPI.OnDeleteBannedUser, DeleteBannedUserCallback); _adminHub.On(AdminHubAPI.OnUpdateOrAddForbiddenFile, UpdateOrAddForbiddenFileCallback); _adminHub.On(AdminHubAPI.OnDeleteForbiddenFile, DeleteForbiddenFileCallback); } Connected?.Invoke(); } else if (ServerState is ServerState.VersionMisMatch or ServerState.NoAccount or ServerState.Unauthorized) { break; } } catch (Exception ex) { Logger.Warn(ex.Message); Logger.Warn(ex.StackTrace ?? string.Empty); Logger.Debug("Failed to establish connection, retrying"); await StopAllConnections(token); await Task.Delay(TimeSpan.FromSeconds(5), token); } } } public void Dispose() { Logger.Verbose("Disposing " + nameof(ApiController)); _dalamudUtil.LogIn -= DalamudUtilOnLogIn; _dalamudUtil.LogOut -= DalamudUtilOnLogOut; Task.Run(async () => await StopAllConnections(_connectionCancellationTokenSource.Token)); } private HubConnection BuildHubConnection(string hubName) { return new HubConnectionBuilder() .WithUrl(ApiUri + hubName, options => { if (!string.IsNullOrEmpty(SecretKey) && !_pluginConfiguration.FullPause) { options.Headers.Add("Authorization", SecretKey); options.Headers.Add("CharacterNameHash", _dalamudUtil.PlayerNameHashed); } options.Transports = HttpTransportType.WebSockets; }) .WithAutomaticReconnect(new ForeverRetryPolicy()) .Build(); } private Task ConnectionHubOnClosed(Exception? arg) { CurrentUploads.Clear(); CurrentDownloads.Clear(); _uploadCancellationTokenSource?.Cancel(); Logger.Debug("Connection closed"); Disconnected?.Invoke(); return Task.CompletedTask; } private Task ConnectionHubOnReconnected(string? arg) { Logger.Debug("Connection restored"); _connectionDto = _connectionHub!.InvokeAsync(ConnectionHubAPI.InvokeHeartbeat).Result; Connected?.Invoke(); return Task.CompletedTask; } private Task ConnectionHubOnReconnecting(Exception? arg) { CurrentUploads.Clear(); CurrentDownloads.Clear(); _uploadCancellationTokenSource?.Cancel(); Logger.Debug("Connection closed... Reconnecting"); Disconnected?.Invoke(); return Task.CompletedTask; } private async Task StopAllConnections(CancellationToken token) { Logger.Verbose("Stopping all connections"); if (_connectionHub is not null) { await _connectionHub.StopAsync(token); _connectionHub.Closed -= ConnectionHubOnClosed; _connectionHub.Reconnected -= ConnectionHubOnReconnected; _connectionHub.Reconnecting += ConnectionHubOnReconnecting; await _connectionHub.DisposeAsync(); _connectionHub = null; } if (_fileHub is not null) { await _fileHub.StopAsync(token); await _fileHub.DisposeAsync(); _fileHub = null; } if (_userHub is not null) { await _userHub.StopAsync(token); await _userHub.DisposeAsync(); _userHub = null; } if (_adminHub is not null) { await _adminHub.StopAsync(token); await _adminHub.DisposeAsync(); _adminHub = null; } } } }