add intro UI for first time registration, add FileCacheManager to scan and rescan for file changes, fix namings, polish UI for normal usage

This commit is contained in:
Stanley Dimant
2022-06-21 01:07:57 +02:00
parent b7b2005dcb
commit 4a12d667f1
11 changed files with 1074 additions and 534 deletions

View File

@@ -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<string, string> ClientSecret { get; internal set; } = new();
public string ApiUri { get; internal set; } = string.Empty;
public bool UseCustomService { get; internal set; }
public Dictionary<string, string> ClientSecret { get; set; } = new();
public Dictionary<string, string> 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);
}
}
}

View File

@@ -15,7 +15,7 @@ namespace MareSynchronos.Factories
public FileReplacement Create()
{
if (!ipcManager.CheckPenumbraAPI())
if (!ipcManager.CheckPenumbraApi())
{
throw new System.Exception();
}

View File

@@ -45,7 +45,7 @@ namespace MareSynchronos.Managers
private string _lastSentHash = string.Empty;
private Task? _playerChangedTask = null;
private HashSet<string> onlineWhitelistedUsers = new();
private HashSet<string> _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<string, PlayerCharacter> 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<string>(apiTask.Result);
_onlineWhitelistedUsers = new HashSet<string>(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<string, int> { { 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<string, string> 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()));

View File

@@ -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<string, bool>(
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<string, bool>(p, false)));
List<FileCache> fileCaches;
await using (FileCacheContext db = new())
{
fileCaches = db.FileCaches.ToList();
}
TotalFiles = scannedFiles.Count;
var fileCachesToUpdate = new ConcurrentBag<FileCache>();
var fileCachesToDelete = new ConcurrentBag<FileCache>();
var fileCachesToAdd = new ConcurrentBag<FileCache>();
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();
}
}
}

View File

