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 allSessions = new Dictionary(); private readonly object processingLock = new object(); private readonly HashSet filesInProgress = new HashSet(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() .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)) { } } } } }