#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Text;
using System.Globalization;
using System.Diagnostics;
using System.IO;
// ReSharper disable UnusedMember.Global
#endregion
namespace Technosoftware.DaAeHdaClient.Utilities
{
///
/// Defines various static utility functions.
///
public static class Utils
{
#region Trace Support
#if DEBUG
private static int traceOutput_ = (int)TraceOutput.DebugAndFile;
private static int traceMasks_ = TraceMasks.All;
#else
private static int traceOutput_ = (int)TraceOutput.FileOnly;
private static int traceMasks_ = (int)TraceMasks.None;
#endif
private static string traceFileName_;
private static long baseLineTicks_ = DateTime.UtcNow.Ticks;
private static object traceFileLock_ = new object();
///
/// The possible trace output mechanisms.
///
public enum TraceOutput
{
///
/// No tracing
///
Off = 0,
///
/// Only write to file (if specified). Default for Release mode.
///
FileOnly = 1,
///
/// Write to debug trace listeners and a file (if specified). Default for Debug mode.
///
DebugAndFile = 2,
///
/// Write to trace listeners and a file (if specified).
///
StdOutAndFile = 3
}
///
/// The masks used to filter trace messages.
///
public static class TraceMasks
{
///
/// Do not output any messages.
///
public const int None = 0x0;
///
/// Output error messages.
///
public const int Error = 0x1;
///
/// Output informational messages.
///
public const int Information = 0x2;
///
/// Output stack traces.
///
public const int StackTrace = 0x4;
///
/// Output basic messages for service calls.
///
public const int Service = 0x8;
///
/// Output detailed messages for service calls.
///
public const int ServiceDetail = 0x10;
///
/// Output basic messages for each operation.
///
public const int Operation = 0x20;
///
/// Output detailed messages for each operation.
///
public const int OperationDetail = 0x40;
///
/// Output messages related to application initialization or shutdown
///
public const int StartStop = 0x80;
///
/// Output messages related to a call to an external system.
///
public const int ExternalSystem = 0x100;
///
/// Output messages related to security
///
public const int Security = 0x200;
///
/// Output all messages.
///
public const int All = 0x7FFFFFFF;
}
///
/// Sets the output for tracing (thead safe).
///
public static void SetTraceOutput(TraceOutput output)
{
lock (traceFileLock_)
{
traceOutput_ = (int)output;
}
}
///
/// Gets the current trace mask settings.
///
public static int TraceMask => traceMasks_;
///
/// Sets the mask for tracing (thead safe).
///
public static void SetTraceMask(int masks)
{
traceMasks_ = masks;
}
///
/// Returns Tracing class instance for event attaching.
///
public static Tracing Tracing => Tracing.Instance;
///
/// Writes a trace statement.
///
private static void TraceWriteLine(string message, params object[] args)
{
// null strings not supported.
if (string.IsNullOrEmpty(message))
{
return;
}
// format the message if format arguments provided.
var output = message;
if (args != null && args.Length > 0)
{
try
{
output = string.Format(CultureInfo.InvariantCulture, message, args);
}
catch (Exception)
{
output = message;
}
}
// write to the log file.
lock (traceFileLock_)
{
// write to debug trace listeners.
if (traceOutput_ == (int)TraceOutput.DebugAndFile)
{
Debug.WriteLine(output);
}
// write to trace listeners.
if (traceOutput_ == (int)TraceOutput.StdOutAndFile)
{
System.Diagnostics.Trace.WriteLine(output);
}
var traceFileName = traceFileName_;
if (traceOutput_ != (int)TraceOutput.Off && !string.IsNullOrEmpty(traceFileName))
{
try
{
var file = new FileInfo(traceFileName);
// limit the file size. hard coded for now - fix later.
var truncated = false;
if (file.Exists && file.Length > 10000000)
{
file.Delete();
truncated = true;
}
using (var writer = new StreamWriter(File.Open(traceFileName, FileMode.Append)))
{
if (truncated)
{
writer.WriteLine("WARNING - LOG FILE TRUNCATED.");
}
writer.WriteLine(output);
writer.Close();
}
}
catch (Exception e)
{
Console.WriteLine(@"Could not write to trace file. Error={0} FilePath={1}", e.Message, traceFileName);
}
}
}
}
///
/// Sets the path to the log file to use for tracing.
///
public static void SetTraceLog(string filePath, bool deleteExisting)
{
// turn tracing on.
lock (traceFileLock_)
{
// check if tracing is being turned off.
if (string.IsNullOrEmpty(filePath))
{
traceFileName_ = null;
return;
}
traceFileName_ = GetAbsoluteFilePath(filePath, true, false, true);
if (traceOutput_ == (int)TraceOutput.Off)
{
traceOutput_ = (int)TraceOutput.FileOnly;
}
try
{
var file = new FileInfo(traceFileName_);
if (deleteExisting && file.Exists)
{
file.Delete();
}
// write initial log message.
TraceWriteLine(
"\r\nPID:{2} {1} Logging started at {0} {1}",
DateTime.Now,
new string('*', 25),
Process.GetCurrentProcess().Id);
}
catch (Exception e)
{
TraceWriteLine(e.Message, null);
}
}
}
///
/// Writes an informational message to the trace log.
///
public static void Trace(string format, params object[] args)
{
Trace(TraceMasks.Information, format, false, args);
}
///
/// Writes an exception/error message to the trace log.
///
public static void Trace(Exception e, string format, params object[] args)
{
Trace(e, format, false, args);
}
///
/// Writes a general message to the trace log.
///
public static void Trace(int traceMask, string format, params object[] args)
{
Trace(traceMask, format, false, args);
}
///
/// Writes an exception/error message to the trace log.
///
internal static void Trace(Exception e, string format, bool handled, params object[] args)
{
var message = new StringBuilder();
// format message.
if (args != null && args.Length > 0)
{
try
{
message.AppendFormat(CultureInfo.InvariantCulture, format, args);
}
catch (Exception)
{
message.Append(format);
}
}
else
{
message.Append(format);
}
// append exception information.
if (e != null)
{
if (e is OpcResultException sre)
{
message.AppendFormat(CultureInfo.InvariantCulture, " {0} '{1}'", sre.Result.Code, sre.Message);
}
else
{
message.AppendFormat(CultureInfo.InvariantCulture, " {0} '{1}'", e.GetType().Name, e.Message);
}
// append stack trace.
if ((traceMasks_ & TraceMasks.StackTrace) != 0)
{
message.AppendFormat(CultureInfo.InvariantCulture, "\r\n\r\n{0}\r\n", new string('=', 40));
message.Append(e.StackTrace);
message.AppendFormat(CultureInfo.InvariantCulture, "\r\n{0}\r\n", new string('=', 40));
}
}
// trace message.
Trace(TraceMasks.Error, message.ToString(), handled, null);
}
///
/// Writes the message to the trace log.
///
private static void Trace(int traceMask, string format, bool handled, params object[] args)
{
if (!handled)
{
Tracing.Instance.RaiseTraceEvent(new TraceEventArgs(traceMask, format, string.Empty, null, args));
}
// do nothing if mask not enabled.
if ((traceMasks_ & traceMask) == 0)
{
return;
}
var message = new StringBuilder();
// append process and timestamp.
message.AppendFormat("{0} - ", Process.GetCurrentProcess().Id);
message.AppendFormat("{0:d} {0:HH:mm:ss.fff} ", HiResClock.UtcNow.ToLocalTime());
// format message.
if (args != null && args.Length > 0)
{
try
{
message.AppendFormat(CultureInfo.InvariantCulture, format, args);
}
catch (Exception)
{
message.Append(format);
}
}
else
{
message.Append(format);
}
TraceWriteLine(message.ToString(), null);
}
#endregion
#region File Access
///
/// Replaces a prefix enclosed in '%' with a special folder or environment variable path (e.g. %ProgramFiles%\MyCompany).
///
public static string ReplaceSpecialFolderNames(string input)
{
// nothing to do for nulls.
if (string.IsNullOrEmpty(input))
{
return null;
}
// check for absolute path.
if (input.Length > 1 && ((input[0] == '\\' && input[1] == '\\') || input[1] == ':'))
{
return input;
}
// check for special folder prefix.
if (input[0] != '%')
{
return input;
}
// extract special folder name.
string folder;
string path;
var index = input.IndexOf('%', 1);
if (index == -1)
{
folder = input.Substring(1);
path = string.Empty;
}
else
{
folder = input.Substring(1, index - 1);
path = input.Substring(index + 1);
}
var buffer = new StringBuilder();
// check for special folder.
try
{
var specialFolder = (Environment.SpecialFolder)Enum.Parse(
typeof(Environment.SpecialFolder),
folder,
true);
buffer.Append(Environment.GetFolderPath(specialFolder));
}
// check for generic environment variable.
catch (Exception)
{
var value = Environment.GetEnvironmentVariable(folder);
if (value != null)
{
buffer.Append(value);
}
}
// construct new path.
buffer.Append(path);
return buffer.ToString();
}
///
/// Checks if the file path is a relative path and returns an absolute path relative to the EXE location.
///
public static string GetAbsoluteFilePath(string filePath)
{
return GetAbsoluteFilePath(filePath, false, true, false);
}
///
/// Checks if the file path is a relative path and returns an absolute path relative to the EXE location.
///
public static string GetAbsoluteFilePath(string filePath, bool checkCurrentDirectory, bool throwOnError, bool createAlways)
{
filePath = ReplaceSpecialFolderNames(filePath);
if (!string.IsNullOrEmpty(filePath))
{
var file = new FileInfo(filePath);
// check for absolute path.
var isAbsolute = filePath.StartsWith("\\\\", StringComparison.Ordinal) || filePath.IndexOf(':') == 1;
if (isAbsolute)
{
if (file.Exists)
{
return filePath;
}
if (createAlways)
{
return CreateFile(file, filePath, throwOnError);
}
}
if (!isAbsolute)
{
// look current directory.
if (checkCurrentDirectory)
{
if (!file.Exists)
{
file = new FileInfo(Format("{0}\\{1}", Environment.CurrentDirectory, filePath));
}
if (file.Exists)
{
return file.FullName;
}
if (createAlways)
{
return CreateFile(file, filePath, throwOnError);
}
}
// look executable directory.
if (!file.Exists)
{
var executablePath = Environment.GetCommandLineArgs()[0];
var executable = new FileInfo(executablePath);
if (executable.Exists)
{
file = new FileInfo(Format("{0}\\{1}", executable.DirectoryName, filePath));
}
if (file.Exists)
{
return file.FullName;
}
if (createAlways)
{
return CreateFile(file, filePath, throwOnError);
}
}
}
}
// file does not exist.
if (throwOnError)
{
throw new OpcResultException(new OpcResult(OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null),
$"File does not exist: {filePath}\r\nCurrent directory is: {Environment.CurrentDirectory}");
}
return null;
}
///
/// Creates an empty file.
///
private static string CreateFile(FileInfo file, string filePath, bool throwOnError)
{
try
{
// create the directory as required.
if (file.Directory != null && !file.Directory.Exists)
{
Directory.CreateDirectory(file.DirectoryName);
}
// open and close the file.
using (file.Open(FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite))
{
return filePath;
}
}
catch (Exception)
{
if (throwOnError)
{
throw;
}
return filePath;
}
}
///
/// Formats a message using the invariant locale.
///
public static string Format(string text, params object[] args)
{
return string.Format(CultureInfo.InvariantCulture, text, args);
}
#endregion
}
#region Tracing Class
///
/// Used as underlying tracing object for event processing.
///
public class Tracing
{
#region Private Members
private static object syncRoot_ = new object();
private static Tracing instance_;
#endregion Private Members
#region Singleton Instance
///
/// Private constructor.
///
private Tracing()
{ }
///
/// Internal Singleton Instance getter.
///
internal static Tracing Instance
{
get
{
if (instance_ == null)
{
lock (syncRoot_)
{
if (instance_ == null)
{
instance_ = new Tracing();
}
}
}
return instance_;
}
}
#endregion Singleton Instance
#region Public Events
///
/// Occurs when a trace call is made.
///
public event EventHandler TraceEventHandler;
#endregion Public Events
internal void RaiseTraceEvent(TraceEventArgs eventArgs)
{
if (TraceEventHandler != null)
{
try
{
TraceEventHandler(this, eventArgs);
}
catch (Exception ex)
{
Utils.Trace(ex, "Exception invoking Trace Event Handler", true, null);
}
}
}
}
#endregion Tracing Class
#region TraceEventArgs Class
///
/// The event arguments provided when a trace event is raised.
///
public class TraceEventArgs : EventArgs
{
#region Constructors
///
/// Initializes a new instance of the TraceEventArgs class.
///
/// The trace mask.
/// The format.
/// The message.
/// The exception.
/// The arguments.
internal TraceEventArgs(int traceMask, string format, string message, Exception exception, object[] args)
{
TraceMask = traceMask;
Format = format;
Message = message;
Exception = exception;
Arguments = args;
}
#endregion Constructors
#region Public Properties
///
/// Gets the trace mask.
///
public int TraceMask { get; }
///
/// Gets the format.
///
public string Format { get; }
///
/// Gets the arguments.
///
public object[] Arguments { get; }
///
/// Gets the message.
///
public string Message { get; }
///
/// Gets the exception.
///
public Exception Exception { get; }
#endregion Public Properties
}
#endregion TraceEventArgs Class
}