Add Transmission Control Protocol file handling and update PCL serialization

- Introduced FileRead and Record classes for handling file reading in the Transmission Control Protocol.
- Enhanced Description, Detail, and other related classes with JSON serialization attributes for improved data handling.
- Implemented methods for reading and processing files, including network stream management.
- Updated unit tests to cover new functionality and ensure robust testing.
- Added new PDSF file handling classes and integrated them into the project structure.
- Refactored existing code to utilize source generation for JSON serialization, improving performance and maintainability.
This commit is contained in:
2025-09-15 09:58:00 -07:00
parent 7561a08adc
commit 2bda7012cd
33 changed files with 1569 additions and 193 deletions

View File

@ -0,0 +1,250 @@
using Adaptation.Eaf.Management.ConfigurationData.CellAutomation;
using Adaptation.Ifx.Eaf.EquipmentConnector.File.Configuration;
using Adaptation.Shared;
using Adaptation.Shared.Duplicator;
using Adaptation.Shared.Methods;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading;
namespace Adaptation.FileHandlers.TransmissionControlProtocol;
#nullable enable
public class FileRead : Shared.FileRead, IFileRead
{
private readonly int _Port;
private readonly Timer _Timer;
private static Record? _Record;
private static long _LastWrite;
private readonly string _IPAddress;
private readonly string _RawDirectory;
private readonly int _DelimiterSeconds;
private readonly string[] _DelimiterPatterns;
private static readonly object _Lock = new();
public FileRead(ISMTP smtp, Dictionary<string, string> fileParameter, string cellInstanceName, int? connectionCount, string cellInstanceConnectionName, FileConnectorConfiguration fileConnectorConfiguration, string equipmentTypeName, string parameterizedModelObjectDefinitionType, IList<ModelObjectParameterDefinition> modelObjectParameters, string equipmentDictionaryName, Dictionary<string, List<long>> dummyRuns, Dictionary<long, List<Shared.Metrology.WS.Results>> staticRuns, bool useCyclicalForDescription, bool isEAFHosted) :
base(new Description(), false, smtp, fileParameter, cellInstanceName, connectionCount, cellInstanceConnectionName, fileConnectorConfiguration, equipmentTypeName, parameterizedModelObjectDefinitionType, modelObjectParameters, equipmentDictionaryName, dummyRuns, staticRuns, useCyclicalForDescription, isEAFHosted: connectionCount is null)
{
_MinFileLength = 10;
_NullData = string.Empty;
_Logistics = new(this);
if (_FileParameter is null)
throw new Exception(cellInstanceConnectionName);
if (_ModelObjectParameterDefinitions is null)
throw new Exception(cellInstanceConnectionName);
if (_IsDuplicator)
throw new Exception(cellInstanceConnectionName);
string sourceFileLocation = fileConnectorConfiguration.SourceFileLocation.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string? ipAddress = Path.GetDirectoryName(sourceFileLocation);
string port = Path.GetFileName(sourceFileLocation);
_Timer = new Timer(Callback, null, Timeout.Infinite, Timeout.Infinite);
_Port = int.Parse(port, System.Globalization.CultureInfo.InvariantCulture);
_DelimiterPatterns = fileConnectorConfiguration.SourceFileFilter.Split('*');
_IPAddress = Path.GetFileName(ipAddress) ?? throw new Exception(sourceFileLocation);
_RawDirectory = Path.GetDirectoryName(ipAddress) ?? throw new Exception(sourceFileLocation);
DateTime fileAgeThresholdTimeOnly = GetFileAgeThresholdTimeOnly(_FileConnectorConfiguration.FileAgeThreshold);
_DelimiterSeconds = fileAgeThresholdTimeOnly.Second;
if (Debugger.IsAttached || fileConnectorConfiguration.PreProcessingMode == FileConnectorConfiguration.PreProcessingModeEnum.Process || _FileConnectorConfiguration.FileScanningIntervalInSeconds is null)
Callback(null);
else
{
TimeSpan timeSpan = new(DateTime.Now.AddSeconds(_FileConnectorConfiguration.FileScanningIntervalInSeconds.Value).Ticks - DateTime.Now.Ticks);
_ = _Timer.Change((long)timeSpan.TotalMilliseconds, Timeout.Infinite);
}
}
void IFileRead.Move(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults, Exception exception) => Move(extractResults);
void IFileRead.WaitForThread() => WaitForThread(thread: null, threadExceptions: null);
string IFileRead.GetEventDescription()
{
string result = _Description.GetEventDescription();
return result;
}
List<string> IFileRead.GetHeaderNames()
{
List<string> results = _Description.GetHeaderNames();
return results;
}
string[] IFileRead.Move(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults, string to, string from, string resolvedFileLocation, Exception exception)
{
string[] results = Move(extractResults, to, from, resolvedFileLocation, exception);
return results;
}
JsonProperty[] IFileRead.GetDefault()
{
JsonProperty[] results = _Description.GetDefault(this, _Logistics);
return results;
}
Dictionary<string, string> IFileRead.GetDisplayNamesJsonElement()
{
Dictionary<string, string> results = _Description.GetDisplayNamesJsonElement(this);
return results;
}
List<IDescription> IFileRead.GetDescriptions(IFileRead fileRead, List<Test> tests, IProcessData processData)
{
List<IDescription> results = _Description.GetDescriptions(fileRead, _Logistics, tests, processData);
return results;
}
Tuple<string, Test[], JsonElement[], List<FileInfo>> IFileRead.GetExtractResult(string reportFullPath, string eventName) => throw new Exception(string.Concat("See ", nameof(Callback)));
Tuple<string, Test[], JsonElement[], List<FileInfo>> IFileRead.ReExtract() => throw new Exception(string.Concat("See ", nameof(Callback)));
private static DateTime GetFileAgeThresholdTimeOnly(string fileAgeThreshold)
{
DateTime result = DateTime.MinValue;
string[] segments = fileAgeThreshold.Split(':');
for (int i = 0; i < segments.Length; i++)
{
result = i switch
{
0 => result.AddDays(double.Parse(segments[i])),
1 => result.AddHours(double.Parse(segments[i])),
2 => result.AddMinutes(double.Parse(segments[i])),
3 => result.AddSeconds(double.Parse(segments[i])),
_ => throw new Exception(),
};
}
return result;
}
private static void ReadFiles(log4net.ILog log, FileConnectorConfiguration fileConnectorConfiguration, string ipAddress, string rawDirectory)
{
List<byte> bytes = new();
string[] files = Directory.GetFiles(rawDirectory, $"{ipAddress}-*.raw", SearchOption.TopDirectoryOnly);
log.Info($"Read {files.Length} files");
foreach (string file in files)
{
foreach (byte @byte in File.ReadAllBytes(file))
bytes.Add(@byte);
}
if (bytes.Count > 0)
{
string bytesFile = Path.Combine(fileConnectorConfiguration.TargetFileLocation, $"{ipAddress}-{DateTime.Now.Ticks}{fileConnectorConfiguration.TargetFileName}");
File.WriteAllBytes(bytesFile, bytes.ToArray());
foreach (string file in files)
File.Delete(file);
}
}
private static void CreateClient(log4net.ILog log, string ipAddress, int port)
{
log.Debug(ipAddress);
TcpClient tcpClient = new(ipAddress, port);
NetworkStream networkStream = tcpClient.GetStream();
Type baseType = typeof(NetworkStream);
PropertyInfo? propertyInfo = baseType.GetProperty("Socket", BindingFlags.Instance | BindingFlags.NonPublic);
_Record = new(binaryReader: new(networkStream), binaryWriter: new(networkStream), networkStream: networkStream, propertyInfo: propertyInfo, readTimes: new());
}
private static byte[] GetBytes(NetworkStream networkStream)
{
List<byte> results = new();
byte[] bytes = new byte[1024];
do
{
int count = networkStream.Read(bytes, 0, bytes.Length);
if (count > 0)
results.AddRange(bytes.Take(count));
}
while (networkStream.DataAvailable);
return results.ToArray();
}
private void Callback()
{
if (_Record?.NetworkStream is null || _Record.PropertyInfo is null || _Record.PropertyInfo.GetValue(_Record.NetworkStream) is not Socket socket || !socket.Connected)
CreateClient(_Log, _IPAddress, _Port);
if (_Record is not null)
{
TimeSpan timeSpan = new(DateTime.Now.Ticks - _LastWrite);
if (_LastWrite == 0 || timeSpan.TotalMinutes > 1)
{
try
{
_Record.NetworkStream.WriteByte(Convert.ToByte('\0'));
_LastWrite = DateTime.Now.Ticks;
}
catch (Exception)
{ }
}
}
if (_Record?.NetworkStream is not null && _Record.NetworkStream.CanRead && _Record.NetworkStream.DataAvailable)
{
byte[] bytes = GetBytes(_Record.NetworkStream);
_Log.Info($"Read {bytes.Length} bytes");
if (bytes.Length > 0)
{
string path = Path.Combine(_RawDirectory, $"{_IPAddress}-{DateTime.Now.Ticks}.raw");
File.WriteAllBytes(path, bytes);
string content = Encoding.ASCII.GetString(bytes);
_Log.Debug($"Content {content}");
foreach (string delimiterPattern in _DelimiterPatterns)
{
if (content.Contains(delimiterPattern))
_Record.ReadTimes.Add(DateTime.Now.Ticks);
}
if (_Record.ReadTimes.Count > 0)
_Record.ReadTimes.Add(DateTime.Now.Ticks);
}
}
if (_Record is not null && _Record.ReadTimes.Count > 0 && _DelimiterSeconds > 0)
{
TimeSpan? timeSpan = new(DateTime.Now.Ticks - _Record.ReadTimes.Last());
if (timeSpan.Value.TotalSeconds > _DelimiterSeconds)
{
ReadFiles(_Log, _FileConnectorConfiguration, _IPAddress, _RawDirectory);
_Record.ReadTimes.Clear();
}
}
}
private void Callback(object? state)
{
try
{
lock (_Lock)
Callback();
}
catch (Exception exception)
{
string subject = string.Concat("Exception:", _CellInstanceConnectionName);
string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace);
try
{ _SMTP.SendHighPriorityEmailMessage(subject, body); }
catch (Exception) { }
}
try
{
if (_FileConnectorConfiguration.FileScanningIntervalInSeconds is null)
throw new Exception(_CellInstanceConnectionName);
TimeSpan timeSpan = new(DateTime.Now.AddSeconds(_FileConnectorConfiguration.FileScanningIntervalInSeconds.Value).Ticks - DateTime.Now.Ticks);
_ = _Timer.Change((long)timeSpan.TotalMilliseconds, Timeout.Infinite);
}
catch (Exception exception)
{
string subject = string.Concat("Exception:", _CellInstanceConnectionName);
string body = string.Concat(exception.Message, Environment.NewLine, Environment.NewLine, exception.StackTrace);
try
{ _SMTP.SendHighPriorityEmailMessage(subject, body); }
catch (Exception) { }
}
}
}