@@ -10,23 +10,23 @@ namespace MareSynchronos.Managers
{
public class IpcManager : IDisposable
{
private readonly DalamudPluginInterface pluginInterface;
private ICallGateSubscriber<object> penumbraInit;
private ICallGateSubscriber<string, string, string>? penumbraResolvePath;
private ICallGateSubscriber<string>? penumbraResolveModDir;
private ICallGateSubscriber<string>? glamourerGetCharacterCustomization;
private ICallGateSubscriber<string, string, object>? glamourerApplyCharacterCustomization;
private ICallGateSubscriber<int> penumbraApiVersion;
private ICallGateSubscriber<int> glamourerApiVersion;
private ICallGateSubscriber<IntPtr, int, object?> penumbraObjectIsRedrawn;
private ICallGateSubscriber<string, int, object>? penumbraRedraw;
private ICallGateSubscriber<string, string, string[]>? penumbraReverseResolvePath;
private ICallGateSubscriber<string, object> glamourerRevertCustomization;
private readonly DalamudPluginInterface _pluginInterface;
private readonly ICallGateSubscriber<object> _penumbraInit;
private readonly ICallGateSubscriber<string, string, string>? _penumbraResolvePath;
private readonly ICallGateSubscriber<string>? _penumbraResolveModDir;
private readonly ICallGateSubscriber<string>? _glamourerGetCharacterCustomization;
private readonly ICallGateSubscriber<string, string, object>? _glamourerApplyCharacterCustomization;
private readonly ICallGateSubscriber<int> _penumbraApiVersion;
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
private readonly ICallGateSubscriber<IntPtr, int, object?> _penumbraObjectIsRedrawn;
private readonly ICallGateSubscriber<string, int, object>? _penumbraRedraw;
private readonly ICallGateSubscriber<string, string, string[]>? _penumbraReverseResolvePath;
private readonly ICallGateSubscriber<string, object> _glamourerRevertCustomization;
private ICallGateSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int, int>
penumbraSetTemporaryMod;
private ICallGateSubscriber<string, string, bool, (int, string)> penumbraCreateTemporaryCollection;
private ICallGateSubscriber<string, int> penumbraRemoveTemporaryCollection;
private readonly ICallGateSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int, int>
_penumbraSetTemporaryMod;
private readonly ICallGateSubscriber<string, string, bool, (int, string)> _penumbraCreateTemporaryCollection;
private readonly ICallGateSubscriber<string, int> _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<object>("Penumbra.Initialized");
penumbraResolvePath = pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
penumbraResolveModDir = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
penumbraRedraw = pluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
glamourerGetCharacterCustomization = pluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization");
glamourerApplyCharacterCustomization = pluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization");
penumbraReverseResolvePath = pluginInterface.GetIpcSubscriber<string, string, string[]>("Penumbra.ReverseResolvePath");
penumbraApiVersion = pluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion");
glamourerApiVersion = pluginInterface.GetIpcSubscriber<int>("Glamourer.ApiVersion");
glamourerRevertCustomization = pluginInterface.GetIpcSubscriber<string, object>("Glamourer.RevertCharacterCustomization");
penumbraObjectIsRedrawn = pluginInterface.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn");
_penumbraInit = _pluginInterface.GetIpcSubscriber<object>("Penumbra.Initialized");
_penumbraResolvePath = _pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
_penumbraResolveModDir = _pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
_penumbraRedraw = _pluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
_glamourerGetCharacterCustomization = _pluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization");
_glamourerApplyCharacterCustomization = _pluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization");
_penumbraReverseResolvePath = _pluginInterface.GetIpcSubscriber<string, string, string[]>("Penumbra.ReverseResolvePath");
_penumbraApiVersion = _pluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion");
_glamourerApiVersion = _pluginInterface.GetIpcSubscriber<int>("Glamourer.ApiVersion");
_glamourerRevertCustomization = _pluginInterface.GetIpcSubscriber<string, object>("Glamourer.RevertCharacterCustomization");
_penumbraObjectIsRedrawn = _pluginInterface.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn");
penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
penumbraInit.Subscribe(RedrawSelf);
_penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
_penumbraInit.Subscribe(RedrawSelf);
penumbraSetTemporaryMod =
pluginInterface
_penumbraSetTemporaryMod =
_pluginInterface
.GetIpcSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int,
int>("Penumbra.AddTemporaryMod");
penumbraCreateTemporaryCollection =
pluginInterface.GetIpcSubscriber<string, string, bool, (int, string)>("Penumbra.CreateTemporaryCollection");
penumbraRemoveTemporaryCollection =
pluginInterface.GetIpcSubscriber<string, int>("Penumbra.RemoveTemporaryCollection");
_penumbraCreateTemporaryCollection =
_pluginInterface.GetIpcSubscriber<string, string, bool, (int, string)>("Penumbra.CreateTemporaryCollection");
_penumbraRemoveTemporaryCollection =
_pluginInterface.GetIpcSubscriber<string, int>("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<string, string> modPaths)
{
if (!CheckPenumbraAPI()) return;
if (!CheckPenumbraApi()) return;
PluginLog.Debug("Assigning temp mods for " + characterName);
try
{
//var result = penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", characterName, modPaths, new List<string>(), 0);
//PluginLog.Debug("Success: " + result);
}
catch (Exception ex)
{
PluginLog.Error(ex, "Error during IPC call");
}
//penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", characterName, modPaths, new List<string>(), 0);
}
public void Dispose()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<string, (long, long)> CurrentUploads { get; private set; } = new();
public ConcurrentDictionary<string, (long, long)> CurrentDownloads { get; private set; } = new();
public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? _pluginConfiguration.ClientSecret[ApiUri] : "-";
private string CacheFolder => _pluginConfiguration.CacheFolder;
public ConcurrentDictionary<string, (long, long)> CurrentUploads { get; } = new();
public ConcurrentDictionary<string, (long, long)> 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<string>("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<List<string>>("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<bool>("IsUploadFinished", uploadToken);
PluginLog.Warning("Uploads open: " + anyUploadsOpen);
PluginLog.Debug("Uploads open: " + anyUploadsOpen);
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
{
anyUploadsOpen = await _fileHub!.InvokeAsync<bool>("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;
}