From 6df82fdf18cd3e36a6f732adb4a70f6e7c2e46c3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 24 Feb 2022 14:02:45 +0100 Subject: [PATCH] Make recapitalizing internal mod folders possible without changing behaviour otherwise. --- Penumbra/Mods/ModFileSystem.cs | 429 +++++++++--------- Penumbra/Mods/ModFolder.cs | 12 +- .../TabInstalled/TabInstalledModPanel.cs | 6 +- 3 files changed, 227 insertions(+), 220 deletions(-) diff --git a/Penumbra/Mods/ModFileSystem.cs b/Penumbra/Mods/ModFileSystem.cs index 0a0dc54f..b0d01bd7 100644 --- a/Penumbra/Mods/ModFileSystem.cs +++ b/Penumbra/Mods/ModFileSystem.cs @@ -1,260 +1,261 @@ using System; using System.Linq; using Penumbra.Mod; -using Penumbra.Util; -namespace Penumbra.Mods +namespace Penumbra.Mods; + +public delegate void OnModFileSystemChange(); + +public static partial class ModFileSystem { - public delegate void OnModFileSystemChange(); + // The root folder that should be used as the base for all structured mods. + public static ModFolder Root = ModFolder.CreateRoot(); - public static partial class ModFileSystem + // Gets invoked every time the file system changes. + public static event OnModFileSystemChange? ModFileSystemChanged; + + internal static void InvokeChange() + => ModFileSystemChanged?.Invoke(); + + // Find a specific mod folder by its path from Root. + // Returns true if the folder was found, and false if not. + // The out parameter will contain the furthest existing folder. + public static bool Find( string path, out ModFolder folder ) { - // The root folder that should be used as the base for all structured mods. - public static ModFolder Root = ModFolder.CreateRoot(); - - // Gets invoked every time the file system changes. - public static event OnModFileSystemChange? ModFileSystemChanged; - - internal static void InvokeChange() - => ModFileSystemChanged?.Invoke(); - - // Find a specific mod folder by its path from Root. - // Returns true if the folder was found, and false if not. - // The out parameter will contain the furthest existing folder. - public static bool Find( string path, out ModFolder folder ) + var split = path.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries ); + folder = Root; + foreach( var part in split ) { - var split = path.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries ); - folder = Root; - foreach( var part in split ) + if( !folder.FindSubFolder( part, out folder ) ) { - if( !folder.FindSubFolder( part, out folder ) ) - { - return false; - } + return false; } + } + return true; + } + + // Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes. + // Saves and returns true if anything changed. + public static bool Rename( this ModData mod, string newName ) + { + if( RenameNoSave( mod, newName ) ) + { + SaveMod( mod ); return true; } - // Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes. - // Saves and returns true if anything changed. - public static bool Rename( this ModData mod, string newName ) - { - if( RenameNoSave( mod, newName ) ) - { - SaveMod( mod ); - return true; - } + return false; + } - return false; + // Rename the target folder, merging it and its subfolders if the new name already exists. + // Saves all mods manipulated thus, and returns true if anything changed. + public static bool Rename( this ModFolder target, string newName ) + { + if( RenameNoSave( target, newName ) ) + { + SaveModChildren( target ); + return true; } - // Rename the target folder, merging it and its subfolders if the new name already exists. - // Saves all mods manipulated thus, and returns true if anything changed. - public static bool Rename( this ModFolder target, string newName ) - { - if( RenameNoSave( target, newName ) ) - { - SaveModChildren( target ); - return true; - } + return false; + } - return false; + // Move a single mod to the target folder. + // Returns true and saves if anything changed. + public static bool Move( this ModData mod, ModFolder target ) + { + if( MoveNoSave( mod, target ) ) + { + SaveMod( mod ); + return true; } - // Move a single mod to the target folder. - // Returns true and saves if anything changed. - public static bool Move( this ModData mod, ModFolder target ) - { - if( MoveNoSave( mod, target ) ) - { - SaveMod( mod ); - return true; - } + return false; + } - return false; + // Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName. + // Creates all necessary Subfolders. + public static void Move( this ModData mod, string sortOrder ) + { + var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries ); + var folder = Root; + for( var i = 0; i < split.Length - 1; ++i ) + { + folder = folder.FindOrCreateSubFolder( split[ i ] ).Item1; } - // Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName. - // Creates all necessary Subfolders. - public static void Move( this ModData mod, string sortOrder ) + if( MoveNoSave( mod, folder ) | RenameNoSave( mod, split.Last() ) ) { - var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries ); - var folder = Root; - for( var i = 0; i < split.Length - 1; ++i ) - { - folder = folder.FindOrCreateSubFolder( split[ i ] ).Item1; - } - - if( MoveNoSave( mod, folder ) | RenameNoSave( mod, split.Last() ) ) - { - SaveMod( mod ); - } - } - - // Moves folder to target. - // If an identically named subfolder of target already exists, merges instead. - // Root is not movable. - public static bool Move( this ModFolder folder, ModFolder target ) - { - if( MoveNoSave( folder, target ) ) - { - SaveModChildren( target ); - return true; - } - - return false; - } - - // Merge source with target, moving all direct mod children of source to target, - // and moving all subfolders of source to target, or merging them with targets subfolders if they exist. - // Returns true and saves if anything changed. - public static bool Merge( this ModFolder source, ModFolder target ) - { - if( MergeNoSave( source, target ) ) - { - SaveModChildren( target ); - return true; - } - - return false; + SaveMod( mod ); } } - // Internal stuff. - public static partial class ModFileSystem + // Moves folder to target. + // If an identically named subfolder of target already exists, merges instead. + // Root is not movable. + public static bool Move( this ModFolder folder, ModFolder target ) { - // Reset all sort orders for all descendants of the given folder. - // Assumes that it is not called on Root, and thus does not remove unnecessary SortOrder entries. - private static void SaveModChildren( ModFolder target ) + if( MoveNoSave( folder, target ) ) { - foreach( var mod in target.AllMods( true ) ) - { - Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; - } - - Penumbra.Config.Save(); - InvokeChange(); - } - - // Sets and saves the sort order of a single mod, removing the entry if it is unnecessary. - private static void SaveMod( ModData mod ) - { - if( ReferenceEquals( mod.SortOrder.ParentFolder, Root ) - && string.Equals( mod.SortOrder.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) ) - { - Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name ); - } - else - { - Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; - } - - Penumbra.Config.Save(); - InvokeChange(); - } - - private static bool RenameNoSave( this ModFolder target, string newName ) - { - if( ReferenceEquals( target, Root ) ) - { - throw new InvalidOperationException( "Can not rename root." ); - } - - newName = newName.Replace( '/', '\\' ); - if( target.Name == newName ) - { - return false; - } - - if( target.Parent!.FindSubFolder( newName, out var preExisting ) ) - { - MergeNoSave( target, preExisting ); - } - else - { - var parent = target.Parent; - parent.RemoveFolderIgnoreEmpty( target ); - target.Name = newName; - parent.FindOrAddSubFolder( target ); - } - + SaveModChildren( target ); return true; } - private static bool RenameNoSave( ModData mod, string newName ) - { - newName = newName.Replace( '/', '\\' ); - if( mod.SortOrder.SortOrderName == newName ) - { - return false; - } + return false; + } - mod.SortOrder.ParentFolder.RemoveModIgnoreEmpty( mod ); - mod.SortOrder = new SortOrder( mod.SortOrder.ParentFolder, newName ); - mod.SortOrder.ParentFolder.AddMod( mod ); + // Merge source with target, moving all direct mod children of source to target, + // and moving all subfolders of source to target, or merging them with targets subfolders if they exist. + // Returns true and saves if anything changed. + public static bool Merge( this ModFolder source, ModFolder target ) + { + if( MergeNoSave( source, target ) ) + { + SaveModChildren( target ); return true; } - private static bool MoveNoSave( ModData mod, ModFolder target ) - { - var oldParent = mod.SortOrder.ParentFolder; - if( ReferenceEquals( target, oldParent ) ) - { - return false; - } + return false; + } +} - oldParent.RemoveMod( mod ); - mod.SortOrder = new SortOrder( target, mod.SortOrder.SortOrderName ); - target.AddMod( mod ); - return true; +// Internal stuff. +public static partial class ModFileSystem +{ + // Reset all sort orders for all descendants of the given folder. + // Assumes that it is not called on Root, and thus does not remove unnecessary SortOrder entries. + private static void SaveModChildren( ModFolder target ) + { + foreach( var mod in target.AllMods( true ) ) + { + Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; } - private static bool MergeNoSave( ModFolder source, ModFolder target ) + Penumbra.Config.Save(); + InvokeChange(); + } + + // Sets and saves the sort order of a single mod, removing the entry if it is unnecessary. + private static void SaveMod( ModData mod ) + { + if( ReferenceEquals( mod.SortOrder.ParentFolder, Root ) + && string.Equals( mod.SortOrder.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) ) { - if( ReferenceEquals( source, target ) ) - { - return false; - } - - var any = false; - while( source.SubFolders.Count > 0 ) - { - any |= MoveNoSave( source.SubFolders.First(), target ); - } - - while( source.Mods.Count > 0 ) - { - any |= MoveNoSave( source.Mods.First(), target ); - } - - source.Parent?.RemoveSubFolder( source ); - - return any || source.Parent != null; + Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name ); + } + else + { + Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; } - private static bool MoveNoSave( ModFolder folder, ModFolder target ) + Penumbra.Config.Save(); + InvokeChange(); + } + + private static bool RenameNoSave( this ModFolder target, string newName ) + { + if( ReferenceEquals( target, Root ) ) { - // Moving a folder into itself is not permitted. - if( ReferenceEquals( folder, target ) ) - { - return false; - } - - if( ReferenceEquals( target, folder.Parent! ) ) - { - return false; - } - - folder.Parent!.RemoveSubFolder( folder ); - var subFolderIdx = target.FindOrAddSubFolder( folder ); - if( subFolderIdx > 0 ) - { - var main = target.SubFolders[ subFolderIdx ]; - MergeNoSave( folder, main ); - } - - return true; + throw new InvalidOperationException( "Can not rename root." ); } + + newName = newName.Replace( '/', '\\' ); + if( target.Name == newName ) + { + return false; + } + + ModFolder.FolderComparer.CompareType = StringComparison.InvariantCulture; + if( target.Parent!.FindSubFolder( newName, out var preExisting ) ) + { + MergeNoSave( target, preExisting ); + ModFolder.FolderComparer.CompareType = StringComparison.InvariantCultureIgnoreCase; + } + else + { + ModFolder.FolderComparer.CompareType = StringComparison.InvariantCultureIgnoreCase; + var parent = target.Parent; + parent.RemoveFolderIgnoreEmpty( target ); + target.Name = newName; + parent.FindOrAddSubFolder( target ); + } + + return true; + } + + private static bool RenameNoSave( ModData mod, string newName ) + { + newName = newName.Replace( '/', '\\' ); + if( mod.SortOrder.SortOrderName == newName ) + { + return false; + } + + mod.SortOrder.ParentFolder.RemoveModIgnoreEmpty( mod ); + mod.SortOrder = new SortOrder( mod.SortOrder.ParentFolder, newName ); + mod.SortOrder.ParentFolder.AddMod( mod ); + return true; + } + + private static bool MoveNoSave( ModData mod, ModFolder target ) + { + var oldParent = mod.SortOrder.ParentFolder; + if( ReferenceEquals( target, oldParent ) ) + { + return false; + } + + oldParent.RemoveMod( mod ); + mod.SortOrder = new SortOrder( target, mod.SortOrder.SortOrderName ); + target.AddMod( mod ); + return true; + } + + private static bool MergeNoSave( ModFolder source, ModFolder target ) + { + if( ReferenceEquals( source, target ) ) + { + return false; + } + + var any = false; + while( source.SubFolders.Count > 0 ) + { + any |= MoveNoSave( source.SubFolders.First(), target ); + } + + while( source.Mods.Count > 0 ) + { + any |= MoveNoSave( source.Mods.First(), target ); + } + + source.Parent?.RemoveSubFolder( source ); + + return any || source.Parent != null; + } + + private static bool MoveNoSave( ModFolder folder, ModFolder target ) + { + // Moving a folder into itself is not permitted. + if( ReferenceEquals( folder, target ) ) + { + return false; + } + + if( ReferenceEquals( target, folder.Parent! ) ) + { + return false; + } + + folder.Parent!.RemoveSubFolder( folder ); + var subFolderIdx = target.FindOrAddSubFolder( folder ); + if( subFolderIdx > 0 ) + { + var main = target.SubFolders[ subFolderIdx ]; + MergeNoSave( folder, main ); + } + + return true; } } \ No newline at end of file diff --git a/Penumbra/Mods/ModFolder.cs b/Penumbra/Mods/ModFolder.cs index 4b4b6422..76768be0 100644 --- a/Penumbra/Mods/ModFolder.cs +++ b/Penumbra/Mods/ModFolder.cs @@ -148,15 +148,19 @@ namespace Penumbra.Mods internal class ModFolderComparer : IComparer< ModFolder > { + public StringComparison CompareType = StringComparison.InvariantCultureIgnoreCase; + // Compare only the direct folder names since this is only used inside an enumeration of subfolders of one folder. public int Compare( ModFolder? x, ModFolder? y ) => ReferenceEquals( x, y ) ? 0 - : string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, StringComparison.InvariantCultureIgnoreCase ); + : string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, CompareType ); } internal class ModDataComparer : IComparer< ModData > { + public StringComparison CompareType = StringComparison.InvariantCultureIgnoreCase; + // Compare only the direct SortOrderNames since this is only used inside an enumeration of direct mod children of one folder. // Since mod SortOrderNames do not have to be unique inside a folder, also compare their BasePaths (and thus their identity) if necessary. public int Compare( ModData? x, ModData? y ) @@ -166,7 +170,7 @@ namespace Penumbra.Mods return 0; } - var cmp = string.Compare( x?.SortOrder.SortOrderName, y?.SortOrder.SortOrderName, StringComparison.InvariantCultureIgnoreCase ); + var cmp = string.Compare( x?.SortOrder.SortOrderName, y?.SortOrder.SortOrderName, CompareType ); if( cmp != 0 ) { return cmp; @@ -176,8 +180,8 @@ namespace Penumbra.Mods } } - private static readonly ModFolderComparer FolderComparer = new(); - private static readonly ModDataComparer ModComparer = new(); + internal static readonly ModFolderComparer FolderComparer = new(); + internal static readonly ModDataComparer ModComparer = new(); // Get an enumerator for actually sorted objects instead of folder-first objects. private IEnumerable< object > GetSortedEnumerator() diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs index 5fcbf4e0..8be04f51 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs @@ -300,16 +300,18 @@ public partial class SettingsInterface var targetUri = new Uri( newDir.FullName ); if( sourceUri.Equals( targetUri ) ) { - var tmpFolder = new DirectoryInfo(TempFile.TempFileName( dir.Parent! ).FullName); + var tmpFolder = new DirectoryInfo( TempFile.TempFileName( dir.Parent! ).FullName ); if( _modManager.RenameModFolder( Mod.Data, tmpFolder ) ) { if( !_modManager.RenameModFolder( Mod.Data, newDir ) ) { - PluginLog.Error("Could not recapitalize folder after renaming, reverting rename." ); + PluginLog.Error( "Could not recapitalize folder after renaming, reverting rename." ); _modManager.RenameModFolder( Mod.Data, dir ); } + _selector.ReloadCurrentMod(); } + ImGui.CloseCurrentPopup(); } else