using Dalamud.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Logging; namespace Macrology { [Serializable] public class Configuration : IPluginConfiguration { private Macrology Plugin { get; set; } = null!; public int Version { get; set; } = 1; [JsonProperty] [JsonConverter(typeof(NodeConverter))] public List Nodes { get; private set; } = new(); public int MaxLength { get; set; } = 10_000; internal void Initialise(Macrology plugin) { this.Plugin = plugin; } internal void Save() { var configPath = ConfigPath(this.Plugin); var configText = JsonConvert.SerializeObject(this, Formatting.Indented); File.WriteAllText(configPath, configText); } private static string ConfigPath(Macrology plugin) { return plugin.Interface.ConfigFile.ToString(); } internal static Configuration? Load(Macrology plugin) { var configPath = ConfigPath(plugin); if (!File.Exists(configPath)) { return new Configuration(); } string configText; try { configText = File.ReadAllText(configPath); } catch (IOException e) { PluginLog.Log($"Could not read config at {configPath}: {e.Message}."); return null; } return JsonConvert.DeserializeObject(configText); } private static IEnumerable Traverse(T item, Func> childSelector) { var stack = new Stack(); stack.Push(item); while (stack.Any()) { var next = stack.Pop(); yield return next; foreach (var child in childSelector(next)) { stack.Push(child); } } } public Macro? FindMacro(Guid id) { return this.Nodes.Select(node => (Macro?) Traverse(node, n => n.Children).FirstOrDefault(n => n.Id == id && n is Macro)).FirstOrDefault(macro => macro != null); } } public interface INode { Guid Id { get; set; } string Name { get; set; } List Children { get; } INode Duplicate(); } public class Folder : INode { public Guid Id { get; set; } public string Name { get; set; } public List Children { get; private set; } = new(); public Folder(string name, List? children = null) { this.Id = Guid.NewGuid(); this.Name = name; if (children != null) { this.Children = children; } } internal Folder(Guid id, string name, List children) { this.Id = id; this.Name = name; this.Children = children; } public INode Duplicate() { return new Folder(this.Id, this.Name, this.Children); } } public class Macro : INode { public Guid Id { get; set; } public string Name { get; set; } public string Contents { get; set; } public List Children => new(); public Macro(string name, string contents) { this.Id = Guid.NewGuid(); this.Name = name; this.Contents = contents; } internal Macro(Guid id, string name, string contents) { this.Id = id; this.Name = name; this.Contents = contents; } public INode Duplicate() { return new Macro(this.Id, this.Name, this.Contents); } } // This custom converter is necessary to enable live reloading of the assembly. Without this converter, trying to use type information will fail // when a new version of the assembly is loaded. Instead, don't use type information and just check for the presence of a "Contents" key, then // manually do the deserialisation. It's gross, but it works. public class NodeConverter : JsonConverter { public override bool CanWrite => false; public override bool CanRead => true; public override bool CanConvert(Type objectType) { return objectType == typeof(INode); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { throw new InvalidOperationException("Use default serialization."); } public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var jsonArray = JArray.Load(reader); var list = new List(); foreach (var token in jsonArray) { var jsonObject = (JObject) token; INode node; if (jsonObject.ContainsKey("Contents")) { node = new Macro( jsonObject["Id"]!.ToObject(), jsonObject["Name"]!.ToObject()!, jsonObject["Contents"]!.ToObject()! ); } else { node = new Folder( jsonObject["Id"]!.ToObject(), jsonObject["Name"]!.ToObject()!, (List) this.ReadJson(jsonObject["Children"]!.CreateReader(), typeof(List), null, serializer) ); } list.Add(node); } return list; } } }