diff --git a/OtterGui b/OtterGui index f641a34f..f48c6886 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f641a34ffa80e89bd61701f60f15d15c4c5b361e +Subproject commit f48c6886cbc163c5a292fa8b9fd919cb01c11d7b diff --git a/Penumbra/Mods/Editor/DuplicateManager.cs b/Penumbra/Mods/Editor/DuplicateManager.cs index 77d10cc4..c8530936 100644 --- a/Penumbra/Mods/Editor/DuplicateManager.cs +++ b/Penumbra/Mods/Editor/DuplicateManager.cs @@ -1,3 +1,4 @@ +using OtterGui.Classes; using Penumbra.Mods.Manager; using Penumbra.Mods.Subclasses; using Penumbra.Services; @@ -81,7 +82,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co if (useModManager) { - modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict); + modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict, SaveType.ImmediateSync); } else { @@ -216,18 +217,21 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co } /// Deduplicate a mod simply by its directory without any confirmation or waiting time. - internal void DeduplicateMod(DirectoryInfo modDirectory) + internal void DeduplicateMod(DirectoryInfo modDirectory, bool useModManager = false) { try { - var mod = new Mod(modDirectory); - modManager.Creator.ReloadMod(mod, true, out _); + if (!useModManager || !modManager.TryGetMod(modDirectory.Name, string.Empty, out var mod)) + { + mod = new Mod(modDirectory); + modManager.Creator.ReloadMod(mod, true, out _); + } Clear(); var files = new ModFileCollection(); files.UpdateAll(mod, mod.Default); - CheckDuplicates(files.Available.OrderByDescending(f => f.FileSize).ToArray(), CancellationToken.None); - DeleteDuplicates(files, mod, mod.Default, false); + CheckDuplicates([.. files.Available.OrderByDescending(f => f.FileSize)], CancellationToken.None); + DeleteDuplicates(files, mod, mod.Default, useModManager); } catch (Exception e) { diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index f5d0e4a4..842b1bb3 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -36,7 +36,7 @@ public class ModMerger : IDisposable public readonly HashSet SelectedOptions = []; - public readonly IReadOnlyList Warnings = new List(); + public readonly IReadOnlyList Warnings = []; public Exception? Error { get; private set; } public ModMerger(ModManager mods, ModOptionEditor editor, ModFileSystemSelector selector, DuplicateManager duplicates, @@ -78,7 +78,8 @@ public class ModMerger : IDisposable MergeWithOptions(); else MergeIntoOption(OptionGroupName, OptionName); - _duplicates.DeduplicateMod(MergeToMod.ModPath); + + _duplicates.DeduplicateMod(MergeToMod.ModPath, true); } catch (Exception ex) { @@ -134,10 +135,10 @@ public class ModMerger : IDisposable return; } - var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, GroupType.Multi, groupName); + var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, GroupType.Multi, groupName, SaveType.None); if (groupCreated) _createdGroups.Add(groupIdx); - var (option, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName); + var (option, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None); if (optionCreated) _createdOptions.Add(option); var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport); @@ -156,27 +157,6 @@ public class ModMerger : IDisposable var swaps = option.FileSwapData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); var manips = option.ManipulationData.ToHashSet(); - bool GetFullPath(FullPath input, out FullPath ret) - { - if (fromFileToFile) - { - if (!_fileToFile.TryGetValue(input.FullName, out var s)) - { - ret = input; - return false; - } - - ret = new FullPath(s); - return true; - } - - if (!Utf8RelPath.FromFile(input, MergeFromMod!.ModPath, out var relPath)) - throw new Exception($"Could not create relative path from {input} and {MergeFromMod!.ModPath}."); - - ret = new FullPath(MergeToMod!.ModPath, relPath); - return true; - } - foreach (var originalOption in mergeOptions) { foreach (var manip in originalOption.Manipulations) @@ -204,9 +184,31 @@ public class ModMerger : IDisposable } } - _editor.OptionSetFiles(MergeToMod!, option.GroupIdx, option.OptionIdx, redirections); - _editor.OptionSetFileSwaps(MergeToMod!, option.GroupIdx, option.OptionIdx, swaps); - _editor.OptionSetManipulations(MergeToMod!, option.GroupIdx, option.OptionIdx, manips); + _editor.OptionSetFiles(MergeToMod!, option.GroupIdx, option.OptionIdx, redirections, SaveType.None); + _editor.OptionSetFileSwaps(MergeToMod!, option.GroupIdx, option.OptionIdx, swaps, SaveType.None); + _editor.OptionSetManipulations(MergeToMod!, option.GroupIdx, option.OptionIdx, manips, SaveType.ImmediateSync); + return; + + bool GetFullPath(FullPath input, out FullPath ret) + { + if (fromFileToFile) + { + if (!_fileToFile.TryGetValue(input.FullName, out var s)) + { + ret = input; + return false; + } + + ret = new FullPath(s); + return true; + } + + if (!Utf8RelPath.FromFile(input, MergeFromMod!.ModPath, out var relPath)) + throw new Exception($"Could not create relative path from {input} and {MergeFromMod!.ModPath}."); + + ret = new FullPath(MergeToMod!.ModPath, relPath); + return true; + } } private void CopyFiles(DirectoryInfo directory) diff --git a/Penumbra/Mods/Manager/ModOptionEditor.cs b/Penumbra/Mods/Manager/ModOptionEditor.cs index 3459ce1a..60508d33 100644 --- a/Penumbra/Mods/Manager/ModOptionEditor.cs +++ b/Penumbra/Mods/Manager/ModOptionEditor.cs @@ -78,7 +78,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS } /// Add a new, empty option group of the given type and name. - public void AddModGroup(Mod mod, GroupType type, string newName) + public void AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync) { if (!VerifyFileName(mod, null, newName, true)) return; @@ -96,18 +96,18 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS Name = newName, Priority = maxPriority, }); - saveService.ImmediateSave(new ModSaveGroup(mod, mod.Groups.Count - 1, config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(mod, mod.Groups.Count - 1, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod.Groups.Count - 1, -1, -1); } /// Add a new mod, empty option group of the given type and name if it does not exist already. - public (IModGroup, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string newName) + public (IModGroup, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync) { var idx = mod.Groups.IndexOf(g => g.Name == newName); if (idx >= 0) return (mod.Groups[idx], idx, false); - AddModGroup(mod, type, newName); + AddModGroup(mod, type, newName, saveType); if (mod.Groups[^1].Name != newName) throw new Exception($"Could not create new mod group with name {newName}."); @@ -226,7 +226,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS } /// Add a new empty option of the given name for the given group. - public void AddOption(Mod mod, int groupIdx, string newName) + public void AddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue) { var group = mod.Groups[groupIdx]; var subMod = new SubMod(mod) { Name = newName }; @@ -241,19 +241,19 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS break; } - saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1); } /// Add a new empty option of the given name for the given group if it does not exist already. - public (SubMod, bool) FindOrAddOption(Mod mod, int groupIdx, string newName) + public (SubMod, bool) FindOrAddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue) { var group = mod.Groups[groupIdx]; var idx = group.IndexOf(o => o.Name == newName); if (idx >= 0) return ((SubMod)group[idx], false); - AddOption(mod, groupIdx, newName); + AddOption(mod, groupIdx, newName, saveType); if (group[^1].Name != newName) throw new Exception($"Could not create new option with name {newName} in {group.Name}."); @@ -324,7 +324,8 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS } /// Set the meta manipulations for a given option. Replaces existing manipulations. - public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet manipulations) + public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet manipulations, + SaveType saveType = SaveType.Queue) { var subMod = GetSubMod(mod, groupIdx, optionIdx); if (subMod.Manipulations.Count == manipulations.Count @@ -333,12 +334,13 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1); subMod.ManipulationData.SetTo(manipulations); - saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1); } /// Set the file redirections for a given option. Replaces existing redirections. - public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary replacements) + public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary replacements, + SaveType saveType = SaveType.Queue) { var subMod = GetSubMod(mod, groupIdx, optionIdx); if (subMod.FileData.SetEquals(replacements)) @@ -346,7 +348,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1); subMod.FileData.SetTo(replacements); - saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1); } @@ -364,7 +366,8 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS } /// Set the file swaps for a given option. Replaces existing swaps. - public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary swaps) + public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary swaps, + SaveType saveType = SaveType.Queue) { var subMod = GetSubMod(mod, groupIdx, optionIdx); if (subMod.FileSwapData.SetEquals(swaps)) @@ -372,7 +375,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1); subMod.FileSwapData.SetTo(swaps); - saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1); } diff --git a/Penumbra/Services/CrashHandlerService.cs b/Penumbra/Services/CrashHandlerService.cs index 5423ec15..078b812b 100644 --- a/Penumbra/Services/CrashHandlerService.cs +++ b/Penumbra/Services/CrashHandlerService.cs @@ -213,7 +213,7 @@ public sealed class CrashHandlerService : IDisposable, IService } catch (Exception ex) { - Penumbra.Log.Debug($"Could not delete {dir}:\n{ex}"); + Penumbra.Log.Verbose($"Could not delete {dir}. This is generally not an error:\n{ex}"); } } }