-
Notifications
You must be signed in to change notification settings - Fork 337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement config import/export buttons for in game addon #1426
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,121 @@ | ||||
using System; | ||||
using System.Collections.Generic; | ||||
using System.IO.Compression; | ||||
using System.IO; | ||||
using System.Threading.Tasks; | ||||
|
||||
namespace XIVLauncher.Common.Support | ||||
{ | ||||
public class DalamudAndPluginConfigImportExport | ||||
{ | ||||
public static async Task<string> ExportConfig(string storagePath) | ||||
{ | ||||
// grab any platform-specific details | ||||
// there's none to grab currently :) | ||||
|
||||
// start making our zip | ||||
var outFile = new FileInfo(Path.Combine(storagePath, $"XIVLauncher-{DateTime.Now:yyyyMMddhhmmss}.xlconf")); | ||||
using var archive = ZipFile.Open(outFile.FullName, ZipArchiveMode.Create); | ||||
|
||||
// add all the stock XIVLauncher based paths | ||||
var accountsListFile = Path.Combine(storagePath, "accountsList.json"); | ||||
var dalamudConfigFile = Path.Combine(storagePath, "dalamudConfig.json"); | ||||
var dalamudVfsFile = Path.Combine(storagePath, "dalamudVfs.db"); | ||||
var pluginConfigsFolder = Path.Combine(storagePath, "pluginConfigs"); | ||||
|
||||
AddIfExist(accountsListFile, archive); | ||||
AddIfExist(dalamudConfigFile, archive); | ||||
AddIfExist(dalamudVfsFile, archive); | ||||
AddIfExist(pluginConfigsFolder, archive); | ||||
|
||||
// add some known special exceptions. It might be better to not build these expectations though | ||||
var backupsFolder = Path.Combine(storagePath, "backups"); // Otter plugins | ||||
var playerTrackBackupsFolder = Path.Combine(storagePath, "playerTrackBackups"); // PlayerTrack | ||||
|
||||
AddIfExist(backupsFolder, archive); | ||||
AddIfExist(playerTrackBackupsFolder, archive); | ||||
|
||||
// return the folder containing our exported settings | ||||
return outFile.FullName; | ||||
} | ||||
|
||||
public static void ImportConfig(string zipFilePath, string storagePath) | ||||
{ | ||||
// grab any platform-specific details | ||||
// there's none to grab currently :) | ||||
|
||||
// TODO: Decide if we're going to alert on overwriting config. | ||||
// Right now, Franz decided against it. The user has to intentionally try to use this feature | ||||
// Also, .Net Framework is dumb and will explode if we use ZipArchive.ExtractToDirectory() | ||||
// and there are any file conflicts. .Net Core doesn't have this issue though and provides | ||||
// an override we could have used to do it anyways. Alternatively, we could just delete | ||||
// all of the files/folders we'd be restoring to first, but that also feels bad. | ||||
|
||||
|
||||
|
||||
var inFile = new FileInfo(zipFilePath); | ||||
using var archive = ZipFile.Open(inFile.FullName, ZipArchiveMode.Read); | ||||
|
||||
// If we weren't on .Net Framework, we could use this... | ||||
// ZipFileExtensions.ExtractToDirectory(archive, storagePath, true); | ||||
|
||||
foreach (var entry in archive.Entries) | ||||
{ | ||||
var extractPath = storagePath + "\\" + entry.FullName; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Path.Combine()? |
||||
// If we were going to warn about overwriting files, it would go here. | ||||
/* | ||||
bool promptAlwaysForOverwrite = true; | ||||
if (promptAlwaysForOverwrite && File.Exists(extractPath)) | ||||
{ | ||||
// Make some prompt. Overwrite? Yes, Yes to All, Cancel | ||||
|
||||
if (result == no) break or something. | ||||
if (result == yestoall) promptAlwaysForOverwrite = false; | ||||
// yes is the default and needs no special handling. | ||||
} | ||||
*/ | ||||
if (!Directory.Exists(Path.GetDirectoryName(extractPath))) | ||||
{ | ||||
Directory.CreateDirectory(Path.GetDirectoryName(extractPath)); | ||||
} | ||||
ZipFileExtensions.ExtractToFile(entry, extractPath, true); | ||||
} | ||||
} | ||||
|
||||
private static void AddIfExist(string entryPath, ZipArchive zip) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have this function in the tspack code. Maybe move to common util class?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one's a little different from tspack code. I had to get a little hacky for directory handling. But that could probably still be used for tspacks too. I've moved it to a util method in xivlauncher.common.util, but the WPF app didn't really like me doing that and VS stopped giving me intellisense when I tried. I might need some handholding to get that moved properly. (It could just be my machine being annoying) |
||||
{ | ||||
if (File.Exists(entryPath)) | ||||
{ | ||||
using var stream = File.Open(entryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); | ||||
var entry = zip.CreateEntry(new FileInfo(entryPath).Name); | ||||
using var entryStream = entry.Open(); | ||||
stream.CopyTo(entryStream); | ||||
//zip.CreateEntryFromFile(file.FullName, file.Name); | ||||
} | ||||
// directory handling solution based on answer from https://stackoverflow.com/a/62797701 | ||||
else if (Directory.Exists(entryPath)) | ||||
{ | ||||
var dir = new DirectoryInfo(entryPath); | ||||
var folders = new Stack<string>(); | ||||
folders.Push(entryPath); | ||||
|
||||
do | ||||
{ | ||||
var currentFolder = folders.Pop(); | ||||
foreach (var filename in Directory.GetFiles(currentFolder)) | ||||
{ | ||||
using var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); | ||||
var entry = zip.CreateEntry($"{dir.Name}\\{filename.Substring(entryPath.Length + 1)}"); | ||||
using var entryStream = entry.Open(); | ||||
stream.CopyTo(entryStream); | ||||
//zip.CreateEntryFromFile(filename, $"{dir.Name}\\{filename.Substring(entryPath.Length + 1)}"); | ||||
} | ||||
foreach (var dirname in Directory.GetDirectories(currentFolder)) | ||||
{ | ||||
folders.Push(dirname); | ||||
} | ||||
} while (folders.Count > 0); | ||||
} | ||||
} | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using System; | ||
using System; | ||
using System.Diagnostics; | ||
using System.Globalization; | ||
using System.IO; | ||
using System.Linq; | ||
|
@@ -59,6 +60,12 @@ public class CmdLineOptions | |
[CommandLine.Option("clientlang", Required = false, HelpText = "Client language to use.")] | ||
public ClientLanguage? ClientLanguage { get; set; } | ||
|
||
[CommandLine.Option("exportconfig", Required = false, HelpText = "Export a zip of all config")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these necessary? What's your use case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not strictly needed. I just like having command args for any of the file-based features. Makes it easy for testing when I want to specify custom appdata paths to certify that it unpacks correctly. |
||
public bool DoExportConfig { get; set; } | ||
|
||
[CommandLine.Option("importconfig", Required = false, HelpText = "Import a zip of all config")] | ||
public string DoImportConfig { get; set; } | ||
|
||
// We don't care about these, just need it so that the parser doesn't error | ||
[CommandLine.Option("squirrel-updated", Hidden = true)] | ||
public string SquirrelUpdated { get; set; } | ||
|
@@ -251,6 +258,49 @@ private static void GenerateLocalizables() | |
Environment.Exit(0); | ||
} | ||
|
||
private static void ExportConfig() | ||
{ | ||
try | ||
{ | ||
var filename= DalamudAndPluginConfigImportExport.ExportConfig(Paths.RoamingPath); | ||
|
||
MessageBox.Show($"Exported config as {filename}.\n\nXIVLauncher will now exit.", "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Asterisk); | ||
Process.Start("explorer.exe", $"/select, \"{filename}\""); | ||
} | ||
catch (Exception ex) | ||
{ | ||
MessageBox.Show(ex.ToString()); | ||
} | ||
|
||
Environment.Exit(0); | ||
} | ||
|
||
private static void ImportConfig(string zipFile) | ||
{ | ||
try | ||
{ | ||
var importPromptResult = MessageBox.Show($"XIVLauncher is going to import config from {zipFile}. " | ||
+ "This will overwrite any files that already exist.", "XIVLauncher", | ||
MessageBoxButton.OKCancel, MessageBoxImage.Asterisk); | ||
|
||
if (importPromptResult == MessageBoxResult.OK) | ||
{ | ||
DalamudAndPluginConfigImportExport.ImportConfig(zipFile, Paths.RoamingPath); | ||
|
||
MessageBox.Show($"Imported config.\n\nXIVLauncher will now exit. Please re-run XIVLauncher.", "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Asterisk); | ||
Environment.Exit(0); | ||
} | ||
|
||
} | ||
catch (Exception ex) | ||
{ | ||
MessageBox.Show(ex.ToString()); | ||
Environment.Exit(0); | ||
} | ||
|
||
|
||
} | ||
|
||
private bool _useFullExceptionHandler = false; | ||
|
||
private void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) | ||
|
@@ -355,6 +405,16 @@ private void App_OnStartup(object sender, StartupEventArgs e) | |
{ | ||
GenerateLocalizables(); | ||
} | ||
|
||
if (CommandLine.DoExportConfig) | ||
{ | ||
ExportConfig(); | ||
} | ||
|
||
if (!string.IsNullOrEmpty(CommandLine.DoImportConfig)) | ||
{ | ||
ImportConfig(CommandLine.DoImportConfig); | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably better to go through the entries directly. Then you can also choose to delete the file if it exists - which is probably what people expect when importing a backup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial vision was to just overwrite from the backup, but ideally yes, a "do you want to replace this file, yes, yes to all, no, cancel" style prompt would be better. My worry here is some plugins generate TONS of files in their config directories and most users are not going to want to click yes to that every time. (ex: CharacterSync making backups of all game config for multiple characters for multiple days/launches)
This was more of a companion to the official launcher's FEA file solution, but for our config. (Although nothing prevents this from later handling game config too if we wanted to do that)