diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index b48128e0..104a2079 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -1253,7 +1253,7 @@ public class IpcTester : IDisposable .FirstOrDefault() ?? "Unknown"; if (ImGui.Button($"Save##{collection.Name}")) - Mod.TemporaryMod.SaveTempCollection(_modManager, collection, character); + TemporaryMod.SaveTempCollection(_modManager, collection, character); ImGuiUtil.DrawTableColumn(collection.Name); ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString()); @@ -1271,7 +1271,7 @@ public class IpcTester : IDisposable using var table = ImRaii.Table("##modTree", 5); - void PrintList(string collectionName, IReadOnlyList list) + void PrintList(string collectionName, IReadOnlyList list) { foreach (var mod in list) { diff --git a/Penumbra/Api/TempCollectionManager.cs b/Penumbra/Api/TempCollectionManager.cs index 18a7e43f..7ed67f53 100644 --- a/Penumbra/Api/TempCollectionManager.cs +++ b/Penumbra/Api/TempCollectionManager.cs @@ -31,7 +31,7 @@ public class TempCollectionManager : IDisposable _communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange; } - private void OnGlobalModChange(Mod.TemporaryMod mod, bool created, bool removed) + private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed) => TempModManager.OnGlobalModChange(_customCollections.Values, mod, created, removed); public int Count diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs index 09625a73..07e65c36 100644 --- a/Penumbra/Api/TempModManager.cs +++ b/Penumbra/Api/TempModManager.cs @@ -20,8 +20,8 @@ public class TempModManager : IDisposable { private readonly CommunicatorService _communicator; - private readonly Dictionary> _mods = new(); - private readonly List _modsForAllCollections = new(); + private readonly Dictionary> _mods = new(); + private readonly List _modsForAllCollections = new(); public TempModManager(CommunicatorService communicator) { @@ -34,10 +34,10 @@ public class TempModManager : IDisposable _communicator.CollectionChange.Event -= OnCollectionChange; } - public IReadOnlyDictionary> Mods + public IReadOnlyDictionary> Mods => _mods; - public IReadOnlyList ModsForAllCollections + public IReadOnlyList ModsForAllCollections => _modsForAllCollections; public RedirectResult Register(string tag, ModCollection? collection, Dictionary dict, @@ -74,7 +74,7 @@ public class TempModManager : IDisposable } // Apply any new changes to the temporary mod. - private void ApplyModChange(Mod.TemporaryMod mod, ModCollection? collection, bool created, bool removed) + private void ApplyModChange(TemporaryMod mod, ModCollection? collection, bool created, bool removed) { if (collection != null) { @@ -92,7 +92,7 @@ public class TempModManager : IDisposable /// /// Apply a mod change to a set of collections. /// - public static void OnGlobalModChange(IEnumerable collections, Mod.TemporaryMod mod, bool created, bool removed) + public static void OnGlobalModChange(IEnumerable collections, TemporaryMod mod, bool created, bool removed) { if (removed) foreach (var c in collections) @@ -104,9 +104,9 @@ public class TempModManager : IDisposable // Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections). // Returns the found or created mod and whether it was newly created. - private Mod.TemporaryMod GetOrCreateMod(string tag, ModCollection? collection, int priority, out bool created) + private TemporaryMod GetOrCreateMod(string tag, ModCollection? collection, int priority, out bool created) { - List list; + List list; if (collection == null) { list = _modsForAllCollections; @@ -117,14 +117,14 @@ public class TempModManager : IDisposable } else { - list = new List(); + list = new List(); _mods.Add(collection, list); } var mod = list.Find(m => m.Priority == priority && m.Name == tag); if (mod == null) { - mod = new Mod.TemporaryMod() + mod = new TemporaryMod() { Name = tag, Priority = priority, diff --git a/Penumbra/Collections/CollectionManager.cs b/Penumbra/Collections/CollectionManager.cs index 0e3fb35a..4b883dd3 100644 --- a/Penumbra/Collections/CollectionManager.cs +++ b/Penumbra/Collections/CollectionManager.cs @@ -92,7 +92,7 @@ public partial class ModCollection _modManager.ModPathChanged -= OnModPathChange; } - private void OnGlobalModChange(Mod.TemporaryMod mod, bool created, bool removed) + private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed) => TempModManager.OnGlobalModChange(_collections, mod, created, removed); // Returns true if the name is not empty, it is not the name of the empty collection diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 4ebcca8f..37bfafd5 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -44,7 +44,7 @@ public partial class ModCollection => CalculateEffectiveFileList(this == Penumbra.CollectionManager.Default); // Handle temporary mods for this collection. - public void Apply(Mod.TemporaryMod tempMod, bool created) + public void Apply(TemporaryMod tempMod, bool created) { if (created) _cache?.AddMod(tempMod, tempMod.TotalManipulations > 0); @@ -52,7 +52,7 @@ public partial class ModCollection _cache?.ReloadMod(tempMod, tempMod.TotalManipulations > 0); } - public void Remove(Mod.TemporaryMod tempMod) + public void Remove(TemporaryMod tempMod) { _cache?.RemoveMod(tempMod, tempMod.TotalManipulations > 0); } diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 285d119a..cd148344 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -192,7 +192,7 @@ public partial class ModCollection // Add all forced redirects. foreach( var tempMod in Penumbra.TempMods.ModsForAllCollections.Concat( - Penumbra.TempMods.Mods.TryGetValue( _collection, out var list ) ? list : Array.Empty< Mod.TemporaryMod >() ) ) + Penumbra.TempMods.Mods.TryGetValue( _collection, out var list ) ? list : Array.Empty< TemporaryMod >() ) ) { AddMod( tempMod, false ); } diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index 7b74f83a..27253ff1 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -39,7 +39,7 @@ public class ModDataEditor mod.Description = description ?? mod.Description; mod.Version = version ?? mod.Version; mod.Website = website ?? mod.Website; - _saveService.ImmediateSave(new ModMeta(mod)); + _saveService.ImmediateSave(new Mod.ModMeta(mod)); } public ModDataChangeType LoadLocalData(Mod mod) @@ -96,7 +96,7 @@ public class ModDataEditor } if (save) - _saveService.QueueSave(new ModData(mod)); + _saveService.QueueSave(new Mod.ModData(mod)); return changes; } @@ -161,7 +161,7 @@ public class ModDataEditor if (Mod.Migration.Migrate(mod, json)) { changes |= ModDataChangeType.Migration; - _saveService.ImmediateSave(new ModMeta(mod)); + _saveService.ImmediateSave(new Mod.ModMeta(mod)); } } @@ -189,7 +189,7 @@ public class ModDataEditor var oldName = mod.Name; mod.Name = newName; - _saveService.QueueSave(new ModMeta(mod)); + _saveService.QueueSave(new Mod.ModMeta(mod)); _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Name, mod, oldName.Text); } @@ -199,7 +199,7 @@ public class ModDataEditor return; mod.Author = newAuthor; - _saveService.QueueSave(new ModMeta(mod)); + _saveService.QueueSave(new Mod.ModMeta(mod)); _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Author, mod, null); } @@ -209,7 +209,7 @@ public class ModDataEditor return; mod.Description = newDescription; - _saveService.QueueSave(new ModMeta(mod)); + _saveService.QueueSave(new Mod.ModMeta(mod)); _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Description, mod, null); } @@ -219,7 +219,7 @@ public class ModDataEditor return; mod.Version = newVersion; - _saveService.QueueSave(new ModMeta(mod)); + _saveService.QueueSave(new Mod.ModMeta(mod)); _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Version, mod, null); } @@ -229,7 +229,7 @@ public class ModDataEditor return; mod.Website = newWebsite; - _saveService.QueueSave(new ModMeta(mod)); + _saveService.QueueSave(new Mod.ModMeta(mod)); _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null); } @@ -245,7 +245,7 @@ public class ModDataEditor return; mod.Favorite = state; - _saveService.QueueSave(new ModData(mod)); + _saveService.QueueSave(new Mod.ModData(mod)); ; _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null); } @@ -256,7 +256,7 @@ public class ModDataEditor return; mod.Note = newNote; - _saveService.QueueSave(new ModData(mod)); + _saveService.QueueSave(new Mod.ModData(mod)); ; _communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null); } @@ -281,10 +281,10 @@ public class ModDataEditor } if (flags.HasFlag(ModDataChangeType.ModTags)) - _saveService.QueueSave(new ModMeta(mod)); + _saveService.QueueSave(new Mod.ModMeta(mod)); if (flags.HasFlag(ModDataChangeType.LocalTags)) - _saveService.QueueSave(new ModData(mod)); + _saveService.QueueSave(new Mod.ModData(mod)); if (flags != 0) _communicatorService.ModDataChanged.Invoke(flags, mod, null); @@ -306,57 +306,4 @@ public class ModDataEditor Penumbra.Log.Error($"Could not move local data file {oldFile} to {newFile}:\n{e}"); } } - - - private readonly struct ModMeta : ISavable - { - private readonly Mod _mod; - - public ModMeta(Mod mod) - => _mod = mod; - - public string ToFilename(FilenameService fileNames) - => fileNames.ModMetaPath(_mod); - - public void Save(StreamWriter writer) - { - var jObject = new JObject - { - { nameof(Mod.FileVersion), JToken.FromObject(_mod.FileVersion) }, - { nameof(Mod.Name), JToken.FromObject(_mod.Name) }, - { nameof(Mod.Author), JToken.FromObject(_mod.Author) }, - { nameof(Mod.Description), JToken.FromObject(_mod.Description) }, - { nameof(Mod.Version), JToken.FromObject(_mod.Version) }, - { nameof(Mod.Website), JToken.FromObject(_mod.Website) }, - { nameof(Mod.ModTags), JToken.FromObject(_mod.ModTags) }, - }; - using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; - jObject.WriteTo(jWriter); - } - } - - private readonly struct ModData : ISavable - { - private readonly Mod _mod; - - public ModData(Mod mod) - => _mod = mod; - - public string ToFilename(FilenameService fileNames) - => fileNames.LocalDataFile(_mod); - - public void Save(StreamWriter writer) - { - var jObject = new JObject - { - { nameof(Mod.FileVersion), JToken.FromObject(_mod.FileVersion) }, - { nameof(Mod.ImportDate), JToken.FromObject(_mod.ImportDate) }, - { nameof(Mod.LocalTags), JToken.FromObject(_mod.LocalTags) }, - { nameof(Mod.Note), JToken.FromObject(_mod.Note) }, - { nameof(Mod.Favorite), JToken.FromObject(_mod.Favorite) }, - }; - using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; - jObject.WriteTo(jWriter); - } - } } diff --git a/Penumbra/Mods/Mod.LocalData.cs b/Penumbra/Mods/Mod.LocalData.cs index 29e11f3d..71a73f0f 100644 --- a/Penumbra/Mods/Mod.LocalData.cs +++ b/Penumbra/Mods/Mod.LocalData.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Newtonsoft.Json; using Penumbra.Services; +using Penumbra.Util; namespace Penumbra.Mods; @@ -49,4 +51,29 @@ public sealed partial class Mod return type; } + + internal readonly struct ModData : ISavable + { + private readonly Mod _mod; + + public ModData(Mod mod) + => _mod = mod; + + public string ToFilename(FilenameService fileNames) + => fileNames.LocalDataFile(_mod); + + public void Save(StreamWriter writer) + { + var jObject = new JObject + { + { nameof(FileVersion), JToken.FromObject(_mod.FileVersion) }, + { nameof(ImportDate), JToken.FromObject(_mod.ImportDate) }, + { nameof(LocalTags), JToken.FromObject(_mod.LocalTags) }, + { nameof(Note), JToken.FromObject(_mod.Note) }, + { nameof(Favorite), JToken.FromObject(_mod.Favorite) }, + }; + using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; + jObject.WriteTo(jWriter); + } + } } diff --git a/Penumbra/Mods/Mod.Meta.cs b/Penumbra/Mods/Mod.Meta.cs index 16ae4d1f..0e29a12c 100644 --- a/Penumbra/Mods/Mod.Meta.cs +++ b/Penumbra/Mods/Mod.Meta.cs @@ -1,9 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using OtterGui.Classes; +using Penumbra.Services; +using Penumbra.Util; namespace Penumbra.Mods; - + public sealed partial class Mod : IMod { public static readonly TemporaryMod ForcedFiles = new() @@ -13,15 +18,42 @@ public sealed partial class Mod : IMod Priority = int.MaxValue, }; - public const uint CurrentFileVersion = 3; - public uint FileVersion { get; internal set; } = CurrentFileVersion; - public LowerString Name { get; internal set; } = "New Mod"; - public LowerString Author { get; internal set; } = LowerString.Empty; - public string Description { get; internal set; } = string.Empty; - public string Version { get; internal set; } = string.Empty; - public string Website { get; internal set; } = string.Empty; - public IReadOnlyList< string > ModTags { get; internal set; } = Array.Empty< string >(); + public const uint CurrentFileVersion = 3; + public uint FileVersion { get; internal set; } = CurrentFileVersion; + public LowerString Name { get; internal set; } = "New Mod"; + public LowerString Author { get; internal set; } = LowerString.Empty; + public string Description { get; internal set; } = string.Empty; + public string Version { get; internal set; } = string.Empty; + public string Website { get; internal set; } = string.Empty; + public IReadOnlyList ModTags { get; internal set; } = Array.Empty(); public override string ToString() => Name.Text; -} \ No newline at end of file + + internal readonly struct ModMeta : ISavable + { + private readonly Mod _mod; + + public ModMeta(Mod mod) + => _mod = mod; + + public string ToFilename(FilenameService fileNames) + => fileNames.ModMetaPath(_mod); + + public void Save(StreamWriter writer) + { + var jObject = new JObject + { + { nameof(FileVersion), JToken.FromObject(_mod.FileVersion) }, + { nameof(Name), JToken.FromObject(_mod.Name) }, + { nameof(Author), JToken.FromObject(_mod.Author) }, + { nameof(Description), JToken.FromObject(_mod.Description) }, + { nameof(Version), JToken.FromObject(_mod.Version) }, + { nameof(Website), JToken.FromObject(_mod.Website) }, + { nameof(ModTags), JToken.FromObject(_mod.ModTags) }, + }; + using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; + jObject.WriteTo(jWriter); + } + } +} diff --git a/Penumbra/Mods/Mod.TemporaryMod.cs b/Penumbra/Mods/Mod.TemporaryMod.cs deleted file mode 100644 index e686ad0d..00000000 --- a/Penumbra/Mods/Mod.TemporaryMod.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using OtterGui.Classes; -using Penumbra.Collections; -using Penumbra.Meta.Manipulations; -using Penumbra.String.Classes; - -namespace Penumbra.Mods; - -public sealed partial class Mod -{ - public class TemporaryMod : IMod - { - public LowerString Name { get; init; } = LowerString.Empty; - public int Index { get; init; } = -2; - public int Priority { get; init; } = int.MaxValue; - - public int TotalManipulations - => Default.Manipulations.Count; - - public ISubMod Default - => _default; - - public IReadOnlyList< IModGroup > Groups - => Array.Empty< IModGroup >(); - - public IEnumerable< ISubMod > AllSubMods - => new[] { Default }; - - private readonly SubMod _default; - - public TemporaryMod() - => _default = new SubMod( this ); - - public void SetFile( Utf8GamePath gamePath, FullPath fullPath ) - => _default.FileData[ gamePath ] = fullPath; - - public bool SetManipulation( MetaManipulation manip ) - => _default.ManipulationData.Remove( manip ) | _default.ManipulationData.Add( manip ); - - public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips ) - { - _default.FileData = dict; - _default.ManipulationData = manips; - } - - public static void SaveTempCollection( Mod.Manager modManager, ModCollection collection, string? character = null ) - { - DirectoryInfo? dir = null; - try - { - dir = Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name ); - var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) ); - modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor, - $"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null ); - var mod = new Mod( dir ); - var defaultMod = mod._default; - foreach( var (gamePath, fullPath) in collection.ResolvedFiles ) - { - if( gamePath.Path.EndsWith( ".imc"u8 ) ) - { - continue; - } - - var targetPath = fullPath.Path.FullName; - if( fullPath.Path.Name.StartsWith( '|' ) ) - { - targetPath = targetPath.Split( '|', 3, StringSplitOptions.RemoveEmptyEntries ).Last(); - } - - if( Path.IsPathRooted(targetPath) ) - { - var target = Path.Combine( fileDir.FullName, Path.GetFileName(targetPath) ); - File.Copy( targetPath, target, true ); - defaultMod.FileData[ gamePath ] = new FullPath( target ); - } - else - { - defaultMod.FileSwapData[ gamePath ] = new FullPath(targetPath); - } - } - - foreach( var manip in collection.MetaCache?.Manipulations ?? Array.Empty< MetaManipulation >() ) - { - defaultMod.ManipulationData.Add( manip ); - } - - mod.SaveDefaultMod(); - modManager.AddMod( dir ); - Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." ); - } - catch( Exception e ) - { - Penumbra.Log.Error( $"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}" ); - if( dir != null && Directory.Exists( dir.FullName ) ) - { - try - { - Directory.Delete( dir.FullName, true ); - } - catch - { - // ignored - } - } - } - } - } -} \ No newline at end of file diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs new file mode 100644 index 00000000..5bf37c95 --- /dev/null +++ b/Penumbra/Mods/TemporaryMod.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OtterGui.Classes; +using Penumbra.Collections; +using Penumbra.Meta.Manipulations; +using Penumbra.String.Classes; + +namespace Penumbra.Mods; + +public class TemporaryMod : IMod +{ + public LowerString Name { get; init; } = LowerString.Empty; + public int Index { get; init; } = -2; + public int Priority { get; init; } = int.MaxValue; + + public int TotalManipulations + => Default.Manipulations.Count; + + public ISubMod Default + => _default; + + public IReadOnlyList< IModGroup > Groups + => Array.Empty< IModGroup >(); + + public IEnumerable< ISubMod > AllSubMods + => new[] { Default }; + + private readonly Mod.SubMod _default; + + public TemporaryMod() + => _default = new Mod.SubMod( this ); + + public void SetFile( Utf8GamePath gamePath, FullPath fullPath ) + => _default.FileData[ gamePath ] = fullPath; + + public bool SetManipulation( MetaManipulation manip ) + => _default.ManipulationData.Remove( manip ) | _default.ManipulationData.Add( manip ); + + public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips ) + { + _default.FileData = dict; + _default.ManipulationData = manips; + } + + public static void SaveTempCollection( Mod.Manager modManager, ModCollection collection, string? character = null ) + { + DirectoryInfo? dir = null; + try + { + dir = Mod.Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name ); + var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) ); + modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor, + $"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null ); + var mod = new Mod( dir ); + var defaultMod = (Mod.SubMod) mod.Default; + foreach( var (gamePath, fullPath) in collection.ResolvedFiles ) + { + if( gamePath.Path.EndsWith( ".imc"u8 ) ) + { + continue; + } + + var targetPath = fullPath.Path.FullName; + if( fullPath.Path.Name.StartsWith( '|' ) ) + { + targetPath = targetPath.Split( '|', 3, StringSplitOptions.RemoveEmptyEntries ).Last(); + } + + if( Path.IsPathRooted(targetPath) ) + { + var target = Path.Combine( fileDir.FullName, Path.GetFileName(targetPath) ); + File.Copy( targetPath, target, true ); + defaultMod.FileData[ gamePath ] = new FullPath( target ); + } + else + { + defaultMod.FileSwapData[ gamePath ] = new FullPath(targetPath); + } + } + + foreach( var manip in collection.MetaCache?.Manipulations ?? Array.Empty< MetaManipulation >() ) + defaultMod.ManipulationData.Add( manip ); + + mod.SaveDefaultMod(); + modManager.AddMod( dir ); + Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." ); + } + catch( Exception e ) + { + Penumbra.Log.Error( $"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}" ); + if( dir != null && Directory.Exists( dir.FullName ) ) + { + try + { + Directory.Delete( dir.FullName, true ); + } + catch + { + // ignored + } + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Services/CommunicatorService.cs b/Penumbra/Services/CommunicatorService.cs index 3feb5b91..f4381883 100644 --- a/Penumbra/Services/CommunicatorService.cs +++ b/Penumbra/Services/CommunicatorService.cs @@ -20,7 +20,7 @@ public class CommunicatorService : IDisposable /// Parameter is whether the mod was newly created. /// Parameter is whether the mod was deleted. /// - public readonly EventWrapper TemporaryGlobalModChange = new(nameof(TemporaryGlobalModChange)); + public readonly EventWrapper TemporaryGlobalModChange = new(nameof(TemporaryGlobalModChange)); /// /// Parameter is the type of change.