Files
certmgr/certmgr/Core/JobRegistry.cs
2025-10-18 21:43:09 +02:00

161 lines
5.3 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using CertMgr.Core.Jobs;
using CertMgr.Core.Log;
using CertMgr.Core.Utils;
namespace CertMgr.Core;
internal sealed class JobRegistry
{
private readonly Dictionary<string, JobDescriptor> _items;
public JobRegistry()
{
_items = new Dictionary<string, JobDescriptor>();
}
public IReadOnlyList<string> Names => _items.Keys.ToList();
public Task LoadAsync(CancellationToken cancellationToken)
{
CLog.Info("Loading job registry...");
if (TryGetAssemblyTypes(Assembly.GetExecutingAssembly(), out Type[] types))
{
foreach (Type type in types)
{
if (type.IsAbstract)
{
continue;
}
JobUtils.TryGetJobName(type, out string? jobName, out string? errorMessage);
TryGetGenericTypes(type, out Type? settingsType, out Type? resultType);
if (jobName != null && settingsType != null)
{
if (_items.TryGetValue(jobName, out JobDescriptor? alreadyRegisteredType))
{
CLog.Error("Job '{0}' has multiple implementations. Types '{0}' and '{1}'. First win.", jobName, alreadyRegisteredType.JobType.ToString(false), type.ToString(false));
}
else
{
_items.Add(jobName, new JobDescriptor(type, settingsType, resultType));
}
}
else
{
if (jobName == null && settingsType != null)
{
if (errorMessage != null)
{
CLog.Error(errorMessage);
}
else
{
CLog.Error("Failed to get name of job from type '{0}'", type.ToString(false));
}
}
if (jobName != null && settingsType == null)
{
CLog.Error("Job '{0}' has job-name '{1}' but the class doesn't inherit from '{2}'", type.ToString(false), jobName, typeof(Job<>).ToString(false));
}
}
}
}
CLog.Info("Loading job registry... done (found {0} jobs)", _items.Count);
return Task.CompletedTask;
}
public bool TryGet(string name, [NotNullWhen(true)] out JobDescriptor? jobDescriptor)
{
jobDescriptor = null;
if (_items.TryGetValue(name, out JobDescriptor? descriptor))
{
jobDescriptor = descriptor;
}
return jobDescriptor != null;
}
private bool TryGetGenericTypes(Type type, [NotNullWhen(true)] out Type? settingType, out Type? resultType)
{
settingType = null;
resultType = null;
for (Type t = type; t != null && type != typeof(object); t = t.BaseType!)
{
if (t.IsGenericType)
{
Type[] genericTypes = t.GetGenericArguments();
Type currentGenericType = t.GetGenericTypeDefinition();
if (currentGenericType == typeof(Job<,>))
{
if (typeof(JobSettings).IsAssignableFrom(genericTypes[0]))
{
settingType = genericTypes[0];
resultType = genericTypes[1];
}
else
{
settingType = genericTypes[1];
resultType = genericTypes[0];
}
}
else if (currentGenericType == typeof(JobBase<>))
{
if (typeof(JobSettings).IsAssignableFrom(genericTypes[0]))
{
settingType = genericTypes[0];
break;
}
}
}
}
return settingType != null;
}
private bool TryGetAssemblyTypes(Assembly assembly, out Type[] types)
{
bool succeeded;
try
{
types = assembly.GetTypes();
succeeded = true;
}
catch (ReflectionTypeLoadException e)
{
using StringBuilderCache.ScopedBuilder lease = StringBuilderCache.AcquireScoped(256);
StringBuilder sb = lease.Builder;
sb.AppendFormat("Failed to load assembly types. {0}: {1} (exceptions-count = {2}).{3}", e.GetType().ToString(false), e.Message, e.LoaderExceptions?.Length ?? -1, Environment.NewLine);
foreach (Exception? le in e.LoaderExceptions ?? Array.Empty<Exception>())
{
if (le == null)
{
continue;
}
sb.AppendFormat("\t{0}: {1}{2}", le.GetType().ToString(false), le.Message, Environment.NewLine);
}
CLog.Error(StringBuilderCache.GetStringAndRelease(sb));
types = Array.Empty<Type>();
succeeded = false;
}
return succeeded;
}
public override string ToString()
{
return string.Format("count = {0}", _items.Count);
}
}