diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 15b738e4..711164dd 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -26,6 +26,7 @@ public partial class Configuration : IPluginConfiguration public bool EnableMods { get; set; } = true; public string ModDirectory { get; set; } = string.Empty; + public string ExportDirectory { get; set; } = string.Empty; public bool HideUiInGPose { get; set; } = false; public bool HideUiInCutscenes { get; set; } = true; diff --git a/Penumbra/Mods/Editor/ModBackup.cs b/Penumbra/Mods/Editor/ModBackup.cs index 48bbc4fa..d554ee0e 100644 --- a/Penumbra/Mods/Editor/ModBackup.cs +++ b/Penumbra/Mods/Editor/ModBackup.cs @@ -17,12 +17,12 @@ public class ModBackup public ModBackup( Mod mod ) { _mod = mod; - Name = _mod.ModPath + ".pmp"; + Name = Path.Combine( Penumbra.ModManager.ExportDirectory.FullName, _mod.ModPath.Name ) + ".pmp"; Exists = File.Exists( Name ); } // Migrate file extensions. - public static void MigrateZipToPmp(Mod.Manager manager) + public static void MigrateZipToPmp( Mod.Manager manager ) { foreach( var mod in manager ) { @@ -40,16 +40,39 @@ public class ModBackup { File.Delete( zipName ); } - Penumbra.Log.Information( $"Migrated mod backup from {zipName} to {pmpName}." ); + + Penumbra.Log.Information( $"Migrated mod export from {zipName} to {pmpName}." ); } catch( Exception e ) { - Penumbra.Log.Warning( $"Could not migrate mod backup of {mod.ModPath} from .pmp to .zip:\n{e}" ); + Penumbra.Log.Warning( $"Could not migrate mod export of {mod.ModPath} from .pmp to .zip:\n{e}" ); } } } } + // Move and/or rename an exported mod. + // This object is unusable afterwards. + public void Move( string? newBasePath = null, string? newName = null ) + { + if( CreatingBackup || !Exists ) + { + return; + } + + try + { + newBasePath ??= Path.GetDirectoryName( Name ) ?? string.Empty; + newName = newName == null ? Path.GetFileName( Name ) : newName + ".pmp"; + var newPath = Path.Combine( newBasePath, newName ); + File.Move( Name, newPath ); + } + catch( Exception e ) + { + Penumbra.Log.Warning( $"Could not move mod export file {Name}:\n{e}" ); + } + } + // Create a backup zip without blocking the main thread. public async void CreateAsync() { @@ -71,11 +94,11 @@ public class ModBackup { Delete(); ZipFile.CreateFromDirectory( _mod.ModPath.FullName, Name, CompressionLevel.Optimal, false ); - Penumbra.Log.Debug( $"Created backup file {Name} from {_mod.ModPath.FullName}."); + Penumbra.Log.Debug( $"Created export file {Name} from {_mod.ModPath.FullName}." ); } catch( Exception e ) { - Penumbra.Log.Error( $"Could not backup mod {_mod.Name} to \"{Name}\":\n{e}" ); + Penumbra.Log.Error( $"Could not export mod {_mod.Name} to \"{Name}\":\n{e}" ); } } @@ -90,7 +113,7 @@ public class ModBackup try { File.Delete( Name ); - Penumbra.Log.Debug( $"Deleted backup file {Name}." ); + Penumbra.Log.Debug( $"Deleted export file {Name}." ); } catch( Exception e ) { @@ -111,12 +134,12 @@ public class ModBackup } ZipFile.ExtractToDirectory( Name, _mod.ModPath.FullName ); - Penumbra.Log.Debug( $"Extracted backup file {Name} to {_mod.ModPath.FullName}."); + Penumbra.Log.Debug( $"Extracted exported file {Name} to {_mod.ModPath.FullName}." ); Penumbra.ModManager.ReloadMod( _mod.Index ); } catch( Exception e ) { - Penumbra.Log.Error( $"Could not restore {_mod.Name} from backup \"{Name}\":\n{e}" ); + Penumbra.Log.Error( $"Could not restore {_mod.Name} from export \"{Name}\":\n{e}" ); } } } \ No newline at end of file diff --git a/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs b/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs index a169cecc..99ab7aaa 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs @@ -58,6 +58,8 @@ public partial class Mod return; } + new ModBackup( mod ).Move( null, dir.Name ); + dir.Refresh(); mod.ModPath = dir; if( !mod.Reload( false, out var metaChange ) ) @@ -109,7 +111,7 @@ public partial class Mod try { Directory.Delete( mod.ModPath.FullName, true ); - Penumbra.Log.Debug( $"Deleted directory {mod.ModPath.FullName} for {mod.Name}."); + Penumbra.Log.Debug( $"Deleted directory {mod.ModPath.FullName} for {mod.Name}." ); } catch( Exception e ) { diff --git a/Penumbra/Mods/Manager/Mod.Manager.Root.cs b/Penumbra/Mods/Manager/Mod.Manager.Root.cs index 7ad2140f..38c2fae7 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.Root.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.Root.cs @@ -1,3 +1,4 @@ +using ImGuizmoNET; using System; using System.IO; @@ -8,6 +9,11 @@ public sealed partial class Mod public sealed partial class Manager { public DirectoryInfo BasePath { get; private set; } = null!; + private DirectoryInfo? _exportDirectory; + + public DirectoryInfo ExportDirectory + => _exportDirectory ?? BasePath; + public bool Valid { get; private set; } public event Action? ModDiscoveryStarted; @@ -103,5 +109,49 @@ public sealed partial class Mod ModBackup.MigrateZipToPmp( this ); } } + + public void UpdateExportDirectory( string newDirectory ) + { + if( newDirectory.Length == 0 ) + { + if( _exportDirectory == null ) + { + return; + } + + _exportDirectory = null; + Penumbra.Config.ExportDirectory = string.Empty; + Penumbra.Config.Save(); + return; + } + + var dir = new DirectoryInfo( newDirectory ); + if( dir.FullName.Equals( _exportDirectory?.FullName, StringComparison.OrdinalIgnoreCase ) ) + { + return; + } + + if( !dir.Exists ) + { + try + { + Directory.CreateDirectory( dir.FullName ); + } + catch( Exception e ) + { + Penumbra.Log.Error( $"Could not create Export Directory:\n{e}" ); + return; + } + } + + foreach( var mod in _mods ) + { + new ModBackup( mod ).Move( dir.FullName ); + } + + _exportDirectory = dir; + Penumbra.Config.ExportDirectory = dir.FullName; + Penumbra.Config.Save(); + } } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModPanel.Edit.cs b/Penumbra/UI/ConfigWindow.ModPanel.Edit.cs index dcd7ef82..e3113bcb 100644 --- a/Penumbra/UI/ConfigWindow.ModPanel.Edit.cs +++ b/Penumbra/UI/ConfigWindow.ModPanel.Edit.cs @@ -119,29 +119,29 @@ public partial class ConfigWindow { var backup = new ModBackup( _mod ); var tt = ModBackup.CreatingBackup - ? "Already creating a backup." + ? "Already exporting a mod." : backup.Exists - ? $"Overwrite current backup \"{backup.Name}\" with current mod." - : $"Create backup archive of current mod at \"{backup.Name}\"."; - if( ImGuiUtil.DrawDisabledButton( "Create Backup", buttonSize, tt, ModBackup.CreatingBackup ) ) + ? $"Overwrite current exported mod \"{backup.Name}\" with current mod." + : $"Create exported archive of current mod at \"{backup.Name}\"."; + if( ImGuiUtil.DrawDisabledButton( "Export Mod", buttonSize, tt, ModBackup.CreatingBackup ) ) { backup.CreateAsync(); } ImGui.SameLine(); tt = backup.Exists - ? $"Delete existing backup file \"{backup.Name}\"." - : $"Backup file \"{backup.Name}\" does not exist."; - if( ImGuiUtil.DrawDisabledButton( "Delete Backup", buttonSize, tt, !backup.Exists ) ) + ? $"Delete existing mod export \"{backup.Name}\"." + : $"Exported mod \"{backup.Name}\" does not exist."; + if( ImGuiUtil.DrawDisabledButton( "Delete Export", buttonSize, tt, !backup.Exists ) ) { backup.Delete(); } tt = backup.Exists - ? $"Restore mod from backup file \"{backup.Name}\"." - : $"Backup file \"{backup.Name}\" does not exist."; + ? $"Restore mod from exported file \"{backup.Name}\"." + : $"Exported mod \"{backup.Name}\" does not exist."; ImGui.SameLine(); - if( ImGuiUtil.DrawDisabledButton( "Restore From Backup", buttonSize, tt, !backup.Exists ) ) + if( ImGuiUtil.DrawDisabledButton( "Restore From Export", buttonSize, tt, !backup.Exists ) ) { backup.Restore(); } diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.General.cs b/Penumbra/UI/ConfigWindow.SettingsTab.General.cs index 1e0e0c50..cc46d52a 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.General.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.General.cs @@ -111,6 +111,7 @@ public partial class ConfigWindow DrawDefaultModImportPath(); DrawDefaultModAuthor(); DrawDefaultModImportFolder(); + DrawDefaultModExportPath(); ImGui.NewLine(); } @@ -228,6 +229,56 @@ public partial class ConfigWindow "Set the directory that gets opened when using the file picker to import mods for the first time." ); } + private string _tempExportDirectory = string.Empty; + + private void DrawDefaultModExportPath() + { + var tmp = Penumbra.Config.ExportDirectory; + var spacing = new Vector2( 3 * ImGuiHelpers.GlobalScale ); + using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, spacing ); + ImGui.SetNextItemWidth( _window._inputTextWidth.X - _window._iconButtonSize.X - spacing.X ); + if( ImGui.InputText( "##defaultModExport", ref tmp, 256 ) ) + { + _tempExportDirectory = tmp; + } + + if( ImGui.IsItemDeactivatedAfterEdit() ) + { + Penumbra.ModManager.UpdateExportDirectory( _tempExportDirectory ); + } + + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( $"{FontAwesomeIcon.Folder.ToIconString()}##export", _window._iconButtonSize, + "Select a directory via dialog.", false, true ) ) + { + if( _dialogOpen ) + { + _dialogManager.Reset(); + _dialogOpen = false; + } + else + { + var startDir = Penumbra.Config.ExportDirectory.Length > 0 && Directory.Exists( Penumbra.Config.ExportDirectory ) + ? Penumbra.Config.ExportDirectory + : Directory.Exists( Penumbra.Config.ModDirectory ) + ? Penumbra.Config.ModDirectory + : "."; + + _dialogManager.OpenFolderDialog( "Choose Default Export Directory", ( b, s ) => + { + Penumbra.ModManager.UpdateExportDirectory( b ? s : Penumbra.Config.ExportDirectory ); + _dialogOpen = false; + }, startDir ); + _dialogOpen = true; + } + } + + style.Pop(); + ImGuiUtil.LabeledHelpMarker( "Default Mod Export Directory", + "Set the directory mods get saved to when using the export function or loaded from when reimporting backups.\n" + + "Keep this empty to use the root directory." ); + } + private void DrawDefaultModAuthor() { var tmp = Penumbra.Config.DefaultModAuthor;