diff --git a/FileInterface.sln b/FileInterface.sln
new file mode 100644
index 0000000..f810b6a
--- /dev/null
+++ b/FileInterface.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31912.275
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileInterface", "FileInterface\FileInterface.csproj", "{1DCDC78F-3042-44C5-91CC-1AFB1C752386}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1DCDC78F-3042-44C5-91CC-1AFB1C752386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1DCDC78F-3042-44C5-91CC-1AFB1C752386}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1DCDC78F-3042-44C5-91CC-1AFB1C752386}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1DCDC78F-3042-44C5-91CC-1AFB1C752386}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F96FA68E-090C-49D8-A249-4452537D06C3}
+ EndGlobalSection
+EndGlobal
diff --git a/FileInterface/App.config b/FileInterface/App.config
new file mode 100644
index 0000000..26aaa84
--- /dev/null
+++ b/FileInterface/App.config
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FileInterface/CodeFile1.cs b/FileInterface/CodeFile1.cs
new file mode 100644
index 0000000..0b0886c
--- /dev/null
+++ b/FileInterface/CodeFile1.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+
+
+namespace com.itac.mes.imsapi.client.dotnet
+{
+ class IMSApiDotNetTestclientBase
+ {
+ public const int FILL_WITH_SPACE = 50;
+
+
+ public static String fillWithSpace(String value)
+ {
+ StringBuilder line = new StringBuilder(value);
+ while (line.Length < FILL_WITH_SPACE)
+ {
+ line.Append(' ');
+ }
+ return line.ToString();
+ }
+
+
+ public static MethodInfo dectectMethod(Type testClientClass, String name)
+ {
+ MethodInfo result = null;
+ MethodInfo[] methods = testClientClass.GetMethods();
+ ArrayList hits = new ArrayList();
+ for (int m = 0; m < methods.Length; m++)
+ {
+ if (methods[m].Name.ToLower().StartsWith(("test_" + name).ToLower()))
+ {
+ hits.Add(methods[m]);
+ }
+ }
+ if (hits.Count == 0)
+ {
+ }
+ else if (hits.Count == 1)
+ {
+ result = (MethodInfo)hits[0];
+ }
+ else
+ {
+ for (int m = 0; m < hits.Count; m++)
+ {
+ Console.Out.WriteLine((m + 1) + ": " + ((MethodInfo)hits[m]).Name.Substring(5));
+ }
+ Console.Out.WriteLine("Choose index of function <1-" + hits.Count + ">: ");
+ try
+ {
+ int index = Int32.Parse(getInput());
+ if (index > 0 && index <= hits.Count)
+ {
+ result = (MethodInfo)hits[index - 1];
+ }
+ }
+ catch (FormatException fe)
+ {
+ Console.Out.WriteLine("Invalid input.");
+ }
+ }
+ return result;
+ }
+
+ public static String getInput()
+ {
+ return Console.ReadLine();
+ }
+
+ public static String getInputNoEcho()
+ {
+ StringBuilder line = new StringBuilder();
+ while (true)
+ {
+ ConsoleKeyInfo info = Console.ReadKey(true);
+ if (info.Key == ConsoleKey.Enter)
+ {
+ Console.Out.WriteLine();
+ break;
+ }
+ else if (info.Key == ConsoleKey.Backspace)
+ {
+ line.Remove(line.Length - 1, 1);
+ }
+ else
+ {
+ line.Append(info.KeyChar);
+ }
+ }
+ return line.ToString();
+ }
+ }
+}
diff --git a/FileInterface/FileInterface.csproj b/FileInterface/FileInterface.csproj
new file mode 100644
index 0000000..edc82bb
--- /dev/null
+++ b/FileInterface/FileInterface.csproj
@@ -0,0 +1,94 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {1DCDC78F-3042-44C5-91CC-1AFB1C752386}
+ Exe
+ FileInterface
+ FileInterface
+ v4.8
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\..\..\..\..\test\imsapi-dotnet-9.20.00-24-net4\IMSApiDotNet.dll
+
+
+ ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+ ..\packages\System.Configuration.ConfigurationManager.6.0.0\lib\net461\System.Configuration.ConfigurationManager.dll
+
+
+
+
+
+
+ ..\packages\System.Security.AccessControl.6.0.0\lib\net461\System.Security.AccessControl.dll
+
+
+ ..\packages\System.Security.Permissions.6.0.0\lib\net461\System.Security.Permissions.dll
+
+
+ ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
\ No newline at end of file
diff --git a/FileInterface/OnChangedProcess.cs b/FileInterface/OnChangedProcess.cs
new file mode 100644
index 0000000..cebc75f
--- /dev/null
+++ b/FileInterface/OnChangedProcess.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using com.itac.mes.imsapi.client.dotnet;
+
+// Stellen Sie sicher, dass Sie die richtigen Namensräume für Ihre IMSApi-Objekte importieren
+using com.itac.mes.imsapi.domain;
+using com.itac.mes.imsapi.domain.container;
+
+public static class OnChangedProcess
+{
+ public static void OnChanged(IMSApiSessionContextStruct sessionContext, object source, FileSystemEventArgs e)
+ {
+ string processpath = ConfigurationManager.AppSettings["PROCESS_FILES_PATH"];
+ string designator = ConfigurationManager.AppSettings["Designator"];
+ string mode = "MODE|" + ConfigurationManager.AppSettings["MODE"];
+ string panelmode = "PANEL_MODE|" + ConfigurationManager.AppSettings["PANEL_MODE"];
+ string processmode = "PROCESS_MODE|" + ConfigurationManager.AppSettings["PROCESS_MODE"];
+ string station = "ST|" + ConfigurationManager.AppSettings["StationNr"];
+
+ string filename = e.Name;
+ string filecontentprocess = "";
+ string parameters = "";
+ IIMSApiDotNet imsapi = null;
+
+ try
+ {
+ // Lesen der Datei
+ using (StreamReader r = new StreamReader(e.FullPath))
+ {
+ filecontentprocess = r.ReadToEnd();
+ }
+
+ // Zusammenfassen der Dateiinhalte mit Designator
+ List lines = new List(filecontentprocess.Split(new string[] { "\r\n" }, StringSplitOptions.None));
+ parameters = string.Join(designator, lines);
+
+ // Ihre IMSApi-Aufruflogik
+ string[] outargs;
+ string customErrorString;
+ int result = imsapi.customFunction(sessionContext, ConfigurationManager.AppSettings["CF_Processfiles"], new string[] { station, mode, panelmode, processmode, "FILENAME|" + filename, "RAW|" + parameters }, out outargs, out customErrorString);
+
+ // Protokollierung und Dateiverschiebung
+ HandleResultAndMoveFile(result, customErrorString, e.FullPath, processpath, filename);
+
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Fehler bei der Verarbeitung der Datei {filename}: {ex.Message}");
+ }
+ }
+
+ private static void HandleResultAndMoveFile(int result, string customErrorString, string originalFilePath, string destinationPath, string filename)
+ {
+ // Protokollierung
+ LogResult(result, customErrorString);
+
+ // Verschieben der Datei in den processed_done Ordner
+ string newPath = Path.Combine(destinationPath, "processed_done", filename);
+ if (!Directory.Exists(Path.GetDirectoryName(newPath)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(newPath));
+ }
+ File.Move(originalFilePath, newPath);
+ }
+
+ private static void LogResult(int result, string customErrorString)
+ {
+ // Implementieren Sie Ihre Logik hier
+ }
+}
diff --git a/FileInterface/Program.cs b/FileInterface/Program.cs
new file mode 100644
index 0000000..c35ee28
--- /dev/null
+++ b/FileInterface/Program.cs
@@ -0,0 +1,754 @@
+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)) { }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileInterface/Properties/AssemblyInfo.cs b/FileInterface/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..7382a68
--- /dev/null
+++ b/FileInterface/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Allgemeine Informationen über eine Assembly werden über die folgenden
+// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
+// die einer Assembly zugeordnet sind.
+[assembly: AssemblyTitle("FileInterface")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("iTAC Software AG")]
+[assembly: AssemblyProduct("FileInterface")]
+[assembly: AssemblyCopyright("Copyright © iTAC Software AG 2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
+// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
+// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("1dcdc78f-3042-44c5-91cc-1afb1c752386")]
+
+// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
+//
+// Hauptversion
+// Nebenversion
+// Buildnummer
+// Revision
+//
+// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
+// indem Sie "*" wie unten gezeigt eingeben:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/FileInterface/Properties/Resources.Designer.cs b/FileInterface/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..0f9cefb
--- /dev/null
+++ b/FileInterface/Properties/Resources.Designer.cs
@@ -0,0 +1,144 @@
+//------------------------------------------------------------------------------
+//
+// Dieser Code wurde von einem Tool generiert.
+// Laufzeitversion:4.0.30319.42000
+//
+// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
+// der Code erneut generiert wird.
+//
+//------------------------------------------------------------------------------
+
+namespace FileInterface.Properties {
+ using System;
+
+
+ ///
+ /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
+ ///
+ // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
+ // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
+ // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
+ // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FileInterface.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
+ /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die FileInterface ähnelt.
+ ///
+ internal static string AppID {
+ get {
+ return ResourceManager.GetString("AppID", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die ähnelt.
+ ///
+ internal static string CF_Name {
+ get {
+ return ResourceManager.GetString("CF_Name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die 01 ähnelt.
+ ///
+ internal static string ClientNO {
+ get {
+ return ResourceManager.GetString("ClientNO", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die c:\temp ähnelt.
+ ///
+ internal static string Destination {
+ get {
+ return ResourceManager.GetString("Destination", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die ähnelt.
+ ///
+ internal static string MODE {
+ get {
+ return ResourceManager.GetString("MODE", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die off ähnelt.
+ ///
+ internal static string resultfile {
+ get {
+ return ResourceManager.GetString("resultfile", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die http://ksbrose:8080/mes ähnelt.
+ ///
+ internal static string Server_URL {
+ get {
+ return ResourceManager.GetString("Server_URL", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die c:\test ähnelt.
+ ///
+ internal static string SourcePath {
+ get {
+ return ResourceManager.GetString("SourcePath", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die 1017VFE010100110 ähnelt.
+ ///
+ internal static string StationNr {
+ get {
+ return ResourceManager.GetString("StationNr", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/FileInterface/Properties/Resources.resx b/FileInterface/Properties/Resources.resx
new file mode 100644
index 0000000..0d7598b
--- /dev/null
+++ b/FileInterface/Properties/Resources.resx
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ FileInterface
+
+
+
+
+
+ 01
+
+
+ c:\temp
+
+
+
+
+
+ off
+
+
+ http://ksbrose:8080/mes
+
+
+ c:\test
+
+
+ 1017VFE010100110
+
+
\ No newline at end of file
diff --git a/FileInterface/packages.config b/FileInterface/packages.config
new file mode 100644
index 0000000..aaef379
--- /dev/null
+++ b/FileInterface/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/readme_de.txt b/readme_de.txt
new file mode 100644
index 0000000..bf0f651
--- /dev/null
+++ b/readme_de.txt
@@ -0,0 +1,4 @@
+Das FileInterface ist ein automatisierter Windows-Dienst zur Verarbeitung von Eingabedateien.
+Es überwacht ein Verzeichnis, liest neue Dateien ein, bereitet deren Inhalte roh auf (z. B. Entfernen leerer Zeilen und Zusammenführen mit einem Trenner) und übergibt die Daten anschließend an eine IMS Custom Function (CF) zur weiteren Verarbeitung im System.
+
+Das Interface dient als flexible Schnittstelle zwischen Dateiquellen (z. B. Maschinen, Fremdsysteme) und der MES-/IMS-Logik.
\ No newline at end of file
diff --git a/readme_en.txt b/readme_en.txt
new file mode 100644
index 0000000..0a2c8ea
--- /dev/null
+++ b/readme_en.txt
@@ -0,0 +1,4 @@
+The FileInterface is an automated Windows service for processing input files.
+It monitors a directory, reads incoming files, performs raw parsing (e.g. removing empty lines and joining content using a separator), and forwards the data to an IMS custom function (CF) for further processing.
+
+The interface acts as a flexible bridge between file-based sources (e.g. machines or external systems) and MES/IMS logic.
\ No newline at end of file