2026-04-03 10:22:54 +02:00

754 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using System.Configuration;
using System.Runtime.InteropServices;
using com.itac.mes.imsapi.client.dotnet;
using com.itac.mes.imsapi.domain.container;
namespace Service
{
public static class Program
{
public const string ServiceName = "FileInterface";
private static TestClient _testClient;
public class Service : ServiceBase
{
public Service()
{
ServiceName = Program.ServiceName;
}
protected override void OnStart(string[] args)
{
Program.Start(args);
}
protected override void OnStop()
{
Program.Stop();
}
}
static void Main(string[] args)
{
if (!Environment.UserInteractive)
{
using (var service = new Service())
{
ServiceBase.Run(service);
}
}
else
{
TestClient.DisableQuickEditMode();
Start(args);
Console.WriteLine("Service läuft im Konsolenmodus. Zum Beenden ENTER drücken.");
Console.ReadLine();
Stop();
}
}
private static void Start(string[] args)
{
try
{
_testClient = new TestClient();
_testClient.Start(args);
}
catch (Exception ex)
{
Trace.TraceError("Fatal error while starting service: " + ex);
throw;
}
}
private static void Stop()
{
try
{
_testClient?.Stop();
}
catch (Exception ex)
{
Trace.TraceError("Fatal error while stopping service: " + ex);
}
}
}
class TestClient
{
private static IIMSApiDotNet imsapi = null;
private static bool initialized = false;
private static IMSApiSessionContextStruct sessionContext = null;
private static readonly Dictionary<long, IMSApiSessionContextStruct> allSessions = new Dictionary<long, IMSApiSessionContextStruct>();
private readonly object processingLock = new object();
private readonly HashSet<string> filesInProgress = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private volatile bool isRunning = false;
private Thread cleaningThread;
private FileSystemWatcher sourceWatcher;
private FileSystemWatcher processWatcher;
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
const int STD_INPUT_HANDLE = -10;
const uint ENABLE_QUICK_EDIT = 0x0040;
private volatile bool fileProcessed = false;
public void Start(string[] args)
{
SetupLogging();
Trace.TraceInformation("Starting TestClient");
InitializeApi();
if (!initialized)
{
Trace.TraceError("Initialization of IMSApi failed. Exiting TestClient");
throw new Exception("Initialization of IMSApi failed.");
}
Login();
isRunning = true;
StartWatchers();
StartCleaningThread();
Trace.TraceInformation("TestClient started successfully");
}
public void Stop()
{
Trace.TraceInformation("Stopping TestClient");
isRunning = false;
try
{
sourceWatcher?.Dispose();
}
catch (Exception ex)
{
Trace.TraceError("Error disposing sourceWatcher: " + ex);
}
try
{
processWatcher?.Dispose();
}
catch (Exception ex)
{
Trace.TraceError("Error disposing processWatcher: " + ex);
}
try
{
if (cleaningThread != null && cleaningThread.IsAlive)
{
if (!cleaningThread.Join(5000))
{
Trace.TraceWarning("Cleaning thread did not stop within timeout.");
}
}
}
catch (Exception ex)
{
Trace.TraceError("Error while stopping cleaning thread: " + ex);
}
Trace.TraceInformation("TestClient stopped");
}
private void StartWatchers()
{
string sourcePath = ConfigurationManager.AppSettings["SourcePath"];
string fileType = ConfigurationManager.AppSettings["Filetyp"];
if (!string.IsNullOrWhiteSpace(sourcePath) &&
!string.Equals(sourcePath, "false", StringComparison.OrdinalIgnoreCase))
{
sourceWatcher = new FileSystemWatcher
{
Path = sourcePath,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime,
Filter = fileType,
IncludeSubdirectories = false,
EnableRaisingEvents = false
};
sourceWatcher.Created += OnSourceFileCreatedOrChanged;
sourceWatcher.Changed += OnSourceFileCreatedOrChanged;
sourceWatcher.EnableRaisingEvents = true;
Trace.TraceInformation("Source watcher started on path: " + sourcePath);
}
string processFilesPath = ConfigurationManager.AppSettings["PROCESS_FILES_PATH"];
if (!string.IsNullOrWhiteSpace(processFilesPath) &&
!string.Equals(processFilesPath, "false", StringComparison.OrdinalIgnoreCase))
{
processWatcher = new FileSystemWatcher
{
Path = processFilesPath,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime,
Filter = fileType,
IncludeSubdirectories = false,
EnableRaisingEvents = false
};
processWatcher.Created += OnProcessFileCreatedOrChanged;
processWatcher.Changed += OnProcessFileCreatedOrChanged;
processWatcher.EnableRaisingEvents = true;
Trace.TraceInformation("Process watcher started on path: " + processFilesPath);
}
}
private void StartCleaningThread()
{
cleaningThread = new Thread(CleaningLoop)
{
IsBackground = true,
Name = "FileInterfaceCleaningThread"
};
cleaningThread.Start();
}
private void CleaningLoop()
{
while (isRunning)
{
try
{
int waitTime = int.Parse(ConfigurationManager.AppSettings["Wait"]);
Thread.Sleep(waitTime);
if (!isRunning)
break;
if (!fileProcessed)
{
CleanOldFiles();
}
fileProcessed = false;
GC.Collect();
}
catch (ThreadInterruptedException)
{
Trace.TraceInformation("Cleaning loop interrupted.");
break;
}
catch (Exception ex)
{
Trace.TraceError("Error in cleaning loop: " + ex);
}
}
}
private void OnSourceFileCreatedOrChanged(object source, FileSystemEventArgs e)
{
TryProcessFromWatcher(e.FullPath, e.Name, ResolveConfigFile(e.Name));
}
private void OnProcessFileCreatedOrChanged(object source, FileSystemEventArgs e)
{
string configFile = ConfigurationManager.AppSettings["CF_Processfiles"];
TryProcessFromWatcher(e.FullPath, e.Name, configFile);
}
private string ResolveConfigFile(string fileName)
{
string startsWith = ConfigurationManager.AppSettings["Check_starts_with"];
if (!string.IsNullOrWhiteSpace(startsWith) &&
!string.Equals(startsWith, "false", StringComparison.OrdinalIgnoreCase) &&
Path.GetFileName(fileName).StartsWith(startsWith, StringComparison.OrdinalIgnoreCase))
{
return ConfigurationManager.AppSettings["CF_Name"];
}
return ConfigurationManager.AppSettings["CF_Processfiles"];
}
private void TryProcessFromWatcher(string filePath, string fileName, string configFile)
{
if (!isRunning)
return;
if (string.IsNullOrWhiteSpace(filePath))
return;
if (!MarkFileAsInProgress(filePath))
{
Trace.TraceInformation("Skipping already running file: " + filePath);
return;
}
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
lock (processingLock)
{
ProcessFile(filePath, fileName, configFile);
fileProcessed = true;
}
}
catch (Exception ex)
{
Trace.TraceError($"Unhandled error in watcher processing for file '{filePath}': {ex}");
}
finally
{
UnmarkFileAsInProgress(filePath);
}
});
}
private bool MarkFileAsInProgress(string filePath)
{
lock (filesInProgress)
{
return filesInProgress.Add(NormalizePath(filePath));
}
}
private void UnmarkFileAsInProgress(string filePath)
{
lock (filesInProgress)
{
filesInProgress.Remove(NormalizePath(filePath));
}
}
private bool IsFileInProgress(string filePath)
{
lock (filesInProgress)
{
return filesInProgress.Contains(NormalizePath(filePath));
}
}
private static string NormalizePath(string filePath)
{
return Path.GetFullPath(filePath).Trim();
}
private void ProcessFile(string filePath, string fileName, string configFile)
{
try
{
if (!File.Exists(filePath))
{
Trace.TraceWarning($"File not found: {filePath}");
return;
}
if (!WaitForFileReady(filePath, retries: 20, delayMs: 500))
{
throw new IOException($"File '{filePath}' is not ready for reading.");
}
string fileContent = File.ReadAllText(filePath);
string designator = ConfigurationManager.AppSettings["Designator"];
if (string.IsNullOrWhiteSpace(designator))
{
throw new Exception("Designator value is not set in the configuration.");
}
string parameters = BuildRawParameters(fileContent, designator);
if (string.IsNullOrWhiteSpace(parameters))
{
throw new Exception($"File '{fileName}' contains no valid elements after parsing.");
}
Trace.TraceInformation($"Processing file '{fileName}' with config '{configFile}'");
string[] outArgs;
string customErrorString;
int result = imsapi.customFunction(sessionContext, configFile, new[]
{
"ST|" + ConfigurationManager.AppSettings["StationNr"],
"MODE|" + ConfigurationManager.AppSettings["MODE"],
"PANEL_MODE|" + ConfigurationManager.AppSettings["PANEL_MODE"],
"PROCESS_MODE|" + ConfigurationManager.AppSettings["PROCESS_MODE"],
"FILENAME|" + fileName,
"RAW|" + parameters
}, out outArgs, out customErrorString);
LogResult(result, customErrorString, outArgs, fileName, parameters);
HandleResult(result, filePath, fileName, outArgs);
}
catch (Exception ex)
{
Trace.TraceError($"Error processing file '{filePath}': {ex}");
MoveFileToDirectory(
filePath,
Path.Combine(ConfigurationManager.AppSettings["Destination"], "unprocessed", fileName));
}
}
private static string BuildRawParameters(string fileContent, string designator)
{
if (string.IsNullOrWhiteSpace(fileContent))
return string.Empty;
var parts = fileContent
.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None)
.Select(x => x?.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToArray();
return string.Join(designator, parts);
}
private static bool WaitForFileReady(string path, int retries, int delayMs)
{
for (int i = 0; i < retries; i++)
{
try
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None))
{
if (stream.Length >= 0)
return true;
}
}
catch (IOException)
{
Thread.Sleep(delayMs);
}
catch (UnauthorizedAccessException)
{
Thread.Sleep(delayMs);
}
}
return false;
}
private void LogResult(int result, string customErrorString, string[] outArgs, string fileName, string parameters)
{
string logInfo = ConfigurationManager.AppSettings["Loginfo"];
if ((logInfo == "error" && result != 0) || logInfo != "error")
{
Trace.TraceInformation("Date: " + DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss"));
Trace.TraceInformation("File: " + fileName);
Trace.TraceInformation("Result: " + result);
Trace.TraceInformation("ErrorString: " + (customErrorString ?? string.Empty));
Trace.TraceInformation("RAW length: " + parameters.Length);
if (outArgs != null && outArgs.Length > 0)
{
Trace.TraceInformation("OutArgs: " + string.Join(" | ", outArgs));
}
Trace.Flush();
}
}
private void HandleResult(int result, string filePath, string fileName, string[] outArgs)
{
string destinationPath = ConfigurationManager.AppSettings["Destination"];
string processedDir = Path.Combine(destinationPath, result == 0 ? "processed" : "error");
MoveFileToDirectory(filePath, Path.Combine(processedDir, fileName));
if (result == 0 &&
outArgs != null &&
outArgs.Length >= 2 &&
!string.Equals(ConfigurationManager.AppSettings["Resultfile"], "false", StringComparison.OrdinalIgnoreCase))
{
WriteResultFile(outArgs[0], outArgs[1]);
}
}
private void WriteResultFile(string resultFileName, string resultFileContent)
{
string resultPathRoot = ConfigurationManager.AppSettings["Resultfile"];
string resultFilePath = Path.Combine(resultPathRoot, resultFileName);
try
{
Directory.CreateDirectory(resultPathRoot);
File.WriteAllText(resultFilePath, resultFileContent ?? string.Empty);
}
catch (IOException ioe)
{
Trace.TraceError($"Error writing result file '{resultFilePath}': {ioe}");
}
catch (Exception ex)
{
Trace.TraceError($"Unexpected error writing result file '{resultFilePath}': {ex}");
}
}
private void MoveFileToDirectory(string sourceFilePath, string destinationFilePath)
{
try
{
if (!File.Exists(sourceFilePath))
{
Trace.TraceWarning($"File not found when trying to move: {sourceFilePath}");
return;
}
string destinationDirectory = Path.GetDirectoryName(destinationFilePath);
if (!string.IsNullOrWhiteSpace(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
File.Copy(sourceFilePath, destinationFilePath, true);
File.Delete(sourceFilePath);
Trace.TraceInformation($"Moved file '{sourceFilePath}' to '{destinationFilePath}'");
}
catch (IOException ioe)
{
Trace.TraceError($"Error moving file from '{sourceFilePath}' to '{destinationFilePath}': {ioe}");
}
catch (Exception ex)
{
Trace.TraceError($"Unexpected error moving file from '{sourceFilePath}' to '{destinationFilePath}': {ex}");
}
}
private void CleanOldFiles()
{
string sourcePath = ConfigurationManager.AppSettings["SourcePath"];
string processPath = ConfigurationManager.AppSettings["PROCESS_FILES_PATH"];
string destinationPath = ConfigurationManager.AppSettings["Destination"];
string processedDoneDir = Path.Combine(destinationPath, "processed_done");
lock (processingLock)
{
ProcessFilesInPath(
sourcePath,
ConfigurationManager.AppSettings["CF_Name"],
useStartsWithLogic: true);
ProcessFilesInPath(
processPath,
ConfigurationManager.AppSettings["CF_Processfiles"],
useStartsWithLogic: false);
}
CreateDirectoryIfNotExists(processedDoneDir);
}
private void ProcessFilesInPath(string path, string fallbackCustomFunction, bool useStartsWithLogic)
{
if (string.IsNullOrWhiteSpace(path) ||
string.Equals(path, "false", StringComparison.OrdinalIgnoreCase) ||
!Directory.Exists(path))
{
return;
}
string filter = ConfigurationManager.AppSettings["Filetyp"];
int ageLimit = int.Parse(ConfigurationManager.AppSettings["FileStamp_older_in_Minutes"]);
var files = Directory.GetFiles(path, filter);
foreach (var file in files)
{
if (!isRunning)
return;
try
{
if (IsFileInProgress(file))
{
Trace.TraceInformation("Skipping cleanup for file already in progress: " + file);
continue;
}
FileInfo fileInfo = new FileInfo(file);
TimeSpan fileAge = DateTime.UtcNow - fileInfo.LastWriteTimeUtc;
if (fileAge.TotalMinutes <= ageLimit)
continue;
if (!MarkFileAsInProgress(file))
continue;
try
{
string configFile = useStartsWithLogic
? ResolveConfigFile(fileInfo.Name)
: fallbackCustomFunction;
ProcessFile(file, fileInfo.Name, configFile);
}
finally
{
UnmarkFileAsInProgress(file);
}
}
catch (Exception ex)
{
Trace.TraceError($"Error while cleaning old file '{file}': {ex}");
}
}
}
private void SetupLogging()
{
string exePath = Assembly.GetExecutingAssembly().Location;
string exeDir = Path.GetDirectoryName(exePath);
if (string.IsNullOrWhiteSpace(exeDir))
exeDir = AppDomain.CurrentDomain.BaseDirectory;
DirectoryCheckAndCreate(exeDir);
string logPath = Path.Combine(exeDir, "IMSApiDotNet.log");
bool listenerExists = Trace.Listeners
.OfType<TextWriterTraceListener>()
.Any(l => string.Equals(l.Name, "IMSApiFileListener", StringComparison.OrdinalIgnoreCase));
if (!listenerExists)
{
var traceListener = new TextWriterTraceListener(logPath, "IMSApiFileListener");
Trace.Listeners.Add(traceListener);
}
Trace.AutoFlush = true;
}
private void InitializeApi()
{
IMSApiDotNet.setProperty("itac.appid", ConfigurationManager.AppSettings["AppID"]);
IMSApiDotNet.setProperty("itac.artes.clusternodes", ConfigurationManager.AppSettings["Server_URL"]);
string exePath = Assembly.GetExecutingAssembly().Location;
IMSApiDotNet.setProperty("itac.propdir", Path.GetDirectoryName(exePath));
imsapi = IMSApiDotNet.loadLibrary();
if (imsapi == null)
throw new Exception("IMSApiDotNet.loadLibrary() returned null.");
test_imsapiGetLibraryVersion();
test_imsapiInit();
}
private void Login()
{
Trace.TraceInformation("calling regLogin()");
test_regLogin();
}
public static void test_imsapiGetLibraryVersion()
{
if (imsapi.imsapiGetLibraryVersion(out string version) != IMSApiDotNetConstants.RES_OK)
{
Trace.TraceError("Error getting library version.");
}
else
{
Trace.TraceInformation($"Library Version: {version}");
}
}
public static void test_imsapiInit()
{
if (imsapi.imsapiInit() != IMSApiDotNetConstants.RES_OK)
{
Trace.TraceError("Initialization of IMSApi failed.");
}
else
{
initialized = true;
Trace.TraceInformation("IMSApi initialized successfully.");
}
}
public static void test_regLogin()
{
var sessValData = new IMSApiSessionValidationStruct
{
stationNumber = ConfigurationManager.AppSettings["StationNr"],
client = ConfigurationManager.AppSettings["CleintNO"],
registrationType = "S"
};
if (imsapi.regLogin(sessValData, out var newSessionContext) != IMSApiDotNetConstants.RES_OK)
{
Trace.TraceError("Login failed.");
}
else
{
sessionContext = newSessionContext;
if (!allSessions.ContainsKey(sessionContext.sessionId))
{
allSessions.Add(sessionContext.sessionId, sessionContext);
}
Trace.TraceInformation("Login successful.");
}
}
public static void DisableQuickEditMode()
{
IntPtr consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
if (GetConsoleMode(consoleHandle, out uint consoleMode))
{
consoleMode &= ~ENABLE_QUICK_EDIT;
SetConsoleMode(consoleHandle, consoleMode);
}
}
public static void DirectoryCheckAndCreate(string exePath)
{
string destination = ConfigurationManager.AppSettings["Destination"];
CreateDirectoryIfNotExists(Path.Combine(destination, "processed"));
CreateDirectoryIfNotExists(Path.Combine(destination, "error"));
CreateDirectoryIfNotExists(Path.Combine(destination, "unprocessed"));
CreateDirectoryIfNotExists(Path.Combine(destination, "processed_done"));
CreateFileIfNotExists(Path.Combine(exePath, "ihas.properties"));
}
private static void CreateDirectoryIfNotExists(string path)
{
if (!string.IsNullOrWhiteSpace(path) && !Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
private static void CreateFileIfNotExists(string path)
{
if (!string.IsNullOrWhiteSpace(path) && !File.Exists(path))
{
using (File.Create(path)) { }
}
}
}
}