From 4c2233476110ef766dbd46cc6e91baec7d38eaa5 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Thu, 22 Dec 2022 01:01:09 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=97=EF=B8=8F=20Import/Export:=20Import,?= =?UTF-8?q?=20event=20rework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pal.Client/Configuration.cs | 25 ++- Pal.Client/LocalState.cs | 6 +- Pal.Client/Marker.cs | 9 ++ Pal.Client/Plugin.cs | 145 +++--------------- .../Scheduled/IQueueOnFrameworkThread.cs | 13 ++ Pal.Client/Scheduled/QueuedConfigUpdate.cs | 19 +++ Pal.Client/Scheduled/QueuedImport.cs | 121 +++++++++++++++ Pal.Client/Scheduled/QueuedSyncResponse.cs | 97 ++++++++++++ Pal.Client/Windows/ConfigWindow.cs | 38 ++++- Pal.Common/ExportConfig.cs | 13 ++ 10 files changed, 350 insertions(+), 136 deletions(-) create mode 100644 Pal.Client/Scheduled/IQueueOnFrameworkThread.cs create mode 100644 Pal.Client/Scheduled/QueuedConfigUpdate.cs create mode 100644 Pal.Client/Scheduled/QueuedImport.cs create mode 100644 Pal.Client/Scheduled/QueuedSyncResponse.cs create mode 100644 Pal.Common/ExportConfig.cs diff --git a/Pal.Client/Configuration.cs b/Pal.Client/Configuration.cs index afbdfde..4d971aa 100644 --- a/Pal.Client/Configuration.cs +++ b/Pal.Client/Configuration.cs @@ -1,5 +1,6 @@ using Dalamud.Configuration; using Dalamud.Logging; +using Pal.Client.Scheduled; using System; using System.Collections.Generic; using System.Linq; @@ -25,6 +26,8 @@ namespace Pal.Client public Dictionary AccountIds { private get; set; } = new(); public Dictionary Accounts { get; set; } = new(); + public List ImportHistory { get; set; } = new(); + public bool ShowTraps { get; set; } = true; public Vector4 TrapColor { get; set; } = new Vector4(1, 0, 0, 0.4f); public bool OnlyVisibleTrapsAfterPomander { get; set; } = true; @@ -36,10 +39,12 @@ namespace Pal.Client public bool ShowSilverCoffers { get; set; } = false; public Vector4 SilverCofferColor { get; set; } = new Vector4(1, 1, 1, 0.4f); public bool FillSilverCoffers { get; set; } = true; - #endregion - public delegate void OnSaved(); - public event OnSaved? Saved; + /// + /// Needs to be manually set. + /// + public string BetaKey { get; set; } = ""; + #endregion #pragma warning disable CS0612 // Type or member is obsolete public void Migrate() @@ -75,7 +80,7 @@ namespace Pal.Client public void Save() { Service.PluginInterface.SavePluginConfig(this); - Saved?.Invoke(); + Service.Plugin.EarlyEventQueue.Enqueue(new QueuedConfigUpdate()); } public enum EMode @@ -105,5 +110,17 @@ namespace Pal.Client /// public List CachedRoles { get; set; } = new List(); } + + public class ImportHistoryEntry + { + public Guid Id { get; set; } + public string? RemoteUrl { get; set; } + public DateTime ExportedAt { get; set; } + + /// + /// Set when the file is imported locally. + /// + public DateTime ImportedAt { get; set; } + } } } diff --git a/Pal.Client/LocalState.cs b/Pal.Client/LocalState.cs index ae62a9f..8cd23f2 100644 --- a/Pal.Client/LocalState.cs +++ b/Pal.Client/LocalState.cs @@ -26,7 +26,11 @@ namespace Pal.Client private void ApplyFilters() { if (Service.Configuration.Mode == Configuration.EMode.Offline) - Markers = new ConcurrentBag(Markers.Where(x => x.Seen)); + Markers = new ConcurrentBag(Markers.Where(x => x.Seen || (x.WasImported && x.Imports.Count > 0))); + else + // ensure old import markers are removed if they are no longer part of a "current" import + // this MAY remove markers the server sent you (and that you haven't seen), but this should be fixed the next time you enter the zone + Markers = new ConcurrentBag(Markers.Where(x => x.Seen || !x.WasImported || x.Imports.Count > 0)); } public static LocalState? Load(uint territoryType) diff --git a/Pal.Client/Marker.cs b/Pal.Client/Marker.cs index 8551a9b..12bb253 100644 --- a/Pal.Client/Marker.cs +++ b/Pal.Client/Marker.cs @@ -41,6 +41,15 @@ namespace Pal.Client [JsonIgnore] public bool RemoteSeenRequested { get; set; } = false; + /// + /// To keep track of which markers were imported through a downloaded file, we save the associated import-id. + /// + /// Importing another file for the same remote server will remove the old import-id, and add the new import-id here. + /// + public List Imports { get; set; } = new List(); + + public bool WasImported { get; set; } + [JsonIgnore] public Element? SplatoonElement { get; set; } diff --git a/Pal.Client/Plugin.cs b/Pal.Client/Plugin.cs index 1fca5f8..6a9e26b 100644 --- a/Pal.Client/Plugin.cs +++ b/Pal.Client/Plugin.cs @@ -15,6 +15,7 @@ using Grpc.Core; using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Pal.Client.Net; +using Pal.Client.Scheduled; using Pal.Client.Windows; using Pal.Common; using System; @@ -35,23 +36,23 @@ namespace Pal.Client private const string SPLATOON_TRAP_HOARD = "PalacePal.TrapHoard"; private const string SPLATOON_REGULAR_COFFERS = "PalacePal.RegularCoffers"; - private readonly ConcurrentQueue _pendingSyncResponses = new(); private readonly static Dictionary _markerConfig = new Dictionary { { Marker.EType.Trap, new MarkerConfig { Radius = 1.7f } }, { Marker.EType.Hoard, new MarkerConfig { Radius = 1.7f, OffsetY = -0.03f } }, { Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } }, }; - private bool _configUpdated = false; private LocalizedChatMessages _localizedChatMessages = new(); internal ConcurrentDictionary FloorMarkers { get; } = new(); internal ConcurrentBag EphemeralMarkers { get; set; } = new(); - internal ushort LastTerritory { get; private set; } + internal ushort LastTerritory { get; set; } public SyncState TerritorySyncState { get; set; } public PomanderState PomanderOfSight { get; set; } = PomanderState.Inactive; public PomanderState PomanderOfIntuition { get; set; } = PomanderState.Inactive; public string? DebugMessage { get; set; } + internal Queue EarlyEventQueue { get; } = new(); + internal Queue LateEventQueue { get; } = new(); public string Name => "Palace Pal"; @@ -101,7 +102,6 @@ namespace Pal.Client pluginInterface.UiBuilder.Draw += Service.WindowSystem.Draw; pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi; Service.Framework.Update += OnFrameworkUpdate; - Service.Configuration.Saved += OnConfigSaved; Service.Chat.ChatMessage += OnChatMessage; Service.CommandManager.AddHandler("/pal", new CommandInfo(OnCommand) { @@ -182,7 +182,6 @@ namespace Pal.Client Service.PluginInterface.UiBuilder.Draw -= Service.WindowSystem.Draw; Service.PluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfigUi; Service.Framework.Update -= OnFrameworkUpdate; - Service.Configuration.Saved -= OnConfigSaved; Service.Chat.ChatMessage -= OnChatMessage; Service.WindowSystem.RemoveAllWindows(); @@ -208,11 +207,6 @@ namespace Pal.Client } #endregion - private void OnConfigSaved() - { - _configUpdated = true; - } - private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString seMessage, ref bool isHandled) { if (Service.Configuration.FirstUse) @@ -259,27 +253,18 @@ namespace Pal.Client try { bool recreateLayout = false; - if (_configUpdated) - { - if (Service.Configuration.Mode == Configuration.EMode.Offline) - { - LocalState.UpdateAll(); - FloorMarkers.Clear(); - EphemeralMarkers.Clear(); - LastTerritory = 0; - } - _configUpdated = false; - recreateLayout = true; - } - bool saveMarkers = false; + + while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued)) + queued?.Run(this, ref recreateLayout, ref saveMarkers); + if (LastTerritory != Service.ClientState.TerritoryType) { LastTerritory = Service.ClientState.TerritoryType; TerritorySyncState = SyncState.NotAttempted; if (IsInDeepDungeon()) - FloorMarkers[LastTerritory] = LocalState.Load(LastTerritory) ?? new LocalState(LastTerritory); + GetFloorMarkers(LastTerritory); EphemeralMarkers.Clear(); PomanderOfSight = PomanderState.Inactive; PomanderOfIntuition = PomanderState.Inactive; @@ -296,15 +281,10 @@ namespace Pal.Client Task.Run(async () => await DownloadMarkersForTerritory(LastTerritory)); } - if (_pendingSyncResponses.Count > 0) - { - HandleSyncResponses(); - recreateLayout = true; - saveMarkers = true; - } + while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued)) + queued?.Run(this, ref recreateLayout, ref saveMarkers); - if (!FloorMarkers.TryGetValue(LastTerritory, out var currentFloor)) - FloorMarkers[LastTerritory] = currentFloor = new LocalState(LastTerritory); + var currentFloor = GetFloorMarkers(LastTerritory); IList visibleMarkers = GetRelevantGameObjects(); HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout); @@ -316,6 +296,11 @@ namespace Pal.Client } } + internal LocalState GetFloorMarkers(ushort territoryType) + { + return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt)); + } + private void HandlePersistentMarkers(LocalState currentFloor, IList visibleMarkers, bool saveMarkers, bool recreateLayout) { var config = Service.Configuration; @@ -403,7 +388,7 @@ namespace Pal.Client List elements = new List(); foreach (var marker in currentFloorMarkers) { - if (marker.Seen || config.Mode == Configuration.EMode.Online) + if (marker.Seen || config.Mode == Configuration.EMode.Online || (marker.WasImported && marker.Imports.Count > 0)) { if (marker.Type == Marker.EType.Trap && config.ShowTraps) { @@ -503,7 +488,7 @@ namespace Pal.Client try { var (success, downloadedMarkers) = await Service.RemoteApi.DownloadRemoteMarkers(territoryId); - _pendingSyncResponses.Enqueue(new Sync + LateEventQueue.Enqueue(new QueuedSyncResponse { Type = SyncType.Download, TerritoryType = territoryId, @@ -522,7 +507,7 @@ namespace Pal.Client try { var (success, uploadedMarkers) = await Service.RemoteApi.UploadMarker(territoryId, markersToUpload); - _pendingSyncResponses.Enqueue(new Sync + LateEventQueue.Enqueue(new QueuedSyncResponse { Type = SyncType.Upload, TerritoryType = territoryId, @@ -541,7 +526,7 @@ namespace Pal.Client try { var success = await Service.RemoteApi.MarkAsSeen(territoryId, markersToUpdate); - _pendingSyncResponses.Enqueue(new Sync + LateEventQueue.Enqueue(new QueuedSyncResponse { Type = SyncType.MarkSeen, TerritoryType = territoryId, @@ -587,70 +572,6 @@ namespace Pal.Client } } - private void HandleSyncResponses() - { - while (_pendingSyncResponses.TryDequeue(out Sync? sync) && sync != null) - { - try - { - var territoryId = sync.TerritoryType; - var remoteMarkers = sync.Markers; - if (Service.Configuration.Mode == Configuration.EMode.Online && sync.Success && FloorMarkers.TryGetValue(territoryId, out var currentFloor) && remoteMarkers.Count > 0) - { - switch (sync.Type) - { - case SyncType.Download: - case SyncType.Upload: - foreach (var remoteMarker in remoteMarkers) - { - // Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved. - Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker); - if (localMarker != null) - { - localMarker.NetworkId = remoteMarker.NetworkId; - continue; - } - - if (sync.Type == SyncType.Download) - currentFloor.Markers.Add(remoteMarker); - } - break; - - case SyncType.MarkSeen: - var accountId = Service.RemoteApi.AccountId; - if (accountId == null) - break; - foreach (var remoteMarker in remoteMarkers) - { - Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker); - if (localMarker != null) - localMarker.RemoteSeenOn.Add(accountId.Value); - } - break; - } - } - - // don't modify state for outdated floors - if (LastTerritory != territoryId) - continue; - - if (sync.Type == SyncType.Download) - { - if (sync.Success) - TerritorySyncState = SyncState.Complete; - else - TerritorySyncState = SyncState.Failed; - } - } - catch (Exception e) - { - DebugMessage = $"{DateTime.Now}\n{e}"; - if (sync.Type == SyncType.Download) - TerritorySyncState = SyncState.Failed; - } - } - } - private IList GetRelevantGameObjects() { List result = new(); @@ -729,30 +650,6 @@ namespace Pal.Client return Service.DataManager.GetExcelSheet()?.GetRow(id)?.Text?.ToString() ?? "Unknown"; } - internal class Sync - { - public SyncType Type { get; set; } - public ushort TerritoryType { get; set; } - public bool Success { get; set; } - public List Markers { get; set; } = new(); - } - - public enum SyncState - { - NotAttempted, - NotNeeded, - Started, - Complete, - Failed, - } - - public enum SyncType - { - Upload, - Download, - MarkSeen, - } - public enum PomanderState { Inactive, diff --git a/Pal.Client/Scheduled/IQueueOnFrameworkThread.cs b/Pal.Client/Scheduled/IQueueOnFrameworkThread.cs new file mode 100644 index 0000000..bf02b9d --- /dev/null +++ b/Pal.Client/Scheduled/IQueueOnFrameworkThread.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pal.Client.Scheduled +{ + internal interface IQueueOnFrameworkThread + { + void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers); + } +} diff --git a/Pal.Client/Scheduled/QueuedConfigUpdate.cs b/Pal.Client/Scheduled/QueuedConfigUpdate.cs new file mode 100644 index 0000000..2f55574 --- /dev/null +++ b/Pal.Client/Scheduled/QueuedConfigUpdate.cs @@ -0,0 +1,19 @@ +namespace Pal.Client.Scheduled +{ + internal class QueuedConfigUpdate : IQueueOnFrameworkThread + { + public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers) + { + if (Service.Configuration.Mode == Configuration.EMode.Offline) + { + LocalState.UpdateAll(); + plugin.FloorMarkers.Clear(); + plugin.EphemeralMarkers.Clear(); + plugin.LastTerritory = 0; + + recreateLayout = true; + saveMarkers = true; + } + } + } +} diff --git a/Pal.Client/Scheduled/QueuedImport.cs b/Pal.Client/Scheduled/QueuedImport.cs new file mode 100644 index 0000000..02f4261 --- /dev/null +++ b/Pal.Client/Scheduled/QueuedImport.cs @@ -0,0 +1,121 @@ +using Account; +using Dalamud.Logging; +using Pal.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; + +namespace Pal.Client.Scheduled +{ + internal class QueuedImport : IQueueOnFrameworkThread + { + private readonly ExportRoot _export; + private Guid _exportId; + private int importedTraps; + private int importedHoardCoffers; + + public QueuedImport(string sourcePath) + { + using (var input = File.OpenRead(sourcePath)) + _export = ExportRoot.Parser.ParseFrom(input); + } + + public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers) + { + try + { + if (!Validate()) + return; + + var config = Service.Configuration; + var oldExportIds = string.IsNullOrEmpty(_export.ServerUrl) ? config.ImportHistory.Where(x => x.RemoteUrl == _export.ServerUrl).Select(x => x.Id).Where(x => x != Guid.Empty).ToList() : new List(); + + foreach (var remoteFloor in _export.Floors) + { + ushort territoryType = (ushort)remoteFloor.TerritoryType; + var localState = plugin.GetFloorMarkers(territoryType); + + CleanupFloor(localState, oldExportIds); + ImportFloor(remoteFloor, localState); + + localState.Save(); + } + + config.ImportHistory.RemoveAll(hist => oldExportIds.Contains(hist.Id) || hist.Id == _exportId); + config.ImportHistory.Add(new Configuration.ImportHistoryEntry + { + Id = _exportId, + RemoteUrl = _export.ServerUrl, + ExportedAt = _export.CreatedAt.ToDateTime(), + ImportedAt = DateTime.UtcNow, + }); + config.Save(); + + recreateLayout = true; + saveMarkers = true; + + Service.Chat.Print($"Imported {importedTraps} new trap locations and {importedHoardCoffers} new hoard coffer locations."); + } + catch (Exception e) + { + PluginLog.Error(e, "Import failed"); + Service.Chat.PrintError($"Import failed: {e}"); + } + } + + private bool Validate() + { + if (_export.ExportVersion != ExportConfig.ExportVersion) + { + Service.Chat.PrintError("Import failed: Incompatible version."); + return false; + } + + if (!Guid.TryParse(_export.ExportId, out _exportId) || _exportId == Guid.Empty) + { + Service.Chat.PrintError("Import failed: No id present."); + return false; + } + + if (string.IsNullOrEmpty(_export.ServerUrl)) + { + // If we allow for backups as import/export, this should be removed + Service.Chat.PrintError("Import failed: Unknown server."); + return false; + } + + return true; + } + + private void CleanupFloor(LocalState localState, List oldExportIds) + { + // When saving a floor state, any markers not seen, not remote seen, and not having an import id are removed; + // so it is possible to remove "wrong" markers by not having them be in the current import. + foreach (var marker in localState.Markers) + marker.Imports.RemoveAll(id => oldExportIds.Contains(id)); + } + + private void ImportFloor(ExportFloor remoteFloor, LocalState localState) + { + var remoteMarkers = remoteFloor.Objects.Select(m => new Marker((Marker.EType)m.Type, new Vector3(m.X, m.Y, m.Z)) { WasImported = true }); + foreach (var remoteMarker in remoteMarkers) + { + Marker? localMarker = localState.Markers.SingleOrDefault(x => x == remoteMarker); + if (localMarker == null) + { + localState.Markers.Add(remoteMarker); + localMarker = remoteMarker; + + if (localMarker.Type == Marker.EType.Trap) + importedTraps++; + else if (localMarker.Type == Marker.EType.Hoard) + importedHoardCoffers++; + } + + remoteMarker.Imports.Add(_exportId); + } + } + } +} diff --git a/Pal.Client/Scheduled/QueuedSyncResponse.cs b/Pal.Client/Scheduled/QueuedSyncResponse.cs new file mode 100644 index 0000000..dc272b7 --- /dev/null +++ b/Pal.Client/Scheduled/QueuedSyncResponse.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Pal.Client.Plugin; + +namespace Pal.Client.Scheduled +{ + internal class QueuedSyncResponse : IQueueOnFrameworkThread + { + public SyncType Type { get; set; } + public ushort TerritoryType { get; set; } + public bool Success { get; set; } + public List Markers { get; set; } = new(); + + public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers) + { + recreateLayout = true; + saveMarkers = true; + + try + { + var remoteMarkers = Markers; + var currentFloor = plugin.GetFloorMarkers(TerritoryType); + if (Service.Configuration.Mode == Configuration.EMode.Online && Success && remoteMarkers.Count > 0) + { + switch (Type) + { + case SyncType.Download: + case SyncType.Upload: + foreach (var remoteMarker in remoteMarkers) + { + // Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved. + Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker); + if (localMarker != null) + { + localMarker.NetworkId = remoteMarker.NetworkId; + continue; + } + + if (Type == SyncType.Download) + currentFloor.Markers.Add(remoteMarker); + } + break; + + case SyncType.MarkSeen: + var accountId = Service.RemoteApi.AccountId; + if (accountId == null) + break; + foreach (var remoteMarker in remoteMarkers) + { + Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker); + if (localMarker != null) + localMarker.RemoteSeenOn.Add(accountId.Value); + } + break; + } + } + + // don't modify state for outdated floors + if (plugin.LastTerritory != TerritoryType) + return; + + if (Type == SyncType.Download) + { + if (Success) + plugin.TerritorySyncState = SyncState.Complete; + else + plugin.TerritorySyncState = SyncState.Failed; + } + } + catch (Exception e) + { + plugin.DebugMessage = $"{DateTime.Now}\n{e}"; + if (Type == SyncType.Download) + plugin.TerritorySyncState = SyncState.Failed; + } + } + } + + public enum SyncState + { + NotAttempted, + NotNeeded, + Started, + Complete, + Failed, + } + + public enum SyncType + { + Upload, + Download, + MarkSeen, + } +} diff --git a/Pal.Client/Windows/ConfigWindow.cs b/Pal.Client/Windows/ConfigWindow.cs index 19c70c5..a32e61a 100644 --- a/Pal.Client/Windows/ConfigWindow.cs +++ b/Pal.Client/Windows/ConfigWindow.cs @@ -4,11 +4,14 @@ using Dalamud.Interface.Components; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Windowing; using Dalamud.Logging; +using ECommons; using ECommons.Reflection; using ECommons.SplatoonAPI; using Google.Protobuf; using ImGuiNET; using Pal.Client.Net; +using Pal.Client.Scheduled; +using Pal.Common; using System; using System.Collections; using System.Collections.Generic; @@ -88,7 +91,7 @@ namespace Pal.Client.Windows { DrawTrapCofferTab(ref save, ref saveAndClose); DrawCommunityTab(ref saveAndClose); - //DrawImportTab(); + DrawImportTab(); DrawExportTab(); DrawDebugTab(); @@ -196,15 +199,26 @@ namespace Pal.Client.Windows private void DrawImportTab() { + if (Service.Configuration.BetaKey != "import") + return; + if (ImGui.BeginTabItem("Import")) { + ImGui.TextWrapped("Using an export is useful if you're unable to connect to the server, or don't wish to share your findings."); + ImGui.TextWrapped("Exports are (currently) generated manually, and they only include traps and hoard coffers encountered by 5 or more people. This may lead to higher floors having very sporadic coverage, but commonly run floors (e.g. PotD 51-60, HoH 21-30) are closer to complete."); + ImGui.TextWrapped("If you aren't offline, importing a file won't have any noticeable effect."); + ImGui.Separator(); + ImGui.TextWrapped("Exports are available from https://github.com/carvelli/PalacePal/releases/ (as *.pal files)."); + if (ImGui.Button("Visit GitHub")) + GenericHelpers.ShellStart("https://github.com/carvelli/PalacePal/releases/"); + ImGui.Separator(); ImGui.Text("File to Import:"); ImGui.SameLine(); ImGui.InputTextWithHint("", "Path to *.pal file", ref _openImportPath, 260); ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Search)) { - _importDialog.OpenFileDialog("Palace Pal - Import", "Palace Pal (*.pal) {*.pal}", (success, paths) => + _importDialog.OpenFileDialog("Palace Pal - Import", "Palace Pal (*.pal) {.pal}", (success, paths) => { if (success && paths.Count == 1) { @@ -213,6 +227,11 @@ namespace Pal.Client.Windows }, selectionCountMax: 1, startPath: _openImportDialogStartPath, isModal: false); _openImportDialogStartPath = null; // only use this once, FileDialogManager will save path between calls } + + ImGui.BeginDisabled(string.IsNullOrEmpty(_openImportPath) || !File.Exists(_openImportPath)); + if (ImGui.Button("Start Import")) + DoImport(_openImportPath); + ImGui.EndDisabled(); ImGui.EndTabItem(); } } @@ -232,7 +251,7 @@ namespace Pal.Client.Windows ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Search)) { - _importDialog.SaveFileDialog("Palace Pal - Export", "Palace Pal (*.pal) {*.pal}", todaysFileName, "pal", (success, path) => + _importDialog.SaveFileDialog("Palace Pal - Export", "Palace Pal (*.pal) {.pal}", todaysFileName, "pal", (success, path) => { if (success && !string.IsNullOrEmpty(path)) { @@ -244,7 +263,7 @@ namespace Pal.Client.Windows ImGui.BeginDisabled(string.IsNullOrEmpty(_saveExportPath) || File.Exists(_saveExportPath)); if (ImGui.Button("Start Export")) - this.DoExport(_saveExportPath); + DoExport(_saveExportPath); ImGui.EndDisabled(); ImGui.EndTabItem(); @@ -382,7 +401,12 @@ namespace Pal.Client.Windows }); } - internal void DoExport(string destination) + internal void DoImport(string sourcePath) + { + Service.Plugin.EarlyEventQueue.Enqueue(new QueuedImport(sourcePath)); + } + + internal void DoExport(string destinationPath) { Task.Run(async () => { @@ -391,10 +415,10 @@ namespace Pal.Client.Windows (bool success, ExportRoot export) = await Service.RemoteApi.DoExport(); if (success) { - using var output = File.Create(destination); + using var output = File.Create(destinationPath); export.WriteTo(output); - Service.Chat.Print($"Export saved as {destination}."); + Service.Chat.Print($"Export saved as {destinationPath}."); } else { diff --git a/Pal.Common/ExportConfig.cs b/Pal.Common/ExportConfig.cs new file mode 100644 index 0000000..4df0263 --- /dev/null +++ b/Pal.Common/ExportConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pal.Common +{ + public static class ExportConfig + { + public static int ExportVersion { get; } = 1; + } +}