1
0

Merge branch 'main' into packing

This commit is contained in:
2024-01-10 08:44:08 +01:00
84 changed files with 2742 additions and 1268 deletions

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configurations>Debug;Release;DebugLocal</Configurations>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10.0</LangVersion>
@@ -11,32 +10,21 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AMWD.Common</PackageId>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Product>AM.WD Common Library</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(GITLAB_CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="'$(GITLAB_CI)' == 'true'">
<SourceLinkGitLabHost Include="git.am-wd.de" Version="$(CI_SERVER_VERSION)" />
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="1.1.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="../icon.png" Pack="true" PackagePath="/" />
<None Include="../README.md" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="2.4.35" />
<PackageReference Include="MessagePack" Version="2.5.129" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,36 @@
namespace AMWD.Common.Cli
{
/// <summary>
/// Represents a logical argument in the command line. Options with their additional
/// parameters are combined in one argument.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class Argument
{
/// <summary>
/// Initialises a new instance of the <see cref="Argument"/> class.
/// </summary>
/// <param name="option">The <see cref="Option"/> that is set in this argument; or null.</param>
/// <param name="values">The additional parameter values for the option; or the argument value.</param>
internal Argument(Option option, string[] values)
{
Option = option;
Values = values;
}
/// <summary>
/// Gets the <see cref="Option"/> that is set in this argument; or null.
/// </summary>
public Option Option { get; private set; }
/// <summary>
/// Gets the additional parameter values for the option; or the argument value.
/// </summary>
public string[] Values { get; private set; }
/// <summary>
/// Gets the first item of <see cref="Values"/>; or null.
/// </summary>
public string Value => Values.Length > 0 ? Values[0] : null;
}
}

View File

@@ -0,0 +1,366 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AMWD.Common.Cli
{
/// <summary>
/// Provides options and arguments parsing from command line arguments or a single string.
/// </summary>
public class CommandLineParser
{
#region Private data
private string[] _args;
private List<Argument> _parsedArguments;
private readonly List<Option> _options = new();
#endregion Private data
#region Configuration properties
/// <summary>
/// Gets or sets a value indicating whether the option names are case-sensitive.
/// (Default: false)
/// </summary>
public bool IsCaseSensitive { get; set; }
/// <summary>
/// Gets or sets a value indicating whether incomplete options can be automatically
/// completed if there is only a single matching option.
/// (Default: true)
/// </summary>
public bool AutoCompleteOptions { get; set; } = true;
#endregion Configuration properties
#region Custom arguments line parsing
// Source: http://stackoverflow.com/a/23961658/143684
/// <summary>
/// Parses a single string into an arguments array.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
public static string[] ParseArgsString(string argsString)
{
// Collects the split argument strings
var args = new List<string>();
// Builds the current argument
var currentArg = new StringBuilder();
// Indicates whether the last character was a backslash escape character
bool escape = false;
// Indicates whether we're in a quoted range
bool inQuote = false;
// Indicates whether there were quotes in the current arguments
bool hadQuote = false;
// Remembers the previous character
char prevCh = '\0';
// Iterate all characters from the input string
for (int i = 0; i < argsString.Length; i++)
{
char ch = argsString[i];
if (ch == '\\' && !escape)
{
// Beginning of a backslash-escape sequence
escape = true;
}
else if (ch == '\\' && escape)
{
// Double backslash, keep one
currentArg.Append(ch);
escape = false;
}
else if (ch == '"' && !escape)
{
// Toggle quoted range
inQuote = !inQuote;
hadQuote = true;
if (inQuote && prevCh == '"')
{
// Doubled quote within a quoted range is like escaping
currentArg.Append(ch);
}
}
else if (ch == '"' && escape)
{
// Backslash-escaped quote, keep it
currentArg.Append(ch);
escape = false;
}
else if (char.IsWhiteSpace(ch) && !inQuote)
{
if (escape)
{
// Add pending escape char
currentArg.Append('\\');
escape = false;
}
// Accept empty arguments only if they are quoted
if (currentArg.Length > 0 || hadQuote)
{
args.Add(currentArg.ToString());
}
// Reset for next argument
currentArg.Clear();
hadQuote = false;
}
else
{
if (escape)
{
// Add pending escape char
currentArg.Append('\\');
escape = false;
}
// Copy character from input, no special meaning
currentArg.Append(ch);
}
prevCh = ch;
}
// Save last argument
if (currentArg.Length > 0 || hadQuote)
{
args.Add(currentArg.ToString());
}
return args.ToArray();
}
/// <summary>
/// Reads the command line arguments from a single string.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
public void ReadArgs(string argsString)
{
_args = ParseArgsString(argsString);
}
#endregion Custom arguments line parsing
#region Options management
/// <summary>
/// Registers a named option without additional parameters.
/// </summary>
/// <param name="name">The option name.</param>
/// <returns>The option instance.</returns>
public Option RegisterOption(string name)
{
return RegisterOption(name, 0);
}
/// <summary>
/// Registers a named option.
/// </summary>
/// <param name="name">The option name.</param>
/// <param name="parameterCount">The number of additional parameters for this option.</param>
/// <returns>The option instance.</returns>
public Option RegisterOption(string name, int parameterCount)
{
var option = new Option(name, parameterCount);
_options.Add(option);
return option;
}
#endregion Options management
#region Parsing method
/// <summary>
/// Parses all command line arguments.
/// </summary>
/// <param name="args">The command line arguments.</param>
public void Parse(string[] args)
{
_args = args ?? throw new ArgumentNullException(nameof(args));
Parse();
}
/// <summary>
/// Parses all command line arguments.
/// </summary>
public void Parse()
{
// Use args of the current process if no other source was given
if (_args == null)
{
_args = Environment.GetCommandLineArgs();
if (_args.Length > 0)
{
// Skip myself (args[0])
_args = _args.Skip(1).ToArray();
}
}
// Clear/reset data
_parsedArguments = new();
foreach (var option in _options)
{
option.IsSet = false;
option.SetCount = 0;
option.Argument = null;
}
var comparison = IsCaseSensitive
? StringComparison.Ordinal
: StringComparison.OrdinalIgnoreCase;
var argumentWalker = new EnumerableWalker<string>(_args);
bool optMode = true;
foreach (string arg in argumentWalker.Cast<string>())
{
if (arg == "--")
{
optMode = false;
}
else if (optMode && (arg.StartsWith("/") || arg.StartsWith("-")))
{
string optName = arg.Substring(arg.StartsWith("--") ? 2 : 1);
// Split option value if separated with : or = instead of whitespace
int separatorIndex = optName.IndexOfAny(new[] { ':', '=' });
string optValue = null;
if (separatorIndex != -1)
{
optValue = optName.Substring(separatorIndex + 1);
optName = optName.Substring(0, separatorIndex);
}
// Find the option with complete name match
var option = _options.FirstOrDefault(o => o.Names.Any(n => n.Equals(optName, comparison)));
if (option == null)
{
// Try to complete the name to a unique registered option
var matchingOptions = _options.Where(o => o.Names.Any(n => n.StartsWith(optName, comparison))).ToList();
if (AutoCompleteOptions && matchingOptions.Count > 1)
throw new Exception("Invalid option, completion is not unique: " + arg);
if (!AutoCompleteOptions || matchingOptions.Count == 0)
throw new Exception("Unknown option: " + arg);
// Accept the single auto-completed option
option = matchingOptions[0];
}
// Check for single usage
if (option.IsSingle && option.IsSet)
throw new Exception("Option cannot be set multiple times: " + arg);
// Collect option values from next argument strings
string[] values = new string[option.ParameterCount];
for (int i = 0; i < option.ParameterCount; i++)
{
if (optValue != null)
{
// The first value was included in this argument string
values[i] = optValue;
optValue = null;
}
else
{
// Fetch another argument string
values[i] = argumentWalker.GetNext();
}
if (values[i] == null)
throw new Exception("Missing argument " + (i + 1) + " for option: " + arg);
}
var argument = new Argument(option, values);
// Set usage data on the option instance for quick access
option.IsSet = true;
option.SetCount++;
option.Argument = argument;
if (option.Action != null)
{
option.Action(argument);
}
else
{
_parsedArguments.Add(argument);
}
}
else
{
_parsedArguments.Add(new Argument(null, new[] { arg }));
}
}
var missingOption = _options.FirstOrDefault(o => o.IsRequired && !o.IsSet);
if (missingOption != null)
throw new Exception("Missing required option: /" + missingOption.Names[0]);
}
#endregion Parsing method
#region Parsed data properties
/// <summary>
/// Gets the parsed arguments.
/// </summary>
/// <remarks>
/// To avoid exceptions thrown, call the <see cref="Parse()"/> method in advance for
/// exception handling.
/// </remarks>
public Argument[] Arguments
{
get
{
if (_parsedArguments == null)
Parse();
return _parsedArguments.ToArray();
}
}
/// <summary>
/// Gets the options that are set in the command line, including their value.
/// </summary>
/// <remarks>
/// To avoid exceptions thrown, call the <see cref="Parse()"/> method in advance for
/// exception handling.
/// </remarks>
public Option[] SetOptions
{
get
{
if (_parsedArguments == null)
Parse();
return _parsedArguments
.Where(a => a.Option != null)
.Select(a => a.Option)
.ToArray();
}
}
/// <summary>
/// Gets the free arguments that are set in the command line and don't belong to an option.
/// </summary>
/// <remarks>
/// To avoid exceptions thrown, call the <see cref="Parse()"/> method in advance for
/// exception handling.
/// </remarks>
public string[] FreeArguments
{
get
{
if (_parsedArguments == null)
Parse();
return _parsedArguments
.Where(a => a.Option == null)
.Select(a => a.Value)
.ToArray();
}
}
#endregion Parsed data properties
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace AMWD.Common.Cli
{
/// <summary>
/// Walks through an <see cref="IEnumerable{T}"/> and allows retrieving additional items.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class EnumerableWalker<T> : IEnumerable<T>
where T : class
{
private readonly IEnumerable<T> _array;
private IEnumerator<T> _enumerator;
/// <summary>
/// Initialises a new instance of the <see cref="EnumerableWalker{T}"/> class.
/// </summary>
/// <param name="array">The array to walk though.</param>
public EnumerableWalker(IEnumerable<T> array)
{
_array = array ?? throw new ArgumentNullException(nameof(array));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
_enumerator = _array.GetEnumerator();
return _enumerator;
}
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>The enumerator.</returns>
public IEnumerator GetEnumerator()
{
_enumerator = _array.GetEnumerator();
return _enumerator;
}
/// <summary>
/// Gets the next item.
/// </summary>
/// <returns>The next item.</returns>
public T GetNext()
{
if (_enumerator.MoveNext())
{
return _enumerator.Current;
}
else
{
return default;
}
}
}
}

113
AMWD.Common/Cli/Option.cs Normal file
View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
namespace AMWD.Common.Cli
{
/// <summary>
/// Represents a named option.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class Option
{
/// <summary>
/// Initialises a new instance of the <see cref="Option"/> class.
/// </summary>
/// <param name="name">The primary name of the option.</param>
/// <param name="parameterCount">The number of additional parameters for this option.</param>
internal Option(string name, int parameterCount)
{
Names = new List<string>() { name };
ParameterCount = parameterCount;
}
/// <summary>
/// Gets the names of this option.
/// </summary>
public List<string> Names { get; private set; }
/// <summary>
/// Gets the number of additional parameters for this option.
/// </summary>
public int ParameterCount { get; private set; }
/// <summary>
/// Gets a value indicating whether this option is required.
/// </summary>
public bool IsRequired { get; private set; }
/// <summary>
/// Gets a value indicating whether this option can only be specified once.
/// </summary>
public bool IsSingle { get; private set; }
/// <summary>
/// Gets the action to invoke when the option is set.
/// </summary>
public Action<Argument> Action { get; private set; }
/// <summary>
/// Gets a value indicating whether this option is set in the command line.
/// </summary>
public bool IsSet { get; internal set; }
/// <summary>
/// Gets the number of times that this option is set in the command line.
/// </summary>
public int SetCount { get; internal set; }
/// <summary>
/// Gets the <see cref="Argument"/> instance that contains additional parameters set
/// for this option.
/// </summary>
public Argument Argument { get; internal set; }
/// <summary>
/// Gets the value of the <see cref="Argument"/> instance for this option.
/// </summary>
public string Value => Argument?.Value;
/// <summary>
/// Sets alias names for this option.
/// </summary>
/// <param name="names">The alias names for this option.</param>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Alias(params string[] names)
{
Names.AddRange(names);
return this;
}
/// <summary>
/// Marks this option as required. If a required option is not set in the command line,
/// an exception is thrown on parsing.
/// </summary>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Required()
{
IsRequired = true;
return this;
}
/// <summary>
/// Marks this option as single. If a single option is set multiple times in the
/// command line, an exception is thrown on parsing.
/// </summary>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Single()
{
IsSingle = true;
return this;
}
/// <summary>
/// Sets the action to invoke when the option is set.
/// </summary>
/// <param name="action">The action to invoke when the option is set.</param>
/// <returns>The current <see cref="Option"/> instance.</returns>
public Option Do(Action<Argument> action)
{
Action = action;
return this;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Newtonsoft.Json
/// <summary>
/// Common JSON serializer settings.
/// </summary>
private static readonly JsonSerializerSettings jsonSerializerSettings = new()
private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Culture = CultureInfo.InvariantCulture
@@ -32,7 +32,7 @@ namespace Newtonsoft.Json
public static void DeserializeJson<T>(this T target, string json)
{
if (!string.IsNullOrWhiteSpace(json))
JsonConvert.PopulateObject(json, target, jsonSerializerSettings);
JsonConvert.PopulateObject(json, target, _jsonSerializerSettings);
}
/// <summary>
@@ -42,7 +42,7 @@ namespace Newtonsoft.Json
/// <param name="json">The JSON string to read the values from.</param>
/// <returns>A new instance of <typeparamref name="T"/> with the deserialized values.</returns>
public static T DeserializeJson<T>(this string json)
=> JsonConvert.DeserializeObject<T>(json, jsonSerializerSettings);
=> JsonConvert.DeserializeObject<T>(json, _jsonSerializerSettings);
/// <summary>
/// Deserializes a JSON string into a new instance or using the fallback value.
@@ -55,7 +55,7 @@ namespace Newtonsoft.Json
{
try
{
return JsonConvert.DeserializeObject<T>(json, jsonSerializerSettings);
return JsonConvert.DeserializeObject<T>(json, _jsonSerializerSettings);
}
catch
{
@@ -83,7 +83,7 @@ namespace Newtonsoft.Json
jw.QuoteChar = '\'';
jw.Formatting = indented ? Formatting.Indented : Formatting.None;
var serializer = useCamelCase ? JsonSerializer.Create(jsonSerializerSettings) : JsonSerializer.CreateDefault();
var serializer = useCamelCase ? JsonSerializer.Create(_jsonSerializerSettings) : JsonSerializer.CreateDefault();
serializer.Error += (s, a) =>
{
@@ -107,7 +107,7 @@ namespace Newtonsoft.Json
if (obj == null)
return null;
var serializer = JsonSerializer.Create(jsonSerializerSettings);
var serializer = JsonSerializer.Create(_jsonSerializerSettings);
return JObject.FromObject(obj, serializer);
}
@@ -121,7 +121,7 @@ namespace Newtonsoft.Json
if (array == null)
return null;
var serializer = JsonSerializer.Create(jsonSerializerSettings);
var serializer = JsonSerializer.Create(_jsonSerializerSettings);
return JArray.FromObject(array, serializer);
}
@@ -146,13 +146,26 @@ namespace Newtonsoft.Json
if (lvlObj == null)
return defaultValue;
lvlObj = lvlObj[level];
string lvl = level;
if (lvlObj.Type == JTokenType.Object)
{
foreach (var prop in ((JObject)lvlObj).Properties())
{
if (prop.Name.Equals(lvl, System.StringComparison.OrdinalIgnoreCase))
{
lvl = prop.Name;
break;
}
}
}
lvlObj = lvlObj[lvl];
}
if (lvlObj == null)
return defaultValue;
return DeepConvert.ChangeType<T>(lvlObj is JValue ? ((JValue)lvlObj).Value : lvlObj.Value<object>());
return DeepConvert.ChangeType<T>(lvlObj is JValue lvlValue ? lvlValue.Value : lvlObj.Value<object>());
}
/// <summary>

View File

@@ -58,30 +58,30 @@
private struct DisposableReadWriteLock : IDisposable
{
private readonly ReaderWriterLockSlim rwLock;
private LockMode lockMode;
private readonly ReaderWriterLockSlim _rwLock;
private LockMode _lockMode;
public DisposableReadWriteLock(ReaderWriterLockSlim rwLock, LockMode lockMode)
{
this.rwLock = rwLock;
this.lockMode = lockMode;
_rwLock = rwLock;
_lockMode = lockMode;
}
public void Dispose()
{
if (lockMode == LockMode.Read)
rwLock.ExitReadLock();
if (_lockMode == LockMode.Read)
_rwLock.ExitReadLock();
if (lockMode == LockMode.Upgradable && rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone
rwLock.ExitWriteLock();
if (_lockMode == LockMode.Upgradable && _rwLock.IsWriteLockHeld) // Upgraded with EnterWriteLock alone
_rwLock.ExitWriteLock();
if (lockMode == LockMode.Upgradable)
rwLock.ExitUpgradeableReadLock();
if (_lockMode == LockMode.Upgradable)
_rwLock.ExitUpgradeableReadLock();
if (lockMode == LockMode.Write)
rwLock.ExitWriteLock();
if (_lockMode == LockMode.Write)
_rwLock.ExitWriteLock();
lockMode = LockMode.None;
_lockMode = LockMode.None;
}
}

View File

@@ -20,11 +20,8 @@ namespace System.IO
/// <returns></returns>
public static string ReadLine(this Stream stream, Encoding encoding = null, char? eol = null)
{
if (encoding == null)
encoding = Encoding.Default;
if (eol == null)
eol = Environment.NewLine.Last();
encoding ??= Encoding.Default;
eol ??= Environment.NewLine.Last();
if (!stream.CanRead)
return null;
@@ -56,11 +53,8 @@ namespace System.IO
/// <returns></returns>
public static async Task<string> ReadLineAsync(this Stream stream, Encoding encoding = null, char? eol = null, CancellationToken cancellationToken = default)
{
if (encoding == null)
encoding = Encoding.Default;
if (eol == null)
eol = Environment.NewLine.Last();
encoding ??= Encoding.Default;
eol ??= Environment.NewLine.Last();
if (!stream.CanRead)
return null;

View File

@@ -177,10 +177,7 @@ namespace System
if (isValid && nameservers?.Any() == true)
{
var dnsClientType = Type.GetType("DNS.Client.DnsClient, DNS");
if (dnsClientType == null)
throw new DllNotFoundException("The DNS NuGet package is required: https://www.nuget.org/packages/DNS/7.0.0");
var dnsClientType = Type.GetType("DNS.Client.DnsClient, DNS") ?? throw new DllNotFoundException("The DNS NuGet package is required: https://www.nuget.org/packages/DNS/7.0.0");
var recordTypeType = Type.GetType("DNS.Protocol.RecordType, DNS");
var resolveMethodInfo = dnsClientType.GetMethod("Resolve", new[] { typeof(string), recordTypeType, typeof(CancellationToken) });

View File

@@ -24,13 +24,13 @@ namespace AMWD.Common.Logging
{
#region Fields
private bool isDisposed = false;
private readonly CancellationTokenSource cancellationTokenSource = new();
private bool _isDisposed = false;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly StreamWriter fileWriter;
private readonly Task writeTask;
private readonly StreamWriter _fileWriter;
private readonly Task _writeTask;
private readonly AsyncQueue<QueueItem> queue = new();
private readonly AsyncQueue<QueueItem> _queue = new();
#endregion Fields
@@ -45,8 +45,8 @@ namespace AMWD.Common.Logging
public FileLogger(string file, bool append = false, Encoding encoding = null)
{
FileName = file;
fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8);
writeTask = Task.Run(() => WriteFileAsync(cancellationTokenSource.Token));
_fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8);
_writeTask = Task.Run(() => WriteFileAsync(_cancellationTokenSource.Token));
}
/// <summary>
@@ -148,7 +148,7 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="ILogger.BeginScope{TState}(TState)" />
public IDisposable BeginScope<TState>(TState state)
{
if (isDisposed)
if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName);
return ScopeProvider?.Push(state) ?? NullScope.Instance;
@@ -157,7 +157,7 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="ILogger.IsEnabled(LogLevel)" />
public bool IsEnabled(LogLevel logLevel)
{
if (isDisposed)
if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName);
return logLevel >= MinLevel;
@@ -166,7 +166,7 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="ILogger.Log{TState}(LogLevel, EventId, TState, Exception, Func{TState, Exception, string})" />
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (isDisposed)
if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName);
if (!IsEnabled(logLevel))
@@ -197,13 +197,13 @@ namespace AMWD.Common.Logging
/// <inheritdoc cref="IDisposable.Dispose" />
public void Dispose()
{
if (!isDisposed)
if (!_isDisposed)
{
isDisposed = true;
_isDisposed = true;
cancellationTokenSource.Cancel();
writeTask.GetAwaiter().GetResult();
fileWriter.Dispose();
_cancellationTokenSource.Cancel();
_writeTask.GetAwaiter().GetResult();
_fileWriter.Dispose();
}
}
@@ -213,7 +213,7 @@ namespace AMWD.Common.Logging
private void WriteMessage(string name, LogLevel logLevel, int eventId, string message, Exception exception)
{
queue.Enqueue(new QueueItem
_queue.Enqueue(new QueueItem
{
Timestamp = UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now,
Name = name,
@@ -236,7 +236,7 @@ namespace AMWD.Common.Logging
QueueItem[] items;
try
{
items = await queue.DequeueAvailableAsync(cancellationToken: token);
items = await _queue.DequeueAvailableAsync(cancellationToken: token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -302,10 +302,10 @@ namespace AMWD.Common.Logging
sb.Append(message.Replace("\n", "\n" + timestampPadding + logLevelPadding));
await fileWriter.WriteLineAsync(sb.ToString());
await _fileWriter.WriteLineAsync(sb.ToString()).ConfigureAwait(false);
}
await fileWriter.FlushAsync();
await _fileWriter.FlushAsync().ConfigureAwait(false);
}
}

View File

@@ -12,10 +12,10 @@ namespace System.Collections.Generic
{
#region Fields
private readonly Queue<T> queue;
private readonly Queue<T> _queue;
private TaskCompletionSource<bool> dequeueTcs = new();
private TaskCompletionSource<bool> availableTcs = new();
private TaskCompletionSource<bool> _dequeueTcs = new();
private TaskCompletionSource<bool> _availableTcs = new();
#endregion Fields
@@ -27,7 +27,7 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public AsyncQueue()
{
queue = new Queue<T>();
_queue = new Queue<T>();
}
/// <summary>
@@ -40,7 +40,7 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public AsyncQueue(IEnumerable<T> collection)
{
queue = new Queue<T>();
_queue = new Queue<T>();
Enqueue(collection);
}
@@ -52,7 +52,7 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public AsyncQueue(int capacity)
{
queue = new Queue<T>(capacity);
_queue = new Queue<T>(capacity);
}
#endregion Constructors
@@ -67,9 +67,9 @@ namespace System.Collections.Generic
{
get
{
lock (queue)
lock (_queue)
{
return queue.Count;
return _queue.Count;
}
}
}
@@ -84,9 +84,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public void Clear()
{
lock (queue)
lock (_queue)
{
queue.Clear();
_queue.Clear();
}
}
@@ -98,9 +98,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public bool Contains(T item)
{
lock (queue)
lock (_queue)
{
return queue.Contains(item);
return _queue.Contains(item);
}
}
@@ -115,9 +115,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public void CopyTo(T[] array, int arrayIndex)
{
lock (queue)
lock (_queue)
{
queue.CopyTo(array, arrayIndex);
_queue.CopyTo(array, arrayIndex);
}
}
@@ -129,9 +129,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public T Dequeue()
{
lock (queue)
lock (_queue)
{
return queue.Dequeue();
return _queue.Dequeue();
}
}
@@ -141,11 +141,11 @@ namespace System.Collections.Generic
/// <param name="item">The object to add to the <see cref="AsyncQueue{T}"/>. The value can be null for reference types.</param>
public void Enqueue(T item)
{
lock (queue)
lock (_queue)
{
queue.Enqueue(item);
SetToken(dequeueTcs);
SetToken(availableTcs);
_queue.Enqueue(item);
SetToken(_dequeueTcs);
SetToken(_availableTcs);
}
}
@@ -157,9 +157,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public T Peek()
{
lock (queue)
lock (_queue)
{
return queue.Peek();
return _queue.Peek();
}
}
@@ -170,9 +170,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public T[] ToArray()
{
lock (queue)
lock (_queue)
{
return queue.ToArray();
return _queue.ToArray();
}
}
@@ -182,9 +182,9 @@ namespace System.Collections.Generic
[ExcludeFromCodeCoverage]
public void TrimExcess()
{
lock (queue)
lock (_queue)
{
queue.TrimExcess();
_queue.TrimExcess();
}
}
@@ -203,21 +203,21 @@ namespace System.Collections.Generic
while (true)
{
TaskCompletionSource<bool> internalDequeueTcs;
lock (queue)
lock (_queue)
{
if (queue.Count > 0)
if (_queue.Count > 0)
{
int count = queue.Count;
int count = _queue.Count;
if (maxCount > 0 && count > maxCount)
count = maxCount;
var items = new T[count];
for (int i = 0; i < count; i++)
items[i] = queue.Dequeue();
items[i] = _queue.Dequeue();
return items;
}
internalDequeueTcs = ResetToken(ref dequeueTcs);
internalDequeueTcs = ResetToken(ref _dequeueTcs);
}
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
@@ -238,17 +238,17 @@ namespace System.Collections.Generic
while (true)
{
TaskCompletionSource<bool> internalDequeueTcs;
lock (queue)
lock (_queue)
{
if (count <= queue.Count)
if (count <= _queue.Count)
{
var items = new T[count];
for (int i = 0; i < count; i++)
items[i] = queue.Dequeue();
items[i] = _queue.Dequeue();
return items;
}
internalDequeueTcs = ResetToken(ref dequeueTcs);
internalDequeueTcs = ResetToken(ref _dequeueTcs);
}
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
@@ -265,12 +265,12 @@ namespace System.Collections.Generic
while (true)
{
TaskCompletionSource<bool> internalDequeueTcs;
lock (queue)
lock (_queue)
{
if (queue.Count > 0)
return queue.Dequeue();
if (_queue.Count > 0)
return _queue.Dequeue();
internalDequeueTcs = ResetToken(ref dequeueTcs);
internalDequeueTcs = ResetToken(ref _dequeueTcs);
}
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
@@ -285,12 +285,12 @@ namespace System.Collections.Generic
public async Task WaitAsync(CancellationToken cancellationToken = default)
{
TaskCompletionSource<bool> internalAvailableTcs;
lock (queue)
lock (_queue)
{
if (queue.Count > 0)
if (_queue.Count > 0)
return;
internalAvailableTcs = ResetToken(ref availableTcs);
internalAvailableTcs = ResetToken(ref _availableTcs);
}
await WaitAsync(internalAvailableTcs, cancellationToken).ConfigureAwait(false);
@@ -347,10 +347,10 @@ namespace System.Collections.Generic
/// <returns>true if item is successfully removed; otherwise, false. This method also returns false if item was not found in the <see cref="AsyncQueue{T}"/>.</returns>
public bool Remove(T item)
{
lock (queue)
lock (_queue)
{
var copy = new Queue<T>(queue);
queue.Clear();
var copy = new Queue<T>(_queue);
_queue.Clear();
bool found = false;
int count = copy.Count;
@@ -359,7 +359,7 @@ namespace System.Collections.Generic
var element = copy.Dequeue();
if (found)
{
queue.Enqueue(element);
_queue.Enqueue(element);
continue;
}
@@ -369,7 +369,7 @@ namespace System.Collections.Generic
continue;
}
queue.Enqueue(element);
_queue.Enqueue(element);
}
return found;
@@ -382,19 +382,19 @@ namespace System.Collections.Generic
/// <param name="collection">The objects to add to the <see cref="AsyncQueue{T}"/>.</param>
public void Enqueue(IEnumerable<T> collection)
{
lock (queue)
lock (_queue)
{
bool hasElements = false;
foreach (var element in collection)
{
hasElements = true;
queue.Enqueue(element);
_queue.Enqueue(element);
}
if (hasElements)
{
SetToken(dequeueTcs);
SetToken(availableTcs);
SetToken(_dequeueTcs);
SetToken(_availableTcs);
}
}
}
@@ -425,7 +425,7 @@ namespace System.Collections.Generic
{
if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task)
{
await tcs.Task;
await tcs.Task.ConfigureAwait(false);
return;
}

View File

@@ -8,9 +8,12 @@ namespace System.Security.Cryptography
/// </summary>
public class CryptographyHelper
{
private static int saltLength = 8;
// "readonly" not added due to UnitTests
#pragma warning disable IDE0044 // Add "readonly" modifier
private static int _saltLength = 8;
#pragma warning restore IDE0044 // Add "readonly" modifier
private readonly string masterKeyFile;
private readonly string _masterKeyFile;
/// <summary>
/// Initializes a new instance of the <see cref="CryptographyHelper"/> class.
@@ -18,17 +21,17 @@ namespace System.Security.Cryptography
/// <param name="keyFile">The (absolute) path to the crypto key file. On <c>null</c> the file 'crypto.key' at the executing assembly location will be used.</param>
public CryptographyHelper(string keyFile = null)
{
masterKeyFile = keyFile;
_masterKeyFile = keyFile;
if (string.IsNullOrWhiteSpace(masterKeyFile))
masterKeyFile = "crypto.key";
if (string.IsNullOrWhiteSpace(_masterKeyFile))
_masterKeyFile = "crypto.key";
if (!Path.IsPathRooted(masterKeyFile))
masterKeyFile = Path.Combine(AppContext.BaseDirectory, masterKeyFile);
if (!Path.IsPathRooted(_masterKeyFile))
_masterKeyFile = Path.Combine(AppContext.BaseDirectory, _masterKeyFile);
string pw = File.Exists(masterKeyFile) ? File.ReadAllText(masterKeyFile) : null;
string pw = File.Exists(_masterKeyFile) ? File.ReadAllText(_masterKeyFile) : null;
if (string.IsNullOrWhiteSpace(pw))
File.WriteAllText(masterKeyFile, GetRandomString(64));
File.WriteAllText(_masterKeyFile, GetRandomString(64));
}
#region Instance methods
@@ -46,8 +49,7 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns>
public byte[] DecryptAes(byte[] cipher, string password = null)
{
if (password == null)
password = File.ReadAllText(masterKeyFile);
password ??= File.ReadAllText(_masterKeyFile);
return AesDecrypt(cipher, password);
}
@@ -63,8 +65,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns>
public byte[] EncryptAes(byte[] plain, string password = null)
{
if (password == null)
password = File.ReadAllText(masterKeyFile);
password ??= File.ReadAllText(_masterKeyFile);
return AesEncrypt(plain, password);
}
@@ -110,8 +111,7 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns>
public byte[] DecryptTripleDes(byte[] cipher, string password = null)
{
if (password == null)
password = File.ReadAllText(masterKeyFile);
password ??= File.ReadAllText(_masterKeyFile);
return TripleDesDecrypt(cipher, password);
}
@@ -127,8 +127,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns>
public byte[] EncryptTripleDes(byte[] plain, string password = null)
{
if (password == null)
password = File.ReadAllText(masterKeyFile);
password ??= File.ReadAllText(_masterKeyFile);
return TripleDesEncrypt(plain, password);
}
@@ -183,8 +182,8 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns>
public static byte[] AesDecrypt(byte[] cipher, string password)
{
byte[] salt = new byte[saltLength];
Array.Copy(cipher, salt, saltLength);
byte[] salt = new byte[_saltLength];
Array.Copy(cipher, salt, _saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt);
using var aes = Aes.Create();
@@ -197,7 +196,7 @@ namespace System.Security.Cryptography
using var ms = new MemoryStream();
using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipher, saltLength, cipher.Length - saltLength);
cs.Write(cipher, _saltLength, cipher.Length - _saltLength);
cs.FlushFinalBlock();
return ms.ToArray();
@@ -224,7 +223,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns>
public static byte[] AesEncrypt(byte[] plain, string password)
{
byte[] salt = GetRandomBytes(saltLength);
byte[] salt = GetRandomBytes(_saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt);
using var aes = Aes.Create();
@@ -269,8 +268,8 @@ namespace System.Security.Cryptography
/// <returns>The decrypted data.</returns>
public static byte[] TripleDesDecrypt(byte[] cipher, string password)
{
byte[] salt = new byte[saltLength];
Array.Copy(cipher, salt, saltLength);
byte[] salt = new byte[_saltLength];
Array.Copy(cipher, salt, _saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt);
using var tdes = TripleDES.Create();
@@ -283,7 +282,7 @@ namespace System.Security.Cryptography
using var ms = new MemoryStream();
using var cs = new CryptoStream(ms, tdes.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipher, saltLength, cipher.Length - saltLength);
cs.Write(cipher, _saltLength, cipher.Length - _saltLength);
cs.FlushFinalBlock();
return ms.ToArray();
@@ -297,7 +296,7 @@ namespace System.Security.Cryptography
/// <returns>The encrypted data (cipher).</returns>
public static byte[] TripleDesEncrypt(byte[] plain, string password)
{
byte[] salt = GetRandomBytes(saltLength);
byte[] salt = GetRandomBytes(_saltLength);
using var gen = new Rfc2898DeriveBytes(password, salt);
using var tdes = TripleDES.Create();

View File

@@ -15,9 +15,9 @@ namespace AMWD.Common.Utilities
{
#region Data
private Timer timer;
private Timer _timer;
private bool nextRunPending;
private bool _nextRunPending;
/// <summary>
/// The synchronisation object.
@@ -130,13 +130,13 @@ namespace AMWD.Common.Utilities
tcs = CreateTcs();
}
IsWaitingToRun = true;
if (timer != null)
if (_timer != null)
{
timer.Change(Delay, Timeout.InfiniteTimeSpan);
_timer.Change(Delay, Timeout.InfiniteTimeSpan);
}
else
{
timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
_timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
}
}
}
@@ -152,9 +152,9 @@ namespace AMWD.Common.Utilities
lock (syncLock)
{
IsWaitingToRun = false;
nextRunPending = false;
timer?.Dispose();
timer = null;
_nextRunPending = false;
_timer?.Dispose();
_timer = null;
if (!IsRunning)
{
localTcs = tcs;
@@ -179,13 +179,13 @@ namespace AMWD.Common.Utilities
return false;
}
IsWaitingToRun = true;
if (timer != null)
if (_timer != null)
{
timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
_timer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
}
else
{
timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
}
return true;
}
@@ -256,7 +256,7 @@ namespace AMWD.Common.Utilities
{
tcs = CreateTcs();
IsWaitingToRun = true;
timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
_timer = new Timer(OnTimerCallback, null, Delay, Timeout.InfiniteTimeSpan);
return this;
}
@@ -285,7 +285,7 @@ namespace AMWD.Common.Utilities
if (IsRunning)
{
// Currently running, remember and do nothing for now
nextRunPending = true;
_nextRunPending = true;
return;
}
IsRunning = true;
@@ -308,12 +308,12 @@ namespace AMWD.Common.Utilities
{
runAgain = false;
IsRunning = false;
nextRunPending = false;
_nextRunPending = false;
localTcs = tcs;
if (!IsWaitingToRun)
{
timer?.Dispose();
timer = null;
_timer?.Dispose();
_timer = null;
}
}
exceptionHandler?.Invoke(ex);
@@ -322,16 +322,16 @@ namespace AMWD.Common.Utilities
{
lock (syncLock)
{
runAgain = nextRunPending;
runAgain = _nextRunPending;
IsRunning = runAgain;
nextRunPending = false;
_nextRunPending = false;
if (!runAgain)
{
if (!IsWaitingToRun)
{
localTcs = tcs;
timer?.Dispose();
timer = null;
_timer?.Dispose();
_timer = null;
}
}
}
@@ -403,19 +403,19 @@ namespace AMWD.Common.Utilities
/// <typeparam name="TResult">The type of the result value.</typeparam>
protected class TaskCompletionSourceWrapper<TResult> : TaskCompletionSourceWrapper
{
private readonly TaskCompletionSource<TResult> tcs;
private readonly TaskCompletionSource<TResult> _tcs;
/// <summary>
/// Gets the <see cref="Task{TResult}"/> of the <see cref="TaskCompletionSource{TResult}"/>.
/// </summary>
public override Task Task => tcs.Task;
public override Task Task => _tcs.Task;
/// <summary>
/// Initializes a new instance of the <see cref="TaskCompletionSourceWrapper{TResult}"/> class.
/// </summary>
public TaskCompletionSourceWrapper()
{
tcs = new TaskCompletionSource<TResult>();
_tcs = new TaskCompletionSource<TResult>();
}
/// <summary>
@@ -424,13 +424,13 @@ namespace AMWD.Common.Utilities
/// </summary>
/// <param name="result">The result value to bind to this <see cref="Task{TResult}"/>.</param>
/// <seealso cref="TaskCompletionSource{TResult}.TrySetResult(TResult)"/>
public void TrySetResult(TResult result) => tcs.TrySetResult(result);
public void TrySetResult(TResult result) => _tcs.TrySetResult(result);
/// <inheritdoc/>
public override void TrySetException(Exception exception) => tcs.TrySetException(exception);
public override void TrySetException(Exception exception) => _tcs.TrySetException(exception);
/// <inheritdoc/>
public override void TrySetCanceled() => tcs.TrySetCanceled();
public override void TrySetCanceled() => _tcs.TrySetCanceled();
}
#endregion Internal TaskCompletionSourceWrapper classes

View File

@@ -150,7 +150,7 @@ namespace AMWD.Common.Utilities
if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily)
return null;
return ipAddress;
return ipAddress.IsIPv4MappedToIPv6 ? ipAddress.MapToIPv4() : ipAddress;
}
return null;