rework the whole connection garbage/custom servers, other cleanups/refactors

This commit is contained in:
Stanley Dimant
2022-06-27 01:19:45 +02:00
parent 404ba8e278
commit 61b178e2c0
12 changed files with 386 additions and 302 deletions

View File

@@ -25,6 +25,7 @@ namespace MareSynchronos
public string CacheFolder { get; set; } = string.Empty;
public Dictionary<string, string> ClientSecret { get; set; } = new();
public Dictionary<string, string> 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<string, string> UidComments { get; set; } = new();
public bool UseCustomService { get; set; } = false;
public int Version { get; set; } = 0;
public bool ShowTransferWindow { get; set; } = true;

View File

@@ -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<CachedPlayer> _onlineCachedPlayers = new();
private readonly List<string> _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);
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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<CharacterData> CreateFullCharacterCache()

View File

@@ -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);

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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("-", "");
}
}
}

View File

@@ -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<PlayerCharacter> 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();
}
}
}

View File

@@ -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<CharacterReceivedEventArgs>? 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<string, (long, long)> CurrentDownloads { get; } = new();
public ConcurrentDictionary<string, (long, long)> CurrentUploads { get; } = new();
public bool IsConnected => !string.IsNullOrEmpty(UID);
public bool IsDownloading { get; private set; }
public bool IsUploading { get; private set; }
public List<ClientPairDto> 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<string, string> ServerDictionary => new Dictionary<string, string>() { { 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<string>("Heartbeat", token);
if (!string.IsNullOrEmpty(UID) && !token.IsCancellationRequested) // user is authorized
{
Logger.Debug("Initializing data");
_userHub.On<ClientPairDto, string>("UpdateClientPairs", UpdateLocalClientPairs);
_userHub.On<CharacterCacheDto, string>("ReceiveCharacterData", ReceiveCharacterData);
_userHub.On<string>("RemoveOnlinePairedPlayer",
(s) => PairedClientOffline?.Invoke(s, EventArgs.Empty));
_userHub.On<string>("AddOnlinePairedPlayer",
(s) => PairedClientOnline?.Invoke(s, EventArgs.Empty));
PairedClients = await _userHub!.InvokeAsync<List<ClientPairDto>>("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<string> 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<string>("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<string>("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<string> 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<ClientPairDto, string>("UpdateClientPairs", UpdateLocalClientPairs);
_userHub.On<CharacterCacheDto, string>("ReceiveCharacterData", ReceiveCharacterData);
_userHub.On<string>("RemoveOnlinePairedPlayer", (s) => PairedClientOffline?.Invoke(s, EventArgs.Empty));
_userHub.On<string>("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<List<ClientPairDto>>("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<string>("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);
}
}
}