using ACE.Server.Command;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ACE.Server.Mods
{
public static class ModHelpers
{
///
/// Adds all commands with a CommandHandlerAttribute to server commands
///
public static void RegisterAllCommands(this ModContainer container, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
var types = container.ModAssembly.GetTypes().ToList();
RegisterCommands(container, types, overrides);
}
///
/// Adds all commands with a CommandHandlerAttribute and not in a CommandCategory to server commands
///
public static void RegisterUncategorizedCommands(this ModContainer container, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
var types = container.ModAssembly.GetTypes().Where(t => !Attribute.IsDefined(t, typeof(CommandCategoryAttribute))).ToList();
RegisterCommands(container, types, overrides);
}
///
/// Adds all commands with a CommandHandlerAttribute matching a pattern to server commands
///
public static void RegisterCommandCategory(this ModContainer container, string categoryPattern, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
try
{
Regex pattern = new(categoryPattern, RegexOptions.IgnoreCase);
List types = new();
foreach(var type in container.ModAssembly.GetTypes())
{
var attr = type.GetCustomAttribute();
var cat = attr?.Category ?? "";
if (pattern.IsMatch(cat))
types.Add(type);
}
RegisterCommands(container, types, overrides);
}
catch (Exception ex) { ModManager.Log(ex.Message, ModManager.LogLevel.Error); }
}
///
/// Adds all commands in a set of Types with a CommandHandlerAttribute to server commands
///
public static void RegisterCommands(this ModContainer container, List types, bool overrides = true)
{
foreach (var type in types)
{
foreach (var method in type.GetMethods())
{
foreach (var attribute in method.GetCustomAttributes())
{
var commandHandler = new CommandHandlerInfo()
{
Handler = (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), method),
Attribute = attribute
};
if (CommandManager.TryAddCommand(commandHandler, overrides))
ModManager.Log($"{container.Meta.Name} added command: {method.Name}");
else
ModManager.Log($"{container.Meta.Name} failed to add command: {method.Name}");
}
}
}
}
///
/// Removes all commands with a CommandHandlerAttribute to server commands
///
public static void UnregisterAllCommands(this ModContainer container, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
var types = container.ModAssembly.GetTypes().ToList();
UnregisterCommands(container, types, overrides);
}
///
/// Removes all commands with a CommandHandlerAttribute and not in a CommandCategory to server commands
///
public static void UnregisterUncategorizedCommands(this ModContainer container, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
var types = container.ModAssembly.GetTypes().Where(t => !Attribute.IsDefined(t, typeof(CommandCategoryAttribute))).ToList();
UnregisterCommands(container, types, overrides);
}
///
/// Removes all commands with a CommandHandlerAttribute matching a pattern to server commands
///
public static void UnregisterCommandCategory(this ModContainer container, string categoryPattern, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
try
{
Regex pattern = new(categoryPattern, RegexOptions.IgnoreCase);
var types = container.ModAssembly.GetTypes().Where(t => pattern.IsMatch(t.GetCustomAttribute()?.Category)).ToList();
UnregisterCommands(container, types, overrides);
}
catch (Exception ex) { ModManager.Log(ex.Message, ModManager.LogLevel.Error); }
}
///
/// Removes all commands in a set of Types with a CommandHandlerAttribute to server commands
///
public static void UnregisterCommands(this ModContainer container, List types, bool overrides = true)
{
if (container?.ModAssembly is null)
return;
foreach (var type in types)
{
foreach (var method in type.GetMethods())
{
foreach (var attribute in method.GetCustomAttributes())
{
if (CommandManager.TryRemoveCommand(attribute.Command))
ModManager.Log($"{container.Meta.Name} removed command: {method.Name}");
else
ModManager.Log($"{container.Meta.Name} failed to remove command: {method.Name}");
}
}
}
}
//Todo: Look into proper way to retry with file locks
//Don't like this, but hacky way of retrying for access to settings on hot loads
///
/// Attempts every second for a number of tries to write to a file.
///
/// True if content was successfully written to file
public static bool RetryWrite(this FileInfo file, string content, int retryCount = 5)
{
try
{
Task.Run(async () => await file.WriteWithRetryAsync(TimeSpan.FromSeconds(1), content, retryCount));
}
catch (Exception)
{
return false;
}
return true;
}
///
/// Attempts every second for a number of tries to write to a file.
///
/// True if content was successfully read from file
public static bool RetryRead(this FileInfo file, out string content, int retryCount = 5)
{
content = null;
try
{
var task = Task.Run(async () =>
{
return await file.ReadWithRetryAsync(TimeSpan.FromSeconds(1), retryCount);
});
content = task.Result;
return true;
}
catch (Exception)
{
return false;
}
}
///
/// Retries a number of times writing content to a file
///
public static async Task WriteWithRetryAsync(this FileInfo file, TimeSpan retryDelay, string content, int retryCount = 5)
{
for (; ; )
{
try
{
//Get a stream. OpenRead only allows read share
await File.WriteAllTextAsync(file.FullName, content);
return;
}
//https://learn.microsoft.com/en-us/dotnet/standard/io/handling-io-errors
catch (Exception) //when ((ex.HResult & 0x0000FFFF) == 32)
{
//Eat IO exceptions while retrying
//ModManager.Log($"Unable to write to {file.FullName}, {retryCount} retries...");
if (--retryCount <= 0)
{
throw;
}
}
await Task.Delay(retryDelay);
}
}
///
/// Retries a number of times reading the content of a file
///
public static async Task ReadWithRetryAsync(this FileInfo file, TimeSpan retryDelay, int retryCount = 5)
{
for (; ; )
{
try
{
//Get a stream. OpenRead only allows read share
StreamReader reader = new(file.OpenRead());
//Return stream if there's no problem
var content = await reader.ReadToEndAsync();
reader.Close();
return content;
}
//https://learn.microsoft.com/en-us/dotnet/standard/io/handling-io-errors
catch (IOException) //when ((ex.HResult & 0x0000FFFF) == 32)
{
//Eat IO exceptions while retrying
//ModManager.Log($"Unable to read {file.FullName}, {retryCount} retries...");
if (--retryCount <= 0)
{
throw;
}
}
await Task.Delay(retryDelay);
}
}
}
}