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 Dalamud.Plugin;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using MareSynchronos.WebAPI;
using Newtonsoft.Json;
namespace MareSynchronos namespace MareSynchronos
{ {
@@ -11,23 +14,49 @@ namespace MareSynchronos
public int Version { get; set; } = 0; public int Version { get; set; } = 0;
public string CacheFolder { get; set; } = string.Empty; public string CacheFolder { get; set; } = string.Empty;
public Dictionary<string, string> ClientSecret { get; internal set; } = new(); public Dictionary<string, string> ClientSecret { get; set; } = new();
public string ApiUri { get; internal set; } = string.Empty; public Dictionary<string, string> UidComments { get; set; } = new();
public bool UseCustomService { get; internal set; } 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 // the below exist just to make saving less cumbersome
[NonSerialized] [NonSerialized]
private DalamudPluginInterface? pluginInterface; private DalamudPluginInterface? _pluginInterface;
public void Initialize(DalamudPluginInterface pluginInterface) public void Initialize(DalamudPluginInterface pluginInterface)
{ {
this.pluginInterface = pluginInterface; this._pluginInterface = pluginInterface;
} }
public void Save() public void Save()
{ {
this.pluginInterface!.SavePluginConfig(this); this._pluginInterface!.SavePluginConfig(this);
} }
} }
} }

View File

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

View File

