diff --git a/MareSynchronos/Configuration.cs b/MareSynchronos/Configuration.cs index a833068..f308d83 100644 --- a/MareSynchronos/Configuration.cs +++ b/MareSynchronos/Configuration.cs @@ -2,6 +2,9 @@ using Dalamud.Plugin; using System; using System.Collections.Generic; +using System.IO; +using MareSynchronos.WebAPI; +using Newtonsoft.Json; namespace MareSynchronos { @@ -11,23 +14,49 @@ namespace MareSynchronos public int Version { get; set; } = 0; public string CacheFolder { get; set; } = string.Empty; - public Dictionary ClientSecret { get; internal set; } = new(); - public string ApiUri { get; internal set; } = string.Empty; - public bool UseCustomService { get; internal set; } + public Dictionary ClientSecret { get; set; } = new(); + public Dictionary UidComments { get; set; } = new(); + private string _apiUri = string.Empty; + public string ApiUri + { + get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri; + set => _apiUri = value; + } + public bool UseCustomService { get; set; } + public bool InitialScanComplete { get; set; } + public bool AcceptedAgreement { get; set; } + private int _maxParallelScan = 10; + public int MaxParallelScan + { + get => _maxParallelScan; + set + { + _maxParallelScan = value switch + { + < 0 => 1, + > 20 => 10, + _ => value + }; + } + } + + [JsonIgnore] + public bool HasValidSetup => AcceptedAgreement && InitialScanComplete && !string.IsNullOrEmpty(CacheFolder) && + Directory.Exists(CacheFolder) && ClientSecret.ContainsKey(ApiUri); // the below exist just to make saving less cumbersome [NonSerialized] - private DalamudPluginInterface? pluginInterface; + private DalamudPluginInterface? _pluginInterface; public void Initialize(DalamudPluginInterface pluginInterface) { - this.pluginInterface = pluginInterface; + this._pluginInterface = pluginInterface; } public void Save() { - this.pluginInterface!.SavePluginConfig(this); + this._pluginInterface!.SavePluginConfig(this); } } } diff --git a/MareSynchronos/Factories/FileReplacementFactory.cs b/MareSynchronos/Factories/FileReplacementFactory.cs index 85e7e08..aaab72c 100644 --- a/MareSynchronos/Factories/FileReplacementFactory.cs +++ b/MareSynchronos/Factories/FileReplacementFactory.cs @@ -15,7 +15,7 @@ namespace MareSynchronos.Factories public FileReplacement Create() { - if (!ipcManager.CheckPenumbraAPI()) + if (!ipcManager.CheckPenumbraApi()) { throw new System.Exception(); } diff --git a/MareSynchronos/Managers/CharacterManager.cs b/MareSynchronos/Managers/CharacterManager.cs index 8b59300..93d5923 100644 --- a/MareSynchronos/Managers/CharacterManager.cs +++ b/MareSynchronos/Managers/CharacterManager.cs @@ -45,7 +45,7 @@ namespace MareSynchronos.Managers private string _lastSentHash = string.Empty; private Task? _playerChangedTask = null; - private HashSet onlineWhitelistedUsers = new(); + private HashSet _onlineWhitelistedUsers = new(); public CharacterManager(ClientState clientState, Framework framework, ApiController apiController, ObjectTable objectTable, IpcManager ipcManager, FileReplacementFactory factory, Configuration pluginConfiguration) @@ -154,6 +154,11 @@ namespace MareSynchronos.Managers _watcher.Disable(); _watcher.PlayerChanged -= Watcher_PlayerChanged; _watcher?.Dispose(); + + foreach (var character in _onlineWhitelistedUsers) + { + RestoreCharacter(character); + } } public void StopWatchPlayer(string name) @@ -164,7 +169,7 @@ namespace MareSynchronos.Managers public async Task UpdatePlayersFromService(Dictionary currentLocalPlayers) { PluginLog.Debug("Updating local players from service"); - currentLocalPlayers = currentLocalPlayers.Where(k => onlineWhitelistedUsers.Contains(k.Key)) + currentLocalPlayers = currentLocalPlayers.Where(k => _onlineWhitelistedUsers.Contains(k.Key)) .ToDictionary(k => k.Key, k => k.Value); await _apiController.GetCharacterData(currentLocalPlayers .ToDictionary( @@ -205,10 +210,10 @@ namespace MareSynchronos.Managers Task.WaitAll(apiTask); - onlineWhitelistedUsers = new HashSet(apiTask.Result); + _onlineWhitelistedUsers = new HashSet(apiTask.Result); var assignTask = AssignLocalPlayersData(); Task.WaitAll(assignTask); - PluginLog.Debug("Online and whitelisted users: " + string.Join(",", onlineWhitelistedUsers)); + PluginLog.Debug("Online and whitelisted users: " + string.Join(",", _onlineWhitelistedUsers)); _framework.Update += Framework_Update; _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; @@ -221,6 +226,12 @@ namespace MareSynchronos.Managers _framework.Update -= Framework_Update; _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _clientState.TerritoryChanged -= ClientState_TerritoryChanged; + foreach (var character in _onlineWhitelistedUsers) + { + RestoreCharacter(character); + } + + _lastSentHash = string.Empty; } private void ApiControllerOnAddedToWhitelist(object? sender, EventArgs e) @@ -230,7 +241,7 @@ namespace MareSynchronos.Managers var players = GetLocalPlayers(); if (players.ContainsKey(characterHash)) { - PluginLog.Debug("You got added to a whitelist, restoring data for " + characterHash); + PluginLog.Debug("Removed from whitelist, restoring data for " + characterHash); _ = _apiController.GetCharacterData(new Dictionary { { characterHash, (int)players[characterHash].ClassJob.Id } }); } } @@ -287,8 +298,8 @@ namespace MareSynchronos.Managers } PluginLog.Debug("Assigned hash to visible player: " + otherPlayerName); - /*ipcManager.PenumbraRemoveTemporaryCollection(otherPlayerName); - ipcManager.PenumbraCreateTemporaryCollection(otherPlayerName); + _ipcManager.PenumbraRemoveTemporaryCollection(otherPlayerName); + _ipcManager.PenumbraCreateTemporaryCollection(otherPlayerName); Dictionary moddedPaths = new(); using (var db = new FileCacheContext()) { @@ -306,41 +317,49 @@ namespace MareSynchronos.Managers } } - ipcManager.PenumbraSetTemporaryMods(otherPlayerName, moddedPaths);*/ + _ipcManager.PenumbraSetTemporaryMods(otherPlayerName, moddedPaths); _ipcManager.GlamourerApplyCharacterCustomization(e.CharacterData.GlamourerData, otherPlayerName); - //ipcManager.PenumbraRedraw(otherPlayerName); + _ipcManager.PenumbraRedraw(otherPlayerName); } private void ApiControllerOnRemovedFromWhitelist(object? sender, EventArgs e) { var characterHash = (string?)sender; if (string.IsNullOrEmpty(characterHash)) return; + RestoreCharacter(characterHash); + } + + private void RestoreCharacter(string characterHash) + { var players = GetLocalPlayers(); + foreach (var entry in _characterCache.Where(c => c.Key.Item1 == characterHash)) { _characterCache.Remove(entry.Key); } - var playerName = players.SingleOrDefault(p => p.Key == characterHash).Value.Name.ToString() ?? null; - if (playerName != null) + foreach (var player in players) { + if (player.Key != characterHash) continue; + var playerName = player.Value.Name.ToString(); RestorePreviousCharacter(playerName); PluginLog.Debug("Removed from whitelist, restoring glamourer state for " + playerName); _ipcManager.PenumbraRemoveTemporaryCollection(playerName); _ipcManager.GlamourerRevertCharacterCustomization(playerName); + break; } } private void ApiControllerOnWhitelistedPlayerOffline(object? sender, EventArgs e) { PluginLog.Debug("Player offline: " + sender!); - onlineWhitelistedUsers.Remove((string)sender!); + _onlineWhitelistedUsers.Remove((string)sender!); } private void ApiControllerOnWhitelistedPlayerOnline(object? sender, EventArgs e) { PluginLog.Debug("Player online: " + sender!); - onlineWhitelistedUsers.Add((string)sender!); + _onlineWhitelistedUsers.Add((string)sender!); } private async Task AssignLocalPlayersData() @@ -351,11 +370,7 @@ namespace MareSynchronos.Managers { if (currentLocalPlayers.ContainsKey(player.Key.Item1)) { - await Task.Run(() => ApiControllerOnCharacterReceived(null, new CharacterReceivedEventArgs - { - CharacterNameHash = player.Key.Item1, - CharacterData = player.Value - })); + await Task.Run(() => ApiControllerOnCharacterReceived(null, new CharacterReceivedEventArgs(player.Key.Item1, player.Value))); } } @@ -413,7 +428,7 @@ namespace MareSynchronos.Managers var pObj = (PlayerCharacter)obj; var hashedName = Crypto.GetHash256(pObj.Name.ToString() + pObj.HomeWorld.Id.ToString()); - if (!onlineWhitelistedUsers.Contains(hashedName)) continue; + if (!_onlineWhitelistedUsers.Contains(hashedName)) continue; localPlayersList.Add(hashedName); if (!_cachedLocalPlayers.ContainsKey(hashedName)) newPlayers[hashedName] = pObj; @@ -455,7 +470,7 @@ namespace MareSynchronos.Managers string playerName = obj.Name.ToString(); if (playerName == GetPlayerName()) continue; var playerObject = (PlayerCharacter)obj; - allLocalPlayers.Add(Crypto.GetHash256(playerObject.Name.ToString() + playerObject.HomeWorld.Id.ToString()), playerObject); + allLocalPlayers[Crypto.GetHash256(playerObject.Name.ToString() + playerObject.HomeWorld.Id.ToString())] = playerObject; } return allLocalPlayers; @@ -509,7 +524,7 @@ namespace MareSynchronos.Managers var cacheDto = characterCacheTask.Result.ToCharacterCacheDto(); if (cacheDto.Hash == _lastSentHash) { - PluginLog.Warning("Not sending data, already sent"); + PluginLog.Debug("Not sending data, already sent"); return; } Task.WaitAll(_apiController.SendCharacterData(cacheDto, GetLocalPlayers().Select(d => d.Key).ToList())); diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs new file mode 100644 index 0000000..9f7373b --- /dev/null +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Dalamud.Logging; +using MareSynchronos.Factories; +using MareSynchronos.FileCacheDB; + +namespace MareSynchronos.Managers +{ + internal class FileCacheManager : IDisposable + { + private const int MinutesForScan = 10; + private readonly FileCacheFactory _fileCacheFactory; + private readonly IpcManager _ipcManager; + private readonly Configuration _pluginConfiguration; + private CancellationTokenSource? _scanCancellationTokenSource; + private System.Timers.Timer? _scanScheduler; + private Task? _scanTask; + private Stopwatch? _timerStopWatch; + public FileCacheManager(FileCacheFactory fileCacheFactory, IpcManager ipcManager, Configuration pluginConfiguration) + { + _fileCacheFactory = fileCacheFactory; + _ipcManager = ipcManager; + _pluginConfiguration = pluginConfiguration; + + if (_ipcManager.CheckPenumbraApi() + && _pluginConfiguration.AcceptedAgreement + && !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) + && _pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri) + && !string.IsNullOrEmpty(_ipcManager.PenumbraModDirectory())) + { + StartInitialScan(); + } + } + + public long CurrentFileProgress { get; private set; } + public bool IsScanRunning => !_scanTask?.IsCompleted ?? false; + + public TimeSpan TimeToNextScan => TimeSpan.FromMinutes(MinutesForScan).Subtract(_timerStopWatch?.Elapsed ?? TimeSpan.FromMinutes(MinutesForScan)); + public long TotalFiles { get; private set; } + + public void Dispose() + { + PluginLog.Debug("Disposing File Cache Manager"); + _scanScheduler?.Stop(); + _scanCancellationTokenSource?.Cancel(); + } + + public void StartInitialScan() + { + _scanCancellationTokenSource = new CancellationTokenSource(); + _scanTask = StartFileScan(_scanCancellationTokenSource.Token); + } + + private async Task StartFileScan(CancellationToken ct) + { + _scanCancellationTokenSource = new CancellationTokenSource(); + var penumbraDir = _ipcManager.PenumbraModDirectory()!; + PluginLog.Debug("Getting files from " + penumbraDir); + var scannedFiles = new ConcurrentDictionary( + Directory.EnumerateFiles(penumbraDir, "*.*", SearchOption.AllDirectories) + .Select(s => s.ToLowerInvariant()) + .Where(f => f.Contains(@"\chara\") && (f.EndsWith(".tex") || f.EndsWith(".mdl") || f.EndsWith(".mtrl"))) + .Select(p => new KeyValuePair(p, false))); + List fileCaches; + await using (FileCacheContext db = new()) + { + fileCaches = db.FileCaches.ToList(); + } + + TotalFiles = scannedFiles.Count; + + var fileCachesToUpdate = new ConcurrentBag(); + var fileCachesToDelete = new ConcurrentBag(); + var fileCachesToAdd = new ConcurrentBag(); + + PluginLog.Debug("Getting file list from Database"); + // scan files from database + Parallel.ForEach(fileCaches, new ParallelOptions() + { + MaxDegreeOfParallelism = _pluginConfiguration.MaxParallelScan, + CancellationToken = ct, + }, + cache => + { + if (ct.IsCancellationRequested) return; + if (!File.Exists(cache.Filepath)) + { + fileCachesToDelete.Add(cache); + } + else + { + if (scannedFiles.ContainsKey(cache.Filepath)) + { + scannedFiles[cache.Filepath] = true; + } + FileInfo fileInfo = new(cache.Filepath); + if (fileInfo.LastWriteTimeUtc.Ticks == long.Parse(cache.LastModifiedDate)) return; + _fileCacheFactory.UpdateFileCache(cache); + fileCachesToUpdate.Add(cache); + } + + var files = CurrentFileProgress; + Interlocked.Increment(ref files); + CurrentFileProgress = files; + }); + + if (ct.IsCancellationRequested) return; + + // scan new files + Parallel.ForEach(scannedFiles.Where(c => c.Value == false), new ParallelOptions() + { + MaxDegreeOfParallelism = _pluginConfiguration.MaxParallelScan, + CancellationToken = ct + }, + file => + { + fileCachesToAdd.Add(_fileCacheFactory.Create(file.Key)); + + var files = CurrentFileProgress; + Interlocked.Increment(ref files); + CurrentFileProgress = files; + }); + + await using (FileCacheContext db = new()) + { + if (fileCachesToAdd.Any() || fileCachesToUpdate.Any() || fileCachesToDelete.Any()) + { + db.FileCaches.AddRange(fileCachesToAdd); + db.FileCaches.UpdateRange(fileCachesToUpdate); + db.FileCaches.RemoveRange(fileCachesToDelete); + + await db.SaveChangesAsync(ct); + } + } + + PluginLog.Debug("Scan complete"); + TotalFiles = 0; + CurrentFileProgress = 0; + + if (!_pluginConfiguration.InitialScanComplete) + { + _pluginConfiguration.InitialScanComplete = true; + _pluginConfiguration.Save(); + _timerStopWatch = Stopwatch.StartNew(); + StartScheduler(); + } + else if (_timerStopWatch == null) + { + StartScheduler(); + _timerStopWatch = Stopwatch.StartNew(); + } + } + + private void StartScheduler() + { + PluginLog.Debug("Scheduling next scan for in " + MinutesForScan + " minutes"); + _scanScheduler = new System.Timers.Timer(TimeSpan.FromMinutes(MinutesForScan).TotalMilliseconds) + { + AutoReset = false, + Enabled = false, + }; + _scanScheduler.AutoReset = true; + _scanScheduler.Elapsed += (_, _) => + { + _timerStopWatch?.Stop(); + if (_scanTask?.IsCompleted ?? false) + { + PluginLog.Warning("Scanning task is still running, not reinitiating."); + return; + } + + PluginLog.Debug("Initiating periodic scan for mod changes"); + _scanTask = StartFileScan(_scanCancellationTokenSource!.Token); + _timerStopWatch = Stopwatch.StartNew(); + }; + + _scanScheduler.Start(); + } + } +} diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 06bae9e..041c783 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -10,23 +10,23 @@ namespace MareSynchronos.Managers { public class IpcManager : IDisposable { - private readonly DalamudPluginInterface pluginInterface; - private ICallGateSubscriber penumbraInit; - private ICallGateSubscriber? penumbraResolvePath; - private ICallGateSubscriber? penumbraResolveModDir; - private ICallGateSubscriber? glamourerGetCharacterCustomization; - private ICallGateSubscriber? glamourerApplyCharacterCustomization; - private ICallGateSubscriber penumbraApiVersion; - private ICallGateSubscriber glamourerApiVersion; - private ICallGateSubscriber penumbraObjectIsRedrawn; - private ICallGateSubscriber? penumbraRedraw; - private ICallGateSubscriber? penumbraReverseResolvePath; - private ICallGateSubscriber glamourerRevertCustomization; + private readonly DalamudPluginInterface _pluginInterface; + private readonly ICallGateSubscriber _penumbraInit; + private readonly ICallGateSubscriber? _penumbraResolvePath; + private readonly ICallGateSubscriber? _penumbraResolveModDir; + private readonly ICallGateSubscriber? _glamourerGetCharacterCustomization; + private readonly ICallGateSubscriber? _glamourerApplyCharacterCustomization; + private readonly ICallGateSubscriber _penumbraApiVersion; + private readonly ICallGateSubscriber _glamourerApiVersion; + private readonly ICallGateSubscriber _penumbraObjectIsRedrawn; + private readonly ICallGateSubscriber? _penumbraRedraw; + private readonly ICallGateSubscriber? _penumbraReverseResolvePath; + private readonly ICallGateSubscriber _glamourerRevertCustomization; - private ICallGateSubscriber, List, int, int> - penumbraSetTemporaryMod; - private ICallGateSubscriber penumbraCreateTemporaryCollection; - private ICallGateSubscriber penumbraRemoveTemporaryCollection; + private readonly ICallGateSubscriber, List, int, int> + _penumbraSetTemporaryMod; + private readonly ICallGateSubscriber _penumbraCreateTemporaryCollection; + private readonly ICallGateSubscriber _penumbraRemoveTemporaryCollection; public bool Initialized { get; private set; } = false; @@ -34,41 +34,41 @@ namespace MareSynchronos.Managers public IpcManager(DalamudPluginInterface pi) { - pluginInterface = pi; + _pluginInterface = pi; - penumbraInit = pluginInterface.GetIpcSubscriber("Penumbra.Initialized"); - penumbraResolvePath = pluginInterface.GetIpcSubscriber("Penumbra.ResolveCharacterPath"); - penumbraResolveModDir = pluginInterface.GetIpcSubscriber("Penumbra.GetModDirectory"); - penumbraRedraw = pluginInterface.GetIpcSubscriber("Penumbra.RedrawObjectByName"); - glamourerGetCharacterCustomization = pluginInterface.GetIpcSubscriber("Glamourer.GetCharacterCustomization"); - glamourerApplyCharacterCustomization = pluginInterface.GetIpcSubscriber("Glamourer.ApplyCharacterCustomization"); - penumbraReverseResolvePath = pluginInterface.GetIpcSubscriber("Penumbra.ReverseResolvePath"); - penumbraApiVersion = pluginInterface.GetIpcSubscriber("Penumbra.ApiVersion"); - glamourerApiVersion = pluginInterface.GetIpcSubscriber("Glamourer.ApiVersion"); - glamourerRevertCustomization = pluginInterface.GetIpcSubscriber("Glamourer.RevertCharacterCustomization"); - penumbraObjectIsRedrawn = pluginInterface.GetIpcSubscriber("Penumbra.GameObjectRedrawn"); + _penumbraInit = _pluginInterface.GetIpcSubscriber("Penumbra.Initialized"); + _penumbraResolvePath = _pluginInterface.GetIpcSubscriber("Penumbra.ResolveCharacterPath"); + _penumbraResolveModDir = _pluginInterface.GetIpcSubscriber("Penumbra.GetModDirectory"); + _penumbraRedraw = _pluginInterface.GetIpcSubscriber("Penumbra.RedrawObjectByName"); + _glamourerGetCharacterCustomization = _pluginInterface.GetIpcSubscriber("Glamourer.GetCharacterCustomization"); + _glamourerApplyCharacterCustomization = _pluginInterface.GetIpcSubscriber("Glamourer.ApplyCharacterCustomization"); + _penumbraReverseResolvePath = _pluginInterface.GetIpcSubscriber("Penumbra.ReverseResolvePath"); + _penumbraApiVersion = _pluginInterface.GetIpcSubscriber("Penumbra.ApiVersion"); + _glamourerApiVersion = _pluginInterface.GetIpcSubscriber("Glamourer.ApiVersion"); + _glamourerRevertCustomization = _pluginInterface.GetIpcSubscriber("Glamourer.RevertCharacterCustomization"); + _penumbraObjectIsRedrawn = _pluginInterface.GetIpcSubscriber("Penumbra.GameObjectRedrawn"); - penumbraObjectIsRedrawn.Subscribe(RedrawEvent); - penumbraInit.Subscribe(RedrawSelf); + _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); + _penumbraInit.Subscribe(RedrawSelf); - penumbraSetTemporaryMod = - pluginInterface + _penumbraSetTemporaryMod = + _pluginInterface .GetIpcSubscriber, List, int, int>("Penumbra.AddTemporaryMod"); - penumbraCreateTemporaryCollection = - pluginInterface.GetIpcSubscriber("Penumbra.CreateTemporaryCollection"); - penumbraRemoveTemporaryCollection = - pluginInterface.GetIpcSubscriber("Penumbra.RemoveTemporaryCollection"); + _penumbraCreateTemporaryCollection = + _pluginInterface.GetIpcSubscriber("Penumbra.CreateTemporaryCollection"); + _penumbraRemoveTemporaryCollection = + _pluginInterface.GetIpcSubscriber("Penumbra.RemoveTemporaryCollection"); Initialized = true; } - public bool CheckPenumbraAPI() + public bool CheckPenumbraApi() { try { - return penumbraApiVersion.InvokeFunc() >= 4; + return _penumbraApiVersion.InvokeFunc() >= 4; } catch { @@ -76,11 +76,11 @@ namespace MareSynchronos.Managers } } - public bool CheckGlamourerAPI() + public bool CheckGlamourerApi() { try { - return glamourerApiVersion.InvokeFunc() >= 0; + return _glamourerApiVersion.InvokeFunc() >= 0; } catch { @@ -95,91 +95,78 @@ namespace MareSynchronos.Managers private void RedrawSelf() { - penumbraRedraw!.InvokeAction("self", 0); + _penumbraRedraw!.InvokeAction("self", 0); } private void Uninitialize() { - penumbraInit.Unsubscribe(RedrawSelf); - penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); - penumbraResolvePath = null; - penumbraResolveModDir = null; - glamourerGetCharacterCustomization = null; - glamourerApplyCharacterCustomization = null; - penumbraReverseResolvePath = null; + _penumbraInit.Unsubscribe(RedrawSelf); + _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); Initialized = false; PluginLog.Debug("IPC Manager disposed"); } public string[] PenumbraReverseResolvePath(string path, string characterName) { - if (!CheckPenumbraAPI()) return new[] { path }; - return penumbraReverseResolvePath!.InvokeFunc(path, characterName); + if (!CheckPenumbraApi()) return new[] { path }; + return _penumbraReverseResolvePath!.InvokeFunc(path, characterName); } public string? PenumbraResolvePath(string path, string characterName) { - if (!CheckPenumbraAPI()) return null; - return penumbraResolvePath!.InvokeFunc(path, characterName); + if (!CheckPenumbraApi()) return null; + return _penumbraResolvePath!.InvokeFunc(path, characterName); } public string? PenumbraModDirectory() { - if (!CheckPenumbraAPI()) return null; - return penumbraResolveModDir!.InvokeFunc(); + if (!CheckPenumbraApi()) return null; + return _penumbraResolveModDir!.InvokeFunc(); } public string? GlamourerGetCharacterCustomization() { - if (!CheckGlamourerAPI()) return null; - return glamourerGetCharacterCustomization!.InvokeFunc(); + if (!CheckGlamourerApi()) return null; + return _glamourerGetCharacterCustomization!.InvokeFunc(); } public void GlamourerApplyCharacterCustomization(string customization, string characterName) { - if (!CheckGlamourerAPI()) return; - glamourerApplyCharacterCustomization!.InvokeAction(customization, characterName); + if (!CheckGlamourerApi()) return; + _glamourerApplyCharacterCustomization!.InvokeAction(customization, characterName); } public void GlamourerRevertCharacterCustomization(string characterName) { - if (!CheckGlamourerAPI()) return; - glamourerRevertCustomization!.InvokeAction(characterName); + if (!CheckGlamourerApi()) return; + _glamourerRevertCustomization!.InvokeAction(characterName); } public void PenumbraRedraw(string actorName) { - if (!CheckPenumbraAPI()) return; - penumbraRedraw!.InvokeAction(actorName, 0); + if (!CheckPenumbraApi()) return; + _penumbraRedraw!.InvokeAction(actorName, 0); } public void PenumbraCreateTemporaryCollection(string characterName) { - if (!CheckPenumbraAPI()) return; + if (!CheckPenumbraApi()) return; PluginLog.Debug("Creating temp collection for " + characterName); //penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true); } public void PenumbraRemoveTemporaryCollection(string characterName) { - if (!CheckPenumbraAPI()) return; + if (!CheckPenumbraApi()) return; PluginLog.Debug("Removing temp collection for " + characterName); //penumbraRemoveTemporaryCollection.InvokeFunc(characterName); } public void PenumbraSetTemporaryMods(string characterName, IReadOnlyDictionary modPaths) { - if (!CheckPenumbraAPI()) return; + if (!CheckPenumbraApi()) return; PluginLog.Debug("Assigning temp mods for " + characterName); - try - { - //var result = penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", characterName, modPaths, new List(), 0); - //PluginLog.Debug("Success: " + result); - } - catch (Exception ex) - { - PluginLog.Error(ex, "Error during IPC call"); - } + //penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", characterName, modPaths, new List(), 0); } public void Dispose() diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 324a79f..00c1602 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -13,44 +13,61 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState; using System; using MareSynchronos.Models; -using Dalamud.Game.Gui; using MareSynchronos.PenumbraMod; using Newtonsoft.Json; using MareSynchronos.Managers; using LZ4; using MareSynchronos.WebAPI; using Dalamud.Interface.Windowing; +using MareSynchronos.UI; namespace MareSynchronos { public sealed class Plugin : IDalamudPlugin { - private const string commandName = "/mare"; - private readonly ClientState clientState; - private readonly Framework framework; - private readonly ObjectTable objectTable; - private readonly WindowSystem windowSystem; - private readonly ApiController apiController; - private CharacterManager? characterManager; - private IpcManager ipcManager; + private const string CommandName = "/mare"; + private readonly ApiController _apiController; + private readonly ClientState _clientState; + private readonly CommandManager _commandManager; + private readonly Configuration _configuration; + private readonly FileCacheManager _fileCacheManager; + private readonly Framework _framework; + private readonly IntroUI _introUi; + private readonly IpcManager _ipcManager; + private readonly ObjectTable _objectTable; + private readonly DalamudPluginInterface _pluginInterface; + private readonly PluginUi _pluginUi; + private readonly WindowSystem _windowSystem; + private CharacterManager? _characterManager; public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager, Framework framework, ObjectTable objectTable, ClientState clientState) { - this.PluginInterface = pluginInterface; - this.CommandManager = commandManager; - this.framework = framework; - this.objectTable = objectTable; - this.clientState = clientState; - Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); - Configuration.Initialize(this.PluginInterface); + _pluginInterface = pluginInterface; + _commandManager = commandManager; + _framework = framework; + _objectTable = objectTable; + _clientState = clientState; + _configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); + _configuration.Initialize(_pluginInterface); - windowSystem = new WindowSystem("MareSynchronos"); + _windowSystem = new WindowSystem("MareSynchronos"); - apiController = new ApiController(Configuration); - ipcManager = new IpcManager(PluginInterface); + _apiController = new ApiController(_configuration); + _ipcManager = new IpcManager(_pluginInterface); + _fileCacheManager = new FileCacheManager(new FileCacheFactory(), _ipcManager, _configuration); + + var uiSharedComponent = + new UIShared(_ipcManager, _apiController, _fileCacheManager, _configuration); // you might normally want to embed resources and load them from the manifest stream - this.PluginUi = new PluginUI(this.Configuration, windowSystem, apiController, ipcManager); + _pluginUi = new PluginUi(_windowSystem, uiSharedComponent, _configuration, _apiController); + _introUi = new IntroUI(_windowSystem, uiSharedComponent, _configuration, _fileCacheManager); + _introUi.FinishedRegistration += (_, _) => + { + _pluginUi.IsOpen = true; + _introUi?.Dispose(); + ClientState_Login(null, EventArgs.Empty); + }; new FileCacheContext().Dispose(); // make sure db is initialized I guess @@ -64,54 +81,64 @@ namespace MareSynchronos } public string Name => "Mare Synchronos"; - private CommandManager CommandManager { get; init; } - private Configuration Configuration { get; init; } - private DalamudPluginInterface PluginInterface { get; init; } - private PluginUI PluginUi { get; init; } public void Dispose() { - this.PluginUi?.Dispose(); - this.CommandManager.RemoveHandler(commandName); - clientState.Login -= ClientState_Login; - clientState.Logout -= ClientState_Logout; - ipcManager?.Dispose(); - characterManager?.Dispose(); - apiController?.Dispose(); + _commandManager.RemoveHandler(CommandName); + _clientState.Login -= ClientState_Login; + _clientState.Logout -= ClientState_Logout; + + _pluginUi?.Dispose(); + _introUi?.Dispose(); + + _fileCacheManager?.Dispose(); + _ipcManager?.Dispose(); + _characterManager?.Dispose(); + _apiController?.Dispose(); } private void ClientState_Login(object? sender, EventArgs e) { PluginLog.Debug("Client login"); + if (!_configuration.HasValidSetup) + { + _introUi.IsOpen = true; + return; + } + else + { + _introUi.IsOpen = false; + _pluginInterface.UiBuilder.Draw += Draw; + } + Task.Run(async () => { - while (clientState.LocalPlayer == null) + while (_clientState.LocalPlayer == null) { await Task.Delay(50); } - characterManager = new CharacterManager( - clientState, framework, apiController, objectTable, ipcManager, new FileReplacementFactory(ipcManager), Configuration); - characterManager.StartWatchingPlayer(); - ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString()); + _characterManager = new CharacterManager( + _clientState, _framework, _apiController, _objectTable, _ipcManager, new FileReplacementFactory(_ipcManager), _configuration); + _characterManager.StartWatchingPlayer(); + _ipcManager.PenumbraRedraw(_clientState.LocalPlayer!.Name.ToString()); }); - PluginInterface.UiBuilder.Draw += Draw; - PluginInterface.UiBuilder.OpenConfigUi += OpenConfigUI; + _pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi; - CommandManager.AddHandler(commandName, new CommandInfo(OnCommand) + _commandManager.AddHandler(CommandName, new CommandInfo(OnCommand) { - HelpMessage = "pass 'scan' to initialize or rescan files into the database" + HelpMessage = "Opens the Mare Synchronos UI" }); } private void ClientState_Logout(object? sender, EventArgs e) { PluginLog.Debug("Client logout"); - characterManager?.Dispose(); - PluginInterface.UiBuilder.Draw -= Draw; - PluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUI; - CommandManager.RemoveHandler(commandName); + _characterManager?.Dispose(); + _pluginInterface.UiBuilder.Draw -= Draw; + _pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi; + _commandManager.RemoveHandler(CommandName); } private void CopyFile(FileReplacement replacement, string targetDirectory, Dictionary? resourceDict = null) @@ -126,18 +153,18 @@ namespace MareSynchronos { var ext = new FileInfo(fileCache.Filepath).Extension; var newFilePath = Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext); - string lc4hcPath = Path.Combine(targetDirectory, "files", "lz4hc." + fileCache.Hash.ToLower() + ext); - if (!File.Exists(lc4hcPath)) + string lc4HcPath = Path.Combine(targetDirectory, "files", "lz4hc." + fileCache.Hash.ToLower() + ext); + if (!File.Exists(lc4HcPath)) { Stopwatch st = Stopwatch.StartNew(); - File.WriteAllBytes(lc4hcPath, LZ4Codec.WrapHC(File.ReadAllBytes(fileCache.Filepath), 0, (int)new FileInfo(fileCache.Filepath).Length)); + File.WriteAllBytes(lc4HcPath, LZ4Codec.WrapHC(File.ReadAllBytes(fileCache.Filepath), 0, (int)new FileInfo(fileCache.Filepath).Length)); st.Stop(); - PluginLog.Debug("Compressed " + new FileInfo(fileCache.Filepath).Length + " bytes to " + new FileInfo(lc4hcPath).Length + " bytes in " + st.Elapsed); + PluginLog.Debug("Compressed " + new FileInfo(fileCache.Filepath).Length + " bytes to " + new FileInfo(lc4HcPath).Length + " bytes in " + st.Elapsed); File.Copy(fileCache.Filepath, newFilePath); if (resourceDict != null) { - foreach(var path in replacement.GamePaths) + foreach (var path in replacement.GamePaths) { resourceDict[path] = $"files\\{fileCache.Hash.ToLower() + ext}"; } @@ -157,40 +184,35 @@ namespace MareSynchronos private void Draw() { - windowSystem.Draw(); - } - - private void OpenConfigUI() - { - this.PluginUi.Toggle(); + _windowSystem.Draw(); } private void OnCommand(string command, string args) { if (args == "printjson") { - _ = characterManager?.DebugJson(); + _ = _characterManager?.DebugJson(); } if (args.StartsWith("watch")) { var playerName = args.Replace("watch", "").Trim(); - characterManager!.WatchPlayer(playerName); + _characterManager!.WatchPlayer(playerName); } if (args.StartsWith("stop")) { var playerName = args.Replace("watch", "").Trim(); - characterManager!.StopWatchPlayer(playerName); + _characterManager!.StopWatchPlayer(playerName); } if (args == "createtestmod") { Task.Run(() => { - var playerName = clientState.LocalPlayer!.Name.ToString(); + var playerName = _clientState.LocalPlayer!.Name.ToString(); var modName = $"Mare Synchronos Test Mod {playerName}"; - var modDirectory = ipcManager!.PenumbraModDirectory()!; + var modDirectory = _ipcManager!.PenumbraModDirectory()!; string modDirectoryPath = Path.Combine(modDirectory, modName); if (Directory.Exists(modDirectoryPath)) { @@ -206,7 +228,7 @@ namespace MareSynchronos Description = "Mare Synchronous Test Mod Export", }; - var resources = characterManager!.BuildCharacterCache(); + var resources = _characterManager!.BuildCharacterCache(); var metaJson = JsonConvert.SerializeObject(meta); File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson); @@ -229,8 +251,13 @@ namespace MareSynchronos if (string.IsNullOrEmpty(args)) { - PluginUi.Toggle(); + _pluginUi.Toggle(); } } + + private void OpenConfigUi() + { + _pluginUi.Toggle(); + } } } diff --git a/MareSynchronos/PluginUI.cs b/MareSynchronos/PluginUI.cs deleted file mode 100644 index c9d8d2c..0000000 --- a/MareSynchronos/PluginUI.cs +++ /dev/null @@ -1,348 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Interface.Colors; -using Dalamud.Interface.Windowing; -using Dalamud.Logging; -using ImGuiNET; -using MareSynchronos.Managers; -using MareSynchronos.WebAPI; -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Configuration; -using MareSynchronos.API; - -namespace MareSynchronos -{ - // It is good to have this be disposable in general, in case you ever need it - // to do any cleanup - class PluginUI : Window, IDisposable - { - private Configuration configuration; - private readonly WindowSystem windowSystem; - private readonly ApiController apiController; - private readonly IpcManager ipcManager; - private string? uid; - private const string mainServer = "Lunae Crescere Incipientis (Central Server EU)"; - - // passing in the image here just for simplicity - public PluginUI(Configuration configuration, WindowSystem windowSystem, ApiController apiController, IpcManager ipcManager) : base("Mare Synchronos Settings", ImGuiWindowFlags.None) - { - SizeConstraints = new WindowSizeConstraints() - { - MinimumSize = new(700, 400), - MaximumSize = new(700, 2000) - }; - - this.configuration = configuration; - this.windowSystem = windowSystem; - this.apiController = apiController; - this.ipcManager = ipcManager; - windowSystem.AddWindow(this); - } - - public void Dispose() - { - windowSystem.RemoveWindow(this); - } - - public override void Draw() - { - if (!IsOpen) - { - return; - } - - - if (apiController.SecretKey != "-" && !apiController.IsConnected && apiController.ServerAlive) - { - if (ImGui.Button("Reset Secret Key")) - { - configuration.ClientSecret.Clear(); - configuration.Save(); - apiController.RestartHeartbeat(); - } - } - else if (apiController.SecretKey == "-") - { - DrawIntroContent(); - } - else - { - if (!OtherPluginStateOk()) return; - - DrawSettingsContent(); - } - } - - private void DrawSettingsContent() - { - PrintServerState(); - ImGui.Separator(); - ImGui.Text("Your UID"); - ImGui.SameLine(); - if (apiController.ServerAlive) - { - ImGui.TextColored(ImGuiColors.ParsedGreen, apiController.UID); - ImGui.SameLine(); - if (ImGui.Button("Copy UID")) - { - ImGui.SetClipboardText(apiController.UID); - } - ImGui.Text("Share this UID to other Mare users so they can add you to their whitelist."); - ImGui.Separator(); - DrawWhiteListContent(); - ImGui.Separator(); - string cachePath = configuration.CacheFolder; - if (ImGui.InputText("CachePath", ref cachePath, 255)) - { - configuration.CacheFolder = cachePath; - configuration.Save(); - } - ImGui.Separator(); - ImGui.Text("Pending uploads"); - ImGui.Text($"Total: {ByteToKiB(apiController.CurrentUploads.Sum(a => a.Value.Item1))} / {ByteToKiB(apiController.CurrentUploads.Sum(a => a.Value.Item2))}"); - if (ImGui.BeginTable("Uploads", 3)) - { - ImGui.TableSetupColumn("File"); - ImGui.TableSetupColumn("Uploaded"); - ImGui.TableSetupColumn("Size"); - ImGui.TableHeadersRow(); - foreach (var hash in apiController.CurrentUploads.Keys) - { - var color = UploadColor(apiController.CurrentUploads[hash]); - ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TableNextColumn(); - ImGui.Text(hash); - ImGui.TableNextColumn(); - ImGui.Text(ByteToKiB(apiController.CurrentUploads[hash].Item1)); - ImGui.TableNextColumn(); - ImGui.Text(ByteToKiB(apiController.CurrentUploads[hash].Item2)); - ImGui.PopStyleColor(); - ImGui.TableNextRow(); - } - ImGui.EndTable(); - } - ImGui.Separator(); - ImGui.Text("Pending Downloads"); - ImGui.Text($"Total: {ByteToKiB(apiController.CurrentDownloads.Sum(a => a.Value.Item1))} / {ByteToKiB(apiController.CurrentDownloads.Sum(a => a.Value.Item2))}"); - if (ImGui.BeginTable("Downloads", 3)) - { - ImGui.TableSetupColumn("File"); - ImGui.TableSetupColumn("Downloaded"); - ImGui.TableSetupColumn("Size"); - ImGui.TableHeadersRow(); - foreach (var hash in apiController.CurrentDownloads.Keys) - { - var color = UploadColor(apiController.CurrentDownloads[hash]); - ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TableNextColumn(); - ImGui.Text(hash); - ImGui.TableNextColumn(); - ImGui.Text(ByteToKiB(apiController.CurrentDownloads[hash].Item1)); - ImGui.TableNextColumn(); - ImGui.Text(ByteToKiB(apiController.CurrentDownloads[hash].Item2)); - ImGui.PopStyleColor(); - ImGui.TableNextRow(); - } - ImGui.EndTable(); - } - - } - else - { - ImGui.TextColored(ImGuiColors.DalamudRed, "Service unavailable"); - } - } - - private Vector4 UploadColor((long, long) data) => data.Item1 == 0 ? ImGuiColors.DalamudGrey : - data.Item1 == data.Item2 ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudYellow; - - private string ByteToKiB(long bytes) => (bytes / 1024.0).ToString("0.00") + " KiB"; - - private void DrawWhiteListContent() - { - if (!apiController.ServerAlive) return; - ImGui.Text("Whitelists"); - if (ImGui.BeginTable("WhitelistTable", 5)) - { - ImGui.TableSetupColumn("Pause"); - ImGui.TableSetupColumn("UID"); - ImGui.TableSetupColumn("Sync"); - ImGui.TableSetupColumn("Paused (you/other)"); - ImGui.TableSetupColumn(""); - ImGui.TableHeadersRow(); - foreach (var item in apiController.WhitelistEntries.ToList()) - { - ImGui.TableNextColumn(); - bool isPaused = item.IsPaused; - if (ImGui.Checkbox("Paused##" + item.OtherUID, ref isPaused)) - { - _ = apiController.SendWhitelistPauseChange(item.OtherUID, isPaused); - } - - ImGui.TableNextColumn(); - ImGui.TextColored(GetBoolColor(item.IsSynced && !item.IsPausedFromOthers && !item.IsPaused), - item.OtherUID); - ImGui.TableNextColumn(); - ImGui.TextColored(GetBoolColor(item.IsSynced), !item.IsSynced ? "Has not added you" : "On both whitelists"); - ImGui.TableNextColumn(); - ImGui.TextColored(GetBoolColor((!item.IsPausedFromOthers && !item.IsPaused)), item.IsPaused + " / " + item.IsPausedFromOthers); - ImGui.TableNextColumn(); - if (ImGui.Button("Delete##" + item.OtherUID)) - { - _ = apiController.SendWhitelistRemoval(item.OtherUID); - apiController.WhitelistEntries.Remove(item); - } - ImGui.TableNextRow(); - } - ImGui.EndTable(); - } - - var whitelistEntry = tempDto.OtherUID; - if (ImGui.InputText("Add new whitelist entry", ref whitelistEntry, 20)) - { - tempDto.OtherUID = whitelistEntry; - } - - ImGui.SameLine(); - if (ImGui.Button("Add")) - { - if (apiController.WhitelistEntries.All(w => w.OtherUID != tempDto.OtherUID)) - { - apiController.WhitelistEntries.Add(new WhitelistDto() - { - OtherUID = tempDto.OtherUID - }); - - _ = apiController.SendWhitelistAddition(tempDto.OtherUID); - - tempDto.OtherUID = string.Empty; - } - } - } - - private WhitelistDto tempDto = new WhitelistDto() { OtherUID = string.Empty }; - - private int serverSelectionIndex = 0; - - private async void DrawIntroContent() - { - ImGui.SetWindowFontScale(1.3f); - ImGui.Text("Welcome to Mare Synchronos!"); - ImGui.SetWindowFontScale(1.0f); - ImGui.Separator(); - ImGui.TextWrapped("Mare Synchronos is a plugin that will replicate your full current character state including all Penumbra mods to other whitelisted Mare Synchronos users. " + - "Note that you will have to have Penumbra as well as Glamourer installed to use this plugin."); - if (!OtherPluginStateOk()) return; - - ImGui.SetWindowFontScale(1.5f); - string readThis = "READ THIS CAREFULLY BEFORE REGISTERING"; - var textSize = ImGui.CalcTextSize(readThis); - ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2); - ImGui.TextColored(ImGuiColors.DalamudRed, readThis); - ImGui.SetWindowFontScale(1.0f); - - ImGui.TextWrapped("All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. " + - "The plugin will exclusively upload the necessary mod files and not the whole mod."); - ImGui.TextWrapped("If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. " + - "Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. " + - "Files present on the service that already represent your active mod files will not be uploaded again. To register at a service you will need to hold ctrl."); - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped("The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. " + - "Please think about who you are going to whitelist since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. " + - "Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod in question."); - ImGui.PopStyleColor(); - ImGui.TextWrapped("Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. After a period of not being used, the mod files will be automatically deleted. " + - "You will also be able to wipe all the files you have personally uploaded on request."); - ImGui.TextColored(ImGuiColors.DalamudRed, "This service is provided as-is."); - ImGui.Separator(); - string[] comboEntries = new[] { mainServer, "Custom Service" }; - if (ImGui.BeginCombo("Service", comboEntries[serverSelectionIndex])) - { - for (int n = 0; n < comboEntries.Length; n++) - { - bool isSelected = serverSelectionIndex == n; - if (ImGui.Selectable(comboEntries[n], isSelected)) - { - serverSelectionIndex = n; - } - - if (isSelected) - { - ImGui.SetItemDefaultFocus(); - } - - bool useCustomService = (serverSelectionIndex != 0); - - if (apiController.UseCustomService != useCustomService) - { - PluginLog.Debug("Configuration " + apiController.UseCustomService + " changing to " + useCustomService); - apiController.UseCustomService = useCustomService; - configuration.Save(); - } - } - - ImGui.EndCombo(); - } - - if (apiController.UseCustomService) - { - string serviceAddress = configuration.ApiUri; - if (ImGui.InputText("Service address", ref serviceAddress, 255)) - { - if (configuration.ApiUri != serviceAddress) - { - configuration.ApiUri = serviceAddress; - apiController.RestartHeartbeat(); - configuration.Save(); - } - } - } - - PrintServerState(); - if (apiController.ServerAlive) - { - if (ImGui.Button("Register")) - { - if (ImGui.GetIO().KeyCtrl) - { - await apiController.Register(); - } - } - } - } - - private Vector4 GetBoolColor(bool input) => input ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - - private bool OtherPluginStateOk() - { - var penumbraExists = ipcManager.CheckPenumbraAPI(); - var glamourerExists = ipcManager.CheckGlamourerAPI(); - - var penumbraColor = penumbraExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var glamourerColor = glamourerExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - ImGui.Text("Penumbra:"); - ImGui.SameLine(); - ImGui.TextColored(penumbraColor, penumbraExists ? "Available" : "Unavailable"); - ImGui.Text("Glamourer:"); - ImGui.SameLine(); - ImGui.TextColored(glamourerColor, glamourerExists ? "Available" : "Unavailable"); - - if (!penumbraExists || !glamourerExists) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Mare Synchronos."); - return false; - } - - return true; - } - - private void PrintServerState() - { - ImGui.Text("Service status of " + (string.IsNullOrEmpty(configuration.ApiUri) ? mainServer : configuration.ApiUri)); - ImGui.SameLine(); - var color = apiController.ServerAlive ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - ImGui.TextColored(color, apiController.ServerAlive ? "Available" : "Unavailable"); - } - } -} diff --git a/MareSynchronos/UI/IntroUI.cs b/MareSynchronos/UI/IntroUI.cs new file mode 100644 index 0000000..c5c5b24 --- /dev/null +++ b/MareSynchronos/UI/IntroUI.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Windowing; +using ImGuiNET; +using MareSynchronos.FileCacheDB; +using MareSynchronos.Managers; +using MareSynchronos.WebAPI; + +namespace MareSynchronos.UI +{ + internal class IntroUI : Window, IDisposable + { + private readonly UIShared _uiShared; + private readonly Configuration _pluginConfiguration; + private readonly FileCacheManager _fileCacheManager; + private readonly WindowSystem _windowSystem; + private bool _readFirstPage = false; + + public event EventHandler? FinishedRegistration; + + public void Dispose() + { + _windowSystem.RemoveWindow(this); + } + + public IntroUI(WindowSystem windowSystem, UIShared uiShared, Configuration pluginConfiguration, + FileCacheManager fileCacheManager) : base("Mare Synchronos Setup") + { + _uiShared = uiShared; + _pluginConfiguration = pluginConfiguration; + _fileCacheManager = fileCacheManager; + _windowSystem = windowSystem; + + SizeConstraints = new WindowSizeConstraints() + { + MinimumSize = new(600, 400), + MaximumSize = new(600, 2000) + }; + + _windowSystem.AddWindow(this); + IsOpen = true; + } + + public override void Draw() + { + if (!IsOpen) + { + return; + } + + if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage) + { + ImGui.SetWindowFontScale(1.3f); + ImGui.Text("Welcome to Mare Synchronos!"); + ImGui.SetWindowFontScale(1.0f); + ImGui.Separator(); + UIShared.TextWrapped("Mare Synchronos is a plugin that will replicate your full current character state including all Penumbra mods to other whitelisted Mare Synchronos users. " + + "Note that you will have to have Penumbra as well as Glamourer installed to use this plugin."); + UIShared.TextWrapped("We will have to setup a few things first before you can start using this plugin. Click on next to continue."); + + if (!_uiShared.DrawOtherPluginState()) return; + ImGui.Separator(); + if (ImGui.Button("Next##toAgreement")) + { + _readFirstPage = true; + } + } + else if (!_pluginConfiguration.AcceptedAgreement && _readFirstPage) + { + ImGui.SetWindowFontScale(1.3f); + ImGui.Text("Agreement of Usage of Service"); + ImGui.SetWindowFontScale(1.0f); + ImGui.Separator(); + ImGui.SetWindowFontScale(1.5f); + string readThis = "READ THIS CAREFULLY"; + var textSize = ImGui.CalcTextSize(readThis); + ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2); + ImGui.TextColored(ImGuiColors.DalamudRed, readThis); + ImGui.SetWindowFontScale(1.0f); + ImGui.Separator(); + UIShared.TextWrapped("All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. " + + "The plugin will exclusively upload the necessary mod files and not the whole mod."); + UIShared.TextWrapped("If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. " + + "Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. " + + "Files present on the service that already represent your active mod files will not be uploaded again."); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + UIShared.TextWrapped("The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. " + + "Please think about who you are going to whitelist since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. " + + "Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod."); + ImGui.PopStyleColor(); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + UIShared.TextWrapped("The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly add everyone to your whitelist."); + ImGui.PopStyleColor(); + UIShared.TextWrapped("Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. " + + "After a period of not being used, the mod files will be automatically deleted. " + + "You will also be able to wipe all the files you have personally uploaded on request. " + + "The service holds no information about which mod files belong to which mod."); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + UIShared.TextWrapped("This service is provided as-is. In case of abuse, contact darkarchon#4313 on Discord or join the Mare Synchronos Discord. " + + "To accept those conditions hold CTRL while clicking 'I agree'"); + ImGui.PopStyleColor(); + ImGui.Separator(); + + if (ImGui.Button("I agree##toSetup")) + { + if (ImGui.GetIO().KeyCtrl) + { + _pluginConfiguration.AcceptedAgreement = true; + _pluginConfiguration.Save(); + } + } + } + else if (_pluginConfiguration.AcceptedAgreement && (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) || _pluginConfiguration.InitialScanComplete == false)) + { + ImGui.SetWindowFontScale(1.3f); + ImGui.Text("File Cache Setup"); + ImGui.SetWindowFontScale(1.0f); + ImGui.Separator(); + UIShared.TextWrapped("To not unnecessary download files already present on your computer, Mare Synchronos will have to scan your Penumbra mod directory. " + + "Additionally, a local cache folder must be set where Mare Synchronos will download its local file cache to. " + + "Once the Cache Folder is set and the scan complete, this page will automatically forward to registration at a service."); + UIShared.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + UIShared.TextWrapped("Warning: once past this step you should not delete the FileCache.db of Mare Synchronos in the Plugin Configurations folder of Dalamud. " + + "Otherwise on the next launch a full re-scan of the file cache database will be initiated."); + ImGui.PopStyleColor(); + _uiShared.DrawCacheDirectorySetting(); + + + if (!_fileCacheManager.IsScanRunning) + { + UIShared.TextWrapped("You can adjust how many parallel threads will be used for scanning. Mind that ultimately it will depend on the amount of mods, your disk speed and your CPU. " + + "More is not necessarily better, the default of 10 should be fine for most cases."); + _uiShared.DrawParallelScansSetting(); + + + if (ImGui.Button("Start Scan##startScan")) + { + _fileCacheManager.StartInitialScan(); + } + } + else + { + _uiShared.DrawFileScanState(); + } + } + else + { + ImGui.SetWindowFontScale(1.3f); + ImGui.Text("Service registration"); + ImGui.SetWindowFontScale(1.0f); + ImGui.Separator(); + if (_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)) + { + ImGui.Separator(); + UIShared.TextWrapped(_pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri]); + ImGui.Separator(); + ImGui.Button("Copy secret key to clipboard"); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); + UIShared.TextWrapped("This is the only time you will be able to see this key in the UI. You can copy it to make a backup somewhere."); + ImGui.PopStyleColor(); + ImGui.Separator(); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.ParsedGreen); + UIShared.TextWrapped("You are now ready to go. Press Finish to finalize the settings and open the Mare Synchronos main UI."); + ImGui.PopStyleColor(); + ImGui.Separator(); + if (ImGui.Button("Finish##finishIntro")) + { + FinishedRegistration?.Invoke(null, EventArgs.Empty); + IsOpen = false; + } + } + else + { + UIShared.TextWrapped("You will now have to register at a service. You can use the provided central service or pick a custom one. " + + "There is no support for custom services from the plugin creator. Use at your own risk."); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + UIShared.TextWrapped("On registration on a service the plugin will create and save a secret key to your plugin configuration. " + + "Make a backup of your secret key. In case of loss, it cannot be restored. The secret key is your identification to the service " + + "to verify who you are. It is directly tied to the UID you will be receiving. In case of loss, you will have to re-register an account."); + UIShared.TextWrapped("Do not ever, under any circumstances, share your secret key to anyone! Likewise do not share your Mare Synchronos plugin configuration to anyone!"); + ImGui.PopStyleColor(); + _uiShared.DrawServiceSelection(); + } + } + } + } +} diff --git a/MareSynchronos/UI/PluginUI.cs b/MareSynchronos/UI/PluginUI.cs new file mode 100644 index 0000000..841dacc --- /dev/null +++ b/MareSynchronos/UI/PluginUI.cs @@ -0,0 +1,263 @@ +using Dalamud.Interface; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Windowing; +using Dalamud.Logging; +using ImGuiNET; +using MareSynchronos.WebAPI; +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Configuration; +using MareSynchronos.API; + +namespace MareSynchronos.UI +{ + class PluginUi : Window, IDisposable + { + private readonly Configuration _configuration; + private readonly WindowSystem _windowSystem; + private readonly ApiController _apiController; + private readonly UIShared _uiShared; + + public PluginUi(WindowSystem windowSystem, + UIShared uiShared, Configuration configuration, ApiController apiController) : base("Mare Synchronos Settings", ImGuiWindowFlags.None) + { + SizeConstraints = new WindowSizeConstraints() + { + MinimumSize = new(1000, 400), + MaximumSize = new(1000, 2000), + }; + + this._configuration = configuration; + this._windowSystem = windowSystem; + this._apiController = apiController; + _uiShared = uiShared; + windowSystem.AddWindow(this); + } + + public void Dispose() + { + _windowSystem.RemoveWindow(this); + } + + public override void Draw() + { + if (!IsOpen) + { + return; + } + + if (_apiController.SecretKey != "-" && !_apiController.IsConnected && _apiController.ServerAlive) + { + if (ImGui.Button("Reset Secret Key")) + { + _configuration.ClientSecret.Clear(); + _configuration.Save(); + _apiController.RestartHeartbeat(); + } + } + else + { + if (!_uiShared.DrawOtherPluginState()) return; + + DrawSettingsContent(); + } + } + + private void DrawSettingsContent() + { + _uiShared.PrintServerState(); + ImGui.Separator(); + ImGui.SetWindowFontScale(1.2f); + ImGui.Text("Your UID"); + ImGui.SameLine(); + if (_apiController.ServerAlive) + { + ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.UID); + ImGui.SameLine(); + ImGui.SetWindowFontScale(1.0f); + if (ImGui.Button("Copy UID")) + { + ImGui.SetClipboardText(_apiController.UID); + } + ImGui.Text("Share this UID to other Mare users so they can add you to their whitelist."); + ImGui.Separator(); + DrawWhiteListContent(); + DrawFileCacheSettings(); + DrawCurrentTransfers(); + } + else + { + ImGui.TextColored(ImGuiColors.DalamudRed, "No UID (Service unavailable)"); + ImGui.SetWindowFontScale(1.0f); + } + } + + private void DrawCurrentTransfers() + { + if (ImGui.TreeNode( + $"Current Transfers")) + { + if (ImGui.BeginTable("TransfersTable", 2)) + { + ImGui.TableSetupColumn( + $"Uploads ({UIShared.ByteToKiB(_apiController.CurrentUploads.Sum(a => a.Value.Item1))} / {UIShared.ByteToKiB(_apiController.CurrentUploads.Sum(a => a.Value.Item2))})"); + ImGui.TableSetupColumn($"Downloads ({UIShared.ByteToKiB(_apiController.CurrentDownloads.Sum(a => a.Value.Item1))} / {UIShared.ByteToKiB(_apiController.CurrentDownloads.Sum(a => a.Value.Item2))})"); + + ImGui.TableHeadersRow(); + + ImGui.TableNextColumn(); + if (ImGui.BeginTable("UploadsTable", 3)) + { + ImGui.TableSetupColumn("File"); + ImGui.TableSetupColumn("Uploaded"); + ImGui.TableSetupColumn("Size"); + ImGui.TableHeadersRow(); + foreach (var hash in _apiController.CurrentUploads.Keys) + { + var color = UIShared.UploadColor(_apiController.CurrentUploads[hash]); + ImGui.PushStyleColor(ImGuiCol.Text, color); + ImGui.TableNextColumn(); + ImGui.Text(hash); + ImGui.TableNextColumn(); + ImGui.Text(UIShared.ByteToKiB(_apiController.CurrentUploads[hash].Item1)); + ImGui.TableNextColumn(); + ImGui.Text(UIShared.ByteToKiB(_apiController.CurrentUploads[hash].Item2)); + ImGui.PopStyleColor(); + ImGui.TableNextRow(); + } + + ImGui.EndTable(); + } + + ImGui.TableNextColumn(); + if (ImGui.BeginTable("DownloadsTable", 3)) + { + ImGui.TableSetupColumn("File"); + ImGui.TableSetupColumn("Downloaded"); + ImGui.TableSetupColumn("Size"); + ImGui.TableHeadersRow(); + foreach (var hash in _apiController.CurrentDownloads.Keys) + { + var color = UIShared.UploadColor(_apiController.CurrentDownloads[hash]); + ImGui.PushStyleColor(ImGuiCol.Text, color); + ImGui.TableNextColumn(); + ImGui.Text(hash); + ImGui.TableNextColumn(); + ImGui.Text(UIShared.ByteToKiB(_apiController.CurrentDownloads[hash].Item1)); + ImGui.TableNextColumn(); + ImGui.Text(UIShared.ByteToKiB(_apiController.CurrentDownloads[hash].Item2)); + ImGui.PopStyleColor(); + ImGui.TableNextRow(); + } + + ImGui.EndTable(); + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } + + private void DrawFileCacheSettings() + { + if (ImGui.TreeNode("File Cache Settings")) + { + _uiShared.DrawFileScanState(); + _uiShared.DrawCacheDirectorySetting(); + _uiShared.DrawParallelScansSetting(); + ImGui.TreePop(); + } + } + + private void DrawWhiteListContent() + { + if (!_apiController.ServerAlive) return; + if (ImGui.TreeNode("Whitelist Configuration")) + { + if (ImGui.BeginTable("WhitelistTable", 6)) + { + ImGui.TableSetupColumn("Pause", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.WidthFixed, 110); + ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Paused", ImGuiTableColumnFlags.WidthFixed, 140); + ImGui.TableSetupColumn("Comment", ImGuiTableColumnFlags.WidthFixed, 400); + ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 70); + + ImGui.TableHeadersRow(); + foreach (var item in _apiController.WhitelistEntries.ToList()) + { + ImGui.TableNextColumn(); + bool isPaused = item.IsPaused; + if (ImGui.Checkbox("##paused" + item.OtherUID, ref isPaused)) + { + _ = _apiController.SendWhitelistPauseChange(item.OtherUID, isPaused); + } + + ImGui.TableNextColumn(); + ImGui.TextColored( + UIShared.GetBoolColor(item.IsSynced && !item.IsPausedFromOthers && !item.IsPaused), + item.OtherUID); + ImGui.TableNextColumn(); + ImGui.TextColored(UIShared.GetBoolColor(item.IsSynced), + !item.IsSynced ? "Has not added you" : "Whitelisted"); + ImGui.TableNextColumn(); + string pauseYou = item.IsPaused ? "You paused them" : ""; + string pauseThey = item.IsPausedFromOthers ? "They paused you" : ""; + string separator = (item.IsPaused && item.IsPausedFromOthers) ? Environment.NewLine : ""; + string entry = pauseYou + separator + pauseThey; + ImGui.TextColored(UIShared.GetBoolColor(!item.IsPausedFromOthers && !item.IsPaused), + string.IsNullOrEmpty(entry) ? "No" : entry); + ImGui.TableNextColumn(); + string charComment = _configuration.UidComments.ContainsKey(item.OtherUID) ? _configuration.UidComments[item.OtherUID] : string.Empty; + ImGui.SetNextItemWidth(400); + if (ImGui.InputTextWithHint("##comment" + item.OtherUID, "Add your comment here (comments will not be synced)", ref charComment, 255)) + { + _configuration.UidComments[item.OtherUID] = charComment; + _configuration.Save(); + } + ImGui.TableNextColumn(); + if (ImGui.Button("Delete##" + item.OtherUID)) + { + _ = _apiController.SendWhitelistRemoval(item.OtherUID); + _apiController.WhitelistEntries.Remove(item); + } + + ImGui.TableNextRow(); + } + + ImGui.EndTable(); + } + + var whitelistEntry = tempDto.OtherUID; + ImGui.SetNextItemWidth(200); + if (ImGui.InputText("UID", ref whitelistEntry, 20)) + { + tempDto.OtherUID = whitelistEntry; + } + + ImGui.SameLine(); + if (ImGui.Button("Add to whitelist")) + { + if (_apiController.WhitelistEntries.All(w => w.OtherUID != tempDto.OtherUID)) + { + _apiController.WhitelistEntries.Add(new WhitelistDto() + { + OtherUID = tempDto.OtherUID + }); + + _ = _apiController.SendWhitelistAddition(tempDto.OtherUID); + + tempDto.OtherUID = string.Empty; + } + } + + ImGui.TreePop(); + } + } + + private WhitelistDto tempDto = new WhitelistDto() { OtherUID = string.Empty }; + } +} diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs new file mode 100644 index 0000000..9828094 --- /dev/null +++ b/MareSynchronos/UI/UIShared.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using Dalamud.Interface.Colors; +using ImGuiNET; +using MareSynchronos.FileCacheDB; +using MareSynchronos.Managers; +using MareSynchronos.WebAPI; + +namespace MareSynchronos.UI +{ + internal class UIShared + { + private readonly IpcManager _ipcManager; + private readonly ApiController _apiController; + private readonly FileCacheManager _fileCacheManager; + private readonly Configuration _pluginConfiguration; + + public UIShared(IpcManager ipcManager, ApiController apiController, FileCacheManager fileCacheManager, Configuration pluginConfiguration) + { + _ipcManager = ipcManager; + _apiController = apiController; + _fileCacheManager = fileCacheManager; + _pluginConfiguration = pluginConfiguration; + } + + public bool DrawOtherPluginState() + { + var penumbraExists = _ipcManager.CheckPenumbraApi(); + var glamourerExists = _ipcManager.CheckGlamourerApi(); + + var penumbraColor = penumbraExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + var glamourerColor = glamourerExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + ImGui.Text("Penumbra:"); + ImGui.SameLine(); + ImGui.TextColored(penumbraColor, penumbraExists ? "Available" : "Unavailable"); + ImGui.SameLine(); + ImGui.Text("Glamourer:"); + ImGui.SameLine(); + ImGui.TextColored(glamourerColor, glamourerExists ? "Available" : "Unavailable"); + + if (!penumbraExists || !glamourerExists) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Mare Synchronos."); + return false; + } + + return true; + } + + public void DrawFileScanState() + { + ImGui.Text("File Scanner Status"); + if (_fileCacheManager.IsScanRunning) + { + ImGui.Text("Scan is running"); + ImGui.Text("Current Progress:"); + ImGui.SameLine(); + ImGui.Text(_fileCacheManager.TotalFiles <= 0 + ? "Collecting files" + : $"Processing {_fileCacheManager.CurrentFileProgress} / {_fileCacheManager.TotalFiles} files"); + } + else if (_fileCacheManager.TimeToNextScan.TotalSeconds == 0) + { + ImGui.Text("Scan not started"); + } + { + ImGui.Text("Next scan in " + _fileCacheManager.TimeToNextScan.ToString(@"mm\:ss") + " minutes"); + } + } + + public void PrintServerState() + { + ImGui.Text("Service status of " + (string.IsNullOrEmpty(_pluginConfiguration.ApiUri) ? ApiController.MainServer : _pluginConfiguration.ApiUri)); + ImGui.SameLine(); + var color = _apiController.ServerAlive ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + ImGui.TextColored(color, _apiController.ServerAlive ? "Available" : "Unavailable"); + } + + public static void TextWrapped(string text) + { + ImGui.PushTextWrapPos(0); + ImGui.TextUnformatted(text); + ImGui.PopTextWrapPos(); + } + + public static Vector4 GetBoolColor(bool input) => input ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + + public static Vector4 UploadColor((long, long) data) => data.Item1 == 0 ? ImGuiColors.DalamudGrey : + data.Item1 == data.Item2 ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudYellow; + + public static string ByteToKiB(long bytes) => (bytes / 1024.0).ToString("0.00") + " KiB"; + + private int _serverSelectionIndex = 0; + + public void DrawServiceSelection() + { + string[] comboEntries = new[] { ApiController.MainServer, "Custom Service" }; + if (ImGui.BeginCombo("Service", comboEntries[_serverSelectionIndex])) + { + for (int n = 0; n < comboEntries.Length; n++) + { + bool isSelected = _serverSelectionIndex == n; + if (ImGui.Selectable(comboEntries[n], isSelected)) + { + _serverSelectionIndex = n; + } + + if (isSelected) + { + ImGui.SetItemDefaultFocus(); + } + + bool useCustomService = _serverSelectionIndex != 0; + + if (_apiController.UseCustomService != useCustomService) + { + _apiController.UseCustomService = useCustomService; + _pluginConfiguration.Save(); + } + } + + ImGui.EndCombo(); + } + + if (_apiController.UseCustomService) + { + string serviceAddress = _pluginConfiguration.ApiUri; + if (ImGui.InputText("Service address", ref serviceAddress, 255)) + { + if (_pluginConfiguration.ApiUri != serviceAddress) + { + _pluginConfiguration.ApiUri = serviceAddress; + _apiController.RestartHeartbeat(); + _pluginConfiguration.Save(); + } + } + } + + PrintServerState(); + if (_apiController.ServerAlive) + { + if (ImGui.Button("Register")) + { + Task.WaitAll(_apiController.Register()); + } + } + } + + public void DrawCacheDirectorySetting() + { + var cacheDirectory = _pluginConfiguration.CacheFolder; + if (ImGui.InputText("Cache Folder##cache", ref cacheDirectory, 255)) + { + _pluginConfiguration.CacheFolder = cacheDirectory; + if (!string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) && Directory.Exists(_pluginConfiguration.CacheFolder)) + { + _pluginConfiguration.Save(); + } + } + + if (!Directory.Exists(cacheDirectory)) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + UIShared.TextWrapped("The folder you selected does not exist. Please provide a valid path."); + ImGui.PopStyleColor(); + } + } + + public void DrawParallelScansSetting() + { + var parallelScans = _pluginConfiguration.MaxParallelScan; + if (ImGui.SliderInt("Parallel File Scans##parallelism", ref parallelScans, 1, 20)) + { + _pluginConfiguration.MaxParallelScan = parallelScans; + _pluginConfiguration.Save(); + } + } + } +} diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index 31f02c4..9d3726e 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -27,32 +27,39 @@ namespace MareSynchronos.WebAPI { public class CharacterReceivedEventArgs : EventArgs { + public CharacterReceivedEventArgs(string characterNameHash, CharacterCacheDto characterData) + { + CharacterData = characterData; + CharacterNameHash = characterNameHash; + } + public CharacterCacheDto CharacterData { get; set; } public string CharacterNameHash { get; set; } } public class ApiController : IDisposable { - private readonly Configuration pluginConfiguration; - private const string MainService = "https://darkarchon.internet-box.ch:5001"; + public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)"; + + private readonly Configuration _pluginConfiguration; + public const string MainServiceUri = "https://darkarchon.internet-box.ch:5001"; public string UID { get; private set; } = string.Empty; - public string SecretKey => pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? pluginConfiguration.ClientSecret[ApiUri] : "-"; - private string CacheFolder => pluginConfiguration.CacheFolder; - public ConcurrentDictionary CurrentUploads { get; private set; } = new(); - public ConcurrentDictionary CurrentDownloads { get; private set; } = new(); + public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? _pluginConfiguration.ClientSecret[ApiUri] : "-"; + private string CacheFolder => _pluginConfiguration.CacheFolder; + public ConcurrentDictionary CurrentUploads { get; } = new(); + public ConcurrentDictionary CurrentDownloads { get; } = new(); public bool IsDownloading { get; private set; } = false; public bool IsUploading { get; private set; } = false; - public int TotalTransfersPending { get; set; } = 0; public bool UseCustomService { - get => pluginConfiguration.UseCustomService; + get => _pluginConfiguration.UseCustomService; set { - pluginConfiguration.UseCustomService = value; - pluginConfiguration.Save(); + _pluginConfiguration.UseCustomService = value; + _pluginConfiguration.Save(); } } - private string ApiUri => UseCustomService ? pluginConfiguration.ApiUri : MainService; + private string ApiUri => UseCustomService ? _pluginConfiguration.ApiUri : MainServiceUri; public bool ServerAlive => (_heartbeatHub?.State ?? HubConnectionState.Disconnected) == HubConnectionState.Connected; @@ -76,7 +83,7 @@ namespace MareSynchronos.WebAPI public ApiController(Configuration pluginConfiguration) { - this.pluginConfiguration = pluginConfiguration; + this._pluginConfiguration = pluginConfiguration; cts = new CancellationTokenSource(); _ = Heartbeat(); @@ -294,8 +301,8 @@ namespace MareSynchronos.WebAPI if (!ServerAlive) return; PluginLog.Debug("Registering at service " + ApiUri); var response = await _userHub!.InvokeAsync("Register"); - pluginConfiguration.ClientSecret[ApiUri] = response; - pluginConfiguration.Save(); + _pluginConfiguration.ClientSecret[ApiUri] = response; + _pluginConfiguration.Save(); RestartHeartbeat(); } @@ -317,7 +324,7 @@ namespace MareSynchronos.WebAPI CancelUpload(); uploadCancellationTokenSource = new CancellationTokenSource(); var uploadToken = uploadCancellationTokenSource.Token; - PluginLog.Warning("New Token Created"); + PluginLog.Debug("New Token Created"); var filesToUpload = await _fileHub!.InvokeAsync>("SendFiles", character.FileReplacements, uploadToken); @@ -344,7 +351,7 @@ namespace MareSynchronos.WebAPI } PluginLog.Debug("Upload tasks complete, waiting for server to confirm"); var anyUploadsOpen = await _fileHub!.InvokeAsync("IsUploadFinished", uploadToken); - PluginLog.Warning("Uploads open: " + anyUploadsOpen); + PluginLog.Debug("Uploads open: " + anyUploadsOpen); while (anyUploadsOpen && !uploadToken.IsCancellationRequested) { anyUploadsOpen = await _fileHub!.InvokeAsync("IsUploadFinished", uploadToken); @@ -357,7 +364,7 @@ namespace MareSynchronos.WebAPI if (!uploadToken.IsCancellationRequested) { - PluginLog.Warning("=== Pushing character data ==="); + PluginLog.Debug("=== Pushing character data ==="); await _userHub!.InvokeAsync("PushCharacterData", character, visibleCharacterIds, uploadToken); } else @@ -372,11 +379,7 @@ namespace MareSynchronos.WebAPI public Task ReceiveCharacterData(CharacterCacheDto character, string characterHash) { PluginLog.Debug("Received DTO for " + characterHash); - CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs() - { - CharacterData = character, - CharacterNameHash = characterHash - }); + CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character)); return Task.CompletedTask; }