Code commit

This commit is contained in:
RisaDev
2024-01-06 01:21:41 +03:00
parent a7d7297c59
commit a486dd2c96
90 changed files with 11576 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.IO;
using CustomizePlus.Core.Data;
using CustomizePlus.Core.Extensions;
using CustomizePlus.Core.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
namespace CustomizePlus.Templates.Data;
/// <summary>
/// Encapsulates the user-controlled aspects of a template, ie all of
/// the information that gets saved to disk by the plugin.
/// </summary>
public sealed class Template : ISavable
{
public LowerString Name { get; internal set; } = "Template";
public int Version { get; internal set; } = Constants.ConfigurationVersion;
public DateTimeOffset CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTimeOffset ModifiedDate { get; internal set; } = DateTime.UtcNow;
public Guid UniqueId { get; internal set; } = Guid.NewGuid();
public bool IsWriteProtected { get; internal set; }
public string Incognito
=> UniqueId.ToString()[..8];
public Dictionary<string, BoneTransform> Bones { get; init; } = new();
public Template()
{
}
/// <summary>
/// Creates a new template based on bone data from another one
/// </summary>
public Template(Template original) : this()
{
foreach (var kvp in original.Bones)
{
Bones[kvp.Key] = new BoneTransform();
Bones[kvp.Key].UpdateToMatch(kvp.Value);
}
}
public override string ToString()
{
return $"Template '{Name.Text.Incognify()}' with {Bones.Count} bone edits [{UniqueId}]";
}
#region Serialization
public new JObject JsonSerialize()
{
var ret = new JObject()
{
["Version"] = Version,
["UniqueId"] = UniqueId,
["CreationDate"] = CreationDate,
["ModifiedDate"] = ModifiedDate,
["Name"] = Name.Text,
["Bones"] = JObject.FromObject(Bones),
["IsWriteProtected"] = IsWriteProtected
};
return ret;
}
#endregion
#region Deserialization
public static Template Load(JObject obj)
{
var version = obj["Version"]?.ToObject<int>() ?? 0;
return version switch
{
//Did not exist before v4
4 => LoadV4(obj),
_ => throw new Exception("The design to be loaded has no valid Version."),
};
}
private static Template LoadV4(JObject obj)
{
var creationDate = obj["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
var template = new Template()
{
CreationDate = creationDate,
UniqueId = obj["UniqueId"]?.ToObject<Guid>() ?? throw new ArgumentNullException("UniqueId"),
Name = new LowerString(obj["Name"]?.ToObject<string>() ?? throw new ArgumentNullException("Name")),
ModifiedDate = obj["ModifiedDate"]?.ToObject<DateTimeOffset>() ?? creationDate,
Bones = obj["Bones"]?.ToObject<Dictionary<string, BoneTransform>>() ?? throw new ArgumentNullException("Bones"),
IsWriteProtected = obj["IsWriteProtected"]?.ToObject<bool>() ?? false
};
if (template.ModifiedDate < creationDate)
template.ModifiedDate = creationDate;
return template;
}
#endregion
#region ISavable
public string ToFilename(FilenameService fileNames)
=> fileNames.TemplateFile(this);
public void Save(StreamWriter writer)
{
using var j = new JsonTextWriter(writer)
{
Formatting = Formatting.Indented,
};
var obj = JsonSerialize();
obj.WriteTo(j);
}
public string LogName(string fileName)
=> Path.GetFileNameWithoutExtension(fileName);
#endregion
}

View File

@@ -0,0 +1,36 @@
using CustomizePlus.Templates.Data;
using OtterGui.Classes;
using System;
namespace CustomizePlus.Templates.Events;
/// <summary>
/// Triggered when Template is changed
/// </summary>
public class TemplateChanged() : EventWrapper<TemplateChanged.Type, Template?, object?, TemplateChanged.Priority>(nameof(TemplateChanged))
{
public enum Type
{
Created,
Deleted,
Renamed,
NewBone,
UpdatedBone,
DeletedBone,
EditorEnabled,
EditorDisabled,
EditorCharacterChanged,
ReloadedAll,
WriteProtection
}
public enum Priority
{
TemplateCombo = -2,
TemplateFileSystemSelector = -1,
TemplateFileSystem,
ArmatureManager,
ProfileManager,
CustomizePlusIpc
}
}

View File

@@ -0,0 +1,288 @@
using CustomizePlus.Core.Data;
using CustomizePlus.Game.Events;
using CustomizePlus.Game.Services;
using CustomizePlus.Profiles.Data;
using CustomizePlus.Templates.Data;
using CustomizePlus.Templates.Events;
using OtterGui.Log;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace CustomizePlus.Templates;
public class TemplateEditorManager : IDisposable
{
private readonly TemplateChanged _event;
private readonly GPoseStateChanged _gposeStateChanged;
private readonly Logger _logger;
private readonly GameObjectService _gameObjectService;
private readonly TemplateManager _templateManager;
/// <summary>
/// Reference to the original template which is currently being edited, should not be edited!
/// </summary>
private Template _currentlyEditedTemplateOriginal;
/// <summary>
/// Internal profile for the editor
/// </summary>
public Profile EditorProfile { get; private set; }
/// <summary>
/// Original ID of the template which is currently being edited
/// </summary>
public Guid CurrentlyEditedTemplateId { get; private set; }
/// <summary>
/// A copy of currently edited template, all changes must be done on this template
/// </summary>
public Template? CurrentlyEditedTemplate { get; private set; }
public bool IsEditorActive { get; private set; }
/// <summary>
/// Is editor currently paused? Happens automatically when editor is not compatible with the current game state.
/// Keeps editor state frozen and prevents any changes to it, also sets editor profile as disabled.
/// </summary>
public bool IsEditorPaused { get; private set; }
/// <summary>
/// Indicates if there are any changes in current editing session or not
/// </summary>
public bool HasChanges { get; private set; }
public bool IsKeepOnlyEditorProfileActive { get; set; } //todo
public TemplateEditorManager(
TemplateChanged @event,
GPoseStateChanged gposeStateChanged,
Logger logger,
TemplateManager templateManager,
GameObjectService gameObjectService)
{
_event = @event;
_gposeStateChanged = gposeStateChanged;
_logger = logger;
_templateManager = templateManager;
_gameObjectService = gameObjectService;
_gposeStateChanged.Subscribe(OnGPoseStateChanged, GPoseStateChanged.Priority.TemplateEditorManager);
EditorProfile = new Profile() { Templates = new List<Template>(), Enabled = false, Name = "Template editor profile" };
}
public void Dispose()
{
_gposeStateChanged.Unsubscribe(OnGPoseStateChanged);
}
/// <summary>
/// Turn on editing of a specific template. If character name not set will default to local player.
/// </summary>
internal bool EnableEditor(Template template, string? characterName = null) //todo: editor is borked
{
if (IsEditorActive || IsEditorPaused)
return false;
_logger.Debug($"Enabling editor profile for {template.Name} via character {characterName}");
CurrentlyEditedTemplateId = template.UniqueId;
_currentlyEditedTemplateOriginal = template;
CurrentlyEditedTemplate = new Template(template)
{
CreationDate = DateTimeOffset.UtcNow,
ModifiedDate = DateTimeOffset.UtcNow,
UniqueId = Guid.NewGuid(),
Name = "Template editor temporary template"
};
if (characterName != null)
EditorProfile.CharacterName = characterName;
else //safeguard
EditorProfile.CharacterName = _gameObjectService.GetCurrentPlayerName();
EditorProfile.Templates.Clear(); //safeguard
EditorProfile.Templates.Add(CurrentlyEditedTemplate);
EditorProfile.Enabled = true;
HasChanges = false;
IsEditorActive = true;
_event.Invoke(TemplateChanged.Type.EditorEnabled, template, characterName);
return true;
}
/// <summary>
/// Turn off editing of a specific template
/// </summary>
internal bool DisableEditor()
{
if (!IsEditorActive || IsEditorPaused)
return false;
_logger.Debug($"Disabling editor profile");
string characterName = EditorProfile.CharacterName;
CurrentlyEditedTemplateId = Guid.Empty;
CurrentlyEditedTemplate = null;
EditorProfile.Enabled = false;
EditorProfile.CharacterName = "";
EditorProfile.Templates.Clear();
IsEditorActive = false;
HasChanges = false;
_event.Invoke(TemplateChanged.Type.EditorDisabled, null, characterName);
return true;
}
public void SaveChanges(bool asCopy = false)
{
var targetTemplate = _templateManager.GetTemplate(CurrentlyEditedTemplateId);
if (targetTemplate == null)
throw new Exception($"Fatal editor error: Template with ID {CurrentlyEditedTemplateId} not found in template manager");
if (asCopy)
targetTemplate = _templateManager.Clone(targetTemplate, $"{targetTemplate.Name} - Copy {Guid.NewGuid().ToString().Substring(0, 4)}", false);
_templateManager.ApplyBoneChangesAndSave(targetTemplate, CurrentlyEditedTemplate!);
}
public bool ChangeEditorCharacter(string characterName)
{
if (!IsEditorActive || EditorProfile.CharacterName == characterName || IsEditorPaused)
return false;
_logger.Debug($"Changing character name for editor profile from {EditorProfile.CharacterName} to {characterName}");
EditorProfile.CharacterName = characterName;
_event.Invoke(TemplateChanged.Type.EditorCharacterChanged, CurrentlyEditedTemplate, (characterName, EditorProfile));
return true;
}
/// <summary>
/// Resets changes in currently edited template to default values
/// </summary>
public bool ResetBoneAttributeChanges(string boneName, BoneAttribute attribute)
{
if (!IsEditorActive || IsEditorPaused)
return false;
if (!CurrentlyEditedTemplate!.Bones.ContainsKey(boneName))
return false;
var resetValue = GetResetValueForAttribute(attribute);
switch (attribute)
{
case BoneAttribute.Position:
if (resetValue == CurrentlyEditedTemplate!.Bones[boneName].Translation)
return false;
break;
case BoneAttribute.Rotation:
if (resetValue == CurrentlyEditedTemplate!.Bones[boneName].Rotation)
return false;
break;
case BoneAttribute.Scale:
if (resetValue == CurrentlyEditedTemplate!.Bones[boneName].Scaling)
return false;
break;
}
CurrentlyEditedTemplate!.Bones[boneName].UpdateAttribute(attribute, resetValue);
if (!HasChanges)
HasChanges = true;
return true;
}
/// <summary>
/// Reverts changes in currently edited template to values set in saved copy of the template.
/// Resets to default value if saved copy doesn't have that bone edited
/// </summary>
public bool RevertBoneAttributeChanges(string boneName, BoneAttribute attribute)
{
if (!IsEditorActive || IsEditorPaused)
return false;
if (!CurrentlyEditedTemplate!.Bones.ContainsKey(boneName))
return false;
Vector3? originalValue = null!;
if (_currentlyEditedTemplateOriginal.Bones.ContainsKey(boneName))
{
switch (attribute)
{
case BoneAttribute.Position:
originalValue = _currentlyEditedTemplateOriginal.Bones[boneName].Translation;
if (originalValue == CurrentlyEditedTemplate!.Bones[boneName].Translation)
return false;
break;
case BoneAttribute.Rotation:
originalValue = _currentlyEditedTemplateOriginal.Bones[boneName].Rotation;
if (originalValue == CurrentlyEditedTemplate!.Bones[boneName].Rotation)
return false;
break;
case BoneAttribute.Scale:
originalValue = _currentlyEditedTemplateOriginal.Bones[boneName].Scaling;
if (originalValue == CurrentlyEditedTemplate!.Bones[boneName].Scaling)
return false;
break;
}
}
else
originalValue = GetResetValueForAttribute(attribute);
CurrentlyEditedTemplate!.Bones[boneName].UpdateAttribute(attribute, originalValue.Value);
if (!HasChanges)
HasChanges = true;
return true;
}
public bool ModifyBoneTransform(string boneName, BoneTransform transform)
{
if (!IsEditorActive || IsEditorPaused)
return false;
if (!_templateManager.ModifyBoneTransform(CurrentlyEditedTemplate!, boneName, transform))
return false;
if (!HasChanges)
HasChanges = true;
return true;
}
private Vector3 GetResetValueForAttribute(BoneAttribute attribute)
{
switch (attribute)
{
case BoneAttribute.Scale:
return Vector3.One;
default:
return Vector3.Zero;
}
}
private void OnGPoseStateChanged(GPoseStateChanged.Type type)
{
switch (type)
{
case GPoseStateChanged.Type.Entered:
IsEditorPaused = true;
EditorProfile.Enabled = false;
break;
case GPoseStateChanged.Type.Exited:
EditorProfile.Enabled = true;
IsEditorPaused = false;
break;
}
}
}

View File

@@ -0,0 +1,150 @@
using Dalamud.Interface.Internal.Notifications;
using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Log;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using CustomizePlus.Core.Services;
using CustomizePlus.Templates.Events;
using CustomizePlus.Templates.Data;
namespace CustomizePlus.Templates;
//Adapted from glamourer source code
public sealed class TemplateFileSystem : FileSystem<Template>, IDisposable, ISavable
{
private readonly TemplateManager _templateManager;
private readonly SaveService _saveService;
private readonly TemplateChanged _templateChanged;
private readonly MessageService _messageService;
private readonly Logger _logger;
public TemplateFileSystem(
TemplateManager templateManager,
SaveService saveService,
TemplateChanged templateChanged,
MessageService messageService,
Logger logger)
{
_templateManager = templateManager;
_saveService = saveService;
_templateChanged = templateChanged;
_messageService = messageService;
_logger = logger;
_templateChanged.Subscribe(OnTemplateChange, TemplateChanged.Priority.TemplateFileSystem);
Changed += OnChange;
Reload();
}
public void Dispose()
{
_templateChanged.Unsubscribe(OnTemplateChange);
}
// Search the entire filesystem for the leaf corresponding to a template.
public bool FindLeaf(Template template, [NotNullWhen(true)] out Leaf? leaf)
{
leaf = Root.GetAllDescendants(ISortMode<Template>.Lexicographical)
.OfType<Leaf>()
.FirstOrDefault(l => l.Value == template);
return leaf != null;
}
private void OnTemplateChange(TemplateChanged.Type type, Template? template, object? data)
{
switch (type)
{
case TemplateChanged.Type.Created:
var parent = Root;
if (data is string path)
try
{
parent = FindOrCreateAllFolders(path);
}
catch (Exception ex)
{
_messageService.NotificationMessage(ex, $"Could not move template to {path} because the folder could not be created.", NotificationType.Error);
}
CreateDuplicateLeaf(parent, template.Name.Text, template);
return;
case TemplateChanged.Type.Deleted:
if (FindLeaf(template, out var leaf1))
Delete(leaf1);
return;
case TemplateChanged.Type.ReloadedAll:
Reload();
return;
case TemplateChanged.Type.Renamed when data is string oldName:
if (!FindLeaf(template, out var leaf2))
return;
var old = oldName.FixName();
if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
RenameWithDuplicates(leaf2, template.Name);
return;
}
}
private void Reload()
{
if (Load(new FileInfo(_saveService.FileNames.TemplateFileSystem), _templateManager.Templates, TemplateToIdentifier, TemplateToName))
{
var shouldReloadAgain = false;
if (!File.Exists(_saveService.FileNames.TemplateFileSystem))
shouldReloadAgain = true;
_saveService.ImmediateSave(this);
//this is a workaround for FileSystem's weird behavior where it doesn't load objects into itself if its file does not exist
if (shouldReloadAgain)
{
_logger.Debug("BUG WORKAROUND: reloading template filesystem again");
Reload();
return;
}
}
_logger.Debug("Reloaded template filesystem.");
}
private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
{
if (type != FileSystemChangeType.Reload)
_saveService.QueueSave(this);
}
// Used for saving and loading.
private static string TemplateToIdentifier(Template template)
=> template.UniqueId.ToString();
private static string TemplateToName(Template template)
=> template.Name.Text.FixName();
private static bool TemplateHasDefaultPath(Template template, string fullPath)
{
var regex = new Regex($@"^{Regex.Escape(TemplateToName(template))}( \(\d+\))?$");
return regex.IsMatch(fullPath);
}
private static (string, bool) SaveTemplate(Template template, string fullPath)
// Only save pairs with non-default paths.
=> TemplateHasDefaultPath(template, fullPath)
? (string.Empty, false)
: (TemplateToIdentifier(template), true);
public string ToFilename(FilenameService fileNames) => fileNames.TemplateFileSystem;
public void Save(StreamWriter writer)
{
SaveToFile(writer, SaveTemplate, true);
}
}

View File

@@ -0,0 +1,332 @@
using CustomizePlus.Core.Data;
using CustomizePlus.Core.Events;
using CustomizePlus.Core.Helpers;
using CustomizePlus.Core.Services;
using CustomizePlus.Templates.Data;
using CustomizePlus.Templates.Events;
using Newtonsoft.Json.Linq;
using OtterGui.Log;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CustomizePlus.Templates;
public class TemplateManager
{
private readonly SaveService _saveService;
private readonly Logger _logger;
private readonly TemplateChanged _event;
private readonly ReloadEvent _reloadEvent;
private readonly List<Template> _templates = new();
public IReadOnlyList<Template> Templates
=> _templates;
public TemplateManager(
SaveService saveService,
Logger logger,
TemplateChanged @event,
ReloadEvent reloadEvent)
{
_saveService = saveService;
_logger = logger;
_event = @event;
_reloadEvent = reloadEvent;
_reloadEvent.Subscribe(OnReload, ReloadEvent.Priority.TemplateManager);
CreateTemplateFolder(saveService);
LoadTemplates();
}
public Template? GetTemplate(Guid templateId) => _templates.FirstOrDefault(d => d.UniqueId == templateId);
public void LoadTemplates()
{
_logger.Information("Loading templates from directory...");
_templates.Clear();
List<(Template, string)> invalidNames = new();
foreach (var file in _saveService.FileNames.Templates())
{
try
{
var text = File.ReadAllText(file.FullName);
var data = JObject.Parse(text);
var template = Template.Load(data);
if (template.UniqueId.ToString() != Path.GetFileNameWithoutExtension(file.Name))
invalidNames.Add((template, file.FullName));
if (_templates.Any(f => f.UniqueId == template.UniqueId))
throw new Exception($"ID {template.UniqueId} was not unique.");
PruneIdempotentTransforms(template);
_templates.Add(template);
}
catch (Exception ex)
{
_logger.Error($"Could not load template, skipped:\n{ex}");
//++skipped;
}
}
var failed = MoveInvalidNames(invalidNames);
if (invalidNames.Count > 0)
_logger.Information(
$"Moved {invalidNames.Count - failed} templates to correct names.{(failed > 0 ? $" Failed to move {failed} templates to correct names." : string.Empty)}");
_logger.Information("Directory load complete");
_event.Invoke(TemplateChanged.Type.ReloadedAll, null, null);
}
public Template Create(string name, Dictionary<string, BoneTransform>? bones, bool handlePath)
{
var (actualName, path) = NameParsingHelper.ParseName(name, handlePath);
var template = new Template
{
CreationDate = DateTimeOffset.UtcNow,
ModifiedDate = DateTimeOffset.UtcNow,
UniqueId = CreateNewGuid(),
Name = actualName,
Bones = bones != null && bones.Count > 0 ? new Dictionary<string, BoneTransform>(bones) : new()
};
if (template.Bones.Count > 0)
PruneIdempotentTransforms(template);
_templates.Add(template);
_logger.Debug($"Added new template {template.UniqueId}.");
_saveService.ImmediateSave(template);
_event.Invoke(TemplateChanged.Type.Created, template, path);
return template;
}
public Template Create(string name, bool handlePath)
{
return Create(name, null, handlePath);
}
/// <summary>
/// Create a new template by cloning passed template
/// </summary>
/// <returns></returns>
public Template Clone(Template clone, string name, bool handlePath)
{
var (actualName, path) = NameParsingHelper.ParseName(name, handlePath);
var template = new Template(clone)
{
CreationDate = DateTimeOffset.UtcNow,
ModifiedDate = DateTimeOffset.UtcNow,
UniqueId = CreateNewGuid(),
Name = actualName
};
_templates.Add(template);
_logger.Debug($"Added new template {template.UniqueId} by cloning.");
_saveService.ImmediateSave(template);
_event.Invoke(TemplateChanged.Type.Created, template, path);
return template;
}
/// <summary>
/// Rename template
/// </summary>
public void Rename(Template template, string newName)
{
var oldName = template.Name.Text;
if (oldName == newName)
return;
template.Name = newName;
SaveTemplate(template);
_logger.Debug($"Renamed template {template.UniqueId}.");
_event.Invoke(TemplateChanged.Type.Renamed, template, oldName);
}
/// <summary>
/// Delete template
/// </summary>
/// <param name="template"></param>
public void Delete(Template template)
{
_templates.Remove(template);
_saveService.ImmediateDelete(template);
_event.Invoke(TemplateChanged.Type.Deleted, template, null);
}
/// <summary>
/// Set write protection state for template
/// </summary>
public void SetWriteProtection(Template template, bool value)
{
if (template.IsWriteProtected == value)
return;
template.IsWriteProtected = value;
SaveTemplate(template);
_logger.Debug($"Set template {template.UniqueId} to {(value ? string.Empty : "no longer be ")} write-protected.");
_event.Invoke(TemplateChanged.Type.WriteProtection, template, value);
}
/// <summary>
/// Copy bone data from source template to target template and queue a save for target template
/// </summary>
/// <param name="targetTemplate"></param>
/// <param name="sourceTemplate"></param>
public void ApplyBoneChangesAndSave(Template targetTemplate, Template sourceTemplate)
{
_logger.Debug($"Copying bones from {sourceTemplate.Name} to {targetTemplate.Name}");
var deletedBones = targetTemplate.Bones.Keys.Except(sourceTemplate.Bones.Keys).ToList();
foreach (var kvPair in sourceTemplate.Bones)
{
ModifyBoneTransform(targetTemplate, kvPair.Key, kvPair.Value);
}
foreach (var boneName in deletedBones)
{
DeleteBoneTransform(targetTemplate, boneName);
}
SaveTemplate(targetTemplate);
}
//Creates, updates or deletes bone transform
//not to be used on editor-related features by anything but TemplateEditorManager
public bool ModifyBoneTransform(Template template, string boneName, BoneTransform transform)
{
if (template.Bones.TryGetValue(boneName, out var boneTransform)
&& boneTransform != null)
{
if (boneTransform == transform)
return false;
if (transform.IsEdited())
{
template.Bones[boneName].UpdateToMatch(transform);
_logger.Debug($"Updated bone {boneName} on {template.Name}");
_event.Invoke(TemplateChanged.Type.UpdatedBone, template, boneName);
}
else
{
template.Bones.Remove(boneName);
_logger.Debug($"Deleted bone {boneName} on {template.Name}");
_event.Invoke(TemplateChanged.Type.DeletedBone, template, boneName);
}
}
else
{
template.Bones[boneName] = new BoneTransform(transform);
_logger.Debug($"Created bone {boneName} on {template.Name}");
_event.Invoke(TemplateChanged.Type.NewBone, template, boneName);
}
return true;
}
private void DeleteBoneTransform(Template template, string boneName)
{
if (!template.Bones.ContainsKey(boneName))
return;
template.Bones.Remove(boneName);
_logger.Debug($"Deleted bone {boneName} on {template.Name}");
_event.Invoke(TemplateChanged.Type.DeletedBone, template, boneName);
}
private static void PruneIdempotentTransforms(Template template)
{
foreach (var kvp in template.Bones)
{
if (!kvp.Value.IsEdited())
{
template.Bones.Remove(kvp.Key);
}
}
}
private void SaveTemplate(Template template)
{
template.ModifiedDate = DateTimeOffset.UtcNow;
_saveService.QueueSave(template);
}
private void OnReload(ReloadEvent.Type type)
{
if (type != ReloadEvent.Type.ReloadTemplates &&
type != ReloadEvent.Type.ReloadAll)
return;
_logger.Debug("Reload event received");
LoadTemplates();
}
private static void CreateTemplateFolder(SaveService service)
{
var ret = service.FileNames.TemplateDirectory;
if (Directory.Exists(ret))
return;
try
{
Directory.CreateDirectory(ret);
}
catch (Exception ex)
{
Plugin.Logger.Error($"Could not create template directory {ret}:\n{ex}");
}
}
/// <summary> Move all files that were discovered to have names not corresponding to their identifier to correct names, if possible. </summary>
/// <returns>The number of files that could not be moved.</returns>
private int MoveInvalidNames(IEnumerable<(Template, string)> invalidNames)
{
var failed = 0;
foreach (var (template, name) in invalidNames)
{
try
{
var correctName = _saveService.FileNames.TemplateFile(template);
File.Move(name, correctName, false);
_logger.Information($"Moved invalid template file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}.");
}
catch (Exception ex)
{
++failed;
_logger.Error($"Failed to move invalid template file from {Path.GetFileName(name)}:\n{ex}");
}
}
return failed;
}
/// <summary>
/// Create new guid until we find one which isn't used by existing template
/// </summary>
/// <returns></returns>
private Guid CreateNewGuid()
{
while (true)
{
var guid = Guid.NewGuid();
if (_templates.All(d => d.UniqueId != guid))
return guid;
}
}
}