- 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.
		
			
				
	
	
		
			250 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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) { }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| } |