Some cleanup.

This commit is contained in:
Ottermandias 2024-01-13 14:06:45 +01:00
parent 91cea50f02
commit 2d8b7efc00
6 changed files with 78 additions and 91 deletions

View file

@ -1,3 +1,4 @@
using OtterGui.Services;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
using Penumbra.Services; using Penumbra.Services;
@ -5,25 +6,15 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor; namespace Penumbra.Mods.Editor;
public class DuplicateManager public class DuplicateManager(ModManager modManager, SaveService saveService, Configuration config)
{ {
private readonly Configuration _config; private readonly SHA256 _hasher = SHA256.Create();
private readonly SaveService _saveService; private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = [];
private readonly ModManager _modManager;
private readonly SHA256 _hasher = SHA256.Create();
private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = new();
public DuplicateManager(ModManager modManager, SaveService saveService, Configuration config)
{
_modManager = modManager;
_saveService = saveService;
_config = config;
}
public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates
=> _duplicates; => _duplicates;
public long SavedSpace { get; private set; } = 0; public long SavedSpace { get; private set; }
public Task Worker { get; private set; } = Task.CompletedTask; public Task Worker { get; private set; } = Task.CompletedTask;
private CancellationTokenSource _cancellationTokenSource = new(); private CancellationTokenSource _cancellationTokenSource = new();
@ -68,6 +59,19 @@ public class DuplicateManager
private void HandleDuplicate(Mod mod, FullPath duplicate, FullPath remaining, bool useModManager) private void HandleDuplicate(Mod mod, FullPath duplicate, FullPath remaining, bool useModManager)
{ {
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
try
{
File.Delete(duplicate.FullName);
}
catch (Exception e)
{
Penumbra.Log.Error($"[DeleteDuplicates] Could not delete duplicate {duplicate.FullName} of {remaining.FullName}:\n{e}");
}
return;
void HandleSubMod(ISubMod subMod, int groupIdx, int optionIdx) void HandleSubMod(ISubMod subMod, int groupIdx, int optionIdx)
{ {
var changes = false; var changes = false;
@ -78,26 +82,15 @@ public class DuplicateManager
if (useModManager) if (useModManager)
{ {
_modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict); modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
} }
else else
{ {
var sub = (SubMod)subMod; var sub = (SubMod)subMod;
sub.FileData = dict; sub.FileData = dict;
_saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport)); saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
} }
} }
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
try
{
File.Delete(duplicate.FullName);
}
catch (Exception e)
{
Penumbra.Log.Error($"[DeleteDuplicates] Could not delete duplicate {duplicate.FullName} of {remaining.FullName}:\n{e}");
}
} }
private static FullPath ChangeDuplicatePath(Mod mod, FullPath value, FullPath from, FullPath to, Utf8GamePath key, ref bool changes) private static FullPath ChangeDuplicatePath(Mod mod, FullPath value, FullPath from, FullPath to, Utf8GamePath key, ref bool changes)
@ -199,15 +192,6 @@ public class DuplicateManager
} }
} }
public static bool CompareHashes(byte[] f1, byte[] f2)
=> StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
public byte[] ComputeHash(FullPath f)
{
using var stream = File.OpenRead(f.FullName);
return _hasher.ComputeHash(stream);
}
/// <summary> /// <summary>
/// Recursively delete all empty directories starting from the given directory. /// Recursively delete all empty directories starting from the given directory.
/// Deletes inner directories first, so that a tree of empty directories is actually deleted. /// Deletes inner directories first, so that a tree of empty directories is actually deleted.
@ -232,14 +216,13 @@ public class DuplicateManager
} }
} }
/// <summary> Deduplicate a mod simply by its directory without any confirmation or waiting time. </summary> /// <summary> Deduplicate a mod simply by its directory without any confirmation or waiting time. </summary>
internal void DeduplicateMod(DirectoryInfo modDirectory) internal void DeduplicateMod(DirectoryInfo modDirectory)
{ {
try try
{ {
var mod = new Mod(modDirectory); var mod = new Mod(modDirectory);
_modManager.Creator.ReloadMod(mod, true, out _); modManager.Creator.ReloadMod(mod, true, out _);
Clear(); Clear();
var files = new ModFileCollection(); var files = new ModFileCollection();
@ -252,4 +235,13 @@ public class DuplicateManager
Penumbra.Log.Warning($"Could not deduplicate mod {modDirectory.Name}:\n{e}"); Penumbra.Log.Warning($"Could not deduplicate mod {modDirectory.Name}:\n{e}");
} }
} }
private static bool CompareHashes(byte[] f1, byte[] f2)
=> StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
private byte[] ComputeHash(FullPath f)
{
using var stream = File.OpenRead(f.FullName);
return _hasher.ComputeHash(stream);
}
} }

