mirror of
https://github.com/SabreTools/SabreTools.IO.git
synced 2026-02-14 13:46:12 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c125dc4ec0 | ||
|
|
f154ae47c0 | ||
|
|
0d0e960b98 | ||
|
|
4a9f84ab66 | ||
|
|
39277ee443 | ||
|
|
ed367ace6d | ||
|
|
80e72832a4 |
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Test.Extensions
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -73,6 +74,19 @@ namespace SabreTools.IO.Extensions
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a WORD (2-byte) from the base stream
|
||||
/// </summary>
|
||||
public static ushort ReadWORD(this BinaryReader reader)
|
||||
=> reader.ReadUInt16();
|
||||
|
||||
/// <summary>
|
||||
/// Read a WORD (2-byte) from the base stream
|
||||
/// </summary>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static ushort ReadWORDBigEndian(this BinaryReader reader)
|
||||
=> reader.ReadUInt16BigEndian();
|
||||
|
||||
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <inheritdoc cref="BinaryReader.ReadHalf"/>
|
||||
@@ -154,6 +168,19 @@ namespace SabreTools.IO.Extensions
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a DWORD (4-byte) from the base stream
|
||||
/// </summary>
|
||||
public static uint ReadDWORD(this BinaryReader reader)
|
||||
=> reader.ReadUInt32();
|
||||
|
||||
/// <summary>
|
||||
/// Read a DWORD (4-byte) from the base stream
|
||||
/// </summary>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static uint ReadDWORDBigEndian(this BinaryReader reader)
|
||||
=> reader.ReadUInt32BigEndian();
|
||||
|
||||
/// <inheritdoc cref="BinaryReader.ReadSingle"/>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static float ReadSingleBigEndian(this BinaryReader reader)
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
53
SabreTools.IO/Extensions/ByteArrayExtensions.cs
Normal file
53
SabreTools.IO/Extensions/ByteArrayExtensions.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
public static class ByteArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert a byte array to a hex string
|
||||
/// </summary>
|
||||
public static string? ByteArrayToString(byte[]? bytes)
|
||||
{
|
||||
// If we get null in, we send null out
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
string hex = BitConverter.ToString(bytes);
|
||||
return hex.Replace("-", string.Empty).ToLowerInvariant();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a hex string to a byte array
|
||||
/// </summary>
|
||||
public static byte[]? StringToByteArray(string? hex)
|
||||
{
|
||||
// If we get null in, we send null out
|
||||
if (string.IsNullOrEmpty(hex))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
int NumberChars = hex!.Length;
|
||||
byte[] bytes = new byte[NumberChars / 2];
|
||||
for (int i = 0; i < NumberChars; i += 2)
|
||||
{
|
||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -105,6 +106,19 @@ namespace SabreTools.IO.Extensions
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a WORD (2-byte) and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static ushort ReadWORD(this byte[] content, ref int offset)
|
||||
=> content.ReadUInt16(ref offset);
|
||||
|
||||
/// <summary>
|
||||
/// Read a WORD (2-byte) and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static ushort ReadWORDBigEndian(this byte[] content, ref int offset)
|
||||
=> content.ReadUInt16BigEndian(ref offset);
|
||||
|
||||
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
@@ -220,6 +234,19 @@ namespace SabreTools.IO.Extensions
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a DWORD (4-byte) and increment the pointer to an array
|
||||
/// </summary>
|
||||
public static uint ReadDWORD(this byte[] content, ref int offset)
|
||||
=> content.ReadUInt32(ref offset);
|
||||
|
||||
/// <summary>
|
||||
/// Read a DWORD (4-byte) and increment the pointer to an array
|
||||
/// </summary>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static uint ReadDWORDBigEndian(this byte[] content, ref int offset)
|
||||
=> content.ReadUInt32BigEndian(ref offset);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Single and increment the pointer to an array
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -89,6 +90,19 @@ namespace SabreTools.IO.Extensions
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a WORD (2-byte) from the stream
|
||||
/// </summary>
|
||||
public static ushort ReadWORD(this Stream stream)
|
||||
=> stream.ReadUInt16();
|
||||
|
||||
/// <summary>
|
||||
/// Read a WORD (2-byte) from the stream
|
||||
/// </summary>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static ushort ReadWORDBigEndian(this Stream stream)
|
||||
=> stream.ReadUInt16BigEndian();
|
||||
|
||||
// Half was introduced in net5.0 but doesn't have a BitConverter implementation until net6.0
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
@@ -204,6 +218,19 @@ namespace SabreTools.IO.Extensions
|
||||
return BitConverter.ToUInt32(buffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a DWORD (4-byte) from the stream
|
||||
/// </summary>
|
||||
public static uint ReadDWORD(this Stream stream)
|
||||
=> stream.ReadUInt32();
|
||||
|
||||
/// <summary>
|
||||
/// Read a DWORD (4-byte) from the stream
|
||||
/// </summary>
|
||||
/// <remarks>Reads in big-endian format</remarks>
|
||||
public static uint ReadDWORDBigEndian(this Stream stream)
|
||||
=> stream.ReadUInt32BigEndian();
|
||||
|
||||
/// <summary>
|
||||
/// Read a Single from the stream
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0618 // Obsolete unmanaged types
|
||||
namespace SabreTools.IO.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
47
SabreTools.IO/Logging/Converters.cs
Normal file
47
SabreTools.IO/Logging/Converters.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace SabreTools.IO.Logging
|
||||
{
|
||||
public static class Converters
|
||||
{
|
||||
#region String to Enum
|
||||
|
||||
/// <summary>
|
||||
/// Get the LogLevel value for an input string, if possible
|
||||
/// </summary>
|
||||
/// <param name="value">String value to parse/param>
|
||||
/// <returns></returns>
|
||||
public static LogLevel AsLogLevel(this string? value)
|
||||
{
|
||||
return value?.ToLowerInvariant() switch
|
||||
{
|
||||
"verbose" => LogLevel.VERBOSE,
|
||||
"user" => LogLevel.USER,
|
||||
"warning" => LogLevel.WARNING,
|
||||
"error" => LogLevel.ERROR,
|
||||
_ => LogLevel.VERBOSE,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enum to String
|
||||
|
||||
/// <summary>
|
||||
/// Get string value from input LogLevel
|
||||
/// </summary>
|
||||
/// <param name="value">LogLevel to get value from</param>
|
||||
/// <returns>String corresponding to the LogLevel</returns>
|
||||
public static string? FromLogLevel(this LogLevel value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
LogLevel.VERBOSE => "VERBOSE",
|
||||
LogLevel.USER => "USER",
|
||||
LogLevel.WARNING => "WARNING",
|
||||
LogLevel.ERROR => "ERROR",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
SabreTools.IO/Logging/Enums.cs
Normal file
13
SabreTools.IO/Logging/Enums.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace SabreTools.IO.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Severity of the logging statement
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
VERBOSE = 0,
|
||||
USER,
|
||||
WARNING,
|
||||
ERROR,
|
||||
}
|
||||
}
|
||||
61
SabreTools.IO/Logging/InternalStopwatch.cs
Normal file
61
SabreTools.IO/Logging/InternalStopwatch.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.IO.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Stopwatch class for keeping track of duration in the code
|
||||
/// </summary>
|
||||
public class InternalStopwatch
|
||||
{
|
||||
private string _subject;
|
||||
private DateTime _startTime;
|
||||
private readonly Logger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that initalizes the stopwatch
|
||||
/// </summary>
|
||||
public InternalStopwatch()
|
||||
{
|
||||
_subject = string.Empty;
|
||||
_logger = new Logger(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that initalizes the stopwatch with a subject and starts immediately
|
||||
/// </summary>
|
||||
/// <param name="subject">Subject of the stopwatch</param>
|
||||
public InternalStopwatch(string subject)
|
||||
{
|
||||
_subject = subject;
|
||||
_logger = new Logger(this);
|
||||
Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the stopwatch and display subject text
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
_startTime = DateTime.Now;
|
||||
_logger.User($"{_subject}...");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the stopwatch and display subject text
|
||||
/// </summary>
|
||||
/// <param name="subject">Text to show on stopwatch start</param>
|
||||
public void Start(string subject)
|
||||
{
|
||||
_subject = subject;
|
||||
Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the stopwatch and display subject text
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_logger.User($"{_subject} completed in {DateTime.Now.Subtract(_startTime):G}");
|
||||
}
|
||||
}
|
||||
}
|
||||
79
SabreTools.IO/Logging/LogEventArgs.cs
Normal file
79
SabreTools.IO/Logging/LogEventArgs.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.IO.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic delegate type for log events
|
||||
/// </summary>
|
||||
public delegate void LogEventHandler(object? sender, LogEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Logging specific event arguments
|
||||
/// </summary>
|
||||
public class LogEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// LogLevel for the event
|
||||
/// </summary>
|
||||
public readonly LogLevel LogLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Log statement to be printed
|
||||
/// </summary>
|
||||
public readonly string? Statement = null;
|
||||
|
||||
/// <summary>
|
||||
/// Exception to be passed along to the event handler
|
||||
/// </summary>
|
||||
public readonly Exception? Exception = null;
|
||||
|
||||
/// <summary>
|
||||
/// Total count for progress log events
|
||||
/// </summary>
|
||||
public readonly long? TotalCount = null;
|
||||
|
||||
/// <summary>
|
||||
/// Current count for progress log events
|
||||
/// </summary>
|
||||
public readonly long? CurrentCount = null;
|
||||
|
||||
/// <summary>
|
||||
/// Statement constructor
|
||||
/// </summary>
|
||||
public LogEventArgs(LogLevel logLevel, string statement)
|
||||
{
|
||||
LogLevel = logLevel;
|
||||
Statement = statement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statement constructor
|
||||
/// </summary>
|
||||
public LogEventArgs(LogLevel logLevel, Exception exception)
|
||||
{
|
||||
LogLevel = logLevel;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statement and exception constructor
|
||||
/// </summary>
|
||||
public LogEventArgs(LogLevel logLevel, string statement, Exception exception)
|
||||
{
|
||||
LogLevel = logLevel;
|
||||
Statement = statement;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progress constructor
|
||||
/// </summary>
|
||||
public LogEventArgs(long total, long current, LogLevel logLevel, string? statement = null)
|
||||
{
|
||||
LogLevel = logLevel;
|
||||
Statement = statement;
|
||||
TotalCount = total;
|
||||
CurrentCount = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
180
SabreTools.IO/Logging/Logger.cs
Normal file
180
SabreTools.IO/Logging/Logger.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.IO.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Per-class logging
|
||||
/// </summary>
|
||||
public class Logger
|
||||
{
|
||||
/// <summary>
|
||||
/// Instance associated with this logger
|
||||
/// </summary>
|
||||
/// TODO: Derive class name for this object, if possible
|
||||
private readonly object? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public Logger(object? instance = null)
|
||||
{
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
#region Log Event Triggers
|
||||
|
||||
#region Verbose
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Verbose(string output)
|
||||
=> LoggerImpl.Verbose(_instance, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Verbose(Exception ex)
|
||||
=> LoggerImpl.Verbose(_instance, ex);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception and string as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Verbose(Exception ex, string output)
|
||||
=> LoggerImpl.Verbose(_instance, ex, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given verbose progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public void Verbose(long total, long current, string? output = null)
|
||||
=> LoggerImpl.Verbose(_instance, total, current, output);
|
||||
|
||||
#endregion
|
||||
|
||||
#region User
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void User(string output)
|
||||
=> LoggerImpl.User(_instance, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void User(Exception ex)
|
||||
=> LoggerImpl.User(_instance, ex);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception and string as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void User(Exception ex, string output)
|
||||
=> LoggerImpl.User(_instance, ex, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given user progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public void User(long total, long current, string? output = null)
|
||||
=> LoggerImpl.User(_instance, total, current, output);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Warning
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Warning(string output)
|
||||
=> LoggerImpl.Warning(_instance, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Warning(Exception ex)
|
||||
=> LoggerImpl.Warning(_instance, ex);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception and string as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Warning(Exception ex, string output)
|
||||
=> LoggerImpl.Warning(_instance, ex, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given warning progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public void Warning(long total, long current, string? output = null)
|
||||
=> LoggerImpl.Warning(_instance, total, current, output);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given string as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Error(string output)
|
||||
=> LoggerImpl.Error(_instance, output);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given exception as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Error(Exception ex)
|
||||
=> LoggerImpl.Error(_instance, ex);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given exception and string as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public void Error(Exception ex, string output)
|
||||
=> LoggerImpl.Error(_instance, ex, output);
|
||||
|
||||
/// <summary>
|
||||
/// Write the given error progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public void Error(long total, long current, string? output = null)
|
||||
=> LoggerImpl.Error(_instance, total, current, output);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
458
SabreTools.IO/Logging/LoggerImpl.cs
Normal file
458
SabreTools.IO/Logging/LoggerImpl.cs
Normal file
@@ -0,0 +1,458 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.IO.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal logging implementation
|
||||
/// </summary>
|
||||
public static class LoggerImpl
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Optional output filename for logs
|
||||
/// </summary>
|
||||
public static string? Filename { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if we're logging to file or not
|
||||
/// </summary>
|
||||
public static bool LogToFile { get { return !string.IsNullOrEmpty(Filename); } }
|
||||
|
||||
/// <summary>
|
||||
/// Optional output log directory
|
||||
/// </summary>
|
||||
public static string? LogDirectory { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the lowest log level to output
|
||||
/// </summary>
|
||||
public static LogLevel LowestLogLevel { get; set; } = LogLevel.VERBOSE;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to prefix log lines with level and datetime
|
||||
/// </summary>
|
||||
public static bool AppendPrefix { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to throw if an exception is logged
|
||||
/// </summary>
|
||||
public static bool ThrowOnError { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Logging start time for metrics
|
||||
/// </summary>
|
||||
public static DateTime StartTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there were errors logged
|
||||
/// </summary>
|
||||
public static bool LoggedErrors { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there were warnings logged
|
||||
/// </summary>
|
||||
public static bool LoggedWarnings { get; private set; } = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
/// <summary>
|
||||
/// StreamWriter representing the output log file
|
||||
/// </summary>
|
||||
private static StreamWriter? _log;
|
||||
|
||||
/// <summary>
|
||||
/// Object lock for multithreaded logging
|
||||
/// </summary>
|
||||
private static readonly object _lock = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Control
|
||||
|
||||
/// <summary>
|
||||
/// Generate and set the log filename
|
||||
/// </summary>
|
||||
/// <param name="filename">Base filename to use</param>
|
||||
/// <param name="addDate">True to append a date to the filename, false otherwise</param>
|
||||
public static void SetFilename(string filename, bool addDate = true)
|
||||
{
|
||||
// Get the full log path
|
||||
string fullPath = Path.GetFullPath(filename);
|
||||
|
||||
// Set the log directory
|
||||
LogDirectory = Path.GetDirectoryName(fullPath);
|
||||
|
||||
// Set the
|
||||
if (addDate)
|
||||
Filename = $"{Path.GetFileNameWithoutExtension(fullPath)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{fullPath.GetNormalizedExtension()}";
|
||||
else
|
||||
Filename = Path.GetFileName(fullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start logging by opening output file (if necessary)
|
||||
/// </summary>
|
||||
/// <returns>True if the logging was started correctly, false otherwise</returns>
|
||||
public static bool Start()
|
||||
{
|
||||
// Setup the logging handler to always use the internal log
|
||||
LogEventHandler += HandleLogEvent;
|
||||
|
||||
// Start the logging
|
||||
StartTime = DateTime.Now;
|
||||
if (!LogToFile)
|
||||
return true;
|
||||
|
||||
// Setup file output and perform initial log
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LogDirectory) && !Directory.Exists(LogDirectory))
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
FileStream logfile = File.Create(Path.Combine(LogDirectory ?? string.Empty, Filename ?? string.Empty));
|
||||
#if NET20 || NET35 || NET40
|
||||
_log = new StreamWriter(logfile, Encoding.UTF8, 4096)
|
||||
#else
|
||||
_log = new StreamWriter(logfile, Encoding.UTF8, 4096, true)
|
||||
#endif
|
||||
{
|
||||
AutoFlush = true
|
||||
};
|
||||
|
||||
_log.WriteLine($"Logging started {StartTime:yyyy-MM-dd HH:mm:ss}");
|
||||
_log.WriteLine($"Command run: {string.Join(" ", Environment.GetCommandLineArgs())}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End logging by closing output file (if necessary)
|
||||
/// </summary>
|
||||
/// <param name="suppress">True if all ending output is to be suppressed, false otherwise (default)</param>
|
||||
/// <returns>True if the logging was ended correctly, false otherwise</returns>
|
||||
public static bool Close(bool suppress = false)
|
||||
{
|
||||
if (!suppress)
|
||||
{
|
||||
if (LoggedWarnings)
|
||||
Console.WriteLine("There were warnings in the last run! Check the log for more details");
|
||||
|
||||
if (LoggedErrors)
|
||||
Console.WriteLine("There were errors in the last run! Check the log for more details");
|
||||
|
||||
TimeSpan span = DateTime.Now.Subtract(StartTime);
|
||||
|
||||
#if NET20 || NET35
|
||||
string total = span.ToString();
|
||||
#else
|
||||
// Special case for multi-day runs
|
||||
string total;
|
||||
if (span >= TimeSpan.FromDays(1))
|
||||
total = span.ToString(@"d\:hh\:mm\:ss");
|
||||
else
|
||||
total = span.ToString(@"hh\:mm\:ss");
|
||||
#endif
|
||||
|
||||
if (!LogToFile)
|
||||
{
|
||||
Console.WriteLine($"Total runtime: {total}");
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_log?.WriteLine($"Logging ended {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
_log?.WriteLine($"Total runtime: {total}");
|
||||
Console.WriteLine($"Total runtime: {total}");
|
||||
_log?.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_log?.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling
|
||||
|
||||
/// <summary>
|
||||
/// Handler for log events
|
||||
/// </summary>
|
||||
public static event LogEventHandler LogEventHandler = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// Default log event handling
|
||||
/// </summary>
|
||||
public static void HandleLogEvent(object? sender, LogEventArgs args)
|
||||
{
|
||||
// Null args means we can't handle it
|
||||
if (args == null)
|
||||
return;
|
||||
|
||||
// If we have an exception and we're throwing on that
|
||||
if (ThrowOnError && args.Exception != null)
|
||||
throw args.Exception;
|
||||
|
||||
// If we have a warning or error, set the flags accordingly
|
||||
if (args.LogLevel == LogLevel.WARNING)
|
||||
LoggedWarnings = true;
|
||||
if (args.LogLevel == LogLevel.ERROR)
|
||||
LoggedErrors = true;
|
||||
|
||||
// Setup the statement based on the inputs
|
||||
string logLine;
|
||||
if (args.Exception != null)
|
||||
{
|
||||
logLine = $"{(args.Statement != null ? args.Statement + ": " : string.Empty)}{args.Exception}";
|
||||
}
|
||||
else if (args.TotalCount != null && args.CurrentCount != null)
|
||||
{
|
||||
double percentage = ((double)args.CurrentCount.Value / args.TotalCount.Value) * 100;
|
||||
logLine = $"{percentage:N2}%{(args.Statement != null ? ": " + args.Statement : string.Empty)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
logLine = args.Statement ?? string.Empty;
|
||||
}
|
||||
|
||||
// Then write to the log
|
||||
Log(logLine, args.LogLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string to the log output
|
||||
/// </summary>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <param name="loglevel">Severity of the information being logged</param>
|
||||
private static void Log(string output, LogLevel loglevel)
|
||||
{
|
||||
// If the log level is less than the filter level, we skip it but claim we didn't
|
||||
if (loglevel < LowestLogLevel)
|
||||
return;
|
||||
|
||||
// USER and ERROR writes to console
|
||||
if (loglevel == LogLevel.USER || loglevel == LogLevel.ERROR)
|
||||
Console.WriteLine((loglevel == LogLevel.ERROR && AppendPrefix ? loglevel.FromLogLevel() + " " : string.Empty) + output);
|
||||
|
||||
// If we're writing to file, use the existing stream
|
||||
if (LogToFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_log?.WriteLine((AppendPrefix ? $"{loglevel.FromLogLevel()} - {DateTime.Now} - " : string.Empty) + output);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ThrowOnError)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
Console.WriteLine("Could not write to log file!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Log Event Triggers
|
||||
|
||||
#region Verbose
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Verbose(object? instance, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, output));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Verbose(object? instance, Exception ex)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception and string as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Verbose(object? instance, Exception ex, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, output, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given verbose progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void Verbose(object? instance, long total, long current, string? output = null)
|
||||
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.VERBOSE, output));
|
||||
|
||||
#endregion
|
||||
|
||||
#region User
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void User(object? instance, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.USER, output));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void User(object? instance, Exception ex)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.USER, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception and string as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void User(object? instance, Exception ex, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.USER, output, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given user progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void User(object? instance, long total, long current, string? output = null)
|
||||
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.USER, output));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Warning
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Warning(object? instance, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, output));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Warning(object? instance, Exception ex)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, ex));
|
||||
|
||||
//// <summary>
|
||||
/// Write the given exception and string as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Warning(object? instance, Exception ex, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, output, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given warning progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void Warning(object? instance, long total, long current, string? output = null)
|
||||
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.WARNING, output));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given string as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Error(object? instance, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, output));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given exception as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Error(object? instance, Exception ex)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given exception and string as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Error(object? instance, Exception ex, string output)
|
||||
=> LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, output, ex));
|
||||
|
||||
/// <summary>
|
||||
/// Write the given error progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void Error(object? instance, long total, long current, string? output = null)
|
||||
=> LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.ERROR, output));
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.4.12</Version>
|
||||
<WarningsNotAsErrors>CS0618</WarningsNotAsErrors>
|
||||
<Version>1.4.13</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
|
||||
Reference in New Issue
Block a user