Chara file data export (#38)

* add rudimentary saving of chara file data

* fix building

* working prototype for MCDF import and application

* adjust code to use streams

* rename cache -> storage
add ui for import/export mcdf

* minor wording adjustments, version bump

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
Co-authored-by: Stanley Dimant <stanley.dimant@varian.com>
This commit is contained in:
rootdarkarchon
2023-01-23 12:56:43 +01:00
committed by GitHub
parent 1c95a6d40e
commit 7a8655f6b1
15 changed files with 664 additions and 51 deletions

View File

@@ -40,6 +40,7 @@ public class CompactUi : Window, IDisposable
public float TransferPartHeight = 0;
public float _windowContentWidth = 0;
private bool _showModalForUserAddition = false;
private bool _wasOpen = false;
private bool showSyncShells = false;
private GroupPanel groupPanel;
@@ -85,6 +86,9 @@ public class CompactUi : Window, IDisposable
_selectPairsForGroupUi = new(_tagHandler, configuration);
_pairGroupsUi = new(_tagHandler, DrawPairedClient, apiController, _selectPairsForGroupUi);
_uiShared.GposeStart += UiShared_GposeStart;
_uiShared.GposeEnd += UiShared_GposeEnd;
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(350, 400),
@@ -94,10 +98,23 @@ public class CompactUi : Window, IDisposable
windowSystem.AddWindow(this);
}
private void UiShared_GposeEnd()
{
IsOpen = _wasOpen;
}
private void UiShared_GposeStart()
{
_wasOpen = IsOpen;
IsOpen = false;
}
public event SwitchUi? OpenSettingsUi;
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(CompactUi));
_uiShared.GposeStart -= UiShared_GposeStart;
_uiShared.GposeEnd -= UiShared_GposeEnd;
_windowSystem.RemoveWindow(this);
}

View File

