diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index 4867366..faa63c3 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -308,7 +308,17 @@ namespace MareSynchronos.Managers db.FileCaches.Remove(entry); } await db.SaveChangesAsync(ct); - db.FileCaches.AddRange(fileCachesToAdd); + foreach (var entry in fileCachesToAdd) + { + try + { + db.FileCaches.Add(entry); + } + catch + { + // ignored + } + } await db.SaveChangesAsync(ct); } catch (Exception ex) diff --git a/MareSynchronos/Models/CachedPlayer.cs b/MareSynchronos/Models/CachedPlayer.cs index 37c052d..bbf0a4f 100644 --- a/MareSynchronos/Models/CachedPlayer.cs +++ b/MareSynchronos/Models/CachedPlayer.cs @@ -54,6 +54,7 @@ public class CachedPlayer public string? PlayerName { get; private set; } public string PlayerNameHash { get; } + private string _lastAppliedEquipmentHash = string.Empty; public bool RequestedPenumbraRedraw { get; set; } @@ -66,28 +67,34 @@ public class CachedPlayer if (string.IsNullOrEmpty(PlayerName) || e.CharacterNameHash != PlayerNameHash) return; Logger.Debug("Received data for " + this); - _downloadCancellationTokenSource?.Cancel(); - _downloadCancellationTokenSource = new CancellationTokenSource(); - var downloadToken = _downloadCancellationTokenSource.Token; - Logger.Debug("Checking for files to download for player " + PlayerName); Logger.Debug("Hash for data is " + e.CharacterData.Hash); if (!_cache.ContainsKey(e.CharacterData.Hash)) { Logger.Debug("Received total " + e.CharacterData.FileReplacements.Count + " file replacement data"); _cache[e.CharacterData.Hash] = e.CharacterData; + _lastAppliedEquipmentHash = e.CharacterData.Hash; } else { Logger.Debug("Had valid local cache for " + PlayerName); } + DownloadAndApplyCharacter(); + } + + private void DownloadAndApplyCharacter() + { + _downloadCancellationTokenSource?.Cancel(); + _downloadCancellationTokenSource = new CancellationTokenSource(); + var downloadToken = _downloadCancellationTokenSource.Token; + Task.Run(async () => { List toDownloadReplacements; Dictionary moddedPaths; - while ((toDownloadReplacements = TryCalculateModdedDictionary(_cache[e.CharacterData.Hash], out moddedPaths)).Count > 0) + while ((toDownloadReplacements = TryCalculateModdedDictionary(_cache[_lastAppliedEquipmentHash], out moddedPaths)).Count > 0) { Logger.Debug("Downloading missing files for player " + PlayerName); await _apiController.DownloadFiles(toDownloadReplacements, downloadToken); @@ -98,7 +105,7 @@ public class CachedPlayer return; } - ApplyCharacterData(e.CharacterData, moddedPaths); + ApplyCharacterData(_cache[_lastAppliedEquipmentHash], moddedPaths); }, downloadToken); } @@ -196,18 +203,29 @@ public class CachedPlayer return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != null); } + private Task? penumbraRedrawEventTask; + private void IpcManagerOnPenumbraRedrawEvent(object? sender, EventArgs e) { var player = _dalamudUtil.GetPlayerCharacterFromObjectTableIndex((int)sender!); if (player == null || player.Name.ToString() != PlayerName) return; - Task.Run(() => + if (!penumbraRedrawEventTask?.IsCompleted ?? false) return; + + penumbraRedrawEventTask = Task.Run(() => { PlayerCharacter = player; _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter.Address); - RequestedPenumbraRedraw = false; - Logger.Debug( - $"Penumbra Redraw done for {PlayerName}"); + if (RequestedPenumbraRedraw == false && !string.IsNullOrEmpty(_lastAppliedEquipmentHash)) + { + DownloadAndApplyCharacter(); + } + else + { + RequestedPenumbraRedraw = false; + Logger.Debug( + $"Penumbra Redraw done for {PlayerName}"); + } }); } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index ec258d6..bc2c64a 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -7,6 +7,7 @@ using Dalamud.Game; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState; using System; +using Dalamud.Interface.ImGuiFileDialog; using MareSynchronos.Managers; using MareSynchronos.WebAPI; using Dalamud.Interface.Windowing; @@ -36,6 +37,7 @@ namespace MareSynchronos private CachedPlayersManager? _characterCacheManager; private readonly IPlayerWatcher _playerWatcher; private readonly DownloadUi _downloadUi; + private readonly FileDialogManager _fileDialogManager; public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager, Framework framework, ObjectTable objectTable, ClientState clientState) @@ -58,9 +60,10 @@ namespace MareSynchronos _ipcManager = new IpcManager(PluginInterface); _fileCacheManager = new FileCacheManager(_ipcManager, _configuration); + _fileDialogManager = new FileDialogManager(); var uiSharedComponent = - new UiShared(_ipcManager, _apiController, _fileCacheManager, _configuration); + new UiShared(_ipcManager, _apiController, _fileCacheManager, _fileDialogManager, _configuration); _pluginUi = new PluginUi(_windowSystem, uiSharedComponent, _configuration, _apiController); _introUi = new IntroUi(_windowSystem, uiSharedComponent, _configuration, _fileCacheManager); _introUi.FinishedRegistration += (_, _) => @@ -178,6 +181,7 @@ namespace MareSynchronos private void Draw() { _windowSystem.Draw(); + _fileDialogManager.Draw(); } private void OnCommand(string command, string args) diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 6fb25c6..ce5387f 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -2,7 +2,9 @@ using System.IO; using System.Numerics; using System.Threading.Tasks; +using Dalamud.Interface; using Dalamud.Interface.Colors; +using Dalamud.Interface.ImGuiFileDialog; using ImGuiNET; using MareSynchronos.Managers; using MareSynchronos.WebAPI; @@ -14,14 +16,16 @@ namespace MareSynchronos.UI private readonly IpcManager _ipcManager; private readonly ApiController _apiController; private readonly FileCacheManager _fileCacheManager; + private readonly FileDialogManager _fileDialogManager; private readonly Configuration _pluginConfiguration; public long FileCacheSize => _fileCacheManager.FileCacheSize; - public UiShared(IpcManager ipcManager, ApiController apiController, FileCacheManager fileCacheManager, Configuration pluginConfiguration) + public UiShared(IpcManager ipcManager, ApiController apiController, FileCacheManager fileCacheManager, FileDialogManager fileDialogManager, Configuration pluginConfiguration) { _ipcManager = ipcManager; _apiController = apiController; _fileCacheManager = fileCacheManager; + _fileDialogManager = fileDialogManager; _pluginConfiguration = pluginConfiguration; } @@ -186,7 +190,11 @@ namespace MareSynchronos.UI public static void DrawHelpText(string helpText) { ImGui.SameLine(); - ImGui.TextDisabled("(?)"); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.SetWindowFontScale(0.8f); + ImGui.TextDisabled(FontAwesomeIcon.Question.ToIconString()); + ImGui.SetWindowFontScale(1.0f); + ImGui.PopFont(); if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); @@ -210,7 +218,23 @@ namespace MareSynchronos.UI } } - if (!Directory.Exists(cacheDirectory)) + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + string folderIcon = FontAwesomeIcon.Folder.ToIconString(); + if (ImGui.Button(folderIcon + "##chooseCacheFolder")) + { + _fileDialogManager.OpenFolderDialog("Pick Mare Synchronos Cache Folder", (success, path) => + { + if (!success) return; + + _pluginConfiguration.CacheFolder = path; + _pluginConfiguration.Save(); + _fileCacheManager.StartWatchers(); + }); + } + ImGui.PopFont(); + + if (!Directory.Exists(cacheDirectory) || !IsDirectoryWritable(cacheDirectory)) { ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); TextWrapped("The folder you selected does not exist. Please provide a valid path."); @@ -218,6 +242,30 @@ namespace MareSynchronos.UI } } + public bool IsDirectoryWritable(string dirPath, bool throwIfFails = false) + { + try + { + using (FileStream fs = File.Create( + Path.Combine( + dirPath, + Path.GetRandomFileName() + ), + 1, + FileOptions.DeleteOnClose) + ) + { } + return true; + } + catch + { + if (throwIfFails) + throw; + else + return false; + } + } + public void DrawParallelScansSetting() { var parallelScans = _pluginConfiguration.MaxParallelScan;