View file

@ -1,11 +1,11 @@
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes; using Penumbra.String.Classes;
namespace Penumbra.Mods; namespace Penumbra.Mods.Editor;
public class FileRegistry : IEquatable<FileRegistry> public class FileRegistry : IEquatable<FileRegistry>
{ {
public readonly List<(ISubMod, Utf8GamePath)> SubModUsage = new(); public readonly List<(ISubMod, Utf8GamePath)> SubModUsage = [];
public FullPath File { get; private init; } public FullPath File { get; private init; }
public Utf8RelPath RelPath { get; private init; } public Utf8RelPath RelPath { get; private init; }
public long FileSize { get; private init; } public long FileSize { get; private init; }

View file

@ -29,12 +29,12 @@ public class ModMerger : IDisposable
public string OptionGroupName = "Merges"; public string OptionGroupName = "Merges";
public string OptionName = string.Empty; public string OptionName = string.Empty;
private readonly Dictionary<string, string> _fileToFile = new(); private readonly Dictionary<string, string> _fileToFile = [];
private readonly HashSet<string> _createdDirectories = new(); private readonly HashSet<string> _createdDirectories = [];
private readonly HashSet<int> _createdGroups = new(); private readonly HashSet<int> _createdGroups = [];
private readonly HashSet<SubMod> _createdOptions = new(); private readonly HashSet<SubMod> _createdOptions = [];
public readonly HashSet<SubMod> SelectedOptions = new(); public readonly HashSet<SubMod> SelectedOptions = [];
public readonly IReadOnlyList<string> Warnings = new List<string>(); public readonly IReadOnlyList<string> Warnings = new List<string>();
public Exception? Error { get; private set; } public Exception? Error { get; private set; }

View file

@ -9,6 +9,7 @@ using OtterGui.Raii;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;

View file

@ -4,6 +4,7 @@ using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;

View file

@ -9,22 +9,14 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.AdvancedWindow; namespace Penumbra.UI.AdvancedWindow;
public class ModMergeTab public class ModMergeTab(ModMerger modMerger)
{ {
private readonly ModMerger _modMerger; private readonly ModCombo _modCombo = new(() => modMerger.ModsWithoutCurrent.ToList());
private readonly ModCombo _modCombo; private string _newModName = string.Empty;
private string _newModName = string.Empty;
public ModMergeTab(ModMerger modMerger)
{
_modMerger = modMerger;
_modCombo = new ModCombo(() => _modMerger.ModsWithoutCurrent.ToList());
}
public void Draw() public void Draw()
{ {
if (_modMerger.MergeFromMod == null) if (modMerger.MergeFromMod == null)
return; return;
using var tab = ImRaii.TabItem("Merge Mods"); using var tab = ImRaii.TabItem("Merge Mods");
@ -54,23 +46,23 @@ public class ModMergeTab
{ {
using var bigGroup = ImRaii.Group(); using var bigGroup = ImRaii.Group();
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted($"Merge {_modMerger.MergeFromMod!.Name} into "); ImGui.TextUnformatted($"Merge {modMerger.MergeFromMod!.Name} into ");
ImGui.SameLine(); ImGui.SameLine();
DrawCombo(size - ImGui.GetItemRectSize().X - ImGui.GetStyle().ItemSpacing.X); DrawCombo(size - ImGui.GetItemRectSize().X - ImGui.GetStyle().ItemSpacing.X);
var width = ImGui.GetItemRectSize(); var width = ImGui.GetItemRectSize();
using (var g = ImRaii.Group()) using (var g = ImRaii.Group())
{ {
using var disabled = ImRaii.Disabled(_modMerger.MergeFromMod.HasOptions); using var disabled = ImRaii.Disabled(modMerger.MergeFromMod.HasOptions);
var buttonWidth = (size - ImGui.GetStyle().ItemSpacing.X) / 2; var buttonWidth = (size - ImGui.GetStyle().ItemSpacing.X) / 2;
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 1); using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 1);
var group = _modMerger.MergeToMod?.Groups.FirstOrDefault(g => g.Name == _modMerger.OptionGroupName); var group = modMerger.MergeToMod?.Groups.FirstOrDefault(g => g.Name == modMerger.OptionGroupName);
var color = group != null || _modMerger.OptionGroupName.Length == 0 && _modMerger.OptionName.Length == 0 var color = group != null || modMerger.OptionGroupName.Length == 0 && modMerger.OptionName.Length == 0
? Colors.PressEnterWarningBg ? Colors.PressEnterWarningBg
: Colors.DiscordColor; : Colors.DiscordColor;
using var c = ImRaii.PushColor(ImGuiCol.Border, color); using var c = ImRaii.PushColor(ImGuiCol.Border, color);
ImGui.SetNextItemWidth(buttonWidth); ImGui.SetNextItemWidth(buttonWidth);
ImGui.InputTextWithHint("##optionGroupInput", "Target Option Group", ref _modMerger.OptionGroupName, 64); ImGui.InputTextWithHint("##optionGroupInput", "Target Option Group", ref modMerger.OptionGroupName, 64);
ImGuiUtil.HoverTooltip( ImGuiUtil.HoverTooltip(
"The name of the new or existing option group to find or create the option in. Leave both group and option name blank for the default option.\n" "The name of the new or existing option group to find or create the option in. Leave both group and option name blank for the default option.\n"
+ "A red border indicates an existing option group, a blue border indicates a new one."); + "A red border indicates an existing option group, a blue border indicates a new one.");
@ -79,29 +71,29 @@ public class ModMergeTab
color = color == Colors.DiscordColor color = color == Colors.DiscordColor
? Colors.DiscordColor ? Colors.DiscordColor
: group == null || group.Any(o => o.Name == _modMerger.OptionName) : group == null || group.Any(o => o.Name == modMerger.OptionName)
? Colors.PressEnterWarningBg ? Colors.PressEnterWarningBg
: Colors.DiscordColor; : Colors.DiscordColor;
c.Push(ImGuiCol.Border, color); c.Push(ImGuiCol.Border, color);
ImGui.SetNextItemWidth(buttonWidth); ImGui.SetNextItemWidth(buttonWidth);
ImGui.InputTextWithHint("##optionInput", "Target Option Name", ref _modMerger.OptionName, 64); ImGui.InputTextWithHint("##optionInput", "Target Option Name", ref modMerger.OptionName, 64);
ImGuiUtil.HoverTooltip( ImGuiUtil.HoverTooltip(
"The name of the new or existing option to merge this mod into. Leave both group and option name blank for the default option.\n" "The name of the new or existing option to merge this mod into. Leave both group and option name blank for the default option.\n"
+ "A red border indicates an existing option, a blue border indicates a new one."); + "A red border indicates an existing option, a blue border indicates a new one.");
} }
if (_modMerger.MergeFromMod.HasOptions) if (modMerger.MergeFromMod.HasOptions)
ImGuiUtil.HoverTooltip("You can only specify a target option if the source mod has no true options itself.", ImGuiUtil.HoverTooltip("You can only specify a target option if the source mod has no true options itself.",
ImGuiHoveredFlags.AllowWhenDisabled); ImGuiHoveredFlags.AllowWhenDisabled);
if (ImGuiUtil.DrawDisabledButton("Merge", new Vector2(size, 0), if (ImGuiUtil.DrawDisabledButton("Merge", new Vector2(size, 0),
_modMerger.CanMerge ? string.Empty : "Please select a target mod different from the current mod.", !_modMerger.CanMerge)) modMerger.CanMerge ? string.Empty : "Please select a target mod different from the current mod.", !modMerger.CanMerge))
_modMerger.Merge(); modMerger.Merge();
} }
private void DrawMergeIntoDesc() private void DrawMergeIntoDesc()
{ {
ImGuiUtil.TextWrapped(_modMerger.MergeFromMod!.HasOptions ImGuiUtil.TextWrapped(modMerger.MergeFromMod!.HasOptions
? "The currently selected mod has options.\n\nThis means, that all of those options will be merged into the target. If merging an option is not possible due to the redirections already existing in an existing option, it will revert all changes and break." ? "The currently selected mod has options.\n\nThis means, that all of those options will be merged into the target. If merging an option is not possible due to the redirections already existing in an existing option, it will revert all changes and break."
: "The currently selected mod has no true options.\n\nThis means that you can select an existing or new option to merge all its changes into in the target mod. On failure to merge into an existing option, all changes will be reverted."); : "The currently selected mod has no true options.\n\nThis means that you can select an existing or new option to merge all its changes into in the target mod. On failure to merge into an existing option, all changes will be reverted.");
} }
@ -110,7 +102,7 @@ public class ModMergeTab
{ {
_modCombo.Draw("##ModSelection", _modCombo.CurrentSelection?.Name.Text ?? "Select the target Mod...", string.Empty, width, _modCombo.Draw("##ModSelection", _modCombo.CurrentSelection?.Name.Text ?? "Select the target Mod...", string.Empty, width,
ImGui.GetTextLineHeight()); ImGui.GetTextLineHeight());
_modMerger.MergeToMod = _modCombo.CurrentSelection; modMerger.MergeToMod = _modCombo.CurrentSelection;
} }
private void DrawSplitOff(float size) private void DrawSplitOff(float size)
@ -121,24 +113,24 @@ public class ModMergeTab
ImGuiUtil.HoverTooltip("Choose a name for the newly created mod. This does not need to be unique."); ImGuiUtil.HoverTooltip("Choose a name for the newly created mod. This does not need to be unique.");
var tt = _newModName.Length == 0 var tt = _newModName.Length == 0
? "Please enter a name for the newly created mod first." ? "Please enter a name for the newly created mod first."
: _modMerger.SelectedOptions.Count == 0 : modMerger.SelectedOptions.Count == 0
? "Please select at least one option to split off." ? "Please select at least one option to split off."
: string.Empty; : string.Empty;
var buttonText = var buttonText =
$"Split Off {_modMerger.SelectedOptions.Count} Option{(_modMerger.SelectedOptions.Count > 1 ? "s" : string.Empty)}###SplitOff"; $"Split Off {modMerger.SelectedOptions.Count} Option{(modMerger.SelectedOptions.Count > 1 ? "s" : string.Empty)}###SplitOff";
if (ImGuiUtil.DrawDisabledButton(buttonText, new Vector2(size, 0), tt, tt.Length > 0)) if (ImGuiUtil.DrawDisabledButton(buttonText, new Vector2(size, 0), tt, tt.Length > 0))
_modMerger.SplitIntoMod(_newModName); modMerger.SplitIntoMod(_newModName);
ImGui.Dummy(Vector2.One); ImGui.Dummy(Vector2.One);
var buttonSize = new Vector2((size - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0); var buttonSize = new Vector2((size - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0);
if (ImGui.Button("Select All", buttonSize)) if (ImGui.Button("Select All", buttonSize))
_modMerger.SelectedOptions.UnionWith(_modMerger.MergeFromMod!.AllSubMods); modMerger.SelectedOptions.UnionWith(modMerger.MergeFromMod!.AllSubMods);
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Unselect All", buttonSize)) if (ImGui.Button("Unselect All", buttonSize))
_modMerger.SelectedOptions.Clear(); modMerger.SelectedOptions.Clear();
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Invert Selection", buttonSize)) if (ImGui.Button("Invert Selection", buttonSize))
_modMerger.SelectedOptions.SymmetricExceptWith(_modMerger.MergeFromMod!.AllSubMods); modMerger.SelectedOptions.SymmetricExceptWith(modMerger.MergeFromMod!.AllSubMods);
DrawOptionTable(size); DrawOptionTable(size);
} }
@ -152,8 +144,8 @@ public class ModMergeTab
private void DrawOptionTable(float size) private void DrawOptionTable(float size)
{ {
var options = _modMerger.MergeFromMod!.AllSubMods.ToList(); var options = modMerger.MergeFromMod!.AllSubMods.ToList();
var height = _modMerger.Warnings.Count == 0 && _modMerger.Error == null var height = modMerger.Warnings.Count == 0 && modMerger.Error == null
? ImGui.GetContentRegionAvail().Y - 3 * ImGui.GetFrameHeightWithSpacing() ? ImGui.GetContentRegionAvail().Y - 3 * ImGui.GetFrameHeightWithSpacing()
: 8 * ImGui.GetFrameHeightWithSpacing(); : 8 * ImGui.GetFrameHeightWithSpacing();
height = Math.Min(height, (options.Count + 1) * ImGui.GetFrameHeightWithSpacing()); height = Math.Min(height, (options.Count + 1) * ImGui.GetFrameHeightWithSpacing());
@ -178,15 +170,7 @@ public class ModMergeTab
foreach (var (option, idx) in options.WithIndex()) foreach (var (option, idx) in options.WithIndex())
{ {
using var id = ImRaii.PushId(idx); using var id = ImRaii.PushId(idx);
var selected = _modMerger.SelectedOptions.Contains(option); var selected = modMerger.SelectedOptions.Contains(option);
void Handle(SubMod option2, bool selected2)
{
if (selected2)
_modMerger.SelectedOptions.Add(option2);
else
_modMerger.SelectedOptions.Remove(option2);
}
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.Checkbox("##check", ref selected)) if (ImGui.Checkbox("##check", ref selected))
@ -222,34 +206,43 @@ public class ModMergeTab
ImGuiUtil.RightAlign(option.FileSwapData.Count.ToString(), 3 * ImGuiHelpers.GlobalScale); ImGuiUtil.RightAlign(option.FileSwapData.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.RightAlign(option.Manipulations.Count.ToString(), 3 * ImGuiHelpers.GlobalScale); ImGuiUtil.RightAlign(option.Manipulations.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
continue;
void Handle(SubMod option2, bool selected2)
{
if (selected2)
modMerger.SelectedOptions.Add(option2);
else
modMerger.SelectedOptions.Remove(option2);
}
} }
} }
private void DrawWarnings() private void DrawWarnings()
{ {
if (_modMerger.Warnings.Count == 0) if (modMerger.Warnings.Count == 0)
return; return;
ImGui.Separator(); ImGui.Separator();
ImGui.Dummy(Vector2.One); ImGui.Dummy(Vector2.One);
using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.TutorialBorder); using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.TutorialBorder);
foreach (var warning in _modMerger.Warnings.SkipLast(1)) foreach (var warning in modMerger.Warnings.SkipLast(1))
{ {
ImGuiUtil.TextWrapped(warning); ImGuiUtil.TextWrapped(warning);
ImGui.Separator(); ImGui.Separator();
} }
ImGuiUtil.TextWrapped(_modMerger.Warnings[^1]); ImGuiUtil.TextWrapped(modMerger.Warnings[^1]);
} }
private void DrawError() private void DrawError()
{ {
if (_modMerger.Error == null) if (modMerger.Error == null)
return; return;
ImGui.Separator(); ImGui.Separator();
ImGui.Dummy(Vector2.One); ImGui.Dummy(Vector2.One);
using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder); using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder);
ImGuiUtil.TextWrapped(_modMerger.Error.ToString()); ImGuiUtil.TextWrapped(modMerger.Error.ToString());
} }
} }