From 61b178e2c0dc2539ab47b8478c2354ace9034b51 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 27 Jun 2022 01:19:45 +0200 Subject: [PATCH] rework the whole connection garbage/custom servers, other cleanups/refactors --- MareSynchronos/Configuration.cs | 2 +- .../Managers/CachedPlayersManager.cs | 28 +- MareSynchronos/Managers/FileCacheManager.cs | 10 +- MareSynchronos/Managers/IpcManager.cs | 4 +- MareSynchronos/Managers/PlayerManager.cs | 13 +- MareSynchronos/Models/CachedPlayer.cs | 19 +- MareSynchronos/Plugin.cs | 60 ++- MareSynchronos/UI/PluginUI.cs | 67 ++-- MareSynchronos/UI/UIShared.cs | 86 +++-- MareSynchronos/Utils/Crypto.cs | 7 + MareSynchronos/Utils/DalamudUtil.cs | 45 ++- MareSynchronos/WebAPI/ApiController.cs | 347 +++++++++--------- 12 files changed, 386 insertions(+), 302 deletions(-) diff --git a/MareSynchronos/Configuration.cs b/MareSynchronos/Configuration.cs index ed8ec17..e5a9d69 100644 --- a/MareSynchronos/Configuration.cs +++ b/MareSynchronos/Configuration.cs @@ -25,6 +25,7 @@ namespace MareSynchronos public string CacheFolder { get; set; } = string.Empty; public Dictionary ClientSecret { get; set; } = new(); + public Dictionary CustomServerList { get; set; } = new(); [JsonIgnore] public bool HasValidSetup => AcceptedAgreement && InitialScanComplete && !string.IsNullOrEmpty(CacheFolder) && Directory.Exists(CacheFolder) && ClientSecret.ContainsKey(ApiUri); @@ -46,7 +47,6 @@ namespace MareSynchronos public bool FullPause { get; set; } = false; public Dictionary UidComments { get; set; } = new(); - public bool UseCustomService { get; set; } = false; public int Version { get; set; } = 0; public bool ShowTransferWindow { get; set; } = true; diff --git a/MareSynchronos/Managers/CachedPlayersManager.cs b/MareSynchronos/Managers/CachedPlayersManager.cs index e80ad37..2b9b42c 100644 --- a/MareSynchronos/Managers/CachedPlayersManager.cs +++ b/MareSynchronos/Managers/CachedPlayersManager.cs @@ -4,12 +4,9 @@ using System.Linq; using System.Threading.Tasks; using Dalamud.Game; using Dalamud.Game.ClientState; -using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Objects.SubKinds; using MareSynchronos.Models; using MareSynchronos.Utils; using MareSynchronos.WebAPI; -using Penumbra.PlayerWatch; namespace MareSynchronos.Managers; @@ -20,23 +17,20 @@ public class CachedPlayersManager : IDisposable private readonly DalamudUtil _dalamudUtil; private readonly Framework _framework; private readonly IpcManager _ipcManager; - private readonly IPlayerWatcher _watcher; - private readonly ObjectTable _objectTable; private readonly List _onlineCachedPlayers = new(); private readonly List _localVisiblePlayers = new(); private DateTime _lastPlayerObjectCheck = DateTime.Now; - public CachedPlayersManager(ClientState clientState, Framework framework, ObjectTable objectTable, ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager, IPlayerWatcher watcher) + public CachedPlayersManager(ClientState clientState, Framework framework, + ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager) { Logger.Debug("Creating " + nameof(CachedPlayersManager)); _clientState = clientState; _framework = framework; - _objectTable = objectTable; _apiController = apiController; _dalamudUtil = dalamudUtil; _ipcManager = ipcManager; - _watcher = watcher; _clientState.Login += ClientStateOnLogin; _clientState.Logout += ClientStateOnLogout; @@ -53,7 +47,7 @@ public class CachedPlayersManager : IDisposable ClientStateOnLogin(null, EventArgs.Empty); } } - + private void IpcManagerOnPenumbraDisposed(object? sender, EventArgs e) { _onlineCachedPlayers.ForEach(p => p.DisposePlayer()); @@ -159,14 +153,10 @@ public class CachedPlayersManager : IDisposable if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return; _localVisiblePlayers.Clear(); - string ownName = _dalamudUtil.PlayerName; - var playerCharacters = _objectTable.Where(obj => - obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && - obj.Name.ToString() != ownName).ToList(); - foreach (var obj in playerCharacters) + var playerCharacters = _dalamudUtil.GetPlayerCharacters(); + foreach (var pChar in playerCharacters) { - var pObj = (PlayerCharacter)obj; - var pObjName = pObj.Name.ToString(); + var pObjName = pChar.Name.ToString(); _localVisiblePlayers.Add(pObjName); var existingCachedPlayer = _onlineCachedPlayers.SingleOrDefault(p => p.PlayerName == pObjName); if (existingCachedPlayer != null) @@ -175,8 +165,8 @@ public class CachedPlayersManager : IDisposable continue; } - var hashedName = Crypto.GetHash256(pObj.Name.ToString() + pObj.HomeWorld.Id.ToString()); - _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pObj); + var hashedName = Crypto.GetHash256(pChar); + _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pChar); } _onlineCachedPlayers.Where(p => !string.IsNullOrEmpty(p.PlayerName) && !_localVisiblePlayers.Contains(p.PlayerName)) @@ -191,6 +181,6 @@ public class CachedPlayersManager : IDisposable private CachedPlayer CreateCachedPlayer(string hashedName) { - return new CachedPlayer(hashedName, _ipcManager, _apiController, _dalamudUtil, _watcher); + return new CachedPlayer(hashedName, _ipcManager, _apiController, _dalamudUtil); } } \ No newline at end of file diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index faa63c3..d33699a 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -34,12 +34,10 @@ namespace MareSynchronos.Managers private void StartWatchersAndScan() { - if (_ipcManager.Initialized && _pluginConfiguration.HasValidSetup) - { - Logger.Debug("Penumbra is active, configuration is valid, starting watchers and scan"); - StartWatchers(); - StartInitialScan(); - } + if (!_ipcManager.Initialized || !_pluginConfiguration.HasValidSetup) return; + Logger.Debug("Penumbra is active, configuration is valid, starting watchers and scan"); + StartWatchers(); + StartInitialScan(); } private void IpcManagerOnPenumbraInitialized(object? sender, EventArgs e) diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index a7c7d58..679aee0 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -146,7 +146,9 @@ namespace MareSynchronos.Managers { if (!CheckPenumbraApi()) return string.Empty; Logger.Debug("Creating temp collection for " + characterName); - return _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true).Item2; + var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true); + Logger.Debug("Penumbra ret: " + ret.Item1); + return ret.Item2; } public string PenumbraGetMetaManipulations(string characterName) diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index cc4ce1a..64f8527 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -4,7 +4,6 @@ using MareSynchronos.Models; using MareSynchronos.Utils; using MareSynchronos.WebAPI; using Newtonsoft.Json; -using Penumbra.PlayerWatch; using System; using System.Diagnostics; using System.Linq; @@ -20,12 +19,11 @@ namespace MareSynchronos.Managers private readonly CharacterDataFactory _characterDataFactory; private readonly DalamudUtil _dalamudUtil; private readonly IpcManager _ipcManager; - private readonly IPlayerWatcher _watcher; private string _lastSentHash = string.Empty; private Task? _playerChangedTask; public PlayerManager(ApiController apiController, IpcManager ipcManager, - CharacterDataFactory characterDataFactory, CachedPlayersManager cachedPlayersManager, DalamudUtil dalamudUtil, IPlayerWatcher watcher) + CharacterDataFactory characterDataFactory, CachedPlayersManager cachedPlayersManager, DalamudUtil dalamudUtil) { Logger.Debug("Creating " + nameof(PlayerManager)); @@ -34,9 +32,8 @@ namespace MareSynchronos.Managers _characterDataFactory = characterDataFactory; _cachedPlayersManager = cachedPlayersManager; _dalamudUtil = dalamudUtil; - _watcher = watcher; - _watcher.AddPlayerToWatch(_dalamudUtil.PlayerName); + _dalamudUtil.AddPlayerToWatch(_dalamudUtil.PlayerName); _apiController.Connected += ApiController_Connected; _apiController.Disconnected += ApiController_Disconnected; @@ -54,7 +51,7 @@ namespace MareSynchronos.Managers _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _apiController.Connected -= ApiController_Connected; _apiController.Disconnected -= ApiController_Disconnected; - _watcher.PlayerChanged -= Watcher_PlayerChanged; + _dalamudUtil.PlayerChanged -= Watcher_PlayerChanged; } private void ApiController_Connected(object? sender, EventArgs args) @@ -69,7 +66,7 @@ namespace MareSynchronos.Managers _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; _ipcManager.PenumbraRedraw(_dalamudUtil.PlayerName); - _watcher.PlayerChanged += Watcher_PlayerChanged; + _dalamudUtil.PlayerChanged += Watcher_PlayerChanged; } private void ApiController_Disconnected(object? sender, EventArgs args) @@ -77,7 +74,7 @@ namespace MareSynchronos.Managers Logger.Debug(nameof(ApiController_Disconnected)); _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; - _watcher.PlayerChanged -= Watcher_PlayerChanged; + _dalamudUtil.PlayerChanged -= Watcher_PlayerChanged; } private async Task CreateFullCharacterCache() diff --git a/MareSynchronos/Models/CachedPlayer.cs b/MareSynchronos/Models/CachedPlayer.cs index bbf0a4f..7f4c0d3 100644 --- a/MareSynchronos/Models/CachedPlayer.cs +++ b/MareSynchronos/Models/CachedPlayer.cs @@ -11,7 +11,6 @@ using MareSynchronos.FileCacheDB; using MareSynchronos.Managers; using MareSynchronos.Utils; using MareSynchronos.WebAPI; -using Penumbra.PlayerWatch; namespace MareSynchronos.Models; @@ -20,16 +19,14 @@ public class CachedPlayer private readonly DalamudUtil _dalamudUtil; private readonly IpcManager _ipcManager; private readonly ApiController _apiController; - private readonly IPlayerWatcher _watcher; private bool _isVisible; - public CachedPlayer(string nameHash, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil, IPlayerWatcher watcher) + public CachedPlayer(string nameHash, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil) { PlayerNameHash = nameHash; _ipcManager = ipcManager; _apiController = apiController; _dalamudUtil = dalamudUtil; - _watcher = watcher; } public bool IsVisible @@ -165,7 +162,7 @@ public class CachedPlayer _downloadCancellationTokenSource?.Cancel(); _downloadCancellationTokenSource?.Dispose(); _downloadCancellationTokenSource = null; - _watcher.RemovePlayerFromWatch(PlayerName); + _dalamudUtil.RemovePlayerFromWatch(PlayerName); _ipcManager.PenumbraRemoveTemporaryCollection(PlayerName); _ipcManager.GlamourerRevertCharacterCustomization(PlayerName); _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerName); @@ -177,7 +174,7 @@ public class CachedPlayer } finally { - _watcher.PlayerChanged -= WatcherOnPlayerChanged; + _dalamudUtil.PlayerChanged -= WatcherOnPlayerChanged; _ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent; _apiController.CharacterReceived -= ApiControllerOnCharacterReceived; PlayerName = string.Empty; @@ -191,8 +188,8 @@ public class CachedPlayer PlayerName = character.Name.ToString(); PlayerCharacter = character; Logger.Debug("Initializing Player " + this); - _watcher.AddPlayerToWatch(PlayerName!); - _watcher.PlayerChanged += WatcherOnPlayerChanged; + _dalamudUtil.AddPlayerToWatch(PlayerName!); + _dalamudUtil.PlayerChanged += WatcherOnPlayerChanged; _ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent; _apiController.CharacterReceived += ApiControllerOnCharacterReceived; _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerName); @@ -203,15 +200,15 @@ public class CachedPlayer return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != null); } - private Task? penumbraRedrawEventTask; + private Task? _penumbraRedrawEventTask; private void IpcManagerOnPenumbraRedrawEvent(object? sender, EventArgs e) { var player = _dalamudUtil.GetPlayerCharacterFromObjectTableIndex((int)sender!); if (player == null || player.Name.ToString() != PlayerName) return; - if (!penumbraRedrawEventTask?.IsCompleted ?? false) return; + if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return; - penumbraRedrawEventTask = Task.Run(() => + _penumbraRedrawEventTask = Task.Run(() => { PlayerCharacter = player; _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter.Address); diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index bc2c64a..6c6c7c1 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -28,14 +28,12 @@ namespace MareSynchronos private readonly FileCacheManager _fileCacheManager; private readonly IntroUi _introUi; private readonly IpcManager _ipcManager; - private readonly ObjectTable _objectTable; public static DalamudPluginInterface PluginInterface { get; set; } private readonly PluginUi _pluginUi; private readonly WindowSystem _windowSystem; private PlayerManager? _playerManager; private readonly DalamudUtil _dalamudUtil; private CachedPlayersManager? _characterCacheManager; - private readonly IPlayerWatcher _playerWatcher; private readonly DownloadUi _downloadUi; private readonly FileDialogManager _fileDialogManager; @@ -46,7 +44,6 @@ namespace MareSynchronos PluginInterface = pluginInterface; _commandManager = commandManager; _framework = framework; - _objectTable = objectTable; _clientState = clientState; _configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); _configuration.Initialize(PluginInterface); @@ -74,13 +71,11 @@ namespace MareSynchronos }; _downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController); - _dalamudUtil = new DalamudUtil(_clientState, _objectTable); - _playerWatcher = PlayerWatchFactory.Create(framework, _clientState, _objectTable); - _playerWatcher.Enable(); + _dalamudUtil = new DalamudUtil(_clientState, objectTable, PlayerWatchFactory.Create(framework, _clientState, objectTable)); clientState.Login += ClientState_Login; clientState.Logout += ClientState_Logout; - _apiController.AccountDeleted += ApiControllerOnAccountDeleted; + _apiController.ChangingServers += ApiControllerOnChangingServers; if (clientState.IsLoggedIn) { @@ -88,19 +83,17 @@ namespace MareSynchronos } } - private void ApiControllerOnAccountDeleted(object? sender, EventArgs e) + private void ApiControllerOnChangingServers(object? sender, EventArgs e) { _pluginUi.IsOpen = false; _introUi.IsOpen = true; - _characterCacheManager?.Dispose(); - _playerManager?.Dispose(); } public string Name => "Mare Synchronos"; public void Dispose() { Logger.Debug("Disposing " + Name); - _apiController.AccountDeleted -= ApiControllerOnAccountDeleted; + _apiController.ChangingServers -= ApiControllerOnChangingServers; _apiController?.Dispose(); _commandManager.RemoveHandler(CommandName); @@ -115,8 +108,7 @@ namespace MareSynchronos _ipcManager?.Dispose(); _playerManager?.Dispose(); _characterCacheManager?.Dispose(); - _playerWatcher.Disable(); - _playerWatcher.Dispose(); + _dalamudUtil.Dispose(); } @@ -152,30 +144,32 @@ namespace MareSynchronos public void ReLaunchCharacterManager() { - _playerManager?.Dispose(); _characterCacheManager?.Dispose(); + _playerManager?.Dispose(); - Task.Run(async () => + Task.Run(WaitForPlayerAndLaunchCharacterManager); + } + + private async Task WaitForPlayerAndLaunchCharacterManager() + { + while (!_dalamudUtil.IsPlayerPresent) { - while (!_dalamudUtil.IsPlayerPresent) - { - await Task.Delay(100); - } + await Task.Delay(100); + } - try - { - var characterCacheFactory = - new CharacterDataFactory(_dalamudUtil, _ipcManager); - _characterCacheManager = new CachedPlayersManager(_clientState, _framework, _objectTable, - _apiController, _dalamudUtil, _ipcManager, _playerWatcher); - _playerManager = new PlayerManager(_apiController, _ipcManager, - characterCacheFactory, _characterCacheManager, _dalamudUtil, _playerWatcher); - } - catch (Exception ex) - { - Logger.Debug(ex.Message); - } - }); + try + { + var characterCacheFactory = + new CharacterDataFactory(_dalamudUtil, _ipcManager); + _characterCacheManager = new CachedPlayersManager(_clientState, _framework, + _apiController, _dalamudUtil, _ipcManager); + _playerManager = new PlayerManager(_apiController, _ipcManager, + characterCacheFactory, _characterCacheManager, _dalamudUtil); + } + catch (Exception ex) + { + Logger.Debug(ex.Message); + } } private void Draw() diff --git a/MareSynchronos/UI/PluginUI.cs b/MareSynchronos/UI/PluginUI.cs index 5cf6926..994d146 100644 --- a/MareSynchronos/UI/PluginUI.cs +++ b/MareSynchronos/UI/PluginUI.cs @@ -51,31 +51,20 @@ namespace MareSynchronos.UI return; } - if (_apiController.SecretKey != "-" && !_apiController.IsConnected && _apiController.ServerAlive) - { - if (ImGui.Button("Reset Secret Key")) - { - _configuration.ClientSecret.Clear(); - _configuration.Save(); - _apiController.RestartHeartbeat(); - } - } - else - { - var pluginState = _uiShared.DrawOtherPluginState(); + var pluginState = _uiShared.DrawOtherPluginState(); - DrawSettingsContent(pluginState); - } + if (pluginState) + DrawSettingsContent(); } - private void DrawSettingsContent(bool pluginState) + private void DrawSettingsContent() { _uiShared.PrintServerState(); ImGui.Separator(); ImGui.SetWindowFontScale(1.2f); ImGui.Text("Your UID"); ImGui.SameLine(); - if (_apiController.ServerAlive) + if (_apiController.IsConnected) { ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.UID); ImGui.SameLine(); @@ -89,18 +78,18 @@ namespace MareSynchronos.UI } else { - ImGui.TextColored(ImGuiColors.DalamudRed, "No UID (Service unavailable)"); + string error = _configuration.FullPause ? "Fully Paused" : "Service unavailable"; + ImGui.TextColored(ImGuiColors.DalamudRed, $"No UID ({error})"); ImGui.SetWindowFontScale(1.0f); } ImGui.Separator(); - if (_apiController.ServerAlive) + if (_apiController.IsConnected) DrawPairedClientsContent(); DrawFileCacheSettings(); - if (_apiController.ServerAlive) + if (_apiController.IsConnected) DrawCurrentTransfers(); - DrawAdministration(_apiController.ServerAlive); - + DrawAdministration(_apiController.IsConnected); } private bool _deleteFilesPopupModalShown = false; @@ -181,15 +170,39 @@ namespace MareSynchronos.UI } } - var marePaused = _configuration.FullPause; - if (ImGui.Checkbox("Pause Mare Synchronos", ref marePaused)) + if (!_configuration.FullPause) { - _configuration.FullPause = marePaused; - _configuration.Save(); - _apiController.RestartHeartbeat(); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + UiShared.TextWrapped("Note: to change servers you need to pause Mare Synchronos."); + ImGui.PopStyleColor(); + } + + var marePaused = _configuration.FullPause; + + if (_configuration.HasValidSetup) + { + if (ImGui.Checkbox("Pause Mare Synchronos", ref marePaused)) + { + _configuration.FullPause = marePaused; + _configuration.Save(); + Task.Run(_apiController.CreateConnections); + } + + UiShared.DrawHelpText("Completely pauses the sync and clear your current data (not uploaded files) on the service."); + } + else + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + ImGui.TextUnformatted("You cannot resume pause without a valid account on the service."); + ImGui.PopStyleColor(); + } + + + if (marePaused) + { + _uiShared.DrawServiceSelection(); } - UiShared.DrawHelpText("Completely pauses the sync and clear your current data (not uploaded files) on the service."); ImGui.TreePop(); } diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index ce5387f..b1592aa 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Numerics; using System.Threading.Tasks; using Dalamud.Interface; @@ -74,7 +75,10 @@ namespace MareSynchronos.UI public void PrintServerState() { - ImGui.Text("Service status of " + (string.IsNullOrEmpty(_pluginConfiguration.ApiUri) ? ApiController.MainServer : _pluginConfiguration.ApiUri)); + var serverName = _apiController.ServerDictionary.ContainsKey(_pluginConfiguration.ApiUri) + ? _apiController.ServerDictionary[_pluginConfiguration.ApiUri] + : _pluginConfiguration.ApiUri; + ImGui.Text("Service status of " + serverName); ImGui.SameLine(); var color = _apiController.ServerAlive ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; ImGui.TextColored(color, _apiController.ServerAlive ? "Available" : "Unavailable"); @@ -132,59 +136,91 @@ namespace MareSynchronos.UI } private int _serverSelectionIndex = 0; + private string _customServerName = ""; + private string _customServerUri = ""; public void DrawServiceSelection() { - string[] comboEntries = new[] { ApiController.MainServer, "Custom Service" }; - if (ImGui.BeginCombo("Service", comboEntries[_serverSelectionIndex])) + string[] comboEntries = _apiController.ServerDictionary.Values.ToArray(); + _serverSelectionIndex = Array.IndexOf(_apiController.ServerDictionary.Keys.ToArray(), _pluginConfiguration.ApiUri); + if (ImGui.BeginCombo("Select Service", comboEntries[_serverSelectionIndex])) { - for (int n = 0; n < comboEntries.Length; n++) + for (int i = 0; i < comboEntries.Length; i++) { - bool isSelected = _serverSelectionIndex == n; - if (ImGui.Selectable(comboEntries[n], isSelected)) + bool isSelected = _serverSelectionIndex == i; + if (ImGui.Selectable(comboEntries[i], isSelected)) { - _serverSelectionIndex = n; + _pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => k.Value == comboEntries[i]).Key; + _pluginConfiguration.Save(); } if (isSelected) { ImGui.SetItemDefaultFocus(); } - - bool useCustomService = _serverSelectionIndex != 0; - - if (_apiController.UseCustomService != useCustomService) - { - _apiController.UseCustomService = useCustomService; - _pluginConfiguration.Save(); - } } ImGui.EndCombo(); } - if (_apiController.UseCustomService) + if (_serverSelectionIndex != 0) { - string serviceAddress = _pluginConfiguration.ApiUri; - if (ImGui.InputText("Service address", ref serviceAddress, 255)) + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteService")) { - if (_pluginConfiguration.ApiUri != serviceAddress) - { - _pluginConfiguration.ApiUri = serviceAddress; - _apiController.RestartHeartbeat(); - _pluginConfiguration.Save(); - } + _pluginConfiguration.CustomServerList.Remove(_pluginConfiguration.ApiUri); + _pluginConfiguration.ApiUri = ApiController.MainServiceUri; + _pluginConfiguration.Save(); } + ImGui.PopFont(); } PrintServerState(); - if (_apiController.ServerAlive) + + if (_apiController.ServerAlive && !_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)) { if (ImGui.Button("Register")) { + _pluginConfiguration.FullPause = false; + _pluginConfiguration.Save(); Task.WaitAll(_apiController.Register()); } } + else + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + TextWrapped("You already have an account on this server."); + ImGui.PopStyleColor(); + ImGui.SameLine(); + if (ImGui.Button("Connect##connectToService")) + { + _pluginConfiguration.FullPause = false; + _pluginConfiguration.Save(); + Task.Run(_apiController.CreateConnections); + } + } + + if (ImGui.TreeNode("Custom Service")) + { + ImGui.SetNextItemWidth(250); + ImGui.InputText("Custom Service Name", ref _customServerName, 255); + ImGui.SetNextItemWidth(250); + ImGui.InputText("Custom Service Address", ref _customServerUri, 255); + if (ImGui.Button("Add Custom Service")) + { + if (!string.IsNullOrEmpty(_customServerUri) + && !string.IsNullOrEmpty(_customServerName) + && !_pluginConfiguration.CustomServerList.ContainsValue(_customServerName)) + { + _pluginConfiguration.CustomServerList[_customServerUri] = _customServerName; + _customServerUri = string.Empty; + _customServerName = string.Empty; + _pluginConfiguration.Save(); + } + } + ImGui.TreePop(); + } } public static void DrawHelpText(string helpText) diff --git a/MareSynchronos/Utils/Crypto.cs b/MareSynchronos/Utils/Crypto.cs index 835daca..36dba7c 100644 --- a/MareSynchronos/Utils/Crypto.cs +++ b/MareSynchronos/Utils/Crypto.cs @@ -2,6 +2,7 @@ using System.IO; using System.Security.Cryptography; using System.Text; +using Dalamud.Game.ClientState.Objects.SubKinds; namespace MareSynchronos.Utils { @@ -24,5 +25,11 @@ namespace MareSynchronos.Utils using SHA256CryptoServiceProvider cryptoProvider = new(); return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); } + + public static string GetHash256(PlayerCharacter character) + { + using SHA256CryptoServiceProvider cryptoProvider = new(); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", ""); + } } } diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index e45bbae..2654869 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -1,22 +1,48 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; -using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Dalamud.Game.ClientState.Objects.Types; +using Penumbra.PlayerWatch; +using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; namespace MareSynchronos.Utils { - public class DalamudUtil + public delegate void PlayerChange(Character actor); + + public class DalamudUtil : IDisposable { private readonly ClientState _clientState; private readonly ObjectTable _objectTable; + private readonly IPlayerWatcher _watcher; + public event PlayerChange? PlayerChanged; - public DalamudUtil(ClientState clientState, ObjectTable objectTable) + public DalamudUtil(ClientState clientState, ObjectTable objectTable, IPlayerWatcher watcher) { _clientState = clientState; _objectTable = objectTable; + _watcher = watcher; + _watcher.Enable(); + _watcher.PlayerChanged += WatcherOnPlayerChanged; + } + + private void WatcherOnPlayerChanged(Character actor) + { + PlayerChanged?.Invoke(actor); + } + + + public void AddPlayerToWatch(string playerName) + { + _watcher.AddPlayerToWatch(playerName); + } + + public void RemovePlayerFromWatch(string playerName) + { + _watcher.RemovePlayerFromWatch(playerName); } public bool IsPlayerPresent => _clientState.LocalPlayer != null; @@ -49,6 +75,13 @@ namespace MareSynchronos.Utils return allLocalPlayers; } + public List GetPlayerCharacters() + { + return _objectTable.Where(obj => + obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && + obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList(); + } + public PlayerCharacter? GetPlayerCharacterFromObjectTableIndex(int index) { var objTableObj = _objectTable[index]; @@ -85,5 +118,11 @@ namespace MareSynchronos.Utils } public void WaitWhileSelfIsDrawing() => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? new IntPtr()); + + public void Dispose() + { + _watcher.Disable(); + _watcher.Dispose(); + } } } diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index 76099f2..2cf83f8 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -16,17 +16,28 @@ using Microsoft.AspNetCore.SignalR.Client; namespace MareSynchronos.WebAPI { - public class ApiController : IDisposable + public partial class ApiController : IDisposable { - public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)"; - +#if DEBUG + public const string MainServer = "darkarchons Debug Server (Dev Server (CH))"; public const string MainServiceUri = "https://darkarchon.internet-box.ch:5001"; - readonly CancellationTokenSource _cts; +#else + public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)"; + public const string MainServiceUri = "to be defined"; +#endif + private readonly Configuration _pluginConfiguration; + + private CancellationTokenSource _cts; + private HubConnection? _fileHub; + private HubConnection? _heartbeatHub; + private CancellationTokenSource? _uploadCancellationTokenSource; + private HubConnection? _userHub; + public ApiController(Configuration pluginConfiguration) { Logger.Debug("Creating " + nameof(ApiController)); @@ -34,9 +45,11 @@ namespace MareSynchronos.WebAPI _pluginConfiguration = pluginConfiguration; _cts = new CancellationTokenSource(); - _ = Heartbeat(); + Task.Run(CreateConnections); } + public event EventHandler? ChangingServers; + public event EventHandler? CharacterReceived; public event EventHandler? Connected; @@ -50,30 +63,165 @@ namespace MareSynchronos.WebAPI public event EventHandler? PairedWithOther; public event EventHandler? UnpairedFromOther; - public event EventHandler? AccountDeleted; public ConcurrentDictionary CurrentDownloads { get; } = new(); + public ConcurrentDictionary CurrentUploads { get; } = new(); + public bool IsConnected => !string.IsNullOrEmpty(UID); + public bool IsDownloading { get; private set; } + public bool IsUploading { get; private set; } + public List PairedClients { get; set; } = new(); + public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? _pluginConfiguration.ClientSecret[ApiUri] : "-"; + public bool ServerAlive => (_heartbeatHub?.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 { get; private set; } = string.Empty; - public bool UseCustomService + + private string ApiUri => _pluginConfiguration.ApiUri; + + public async Task CreateConnections() { - get => _pluginConfiguration.UseCustomService; - set + _cts = new CancellationTokenSource(); + var token = _cts.Token; + await StopAllConnections(token); + + while (!ServerAlive && !token.IsCancellationRequested) { - _pluginConfiguration.UseCustomService = value; - _pluginConfiguration.Save(); + await StopAllConnections(token); + + try + { + Logger.Debug("Building connection"); + _heartbeatHub = BuildHubConnection("heartbeat"); + _userHub = BuildHubConnection("user"); + _fileHub = BuildHubConnection("files"); + + await _heartbeatHub.StartAsync(token); + await _userHub.StartAsync(token); + await _fileHub.StartAsync(token); + + if (_pluginConfiguration.FullPause) + { + UID = string.Empty; + return; + } + UID = await _heartbeatHub.InvokeAsync("Heartbeat", token); + if (!string.IsNullOrEmpty(UID) && !token.IsCancellationRequested) // user is authorized + { + Logger.Debug("Initializing data"); + _userHub.On("UpdateClientPairs", UpdateLocalClientPairs); + _userHub.On("ReceiveCharacterData", ReceiveCharacterData); + _userHub.On("RemoveOnlinePairedPlayer", + (s) => PairedClientOffline?.Invoke(s, EventArgs.Empty)); + _userHub.On("AddOnlinePairedPlayer", + (s) => PairedClientOnline?.Invoke(s, EventArgs.Empty)); + + PairedClients = await _userHub!.InvokeAsync>("GetPairedClients", token); + + _heartbeatHub.Closed += HeartbeatHubOnClosed; + _heartbeatHub.Reconnected += HeartbeatHubOnReconnected; + _heartbeatHub.Reconnecting += HeartbeatHubOnReconnecting; + Connected?.Invoke(this, EventArgs.Empty); + } + } + catch (Exception ex) + { + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace); + Logger.Debug("Failed to establish connection, retrying"); + await Task.Delay(TimeSpan.FromSeconds(5), token); + } } } - private string ApiUri => UseCustomService ? _pluginConfiguration.ApiUri : MainServiceUri; + public void Dispose() + { + Logger.Debug("Disposing " + nameof(ApiController)); + + Task.Run(async () => await StopAllConnections(_cts.Token)); + _cts?.Cancel(); + } + + private HubConnection BuildHubConnection(string hubName) + { + return new HubConnectionBuilder() + .WithUrl(ApiUri + "/" + hubName, options => + { + if (!string.IsNullOrEmpty(SecretKey) && !_pluginConfiguration.FullPause) + { + options.Headers.Add("Authorization", SecretKey); + } +#if DEBUG + options.HttpMessageHandlerFactory = (message) => + { + if (message is HttpClientHandler clientHandler) + clientHandler.ServerCertificateCustomValidationCallback += + (sender, certificate, chain, sslPolicyErrors) => true; + return message; + }; +#endif + }) + .WithAutomaticReconnect(new ForeverRetryPolicy()) + .Build(); + } + + private Task HeartbeatHubOnClosed(Exception? arg) + { + Logger.Debug("Connection closed"); + Disconnected?.Invoke(null, EventArgs.Empty); + return Task.CompletedTask; + } + + private Task HeartbeatHubOnReconnected(string? arg) + { + Logger.Debug("Connection restored"); + Connected?.Invoke(this, EventArgs.Empty); + return Task.CompletedTask; + } + + private Task HeartbeatHubOnReconnecting(Exception? arg) + { + Logger.Debug("Connection closed... Reconnecting…"); + Disconnected?.Invoke(null, EventArgs.Empty); + return Task.CompletedTask; + } + + private async Task StopAllConnections(CancellationToken token) + { + if (_heartbeatHub is { State: HubConnectionState.Connected }) + { + await _heartbeatHub.StopAsync(token); + _heartbeatHub.Closed -= HeartbeatHubOnClosed; + _heartbeatHub.Reconnected -= HeartbeatHubOnReconnected; + _heartbeatHub.Reconnecting += HeartbeatHubOnReconnecting; + await _heartbeatHub.DisposeAsync(); + } + + if (_fileHub is { State: HubConnectionState.Connected }) + { + await _fileHub.StopAsync(token); + await _fileHub.DisposeAsync(); + } + + if (_userHub is { State: HubConnectionState.Connected }) + { + await _userHub.StopAsync(token); + await _userHub.DisposeAsync(); + } + } + } + + public partial class ApiController + { public void CancelUpload() { if (_uploadCancellationTokenSource != null) @@ -84,12 +232,17 @@ namespace MareSynchronos.WebAPI } } - public void Dispose() + public async Task DeleteAccount() { - Logger.Debug("Disposing " + nameof(ApiController)); + _pluginConfiguration.ClientSecret.Remove(ApiUri); + await _fileHub!.SendAsync("DeleteAllFiles"); + await _userHub!.SendAsync("DeleteAccount"); + await CreateConnections(); + } - _cts?.Cancel(); - _ = DisposeHubConnections(); + public async Task DeleteAllMyFiles() + { + await _fileHub!.SendAsync("DeleteAllFiles"); } public async Task DownloadFile(string hash, CancellationToken ct) @@ -167,41 +320,6 @@ namespace MareSynchronos.WebAPI hashedCharacterNames); } - public async Task Heartbeat() - { - while (!ServerAlive && !_cts.Token.IsCancellationRequested) - { - try - { - if (_pluginConfiguration.FullPause) return; - Logger.Debug("Attempting to establish heartbeat connection to " + ApiUri); - _heartbeatHub = BuildHubConnection("heartbeat"); - - await _heartbeatHub.StartAsync(_cts.Token); - UID = await _heartbeatHub!.InvokeAsync("Heartbeat"); - Logger.Debug("Heartbeat started: " + ApiUri); - try - { - await InitializeHubConnections(); - await LoadInitialData(); - Connected?.Invoke(this, EventArgs.Empty); - } - catch - { - //PluginLog.Error(ex, "Error during Heartbeat initialization"); - } - - _heartbeatHub.Closed += OnHeartbeatHubOnClosed; - _heartbeatHub.Reconnected += OnHeartbeatHubOnReconnected; - Logger.Debug("Heartbeat established to: " + ApiUri); - } - catch (Exception ex) - { - PluginLog.Error(ex, "Creating heartbeat failure"); - } - } - } - public Task ReceiveCharacterData(CharacterCacheDto character, string characterHash) { Logger.Debug("Received DTO for " + characterHash); @@ -216,29 +334,9 @@ namespace MareSynchronos.WebAPI var response = await _userHub!.InvokeAsync("Register"); _pluginConfiguration.ClientSecret[ApiUri] = response; _pluginConfiguration.Save(); - RestartHeartbeat(); + ChangingServers?.Invoke(null, EventArgs.Empty); + await CreateConnections(); } - - public void RestartHeartbeat() - { - Logger.Debug("Restarting heartbeat"); - - Task.Run(async () => - { - if (_heartbeatHub != null) - { - _heartbeatHub.Closed -= OnHeartbeatHubOnClosed; - _heartbeatHub.Reconnected -= OnHeartbeatHubOnReconnected; - await _heartbeatHub.StopAsync(_cts.Token); - await _heartbeatHub.DisposeAsync(); - await OnHeartbeatHubOnClosed(null); - _heartbeatHub = null!; - } - - _ = Heartbeat(); - }); - } - public async Task SendCharacterData(CharacterCacheDto character, List visibleCharacterIds) { if (!IsConnected || SecretKey == "-") return; @@ -323,40 +421,6 @@ namespace MareSynchronos.WebAPI await _userHub!.SendAsync("SendPairedClientRemoval", uid); } - public async Task DeleteAllMyFiles() - { - await _fileHub!.SendAsync("DeleteAllFiles"); - } - - public async Task DeleteAccount() - { - _pluginConfiguration.ClientSecret.Remove(ApiUri); - await _fileHub!.SendAsync("DeleteAllFiles"); - await _userHub!.SendAsync("DeleteAccount"); - _ = OnHeartbeatHubOnClosed(null); - AccountDeleted?.Invoke(null, EventArgs.Empty); - } - - private async Task DisposeHubConnections() - { - if (_fileHub != null) - { - Logger.Debug("Disposing File Hub"); - CancelUpload(); - await _fileHub!.StopAsync(); - await _fileHub!.DisposeAsync(); - _fileHub = null; - } - - if (_userHub != null) - { - Logger.Debug("Disposing User Hub"); - await _userHub.StopAsync(); - await _userHub.DisposeAsync(); - _userHub = null; - } - } - private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken) { await using var db = new FileCacheContext(); @@ -364,67 +428,6 @@ namespace MareSynchronos.WebAPI return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache.Filepath, uploadToken), 0, (int)new FileInfo(fileCache.Filepath).Length)); } - - private async Task InitializeHubConnections() - { - await DisposeHubConnections(); - - Logger.Debug("Creating User Hub"); - _userHub = BuildHubConnection("user"); - await _userHub.StartAsync(); - _userHub.On("UpdateClientPairs", UpdateLocalClientPairs); - _userHub.On("ReceiveCharacterData", ReceiveCharacterData); - _userHub.On("RemoveOnlinePairedPlayer", (s) => PairedClientOffline?.Invoke(s, EventArgs.Empty)); - _userHub.On("AddOnlinePairedPlayer", (s) => PairedClientOnline?.Invoke(s, EventArgs.Empty)); - - Logger.Debug("Creating File Hub"); - _fileHub = BuildHubConnection("files"); - await _fileHub.StartAsync(_cts.Token); - } - - private HubConnection BuildHubConnection(string hubName) - { - return new HubConnectionBuilder() - .WithUrl(ApiUri + "/" + hubName, options => - { - if (!string.IsNullOrEmpty(SecretKey) && !_pluginConfiguration.FullPause) - { - options.Headers.Add("Authorization", SecretKey); - } -#if DEBUG - options.HttpMessageHandlerFactory = (message) => - { - if (message is HttpClientHandler clientHandler) - clientHandler.ServerCertificateCustomValidationCallback += - (sender, certificate, chain, sslPolicyErrors) => true; - return message; - }; -#endif - }) - .Build(); - } - - private async Task LoadInitialData() - { - var pairedClients = await _userHub!.InvokeAsync>("GetPairedClients"); - PairedClients = pairedClients.ToList(); - } - - private Task OnHeartbeatHubOnClosed(Exception? exception) - { - Logger.Debug("Connection closed: " + ApiUri); - Disconnected?.Invoke(null, EventArgs.Empty); - Task.Run(DisposeHubConnections); - RestartHeartbeat(); - return Task.CompletedTask; - } - - private async Task OnHeartbeatHubOnReconnected(string? s) - { - Logger.Debug("Reconnected: " + ApiUri); - UID = await _heartbeatHub!.InvokeAsync("Heartbeat"); - } - private void UpdateLocalClientPairs(ClientPairDto dto, string characterIdentifier) { var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID); @@ -492,4 +495,12 @@ namespace MareSynchronos.WebAPI public CharacterCacheDto CharacterData { get; set; } public string CharacterNameHash { get; set; } } + + public class ForeverRetryPolicy : IRetryPolicy + { + public TimeSpan? NextRetryDelay(RetryContext retryContext) + { + return TimeSpan.FromSeconds(5); + } + } }