Major upgrade
This commit is contained in:
@@ -16,22 +16,10 @@ GOVERNMENT.
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
using System;
|
||||
using ProgramLib;
|
||||
using Raytheon.Common;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
internal abstract class BasicTest
|
||||
internal abstract class BasicAction
|
||||
{
|
||||
protected abstract void CheckPrerequisite();
|
||||
|
||||
protected abstract void ExecuteTest();
|
||||
|
||||
public void RunTest()
|
||||
{
|
||||
CheckPrerequisite();
|
||||
ExecuteTest();
|
||||
}
|
||||
abstract public void Run();
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,7 @@ GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
@@ -31,12 +26,14 @@ namespace ProgramLib
|
||||
{
|
||||
protected Thread _thread;
|
||||
|
||||
protected ManualResetEvent _quitEvent = new ManualResetEvent(false);
|
||||
|
||||
/// <summary>
|
||||
/// Spawn thread and start it
|
||||
/// </summary>
|
||||
/// <param name=""></param>
|
||||
/// <returns></returns>
|
||||
public void Start()
|
||||
public void Start()
|
||||
{
|
||||
_thread = new Thread(new ThreadStart(DoWork));
|
||||
_thread.Start();
|
||||
@@ -55,15 +52,18 @@ namespace ProgramLib
|
||||
/// </summary>
|
||||
/// <param name=""></param>
|
||||
/// <returns></returns>
|
||||
public virtual void Quit() { }
|
||||
public virtual void Quit()
|
||||
{
|
||||
_quitEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for thread to exit
|
||||
/// </summary>
|
||||
/// <param name=""></param>
|
||||
/// <returns></returns>
|
||||
public virtual void WaitForExit()
|
||||
{
|
||||
public virtual void WaitForExit()
|
||||
{
|
||||
if (_thread != null)
|
||||
{
|
||||
// wait for thread to terminate
|
||||
|
||||
226
Source/Program/Common/Comm/TcpClient.cs
Normal file
226
Source/Program/Common/Comm/TcpClient.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for controlling a TCP client communication device
|
||||
/// </summary>
|
||||
internal class TcpClient
|
||||
{
|
||||
#region PrivateClassMembers
|
||||
|
||||
private Socket _sock;
|
||||
private string _remoteAddress;
|
||||
private int _remotePort;
|
||||
private IPEndPoint _remoteEP = null;
|
||||
private IPAddress _ipAddress = null;
|
||||
private object _syncObj = new object();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="remoteAddress"></param>
|
||||
/// <param name="remotePort"></param>
|
||||
public TcpClient(string remoteAddress, int remotePort)
|
||||
{
|
||||
_remoteAddress = remoteAddress;
|
||||
_remotePort = remotePort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// initialize instrument
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// if remoteAddress is a hostname
|
||||
if (!IPAddress.TryParse(_remoteAddress, out _ipAddress))
|
||||
{
|
||||
string preferredSubnet = "";
|
||||
|
||||
IPHostEntry host = Dns.GetHostEntry(_remoteAddress);
|
||||
foreach (IPAddress ip in host.AddressList)
|
||||
{
|
||||
AddressFamily af = ip.AddressFamily;
|
||||
if (af == AddressFamily.InterNetwork)
|
||||
{
|
||||
if (preferredSubnet != String.Empty)
|
||||
{
|
||||
if (Regex.IsMatch(ip.ToString(), preferredSubnet, RegexOptions.IgnoreCase))
|
||||
_ipAddress = ip;
|
||||
}
|
||||
else
|
||||
_ipAddress = ip;
|
||||
|
||||
if (_ipAddress != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_ipAddress != null)
|
||||
{
|
||||
if (_remoteEP == null)
|
||||
_remoteEP = new IPEndPoint(_ipAddress, _remotePort);
|
||||
}
|
||||
else
|
||||
throw new Exception($"Unable to create IPEndPoint to {_remoteAddress}, port {_remotePort}");
|
||||
|
||||
if (_sock == null)
|
||||
_sock = new Socket(_ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to remote host
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Connect()
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_sock.Connected && IsRemoteHostAlive())
|
||||
_sock.Connect(_remoteEP);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_sock = new Socket(_ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
if (IsRemoteHostAlive())
|
||||
_sock.Connect(_remoteEP);
|
||||
}
|
||||
catch (Exception) { throw; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from remote host
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Disconnect()
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (_sock.Connected)
|
||||
{
|
||||
_sock.Shutdown(SocketShutdown.Both);
|
||||
_sock.Disconnect(true);
|
||||
_sock.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ping if remote host is alive
|
||||
/// </summary>
|
||||
/// <returns>true/false</returns>
|
||||
bool IsRemoteHostAlive()
|
||||
{
|
||||
bool isRemoteHostAlive = true;
|
||||
|
||||
//Do a ping test to see if the server is reachable
|
||||
try
|
||||
{
|
||||
Ping pingTest = new Ping();
|
||||
PingReply reply = pingTest.Send(_ipAddress);
|
||||
if (reply.Status != IPStatus.Success)
|
||||
isRemoteHostAlive = false;
|
||||
}
|
||||
catch (PingException)
|
||||
{
|
||||
isRemoteHostAlive = false;
|
||||
}
|
||||
|
||||
//See if the tcp state is ok
|
||||
if (_sock.Poll(5000, SelectMode.SelectRead) && (_sock.Available == 0))
|
||||
{
|
||||
isRemoteHostAlive = false;
|
||||
}
|
||||
|
||||
return isRemoteHostAlive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the device.
|
||||
/// </summary>
|
||||
/// <param name="dataBuf"></param>
|
||||
/// <param name="responseTimeOutMs"></param>
|
||||
/// <returns>The number of bytes read</returns>
|
||||
public uint Read(ref byte[] dataBuf)
|
||||
{
|
||||
int bytesRec = 0;
|
||||
lock (_syncObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
bytesRec = _sock.Receive(dataBuf);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
bytesRec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint)bytesRec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the read timeout
|
||||
/// </summary>
|
||||
/// <param name="timeoutMs"></param>
|
||||
public void SetReadTimeout(uint timeoutMs)
|
||||
{
|
||||
_sock.ReceiveTimeout = (int)timeoutMs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data to device.
|
||||
/// </summary>
|
||||
/// <param name="dataBuf"></param>
|
||||
/// <returns>The number of bytes written</returns>
|
||||
public uint Write(byte[] dataBuf)
|
||||
{
|
||||
int bytesWritten = 0;
|
||||
lock (_syncObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
bytesWritten = _sock.Send(dataBuf, dataBuf.Length, SocketFlags.None);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
bytesWritten = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint)bytesWritten;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
34
Source/Program/Common/ConfigLogic/CableSelfTestConfigXml.cs
Normal file
34
Source/Program/Common/ConfigLogic/CableSelfTestConfigXml.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Define non-specific constants
|
||||
/// </summary>
|
||||
internal static class CableSelfTestConfigXml
|
||||
{
|
||||
// define all paths here
|
||||
public const string RootPath = "/root";
|
||||
public const string SelfTestPath = RootPath + "/selftest";
|
||||
|
||||
// define all attribute names here
|
||||
public const string UniversalCableAttributeName = "universal_cable";
|
||||
public const string SacrificialCableAttributeName = "sacrificial_cable";
|
||||
public const string LastRunDateAttributeName = "last_run_date";
|
||||
}
|
||||
}
|
||||
@@ -15,55 +15,39 @@ GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Store UUT information
|
||||
/// Define all the sections and keys that exists in the INI file
|
||||
/// </summary>
|
||||
internal class UutInfo
|
||||
internal enum ProgramGeneralConfigIni
|
||||
{
|
||||
#region PublicMembers
|
||||
#endregion
|
||||
// list all the sections below
|
||||
GENERAL,
|
||||
|
||||
#region PrivateClassMembers
|
||||
// class variables
|
||||
private readonly string _partNumber;
|
||||
private readonly string _serialNumber;
|
||||
#endregion
|
||||
// list all the keys below
|
||||
|
||||
#region PrivateFuctions
|
||||
/// <summary>
|
||||
/// The constructor
|
||||
/// </summary>
|
||||
public UutInfo(string partNumber, string serialNumber)
|
||||
{
|
||||
_partNumber = partNumber;
|
||||
_serialNumber = serialNumber;
|
||||
}
|
||||
#endregion
|
||||
// file names
|
||||
PROGRAM_SPECIFIC_CONFIG_FILE_NAME,
|
||||
SWITCH_MEASUREMENT_MANAGER_CONFIG_FILE_NAME,
|
||||
COE_MEASUREMENT_MANAGER_CONFIG_FILE_NAME,
|
||||
UUT_TEST_MESSAGES_CONFIG_FILE_NAME,
|
||||
CABLE_SELF_TEST_RUN_LOG_FILE_NAME,
|
||||
TEST_RUN_LOG_FILE_NAME,
|
||||
|
||||
#region PublicFuctions
|
||||
// file name prefix
|
||||
NLOG_FILE_NAME_PREFIX,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string GetPartNumber()
|
||||
{
|
||||
return _partNumber;
|
||||
}
|
||||
// folder names
|
||||
NLOG_FOLDER_NAME,
|
||||
TESTSTAND_FOLDER_NAME,
|
||||
HARDWARE_CONFIG_FOLDER_NAME,
|
||||
SIM_CONFIG_FOLDER_NAME,
|
||||
INSTRUMENT_CONFIG_FOLDER_NAME,
|
||||
MEASUREMENT_CONFIG_FOLDER_NAME,
|
||||
PDEL_FOLDER_NAME,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal string GetSerialNumber()
|
||||
{
|
||||
return _serialNumber;
|
||||
}
|
||||
|
||||
#endregion
|
||||
// teststand variable names
|
||||
DESTINATION_TEST_REPORT_PATH_VAR_NAME,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Define all the sections and keys that exists in the INI file
|
||||
/// </summary>
|
||||
internal enum ProgramSpecificConfigIni
|
||||
{
|
||||
// list all the sections below
|
||||
CABLE_ID_RELAYS,
|
||||
FILE_NAMES,
|
||||
GENERAL,
|
||||
POLL_RATES,
|
||||
POWER_MODULES_TO_BE_POWERED,
|
||||
POWER_MODULES_TO_BE_DISPLAYED,
|
||||
UUT_INFO,
|
||||
|
||||
// list all the keys below
|
||||
|
||||
// general
|
||||
PRIMARY_DRIVE,
|
||||
SECONDARY_DRIVE,
|
||||
DATA_BASE_FOLDER,
|
||||
DATA_GENERAL_FOLDER_NAME,
|
||||
DATA_GENERAL_TEMP_FOLDER_NAME,
|
||||
|
||||
APP_RELEASE_CONTROLLED_FOLDER,
|
||||
|
||||
LOG_DASHBOARD_APP_PATH,
|
||||
|
||||
ENFORCE_CABLE_SELF_TEST_IS_RUN,
|
||||
|
||||
// relays
|
||||
W1_CABLE_PART_NUMBER,
|
||||
W1_CABLE_SERIAL_NUMBER,
|
||||
W2_CABLE_PART_NUMBER,
|
||||
W2_CABLE_SERIAL_NUMBER,
|
||||
W3_CABLE_PART_NUMBER,
|
||||
W3_CABLE_SERIAL_NUMBER,
|
||||
W4_CABLE_PART_NUMBER,
|
||||
W4_CABLE_SERIAL_NUMBER,
|
||||
W5_CABLE_PART_NUMBER,
|
||||
W5_CABLE_SERIAL_NUMBER,
|
||||
|
||||
// poll rates
|
||||
POWER_SUPPLY_LOG_RATE,
|
||||
POWER_SUPPLY_READ_RATE,
|
||||
PASSTHROUGH_DATA_UPDATE_RATE,
|
||||
|
||||
// File names
|
||||
POWER_SUPPLY_LOG_PREFIX,
|
||||
POWER_SUPPLY_LOG_FILE_EXTENSION,
|
||||
|
||||
// UUT info
|
||||
UUT_IP_ADDRESS,
|
||||
UUT_TEST_PORT_TCP,
|
||||
|
||||
}
|
||||
}
|
||||
45
Source/Program/Common/ConfigLogic/TestRunConfigXml.cs
Normal file
45
Source/Program/Common/ConfigLogic/TestRunConfigXml.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Define non-specific constants
|
||||
/// </summary>
|
||||
internal static class TestRunConfigXml
|
||||
{
|
||||
// define all paths here
|
||||
public const string RootPath = "/root";
|
||||
public const string TestRunPath = RootPath + "/test_run";
|
||||
public const string TestRunPowerPath = TestRunPath + "/power";
|
||||
|
||||
// define all attribute names here
|
||||
public const string TestRunPathAttributeName = "path";
|
||||
public const string TestRunTestNameAttributeName = "test_name";
|
||||
public const string TestRunTesterAttributeName = "tester";
|
||||
public const string TestRunStartDateAttributeName = "start_date";
|
||||
public const string TestRunStartTimeAttributeName = "start_time";
|
||||
public const string TestRunEndDateAttributeName = "end_date";
|
||||
public const string TestRunEndTimeAttributeName = "end_time";
|
||||
|
||||
public const string TestRunPowerOnDateAttributeName = "on_date";
|
||||
public const string TestRunPowerOnTimeAttributeName = "on_time";
|
||||
public const string TestRunPowerOffDateAttributeName = "off_date";
|
||||
public const string TestRunPowerOffTimeAttributeName = "off_time";
|
||||
public const string TestRunPowerOnDurationAttributeName = "duration_sec";
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,24 @@ GOVERNMENT.
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Define all the sections and keys that exists in the INI file
|
||||
/// Exception for support threads to throw to signal to main thread of a fatal failure
|
||||
/// </summary>
|
||||
internal enum ProgramConfigIni
|
||||
internal class FatalFailureException : Exception
|
||||
{
|
||||
// list all the sections here
|
||||
GENERAL,
|
||||
protected FatalFailureException() { }
|
||||
|
||||
// list all the keys here
|
||||
DATA_BASE_PATH,
|
||||
DATA_TEMP_PATH,
|
||||
APP_BASE_PATH,
|
||||
POWER_SUPPLY_SELF_TEST_DATETIME,
|
||||
POWER_SUPPLY_READ_RATE
|
||||
public FatalFailureException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public FatalFailureException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Source/Program/Common/Lib/EthernetSocketManager.cs
Normal file
75
Source/Program/Common/Lib/EthernetSocketManager.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
internal enum TcpClientNames
|
||||
{
|
||||
UUT_TEST_PORT
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores global events that any thread can use to wait on
|
||||
/// </summary>
|
||||
internal class EthernetSocketManager
|
||||
{
|
||||
private Dictionary<TcpClientNames, TcpClient> _tcpClientsDict = new Dictionary<TcpClientNames, TcpClient>();
|
||||
|
||||
/// <summary>
|
||||
/// The private constructor
|
||||
/// </summary>
|
||||
public EthernetSocketManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~EthernetSocketManager()
|
||||
{
|
||||
foreach (KeyValuePair<TcpClientNames, TcpClient> item in _tcpClientsDict)
|
||||
{
|
||||
item.Value.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Tcp Client Socket
|
||||
/// </summary>
|
||||
public TcpClient GetTcpClient(TcpClientNames tcpClientName)
|
||||
{
|
||||
TcpClient tcpClient = null;
|
||||
|
||||
if (_tcpClientsDict.ContainsKey(tcpClientName))
|
||||
tcpClient = _tcpClientsDict[tcpClientName];
|
||||
|
||||
return tcpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add Tcp Client Socket
|
||||
/// </summary>
|
||||
public void AddTcpClient(TcpClientNames tcpClientName, TcpClient tcpClient)
|
||||
{
|
||||
if (!_tcpClientsDict.ContainsKey(tcpClientName))
|
||||
_tcpClientsDict[tcpClientName] = tcpClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,7 @@ UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
@@ -57,7 +55,7 @@ namespace ProgramLib
|
||||
/// if (id == Events.EVENT1){}
|
||||
///
|
||||
/// </summary>
|
||||
internal class EventGroup<T1,T2>
|
||||
internal class EventGroup<T1, T2>
|
||||
{
|
||||
Dictionary<T1, T2> _eventDict = null;
|
||||
T2[] _eventArray = null;
|
||||
@@ -15,12 +15,6 @@ GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
@@ -16,16 +16,13 @@ GOVERNMENT.
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores global events that any thread can use to monitor
|
||||
/// Stores global events that any thread can use to wait on
|
||||
/// </summary>
|
||||
internal partial class EventManager
|
||||
{
|
||||
@@ -15,12 +15,6 @@ GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
@@ -32,12 +26,19 @@ namespace ProgramLib
|
||||
{
|
||||
// List data folder + its subfolders
|
||||
DATA,
|
||||
DATA_TEMP,
|
||||
DATA_GENERAL,
|
||||
DATA_GENERAL_TEMP,
|
||||
DATA_TEST,
|
||||
|
||||
CONFIG,
|
||||
CONFIG_MEASUREMENT
|
||||
}
|
||||
|
||||
public enum Files
|
||||
{
|
||||
POWER_SUPPLY_SELF_TEST_DATETIME
|
||||
NLOG_TEMP,
|
||||
CABLE_SELF_TEST_RUN_LOG,
|
||||
TEST_RUN_LOG
|
||||
}
|
||||
}
|
||||
}
|
||||
227
Source/Program/Common/Lib/FileAndFolderManager.cs
Normal file
227
Source/Program/Common/Lib/FileAndFolderManager.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Raytheon.Common;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial class... this is the main class responsible for parsing config.ini in order
|
||||
/// to construct file paths and folder paths
|
||||
/// </summary>
|
||||
internal partial class FileAndFolderManager
|
||||
{
|
||||
private Dictionary<Folders, string> foldersDict = new Dictionary<Folders, string>();
|
||||
private Dictionary<Files, string> filesDict = new Dictionary<Files, string>();
|
||||
|
||||
private IConfigurationFile _programGeneralConfig;
|
||||
private IConfigurationFile _programSpecificConfig;
|
||||
private bool _isThereHardware;
|
||||
private string _configSubFolderName;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="programGeneralConfig">the UUT part number</param>
|
||||
public FileAndFolderManager(IConfigurationFile programGeneralConfig, out IConfigurationFile programSpecificConfig, bool isThereHardware, string configSubFolderName)
|
||||
{
|
||||
_programGeneralConfig = programGeneralConfig;
|
||||
_isThereHardware = isThereHardware;
|
||||
_configSubFolderName = configSubFolderName;
|
||||
|
||||
ConstructFolderPaths();
|
||||
CreateFolders();
|
||||
ConstructFilePaths();
|
||||
|
||||
programSpecificConfig = _programSpecificConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~FileAndFolderManager()
|
||||
{
|
||||
if (foldersDict.ContainsKey(Folders.DATA_TEST))
|
||||
{
|
||||
if (Directory.Exists(foldersDict[Folders.DATA_TEST]) && Directory.Exists(foldersDict[Folders.DATA]))
|
||||
{
|
||||
// delete all empty test folders
|
||||
if (foldersDict[Folders.DATA_TEST].Contains(foldersDict[Folders.DATA]))
|
||||
{
|
||||
string parentFolder = Path.GetFullPath(Path.Combine(foldersDict[Folders.DATA_TEST], ".."));
|
||||
|
||||
while (!String.Equals(parentFolder, foldersDict[Folders.DATA], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string[] dirs = Directory.GetDirectories(parentFolder);
|
||||
|
||||
foreach (string dir in dirs)
|
||||
{
|
||||
if (Util.IsDirectoryEmpty(dir))
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
|
||||
if (Util.IsDirectoryEmpty(parentFolder))
|
||||
Directory.Delete(parentFolder, true);
|
||||
|
||||
parentFolder = Path.GetFullPath(Path.Combine(parentFolder, ".."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ConstructTestFolder(UutInfo uutInfo, TestInfo testInfo)
|
||||
{
|
||||
string buildLevel = uutInfo.UutBuildLevel.ToString();
|
||||
if (uutInfo.UutBuildLevel == UutInfo.BuildLevel.NOT_SET)
|
||||
{
|
||||
buildLevel = "NO_UUT";
|
||||
}
|
||||
foldersDict[Folders.DATA_TEST] = Path.Combine(foldersDict[Folders.DATA], buildLevel, uutInfo.PartNumber,
|
||||
uutInfo.SerialNumber, testInfo.TestType.ToLower(), testInfo.TestStartDateTime.ToString("yyyy_MM_dd"),
|
||||
testInfo.TestName, testInfo.TestStartDateTime.ToString("HH_mm_ss"));
|
||||
|
||||
CreateFolders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build folder paths so we can acccess them later
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void ConstructFolderPaths()
|
||||
{
|
||||
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
string configHardwareOrSimSubFolderName = _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.SIM_CONFIG_FOLDER_NAME.ToString());
|
||||
if (_isThereHardware)
|
||||
configHardwareOrSimSubFolderName = _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.HARDWARE_CONFIG_FOLDER_NAME.ToString());
|
||||
|
||||
foldersDict[Folders.CONFIG] = Path.Combine(assemblyFolder, GeneralConstants.ConfigFolderName, configHardwareOrSimSubFolderName, _configSubFolderName);
|
||||
|
||||
string measurementConfigFolderName = _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.MEASUREMENT_CONFIG_FOLDER_NAME.ToString());
|
||||
foldersDict[Folders.CONFIG_MEASUREMENT] = Path.Combine(foldersDict[Folders.CONFIG], measurementConfigFolderName);
|
||||
|
||||
string programSpecificConfigFilePath = Path.Combine(GetFolder(Folders.CONFIG), _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.PROGRAM_SPECIFIC_CONFIG_FILE_NAME.ToString()));
|
||||
_programSpecificConfig = new ConfigurationFile(programSpecificConfigFilePath);
|
||||
|
||||
string dataRootPath;
|
||||
|
||||
// If primary drive exists on machine, all TE software files and folders should be located on that drive
|
||||
string drive = _programSpecificConfig.ReadValue(ProgramSpecificConfigIni.GENERAL.ToString(), ProgramSpecificConfigIni.PRIMARY_DRIVE.ToString());
|
||||
if (!Directory.Exists(drive))
|
||||
{
|
||||
drive = _programSpecificConfig.ReadValue(ProgramSpecificConfigIni.GENERAL.ToString(), ProgramSpecificConfigIni.SECONDARY_DRIVE.ToString());
|
||||
}
|
||||
dataRootPath = Path.Combine(drive, _programSpecificConfig.ReadValue(ProgramSpecificConfigIni.GENERAL.ToString(), ProgramSpecificConfigIni.DATA_BASE_FOLDER.ToString()));
|
||||
|
||||
if (!Path.IsPathRooted(dataRootPath))
|
||||
dataRootPath = Path.Combine(assemblyFolder, dataRootPath);
|
||||
else
|
||||
{
|
||||
Match regexMatch;
|
||||
|
||||
regexMatch = Regex.Match(dataRootPath, @"^([a-zA-Z]:\\).+", RegexOptions.IgnoreCase);
|
||||
|
||||
if (regexMatch.Success)
|
||||
{
|
||||
string driveLetter = regexMatch.Groups[1].Value;
|
||||
|
||||
if (!Directory.Exists(driveLetter))
|
||||
{
|
||||
|
||||
string err = $@"Invalid drive: {driveLetter}. Please provide a valid drive, ie C:\. File: {programSpecificConfigFilePath}. Section: {ProgramSpecificConfigIni.GENERAL.ToString()}. Key: {ProgramSpecificConfigIni.PRIMARY_DRIVE.ToString()} and Key: {ProgramSpecificConfigIni.SECONDARY_DRIVE.ToString()} were invalid drives.";
|
||||
|
||||
throw new Exception(err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string err = $@"Invalid path: {dataRootPath}. Please specify desired folder name, i.e. Data. File: {programSpecificConfigFilePath}. Section: {ProgramSpecificConfigIni.GENERAL.ToString()}. Key: {ProgramSpecificConfigIni.DATA_BASE_FOLDER.ToString()}";
|
||||
|
||||
throw new Exception(err);
|
||||
}
|
||||
}
|
||||
|
||||
foldersDict[Folders.DATA] = Path.GetFullPath(dataRootPath);
|
||||
|
||||
string val = _programSpecificConfig.ReadValue(ProgramSpecificConfigIni.GENERAL.ToString(), ProgramSpecificConfigIni.DATA_GENERAL_FOLDER_NAME.ToString());
|
||||
foldersDict[Folders.DATA_GENERAL] = Path.Combine(dataRootPath, val);
|
||||
|
||||
val = _programSpecificConfig.ReadValue(ProgramSpecificConfigIni.GENERAL.ToString(), ProgramSpecificConfigIni.DATA_GENERAL_TEMP_FOLDER_NAME.ToString());
|
||||
foldersDict[Folders.DATA_GENERAL_TEMP] = Path.Combine(foldersDict[Folders.DATA_GENERAL], val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We create all the necessary folders
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void CreateFolders()
|
||||
{
|
||||
// create all the folders if they don't exist
|
||||
foreach (KeyValuePair<Folders, string> entry in foldersDict)
|
||||
{
|
||||
Directory.CreateDirectory(entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build file paths so we can acccess them later
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void ConstructFilePaths()
|
||||
{
|
||||
string nlogFileNamePrefix = _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.NLOG_FILE_NAME_PREFIX.ToString());
|
||||
filesDict[Files.NLOG_TEMP] = Path.Combine(GetFolder(FileAndFolderManager.Folders.DATA_GENERAL_TEMP), _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.NLOG_FOLDER_NAME.ToString()), DateTime.Now.ToString("yyyy_MM_dd"), Util.GenerateUniqueFilenameUsingDateTime(nlogFileNamePrefix, "log"));
|
||||
filesDict[Files.CABLE_SELF_TEST_RUN_LOG] = Path.Combine(GetFolder(FileAndFolderManager.Folders.DATA_GENERAL), _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.CABLE_SELF_TEST_RUN_LOG_FILE_NAME.ToString()));
|
||||
filesDict[Files.TEST_RUN_LOG] = Path.Combine(GetFolder(FileAndFolderManager.Folders.DATA_GENERAL), _programGeneralConfig.ReadValue(ProgramGeneralConfigIni.GENERAL.ToString(), ProgramGeneralConfigIni.TEST_RUN_LOG_FILE_NAME.ToString()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the full folder path
|
||||
/// </summary>
|
||||
/// <param name="folder"></param>
|
||||
/// <returns></returns>
|
||||
public string GetFolder(Folders folder)
|
||||
{
|
||||
if (foldersDict.ContainsKey(folder))
|
||||
{
|
||||
return foldersDict[folder];
|
||||
}
|
||||
else
|
||||
throw new Exception($"{folder.ToString()} is invalid");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the full file path
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetFile(Files file)
|
||||
{
|
||||
if (filesDict.ContainsKey(file))
|
||||
{
|
||||
return filesDict[file];
|
||||
}
|
||||
else
|
||||
throw new Exception($"{file.ToString()} is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,6 @@ GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
@@ -28,8 +22,8 @@ namespace ProgramLib
|
||||
/// </summary>
|
||||
internal static class GeneralConstants
|
||||
{
|
||||
public const string ProgramConfigFilename = "config.ini";
|
||||
public const string TestMethodConfigFolderName = "TestMethodConfig";
|
||||
public const string TestMethodConfigFileName = "Test_Method_Configuration.ini";
|
||||
public const string ConfigFolderName = "ConfigFiles";
|
||||
|
||||
public const string ProgramGeneralConfigFilename = "ProgramGeneral.ini";
|
||||
}
|
||||
}
|
||||
307
Source/Program/Common/Lib/Util.cs
Normal file
307
Source/Program/Common/Lib/Util.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.DirectoryServices;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utility functions for other classes to use
|
||||
/// </summary>
|
||||
internal static class Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if directory is empty
|
||||
/// </summary>
|
||||
public static bool IsDirectoryEmpty(string path)
|
||||
{
|
||||
return !Directory.EnumerateFileSystemEntries(path).Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate random fractional part
|
||||
/// </summary>
|
||||
public static float GenerateRandomFraction()
|
||||
{
|
||||
Random rnd = new Random();
|
||||
int randNum = 0;
|
||||
const int minimum = 1;
|
||||
|
||||
randNum = rnd.Next(20);
|
||||
|
||||
if (randNum <= minimum)
|
||||
{
|
||||
randNum += minimum;
|
||||
}
|
||||
|
||||
return (float)(1.0 / (float)randNum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate unique file name
|
||||
/// Format: [Prefix]_YYYY_MM_DD_HH_MM_SS.[ext]
|
||||
/// </summary>
|
||||
public static string GenerateUniqueFilenameUsingDateTime(string prefix, string fileExtension)
|
||||
{
|
||||
string filename = prefix + "_" + DateTime.Now.ToString("yyyy_MM_dd") + "_" + DateTime.Now.ToString("HH_mm_ss");
|
||||
|
||||
if (fileExtension[0] != '.')
|
||||
{
|
||||
fileExtension = "." + fileExtension;
|
||||
}
|
||||
|
||||
filename += fileExtension;
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log Exception
|
||||
/// </summary>
|
||||
public static string LogException(Exception ex, ILogger logger)
|
||||
{
|
||||
string errorMsg = ex.Message;
|
||||
string stackTrace = ex.StackTrace;
|
||||
|
||||
Exception innerEx = ex.InnerException;
|
||||
while (innerEx != null)
|
||||
{
|
||||
errorMsg = innerEx.Message;
|
||||
stackTrace = innerEx.StackTrace + "\r\n" + stackTrace;
|
||||
|
||||
innerEx = innerEx.InnerException;
|
||||
}
|
||||
|
||||
logger.Error(errorMsg + "\r\n" + stackTrace);
|
||||
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto format any numeric type to string
|
||||
/// </summary>
|
||||
/// <param name="wholeNumberMaxDigits">number of digits to display for the whole number before it display in scientific notation</param>
|
||||
public static string AutoFormatNumberToString<T>(T o, int wholeNumberMaxDigits = 3, int numDecimalPlaces = 2) where T : IConvertible
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Convert.ChangeType(o, typeof(double)) is double @double)
|
||||
{
|
||||
string originalStr = @double.ToString("G"); // Most compact
|
||||
string tempStr = originalStr;
|
||||
int indexOfDecimalPoint = tempStr.IndexOf(".");
|
||||
if (indexOfDecimalPoint == -1)
|
||||
tempStr += ".0";
|
||||
indexOfDecimalPoint = tempStr.IndexOf(".");
|
||||
int tempStrIndex = 0;
|
||||
int indexOfMinusSign = tempStr.IndexOf("-");
|
||||
if (indexOfMinusSign != -1)
|
||||
tempStrIndex = 1;
|
||||
int wholeNumberNumDigits = tempStr.Substring(tempStrIndex, indexOfDecimalPoint - tempStrIndex).Length;
|
||||
int fractionalNumDigits = tempStr.Substring(indexOfDecimalPoint + 1, tempStr.Length - (indexOfDecimalPoint + 1)).Length;
|
||||
if (wholeNumberNumDigits > wholeNumberMaxDigits || tempStr.IndexOf("E") != -1)
|
||||
{
|
||||
string decimalPlaces = String.Empty;
|
||||
for (int i = 1; i <= numDecimalPlaces; i++)
|
||||
{
|
||||
decimalPlaces += "#";
|
||||
}
|
||||
var custom = @double.ToString($"0.{decimalPlaces}E0").Replace("E+", "E");
|
||||
while (custom.Contains("E0")) custom = custom.Replace("E0", "E");
|
||||
while (custom.Contains("-0")) custom = custom.Replace("-0", "-");
|
||||
if ((@double < 0) && custom[0] != '-')
|
||||
{
|
||||
custom.Insert(0, "-");
|
||||
}
|
||||
return custom;
|
||||
}
|
||||
else if (fractionalNumDigits > numDecimalPlaces)
|
||||
{
|
||||
return @double.ToString("0.##");
|
||||
}
|
||||
else
|
||||
{
|
||||
return originalStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return o.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get full name of windows user
|
||||
/// </summary>
|
||||
public static string GetWindowsUserFullName(string domain, string userName)
|
||||
{
|
||||
string name = "";
|
||||
|
||||
try
|
||||
{
|
||||
DirectoryEntry userEntry = new DirectoryEntry("WinNT://" + domain + "/" + userName + ",User");
|
||||
name = (string)userEntry.Properties["fullname"].Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
name = userName;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert struct to byte array
|
||||
/// </summary>
|
||||
public static byte[] StructToByteList<T>(T data)
|
||||
{
|
||||
int size = Marshal.SizeOf(data);
|
||||
|
||||
byte[] arr = new byte[size];
|
||||
|
||||
GCHandle h = default(GCHandle);
|
||||
|
||||
try
|
||||
{
|
||||
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
|
||||
|
||||
Marshal.StructureToPtr(data, h.AddrOfPinnedObject(), false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (h.IsAllocated)
|
||||
{
|
||||
h.Free();
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert byte array to struct
|
||||
/// </summary>
|
||||
public static T ByteArrayToStruct<T>(byte[] array)
|
||||
{
|
||||
object obj;
|
||||
|
||||
GCHandle h = default(GCHandle);
|
||||
|
||||
try
|
||||
{
|
||||
h = GCHandle.Alloc(array, GCHandleType.Pinned);
|
||||
|
||||
obj = Marshal.PtrToStructure(h.AddrOfPinnedObject(), typeof(T));
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (h.IsAllocated)
|
||||
{
|
||||
h.Free();
|
||||
}
|
||||
}
|
||||
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Two's Compliment Checksum
|
||||
/// </summary>
|
||||
public static byte GetTwosComplimentChecksum(byte[] array)
|
||||
{
|
||||
return (byte)(0x100u - ComputeSum(array));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add up all bytes in a byte array
|
||||
/// </summary>
|
||||
public static byte ComputeSum(byte[] array)
|
||||
{
|
||||
byte sum = 0;
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
sum += array[i];
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display each byte in byte array as hex string
|
||||
/// </summary>
|
||||
public static string ByteArrayToHexString(byte[] bytes, int numBytes = -1)
|
||||
{
|
||||
StringBuilder Result = new StringBuilder(bytes.Length * 2);
|
||||
string HexAlphabet = "0123456789ABCDEF";
|
||||
|
||||
if (numBytes < 1)
|
||||
{
|
||||
numBytes = bytes.Length;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numBytes; i++)
|
||||
{
|
||||
Result.Append("0x" + HexAlphabet[(int)(bytes[i] >> 4)]);
|
||||
Result.Append(HexAlphabet[(int)(bytes[i] & 0xF)] + " ");
|
||||
}
|
||||
|
||||
return Result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a variable of type TimeSpan, describe the time in days, hours, mins, seconds
|
||||
/// </summary>
|
||||
public static string DescribeTimeElapsed(TimeSpan ts)
|
||||
{
|
||||
string describe = "";
|
||||
|
||||
if (ts.Days > 0)
|
||||
describe += ts.Days.ToString() + " d";
|
||||
|
||||
if (ts.Hours > 0)
|
||||
{
|
||||
if (describe.Length > 0)
|
||||
describe += " ";
|
||||
describe += ts.Hours.ToString() + " h";
|
||||
}
|
||||
|
||||
if (ts.Minutes > 0)
|
||||
{
|
||||
if (describe.Length > 0)
|
||||
describe += " ";
|
||||
describe += ts.Minutes.ToString() + " m";
|
||||
}
|
||||
|
||||
if (ts.Seconds > 0)
|
||||
{
|
||||
if (describe.Length > 0)
|
||||
describe += " ";
|
||||
describe += ts.Seconds.ToString() + " s";
|
||||
}
|
||||
|
||||
if (describe.Length == 0)
|
||||
describe = "0 s";
|
||||
|
||||
return describe;
|
||||
}
|
||||
}
|
||||
}
|
||||
237
Source/Program/Common/Lib/XmlDocumentWrapper.cs
Normal file
237
Source/Program/Common/Lib/XmlDocumentWrapper.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// A XML Wrapper class to create/modify XML files
|
||||
/// </summary>
|
||||
internal class XmlDocumentWrapper
|
||||
{
|
||||
public enum AddNodePosition
|
||||
{
|
||||
First,
|
||||
Last
|
||||
}
|
||||
|
||||
private XmlDocument _xmlDoc = null;
|
||||
private string _xmlFilePath = String.Empty;
|
||||
private object _syncObj = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="xmlFilePath">XML file path</param>
|
||||
/// <param name="rootNodeName">name of the root node.</param>
|
||||
public XmlDocumentWrapper(string xmlFilePath, string rootNodeName = "root")
|
||||
{
|
||||
_xmlFilePath = xmlFilePath;
|
||||
CreateXmlDocument(rootNodeName);
|
||||
|
||||
_xmlDoc = new XmlDocument();
|
||||
_xmlDoc.Load(xmlFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create XML document if it doesn't exist
|
||||
/// </summary>
|
||||
private void CreateXmlDocument(string rootNodeName)
|
||||
{
|
||||
if (!File.Exists(_xmlFilePath))
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
XmlElement root = doc.CreateElement(rootNodeName);
|
||||
doc.AppendChild(root);
|
||||
|
||||
using (TextWriter sw = new StreamWriter(_xmlFilePath, false, Encoding.UTF8))
|
||||
{
|
||||
doc.Save(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save XML document to file
|
||||
/// </summary>
|
||||
public void SaveToFile()
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
using (TextWriter sw = new StreamWriter(_xmlFilePath, false, Encoding.UTF8))
|
||||
{
|
||||
_xmlDoc.Save(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a node defined by {path}
|
||||
/// </summary>
|
||||
/// <param name="path">example: /root/node1/node2</param>
|
||||
/// <param name="attributesDict">a dictionary of [name,value] pairs</param>
|
||||
/// <param name="innerText">text of the node</param>
|
||||
public void AddNode(string path, Dictionary<string, string> attributesDict = null, string innerText = null, AddNodePosition addNodePosition = AddNodePosition.Last)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
XmlElement elem = (XmlElement)MakeXPath(_xmlDoc, path, addNodePosition);
|
||||
|
||||
if (!String.IsNullOrEmpty(innerText))
|
||||
elem.InnerText = innerText;
|
||||
|
||||
if (attributesDict != null)
|
||||
{
|
||||
List<XmlAttribute> attributes = new List<XmlAttribute>();
|
||||
|
||||
foreach (KeyValuePair<string, string> item in attributesDict)
|
||||
{
|
||||
XmlAttribute attr = _xmlDoc.CreateAttribute(item.Key);
|
||||
attr.Value = item.Value;
|
||||
|
||||
attributes.Add(attr);
|
||||
}
|
||||
|
||||
SetAttrSafe(elem, attributes.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a node
|
||||
/// </summary>
|
||||
public void RemoveNode(XmlNode node)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
node.ParentNode.RemoveChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change attributes and/or inner text of an existing node
|
||||
/// </summary>
|
||||
public void ChangeNode(XmlNode node, Dictionary<string, string> attributesDict = null, string innerText = null)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(innerText))
|
||||
node.InnerText = innerText;
|
||||
|
||||
if (attributesDict != null)
|
||||
{
|
||||
List<XmlAttribute> attributes = new List<XmlAttribute>();
|
||||
|
||||
foreach (KeyValuePair<string, string> item in attributesDict)
|
||||
{
|
||||
XmlAttribute attr = _xmlDoc.CreateAttribute(item.Key);
|
||||
attr.Value = item.Value;
|
||||
|
||||
attributes.Add(attr);
|
||||
}
|
||||
|
||||
SetAttrSafe(node, attributes.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first node in {path}
|
||||
/// Useful for iterating through the sibling nodes to find the node with a specific attribute so we can modify the node
|
||||
/// </summary>
|
||||
/// <param name="path">example: /root/node1/node2</param>
|
||||
public XmlNode GetNode(string path)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
return _xmlDoc.SelectSingleNode(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all the nodes matching the path
|
||||
/// Useful for iterating through the matched nodes
|
||||
/// </summary>
|
||||
/// <param name="path">example: /root/node1/node2</param>
|
||||
public XmlNodeList GetNodes(string path)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
return _xmlDoc.SelectNodes(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set attribute
|
||||
/// </summary>
|
||||
private void SetAttrSafe(XmlNode node, XmlAttribute[] attrList)
|
||||
{
|
||||
foreach (var attr in attrList)
|
||||
{
|
||||
if (node.Attributes[attr.Name] != null)
|
||||
{
|
||||
node.Attributes[attr.Name].Value = attr.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Attributes.Append(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a node using {xpath}
|
||||
/// </summary>
|
||||
/// <param name="xpath">example: /root/node1/node2</param>
|
||||
private XmlNode MakeXPath(XmlDocument doc, string xpath, AddNodePosition addNodePosition)
|
||||
{
|
||||
return MakeXPath(doc, doc as XmlNode, xpath, addNodePosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterate through each node in {xpath} and create them
|
||||
/// </summary>
|
||||
private XmlNode MakeXPath(XmlDocument doc, XmlNode parent, string xpath, AddNodePosition addNodePosition)
|
||||
{
|
||||
// grab the next node name in the xpath; or return parent if empty
|
||||
string[] partsOfXPath = xpath.Trim('/').Split('/');
|
||||
string nextNodeInXPath = partsOfXPath.First();
|
||||
if (string.IsNullOrEmpty(nextNodeInXPath))
|
||||
return parent;
|
||||
|
||||
// get or create the node from the name
|
||||
XmlNode node = parent.SelectSingleNode(nextNodeInXPath);
|
||||
if (partsOfXPath.Length == 1)
|
||||
{
|
||||
if (addNodePosition == AddNodePosition.Last)
|
||||
node = parent.AppendChild(doc.CreateElement(nextNodeInXPath));
|
||||
else
|
||||
node = parent.PrependChild(doc.CreateElement(nextNodeInXPath));
|
||||
}
|
||||
|
||||
// rejoin the remainder of the array as an xpath expression and recurse
|
||||
string rest = String.Join("/", partsOfXPath.Skip(1).ToArray());
|
||||
return MakeXPath(doc, node, rest, addNodePosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
// UNCLASSIFIED
|
||||
/*-------------------------------------------------------------------------
|
||||
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
|
||||
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
|
||||
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
|
||||
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
|
||||
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
|
||||
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
|
||||
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
|
||||
COMPANY.
|
||||
|
||||
THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
|
||||
GOVERNMENT.
|
||||
|
||||
UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
||||
-------------------------------------------------------------------------*/
|
||||
using Raytheon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ProgramLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial class... this is the main class responsible for parsing config.ini in order
|
||||
/// to construct file paths and folder paths
|
||||
/// </summary>
|
||||
internal partial class FileAndFolderManager
|
||||
{
|
||||
private Dictionary<Folders, string> foldersDict = new Dictionary<Folders, string>();
|
||||
private Dictionary<Files, string> filesDict = new Dictionary<Files, string>();
|
||||
|
||||
private IConfigurationFile _programConfig;
|
||||
|
||||
/// <summary>
|
||||
/// The private constructor
|
||||
/// </summary>
|
||||
/// <param name="programConfig">the UUT part number</param>
|
||||
public FileAndFolderManager(IConfigurationFile programConfig)
|
||||
{
|
||||
_programConfig = programConfig;
|
||||
|
||||
ConstructFolderPaths();
|
||||
CreateFolders();
|
||||
ConstructFilePaths();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build folder paths so we can acccess them later
|
||||
/// </summary>
|
||||
/// <param name="iniObj"></param>
|
||||
/// <returns></returns>
|
||||
private void ConstructFolderPaths()
|
||||
{
|
||||
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
string dataRootPath = _programConfig.ReadValue(ProgramConfigIni.GENERAL.ToString(), ProgramConfigIni.DATA_BASE_PATH.ToString(), "NOT SET");
|
||||
if (!Path.IsPathRooted(dataRootPath))
|
||||
dataRootPath = Path.Combine(assemblyFolder, dataRootPath);
|
||||
|
||||
foldersDict[Folders.DATA] = Path.GetFullPath(dataRootPath);
|
||||
|
||||
string val = _programConfig.ReadValue(ProgramConfigIni.GENERAL.ToString(), ProgramConfigIni.DATA_TEMP_PATH.ToString(), "NOT SET");
|
||||
foldersDict[Folders.DATA_TEMP] = Path.Combine(dataRootPath, val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We create all the necessary folders
|
||||
/// </summary>
|
||||
/// <param name="iniObj"></param>
|
||||
/// <returns></returns>
|
||||
private void CreateFolders()
|
||||
{
|
||||
Directory.CreateDirectory(foldersDict[Folders.DATA_TEMP]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build file paths so we can acccess them later
|
||||
/// </summary>
|
||||
/// <param name="iniObj"></param>
|
||||
/// <returns></returns>
|
||||
private void ConstructFilePaths()
|
||||
{
|
||||
string val = _programConfig.ReadValue(ProgramConfigIni.GENERAL.ToString(), ProgramConfigIni.POWER_SUPPLY_SELF_TEST_DATETIME.ToString(), "NOT SET");
|
||||
filesDict[Files.POWER_SUPPLY_SELF_TEST_DATETIME] = Path.Combine(foldersDict[Folders.DATA_TEMP], val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the full folder path
|
||||
/// </summary>
|
||||
/// <param name="folder"></param>
|
||||
/// <returns></returns>
|
||||
public string getFolder(Folders folder)
|
||||
{
|
||||
if (foldersDict.ContainsKey(folder))
|
||||
{
|
||||
return foldersDict[folder];
|
||||
}
|
||||
else
|
||||
throw new Exception($"{folder.ToString()} is invalid");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the full file path
|
||||
/// </summary>
|
||||
/// <param name="iniObj"></param>
|
||||
/// <returns></returns>
|
||||
public string getFile(Files file)
|
||||
{
|
||||
if (filesDict.ContainsKey(file))
|
||||
{
|
||||
return filesDict[file];
|
||||
}
|
||||
else
|
||||
throw new Exception($"{file.ToString()} is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user