initial commit
This commit is contained in:
6
certmgr/Core/Converters/ConverterContext.cs
Normal file
6
certmgr/Core/Converters/ConverterContext.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public class ConverterContext
|
||||
{
|
||||
public static readonly ConverterContext Empty = new ConverterContext();
|
||||
}
|
||||
15
certmgr/Core/Converters/ConverterException.cs
Normal file
15
certmgr/Core/Converters/ConverterException.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public sealed class ConverterException : Exception
|
||||
{
|
||||
internal ConverterException(string messageFormat, params object?[] messageArgs)
|
||||
: base(StringFormatter.Format(messageFormat, messageArgs))
|
||||
{
|
||||
}
|
||||
internal ConverterException(Exception? innerException, string messageFormat, params object?[] messageArgs)
|
||||
: base(StringFormatter.Format(messageFormat, messageArgs), innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
19
certmgr/Core/Converters/ConverterFactory.cs
Normal file
19
certmgr/Core/Converters/ConverterFactory.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using CertMgr.Core.Converters.Impl;
|
||||
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public sealed class ConverterFactory
|
||||
{
|
||||
internal ConverterFactory()
|
||||
{
|
||||
}
|
||||
|
||||
public ConverterStash CreateDefault()
|
||||
{
|
||||
ConverterStash stash = new ConverterStash();
|
||||
stash.Set(typeof(int), typeof(IntConverter));
|
||||
stash.Set(typeof(string), typeof(StringConverter));
|
||||
stash.Set(typeof(Enum), typeof(EnumConverter));
|
||||
return stash;
|
||||
}
|
||||
}
|
||||
102
certmgr/Core/Converters/ConverterStash.cs
Normal file
102
certmgr/Core/Converters/ConverterStash.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public sealed class ConverterStash
|
||||
{
|
||||
private readonly Dictionary<Type, ConverterDescriptor> _items;
|
||||
|
||||
internal ConverterStash()
|
||||
{
|
||||
_items = new Dictionary<Type, ConverterDescriptor>();
|
||||
}
|
||||
|
||||
public bool Set(Type conversionType, Type converterType, bool replace = false)
|
||||
{
|
||||
bool added = false;
|
||||
|
||||
if (_items.ContainsKey(conversionType))
|
||||
{
|
||||
if (replace)
|
||||
{
|
||||
_items[conversionType] = new ConverterDescriptor(converterType);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_items.Add(conversionType, new ConverterDescriptor(converterType));
|
||||
added = true;
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
public bool TryGet(Type type, [NotNullWhen(true)] out IValueConverter? converter)
|
||||
{
|
||||
converter = null;
|
||||
|
||||
if (_items.TryGetValue(type, out ConverterDescriptor? descriptor))
|
||||
{
|
||||
converter = descriptor.Instance;
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
if (_items.TryGetValue(typeof(Enum), out descriptor))
|
||||
{
|
||||
converter = descriptor.Instance;
|
||||
}
|
||||
}
|
||||
|
||||
return converter != null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("count = {0}", _items.Count);
|
||||
}
|
||||
|
||||
private sealed class ConverterDescriptor
|
||||
{
|
||||
private IValueConverter? _converter;
|
||||
|
||||
public ConverterDescriptor(Type converterType)
|
||||
{
|
||||
ConverterType = converterType;
|
||||
}
|
||||
|
||||
public Type ConverterType { [DebuggerStepThrough] get; }
|
||||
|
||||
public IValueConverter Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_converter == null)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (_converter == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_converter = (IValueConverter?)Activator.CreateInstance(ConverterType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ConverterException(e, "Failed to create instance of converter of type '{0}'", ConverterType.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_converter == null)
|
||||
{
|
||||
throw new ConverterException("Failed to create instance of converter of type '{0}'", ConverterType.Name);
|
||||
}
|
||||
|
||||
return _converter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
certmgr/Core/Converters/EnumConverterContext.cs
Normal file
13
certmgr/Core/Converters/EnumConverterContext.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public class EnumConverterContext : ConverterContext
|
||||
{
|
||||
internal EnumConverterContext(Type targetType)
|
||||
{
|
||||
TargetType = targetType;
|
||||
}
|
||||
|
||||
public Type TargetType { [DebuggerStepThrough] get; }
|
||||
}
|
||||
6
certmgr/Core/Converters/IValueConverter.cs
Normal file
6
certmgr/Core/Converters/IValueConverter.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public interface IValueConverter
|
||||
{
|
||||
Task<object?> ConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken);
|
||||
}
|
||||
6
certmgr/Core/Converters/IValueConverterT.cs
Normal file
6
certmgr/Core/Converters/IValueConverterT.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public interface IValueConverter<T> : IValueConverter
|
||||
{
|
||||
new Task<T?> ConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken);
|
||||
}
|
||||
43
certmgr/Core/Converters/Impl/EnumConverter.cs
Normal file
43
certmgr/Core/Converters/Impl/EnumConverter.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Converters.Impl;
|
||||
|
||||
public sealed class EnumConverter : ValueConverter<Enum>
|
||||
{
|
||||
protected override Task<Enum?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
Type resultType;
|
||||
|
||||
bool isNullable = targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
if (isNullable)
|
||||
{
|
||||
Type[] args = targetType.GetGenericArguments();
|
||||
if (args.Length != 1)
|
||||
{
|
||||
throw new ConverterException("Cannot convert nullable type '{0}' to enum", targetType.ToString(false));
|
||||
}
|
||||
if (args[0].IsEnum)
|
||||
{
|
||||
resultType = args[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ConverterException("Cannot convert nullable type '{0}' as enum as it is not enum", targetType.ToString(false));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!targetType.IsEnum)
|
||||
{
|
||||
resultType = targetType;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ConverterException("Cannot convert type '{0}' as enum as it is not enum", targetType.ToString(false));
|
||||
}
|
||||
}
|
||||
|
||||
object? result = Enum.Parse(resultType, rawValue, true);
|
||||
return Task.FromResult((Enum?)result);
|
||||
}
|
||||
}
|
||||
15
certmgr/Core/Converters/Impl/EnumNullableConverter.cs
Normal file
15
certmgr/Core/Converters/Impl/EnumNullableConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace CertMgr.Core.Converters.Impl;
|
||||
|
||||
public sealed class EnumNullableConverter : ValueConverter<Enum?>
|
||||
{
|
||||
protected override Task<Enum?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!targetType.IsEnum)
|
||||
{
|
||||
throw new ConverterException("Cannot convert type '{0}' as enum as it is not enum", targetType.Name);
|
||||
}
|
||||
|
||||
object? result = Enum.Parse(targetType, rawValue, true);
|
||||
return Task.FromResult((Enum?)result);
|
||||
}
|
||||
}
|
||||
10
certmgr/Core/Converters/Impl/IntConverter.cs
Normal file
10
certmgr/Core/Converters/Impl/IntConverter.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace CertMgr.Core.Converters.Impl;
|
||||
|
||||
public sealed class IntConverter : ValueConverter<int>
|
||||
{
|
||||
protected override Task<int> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
int result = int.Parse(rawValue);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
121
certmgr/Core/Converters/Impl/StorageConverter.cs
Normal file
121
certmgr/Core/Converters/Impl/StorageConverter.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using CertMgr.Core.Log;
|
||||
using CertMgr.Core.Storage;
|
||||
|
||||
namespace CertMgr.Core.Converters.Impl;
|
||||
|
||||
internal sealed class StorageConverter : ValueConverter<IStorage>
|
||||
{
|
||||
private const char Separator = '|';
|
||||
|
||||
protected override Task<IStorage?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
ReadOnlySpan<char> rawSpan = rawValue.AsSpan();
|
||||
|
||||
int storageTypeSplitIndex = rawSpan.IndexOf(Separator);
|
||||
if (storageTypeSplitIndex == -1)
|
||||
{
|
||||
return Task.FromResult((IStorage?)EmptyStorage.Empty);
|
||||
}
|
||||
|
||||
IStorage? storage;
|
||||
|
||||
ReadOnlySpan<char> storageTypeSpan = rawSpan.Slice(0, storageTypeSplitIndex);
|
||||
ReadOnlySpan<char> storageDefinition = rawSpan.Slice(storageTypeSplitIndex + 1);
|
||||
switch (storageTypeSpan)
|
||||
{
|
||||
case "file":
|
||||
if (!TryGetFileStore(storageDefinition, out storage))
|
||||
{
|
||||
storage = EmptyStorage.Empty;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
storage = EmptyStorage.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.FromResult((IStorage?)storage);
|
||||
}
|
||||
|
||||
private bool TryGetFileStore(ReadOnlySpan<char> storageDefinition, [NotNullWhen(true)] out IStorage? storage)
|
||||
{
|
||||
// expecting that 'storageDefinition' is something like:
|
||||
// <store-mode>|<file-path>
|
||||
// or
|
||||
// |<file-path>
|
||||
// or
|
||||
// <file-path>
|
||||
//
|
||||
// where <store-mode> can be:
|
||||
// 'o' or 'overwrite' or 'overwriteornew'
|
||||
// or
|
||||
// 'a' or 'append' or 'appendornew'
|
||||
// or
|
||||
// 'c' or 'create' or 'createnew'
|
||||
//
|
||||
// where <file-path> can be:
|
||||
// 'c:\path\myfile.txt'
|
||||
// or
|
||||
// '/path/myfile.txt'
|
||||
// or
|
||||
// './path/myfile.txt'
|
||||
// in addition - <file-path> can be either quoted or double-quoted
|
||||
|
||||
storage = null;
|
||||
|
||||
FileStoreMode defaultStoreMode = FileStoreMode.OverwriteOrNew;
|
||||
FileStoreMode storeMode;
|
||||
ReadOnlySpan<char> filename;
|
||||
|
||||
int firstSplitIndex = storageDefinition.IndexOf(Separator);
|
||||
if (firstSplitIndex != -1)
|
||||
{
|
||||
// there is a splitter. On the left side of it there must be <store-mode>. On the right there is a <file-path>
|
||||
|
||||
ReadOnlySpan<char> firstPart = storageDefinition.Slice(0, firstSplitIndex);
|
||||
filename = storageDefinition.Slice(firstSplitIndex + 1);
|
||||
|
||||
if (firstPart.Length == 0)
|
||||
{
|
||||
storeMode = defaultStoreMode;
|
||||
}
|
||||
else if (Enum.TryParse(firstPart, true, out FileStoreMode tmpMode))
|
||||
{
|
||||
storeMode = tmpMode;
|
||||
}
|
||||
else if (firstPart.Equals("w", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("overwrite", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
storeMode = FileStoreMode.OverwriteOrNew;
|
||||
}
|
||||
else if (firstPart.Equals("c", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("createnew", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
storeMode = FileStoreMode.Create;
|
||||
}
|
||||
else if (firstPart.Equals("a", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("append", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
storeMode = FileStoreMode.AppendOrNew;
|
||||
}
|
||||
else if (firstPart.Equals("o", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("open", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
storeMode = FileStoreMode.Open;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it is not store-mode or there is a typo or unsupported value
|
||||
CLog.Error(string.Concat("Unsupported store-mode '", firstPart, "'"));
|
||||
storeMode = defaultStoreMode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no split-char => just filename
|
||||
storeMode = defaultStoreMode;
|
||||
filename = storageDefinition;
|
||||
}
|
||||
|
||||
storage = new FileStorage(filename.ToString(), storeMode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
10
certmgr/Core/Converters/Impl/StringConverter.cs
Normal file
10
certmgr/Core/Converters/Impl/StringConverter.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace CertMgr.Core.Converters.Impl;
|
||||
|
||||
public sealed class StringConverter : ValueConverter<string>
|
||||
{
|
||||
protected override Task<string?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(rawValue);
|
||||
}
|
||||
}
|
||||
|
||||
49
certmgr/Core/Converters/Impl/TimeSpanConverter.cs
Normal file
49
certmgr/Core/Converters/Impl/TimeSpanConverter.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace CertMgr.Core.Converters.Impl;
|
||||
|
||||
public sealed class TimeSpanConverter : ValueConverter<TimeSpan?>
|
||||
{
|
||||
protected override Task<TimeSpan?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rawValue) || rawValue.Length == 1)
|
||||
{
|
||||
return Task.FromResult((TimeSpan?)null);
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> rawSpan = rawValue.AsSpan();
|
||||
ReadOnlySpan<char> valueSpan = rawSpan.Slice(0, rawSpan.Length - 1);
|
||||
char unit = char.ToLower(rawValue[rawValue.Length - 1]);
|
||||
|
||||
TimeSpan? result;
|
||||
|
||||
if (int.TryParse(valueSpan, out int value))
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case 's':
|
||||
result = TimeSpan.FromSeconds(value);
|
||||
break;
|
||||
case 'm':
|
||||
result = TimeSpan.FromMinutes(value);
|
||||
break;
|
||||
case 'h':
|
||||
result = TimeSpan.FromHours(value);
|
||||
break;
|
||||
case 'd':
|
||||
result = TimeSpan.FromDays(value);
|
||||
break;
|
||||
case 'y':
|
||||
result = TimeSpan.FromDays(value * 365);
|
||||
break;
|
||||
default:
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
11
certmgr/Core/Converters/ValueConverter.cs
Normal file
11
certmgr/Core/Converters/ValueConverter.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public abstract class ValueConverter : IValueConverter
|
||||
{
|
||||
public Task<object?> ConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
return DoConvertAsync(rawValue, targetType, cancellationToken);
|
||||
}
|
||||
|
||||
protected abstract Task<object?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken);
|
||||
}
|
||||
30
certmgr/Core/Converters/ValueConverterT.cs
Normal file
30
certmgr/Core/Converters/ValueConverterT.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace CertMgr.Core.Converters;
|
||||
|
||||
public abstract class ValueConverter<T> : IValueConverter<T>
|
||||
{
|
||||
public Task<T?> ConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
if (typeof(T) != targetType)
|
||||
{
|
||||
throw new ConverterException("This converter ('{0}') converts string value to type '{1}' but type '{2}' is expected this time", GetType().Name, typeof(T), targetType.Name);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return DoConvertAsync(rawValue, targetType, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ConverterException(e, "Failed to convert value '{0}' to type '{1}'", rawValue ?? "<null>", typeof(T).Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async Task<object?> IValueConverter.ConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||
{
|
||||
T? typedValue = await DoConvertAsync(rawValue, targetType, cancellationToken);
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
protected abstract Task<T?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken);
|
||||
}
|
||||
Reference in New Issue
Block a user