@@ -14,6 +14,7 @@ public class DownloadUi : Window, IDisposable
private readonly Configuration _pluginConfiguration;
private readonly ApiController _apiController;
private readonly UiShared _uiShared;
private bool _wasOpen = false;
public void Dispose()
{
@@ -52,6 +53,13 @@ public class DownloadUi : Window, IDisposable
public override void PreDraw()
{
if (_uiShared.IsInGpose)
{
_wasOpen = IsOpen;
IsOpen = false;
}
base.PreDraw();
if (_uiShared.EditTrackerPosition)
{

View File

@@ -0,0 +1,87 @@
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using MareSynchronos.Export;
using MareSynchronos.Utils;
using System;
using System.Threading.Tasks;
namespace MareSynchronos.UI;
public class GposeUi : Window, IDisposable
{
private readonly WindowSystem _windowSystem;
private readonly MareCharaFileManager _mareCharaFileManager;
private readonly DalamudUtil _dalamudUtil;
private readonly FileDialogManager _fileDialogManager;
private readonly Configuration _configuration;
public GposeUi(WindowSystem windowSystem, MareCharaFileManager mareCharaFileManager, DalamudUtil dalamudUtil, FileDialogManager fileDialogManager, Configuration configuration) : base("Mare Synchronos Gpose Import UI###MareSynchronosGposeUI")
{
_windowSystem = windowSystem;
_mareCharaFileManager = mareCharaFileManager;
_dalamudUtil = dalamudUtil;
_fileDialogManager = fileDialogManager;
_configuration = configuration;
_dalamudUtil.GposeStart += StartGpose;
_dalamudUtil.GposeEnd += EndGpose;
IsOpen = _dalamudUtil.IsInGpose;
Flags = ImGuiWindowFlags.AlwaysAutoResize;
_windowSystem.AddWindow(this);
}
private void EndGpose()
{
IsOpen = false;
_mareCharaFileManager.ClearMareCharaFile();
}
private void StartGpose()
{
IsOpen = _configuration.OpenGposeImportOnGposeStart;
}
public void Dispose()
{
_dalamudUtil.GposeStart -= StartGpose;
_dalamudUtil.GposeEnd -= EndGpose;
_windowSystem.RemoveWindow(this);
}
public override void Draw()
{
if (!_dalamudUtil.IsInGpose) IsOpen = false;
if (!_mareCharaFileManager.CurrentlyWorking)
{
if (UiShared.IconTextButton(FontAwesomeIcon.FolderOpen, "Load MCDF"))
{
_fileDialogManager.OpenFileDialog("Pick MCDF file", ".mcdf", (success, path) =>
{
if (!success) return;
Task.Run(() => _mareCharaFileManager.LoadMareCharaFile(path));
});
}
UiShared.AttachToolTip("Applies it to the currently selected GPose actor");
if (_mareCharaFileManager.LoadedCharaFile != null)
{
UiShared.TextWrapped("Loaded file: " + _mareCharaFileManager.LoadedCharaFile.FilePath);
UiShared.TextWrapped("File Description: " + _mareCharaFileManager.LoadedCharaFile.CharaFileData.Description);
if (UiShared.IconTextButton(FontAwesomeIcon.Check, "Apply loaded MCDF"))
{
Task.Run(async () => await _mareCharaFileManager.ApplyMareCharaFile(_dalamudUtil.GposeTargetGameObject).ConfigureAwait(false));
}
UiShared.AttachToolTip("Applies it to the currently selected GPose actor");
UiShared.ColorTextWrapped("Warning: redrawing or changing the character will revert all applied mods.", ImGuiColors.DalamudYellow);
}
}
else
{
UiShared.ColorTextWrapped("Loading Character...", ImGuiColors.DalamudYellow);
}
UiShared.TextWrapped("Hint: You can disable the automatic loading of this window in the Mare settings and open it manually with /mare gpose");
}
}

View File

@@ -62,6 +62,8 @@ internal class IntroUi : Window, IDisposable
public override void Draw()
{
if (_uiShared.IsInGpose) return;
if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage)
{
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
@@ -149,7 +151,7 @@ internal class IntroUi : Window, IDisposable
|| !Directory.Exists(_pluginConfiguration.CacheFolder)))
{
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
ImGui.TextUnformatted("File Cache Setup");
ImGui.TextUnformatted("File Storage Setup");
if (_uiShared.UidFontBuilt) ImGui.PopFont();
ImGui.Separator();
@@ -160,11 +162,12 @@ internal class IntroUi : Window, IDisposable
else
{
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.");
"Additionally, a local storage folder must be set where Mare Synchronos will download other character files to. " +
"Once the storage 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.");
UiShared.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.csv 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.", ImGuiColors.DalamudYellow);
UiShared.ColorTextWrapped("Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly.", ImGuiColors.DalamudYellow);
_uiShared.DrawCacheDirectorySetting();
}

View File

@@ -13,6 +13,7 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Dalamud.Utility;
using Newtonsoft.Json;
using MareSynchronos.Export;
namespace MareSynchronos.UI;
@@ -22,6 +23,7 @@ public class SettingsUi : Window, IDisposable
private readonly Configuration _configuration;
private readonly WindowSystem _windowSystem;
private readonly ApiController _apiController;
private readonly MareCharaFileManager _mareCharaFileManager;
private readonly UiShared _uiShared;
public CharacterCacheDto LastCreatedCharacterData { private get; set; }
@@ -32,9 +34,11 @@ public class SettingsUi : Window, IDisposable
private bool _openPopupOnAddition;
private bool _hideInfoMessages;
private bool _disableOptionalPluginsWarnings;
private bool _wasOpen = false;
public SettingsUi(WindowSystem windowSystem,
UiShared uiShared, Configuration configuration, ApiController apiController) : base("Mare Synchronos Settings")
UiShared uiShared, Configuration configuration, ApiController apiController,
MareCharaFileManager mareCharaFileManager) : base("Mare Synchronos Settings")
{
Logger.Verbose("Creating " + nameof(SettingsUi));
@@ -47,17 +51,36 @@ public class SettingsUi : Window, IDisposable
_configuration = configuration;
_windowSystem = windowSystem;
_apiController = apiController;
_mareCharaFileManager = mareCharaFileManager;
_uiShared = uiShared;
_openPopupOnAddition = _configuration.OpenPopupOnAdd;
_hideInfoMessages = _configuration.HideInfoMessages;
_disableOptionalPluginsWarnings = _configuration.DisableOptionalPluginWarnings;
_uiShared.GposeStart += _uiShared_GposeStart;
_uiShared.GposeEnd += _uiShared_GposeEnd;
windowSystem.AddWindow(this);
}
private void _uiShared_GposeEnd()
{
IsOpen = _wasOpen;
}
private void _uiShared_GposeStart()
{
_wasOpen = IsOpen;
IsOpen = false;
}
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(SettingsUi));
_uiShared.GposeStart -= _uiShared_GposeStart;
_uiShared.GposeEnd -= _uiShared_GposeEnd;
_windowSystem.RemoveWindow(this);
}
@@ -87,9 +110,9 @@ public class SettingsUi : Window, IDisposable
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Cache Settings"))
if (ImGui.BeginTabItem("Export & Storage"))
{
DrawFileCacheSettings();
DrawFileStorageSettings();
ImGui.EndTabItem();
}
@@ -140,6 +163,7 @@ public class SettingsUi : Window, IDisposable
}
_lastTab = "General";
UiShared.FontText("Notes", _uiShared.UidFont);
if (UiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard"))
{
ImGui.SetClipboardText(_uiShared.GetNotes());
@@ -162,7 +186,6 @@ public class SettingsUi : Window, IDisposable
{
UiShared.ColorTextWrapped("Attempt to import notes from clipboard failed. Check formatting and try again", ImGuiColors.DalamudRed);
}
ImGui.Separator();
if (ImGui.Checkbox("Open Notes Popup on user addition", ref _openPopupOnAddition))
{
_apiController.LastAddedUser = null;
@@ -170,6 +193,9 @@ public class SettingsUi : Window, IDisposable
_configuration.Save();
}
UiShared.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs.");
ImGui.Separator();
UiShared.FontText("Server Messages", _uiShared.UidFont);
if (ImGui.Checkbox("Hide Server Info Messages", ref _hideInfoMessages))
{
_configuration.HideInfoMessages = _hideInfoMessages;
@@ -520,7 +546,7 @@ public class SettingsUi : Window, IDisposable
if (!_configuration.FullPause)
{
UiShared.ColorTextWrapped("Note: to change servers you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow);
UiShared.ColorTextWrapped("Note: to change servers or your secret key you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow);
}
var marePaused = _configuration.FullPause;
@@ -546,6 +572,10 @@ public class SettingsUi : Window, IDisposable
_uiShared.DrawServiceSelection(() => { });
}
ImGui.Separator();
UiShared.FontText("Debug", _uiShared.UidFont);
if (UiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
{
if (LastCreatedCharacterData != null)
@@ -560,6 +590,9 @@ public class SettingsUi : Window, IDisposable
UiShared.AttachToolTip("Use this when reporting mods being rejected from the server.");
}
private string _charaFileSavePath = string.Empty;
private string _charaFileLoadPath = string.Empty;
private void DrawBlockedTransfers()
{
_lastTab = "BlockedTransfers";
@@ -675,15 +708,77 @@ public class SettingsUi : Window, IDisposable
}
}
private void DrawFileCacheSettings()
private bool _readExport = false;
private string _exportDescription = string.Empty;
private void DrawFileStorageSettings()
{
_lastTab = "FileCache";
UiShared.FontText("Export MCDF", _uiShared.UidFont);
UiShared.TextWrapped("This feature allows you to pack your character into a MCDF file and manually send it to other people. MCDF files can officially only be imported during GPose through Mare. " +
"Be aware that the possibility exists that people write unoffocial custom exporters to extract the containing data.");
ImGui.Checkbox("##readExport", ref _readExport);
ImGui.SameLine();
UiShared.TextWrapped("I understand that by exporting my character data and sending it to other people I am giving away my current character appearance irrevocably. People I am sharing my data with have the ability to share it with other people without limitations.");
if (_readExport)
{
if (!_mareCharaFileManager.CurrentlyWorking)
{
ImGui.Indent();
ImGui.InputTextWithHint("Export Descriptor", "This description will be shown on loading the data", ref _exportDescription, 255);
if (UiShared.IconTextButton(FontAwesomeIcon.Save, "Export Character as MCDF"))
{
_uiShared.FileDialogManager.SaveFileDialog("Export Character to file", ".mcdf", "export.mcdf", ".mcdf", (success, path) =>
{
if (!success) return;
Task.Run(() =>
{
try
{
_mareCharaFileManager.SaveMareCharaFile(LastCreatedCharacterData, _exportDescription, path);
_exportDescription = string.Empty;
}
catch (Exception ex)
{
Logger.Error("Error saving data", ex);
}
});
});
}
ImGui.Unindent();
}
else
{
UiShared.ColorTextWrapped("Export in progress", ImGuiColors.DalamudYellow);
}
}
bool openInGpose = _configuration.OpenGposeImportOnGposeStart;
if (ImGui.Checkbox("Open MCDF import window when GPose loads", ref openInGpose))
{
_configuration.OpenGposeImportOnGposeStart = openInGpose;
_configuration.Save();
}
UiShared.DrawHelpText("This will automatically open the import menu when loading into Gpose. If unchecked you can open the menu manually with /mare gpose");
ImGui.Separator();
UiShared.FontText("Storage", _uiShared.UidFont);
UiShared.TextWrapped("Mare stores downloaded files from paired people permanently. This is to improve loading performance and requiring less downloads. " +
"The storage governs itself by clearing data beyond the set storage size. Please set the storage size accordingly. It is not necessary to manually clear the storage.");
_uiShared.DrawFileScanState();
_uiShared.DrawTimeSpanBetweenScansSetting();
_uiShared.DrawCacheDirectorySetting();
ImGui.Text($"Local cache size: {UiShared.ByteToString(_uiShared.FileCacheSize)}");
ImGui.Text($"Local storage size: {UiShared.ByteToString(_uiShared.FileCacheSize)}");
ImGui.SameLine();
if (ImGui.Button("Clear local cache"))
if (ImGui.Button("Clear local storage"))
{
if (UiShared.CtrlPressed())
{
@@ -699,7 +794,7 @@ public class SettingsUi : Window, IDisposable
}
}
UiShared.AttachToolTip("You normally do not need to do this. This will solely remove all downloaded data from all players and will require you to re-download everything again." + Environment.NewLine
+ "Mares Cache is self-clearing and will not surpass the limit you have set it to." + Environment.NewLine
+ "Mares storage is self-clearing and will not surpass the limit you have set it to." + Environment.NewLine
+ "If you still think you need to do this hold CTRL while pressing the button.");
}

View File

@@ -28,7 +28,7 @@ public class UiShared : IDisposable
private readonly IpcManager _ipcManager;
private readonly ApiController _apiController;
private readonly PeriodicFileScanner _cacheScanner;
private readonly FileDialogManager _fileDialogManager;
public readonly FileDialogManager FileDialogManager;
private readonly Configuration _pluginConfiguration;
private readonly DalamudUtil _dalamudUtil;
private readonly DalamudPluginInterface _pluginInterface;
@@ -39,6 +39,9 @@ public class UiShared : IDisposable
public bool EditTrackerPosition { get; set; }
public ImFontPtr UidFont { get; private set; }
public bool UidFontBuilt { get; private set; }
public bool IsInGpose => _dalamudUtil.IsInGpose;
public event VoidDelegate? GposeStart;
public event VoidDelegate? GposeEnd;
public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0;
public static bool ShiftPressed() => (GetKeyState(0xA1) & 0x8000) != 0 || (GetKeyState(0xA0) & 0x8000) != 0;
@@ -54,7 +57,7 @@ public class UiShared : IDisposable
_ipcManager = ipcManager;
_apiController = apiController;
_cacheScanner = cacheScanner;
_fileDialogManager = fileDialogManager;
FileDialogManager = fileDialogManager;
_pluginConfiguration = pluginConfiguration;
_dalamudUtil = dalamudUtil;
_pluginInterface = pluginInterface;
@@ -63,6 +66,19 @@ public class UiShared : IDisposable
_pluginInterface.UiBuilder.BuildFonts += BuildFont;
_pluginInterface.UiBuilder.RebuildFonts();
_dalamudUtil.GposeStart += _dalamudUtil_GposeStart;
_dalamudUtil.GposeEnd += _dalamudUtil_GposeEnd;
}
private void _dalamudUtil_GposeEnd()
{
GposeEnd?.Invoke();
}
private void _dalamudUtil_GposeStart()
{
GposeStart?.Invoke();
}
public static float GetWindowContentRegionWidth()
@@ -268,7 +284,7 @@ public class UiShared : IDisposable
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
}
public static void FontText(string text, ImFontPtr font)
{
ImGui.PushFont(font);
@@ -490,16 +506,16 @@ public class UiShared : IDisposable
public void DrawCacheDirectorySetting()
{
ColorTextWrapped("Note: The cache folder should be somewhere close to root (i.e. C:\\MareCache) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\MareStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
var cacheDirectory = _pluginConfiguration.CacheFolder;
ImGui.InputText("Cache Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
ImGui.InputText("Storage Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
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) =>
FileDialogManager.OpenFolderDialog("Pick Mare Synchronos Storage Folder", (success, path) =>
{
if (!success) return;
@@ -525,7 +541,7 @@ public class UiShared : IDisposable
if (_isPenumbraDirectory)
{
ColorTextWrapped("Do not point the cache path directly to the Penumbra directory. If necessary, make a subfolder in it.", ImGuiColors.DalamudRed);
ColorTextWrapped("Do not point the storage path directly to the Penumbra directory. If necessary, make a subfolder in it.", ImGuiColors.DalamudRed);
}
else if (!_isDirectoryWritable)
{
@@ -533,7 +549,7 @@ public class UiShared : IDisposable
}
else if (_cacheDirectoryHasOtherFilesThanCache)
{
ColorTextWrapped("Your selected directory has files inside that are not Mare related. Use an empty directory or a previous Mare cache directory only.", ImGuiColors.DalamudRed);
ColorTextWrapped("Your selected directory has files inside that are not Mare related. Use an empty directory or a previous Mare storage directory only.", ImGuiColors.DalamudRed);
}
else if (!_cacheDirectoryIsValidPath)
{
@@ -542,12 +558,12 @@ public class UiShared : IDisposable
}
float maxCacheSize = (float)_pluginConfiguration.MaxLocalCacheInGiB;
if (ImGui.SliderFloat("Maximum Cache Size in GiB", ref maxCacheSize, 1f, 200f, "%.2f GiB"))
if (ImGui.SliderFloat("Maximum Storage Size in GiB", ref maxCacheSize, 1f, 200f, "%.2f GiB"))
{
_pluginConfiguration.MaxLocalCacheInGiB = maxCacheSize;
_pluginConfiguration.Save();
}
DrawHelpText("The cache is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
DrawHelpText("The storage is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
}
private bool _isDirectoryWritable = false;
@@ -697,5 +713,7 @@ public class UiShared : IDisposable
public void Dispose()
{
_pluginInterface.UiBuilder.BuildFonts -= BuildFont;
_dalamudUtil.GposeStart -= _dalamudUtil_GposeStart;
_dalamudUtil.GposeEnd -= _dalamudUtil_GposeEnd;
}
}