@@ -45,7 +45,7 @@ namespace MareSynchronos.Managers
private string _lastSentHash = string.Empty; private string _lastSentHash = string.Empty;
private Task? _playerChangedTask = null; 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, public CharacterManager(ClientState clientState, Framework framework, ApiController apiController, ObjectTable objectTable, IpcManager ipcManager, FileReplacementFactory factory,
Configuration pluginConfiguration) Configuration pluginConfiguration)
@@ -154,6 +154,11 @@ namespace MareSynchronos.Managers
_watcher.Disable(); _watcher.Disable();
_watcher.PlayerChanged -= Watcher_PlayerChanged; _watcher.PlayerChanged -= Watcher_PlayerChanged;
_watcher?.Dispose(); _watcher?.Dispose();
foreach (var character in _onlineWhitelistedUsers)
{
RestoreCharacter(character);
}
} }
public void StopWatchPlayer(string name) public void StopWatchPlayer(string name)
@@ -164,7 +169,7 @@ namespace MareSynchronos.Managers
public async Task UpdatePlayersFromService(Dictionary<string, PlayerCharacter> currentLocalPlayers) public async Task UpdatePlayersFromService(Dictionary<string, PlayerCharacter> currentLocalPlayers)
{ {
PluginLog.Debug("Updating local players from service"); 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); .ToDictionary(k => k.Key, k => k.Value);
await _apiController.GetCharacterData(currentLocalPlayers await _apiController.GetCharacterData(currentLocalPlayers
.ToDictionary( .ToDictionary(
@@ -205,10 +210,10 @@ namespace MareSynchronos.Managers
Task.WaitAll(apiTask); Task.WaitAll(apiTask);
onlineWhitelistedUsers = new HashSet<string>(apiTask.Result); _onlineWhitelistedUsers = new HashSet<string>(apiTask.Result);
var assignTask = AssignLocalPlayersData(); var assignTask = AssignLocalPlayersData();
Task.WaitAll(assignTask); 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; _framework.Update += Framework_Update;
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
@@ -221,6 +226,12 @@ namespace MareSynchronos.Managers
_framework.Update -= Framework_Update; _framework.Update -= Framework_Update;
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
_clientState.TerritoryChanged -= ClientState_TerritoryChanged; _clientState.TerritoryChanged -= ClientState_TerritoryChanged;
foreach (var character in _onlineWhitelistedUsers)
{
RestoreCharacter(character);
}
_lastSentHash = string.Empty;
} }
private void ApiControllerOnAddedToWhitelist(object? sender, EventArgs e) private void ApiControllerOnAddedToWhitelist(object? sender, EventArgs e)
@@ -230,7 +241,7 @@ namespace MareSynchronos.Managers
var players = GetLocalPlayers(); var players = GetLocalPlayers();
if (players.ContainsKey(characterHash)) 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 } }); _ = _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); PluginLog.Debug("Assigned hash to visible player: " + otherPlayerName);
/*ipcManager.PenumbraRemoveTemporaryCollection(otherPlayerName); _ipcManager.PenumbraRemoveTemporaryCollection(otherPlayerName);
ipcManager.PenumbraCreateTemporaryCollection(otherPlayerName); _ipcManager.PenumbraCreateTemporaryCollection(otherPlayerName);
Dictionary<string, string> moddedPaths = new(); Dictionary<string, string> moddedPaths = new();
using (var db = new FileCacheContext()) 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.GlamourerApplyCharacterCustomization(e.CharacterData.GlamourerData, otherPlayerName);
//ipcManager.PenumbraRedraw(otherPlayerName); _ipcManager.PenumbraRedraw(otherPlayerName);
} }
private void ApiControllerOnRemovedFromWhitelist(object? sender, EventArgs e) private void ApiControllerOnRemovedFromWhitelist(object? sender, EventArgs e)
{ {
var characterHash = (string?)sender; var characterHash = (string?)sender;
if (string.IsNullOrEmpty(characterHash)) return; if (string.IsNullOrEmpty(characterHash)) return;
RestoreCharacter(characterHash);
}
private void RestoreCharacter(string characterHash)
{
var players = GetLocalPlayers(); var players = GetLocalPlayers();
foreach (var entry in _characterCache.Where(c => c.Key.Item1 == characterHash)) foreach (var entry in _characterCache.Where(c => c.Key.Item1 == characterHash))
{ {
_characterCache.Remove(entry.Key); _characterCache.Remove(entry.Key);
} }
var playerName = players.SingleOrDefault(p => p.Key == characterHash).Value.Name.ToString() ?? null; foreach (var player in players)
if (playerName != null)
{ {
if (player.Key != characterHash) continue;
var playerName = player.Value.Name.ToString();
RestorePreviousCharacter(playerName); RestorePreviousCharacter(playerName);
PluginLog.Debug("Removed from whitelist, restoring glamourer state for " + playerName); PluginLog.Debug("Removed from whitelist, restoring glamourer state for " + playerName);
_ipcManager.PenumbraRemoveTemporaryCollection(playerName); _ipcManager.PenumbraRemoveTemporaryCollection(playerName);
_ipcManager.GlamourerRevertCharacterCustomization(playerName); _ipcManager.GlamourerRevertCharacterCustomization(playerName);
break;
} }
} }
private void ApiControllerOnWhitelistedPlayerOffline(object? sender, EventArgs e) private void ApiControllerOnWhitelistedPlayerOffline(object? sender, EventArgs e)
{ {
PluginLog.Debug("Player offline: " + sender!); PluginLog.Debug("Player offline: " + sender!);
onlineWhitelistedUsers.Remove((string)sender!); _onlineWhitelistedUsers.Remove((string)sender!);
} }
private void ApiControllerOnWhitelistedPlayerOnline(object? sender, EventArgs e) private void ApiControllerOnWhitelistedPlayerOnline(object? sender, EventArgs e)
{ {
PluginLog.Debug("Player online: " + sender!); PluginLog.Debug("Player online: " + sender!);
onlineWhitelistedUsers.Add((string)sender!); _onlineWhitelistedUsers.Add((string)sender!);
} }
private async Task AssignLocalPlayersData() private async Task AssignLocalPlayersData()
@@ -351,11 +370,7 @@ namespace MareSynchronos.Managers
{ {
if (currentLocalPlayers.ContainsKey(player.Key.Item1)) if (currentLocalPlayers.ContainsKey(player.Key.Item1))
{ {
await Task.Run(() => ApiControllerOnCharacterReceived(null, new CharacterReceivedEventArgs await Task.Run(() => ApiControllerOnCharacterReceived(null, new CharacterReceivedEventArgs(player.Key.Item1, player.Value)));
{
CharacterNameHash = player.Key.Item1,
CharacterData = player.Value
}));
} }
} }
@@ -413,7 +428,7 @@ namespace MareSynchronos.Managers
var pObj = (PlayerCharacter)obj; var pObj = (PlayerCharacter)obj;
var hashedName = Crypto.GetHash256(pObj.Name.ToString() + pObj.HomeWorld.Id.ToString()); var hashedName = Crypto.GetHash256(pObj.Name.ToString() + pObj.HomeWorld.Id.ToString());
if (!onlineWhitelistedUsers.Contains(hashedName)) continue; if (!_onlineWhitelistedUsers.Contains(hashedName)) continue;
localPlayersList.Add(hashedName); localPlayersList.Add(hashedName);
if (!_cachedLocalPlayers.ContainsKey(hashedName)) newPlayers[hashedName] = pObj; if (!_cachedLocalPlayers.ContainsKey(hashedName)) newPlayers[hashedName] = pObj;
@@ -455,7 +470,7 @@ namespace MareSynchronos.Managers
string playerName = obj.Name.ToString(); string playerName = obj.Name.ToString();
if (playerName == GetPlayerName()) continue; if (playerName == GetPlayerName()) continue;
var playerObject = (PlayerCharacter)obj; 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; return allLocalPlayers;
@@ -509,7 +524,7 @@ namespace MareSynchronos.Managers
var cacheDto = characterCacheTask.Result.ToCharacterCacheDto(); var cacheDto = characterCacheTask.Result.ToCharacterCacheDto();
if (cacheDto.Hash == _lastSentHash) if (cacheDto.Hash == _lastSentHash)
{ {
PluginLog.Warning("Not sending data, already sent"); PluginLog.Debug("Not sending data, already sent");
return; return;
} }
Task.WaitAll(_apiController.SendCharacterData(cacheDto, GetLocalPlayers().Select(d => d.Key).ToList())); 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 public class IpcManager : IDisposable
{ {
private readonly DalamudPluginInterface pluginInterface; private readonly DalamudPluginInterface _pluginInterface;
private ICallGateSubscriber<object> penumbraInit; private readonly ICallGateSubscriber<object> _penumbraInit;
private ICallGateSubscriber<string, string, string>? penumbraResolvePath; private readonly ICallGateSubscriber<string, string, string>? _penumbraResolvePath;
private ICallGateSubscriber<string>? penumbraResolveModDir; private readonly ICallGateSubscriber<string>? _penumbraResolveModDir;
private ICallGateSubscriber<string>? glamourerGetCharacterCustomization; private readonly ICallGateSubscriber<string>? _glamourerGetCharacterCustomization;
private ICallGateSubscriber<string, string, object>? glamourerApplyCharacterCustomization; private readonly ICallGateSubscriber<string, string, object>? _glamourerApplyCharacterCustomization;
private ICallGateSubscriber<int> penumbraApiVersion; private readonly ICallGateSubscriber<int> _penumbraApiVersion;
private ICallGateSubscriber<int> glamourerApiVersion; private readonly ICallGateSubscriber<int> _glamourerApiVersion;
private ICallGateSubscriber<IntPtr, int, object?> penumbraObjectIsRedrawn; private readonly ICallGateSubscriber<IntPtr, int, object?> _penumbraObjectIsRedrawn;
private ICallGateSubscriber<string, int, object>? penumbraRedraw; private readonly ICallGateSubscriber<string, int, object>? _penumbraRedraw;
private ICallGateSubscriber<string, string, string[]>? penumbraReverseResolvePath; private readonly ICallGateSubscriber<string, string, string[]>? _penumbraReverseResolvePath;
private ICallGateSubscriber<string, object> glamourerRevertCustomization; private readonly ICallGateSubscriber<string, object> _glamourerRevertCustomization;
private ICallGateSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int, int> private readonly ICallGateSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int, int>
penumbraSetTemporaryMod; _penumbraSetTemporaryMod;
private ICallGateSubscriber<string, string, bool, (int, string)> penumbraCreateTemporaryCollection; private readonly ICallGateSubscriber<string, string, bool, (int, string)> _penumbraCreateTemporaryCollection;
private ICallGateSubscriber<string, int> penumbraRemoveTemporaryCollection; private readonly ICallGateSubscriber<string, int> _penumbraRemoveTemporaryCollection;
public bool Initialized { get; private set; } = false; public bool Initialized { get; private set; } = false;
@@ -34,41 +34,41 @@ namespace MareSynchronos.Managers
public IpcManager(DalamudPluginInterface pi) public IpcManager(DalamudPluginInterface pi)
{ {
pluginInterface = pi; _pluginInterface = pi;
penumbraInit = pluginInterface.GetIpcSubscriber<object>("Penumbra.Initialized"); _penumbraInit = _pluginInterface.GetIpcSubscriber<object>("Penumbra.Initialized");
penumbraResolvePath = pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath"); _penumbraResolvePath = _pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
penumbraResolveModDir = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory"); _penumbraResolveModDir = _pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
penumbraRedraw = pluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName"); _penumbraRedraw = _pluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
glamourerGetCharacterCustomization = pluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization"); _glamourerGetCharacterCustomization = _pluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization");
glamourerApplyCharacterCustomization = pluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization"); _glamourerApplyCharacterCustomization = _pluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization");
penumbraReverseResolvePath = pluginInterface.GetIpcSubscriber<string, string, string[]>("Penumbra.ReverseResolvePath"); _penumbraReverseResolvePath = _pluginInterface.GetIpcSubscriber<string, string, string[]>("Penumbra.ReverseResolvePath");
penumbraApiVersion = pluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion"); _penumbraApiVersion = _pluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion");
glamourerApiVersion = pluginInterface.GetIpcSubscriber<int>("Glamourer.ApiVersion"); _glamourerApiVersion = _pluginInterface.GetIpcSubscriber<int>("Glamourer.ApiVersion");
glamourerRevertCustomization = pluginInterface.GetIpcSubscriber<string, object>("Glamourer.RevertCharacterCustomization"); _glamourerRevertCustomization = _pluginInterface.GetIpcSubscriber<string, object>("Glamourer.RevertCharacterCustomization");
penumbraObjectIsRedrawn = pluginInterface.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn"); _penumbraObjectIsRedrawn = _pluginInterface.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn");
penumbraObjectIsRedrawn.Subscribe(RedrawEvent); _penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
penumbraInit.Subscribe(RedrawSelf); _penumbraInit.Subscribe(RedrawSelf);
penumbraSetTemporaryMod = _penumbraSetTemporaryMod =
pluginInterface _pluginInterface
.GetIpcSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int, .GetIpcSubscriber<string, string, IReadOnlyDictionary<string, string>, List<string>, int,
int>("Penumbra.AddTemporaryMod"); int>("Penumbra.AddTemporaryMod");
penumbraCreateTemporaryCollection = _penumbraCreateTemporaryCollection =
pluginInterface.GetIpcSubscriber<string, string, bool, (int, string)>("Penumbra.CreateTemporaryCollection"); _pluginInterface.GetIpcSubscriber<string, string, bool, (int, string)>("Penumbra.CreateTemporaryCollection");
penumbraRemoveTemporaryCollection = _penumbraRemoveTemporaryCollection =
pluginInterface.GetIpcSubscriber<string, int>("Penumbra.RemoveTemporaryCollection"); _pluginInterface.GetIpcSubscriber<string, int>("Penumbra.RemoveTemporaryCollection");
Initialized = true; Initialized = true;
} }
public bool CheckPenumbraAPI() public bool CheckPenumbraApi()
{ {
try try
{ {
return penumbraApiVersion.InvokeFunc() >= 4; return _penumbraApiVersion.InvokeFunc() >= 4;
} }
catch catch
{ {
@@ -76,11 +76,11 @@ namespace MareSynchronos.Managers
} }
} }
public bool CheckGlamourerAPI() public bool CheckGlamourerApi()
{ {
try try
{ {
return glamourerApiVersion.InvokeFunc() >= 0; return _glamourerApiVersion.InvokeFunc() >= 0;
} }
catch catch
{ {
@@ -95,91 +95,78 @@ namespace MareSynchronos.Managers
private void RedrawSelf() private void RedrawSelf()
{ {
penumbraRedraw!.InvokeAction("self", 0); _penumbraRedraw!.InvokeAction("self", 0);
} }
private void Uninitialize() private void Uninitialize()
{ {
penumbraInit.Unsubscribe(RedrawSelf); _penumbraInit.Unsubscribe(RedrawSelf);
penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
penumbraResolvePath = null;
penumbraResolveModDir = null;
glamourerGetCharacterCustomization = null;
glamourerApplyCharacterCustomization = null;
penumbraReverseResolvePath = null;
Initialized = false; Initialized = false;
PluginLog.Debug("IPC Manager disposed"); PluginLog.Debug("IPC Manager disposed");
} }
public string[] PenumbraReverseResolvePath(string path, string characterName) public string[] PenumbraReverseResolvePath(string path, string characterName)
{ {
if (!CheckPenumbraAPI()) return new[] { path }; if (!CheckPenumbraApi()) return new[] { path };
return penumbraReverseResolvePath!.InvokeFunc(path, characterName); return _penumbraReverseResolvePath!.InvokeFunc(path, characterName);
} }
public string? PenumbraResolvePath(string path, string characterName) public string? PenumbraResolvePath(string path, string characterName)
{ {
if (!CheckPenumbraAPI()) return null; if (!CheckPenumbraApi()) return null;
return penumbraResolvePath!.InvokeFunc(path, characterName); return _penumbraResolvePath!.InvokeFunc(path, characterName);
} }
public string? PenumbraModDirectory() public string? PenumbraModDirectory()
{ {
if (!CheckPenumbraAPI()) return null; if (!CheckPenumbraApi()) return null;
return penumbraResolveModDir!.InvokeFunc(); return _penumbraResolveModDir!.InvokeFunc();
} }
public string? GlamourerGetCharacterCustomization() public string? GlamourerGetCharacterCustomization()
{ {
if (!CheckGlamourerAPI()) return null; if (!CheckGlamourerApi()) return null;
return glamourerGetCharacterCustomization!.InvokeFunc(); return _glamourerGetCharacterCustomization!.InvokeFunc();
} }
public void GlamourerApplyCharacterCustomization(string customization, string characterName) public void GlamourerApplyCharacterCustomization(string customization, string characterName)
{ {
if (!CheckGlamourerAPI()) return; if (!CheckGlamourerApi()) return;
glamourerApplyCharacterCustomization!.InvokeAction(customization, characterName); _glamourerApplyCharacterCustomization!.InvokeAction(customization, characterName);
} }
public void GlamourerRevertCharacterCustomization(string characterName) public void GlamourerRevertCharacterCustomization(string characterName)
{ {
if (!CheckGlamourerAPI()) return; if (!CheckGlamourerApi()) return;
glamourerRevertCustomization!.InvokeAction(characterName); _glamourerRevertCustomization!.InvokeAction(characterName);
} }
public void PenumbraRedraw(string actorName) public void PenumbraRedraw(string actorName)
{ {
if (!CheckPenumbraAPI()) return; if (!CheckPenumbraApi()) return;
penumbraRedraw!.InvokeAction(actorName, 0); _penumbraRedraw!.InvokeAction(actorName, 0);
} }
public void PenumbraCreateTemporaryCollection(string characterName) public void PenumbraCreateTemporaryCollection(string characterName)
{ {
if (!CheckPenumbraAPI()) return; if (!CheckPenumbraApi()) return;
PluginLog.Debug("Creating temp collection for " + characterName); PluginLog.Debug("Creating temp collection for " + characterName);
//penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true); //penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true);
} }
public void PenumbraRemoveTemporaryCollection(string characterName) public void PenumbraRemoveTemporaryCollection(string characterName)
{ {
if (!CheckPenumbraAPI()) return; if (!CheckPenumbraApi()) return;
PluginLog.Debug("Removing temp collection for " + characterName); PluginLog.Debug("Removing temp collection for " + characterName);
//penumbraRemoveTemporaryCollection.InvokeFunc(characterName); //penumbraRemoveTemporaryCollection.InvokeFunc(characterName);
} }
public void PenumbraSetTemporaryMods(string characterName, IReadOnlyDictionary<string, string> modPaths) public void PenumbraSetTemporaryMods(string characterName, IReadOnlyDictionary<string, string> modPaths)
{ {
if (!CheckPenumbraAPI()) return; if (!CheckPenumbraApi()) return;
PluginLog.Debug("Assigning temp mods for " + characterName); PluginLog.Debug("Assigning temp mods for " + characterName);
try //penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", characterName, modPaths, new List<string>(), 0);
{
//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");
}
} }
public void Dispose() public void Dispose()

View File

@@ -13,44 +13,61 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState; using Dalamud.Game.ClientState;
using System; using System;
using MareSynchronos.Models; using MareSynchronos.Models;
using Dalamud.Game.Gui;
using MareSynchronos.PenumbraMod; using MareSynchronos.PenumbraMod;
using Newtonsoft.Json; using Newtonsoft.Json;
using MareSynchronos.Managers; using MareSynchronos.Managers;
using LZ4; using LZ4;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using MareSynchronos.UI;
namespace MareSynchronos namespace MareSynchronos
{ {
public sealed class Plugin : IDalamudPlugin public sealed class Plugin : IDalamudPlugin
{ {
private const string commandName = "/mare"; private const string CommandName = "/mare";
private readonly ClientState clientState; private readonly ApiController _apiController;
private readonly Framework framework; private readonly ClientState _clientState;
private readonly ObjectTable objectTable; private readonly CommandManager _commandManager;
private readonly WindowSystem windowSystem; private readonly Configuration _configuration;
private readonly ApiController apiController; private readonly FileCacheManager _fileCacheManager;
private CharacterManager? characterManager; private readonly Framework _framework;
private IpcManager ipcManager; 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, public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
Framework framework, ObjectTable objectTable, ClientState clientState) Framework framework, ObjectTable objectTable, ClientState clientState)
{ {
this.PluginInterface = pluginInterface; _pluginInterface = pluginInterface;
this.CommandManager = commandManager; _commandManager = commandManager;
this.framework = framework; _framework = framework;
this.objectTable = objectTable; _objectTable = objectTable;
this.clientState = clientState; _clientState = clientState;
Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); _configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
Configuration.Initialize(this.PluginInterface); _configuration.Initialize(_pluginInterface);
windowSystem = new WindowSystem("MareSynchronos"); _windowSystem = new WindowSystem("MareSynchronos");
apiController = new ApiController(Configuration); _apiController = new ApiController(_configuration);
ipcManager = new IpcManager(PluginInterface); _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 // 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 new FileCacheContext().Dispose(); // make sure db is initialized I guess
@@ -64,54 +81,64 @@ namespace MareSynchronos
} }
public string Name => "Mare Synchronos"; 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() public void Dispose()
{ {
this.PluginUi?.Dispose(); _commandManager.RemoveHandler(CommandName);
this.CommandManager.RemoveHandler(commandName); _clientState.Login -= ClientState_Login;
clientState.Login -= ClientState_Login; _clientState.Logout -= ClientState_Logout;
clientState.Logout -= ClientState_Logout;
ipcManager?.Dispose(); _pluginUi?.Dispose();
characterManager?.Dispose(); _introUi?.Dispose();
apiController?.Dispose();
_fileCacheManager?.Dispose();
_ipcManager?.Dispose();
_characterManager?.Dispose();
_apiController?.Dispose();
} }
private void ClientState_Login(object? sender, EventArgs e) private void ClientState_Login(object? sender, EventArgs e)
{ {
PluginLog.Debug("Client login"); PluginLog.Debug("Client login");
if (!_configuration.HasValidSetup)
{
_introUi.IsOpen = true;
return;
}
else
{
_introUi.IsOpen = false;
_pluginInterface.UiBuilder.Draw += Draw;
}
Task.Run(async () => Task.Run(async () =>
{ {
while (clientState.LocalPlayer == null) while (_clientState.LocalPlayer == null)
{ {
await Task.Delay(50); await Task.Delay(50);
} }
characterManager = new CharacterManager( _characterManager = new CharacterManager(
clientState, framework, apiController, objectTable, ipcManager, new FileReplacementFactory(ipcManager), Configuration); _clientState, _framework, _apiController, _objectTable, _ipcManager, new FileReplacementFactory(_ipcManager), _configuration);
characterManager.StartWatchingPlayer(); _characterManager.StartWatchingPlayer();
ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString()); _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) private void ClientState_Logout(object? sender, EventArgs e)
{ {
PluginLog.Debug("Client logout"); PluginLog.Debug("Client logout");
characterManager?.Dispose(); _characterManager?.Dispose();
PluginInterface.UiBuilder.Draw -= Draw; _pluginInterface.UiBuilder.Draw -= Draw;
PluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUI; _pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
CommandManager.RemoveHandler(commandName); _commandManager.RemoveHandler(CommandName);
} }
private void CopyFile(FileReplacement replacement, string targetDirectory, Dictionary<string, string>? resourceDict = null) 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 ext = new FileInfo(fileCache.Filepath).Extension;
var newFilePath = Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext); var newFilePath = Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext);
string lc4hcPath = Path.Combine(targetDirectory, "files", "lz4hc." + fileCache.Hash.ToLower() + ext); string lc4HcPath = Path.Combine(targetDirectory, "files", "lz4hc." + fileCache.Hash.ToLower() + ext);
if (!File.Exists(lc4hcPath)) if (!File.Exists(lc4HcPath))
{ {
Stopwatch st = Stopwatch.StartNew(); 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(); 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); File.Copy(fileCache.Filepath, newFilePath);
if (resourceDict != null) if (resourceDict != null)
{ {
foreach(var path in replacement.GamePaths) foreach (var path in replacement.GamePaths)
{ {
resourceDict[path] = $"files\\{fileCache.Hash.ToLower() + ext}"; resourceDict[path] = $"files\\{fileCache.Hash.ToLower() + ext}";
} }
@@ -157,40 +184,35 @@ namespace MareSynchronos
private void Draw() private void Draw()
{ {
windowSystem.Draw(); _windowSystem.Draw();
}
private void OpenConfigUI()
{
this.PluginUi.Toggle();
} }
private void OnCommand(string command, string args) private void OnCommand(string command, string args)
{ {
if (args == "printjson") if (args == "printjson")
{ {
_ = characterManager?.DebugJson(); _ = _characterManager?.DebugJson();
} }
if (args.StartsWith("watch")) if (args.StartsWith("watch"))
{ {
var playerName = args.Replace("watch", "").Trim(); var playerName = args.Replace("watch", "").Trim();
characterManager!.WatchPlayer(playerName); _characterManager!.WatchPlayer(playerName);
} }
if (args.StartsWith("stop")) if (args.StartsWith("stop"))
{ {
var playerName = args.Replace("watch", "").Trim(); var playerName = args.Replace("watch", "").Trim();
characterManager!.StopWatchPlayer(playerName); _characterManager!.StopWatchPlayer(playerName);
} }
if (args == "createtestmod") if (args == "createtestmod")
{ {
Task.Run(() => Task.Run(() =>
{ {
var playerName = clientState.LocalPlayer!.Name.ToString(); var playerName = _clientState.LocalPlayer!.Name.ToString();
var modName = $"Mare Synchronos Test Mod {playerName}"; var modName = $"Mare Synchronos Test Mod {playerName}";
var modDirectory = ipcManager!.PenumbraModDirectory()!; var modDirectory = _ipcManager!.PenumbraModDirectory()!;
string modDirectoryPath = Path.Combine(modDirectory, modName); string modDirectoryPath = Path.Combine(modDirectory, modName);
if (Directory.Exists(modDirectoryPath)) if (Directory.Exists(modDirectoryPath))
{ {
@@ -206,7 +228,7 @@ namespace MareSynchronos
Description = "Mare Synchronous Test Mod Export", Description = "Mare Synchronous Test Mod Export",
}; };
var resources = characterManager!.BuildCharacterCache(); var resources = _characterManager!.BuildCharacterCache();
var metaJson = JsonConvert.SerializeObject(meta); var metaJson = JsonConvert.SerializeObject(meta);
File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson); File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson);
@@ -229,8 +251,13 @@ namespace MareSynchronos
if (string.IsNullOrEmpty(args)) 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 class CharacterReceivedEventArgs : EventArgs
{ {
public CharacterReceivedEventArgs(string characterNameHash, CharacterCacheDto characterData)
{
CharacterData = characterData;
CharacterNameHash = characterNameHash;
}
public CharacterCacheDto CharacterData { get; set; } public CharacterCacheDto CharacterData { get; set; }
public string CharacterNameHash { get; set; } public string CharacterNameHash { get; set; }
} }
public class ApiController : IDisposable public class ApiController : IDisposable
{ {
private readonly Configuration pluginConfiguration; public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
private const string MainService = "https://darkarchon.internet-box.ch:5001";
private readonly Configuration _pluginConfiguration;
public const string MainServiceUri = "https://darkarchon.internet-box.ch:5001";
public string UID { get; private set; } = string.Empty; public string UID { get; private set; } = string.Empty;
public string SecretKey => pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? pluginConfiguration.ClientSecret[ApiUri] : "-"; public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? _pluginConfiguration.ClientSecret[ApiUri] : "-";
private string CacheFolder => pluginConfiguration.CacheFolder; private string CacheFolder => _pluginConfiguration.CacheFolder;
public ConcurrentDictionary<string, (long, long)> CurrentUploads { get; private set; } = new(); public ConcurrentDictionary<string, (long, long)> CurrentUploads { get; } = new();
public ConcurrentDictionary<string, (long, long)> CurrentDownloads { get; private set; } = new(); public ConcurrentDictionary<string, (long, long)> CurrentDownloads { get; } = new();
public bool IsDownloading { get; private set; } = false; public bool IsDownloading { get; private set; } = false;
public bool IsUploading { get; private set; } = false; public bool IsUploading { get; private set; } = false;
public int TotalTransfersPending { get; set; } = 0;
public bool UseCustomService public bool UseCustomService
{ {
get => pluginConfiguration.UseCustomService; get => _pluginConfiguration.UseCustomService;
set set
{ {
pluginConfiguration.UseCustomService = value; _pluginConfiguration.UseCustomService = value;
pluginConfiguration.Save(); _pluginConfiguration.Save();
} }
} }
private string ApiUri => UseCustomService ? pluginConfiguration.ApiUri : MainService; private string ApiUri => UseCustomService ? _pluginConfiguration.ApiUri : MainServiceUri;
public bool ServerAlive => public bool ServerAlive =>
(_heartbeatHub?.State ?? HubConnectionState.Disconnected) == HubConnectionState.Connected; (_heartbeatHub?.State ?? HubConnectionState.Disconnected) == HubConnectionState.Connected;
@@ -76,7 +83,7 @@ namespace MareSynchronos.WebAPI
public ApiController(Configuration pluginConfiguration) public ApiController(Configuration pluginConfiguration)
{ {
this.pluginConfiguration = pluginConfiguration; this._pluginConfiguration = pluginConfiguration;
cts = new CancellationTokenSource(); cts = new CancellationTokenSource();
_ = Heartbeat(); _ = Heartbeat();
@@ -294,8 +301,8 @@ namespace MareSynchronos.WebAPI
if (!ServerAlive) return; if (!ServerAlive) return;
PluginLog.Debug("Registering at service " + ApiUri); PluginLog.Debug("Registering at service " + ApiUri);
var response = await _userHub!.InvokeAsync<string>("Register"); var response = await _userHub!.InvokeAsync<string>("Register");
pluginConfiguration.ClientSecret[ApiUri] = response; _pluginConfiguration.ClientSecret[ApiUri] = response;
pluginConfiguration.Save(); _pluginConfiguration.Save();
RestartHeartbeat(); RestartHeartbeat();
} }
@@ -317,7 +324,7 @@ namespace MareSynchronos.WebAPI
CancelUpload(); CancelUpload();
uploadCancellationTokenSource = new CancellationTokenSource(); uploadCancellationTokenSource = new CancellationTokenSource();
var uploadToken = uploadCancellationTokenSource.Token; 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); 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"); PluginLog.Debug("Upload tasks complete, waiting for server to confirm");
var anyUploadsOpen = await _fileHub!.InvokeAsync<bool>("IsUploadFinished", uploadToken); var anyUploadsOpen = await _fileHub!.InvokeAsync<bool>("IsUploadFinished", uploadToken);
PluginLog.Warning("Uploads open: " + anyUploadsOpen); PluginLog.Debug("Uploads open: " + anyUploadsOpen);
while (anyUploadsOpen && !uploadToken.IsCancellationRequested) while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
{ {
anyUploadsOpen = await _fileHub!.InvokeAsync<bool>("IsUploadFinished", uploadToken); anyUploadsOpen = await _fileHub!.InvokeAsync<bool>("IsUploadFinished", uploadToken);
@@ -357,7 +364,7 @@ namespace MareSynchronos.WebAPI
if (!uploadToken.IsCancellationRequested) if (!uploadToken.IsCancellationRequested)
{ {
PluginLog.Warning("=== Pushing character data ==="); PluginLog.Debug("=== Pushing character data ===");
await _userHub!.InvokeAsync("PushCharacterData", character, visibleCharacterIds, uploadToken); await _userHub!.InvokeAsync("PushCharacterData", character, visibleCharacterIds, uploadToken);
} }
else else
@@ -372,11 +379,7 @@ namespace MareSynchronos.WebAPI
public Task ReceiveCharacterData(CharacterCacheDto character, string characterHash) public Task ReceiveCharacterData(CharacterCacheDto character, string characterHash)
{ {
PluginLog.Debug("Received DTO for " + characterHash); PluginLog.Debug("Received DTO for " + characterHash);
CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs() CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character));
{
CharacterData = character,
CharacterNameHash = characterHash
});
return Task.CompletedTask; return Task.CompletedTask;
} }