188 lines
5.3 KiB
C#
188 lines
5.3 KiB
C#
#define RELEASE
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using Dalamud.Plugin;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller.Utils;
|
|
using Questionable.Validation;
|
|
|
|
namespace Questionable.Controller;
|
|
|
|
internal abstract class DefinitionRegistry<TDefinition> where TDefinition : class
|
|
{
|
|
private readonly IDalamudPluginInterface _pluginInterface;
|
|
|
|
private readonly IDefinitionSchemaValidator _schemaValidator;
|
|
|
|
private readonly QuestValidator _questValidator;
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
private readonly Dictionary<ushort, TDefinition> _definitions = new Dictionary<ushort, TDefinition>();
|
|
|
|
public IReadOnlyDictionary<ushort, TDefinition> Definitions => _definitions;
|
|
|
|
protected abstract string TypeLabel { get; }
|
|
|
|
protected abstract string ConfigDirectoryName { get; }
|
|
|
|
protected abstract string ProjectDirectoryName { get; }
|
|
|
|
protected abstract EPathType PathType { get; }
|
|
|
|
protected DefinitionRegistry(IDalamudPluginInterface pluginInterface, IDefinitionSchemaValidator schemaValidator, QuestValidator questValidator, ILogger logger)
|
|
{
|
|
_pluginInterface = pluginInterface;
|
|
_schemaValidator = schemaValidator;
|
|
_questValidator = questValidator;
|
|
_logger = logger;
|
|
Reload();
|
|
}
|
|
|
|
protected abstract IReadOnlyDictionary<ushort, TDefinition> GetAssemblyDefinitions();
|
|
|
|
protected virtual DateTime? GetExpiry(TDefinition definition)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
protected virtual string? GetName(TDefinition definition)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public void Reload()
|
|
{
|
|
_definitions.Clear();
|
|
_schemaValidator.Reset();
|
|
LoadFromAssembly();
|
|
try
|
|
{
|
|
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, ConfigDirectoryName)));
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_logger.LogError(exception, "Failed to load {TypeLabel} definitions from user directory (some may have been successfully loaded)", TypeLabel);
|
|
}
|
|
foreach (ValidationIssue item in _schemaValidator.Validate())
|
|
{
|
|
_questValidator.AddValidationIssue(item);
|
|
}
|
|
RemoveExpiredDefinitions();
|
|
_logger.LogDebug("Loaded {Count} {TypeLabel} definitions in total", _definitions.Count, TypeLabel);
|
|
}
|
|
|
|
[Conditional("RELEASE")]
|
|
private void LoadFromAssembly()
|
|
{
|
|
IReadOnlyDictionary<ushort, TDefinition> assemblyDefinitions = GetAssemblyDefinitions();
|
|
foreach (var (key, value) in assemblyDefinitions)
|
|
{
|
|
_definitions[key] = value;
|
|
}
|
|
_logger.LogDebug("Loaded {Count} {TypeLabel} definitions from assembly", assemblyDefinitions.Count, TypeLabel);
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void LoadFromProjectDirectory()
|
|
{
|
|
DirectoryInfo directoryInfo = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
|
|
if (directoryInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
DirectoryInfo directory = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "Paths", ProjectDirectoryName));
|
|
try
|
|
{
|
|
LoadFromDirectory(directory);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_definitions.Clear();
|
|
_logger.LogError(exception, "Failed to load {TypeLabel} definitions from project directory", TypeLabel);
|
|
}
|
|
}
|
|
|
|
private void LoadFromDirectory(DirectoryInfo directory)
|
|
{
|
|
if (!directory.Exists)
|
|
{
|
|
_logger.LogDebug("Not loading {TypeLabel} definitions from {DirectoryName} (doesn't exist)", TypeLabel, directory);
|
|
return;
|
|
}
|
|
FileInfo[] files = directory.GetFiles("*.json");
|
|
foreach (FileInfo fileInfo in files)
|
|
{
|
|
ushort? num = ExtractIdFromName(fileInfo.Name);
|
|
if (!num.HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
try
|
|
{
|
|
using FileStream utf8Json = fileInfo.OpenRead();
|
|
JsonNode jsonNode = JsonNode.Parse(utf8Json);
|
|
if (jsonNode != null)
|
|
{
|
|
_schemaValidator.Enqueue(num.Value, jsonNode);
|
|
TDefinition val = jsonNode.Deserialize<TDefinition>();
|
|
if (val != null)
|
|
{
|
|
_definitions[num.Value] = val;
|
|
}
|
|
}
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_logger.LogError(ex, "Unable to parse {TypeLabel} definition file {FileName}", TypeLabel, fileInfo.FullName);
|
|
_questValidator.AddValidationIssue(new ValidationIssue
|
|
{
|
|
PathType = PathType,
|
|
Id = num.Value.ToString(CultureInfo.InvariantCulture),
|
|
Location = null,
|
|
Type = EIssueType.InvalidJsonSyntax,
|
|
Severity = EIssueSeverity.Error,
|
|
Description = "JSON syntax error in " + fileInfo.Name + ": " + ex.Message
|
|
});
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_logger.LogError(exception, "Unable to load {TypeLabel} definition file {FileName}", TypeLabel, fileInfo.FullName);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RemoveExpiredDefinitions()
|
|
{
|
|
foreach (ushort item in (from kvp in _definitions.Where(delegate(KeyValuePair<ushort, TDefinition> kvp)
|
|
{
|
|
DateTime? expiry = GetExpiry(kvp.Value);
|
|
if (expiry.HasValue)
|
|
{
|
|
DateTime valueOrDefault = expiry.GetValueOrDefault();
|
|
return ExpiryUtils.NormalizeExpiry(valueOrDefault) < DateTime.UtcNow;
|
|
}
|
|
return false;
|
|
})
|
|
select kvp.Key).ToList())
|
|
{
|
|
_logger.LogDebug("Removing expired {TypeLabel} definition {Id} '{Name}'", TypeLabel, item, GetName(_definitions[item]) ?? item.ToString(CultureInfo.InvariantCulture));
|
|
_definitions.Remove(item);
|
|
}
|
|
}
|
|
|
|
private static ushort? ExtractIdFromName(string fileName)
|
|
{
|
|
if (!ushort.TryParse(Path.GetFileNameWithoutExtension(fileName).Split('_', 2)[0], out var result))
|
|
{
|
|
return null;
|
|
}
|
|
return result;
|
|
}
|
|
}
|