新增 FlowScope Git 包雏形
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FlowScope.Config
|
||||
{
|
||||
public sealed class JsonConfigProvider : IConfigProvider
|
||||
{
|
||||
private readonly IReadOnlyList<ConfigMapping> _mappings;
|
||||
private readonly Dictionary<Type, object> _rowsByType = new();
|
||||
|
||||
public JsonConfigProvider(IReadOnlyList<ConfigMapping> mappings)
|
||||
{
|
||||
_mappings = mappings ?? throw new ArgumentNullException(nameof(mappings));
|
||||
}
|
||||
|
||||
public static ConfigMapping Mapping<T>(string fileName, Func<CancellationToken, Task<string>> loadTextAsync)
|
||||
where T : class, IConfigRow
|
||||
{
|
||||
return Mapping<T>(
|
||||
fileName,
|
||||
new DelegateConfigSource(fileName, loadTextAsync),
|
||||
new JsonConfigParser());
|
||||
}
|
||||
|
||||
public static ConfigMapping Mapping<T>(
|
||||
string fileName,
|
||||
IConfigSource source,
|
||||
IConfigParser parser)
|
||||
where T : class, IConfigRow
|
||||
{
|
||||
return new ConfigMapping(typeof(T), fileName, source, parser);
|
||||
}
|
||||
|
||||
public async Task LoadAllAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var loadedRows = new Dictionary<Type, object>();
|
||||
|
||||
foreach (var mapping in _mappings)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string json;
|
||||
try
|
||||
{
|
||||
json = await mapping.Source.LoadTextAsync(mapping.FileName, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to load config file '{mapping.FileName}' from source '{mapping.Source.Name}'.",
|
||||
exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
loadedRows[mapping.RowType] = mapping.Parse(json);
|
||||
}
|
||||
catch (Exception exception) when (exception is not OperationCanceledException)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to parse config file '{mapping.FileName}': {exception.GetBaseException().Message}",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
_rowsByType.Clear();
|
||||
foreach (var pair in loadedRows)
|
||||
{
|
||||
_rowsByType[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public T Get<T>(int id) where T : class, IConfigRow
|
||||
{
|
||||
return _rowsByType.TryGetValue(typeof(T), out var table) &&
|
||||
table is Dictionary<int, T> rows &&
|
||||
rows.TryGetValue(id, out var row)
|
||||
? row
|
||||
: null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow
|
||||
{
|
||||
if (!_rowsByType.TryGetValue(typeof(T), out var table) || table is not Dictionary<int, T> rows)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
return new List<T>(rows.Values);
|
||||
}
|
||||
|
||||
public sealed class ConfigMapping
|
||||
{
|
||||
private readonly MethodInfo _parseRowsMethod;
|
||||
|
||||
internal ConfigMapping(
|
||||
Type rowType,
|
||||
string fileName,
|
||||
IConfigSource source,
|
||||
IConfigParser parser)
|
||||
{
|
||||
RowType = rowType ?? throw new ArgumentNullException(nameof(rowType));
|
||||
FileName = string.IsNullOrWhiteSpace(fileName)
|
||||
? throw new ArgumentException("Config file name is required.", nameof(fileName))
|
||||
: fileName;
|
||||
Source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
Parser = parser ?? throw new ArgumentNullException(nameof(parser));
|
||||
_parseRowsMethod = typeof(IConfigParser)
|
||||
.GetMethod(nameof(IConfigParser.ParseRows))
|
||||
.MakeGenericMethod(rowType);
|
||||
}
|
||||
|
||||
public Type RowType { get; }
|
||||
public string FileName { get; }
|
||||
public IConfigSource Source { get; }
|
||||
public IConfigParser Parser { get; }
|
||||
|
||||
internal object Parse(string json)
|
||||
{
|
||||
var rows = (System.Collections.IEnumerable)_parseRowsMethod.Invoke(
|
||||
Parser,
|
||||
new object[] { FileName, json });
|
||||
var byIdType = typeof(Dictionary<,>).MakeGenericType(typeof(int), RowType);
|
||||
var byId = (System.Collections.IDictionary)Activator.CreateInstance(byIdType);
|
||||
foreach (IConfigRow row in rows)
|
||||
{
|
||||
if (byId.Contains(row.Id))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate config id {row.Id} for {RowType.Name}.");
|
||||
}
|
||||
|
||||
byId.Add(row.Id, row);
|
||||
}
|
||||
|
||||
return byId;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DelegateConfigSource : IConfigSource
|
||||
{
|
||||
private readonly Func<CancellationToken, Task<string>> _loadTextAsync;
|
||||
|
||||
public DelegateConfigSource(
|
||||
string fileName,
|
||||
Func<CancellationToken, Task<string>> loadTextAsync)
|
||||
{
|
||||
Name = fileName;
|
||||
_loadTextAsync = loadTextAsync ?? throw new ArgumentNullException(nameof(loadTextAsync));
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public Task<string> LoadTextAsync(string fileName, CancellationToken cancellationToken)
|
||||
{
|
||||
return _loadTextAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user