Compare commits

..
Author SHA1 Message Date
michal c8b5732046 Fix Genesis scheduling and synchronization for up to 10 workers
(cherry picked from commit 684513ee59)
2026-09-10 22:59:56 +02:00
michal 02de7c6c57 FIX GENESIS
Enhance Genesis configuration and validation mechanisms

- Introduce new test methods for testing Genesis properties, boundary validations, runtime behavior, and tooltips with culture-specific fallback.
- Expand configuration validation logic with enhanced error messaging and logging.
- Simplify Genesis property UI: remove legacy settings and refactor controls for robust behavior.
- Add detailed logging for runtime and configuration updates.
- Provide tooltips and localized support for Genesis properties in multiple languages (e.g., `de`, `sk`).
- Update project file to include new test files.
2026-09-10 21:33:55 +02:00
487 changed files with 3994 additions and 104259 deletions
+1 -64
View File
@@ -1,10 +1,3 @@
# Generated build outputs for all projects, including Debug/Release archives.
[Bb]in/
[Oo]bj/
# Generated test results and hardware diagnostic captures.
[Tt]est[Rr]esults/
AppDiagnostic/bin/
AppDiagnostic/obj/
Common/bin/
@@ -100,8 +93,7 @@ Users/obj/
TBF.sln.DotSettings.user
/ExternalProjects
/GenesisCordonelTester
/packages/*
!/packages/Common/
/packages
/GenesisCordonelInterface/bin/Debug/GenesisCordonelInterfaceLogger.log
/GenesisCordonelInterface/bin/Debug/GenesisCordonelTesterLogger.log
/GenesisCordonelInterface/bin/Debug/SirtConfig.json
@@ -109,58 +101,3 @@ TBF.sln.DotSettings.user
/GenesisCordonelInterface/obj/Debug
/GenesisCordonelInterface/obj/Release
/ExternalProjects
/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.dll
/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.pdb
/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfo.cs
/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache
/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache
/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.dll
/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Metrology.Measurements.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Metrology.Measurements.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Ui.CordonelPreadjustmentUi.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Ui.CordonelPreadjustmentUi.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Ui.GenesisToolBox.exe
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Ui.GenesisToolBox.pdb
/GenesisCordonelInterface/bin/Debug/XylemCommonUiLegacyGenCtl.dll
/GenesisCordonelInterface/bin/Debug/XylemCommonUiLegacyGenCtl.pdb
/GenesisCordonelInterface/bin/Debug/de/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.resources.dll
/GenesisCordonelInterface/bin/Debug/GenesisCordonelInterface.exe
/GenesisCordonelInterface/bin/Debug/GenesisCordonelInterface.pdb
/GenesisCordonelInterface/bin/Debug/TempFlansh.exe
/GenesisCordonelInterface/bin/Debug/TempFlansh.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.eRegister.eRegisterCore.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.eRegister.eRegisterCore.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.StreamingProtocol.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.StreamingProtocol.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxConfig.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxConfig.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Logic.RelatePcb.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Logic.RelatePcb.pdb
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Logic.ServiceCore.dll
/GenesisCordonelInterface/bin/Debug/Xylem.Common.Logic.ServiceCore.pdb
/GenesisCordonelInterface/RuntimePackage/Package
/SharedComponents/obj/SharedComponents.csproj.nuget.g.props
/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfo.cs
/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache
/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache
/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.dll
/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.pdb
/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.dll
/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.pdb
@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<LangVersion>7.3</LangVersion>
<RootNamespace>AppDiagnostic.Logging</RootNamespace>
<AssemblyName>AppDiagnostic.Logging</AssemblyName>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<ProjectGuid>{A7AF8ED2-362E-4DBB-B607-EBA2820719EC}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="5.2.2" />
<Reference Include="System.Runtime.Serialization" />
</ItemGroup>
</Project>
@@ -1,41 +0,0 @@
using NLog;
using NLog.Config;
using NLog.Layouts;
namespace AppDiagnostic.Logging
{
public static class AppDiagnosticConnector
{
private static readonly object syncRoot = new object();
public static void Register()
{
lock (syncRoot)
{
LoggingConfiguration configuration = LogManager.Configuration ?? new LoggingConfiguration();
NLog.Targets.Target existingTarget = configuration.FindTargetByName("appDiagnostic");
if (existingTarget != null)
{
return;
}
var target = new AppDiagnosticTarget
{
Name = "appDiagnostic",
PipeName = DiagnosticProtocol.DefaultPipeName,
QueueLimit = DiagnosticProtocol.DefaultQueueLimit,
ReconnectDelayMilliseconds = DiagnosticProtocol.DefaultReconnectDelayMilliseconds,
ConnectTimeoutMilliseconds = DiagnosticProtocol.DefaultConnectTimeoutMilliseconds,
Layout = new SimpleLayout("${message}")
};
configuration.AddTarget(target.Name, target);
configuration.AddRule(LogLevel.Trace, LogLevel.Fatal, target, "*");
LogManager.Configuration = configuration;
LogManager.ReconfigExistingLoggers();
}
}
}
}
@@ -1,233 +0,0 @@
using NLog;
using NLog.Targets;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
namespace AppDiagnostic.Logging
{
[Target("AppDiagnostic")]
public sealed class AppDiagnosticTarget : TargetWithLayout
{
private readonly object connectionLock = new object();
private readonly string applicationName;
private readonly int processId;
private BlockingCollection<DiagnosticLogEntry> queue;
private CancellationTokenSource cancellation;
private Task senderTask;
private NamedPipeClientStream pipeClient;
public AppDiagnosticTarget()
{
using (Process process = Process.GetCurrentProcess())
{
applicationName = process.ProcessName;
processId = process.Id;
}
PipeName = DiagnosticProtocol.DefaultPipeName;
QueueLimit = DiagnosticProtocol.DefaultQueueLimit;
ReconnectDelayMilliseconds =
DiagnosticProtocol.DefaultReconnectDelayMilliseconds;
ConnectTimeoutMilliseconds =
DiagnosticProtocol.DefaultConnectTimeoutMilliseconds;
}
public string PipeName { get; set; }
public int QueueLimit { get; set; }
public int ReconnectDelayMilliseconds { get; set; }
public int ConnectTimeoutMilliseconds { get; set; }
protected override void InitializeTarget()
{
base.InitializeTarget();
int queueLimit = QueueLimit > 0
? QueueLimit
: DiagnosticProtocol.DefaultQueueLimit;
queue = new BlockingCollection<DiagnosticLogEntry>(queueLimit);
cancellation = new CancellationTokenSource();
senderTask = Task.Factory.StartNew(
ProcessQueue,
cancellation.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
protected override void Write(LogEventInfo logEvent)
{
if (logEvent == null || queue == null || queue.IsAddingCompleted)
{
return;
}
var entry = new DiagnosticLogEntry
{
TimestampUtc = logEvent.TimeStamp.ToUniversalTime(),
MachineName = Environment.MachineName,
ApplicationName = applicationName,
ProcessId = processId,
LoggerName = logEvent.LoggerName ?? string.Empty,
Level = logEvent.Level?.Name ?? string.Empty,
Message = Layout != null
? Layout.Render(logEvent)
: logEvent.FormattedMessage,
Exception = logEvent.Exception?.ToString() ?? string.Empty
};
try
{
queue.TryAdd(entry);
}
catch (ObjectDisposedException)
{
// A concurrent NLog shutdown may dispose the queue.
}
catch (InvalidOperationException)
{
// A concurrent NLog shutdown may complete the queue.
}
}
protected override void CloseTarget()
{
BlockingCollection<DiagnosticLogEntry> currentQueue = queue;
CancellationTokenSource currentCancellation = cancellation;
if (currentQueue != null)
{
currentQueue.CompleteAdding();
}
if (currentCancellation != null)
{
currentCancellation.Cancel();
}
CloseConnection();
try
{
senderTask?.Wait(2000);
}
catch (AggregateException)
{
// Cancellation during application shutdown is expected.
}
currentQueue?.Dispose();
currentCancellation?.Dispose();
queue = null;
cancellation = null;
senderTask = null;
base.CloseTarget();
}
private void ProcessQueue()
{
CancellationToken token = cancellation.Token;
try
{
foreach (DiagnosticLogEntry entry in
queue.GetConsumingEnumerable(token))
{
SendWhenConnected(entry, token);
}
}
catch (OperationCanceledException)
{
// Target shutdown is expected to cancel the sender.
}
}
private void SendWhenConnected(
DiagnosticLogEntry entry,
CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
EnsureConnected();
DiagnosticMessageSerializer.WriteFramed(pipeClient, entry);
return;
}
catch (Exception exception) when (
exception is IOException ||
exception is TimeoutException ||
exception is InvalidOperationException)
{
CloseConnection();
int delay = ReconnectDelayMilliseconds > 0
? ReconnectDelayMilliseconds
: DiagnosticProtocol.DefaultReconnectDelayMilliseconds;
if (token.WaitHandle.WaitOne(delay))
{
return;
}
}
}
}
private void EnsureConnected()
{
lock (connectionLock)
{
if (pipeClient != null && pipeClient.IsConnected)
{
return;
}
CloseConnectionWithoutLock();
pipeClient = new NamedPipeClientStream(
".",
string.IsNullOrWhiteSpace(PipeName)
? DiagnosticProtocol.DefaultPipeName
: PipeName,
PipeDirection.Out,
PipeOptions.Asynchronous);
int timeout = ConnectTimeoutMilliseconds > 0
? ConnectTimeoutMilliseconds
: DiagnosticProtocol.DefaultConnectTimeoutMilliseconds;
pipeClient.Connect(timeout);
}
}
private void CloseConnection()
{
lock (connectionLock)
{
CloseConnectionWithoutLock();
}
}
private void CloseConnectionWithoutLock()
{
if (pipeClient == null)
{
return;
}
pipeClient.Dispose();
pipeClient = null;
}
}
}
@@ -1,33 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace AppDiagnostic.Logging
{
[DataContract]
public sealed class DiagnosticLogEntry
{
[DataMember(Order = 1)]
public DateTime TimestampUtc { get; set; }
[DataMember(Order = 2)]
public string MachineName { get; set; }
[DataMember(Order = 3)]
public string ApplicationName { get; set; }
[DataMember(Order = 4)]
public int ProcessId { get; set; }
[DataMember(Order = 5)]
public string LoggerName { get; set; }
[DataMember(Order = 6)]
public string Level { get; set; }
[DataMember(Order = 7)]
public string Message { get; set; }
[DataMember(Order = 8)]
public string Exception { get; set; }
}
}
@@ -1,113 +0,0 @@
using System;
using System.IO;
using System.Runtime.Serialization.Json;
namespace AppDiagnostic.Logging
{
public static class DiagnosticMessageSerializer
{
private static readonly DataContractJsonSerializer Serializer =
new DataContractJsonSerializer(typeof(DiagnosticLogEntry));
private static readonly object SerializerLock = new object();
public static byte[] Serialize(DiagnosticLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (var stream = new MemoryStream())
{
lock (SerializerLock)
{
Serializer.WriteObject(stream, entry);
}
return stream.ToArray();
}
}
public static DiagnosticLogEntry Deserialize(byte[] payload)
{
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
if (payload.Length == 0 ||
payload.Length > DiagnosticProtocol.MaximumMessageSize)
{
throw new InvalidDataException("Invalid diagnostic message size.");
}
using (var stream = new MemoryStream(payload, false))
{
lock (SerializerLock)
{
return (DiagnosticLogEntry)Serializer.ReadObject(stream);
}
}
}
public static void WriteFramed(Stream stream, DiagnosticLogEntry entry)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
byte[] payload = Serialize(entry);
if (payload.Length > DiagnosticProtocol.MaximumMessageSize)
{
throw new InvalidDataException("Diagnostic message is too large.");
}
byte[] length = BitConverter.GetBytes(payload.Length);
stream.Write(length, 0, length.Length);
stream.Write(payload, 0, payload.Length);
stream.Flush();
}
public static DiagnosticLogEntry ReadFramed(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
byte[] lengthBuffer = ReadExactly(stream, sizeof(int));
int payloadLength = BitConverter.ToInt32(lengthBuffer, 0);
if (payloadLength <= 0 ||
payloadLength > DiagnosticProtocol.MaximumMessageSize)
{
throw new InvalidDataException("Invalid diagnostic message size.");
}
return Deserialize(ReadExactly(stream, payloadLength));
}
private static byte[] ReadExactly(Stream stream, int count)
{
byte[] buffer = new byte[count];
int offset = 0;
while (offset < count)
{
int bytesRead = stream.Read(buffer, offset, count - offset);
if (bytesRead == 0)
{
throw new EndOfStreamException();
}
offset += bytesRead;
}
return buffer;
}
}
}
@@ -1,11 +0,0 @@
namespace AppDiagnostic.Logging
{
public static class DiagnosticProtocol
{
public const string DefaultPipeName = "Sensus.AppDiagnostic.LogPipe";
public const int MaximumMessageSize = 1024 * 1024;
public const int DefaultQueueLimit = 10000;
public const int DefaultReconnectDelayMilliseconds = 500;
public const int DefaultConnectTimeoutMilliseconds = 250;
}
}
-37
View File
@@ -1,37 +0,0 @@
# AppDiagnostic.Logging
Reusable NLog target for sending structured diagnostic events from multiple
.NET Framework processes to `AppDiagnostic.exe` through a local named pipe.
## Producer integration
Reference this project from the producer application and add the extension and
target to its NLog configuration:
```xml
<extensions>
<add assembly="AppDiagnostic.Logging" />
</extensions>
<targets>
<target name="appDiagnostic"
xsi:type="AppDiagnostic"
layout="${message}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="appDiagnostic" />
</rules>
```
The target writes to a bounded background queue. Producer threads are not
blocked while the diagnostic viewer is unavailable. Queued messages are sent
after the viewer becomes available; new messages are discarded when the queue
limit is reached.
The receiver can read messages with:
```csharp
DiagnosticLogEntry entry =
DiagnosticMessageSerializer.ReadFramed(namedPipeServerStream);
```
Binary file not shown.
Binary file not shown.
@@ -1,65 +0,0 @@
{
"format": 1,
"restore": {
"C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj": {}
},
"projects": {
"C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
"projectName": "AppDiagnostic.Logging",
"projectPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
"packagesPath": "C:\\Users\\MFRNIAK\\.nuget\\packages\\",
"outputPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\MFRNIAK\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net472"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net472": {
"framework": "net472",
"targetAlias": "net472",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.300"
},
"frameworks": {
"net472": {
"framework": "net472",
"targetAlias": "net472",
"dependencies": {
"NLog": {
"target": "Package",
"version": "[5.2.2, )"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.303\\RuntimeIdentifierGraph.json"
}
}
}
}
}
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
@@ -1,8 +0,0 @@
is_global = true
build_property.RootNamespace = AppDiagnostic.Logging
build_property.ProjectDir = C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.CsWinRTUseWindowsUIXamlProjections = false
build_property.EffectiveAnalysisLevelStyle =
build_property.EnableCodeStyleSeverity =
@@ -1 +0,0 @@
551ba94934db30a277a1a9cec2dacbe03f8393107e5d0b2436695f9bd62bd569
@@ -1,11 +0,0 @@
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Debug\net472\AppDiagnostic.Logging.dll
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Debug\net472\AppDiagnostic.Logging.pdb
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Debug\net472\NLog.dll
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.csproj.AssemblyReference.cache
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.AssemblyInfoInputs.cache
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.AssemblyInfo.cs
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagn.CFEA7AC0.Up2Date
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.dll
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.pdb
@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
@@ -1,8 +0,0 @@
is_global = true
build_property.RootNamespace = AppDiagnostic.Logging
build_property.ProjectDir = C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.CsWinRTUseWindowsUIXamlProjections = false
build_property.EffectiveAnalysisLevelStyle =
build_property.EnableCodeStyleSeverity =
@@ -1 +0,0 @@
a53d59482cad49b656138b1af5cf3e55edf2108969aedc574c362be6c671436b
@@ -1,11 +0,0 @@
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Release\net472\AppDiagnostic.Logging.dll
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Release\net472\AppDiagnostic.Logging.pdb
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Release\net472\NLog.dll
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.csproj.AssemblyReference.cache
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.AssemblyInfoInputs.cache
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.AssemblyInfo.cs
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagn.CFEA7AC0.Up2Date
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.dll
C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.pdb
@@ -1,119 +0,0 @@
{
"version": 4,
"targets": {
"net472": {
"NLog/5.2.2": {
"type": "package",
"frameworkAssemblies": [
"System",
"System.Configuration",
"System.Core",
"System.IO.Compression",
"System.Xml"
],
"compile": {
"lib/net46/NLog.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net46/NLog.dll": {
"related": ".xml"
}
}
}
}
},
"libraries": {
"NLog/5.2.2": {
"sha512": "r6Q9740g29gTwmTWlsgdIFm0mhNsfNZmbvWKX/Fxmi8X89ZrpUowHM2T2X1lP7RVpND+ef+XnfKL5g6Q1iNGXA==",
"type": "package",
"path": "nlog/5.2.2",
"files": [
".nupkg.metadata",
".signature.p7s",
"N.png",
"lib/net35/NLog.dll",
"lib/net35/NLog.xml",
"lib/net45/NLog.dll",
"lib/net45/NLog.xml",
"lib/net46/NLog.dll",
"lib/net46/NLog.xml",
"lib/netstandard1.3/NLog.dll",
"lib/netstandard1.3/NLog.xml",
"lib/netstandard1.5/NLog.dll",
"lib/netstandard1.5/NLog.xml",
"lib/netstandard2.0/NLog.dll",
"lib/netstandard2.0/NLog.xml",
"nlog.5.2.2.nupkg.sha512",
"nlog.nuspec"
]
}
},
"projectFileDependencyGroups": {
"net472": [
"NLog >= 5.2.2"
]
},
"packageFolders": {
"C:\\Users\\MFRNIAK\\.nuget\\packages\\": {},
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
},
"project": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
"projectName": "AppDiagnostic.Logging",
"projectPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
"packagesPath": "C:\\Users\\MFRNIAK\\.nuget\\packages\\",
"outputPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\MFRNIAK\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net472"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net472": {
"framework": "net472",
"targetAlias": "net472",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.300"
},
"frameworks": {
"net472": {
"framework": "net472",
"targetAlias": "net472",
"dependencies": {
"NLog": {
"target": "Package",
"version": "[5.2.2, )"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.303\\RuntimeIdentifierGraph.json"
}
}
}
}
@@ -1,10 +0,0 @@
{
"version": 2,
"dgSpecHash": "ezwKOPZAAXU=",
"success": true,
"projectFilePath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
"expectedPackageFiles": [
"C:\\Users\\MFRNIAK\\.nuget\\packages\\nlog\\5.2.2\\nlog.5.2.2.nupkg.sha512"
],
"logs": []
}
+14 -9
View File
@@ -3,12 +3,17 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="NLog" publicKeyToken="5120e14c03d0593c" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
<log4net>
<appender name="LogCacheAppender" type="Namespace.LogCacheAppender, AssemblyName">
<param name="Cache" value="YourCacheInstance" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="LogCacheAppender" />
</root>
</log4net>
</configuration>
+12 -11
View File
@@ -33,6 +33,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.3.0.3\lib\net462\log4net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
@@ -48,15 +51,12 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ColorRuleEditorForm.cs">
<SubType>Form</SubType>
<Compile Include="BufferedListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="DiagnosticLogFormatter.cs" />
<Compile Include="DiagnosticLogStore.cs" />
<Compile Include="DiagnosticPipeServer.cs" />
<Compile Include="LogColorizer.cs" />
<Compile Include="LogColorRule.cs" />
<Compile Include="LogColorRuleRepository.cs" />
<Compile Include="DiagApi.cs" />
<Compile Include="LogAdapter.cs" />
<Compile Include="LogFilter.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
@@ -78,6 +78,7 @@
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
@@ -92,9 +93,9 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppDiagnostic.Logging\AppDiagnostic.Logging.csproj">
<Project>{A7AF8ED2-362E-4DBB-B607-EBA2820719EC}</Project>
<Name>AppDiagnostic.Logging</Name>
<ProjectReference Include="..\SharedComponents\SharedComponents.csproj">
<Project>{8f942729-f454-4c99-ba6c-746962065ae3}</Project>
<Name>SharedComponents</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
-319
View File
@@ -1,319 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace AppDiagnostic
{
public sealed class ColorRuleEditorForm : Form
{
private readonly List<LogColorRule> rules;
private readonly ListBox ruleList = new ListBox();
private readonly TextBox patternTextBox = new TextBox();
private readonly CheckBox enabledCheckBox = new CheckBox();
private readonly CheckBox boldCheckBox = new CheckBox();
private readonly CheckBox backgroundCheckBox = new CheckBox();
private readonly Button foregroundButton = new Button();
private readonly Button backgroundButton = new Button();
private readonly RichTextBox previewBox = new RichTextBox();
private readonly Font previewBoldFont;
private bool loadingRule;
public ColorRuleEditorForm(IEnumerable<LogColorRule> sourceRules)
{
rules = sourceRules.Select(rule => rule.Clone()).ToList();
InitializeWindow();
previewBoldFont = new Font(previewBox.Font, FontStyle.Bold);
RefreshRuleList(0);
RefreshPreview();
}
public List<LogColorRule> Rules => rules.Select(rule => rule.Clone()).ToList();
protected override void Dispose(bool disposing)
{
if (disposing)
{
previewBoldFont.Dispose();
}
base.Dispose(disposing);
}
private void InitializeWindow()
{
Text = "Log color rules";
Icon = Properties.Resources.TBF_icon;
StartPosition = FormStartPosition.CenterParent;
MinimumSize = new Size(760, 500);
ClientSize = new Size(860, 560);
var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 3, Padding = new Padding(10) };
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 145F));
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 42F));
var split = new SplitContainer { Dock = DockStyle.Fill, SplitterDistance = 280 };
split.Panel1.Padding = new Padding(0, 0, 8, 0);
split.Panel2.Padding = new Padding(8, 0, 0, 0);
ruleList.Dock = DockStyle.Fill;
ruleList.IntegralHeight = false;
ruleList.SelectedIndexChanged += RuleList_SelectedIndexChanged;
var listButtons = new FlowLayoutPanel { Dock = DockStyle.Bottom, Height = 36, FlowDirection = FlowDirection.LeftToRight };
listButtons.Controls.Add(CreateButton("Add", AddRule_Click));
listButtons.Controls.Add(CreateButton("Delete", DeleteRule_Click));
listButtons.Controls.Add(CreateButton("Up", MoveUp_Click));
listButtons.Controls.Add(CreateButton("Down", MoveDown_Click));
split.Panel1.Controls.Add(ruleList);
split.Panel1.Controls.Add(listButtons);
var editor = new TableLayoutPanel { Dock = DockStyle.Top, AutoSize = true, ColumnCount = 2, Padding = new Padding(4) };
editor.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120F));
editor.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
enabledCheckBox.Text = "Rule enabled";
enabledCheckBox.AutoSize = true;
enabledCheckBox.CheckedChanged += EditorValueChanged;
AddEditorRow(editor, "State", enabledCheckBox);
patternTextBox.Dock = DockStyle.Fill;
patternTextBox.TextChanged += EditorValueChanged;
AddEditorRow(editor, "Substring", patternTextBox);
foregroundButton.Text = "Choose text color...";
foregroundButton.AutoSize = true;
foregroundButton.Click += ForegroundButton_Click;
AddEditorRow(editor, "Text color", foregroundButton);
var backgroundPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true };
backgroundCheckBox.Text = "Use background";
backgroundCheckBox.AutoSize = true;
backgroundCheckBox.CheckedChanged += EditorValueChanged;
backgroundButton.Text = "Choose...";
backgroundButton.AutoSize = true;
backgroundButton.Click += BackgroundButton_Click;
backgroundPanel.Controls.Add(backgroundCheckBox);
backgroundPanel.Controls.Add(backgroundButton);
AddEditorRow(editor, "Background", backgroundPanel);
boldCheckBox.Text = "Bold text";
boldCheckBox.AutoSize = true;
boldCheckBox.CheckedChanged += EditorValueChanged;
AddEditorRow(editor, "Font", boldCheckBox);
var resetButton = CreateButton("Restore default rules", RestoreDefaults_Click);
resetButton.AutoSize = true;
AddEditorRow(editor, string.Empty, resetButton);
split.Panel2.Controls.Add(editor);
previewBox.Dock = DockStyle.Fill;
previewBox.ReadOnly = true;
previewBox.WordWrap = true;
previewBox.Font = new Font("Consolas", 9F);
previewBox.Text = "2026-09-10 14:25:31.042|DEBUG|TBF[14280]|TBF.UI.MainWnd|Device configuration loaded.\n2026-09-10 14:25:31.384|INFO |TBF[14280]|TBF.Rig.StateMachine|Test bench started.\n2026-09-10 14:25:32.018|WARN |TBF[14280]|TBF.Device|Pressure is approaching the limit.\n2026-09-10 14:25:32.611|ERROR|TBF[14280]|TBF.Device|Communication failed.";
var previewGroup = new GroupBox { Text = "Live preview", Dock = DockStyle.Fill, Padding = new Padding(8) };
previewGroup.Controls.Add(previewBox);
var footer = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.RightToLeft, Padding = new Padding(0, 8, 0, 0) };
var okButton = new Button { Text = "OK", DialogResult = DialogResult.OK, AutoSize = true };
var cancelButton = new Button { Text = "Cancel", DialogResult = DialogResult.Cancel, AutoSize = true };
footer.Controls.Add(okButton);
footer.Controls.Add(cancelButton);
AcceptButton = okButton;
CancelButton = cancelButton;
root.Controls.Add(split, 0, 0);
root.Controls.Add(previewGroup, 0, 1);
root.Controls.Add(footer, 0, 2);
Controls.Add(root);
}
private static Button CreateButton(string text, EventHandler clickHandler)
{
var button = new Button { Text = text, AutoSize = true };
button.Click += clickHandler;
return button;
}
private static void AddEditorRow(TableLayoutPanel panel, string labelText, Control control)
{
int row = panel.RowCount++;
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
var label = new Label { Text = labelText, AutoSize = true, Anchor = AnchorStyles.Left, Margin = new Padding(3, 8, 3, 3) };
control.Margin = new Padding(3, 5, 3, 5);
panel.Controls.Add(label, 0, row);
panel.Controls.Add(control, 1, row);
}
private void RefreshRuleList(int selectedIndex)
{
ruleList.BeginUpdate();
ruleList.Items.Clear();
foreach (LogColorRule rule in rules)
{
ruleList.Items.Add(rule);
}
ruleList.EndUpdate();
if (rules.Count > 0)
{
ruleList.SelectedIndex = Math.Max(0, Math.Min(selectedIndex, rules.Count - 1));
}
}
private void RuleList_SelectedIndexChanged(object sender, EventArgs e)
{
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
if (rule == null)
{
return;
}
loadingRule = true;
enabledCheckBox.Checked = rule.Enabled;
patternTextBox.Text = rule.Pattern;
foregroundButton.BackColor = rule.ForeColor;
backgroundCheckBox.Checked = rule.UseBackColor;
backgroundButton.Enabled = rule.UseBackColor;
backgroundButton.BackColor = rule.UseBackColor ? rule.BackColor : SystemColors.Control;
boldCheckBox.Checked = rule.Bold;
loadingRule = false;
}
private void EditorValueChanged(object sender, EventArgs e)
{
if (loadingRule)
{
return;
}
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
if (rule == null)
{
return;
}
rule.Enabled = enabledCheckBox.Checked;
rule.Pattern = patternTextBox.Text;
rule.UseBackColor = backgroundCheckBox.Checked;
rule.Bold = boldCheckBox.Checked;
backgroundButton.Enabled = rule.UseBackColor;
int index = ruleList.SelectedIndex;
RefreshRuleList(index);
RefreshPreview();
}
private void ForegroundButton_Click(object sender, EventArgs e)
{
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
if (rule == null)
{
return;
}
using (var dialog = new ColorDialog { Color = rule.ForeColor, FullOpen = true })
{
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
rule.ForeColorArgb = dialog.Color.ToArgb();
foregroundButton.BackColor = dialog.Color;
RefreshPreview();
}
}
private void BackgroundButton_Click(object sender, EventArgs e)
{
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
if (rule == null)
{
return;
}
Color initialColor = rule.UseBackColor ? rule.BackColor : SystemColors.Window;
using (var dialog = new ColorDialog { Color = initialColor, FullOpen = true })
{
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
rule.BackColorArgb = dialog.Color.ToArgb();
rule.UseBackColor = true;
backgroundCheckBox.Checked = true;
backgroundButton.BackColor = dialog.Color;
RefreshPreview();
}
}
private void AddRule_Click(object sender, EventArgs e)
{
rules.Add(new LogColorRule { Pattern = "NEW SUBSTRING", ForeColorArgb = Color.MediumPurple.ToArgb() });
RefreshRuleList(rules.Count - 1);
patternTextBox.Focus();
patternTextBox.SelectAll();
}
private void DeleteRule_Click(object sender, EventArgs e)
{
int index = ruleList.SelectedIndex;
if (index < 0)
{
return;
}
rules.RemoveAt(index);
RefreshRuleList(Math.Min(index, rules.Count - 1));
RefreshPreview();
}
private void MoveUp_Click(object sender, EventArgs e)
{
MoveSelectedRule(-1);
}
private void MoveDown_Click(object sender, EventArgs e)
{
MoveSelectedRule(1);
}
private void MoveSelectedRule(int offset)
{
int index = ruleList.SelectedIndex;
int newIndex = index + offset;
if (index < 0 || newIndex < 0 || newIndex >= rules.Count)
{
return;
}
LogColorRule rule = rules[index];
rules.RemoveAt(index);
rules.Insert(newIndex, rule);
RefreshRuleList(newIndex);
RefreshPreview();
}
private void RestoreDefaults_Click(object sender, EventArgs e)
{
rules.Clear();
rules.AddRange(LogColorRule.CreateDefaults());
RefreshRuleList(0);
RefreshPreview();
}
private void RefreshPreview()
{
int selectionStart = previewBox.SelectionStart;
int selectionLength = previewBox.SelectionLength;
string text = previewBox.Text;
LogColorizer.Apply(previewBox, 0, text.Length, text, rules, previewBox.Font, previewBoldFont);
previewBox.Select(Math.Min(selectionStart, previewBox.TextLength), Math.Min(selectionLength, Math.Max(0, previewBox.TextLength - selectionStart)));
}
}
}
-24
View File
@@ -1,24 +0,0 @@
using AppDiagnostic.Logging;
using System;
namespace AppDiagnostic
{
public static class DiagnosticLogFormatter
{
public static string Format(DiagnosticLogEntry entry)
{
if (entry == null)
{
return string.Empty;
}
string level = (entry.Level ?? string.Empty).ToUpperInvariant().PadRight(5);
string application = string.IsNullOrWhiteSpace(entry.ApplicationName) ? "Unknown" : entry.ApplicationName;
string logger = entry.LoggerName ?? string.Empty;
string message = entry.Message ?? string.Empty;
string exception = string.IsNullOrWhiteSpace(entry.Exception) ? string.Empty : Environment.NewLine + entry.Exception;
return $"{entry.TimestampUtc.ToLocalTime():yyyy-MM-dd HH:mm:ss.fff}|{level}|{application}[{entry.ProcessId}]|{logger}|{message}{exception}";
}
}
}
-82
View File
@@ -1,82 +0,0 @@
using AppDiagnostic.Logging;
using System.Collections.Generic;
using System.Linq;
namespace AppDiagnostic
{
public sealed class DiagnosticLogStore
{
private const int MaximumLogCount = 10000;
private static readonly DiagnosticLogStore instance = new DiagnosticLogStore();
private readonly object syncRoot = new object();
private readonly List<DiagnosticLogEntry> entries = new List<DiagnosticLogEntry>();
private long totalReceived;
private long version;
private DiagnosticLogStore()
{
}
public static DiagnosticLogStore Instance => instance;
public long TotalReceived
{
get
{
lock (syncRoot)
{
return totalReceived;
}
}
}
public long Version
{
get
{
lock (syncRoot)
{
return version;
}
}
}
public void Add(DiagnosticLogEntry entry)
{
if (entry == null)
{
return;
}
lock (syncRoot)
{
entries.Add(entry);
totalReceived++;
version++;
int excessCount = entries.Count - MaximumLogCount;
if (excessCount > 0)
{
entries.RemoveRange(0, excessCount);
}
}
}
public List<DiagnosticLogEntry> GetSnapshot()
{
lock (syncRoot)
{
return entries.ToList();
}
}
public void Clear()
{
lock (syncRoot)
{
entries.Clear();
version++;
}
}
}
}
-164
View File
@@ -1,164 +0,0 @@
using AppDiagnostic.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
namespace AppDiagnostic
{
public sealed class DiagnosticPipeServer : IDisposable
{
private readonly Action<DiagnosticLogEntry> messageReceived;
private readonly CancellationTokenSource cancellation = new CancellationTokenSource();
private readonly object syncRoot = new object();
private readonly List<NamedPipeServerStream> connectedClients = new List<NamedPipeServerStream>();
private NamedPipeServerStream waitingServer;
private Task serverTask;
public DiagnosticPipeServer(Action<DiagnosticLogEntry> messageReceived)
{
this.messageReceived = messageReceived ?? throw new ArgumentNullException(nameof(messageReceived));
}
public int ConnectedClientCount
{
get
{
lock (syncRoot)
{
return connectedClients.Count;
}
}
}
public void Start()
{
if (serverTask != null)
{
return;
}
serverTask = Task.Run((Action)AcceptConnections);
}
private void AcceptConnections()
{
while (!cancellation.IsCancellationRequested)
{
NamedPipeServerStream server = null;
try
{
server = new NamedPipeServerStream(DiagnosticProtocol.DefaultPipeName, PipeDirection.In, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
lock (syncRoot)
{
waitingServer = server;
}
server.WaitForConnection();
lock (syncRoot)
{
waitingServer = null;
connectedClients.Add(server);
}
NamedPipeServerStream connectedServer = server;
server = null;
Task.Run(() => ReadClient(connectedServer));
}
catch (ObjectDisposedException)
{
if (!cancellation.IsCancellationRequested)
{
throw;
}
}
catch (IOException)
{
if (!cancellation.IsCancellationRequested)
{
Thread.Sleep(250);
}
}
finally
{
lock (syncRoot)
{
if (ReferenceEquals(waitingServer, server))
{
waitingServer = null;
}
}
server?.Dispose();
}
}
}
private void ReadClient(NamedPipeServerStream client)
{
try
{
while (client.IsConnected && !cancellation.IsCancellationRequested)
{
DiagnosticLogEntry entry = DiagnosticMessageSerializer.ReadFramed(client);
messageReceived(entry);
}
}
catch (EndOfStreamException)
{
// The producer application was closed.
}
catch (IOException)
{
// The producer application disconnected.
}
catch (ObjectDisposedException)
{
// The diagnostic application is shutting down.
}
finally
{
lock (syncRoot)
{
connectedClients.Remove(client);
}
client.Dispose();
}
}
public void Dispose()
{
cancellation.Cancel();
lock (syncRoot)
{
waitingServer?.Dispose();
waitingServer = null;
foreach (NamedPipeServerStream client in connectedClients.ToArray())
{
client.Dispose();
}
connectedClients.Clear();
}
try
{
serverTask?.Wait(2000);
}
catch (AggregateException)
{
// Cancellation during shutdown is expected.
}
cancellation.Dispose();
}
}
}
-65
View File
@@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace AppDiagnostic
{
[Serializable]
public sealed class LogColorRule
{
public bool Enabled { get; set; } = true;
public string Pattern { get; set; } = string.Empty;
public int ForeColorArgb { get; set; } = Color.Black.ToArgb();
public int BackColorArgb { get; set; }
public bool UseBackColor { get; set; }
public bool Bold { get; set; }
public Color ForeColor => Color.FromArgb(ForeColorArgb);
public Color BackColor => Color.FromArgb(BackColorArgb);
public LogColorRule Clone()
{
return new LogColorRule
{
Enabled = Enabled,
Pattern = Pattern,
ForeColorArgb = ForeColorArgb,
BackColorArgb = BackColorArgb,
UseBackColor = UseBackColor,
Bold = Bold
};
}
public override string ToString()
{
string state = Enabled ? string.Empty : "[disabled] ";
return state + Pattern;
}
public static List<LogColorRule> CreateDefaults()
{
return new List<LogColorRule>
{
Create("|TRACE|", Color.DimGray, false),
Create("|DEBUG|", Color.RoyalBlue, false),
Create("|INFO |", Color.ForestGreen, false),
Create("|WARN |", Color.DarkOrange, true),
Create("|ERROR|", Color.Crimson, true),
Create("|FATAL|", Color.White, true, Color.DarkRed)
};
}
private static LogColorRule Create(string pattern, Color foreground, bool bold, Color? background = null)
{
return new LogColorRule
{
Enabled = true,
Pattern = pattern,
ForeColorArgb = foreground.ToArgb(),
BackColorArgb = background?.ToArgb() ?? Color.Empty.ToArgb(),
UseBackColor = background.HasValue,
Bold = bold
};
}
}
}
-46
View File
@@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace AppDiagnostic
{
public static class LogColorRuleRepository
{
private static readonly string settingsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Sensus", "AppDiagnostic");
private static readonly string settingsPath = Path.Combine(settingsFolder, "color-rules.xml");
public static List<LogColorRule> Load()
{
try
{
if (!File.Exists(settingsPath))
{
return LogColorRule.CreateDefaults();
}
var serializer = new XmlSerializer(typeof(List<LogColorRule>));
using (FileStream stream = File.OpenRead(settingsPath))
{
var rules = serializer.Deserialize(stream) as List<LogColorRule>;
return rules == null || rules.Count == 0 ? LogColorRule.CreateDefaults() : rules;
}
}
catch
{
return LogColorRule.CreateDefaults();
}
}
public static void Save(IEnumerable<LogColorRule> rules)
{
Directory.CreateDirectory(settingsFolder);
var serializer = new XmlSerializer(typeof(List<LogColorRule>));
using (FileStream stream = File.Create(settingsPath))
{
serializer.Serialize(stream, new List<LogColorRule>(rules));
}
}
}
}
-47
View File
@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace AppDiagnostic
{
public static class LogColorizer
{
public static void Apply(RichTextBox target, int startIndex, int length, string text, IEnumerable<LogColorRule> rules, Font regularFont, Font boldFont)
{
if (target == null || length <= 0)
{
return;
}
target.Select(startIndex, length);
target.SelectionColor = target.ForeColor;
target.SelectionBackColor = target.BackColor;
target.SelectionFont = regularFont;
foreach (LogColorRule rule in rules)
{
if (rule == null || !rule.Enabled || string.IsNullOrEmpty(rule.Pattern))
{
continue;
}
int searchIndex = 0;
while (searchIndex < text.Length)
{
int matchIndex = text.IndexOf(rule.Pattern, searchIndex, StringComparison.OrdinalIgnoreCase);
if (matchIndex < 0)
{
break;
}
target.Select(startIndex + matchIndex, rule.Pattern.Length);
target.SelectionColor = rule.ForeColor;
target.SelectionBackColor = rule.UseBackColor ? rule.BackColor : target.BackColor;
target.SelectionFont = rule.Bold ? boldFont : regularFont;
searchIndex = matchIndex + rule.Pattern.Length;
}
}
}
}
}
+250 -281
View File
@@ -1,309 +1,278 @@
using System.Windows.Forms;
namespace AppDiagnostic
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.ToolStrip mainToolStrip;
private System.Windows.Forms.ToolStripButton pauseButton;
private System.Windows.Forms.ToolStripButton refreshButton;
private System.Windows.Forms.ToolStripButton clearButton;
private System.Windows.Forms.ToolStripButton saveButton;
private System.Windows.Forms.ToolStripButton copyButton;
private System.Windows.Forms.ToolStripButton copyAllButton;
private System.Windows.Forms.ToolStripButton wrapButton;
private System.Windows.Forms.ToolStripButton autoScrollButton;
private System.Windows.Forms.ToolStripButton alwaysOnTopButton;
private System.Windows.Forms.ToolStripButton colorRulesButton;
private System.Windows.Forms.ToolStripButton closeButton;
private System.Windows.Forms.TableLayoutPanel filterPanel;
private System.Windows.Forms.Label filterLabel;
private System.Windows.Forms.TextBox filterTextBox;
private System.Windows.Forms.Label levelLabel;
private System.Windows.Forms.ComboBox levelComboBox;
private System.Windows.Forms.Label applicationLabel;
private System.Windows.Forms.ComboBox applicationComboBox;
private System.Windows.Forms.Button applyFilterButton;
private System.Windows.Forms.Button clearFilterButton;
private System.Windows.Forms.RichTextBox logView;
private System.Windows.Forms.StatusStrip statusStrip;
private System.Windows.Forms.ToolStripStatusLabel receiverStatusLabel;
private System.Windows.Forms.ToolStripStatusLabel statusSpacer;
private System.Windows.Forms.ToolStripStatusLabel countStatusLabel;
private System.Windows.Forms.ContextMenuStrip logContextMenu;
private System.Windows.Forms.ToolStripMenuItem copySelectionMenuItem;
private System.Windows.Forms.ToolStripMenuItem copyLineMenuItem;
private System.Windows.Forms.ToolStripMenuItem copyAllMenuItem;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
//private BufferedListView memoListView;
private System.Windows.Forms.ColumnHeader columnHeaderTime;
private System.Windows.Forms.ColumnHeader columnHeaderMessage;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.mainToolStrip = new System.Windows.Forms.ToolStrip();
this.pauseButton = new System.Windows.Forms.ToolStripButton();
this.refreshButton = new System.Windows.Forms.ToolStripButton();
this.clearButton = new System.Windows.Forms.ToolStripButton();
this.saveButton = new System.Windows.Forms.ToolStripButton();
this.copyButton = new System.Windows.Forms.ToolStripButton();
this.copyAllButton = new System.Windows.Forms.ToolStripButton();
this.wrapButton = new System.Windows.Forms.ToolStripButton();
this.autoScrollButton = new System.Windows.Forms.ToolStripButton();
this.alwaysOnTopButton = new System.Windows.Forms.ToolStripButton();
this.colorRulesButton = new System.Windows.Forms.ToolStripButton();
this.closeButton = new System.Windows.Forms.ToolStripButton();
this.filterPanel = new System.Windows.Forms.TableLayoutPanel();
this.filterLabel = new System.Windows.Forms.Label();
this.columnHeaderTime = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeaderMessage = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.panel1 = new System.Windows.Forms.Panel();
this.refreshMemoButton = new System.Windows.Forms.Button();
this.button6 = new System.Windows.Forms.Button();
this.button7 = new System.Windows.Forms.Button();
this.filterManagementPanel = new System.Windows.Forms.Panel();
this.button5 = new System.Windows.Forms.Button();
this.filterButton = new System.Windows.Forms.Button();
this.filterTextBox = new System.Windows.Forms.TextBox();
this.levelLabel = new System.Windows.Forms.Label();
this.levelComboBox = new System.Windows.Forms.ComboBox();
this.applicationLabel = new System.Windows.Forms.Label();
this.applicationComboBox = new System.Windows.Forms.ComboBox();
this.applyFilterButton = new System.Windows.Forms.Button();
this.clearFilterButton = new System.Windows.Forms.Button();
this.logContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
this.copySelectionMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.copyLineMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.copyAllMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.logView = new System.Windows.Forms.RichTextBox();
this.statusStrip = new System.Windows.Forms.StatusStrip();
this.receiverStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.statusSpacer = new System.Windows.Forms.ToolStripStatusLabel();
this.countStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.mainToolStrip.SuspendLayout();
this.filterPanel.SuspendLayout();
this.logContextMenu.SuspendLayout();
this.statusStrip.SuspendLayout();
this.label2 = new System.Windows.Forms.Label();
this.wndControlPanel = new System.Windows.Forms.Panel();
this.button2 = new System.Windows.Forms.Button();
this.flowControlsPanel = new System.Windows.Forms.Panel();
this.runButton = new System.Windows.Forms.Button();
this.stopButton = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.memoListView = new BufferedListView();
this.panel1.SuspendLayout();
this.filterManagementPanel.SuspendLayout();
this.wndControlPanel.SuspendLayout();
this.flowControlsPanel.SuspendLayout();
this.SuspendLayout();
//
// mainToolStrip
//
this.mainToolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.mainToolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.pauseButton,
this.refreshButton,
this.clearButton,
this.saveButton,
new System.Windows.Forms.ToolStripSeparator(),
this.copyButton,
this.copyAllButton,
new System.Windows.Forms.ToolStripSeparator(),
this.wrapButton,
this.autoScrollButton,
this.alwaysOnTopButton,
this.colorRulesButton,
this.closeButton});
this.mainToolStrip.Location = new System.Drawing.Point(0, 0);
this.mainToolStrip.Name = "mainToolStrip";
this.mainToolStrip.Padding = new System.Windows.Forms.Padding(6, 3, 6, 3);
this.mainToolStrip.Size = new System.Drawing.Size(1184, 31);
this.mainToolStrip.TabIndex = 0;
//
// pauseButton
//
this.pauseButton.CheckOnClick = true;
this.pauseButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.pauseButton.Text = "Pause";
this.pauseButton.ToolTipText = "Pause screen updates while continuing to receive logs";
this.pauseButton.Click += new System.EventHandler(this.pauseButton_Click);
//
// refreshButton
//
this.refreshButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.refreshButton.Text = "Refresh";
this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
//
// clearButton
//
this.clearButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.clearButton.Text = "Clear";
this.clearButton.Click += new System.EventHandler(this.clearButton_Click);
//
// saveButton
//
this.saveButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.saveButton.Text = "Save...";
this.saveButton.Click += new System.EventHandler(this.saveButton_Click);
//
// copyButton
//
this.copyButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.copyButton.Text = "Copy";
this.copyButton.ToolTipText = "Copy selected text or the current log line";
this.copyButton.Click += new System.EventHandler(this.copyButton_Click);
//
// copyAllButton
//
this.copyAllButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.copyAllButton.Text = "Copy all";
this.copyAllButton.Click += new System.EventHandler(this.copyAllButton_Click);
//
// wrapButton
//
this.wrapButton.Checked = true;
this.wrapButton.CheckOnClick = true;
this.wrapButton.CheckState = System.Windows.Forms.CheckState.Checked;
this.wrapButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.wrapButton.Text = "Wrap lines";
this.wrapButton.Click += new System.EventHandler(this.wrapButton_Click);
//
// autoScrollButton
//
this.autoScrollButton.Checked = true;
this.autoScrollButton.CheckOnClick = true;
this.autoScrollButton.CheckState = System.Windows.Forms.CheckState.Checked;
this.autoScrollButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.autoScrollButton.Text = "Auto-scroll";
//
// alwaysOnTopButton
//
this.alwaysOnTopButton.CheckOnClick = true;
this.alwaysOnTopButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.alwaysOnTopButton.Text = "Always on top";
this.alwaysOnTopButton.Click += new System.EventHandler(this.alwaysOnTopButton_Click);
//
// colorRulesButton
//
this.colorRulesButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.colorRulesButton.Text = "Color rules...";
this.colorRulesButton.Click += new System.EventHandler(this.colorRulesButton_Click);
//
// closeButton
//
this.closeButton.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
this.closeButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.closeButton.Text = "Close";
this.closeButton.Click += new System.EventHandler(this.closeButton_Click);
//
// filterPanel
//
this.filterPanel.ColumnCount = 8;
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 42F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 42F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 105F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 72F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 180F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 66F));
this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 66F));
this.filterPanel.Controls.Add(this.filterLabel, 0, 0);
this.filterPanel.Controls.Add(this.filterTextBox, 1, 0);
this.filterPanel.Controls.Add(this.levelLabel, 2, 0);
this.filterPanel.Controls.Add(this.levelComboBox, 3, 0);
this.filterPanel.Controls.Add(this.applicationLabel, 4, 0);
this.filterPanel.Controls.Add(this.applicationComboBox, 5, 0);
this.filterPanel.Controls.Add(this.applyFilterButton, 6, 0);
this.filterPanel.Controls.Add(this.clearFilterButton, 7, 0);
this.filterPanel.Dock = System.Windows.Forms.DockStyle.Top;
this.filterPanel.Location = new System.Drawing.Point(0, 31);
this.filterPanel.Name = "filterPanel";
this.filterPanel.Padding = new System.Windows.Forms.Padding(6, 5, 6, 5);
this.filterPanel.RowCount = 1;
this.filterPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.filterPanel.Size = new System.Drawing.Size(1184, 38);
this.filterPanel.TabIndex = 1;
//
// filter controls
//
this.filterLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.filterLabel.AutoSize = true;
this.filterLabel.Text = "Filter";
this.filterTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.filterTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.filterTextBox_KeyDown);
this.levelLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.levelLabel.AutoSize = true;
this.levelLabel.Text = "Level";
this.levelComboBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.levelComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.levelComboBox.SelectedIndexChanged += new System.EventHandler(this.filterValueChanged);
this.applicationLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.applicationLabel.AutoSize = true;
this.applicationLabel.Text = "Application";
this.applicationComboBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.applicationComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.applicationComboBox.SelectedIndexChanged += new System.EventHandler(this.filterValueChanged);
this.applyFilterButton.Dock = System.Windows.Forms.DockStyle.Fill;
this.applyFilterButton.Text = "Apply";
this.applyFilterButton.Click += new System.EventHandler(this.applyFilterButton_Click);
this.clearFilterButton.Dock = System.Windows.Forms.DockStyle.Fill;
this.clearFilterButton.Text = "Reset";
this.clearFilterButton.Click += new System.EventHandler(this.clearFilterButton_Click);
//
// logContextMenu
//
this.logContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.copySelectionMenuItem,
this.copyLineMenuItem,
this.copyAllMenuItem});
this.copySelectionMenuItem.Text = "Copy selection";
this.copySelectionMenuItem.Click += new System.EventHandler(this.copySelectionMenuItem_Click);
this.copyLineMenuItem.Text = "Copy current line";
this.copyLineMenuItem.Click += new System.EventHandler(this.copyLineMenuItem_Click);
this.copyAllMenuItem.Text = "Copy all visible logs";
this.copyAllMenuItem.Click += new System.EventHandler(this.copyAllButton_Click);
//
// logView
//
this.logView.BackColor = System.Drawing.SystemColors.Window;
this.logView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.logView.ContextMenuStrip = this.logContextMenu;
this.logView.DetectUrls = false;
this.logView.Dock = System.Windows.Forms.DockStyle.Fill;
this.logView.Font = new System.Drawing.Font("Consolas", 9F);
this.logView.HideSelection = false;
this.logView.Location = new System.Drawing.Point(0, 69);
this.logView.Name = "logView";
this.logView.ReadOnly = true;
this.logView.Size = new System.Drawing.Size(1184, 554);
this.logView.TabIndex = 2;
this.logView.Text = "";
this.logView.WordWrap = true;
//
// statusStrip
//
this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.receiverStatusLabel,
this.statusSpacer,
this.countStatusLabel});
this.statusStrip.Location = new System.Drawing.Point(0, 623);
this.statusStrip.Name = "statusStrip";
this.statusStrip.Size = new System.Drawing.Size(1184, 22);
this.statusStrip.TabIndex = 3;
this.receiverStatusLabel.Text = "Listening";
this.statusSpacer.Spring = true;
this.countStatusLabel.Text = "Received: 0 | Visible: 0";
//
//
// columnHeaderTime
//
this.columnHeaderTime.Text = "Time";
this.columnHeaderTime.Width = 150;
//
// columnHeaderMessage
//
this.columnHeaderMessage.Text = "Message";
this.columnHeaderMessage.Width = 350;
//
// panel1
//
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.panel1.Controls.Add(this.refreshMemoButton);
this.panel1.Controls.Add(this.button6);
this.panel1.Controls.Add(this.button7);
this.panel1.Location = new System.Drawing.Point(189, 356);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(283, 26);
this.panel1.TabIndex = 16;
//
// refreshMemoButton
//
this.refreshMemoButton.Location = new System.Drawing.Point(108, 0);
this.refreshMemoButton.Name = "refreshMemoButton";
this.refreshMemoButton.Size = new System.Drawing.Size(84, 26);
this.refreshMemoButton.TabIndex = 17;
this.refreshMemoButton.Text = "Refresh memo";
this.refreshMemoButton.UseVisualStyleBackColor = true;
this.refreshMemoButton.Click += new System.EventHandler(this.refreshMemoButton_Click);
//
// button6
//
this.button6.Location = new System.Drawing.Point(0, 0);
this.button6.Name = "button6";
this.button6.Size = new System.Drawing.Size(102, 26);
this.button6.TabIndex = 4;
this.button6.Text = "Clean diag cache";
this.button6.UseVisualStyleBackColor = true;
this.button6.Click += new System.EventHandler(this.button6_Click);
//
// button7
//
this.button7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.button7.Location = new System.Drawing.Point(198, 0);
this.button7.Name = "button7";
this.button7.Size = new System.Drawing.Size(72, 26);
this.button7.TabIndex = 5;
this.button7.Text = "Save logs";
this.button7.UseVisualStyleBackColor = true;
this.button7.Click += new System.EventHandler(this.button7_Click);
//
// filterManagementPanel
//
this.filterManagementPanel.Controls.Add(this.button5);
this.filterManagementPanel.Controls.Add(this.filterButton);
this.filterManagementPanel.Controls.Add(this.filterTextBox);
this.filterManagementPanel.Controls.Add(this.label2);
this.filterManagementPanel.Location = new System.Drawing.Point(127, 6);
this.filterManagementPanel.Name = "filterManagementPanel";
this.filterManagementPanel.Size = new System.Drawing.Size(393, 26);
this.filterManagementPanel.TabIndex = 15;
//
// button5
//
this.button5.Enabled = false;
this.button5.Location = new System.Drawing.Point(294, 4);
this.button5.Name = "button5";
this.button5.Size = new System.Drawing.Size(81, 20);
this.button5.TabIndex = 11;
this.button5.Text = "Add new filter";
this.button5.UseVisualStyleBackColor = true;
//
// filterButton
//
this.filterButton.Location = new System.Drawing.Point(225, 4);
this.filterButton.Name = "filterButton";
this.filterButton.Size = new System.Drawing.Size(64, 20);
this.filterButton.TabIndex = 10;
this.filterButton.Text = "Set filter";
this.filterButton.UseVisualStyleBackColor = true;
this.filterButton.Click += new System.EventHandler(this.filterButton_Click_1);
//
// filterTextBox
//
this.filterTextBox.Location = new System.Drawing.Point(33, 4);
this.filterTextBox.Name = "filterTextBox";
this.filterTextBox.Size = new System.Drawing.Size(187, 20);
this.filterTextBox.TabIndex = 7;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(3, 8);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(29, 13);
this.label2.TabIndex = 6;
this.label2.Text = "Filter";
//
// wndControlPanel
//
this.wndControlPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.wndControlPanel.Controls.Add(this.button2);
this.wndControlPanel.Location = new System.Drawing.Point(831, 356);
this.wndControlPanel.Name = "wndControlPanel";
this.wndControlPanel.Size = new System.Drawing.Size(70, 26);
this.wndControlPanel.TabIndex = 14;
//
// button2
//
this.button2.Location = new System.Drawing.Point(3, 0);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(64, 26);
this.button2.TabIndex = 2;
this.button2.Text = "Close";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// flowControlsPanel
//
this.flowControlsPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.flowControlsPanel.Controls.Add(this.runButton);
this.flowControlsPanel.Controls.Add(this.stopButton);
this.flowControlsPanel.Location = new System.Drawing.Point(12, 356);
this.flowControlsPanel.Name = "flowControlsPanel";
this.flowControlsPanel.Size = new System.Drawing.Size(133, 26);
this.flowControlsPanel.TabIndex = 13;
//
// runButton
//
this.runButton.Enabled = false;
this.runButton.Location = new System.Drawing.Point(0, 0);
this.runButton.Name = "runButton";
this.runButton.Size = new System.Drawing.Size(64, 26);
this.runButton.TabIndex = 4;
this.runButton.Text = "Run";
this.runButton.UseVisualStyleBackColor = true;
this.runButton.Click += new System.EventHandler(this.runButton_Click);
//
// stopButton
//
this.stopButton.Location = new System.Drawing.Point(69, 0);
this.stopButton.Name = "stopButton";
this.stopButton.Size = new System.Drawing.Size(64, 26);
this.stopButton.TabIndex = 5;
this.stopButton.Text = "Stop";
this.stopButton.UseVisualStyleBackColor = true;
this.stopButton.Click += new System.EventHandler(this.stopButton_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 19);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(76, 13);
this.label1.TabIndex = 12;
this.label1.Text = "Flow of events";
//
// memoListView
//
this.memoListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.memoListView.FullRowSelect = true;
this.memoListView.GridLines = true;
this.memoListView.HideSelection = false;
this.memoListView.Location = new System.Drawing.Point(12, 38);
this.memoListView.Name = "memoListView";
this.memoListView.Size = new System.Drawing.Size(889, 312);
this.memoListView.TabIndex = 0;
this.memoListView.UseCompatibleStateImageBehavior = false;
this.memoListView.View = System.Windows.Forms.View.Details;
this.memoListView.DoubleClick += new System.EventHandler(this.MemoListView_DoubleClick);
//
// MainForm
//
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1184, 645);
this.Controls.Add(this.logView);
this.Controls.Add(this.filterPanel);
this.Controls.Add(this.mainToolStrip);
this.Controls.Add(this.statusStrip);
this.KeyPreview = true;
this.MinimumSize = new System.Drawing.Size(860, 480);
this.ClientSize = new System.Drawing.Size(913, 388);
this.Controls.Add(this.memoListView);
this.Controls.Add(this.panel1);
this.Controls.Add(this.filterManagementPanel);
this.Controls.Add(this.wndControlPanel);
this.Controls.Add(this.flowControlsPanel);
this.Controls.Add(this.label1);
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Application live diagnostic";
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.MainForm_KeyDown);
this.mainToolStrip.ResumeLayout(false);
this.mainToolStrip.PerformLayout();
this.filterPanel.ResumeLayout(false);
this.filterPanel.PerformLayout();
this.logContextMenu.ResumeLayout(false);
this.statusStrip.ResumeLayout(false);
this.statusStrip.PerformLayout();
this.panel1.ResumeLayout(false);
this.filterManagementPanel.ResumeLayout(false);
this.filterManagementPanel.PerformLayout();
this.wndControlPanel.ResumeLayout(false);
this.flowControlsPanel.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button button6;
private System.Windows.Forms.Button button7;
private System.Windows.Forms.Panel filterManagementPanel;
private System.Windows.Forms.Button button5;
private System.Windows.Forms.Button filterButton;
private System.Windows.Forms.TextBox filterTextBox;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Panel wndControlPanel;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Panel flowControlsPanel;
private System.Windows.Forms.Button runButton;
private System.Windows.Forms.Button stopButton;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button refreshMemoButton;
private BufferedListView memoListView;
}
}
+220 -398
View File
@@ -1,445 +1,267 @@
using AppDiagnostic.Logging;
using log4net.Config;
using log4net;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using log4net.Appender;
using SharedComponents;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using System.IO; // Pre File.WriteAllLines a prácu so súbormi
using System.Linq;
namespace AppDiagnostic
{
public partial class MainForm : Form
{
private readonly Func<int> connectedClientCountProvider;
private readonly Func<bool> activationRequestProvider;
private readonly Timer refreshTimer;
private readonly Font regularLogFont;
private readonly Font boldLogFont;
private List<LogColorRule> colorRules;
private long lastRenderedVersion = -1;
private int observedEntryCount;
private int visibleEntryCount;
private bool loadingFilters;
private bool stopUpdate = false; // Premenná na zastavenie aktualizácie
private int lastDisplayedLogIndex = 0;
private bool autoScrollEnabled = true; // Automatický posun, predvolene povolený
private MainForm appDiagnosticForm;
private Timer refreshTimer;
private LogAdapter _logAdapter; // Pre LogAdapter
private LogFilter _logFilter; // LogFilter objekt pre filtráciu
public MainForm() : this(null, null)
public MainForm()
{
this.Icon = Properties.Resources.TBF_icon;
InitializeComponent();
// Inicializujeme LogAdapter a priradíme ho k memoListView
_logAdapter = new LogAdapter(memoListView);
RefreshLogView();
// Pripojenie na udalosť pri pridaní nového logu
// nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem
//LiveLogCache.Instance.LogAdded += OnLogAdded;
// Vytvoríme LogFilter objekt
//_logFilter = new LogFilter(memoListView);
}
public MainForm(Func<int> connectedClientCountProvider, Func<bool> activationRequestProvider)
private void OnLogAdded(string log)
{
this.connectedClientCountProvider = connectedClientCountProvider;
this.activationRequestProvider = activationRequestProvider;
InitializeComponent();
Icon = Properties.Resources.TBF_icon;
try
{
RefreshLogView();
}
catch (Exception ex)
{
MessageBox.Show($"Error in OnLogAdded: {ex.Message}");
throw;
}
}
regularLogFont = new Font(logView.Font, FontStyle.Regular);
boldLogFont = new Font(logView.Font, FontStyle.Bold);
colorRules = LogColorRuleRepository.Load();
private void RefreshLogView()
{
// Skontrolujte, či voláme z iného vlákna
if (InvokeRequired)
{
Invoke((Action)RefreshLogView);
return;
}
InitializeFilters();
memoListView.BeginUpdate(); // Zabraňuje vizuálnym aktualizáciám počas vykresľovania
refreshTimer = new Timer { Interval = 250 };
refreshTimer.Tick += refreshTimer_Tick;
refreshTimer.Start();
try
{
// Skontrolujte, či je užívateľ na poslednom viditeľnom zázname
if (memoListView.Items.Count > 0)
{
var lastVisibleIndex = memoListView.TopItem.Index + memoListView.ClientRectangle.Height / memoListView.Items[0].Bounds.Height;
autoScrollEnabled = lastVisibleIndex >= memoListView.Items.Count - 1;
}
RefreshView(true);
// Získajte nové logy od posledného indexu
var logs = LiveLogCache.Instance.GetLogs(lastDisplayedLogIndex, LiveLogCache.Instance.Logs.Count - lastDisplayedLogIndex);
// Pridajte len nové logy
foreach (var log in logs)
{
memoListView.Items.Add(new ListViewItem(log));
}
// Aktualizujte posledný zobrazený index
lastDisplayedLogIndex = LiveLogCache.Instance.Logs.Count;
// Ak je autoScrollEnabled, posuňte sa na posledný záznam
if (autoScrollEnabled && memoListView.Items.Count > 0)
{
memoListView.EnsureVisible(memoListView.Items.Count - 1);
}
}
finally
{
memoListView.EndUpdate(); // Umožní vizuálne aktualizácie
}
}
// Táto metóda sa spustí pri scrollovaní v memoListView (tzn. ak sa používateľ dostane na spodok listu)
private void memoListView_Scroll(object sender, EventArgs e)
{
// Skontrolujte, či používateľ dosiahol spodok ListView
if (memoListView.Items.Count > 0 &&
memoListView.Items[memoListView.Items.Count - 1].Bounds.Bottom <= memoListView.ClientSize.Height)
{
// Ak áno, načítame ďalšie logy
RefreshLogView();
}
}
private void button6_Click(object sender, EventArgs e)
{
//LiveLogCache.Instance.AddLog("Nový log z hlavnej aplikácie");
LiveLogCache.Instance.ClearLogs();
ClearListView();
}
private void runButton_Click(object sender, EventArgs e)
{
if (stopUpdate) // Ak je časovač zastavený, spustíme ho
{
stopUpdate = false; // Zastav aktualizáciu
runButton.Enabled = false; // Zakážeme tlačidlo Start počas behu
stopButton.Enabled = true; // Povolenie tlačidla Stop
refreshTimer.Start();
}
}
private void stopButton_Click(object sender, EventArgs e)
{
if (!stopUpdate) // Ak časovač beží, môžeme ho zastaviť
{
stopUpdate = true; // Spusti aktualizáciu
runButton.Enabled = true; // Povolenie tlačidla Start
stopButton.Enabled = false; // Zakážeme tlačidlo Stop
refreshTimer.Stop();
}
}
private void ClearListView()
{
if (InvokeRequired)
{
Invoke(new Action(ClearListView));
return;
}
memoListView.Items.Clear();
}
private void refreshMemoButton_Click(object sender, EventArgs e)
{
}
private void button7_Click(object sender, EventArgs e)
{
SaveLogsToFile();
//LiveLogCache.Instance.AddLog("Toto je nový log.");
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
refreshTimer.Stop();
refreshTimer.Dispose();
regularLogFont.Dispose();
boldLogFont.Dispose();
// Odpojenie udalosti, keď sa okno zatvára, inak môže po opätovnom spustení okna a pridaní nového itemu do listu zhhodiť program
// nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem
//LiveLogCache.Instance.LogAdded -= OnLogAdded;
_logAdapter = null;
base.OnFormClosing(e);
}
private void InitializeFilters()
private void filterButton_Click_1(object sender, EventArgs e)
{
loadingFilters = true;
levelComboBox.Items.AddRange(new object[] { "All levels", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" });
levelComboBox.SelectedIndex = 0;
applicationComboBox.Items.Add("All applications");
applicationComboBox.SelectedIndex = 0;
loadingFilters = false;
// Aplikujeme filter na základe textu z filterTextBox
string filterText = filterTextBox.Text;
//_logFilter.ApplyFilter(filterText); // Aplikovanie filtra
_logAdapter.ApplyFilter(filterText);
}
private void refreshTimer_Tick(object sender, EventArgs e)
private void SubForm_FormClosing(object sender, FormClosingEventArgs e)
{
ProcessActivationRequest();
RefreshView(false);
}
private void ProcessActivationRequest()
{
if (activationRequestProvider == null || !activationRequestProvider())
{
return;
}
if (WindowState == FormWindowState.Minimized)
{
WindowState = FormWindowState.Normal;
}
Show();
BringToFront();
Activate();
}
private void RefreshView(bool forceFullRefresh)
{
List<DiagnosticLogEntry> snapshot = DiagnosticLogStore.Instance.GetSnapshot();
UpdateApplicationFilter(snapshot);
if (pauseButton.Checked)
{
UpdateStatus(snapshot.Count);
return;
}
long currentVersion = DiagnosticLogStore.Instance.Version;
if (!forceFullRefresh && currentVersion == lastRenderedVersion)
{
UpdateStatus(snapshot.Count);
return;
}
bool canAppend = !forceFullRefresh && snapshot.Count > observedEntryCount;
if (canAppend)
{
AppendEntries(snapshot.Skip(observedEntryCount));
}
else
{
RebuildView(snapshot);
}
observedEntryCount = snapshot.Count;
lastRenderedVersion = currentVersion;
UpdateStatus(snapshot.Count);
}
private void RebuildView(IEnumerable<DiagnosticLogEntry> entries)
{
logView.SuspendLayout();
logView.Clear();
visibleEntryCount = 0;
AppendEntries(entries, false);
logView.ResumeLayout();
if (autoScrollButton.Checked)
{
ScrollToEnd();
}
}
private void AppendEntries(IEnumerable<DiagnosticLogEntry> entries, bool scrollAfterAppend = true)
{
int originalSelectionStart = logView.SelectionStart;
int originalSelectionLength = logView.SelectionLength;
foreach (DiagnosticLogEntry entry in entries)
{
if (!MatchesFilter(entry))
{
continue;
}
AppendEntry(entry);
visibleEntryCount++;
}
if (scrollAfterAppend && autoScrollButton.Checked)
{
ScrollToEnd();
}
else if (!autoScrollButton.Checked)
{
int safeStart = Math.Min(originalSelectionStart, logView.TextLength);
int safeLength = Math.Min(originalSelectionLength, logView.TextLength - safeStart);
logView.Select(safeStart, safeLength);
}
}
private void AppendEntry(DiagnosticLogEntry entry)
{
string text = DiagnosticLogFormatter.Format(entry);
if (logView.TextLength > 0)
{
logView.AppendText(Environment.NewLine);
}
int startIndex = logView.TextLength;
logView.AppendText(text);
LogColorizer.Apply(logView, startIndex, text.Length, text, colorRules, regularLogFont, boldLogFont);
logView.Select(logView.TextLength, 0);
}
private bool MatchesFilter(DiagnosticLogEntry entry)
{
string selectedLevel = levelComboBox.SelectedItem as string;
if (!string.IsNullOrEmpty(selectedLevel) && selectedLevel != "All levels" && !string.Equals(entry.Level, selectedLevel, StringComparison.OrdinalIgnoreCase))
{
return false;
}
string selectedApplication = applicationComboBox.SelectedItem as string;
if (!string.IsNullOrEmpty(selectedApplication) && selectedApplication != "All applications" && !string.Equals(entry.ApplicationName, selectedApplication, StringComparison.OrdinalIgnoreCase))
{
return false;
}
string filter = filterTextBox.Text.Trim();
return filter.Length == 0 || DiagnosticLogFormatter.Format(entry).IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void UpdateApplicationFilter(IEnumerable<DiagnosticLogEntry> entries)
{
string selectedApplication = applicationComboBox.SelectedItem as string ?? "All applications";
List<string> applications = entries.Select(entry => entry.ApplicationName).Where(name => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(name => name).ToList();
List<string> currentApplications = applicationComboBox.Items.Cast<string>().Skip(1).ToList();
if (applications.SequenceEqual(currentApplications, StringComparer.OrdinalIgnoreCase))
{
return;
}
loadingFilters = true;
applicationComboBox.BeginUpdate();
applicationComboBox.Items.Clear();
applicationComboBox.Items.Add("All applications");
applicationComboBox.Items.AddRange(applications.Cast<object>().ToArray());
applicationComboBox.SelectedItem = applicationComboBox.Items.Cast<string>().FirstOrDefault(item => string.Equals(item, selectedApplication, StringComparison.OrdinalIgnoreCase)) ?? "All applications";
applicationComboBox.EndUpdate();
loadingFilters = false;
}
private void UpdateStatus(int storedCount)
{
int clients = connectedClientCountProvider?.Invoke() ?? 0;
receiverStatusLabel.Text = pauseButton.Checked ? $"Paused | Connected sources: {clients}" : $"Listening | Connected sources: {clients}";
countStatusLabel.Text = $"Received: {DiagnosticLogStore.Instance.TotalReceived:N0} | Stored: {storedCount:N0} | Visible: {visibleEntryCount:N0}";
}
private void ScrollToEnd()
{
logView.Select(logView.TextLength, 0);
logView.ScrollToCaret();
}
private void pauseButton_Click(object sender, EventArgs e)
{
pauseButton.Text = pauseButton.Checked ? "Resume" : "Pause";
if (!pauseButton.Checked)
{
RefreshView(true);
}
else
{
UpdateStatus(DiagnosticLogStore.Instance.GetSnapshot().Count);
}
}
private void refreshButton_Click(object sender, EventArgs e)
{
RefreshView(true);
}
private void clearButton_Click(object sender, EventArgs e)
{
DiagnosticLogStore.Instance.Clear();
observedEntryCount = 0;
visibleEntryCount = 0;
RefreshView(true);
}
private void saveButton_Click(object sender, EventArgs e)
{
using (var dialog = new SaveFileDialog())
{
dialog.Filter = "Text files (*.txt)|*.txt|Log files (*.log)|*.log|All files (*.*)|*.*";
dialog.Title = "Save visible diagnostic logs";
dialog.DefaultExt = "log";
dialog.FileName = $"AppDiagnostic_{DateTime.Now:yyyyMMdd_HHmmss}.log";
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
try
{
File.WriteAllText(dialog.FileName, logView.Text);
}
catch (Exception exception)
{
MessageBox.Show(this, $"Unable to save diagnostic logs: {exception.Message}", "Save error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void copyButton_Click(object sender, EventArgs e)
{
CopySelectionOrCurrentLine();
}
private void copySelectionMenuItem_Click(object sender, EventArgs e)
{
if (logView.SelectionLength > 0)
{
CopyText(logView.SelectedText);
}
}
private void copyLineMenuItem_Click(object sender, EventArgs e)
{
CopyCurrentLine();
}
private void copyAllButton_Click(object sender, EventArgs e)
{
CopyText(logView.Text);
}
private void CopySelectionOrCurrentLine()
{
if (logView.SelectionLength > 0)
{
CopyText(logView.SelectedText);
}
else
{
CopyCurrentLine();
}
}
private void CopyCurrentLine()
{
if (logView.Lines.Length == 0)
{
return;
}
int lineIndex = logView.GetLineFromCharIndex(logView.SelectionStart);
if (lineIndex >= 0 && lineIndex < logView.Lines.Length)
{
CopyText(logView.Lines[lineIndex]);
}
}
private void CopyText(string text)
{
if (string.IsNullOrEmpty(text))
{
return;
}
try
{
Clipboard.SetText(text);
// Vaša logika pri uzatváraní formy
// this.Hide(); // Podforma sa len skryje
}
catch (Exception exception)
catch (Exception ex)
{
MessageBox.Show(this, $"Unable to copy text: {exception.Message}", "Clipboard error", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Ošetrenie výnimky
MessageBox.Show("Chyba pri zatváraní pod-aplikácie: " + ex.Message);
}
}
private void wrapButton_Click(object sender, EventArgs e)
private void SaveLogsToFile()
{
logView.WordWrap = wrapButton.Checked;
}
private void alwaysOnTopButton_Click(object sender, EventArgs e)
{
TopMost = alwaysOnTopButton.Checked;
}
private void colorRulesButton_Click(object sender, EventArgs e)
{
using (var editor = new ColorRuleEditorForm(colorRules))
using (SaveFileDialog saveFileDialog = new SaveFileDialog())
{
if (editor.ShowDialog(this) != DialogResult.OK)
saveFileDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*";
saveFileDialog.Title = "Save TBF live logs";
saveFileDialog.DefaultExt = "txt";
saveFileDialog.FileName = "TBFLiveLogs.txt";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
return;
try
{
// Načítanie všetkých logov z LiveLogCache
var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.GetCount());
// Uloženie logov do vybraného súboru
File.WriteAllLines(saveFileDialog.FileName, logs);
MessageBox.Show("Logs saved successfully.", "Save TBF live logs", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"An error occurred while saving TBF live logs: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
colorRules = editor.Rules;
private void button2_Click(object sender, EventArgs e)
{
this.Close();
//this.Hide(); // Podforma sa len skryje
}
try
private void MemoListView_DoubleClick(object sender, EventArgs e)
{
if (memoListView.SelectedItems.Count > 0)
{
var selectedItem = memoListView.SelectedItems[0];
int selectedIndex = memoListView.Items.IndexOf(selectedItem);
if (selectedIndex >= 0)
{
LogColorRuleRepository.Save(colorRules);
// Zrušenie filtra
_logAdapter._filterText = string.Empty;
// Načítanie logov od indexu vybraného riadku
var logs = LiveLogCache.Instance.GetLogs(selectedIndex, LiveLogCache.Instance.GetCount() - selectedIndex);
// Obnovenie ListView s novými údajmi
memoListView.BeginUpdate();
memoListView.Items.Clear();
foreach (var log in logs)
{
var item = new ListViewItem(log)
{
BackColor = memoListView.Items.Count % 2 == 0 ? Color.White : Color.FromArgb(240, 240, 240)
};
memoListView.Items.Add(item);
}
memoListView.EndUpdate();
}
catch (Exception exception)
{
MessageBox.Show(this, $"Unable to save color rules: {exception.Message}", "Settings error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
RefreshView(true);
}
}
private void applyFilterButton_Click(object sender, EventArgs e)
{
RefreshView(true);
}
private void clearFilterButton_Click(object sender, EventArgs e)
{
loadingFilters = true;
filterTextBox.Clear();
levelComboBox.SelectedIndex = 0;
applicationComboBox.SelectedIndex = 0;
loadingFilters = false;
RefreshView(true);
}
private void filterValueChanged(object sender, EventArgs e)
{
if (!loadingFilters)
{
RefreshView(true);
}
}
private void filterTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
RefreshView(true);
e.SuppressKeyPress = true;
}
else if (e.KeyCode == Keys.Escape)
{
clearFilterButton_Click(sender, EventArgs.Empty);
e.SuppressKeyPress = true;
}
}
private void closeButton_Click(object sender, EventArgs e)
{
Close();
}
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.F)
{
filterTextBox.Focus();
filterTextBox.SelectAll();
e.SuppressKeyPress = true;
}
else if (e.Control && e.KeyCode == Keys.S)
{
saveButton_Click(sender, EventArgs.Empty);
e.SuppressKeyPress = true;
}
else if (e.KeyCode == Keys.F5)
{
RefreshView(true);
e.SuppressKeyPress = true;
}
}
}
+3 -21
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -15,26 +14,9 @@ namespace AppDiagnostic
[STAThread]
static void Main()
{
bool isFirstInstance;
using (var instanceMutex = new Mutex(true, "Sensus.AppDiagnostic.SingleInstance", out isFirstInstance))
using (var activationEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "Sensus.AppDiagnostic.Activate"))
{
if (!isFirstInstance)
{
activationEvent.Set();
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (var diagnosticServer = new DiagnosticPipeServer(DiagnosticLogStore.Instance.Add))
{
diagnosticServer.Start();
Application.Run(new MainForm(() => diagnosticServer.ConnectedClientCount, () => activationEvent.WaitOne(0)));
}
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
+1 -3
View File
@@ -126,9 +126,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Strings.cs.resx" />
<EmbeddedResource Include="Resources\Strings.de.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Strings.de.resx" />
<EmbeddedResource Include="Resources\Strings.fr.resx" />
<EmbeddedResource Include="Resources\Strings.it.resx" />
<EmbeddedResource Include="Resources\Strings.pl.resx" />
@@ -5,6 +5,7 @@ using CordonelPreadjustmentUi.Processes.Itinerary;
using GenesisCordonelInterface.Core.Threading;
using GenesisCordonelInterface.UI.LaatzenAPI_CordonelPreadjustmentUI;
using NLog;
using NLog.Fluent;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -53,8 +54,6 @@ namespace GenesisCordonelInterface.API
private readonly ConcurrentDictionary<int, bool> _selectedSlots = new ConcurrentDictionary<int, bool>();
private readonly ConcurrentDictionary<int, IDisposable> _streamingLeases = new ConcurrentDictionary<int, IDisposable>();
public InterfaceGCIToLaatzen()
{
@@ -283,7 +282,7 @@ namespace GenesisCordonelInterface.API
PortConfig? str,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => InitSlot(slot, cfg, pwd, req, str), token, nameof(InitSlot));
return GetWorker(slot).RunAsync(() => InitSlot(slot, cfg, pwd, req, str), token);
}
public PublicModels.GciInitSlotResult InitSlot(
@@ -335,7 +334,7 @@ namespace GenesisCordonelInterface.API
PortConfig? str,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => UpdateSlot(slot, cfg, pwd, req, str), token, nameof(UpdateSlot));
return GetWorker(slot).RunAsync(() => UpdateSlot(slot, cfg, pwd, req, str), token);
}
public PublicModels.GciInitSlotResult UpdateSlot(
@@ -492,7 +491,7 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => GetOneMeterInfo(slot), token, nameof(GetOneMeterInfo));
return GetWorker(slot).RunAsync(() => GetOneMeterInfo(slot), token);
}
public PublicModels.GciSlotInfo GetOneMeterInfo(int slot)
@@ -703,7 +702,7 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default(CancellationToken))
{
return GetWorker(slot).RunAsync(() => DetectStreamingPort(slot), token, nameof(DetectStreamingPort));
return GetWorker(slot).RunAsync(() => DetectStreamingPort(slot), token);
}
public PortDetectionResult DetectStreamingPort(int slot)
@@ -758,7 +757,7 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default(CancellationToken))
{
return GetWorker(slot).RunAsync(() => DetectRequestPort(slot), token, nameof(DetectRequestPort));
return GetWorker(slot).RunAsync(() => DetectRequestPort(slot), token);
}
public PortDetectionResult DetectRequestPort(int slot)
@@ -808,7 +807,7 @@ namespace GenesisCordonelInterface.API
#region ================================== LOGIN ==================================
public Task<PublicModels.GciLoginResult> LoginOneSlotAsync(int slot, CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => LoginOneSlot(slot), token, nameof(LoginOneSlot));
return GetWorker(slot).RunAsync(() => LoginOneSlot(slot), token);
}
public PublicModels.GciLoginResult LoginOneSlot(int slot)
@@ -880,106 +879,9 @@ namespace GenesisCordonelInterface.API
#region ================================== CONNECT ==================================
/// <summary>
/// Borrows the streaming port for one externally controlled preadjustment phase.
/// The caller must stop its reader first and dispose the lease after all phase tasks finish.
/// </summary>
public Task<IDisposable> AcquirePreadjustmentStreamingPortAsync(
int slot, PortConfig streamingPort, CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() =>
{
token.ThrowIfCancellationRequested();
var meter = GetMeterThreadSafe(slot) as ZeroFlowGenesisMeter;
if (meter == null || !meter.requestPortConfig.HasValue)
throw new InvalidOperationException($"Slot {slot} must be initialized before borrowing its streaming port.");
if (string.IsNullOrWhiteSpace(streamingPort.PortName) ||
string.Equals(streamingPort.PortName, "NA", StringComparison.OrdinalIgnoreCase) ||
string.Equals(streamingPort.PortName, meter.requestPortConfig.Value.PortName, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException("A separate, valid streaming COM port is required.", nameof(streamingPort));
var lease = new StreamingPortLease(() =>
GetWorker(slot).RunAsync(() =>
{
ReleasePreadjustmentStreamingPort(slot, meter);
return true;
}, CancellationToken.None, "ReleasePreadjustmentStreamingPort").GetAwaiter().GetResult());
if (!_streamingLeases.TryAdd(slot, lease))
throw new InvalidOperationException($"Slot {slot} already has an active streaming-port lease.");
try
{
// Borrow only streaming resources. Keep the existing request protocol,
// authentication and process settings throughout the handoff.
meter.ConnectStreamingPort(streamingPort);
if (meter.StreamingPort == null || !meter.StreamingPort.IsOpen())
throw new InvalidOperationException($"GCI could not open {streamingPort.PortName} for slot {slot}.");
token.ThrowIfCancellationRequested();
// Preparation originally installed this handler for the entire sequence.
// Each newly created protocol needs its own subscription and decoder mode.
meter.StartRecordData();
LogInfo("AcquirePreadjustmentStreamingPort", $"Slot={slot}, Port={streamingPort.PortName}, Owner=GCI, LoggedOn={meter.IsLoggedOn}, RecordingStarted=True");
return (IDisposable)lease;
}
catch (Exception acquisitionError)
{
// Cleanup is streaming-only, including cancellation during acquisition.
try
{
meter.DisconnectStreamingPort();
}
catch (Exception restoreError)
{
throw new AggregateException("Streaming acquisition and streaming cleanup failed.", acquisitionError, restoreError);
}
finally
{
_streamingLeases.TryRemove(slot, out _);
}
throw;
}
}, token, nameof(AcquirePreadjustmentStreamingPortAsync));
}
private void ReleasePreadjustmentStreamingPort(int slot, ZeroFlowGenesisMeter meter)
{
string portName = meter.StreamingPort == null ? "NA" : meter.StreamingPort.GetPortName();
try
{
meter.StopRecordData();
}
catch (Exception ex)
{
// A failed decoder stop must never prevent physical port release.
LogError("StopRecordDataBeforeStreamingRelease", ex);
}
meter.DisconnectStreamingPort();
_streamingLeases.TryRemove(slot, out _);
LogInfo("ReleasePreadjustmentStreamingPort", $"Slot={slot}, Port={portName}, Owner=Reader, RequestConnected={meter.IsConnected}");
}
private sealed class StreamingPortLease : IDisposable
{
private readonly object gate = new object();
private Action release;
public StreamingPortLease(Action releaseAction) { release = releaseAction; }
public void Dispose()
{
lock (gate)
{
if (release == null) return;
release();
release = null;
}
}
}
public Task<PublicModels.GciConnectResult> ConnectOneSlotAsync(int slot, CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => ConnectOneSlot(slot), token, nameof(ConnectOneSlot));
return GetWorker(slot).RunAsync(() => ConnectOneSlot(slot), token);
}
public PublicModels.GciConnectResult ConnectOneSlot(int slot)
@@ -1070,7 +972,7 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => Disconnect(slot), token, nameof(Disconnect));
return GetWorker(slot).RunAsync(() => Disconnect(slot), token);
}
public PublicModels.GciDisconnectResult Disconnect(int slot)
@@ -1157,7 +1059,7 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default(CancellationToken))
{
return GetWorker(slot).RunAsync(() => GetPcbId(slot), token, nameof(GetPcbId));
return GetWorker(slot).RunAsync(() => GetPcbId(slot), token, "GetPcbId");
}
public PublicModels.GciGetPcbIdResult GetPcbId(int slot)
@@ -1210,7 +1112,7 @@ namespace GenesisCordonelInterface.API
public Task<RegisterReadResult> ReadRegisterAsync(int slot, string name, CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => ReadRegister(slot, name), token, nameof(ReadRegister));
return GetWorker(slot).RunAsync(() => ReadRegister(slot, name), token);
}
public RegisterReadResult ReadRegister(int slot, string name)
@@ -1264,7 +1166,7 @@ namespace GenesisCordonelInterface.API
bool refreshSystemState = false,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(() => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState), token, nameof(WriteRegister));
return GetWorker(slot).RunAsync(() => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState), token);
}
public RegisterWriteResult WriteRegister(
@@ -1377,7 +1279,7 @@ namespace GenesisCordonelInterface.API
{
return GetWorker(slot).RunAsync(() => SetMeterPassword(slot, password),
token,
nameof(SetMeterPassword));
"SetMeterPassword");
}
public PublicModels.GciSetPasswordResult SetMeterPassword(
@@ -1645,38 +1547,15 @@ namespace GenesisCordonelInterface.API
public PreAdjustmentInitializationResult Preadjustment_Initialization(
ProcessProgress pp,
List<MeterStateControl> mc)
{
return Preadjustment_Initialization(pp, mc, CancellationToken.None);
}
public PreAdjustmentInitializationResult Preadjustment_Initialization(
ProcessProgress pp,
List<MeterStateControl> mc,
CancellationToken token)
{
const string operation = nameof(Preadjustment_Initialization);
VerifyPreadjustmentAttemptAccess();
try
{
LogInfo(operation, "Start.");
token.ThrowIfCancellationRequested();
lock (preadjustmentProcessGate)
{
if (activePreadjustmentCancellation != null || (_progressProcess != null && _progressProcess.IsBusy))
throw new InvalidOperationException("Cannot initialize while a preadjustment process is running.");
_progressProcess = pp;
_meterControls = mc;
// A new attempt must not inherit the previous selection/failure flags.
foreach (var control in mc)
{
control.Failed = false;
control.Ok = false;
SetSlotSelected(control.Slot, control.IsEnabled);
}
}
_progressProcess = pp;
_meterControls = mc;
var initializedSlots = _meterBatch.ListOfMeters
.Select(m => m.Slot)
@@ -1731,7 +1610,6 @@ namespace GenesisCordonelInterface.API
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_Detect);
VerifyPreadjustmentAttemptAccess();
try
{
@@ -1787,39 +1665,20 @@ namespace GenesisCordonelInterface.API
SetUnknownStatus(allMeterControls);
CheckNormalMeters(_meterControls, token);
CheckTemperatureMeters(
_progressProcess.Setting,
_tempMeterControls,
token);
List<MeterStateControl> failedMeters = _meterControls.Where(m => m.IsEnabled && !m.Ok).ToList();
foreach (var failed in failedMeters)
{
failed.Failed = true;
failed.IsEnabled = false;
}
return new PreadjustmentDetectResult
{
Success = failedMeters.Count == 0,
DetectedMeterCount = _meterControls.Count(m => m.IsEnabled && m.Ok),
DetectedThermometerCount = _tempMeterControls.Count(t => t.IsEnabled && t.Ok),
ErrorMessage = failedMeters.Count == 0 ? null : $"Request or streaming port check failed for slots: {string.Join(",", failedMeters.Select(m => m.Slot))}."
Success = true,
DetectedMeterCount = _meterControls.Count(m => m.IsEnabled),
DetectedThermometerCount = _tempMeterControls.Count(t => t.IsEnabled)
};
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
// A batch-level detection error cannot reuse an earlier successful status.
if (_meterControls != null)
foreach (var control in _meterControls)
{
control.Ok = false;
control.Failed = true;
control.IsEnabled = false;
}
return new PreadjustmentDetectResult
{
Success = false,
@@ -1925,25 +1784,17 @@ namespace GenesisCordonelInterface.API
List<MeterStateControl> meterControls,
CancellationToken token)
{
var checks = meterControls.Where(m => m != null && m.IsEnabled).Select(meterCtl =>
GetWorker(meterCtl.Slot).RunAsync(() =>
foreach (var meterCtl in meterControls)
{
token.ThrowIfCancellationRequested();
try
if (meterCtl != null && meterCtl.IsEnabled)
{
meterCtl.Ok = meterCtl.Meter != null &&
meterCtl.Ok =
meterCtl.Meter.CheckRequestPort() &&
meterCtl.Meter.CheckStreamingPort();
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
meterCtl.Ok = false;
LogError($"DetectSlot{meterCtl.Slot}", ex);
}
return meterCtl.Ok;
}, token, "PreadjustmentDetectSlot")).ToArray();
Task.WhenAll(checks).GetAwaiter().GetResult();
}
}
private void CheckTemperatureMeters(
@@ -1974,30 +1825,22 @@ namespace GenesisCordonelInterface.API
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => PreAdjustment_Preparation(slot, token),
token,
nameof(PreAdjustment_Preparation));
() => PreAdjustment_Preparation(slot),
token);
}
public PreAdjustmentProcessResult PreAdjustment_PreparationDirect()
{
return PreAdjustment_PreparationDirect(CancellationToken.None);
}
public PreAdjustmentProcessResult PreAdjustment_PreparationDirect(CancellationToken token)
{
return PreAdjustment_Preparation(-1, token);
return PreAdjustment_Preparation();
}
/// <summary>
/// Executes standalone preparation process.
/// </summary>
public PreAdjustmentProcessResult PreAdjustment_Preparation(
int slot = -1,
CancellationToken token = default)
int slot = -1)
{
const string operation = nameof(PreAdjustment_Preparation);
VerifyPreadjustmentAttemptAccess();
try
{
@@ -2006,7 +1849,7 @@ namespace GenesisCordonelInterface.API
BaseProcess process = CreatePreparationProcess(_progressProcess);
bool success =
ExecuteProcess(process, _progressProcess, token);
ExecuteProcess(process, _progressProcess);
var result =
new PreAdjustmentProcessResult
@@ -2023,7 +1866,6 @@ namespace GenesisCordonelInterface.API
return result;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
LogError(operation, ex);
@@ -2082,27 +1924,19 @@ namespace GenesisCordonelInterface.API
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => PreAdjustment_AmplitudeTest(slot, token),
token,
nameof(PreAdjustment_AmplitudeTest));
() => PreAdjustment_AmplitudeTest(slot),
token);
}
public PreAdjustmentProcessResult PreAdjustment_AmplitudeTestDirect()
{
return PreAdjustment_AmplitudeTestDirect(CancellationToken.None);
}
public PreAdjustmentProcessResult PreAdjustment_AmplitudeTestDirect(CancellationToken token)
{
return PreAdjustment_AmplitudeTest(-1, token);
return PreAdjustment_AmplitudeTest();
}
public PreAdjustmentProcessResult PreAdjustment_AmplitudeTest(
int slot = -1,
CancellationToken token = default)
int slot = -1)
{
const string operation = nameof(PreAdjustment_AmplitudeTest);
VerifyPreadjustmentAttemptAccess();
try
{
@@ -2120,7 +1954,7 @@ namespace GenesisCordonelInterface.API
BaseProcess process = CreateAmplitudeTestProcess(_progressProcess);
bool success = ExecuteProcess(process, _progressProcess, token);
bool success = ExecuteProcess(process, _progressProcess);
LogInfo(operation, $"Finished. Slot={slot}, Success={success}");
@@ -2132,7 +1966,6 @@ namespace GenesisCordonelInterface.API
ErrorMessage = success ? null : process.FailedMessage
};
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
LogError(operation, ex);
@@ -2181,114 +2014,55 @@ namespace GenesisCordonelInterface.API
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => PreAdjustment_TemperatureCalibration(slot, token),
token,
nameof(PreAdjustment_TemperatureCalibration));
() => PreAdjustment_TemperatureCalibration(slot),
token);
}
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect()
{
return PreAdjustment_TemperatureCalibrationDirect(CancellationToken.None);
}
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect(CancellationToken token)
{
return PreAdjustment_TemperatureCalibration(-1, token);
}
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect(
CalibrationTemperatureInput temperature, CancellationToken token)
{
return PreAdjustment_TemperatureCalibration(-1, token, temperature);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
int slot, CalibrationTemperatureInput temperature, CancellationToken token)
{
return GetWorker(slot).RunAsync(
() => PreAdjustment_TemperatureCalibration(slot, token, temperature),
token, nameof(PreAdjustment_TemperatureCalibration));
return PreAdjustment_TemperatureCalibration();
}
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibration(
int slot = -1, CancellationToken token = default)
{
return PreAdjustment_TemperatureCalibration(slot, token, null);
}
private PreAdjustmentProcessResult PreAdjustment_TemperatureCalibration(
int slot, CancellationToken token, CalibrationTemperatureInput temperature)
int slot = -1)
{
const string operation = nameof(PreAdjustment_TemperatureCalibration);
VerifyPreadjustmentAttemptAccess();
BaseProcess process = null;
int[] participatingSlots = new int[0];
try
{
LogInfo(operation, "Start.");
participatingSlots = _meterControls.Where(m => m.IsEnabled).Select(m => m.Slot).ToArray();
process = CreateTemperatureCalibrationProcess(_progressProcess, temperature);
bool success = ExecuteProcess(process, _progressProcess, token);
var result = BuildTemperatureCalibrationResult(process, participatingSlots, slot,
success, success ? null : process.FailedMessage);
LogInfo(operation, $"Start.");
BaseProcess process = CreateTemperatureCalibrationProcess(_progressProcess);
bool success = ExecuteProcess(process, _progressProcess);
var result =
new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
ProcessName = process.ProcessName,
ErrorMessage = success
? null
: process.FailedMessage
};
LogInfo(operation, result.ToString());
return result;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
LogError(operation, ex);
return BuildTemperatureCalibrationResult(process, participatingSlots, slot, false, ex.Message);
}
}
private TemperatureCalibrationResult BuildTemperatureCalibrationResult(
BaseProcess process, int[] participatingSlots, int slot, bool success, string error)
{
var source = process as ITemperatureCalibrationMeasurements;
var measurements = source == null
? new Dictionary<int, CalibrationTemperatureMeasurement>()
: source.GetTemperatureMeasurements().ToDictionary(m => m.Slot);
var result = new TemperatureCalibrationResult
{
Success = success, Slot = slot, ProcessName = process?.ProcessName ?? "Temperature Calibration",
ErrorMessage = error
};
foreach (int meterSlot in participatingSlots)
{
CalibrationTemperatureMeasurement measurement;
measurements.TryGetValue(meterSlot, out measurement);
var control = _meterControls.FirstOrDefault(m => m.Slot == meterSlot);
bool completed = measurement != null && measurement.CalibrationCompleted &&
control != null && !control.Failed;
result.Slots.Add(new TemperatureCalibrationSlotResult
return new PreAdjustmentProcessResult
{
Slot = meterSlot, ProcessName = result.ProcessName, Success = completed,
ReferenceTemperatureCelsius = measurement?.ReferenceTemperatureCelsius,
MeterTemperatureCelsius = measurement?.MeterTemperatureCelsius,
AbsoluteDifferenceCelsius = measurement?.AbsoluteDifferenceCelsius,
DeviationLimitCelsius = measurement?.DeviationLimitCelsius,
SampleCount = measurement?.SampleCount ?? 0,
TemperatureCheckPassed = measurement?.TemperatureCheckPassed,
ErrorMessage = completed ? null :
(measurement?.ErrorMessage ?? error ?? "Temperature calibration did not complete.")
});
Success = false,
Slot = slot,
ProcessName = "Temperature Calibration",
ErrorMessage = ex.Message
};
}
return result;
}
public TemperatureCalibrationResult PreAdjustment_TemperatureCalibrationDetailedDirect(
CalibrationTemperatureInput temperature, CancellationToken token)
{
return TemperatureCalibrationResult.FromProcessResult(
PreAdjustment_TemperatureCalibration(-1, token, temperature));
}
public async Task<TemperatureCalibrationResult> PreAdjustment_TemperatureCalibrationDetailedAsync(
int slot, CalibrationTemperatureInput temperature, CancellationToken token)
{
var result = await PreAdjustment_TemperatureCalibrationAsync(slot, temperature, token).ConfigureAwait(false);
return TemperatureCalibrationResult.FromProcessResult(result);
}
public bool PreAdjustment_PushTemperature(double temperature)
@@ -2308,7 +2082,7 @@ namespace GenesisCordonelInterface.API
/// </summary>
/// <param name="pp">Current process progress context</param>
/// <returns>Configured temperature calibration process</returns>
private BaseProcess CreateTemperatureCalibrationProcess(ProcessProgress pp, CalibrationTemperatureInput temperature)
private BaseProcess CreateTemperatureCalibrationProcess(ProcessProgress pp)
{
if (pp.Setting.NumberOfPaths == 1)
{
@@ -2317,7 +2091,7 @@ namespace GenesisCordonelInterface.API
PreAdjustmentControl.StatusPanelItems.TempCal,
PreAdjustmentControl.PredefinedMessages.WaitUntilTemperatureCalibrationFinished(pp.Setting.Culture),
PreAdjustmentControl.PredefinedMessages.TempCalFailed(pp.Setting.Culture),
2 * 60) { DirectTemperature = temperature };
2 * 60);
}
return new TemperatureCalibrationProcess(
@@ -2325,7 +2099,7 @@ namespace GenesisCordonelInterface.API
PreAdjustmentControl.StatusPanelItems.TempCal,
PreAdjustmentControl.PredefinedMessages.WaitUntilTemperatureCalibrationFinished(pp.Setting.Culture),
PreAdjustmentControl.PredefinedMessages.TempCalFailed(pp.Setting.Culture),
2 * 60) { DirectTemperature = temperature };
2 * 60);
}
#endregion
@@ -2337,27 +2111,19 @@ namespace GenesisCordonelInterface.API
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => PreAdjustment_OffsetTest(slot, token),
token,
nameof(PreAdjustment_OffsetTest));
() => PreAdjustment_OffsetTest(slot),
token);
}
public PreAdjustmentProcessResult PreAdjustment_OffsetTestDirect()
{
return PreAdjustment_OffsetTestDirect(CancellationToken.None);
}
public PreAdjustmentProcessResult PreAdjustment_OffsetTestDirect(CancellationToken token)
{
return PreAdjustment_OffsetTest(-1, token);
return PreAdjustment_OffsetTest();
}
public PreAdjustmentProcessResult PreAdjustment_OffsetTest(
int slot = -1,
CancellationToken token = default)
int slot = -1)
{
const string operation = nameof(PreAdjustment_OffsetTest);
VerifyPreadjustmentAttemptAccess();
try
{
@@ -2375,7 +2141,7 @@ namespace GenesisCordonelInterface.API
BaseProcess process = CreateOffsetTestProcess(_progressProcess);
bool success = ExecuteProcess(process, _progressProcess, token);
bool success = ExecuteProcess(process, _progressProcess);
var result =
new PreAdjustmentProcessResult
@@ -2392,7 +2158,6 @@ namespace GenesisCordonelInterface.API
return result;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
LogError(operation, ex);
@@ -2441,27 +2206,19 @@ namespace GenesisCordonelInterface.API
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => PreAdjustment_Completion(slot, token),
token,
nameof(PreAdjustment_Completion));
() => PreAdjustment_Completion(slot),
token);
}
public PreAdjustmentProcessResult PreAdjustment_CompletionDirect()
{
return PreAdjustment_CompletionDirect(CancellationToken.None);
}
public PreAdjustmentProcessResult PreAdjustment_CompletionDirect(CancellationToken token)
{
return PreAdjustment_Completion(-1, token);
return PreAdjustment_Completion();
}
public PreAdjustmentProcessResult PreAdjustment_Completion(
int slot = -1,
CancellationToken token = default)
int slot = -1)
{
const string operation = nameof(PreAdjustment_Completion);
VerifyPreadjustmentAttemptAccess();
try
{
@@ -2469,10 +2226,10 @@ namespace GenesisCordonelInterface.API
BaseProcess process = CreateCompletionProcess(_progressProcess);
bool success = ExecuteProcess(process, _progressProcess, token);
bool success = ExecuteProcess(process, _progressProcess);
var result =
new CompletionProcessResult
new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
@@ -2482,12 +2239,10 @@ namespace GenesisCordonelInterface.API
: process.FailedMessage
};
CaptureCompletionDatabaseRecords(process, result);
LogInfo(operation, result.ToString());
return result;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
LogError(operation, ex);
@@ -2502,54 +2257,6 @@ namespace GenesisCordonelInterface.API
}
}
private void CaptureCompletionDatabaseRecords(BaseProcess process, CompletionProcessResult result)
{
var completion = process as CompletionProcess;
if (completion == null)
{
// Single-path Completion has a different readout contract.
foreach (var control in _meterControls.Where(m => m.IsEnabled || m.Failed))
_progressProcess.DebugMessage("SQL Completion export is unavailable for this process type.", control.Slot, "DB");
return;
}
var snapshots = completion.GetDatabaseSnapshots();
foreach (var snapshot in snapshots)
{
int pcbId;
int.TryParse(snapshot.PcbId, out pcbId);
// The writer reports invalid identity without sending it to SQL.
result.DatabaseRecords.Add(new PreadjustmentCompletionRecord
{
Slot = snapshot.Slot, PcbId = pcbId, MeterSize = snapshot.MeterSize,
ZeroOffset1 = snapshot.ZeroOffset1, ZeroOffset2 = snapshot.ZeroOffset2,
ZeroOffset3 = snapshot.ZeroOffset3,
FirstHitUpdatePeriod = snapshot.FirstHitUpdatePeriod,
FirstHitShift = snapshot.FirstHitShift,
FirstHitPercent1 = snapshot.FirstHitPercent1,
FirstHitPercent2 = snapshot.FirstHitPercent2,
FirstHitPercent3 = snapshot.FirstHitPercent3,
ToFTempOffset1 = snapshot.ToFTempOffset1,
ToFTempOffset2 = snapshot.ToFTempOffset2,
ToFTempOffset3 = snapshot.ToFTempOffset3,
Date = snapshot.Date, Successful = snapshot.Successful
});
}
foreach (var control in _meterControls.Where(m => m.IsEnabled || m.Failed))
{
if (snapshots.Any(s => s.Slot == control.Slot)) continue;
_progressProcess.DebugMessage("SQL write skipped: Completion produced no verified register readout.", control.Slot, "DB");
result.DatabaseWrites.Add(new CompletionDatabaseWriteResult
{
Slot = control.Slot,
Result = new CalibrationWriteResult
{
Status = CalibrationWriteStatus.Failed,
ErrorMessage = "Completion produced no verified register readout."
}
});
}
}
/// <summary>
/// Creates completion process instance based on current configuration.
/// </summary>
@@ -2578,184 +2285,36 @@ namespace GenesisCordonelInterface.API
#endregion
#region ================================== PreAdjustmentUI ExecuteProcess ==================================
private readonly AsyncLocal<object> preadjustmentAttemptContext = new AsyncLocal<object>();
private object activePreadjustmentAttempt;
private void VerifyPreadjustmentAttemptAccess()
private bool ExecuteProcess(
BaseProcess process,
ProcessProgress pp)
{
lock (preadjustmentProcessGate)
{
if (activePreadjustmentAttempt != null &&
!ReferenceEquals(activePreadjustmentAttempt, preadjustmentAttemptContext.Value))
throw new InvalidOperationException("Another preadjustment attempt owns the slot selection.");
}
}
pp.IsBusy = true;
/// <summary>
/// Executes one synchronous phase attempt with a temporary list of participating slots.
/// The callback must return only after all phase workers and streaming leases are drained.
/// This changes process participation only; it does not connect, disconnect or log in.
/// </summary>
public T ExecutePreadjustmentAttempt<T>(IEnumerable<int> slots, bool retry,
bool initialization, CancellationToken token, Func<T> action)
{
if (slots == null) throw new ArgumentNullException(nameof(slots));
if (action == null) throw new ArgumentNullException(nameof(action));
var selected = new HashSet<int>(slots);
if (selected.Count == 0 || selected.Any(s => s <= 0))
throw new ArgumentException("A phase attempt requires valid slots.", nameof(slots));
process.StartProcess(
pp,
_meterControls,
_tempMeterControls);
List<MeterStateControl> original;
Dictionary<int, bool> originalSelection;
object previousContext = preadjustmentAttemptContext.Value;
var owner = new object();
lock (preadjustmentProcessGate)
while (pp.IsBusy && !pp.StopSequence)
{
token.ThrowIfCancellationRequested();
if (activePreadjustmentAttempt != null || activePreadjustmentCancellation != null ||
(_progressProcess != null && _progressProcess.IsBusy))
throw new InvalidOperationException("A preadjustment process is still running.");
original = (_meterControls ?? new List<MeterStateControl>()).ToList();
if (!initialization && selected.Any(s => !original.Any(c => c.Slot == s)))
throw new InvalidOperationException("Initialize preadjustment before retrying a phase.");
originalSelection = original.ToDictionary(c => c.Slot, c => IsSlotSelected(c.Slot));
activePreadjustmentAttempt = owner;
preadjustmentAttemptContext.Value = owner;
Thread.Sleep(50);
}
try
foreach (var meterCtrl in _meterControls)
{
if (!initialization)
if (meterCtrl.IsEnabled && meterCtrl.Failed)
{
_meterControls = original.Where(c => selected.Contains(c.Slot)).ToList();
foreach (var control in _meterControls)
{
token.ThrowIfCancellationRequested();
if (retry)
{
// Restore execution state only. Do not call SetChecked here:
// its UI dispatch may block on the thread that created the control.
// IsEnabled controls phase eligibility; keep meter calibration data.
control.Failed = false;
control.Ok = false;
control.IsEnabled = true;
}
}
if (retry && _progressProcess != null) _progressProcess.Aborted = false;
foreach (var control in original)
{
token.ThrowIfCancellationRequested();
SetSlotSelected(control.Slot, selected.Contains(control.Slot) && control.IsEnabled);
}
}
token.ThrowIfCancellationRequested();
return action();
}
finally
{
try
{
// Detect can create replacement controls. Merge these back by slot so successful
// meters from an earlier attempt remain available for subsequent phases.
if (!initialization)
{
var updated = (_meterControls ?? new List<MeterStateControl>())
.Where(c => selected.Contains(c.Slot)).ToDictionary(c => c.Slot);
_meterControls = original.Select(c => updated.ContainsKey(c.Slot) ? updated[c.Slot] : c).ToList();
foreach (var control in _meterControls)
SetSlotSelected(control.Slot, selected.Contains(control.Slot)
? control.IsEnabled && !control.Failed : originalSelection[control.Slot]);
}
}
finally
{
lock (preadjustmentProcessGate)
{
activePreadjustmentAttempt = null;
preadjustmentAttemptContext.Value = previousContext;
}
pp.GenerateReturnNote(process.FailedMessage, meterCtrl);
meterCtrl.IsEnabled = false;
}
}
}
private readonly object preadjustmentProcessGate = new object();
private CancellationTokenSource activePreadjustmentCancellation;
if (pp.StopSequence)
return false;
public void CancelPreadjustment()
{
CancellationTokenSource cancellation;
lock (preadjustmentProcessGate)
cancellation = activePreadjustmentCancellation;
if (cancellation == null) return;
try { cancellation.Cancel(); }
catch (ObjectDisposedException) { /* The process has already completed. */ }
}
private bool ExecuteProcess(BaseProcess process, ProcessProgress pp, CancellationToken token)
{
CancellationTokenSource previousSource;
CancellationTokenSource runSource;
lock (preadjustmentProcessGate)
{
VerifyPreadjustmentAttemptAccess();
if (activePreadjustmentCancellation != null || pp.IsBusy)
throw new InvalidOperationException("A preadjustment process is still running.");
token.ThrowIfCancellationRequested();
previousSource = pp.CancellationSource;
// A cancelled, completed run must not poison the next explicit run.
if (previousSource == null || previousSource.IsCancellationRequested)
previousSource = new CancellationTokenSource();
runSource = CancellationTokenSource.CreateLinkedTokenSource(token, previousSource.Token);
activePreadjustmentCancellation = runSource;
lock (pp) pp.CancellationSource = runSource;
pp.StopSequence = false;
pp.IsBusy = true;
}
try
{
if (_meterControls == null || !_meterControls.Any(m => m.IsEnabled && !m.Failed))
throw new InvalidOperationException("No eligible meters remain; preadjustment process was not executed. Run Initialization and Detect for a new attempt.");
foreach (var meterCtrl in _meterControls) meterCtrl.Ok = false;
process.StartProcess(pp, _meterControls, _tempMeterControls);
// Do not return when cancellation is requested: wait for every owned task.
process.Completion.GetAwaiter().GetResult();
runSource.Token.ThrowIfCancellationRequested();
foreach (var meterCtrl in _meterControls)
{
// Set only after all phase tasks completed; failed/absent slots stay false.
meterCtrl.Ok = meterCtrl.IsEnabled && !meterCtrl.Failed;
if (meterCtrl.IsEnabled && meterCtrl.Failed)
{
runSource.Token.ThrowIfCancellationRequested();
// This API runs in a background batch. Return-note generation can show
// a modal MessageBox/PDF UI and would leave the batch waiting forever.
// The interactive PreAdjustmentControl retains responsibility for reports.
pp.DebugMessage(
process.ProcessName + (activePreadjustmentAttempt != null
? " failed in this attempt."
: " failed; slot excluded from remaining preadjustment phases."),
meterCtrl.Slot,
"GCI",
meterCtrl.Meter == null ? string.Empty : meterCtrl.Meter.PcbId);
meterCtrl.IsEnabled = false;
}
}
return _meterControls.Any(m => m.IsEnabled && !m.Failed);
}
finally
{
// Completion has drained workers before the shared progress can be reused.
lock (preadjustmentProcessGate)
{
lock (pp) pp.CancellationSource = previousSource;
pp.IsBusy = false;
activePreadjustmentCancellation = null;
}
runSource.Dispose();
}
return _meterControls.Any(m => m.IsEnabled && !m.Failed);
}
#endregion
}
}
}
@@ -42,82 +42,6 @@ namespace GenesisCordonelInterface.API
public event Action<List<MeterBatchDebugStatus>> MeterBatchStatusChanged;
private readonly IMeterLoginPasswordReader loginPasswordsReader;
private readonly IPreAdjustmentCalibrationParamsReader calibrationParamsReader;
private readonly Core.DataStorage.Writing.CalibrationResultWriter calibrationResultWriter =
new Core.DataStorage.Writing.CalibrationResultWriter(null);
/// <summary>Stores one detached completion record without accessing meter ports.</summary>
public Task<CalibrationWriteResult> StorePreadjustmentCompletionResultAsync(
PreadjustmentCompletionRecord record, CancellationToken token = default(CancellationToken))
{
if (record == null) throw new ArgumentNullException(nameof(record));
// Capture the progress instance and slot for this write, even if a new
// batch is initialized while the database operation is still finishing.
var progress = _innerMeterAPI?._progressProcess;
int? slot = record.Slot;
Action<string> diagnostic = null;
if (progress != null && slot.HasValue && slot.Value > 0)
diagnostic = message => progress.DebugMessage(message, slot.Value, "DB");
return calibrationResultWriter.StorePreadjustmentCompletionAsync(record, token, diagnostic);
}
/// <summary>Creates a synthetic Completion record and exercises the real configured SQL writer.</summary>
public async Task<CompletionProcessResult> StoreSimulatedCompletionResultAsync(
int slot, int pcbId, bool phaseSuccess, string phaseError,
CancellationToken token, Action<string> diagnostic)
{
token.ThrowIfCancellationRequested();
if (slot <= 0) throw new ArgumentOutOfRangeException(nameof(slot));
// Synthetic fixture values belong to GCI, not to the hardware Completion process.
var record = new PreadjustmentCompletionRecord
{
Slot = slot, PcbId = pcbId, MeterSize = 2,
ZeroOffset1 = -20, ZeroOffset2 = -30, ZeroOffset3 = -67,
FirstHitUpdatePeriod = 10, FirstHitShift = 4,
FirstHitPercent1 = 17, FirstHitPercent2 = 13, FirstHitPercent3 = 17,
ToFTempOffset1 = 22260, ToFTempOffset2 = -115410, ToFTempOffset3 = 14992,
Date = DateTime.UtcNow, Successful = phaseSuccess
};
// Optional diagnostic subscribers must never change the storage outcome.
try { diagnostic?.Invoke("Simulated calibration data; actual SQL write requested. Fixture MeterSize=2."); }
catch (Exception) { }
var write = await calibrationResultWriter.StorePreadjustmentCompletionAsync(
record, token, diagnostic).ConfigureAwait(false);
var result = new CompletionProcessResult
{
Slot = slot, Success = phaseSuccess, ProcessName = "PreAdjustment_Completion",
ErrorMessage = phaseError
};
result.DatabaseRecords.Add(record);
result.DatabaseWrites.Add(new CompletionDatabaseWriteResult { Slot = slot, Result = write });
return result;
}
/// <summary>Stores Q3 values supplied by TBF; does not perform Q3 calibration.</summary>
public Task<CalibrationWriteResult> StoreQ3CalibrationResultAsync(
Q3CalibrationRecord record, CancellationToken token = default(CancellationToken))
{
return calibrationResultWriter.StoreQ3CalibrationAsync(record, token);
}
public Task<CalibrationWriteResult> StoreQ3CalibrationResultAsync(
Q3CalibrationRecord record, CancellationToken token, Action<string> diagnostic)
{
return calibrationResultWriter.StoreQ3CalibrationAsync(record, token, diagnostic);
}
public Task<CalibrationWriteResult> StoreSimulatedQ3CalibrationResultAsync(
int pcbId, CancellationToken token, Action<string> diagnostic)
{
token.ThrowIfCancellationRequested();
try { diagnostic?.Invoke("Simulated Q3 fixture values; actual SQL write requested."); }
catch (Exception) { }
return calibrationResultWriter.StoreQ3CalibrationAsync(new Q3CalibrationRecord
{
PcbId = pcbId, P1Offset = -0.3, P1Cal = 15260,
P2Offset = -0.3, P2Cal = 15451, P3Offset = -0.3, P3Cal = 15204,
Date = DateTime.UtcNow
}, token, diagnostic);
}
/// <summary>
/// Initializes a new instance of the public GCI facade.
@@ -134,20 +58,10 @@ namespace GenesisCordonelInterface.API
InterfaceGCIToLaatzen innerMeterApi,
IMeterLoginPasswordReader passwordReader,
IPreAdjustmentCalibrationParamsReader calibrationReader)
: this(innerMeterApi, passwordReader, calibrationReader, null)
{
}
public InterfaceOutsideToGCI(
InterfaceGCIToLaatzen innerMeterApi,
IMeterLoginPasswordReader passwordReader,
IPreAdjustmentCalibrationParamsReader calibrationReader,
GciDataStorageConfig storageConfig)
{
_innerMeterAPI = innerMeterApi?? throw new ArgumentNullException(nameof(innerMeterApi));
loginPasswordsReader = passwordReader ?? throw new ArgumentNullException(nameof(passwordReader));
calibrationParamsReader = calibrationReader ?? throw new ArgumentNullException(nameof(calibrationReader));
calibrationResultWriter = new Core.DataStorage.Writing.CalibrationResultWriter(storageConfig);
//init handlers
//_innerMeterAPI._progressProcess.OnRequestedCalibrationParamsFromDb += RequestedCalibrationParamsFromDb;
@@ -157,36 +71,23 @@ namespace GenesisCordonelInterface.API
object sender,
EventArgsProcessProgress e)
{
var progress = e.Value;
var source = progress.CancellationSource;
var token = source.Token;
try
{
token.ThrowIfCancellationRequested();
DataQuery query = new DataQuery();
query.QueryParams.Add(((int)e.Value.Setting.MeterSize).ToString());
Dictionary<string, UInt32> calibrationParams = await ReadPreAdjustmentCalibrationParamsAsync(query, token).ConfigureAwait(false);
Dictionary<string, UInt32> calibrationParams = await ReadPreAdjustmentCalibrationParamsAsync(query).ConfigureAwait(false);
lock (progress)
{
if (!token.IsCancellationRequested && ReferenceEquals(progress.CancellationSource, source))
progress.PushedCalibrationParams = calibrationParams;
}
e.Value.PushedCalibrationParams = calibrationParams;
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
if (!token.IsCancellationRequested && ReferenceEquals(progress.CancellationSource, source))
progress.DebugMessage($"Calibration params reading failed: {ex.Message}");
e.Value.DebugMessage(
$"Calibration params reading failed: {ex.Message}");
}
finally
{
lock (progress)
{
if (!token.IsCancellationRequested && ReferenceEquals(progress.CancellationSource, source))
progress.CalibrationParamsReadEvent.Set();
}
e.Value.CalibrationParamsReadEvent.Set();
}
}
@@ -230,16 +131,6 @@ namespace GenesisCordonelInterface.API
#region ================================== INIT/UPDATE/GET slot ==================================
/// <summary>Acquires streaming reception until the returned lease is disposed.</summary>
public Task<IDisposable> AcquirePreadjustmentStreamingPortAsync(
int slot, GciPortConfig streamingPort, CancellationToken token = default)
{
if (streamingPort == null) throw new ArgumentNullException(nameof(streamingPort));
var mappedPort = ModelsMapping.MapPort(streamingPort);
if (!mappedPort.HasValue) throw new ArgumentException("A streaming port is required.", nameof(streamingPort));
return _innerMeterAPI.AcquirePreadjustmentStreamingPortAsync(slot, mappedPort.Value, token);
}
/// <summary>
/// Initializes a meter slot using the provided slot configuration.
/// </summary>
@@ -747,27 +638,11 @@ namespace GenesisCordonelInterface.API
List<MeterStateControl> mc)
{
//init handlers
pp.OnRequestedCalibrationParamsFromDb -= RequestedCalibrationParamsFromDb;
pp.OnRequestedCalibrationParamsFromDb += RequestedCalibrationParamsFromDb;
return _innerMeterAPI?.Preadjustment_Initialization(pp, mc);
}
public PreAdjustmentInitializationResult Preadjustment_Initialization(
ProcessProgress pp,
List<MeterStateControl> mc,
CancellationToken token)
{
pp.OnRequestedCalibrationParamsFromDb -= RequestedCalibrationParamsFromDb;
pp.OnRequestedCalibrationParamsFromDb += RequestedCalibrationParamsFromDb;
return _innerMeterAPI?.Preadjustment_Initialization(pp, mc, token);
}
public void CancelPreadjustment()
{
_innerMeterAPI?.CancelPreadjustment();
}
public Task<PreadjustmentDetectResult> PreAdjustment_DetectAsync(
IEnumerable<PublicModels.MeterBatchDebugStatus> selectedSlots,
CancellationToken token = default)
@@ -794,11 +669,6 @@ namespace GenesisCordonelInterface.API
return _innerMeterAPI.PreAdjustment_PreparationDirect();
}
public PreAdjustmentProcessResult PreAdjustment_PreparationDirect(CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_PreparationDirect(token);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_AmplitudeTestAsync(
int slot,
CancellationToken token = default)
@@ -811,11 +681,6 @@ namespace GenesisCordonelInterface.API
return _innerMeterAPI.PreAdjustment_AmplitudeTestDirect();
}
public PreAdjustmentProcessResult PreAdjustment_AmplitudeTestDirect(CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_AmplitudeTestDirect(token);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
int slot,
CancellationToken token = default)
@@ -828,49 +693,12 @@ namespace GenesisCordonelInterface.API
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationDirect();
}
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect(CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationDirect(token);
}
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect(
CalibrationTemperatureInput temperature, CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationDirect(temperature, token);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
int slot, CalibrationTemperatureInput temperature, CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationAsync(slot, temperature, token);
}
public TemperatureCalibrationResult PreAdjustment_TemperatureCalibrationDetailedDirect(
CalibrationTemperatureInput temperature, CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationDetailedDirect(temperature, token);
}
public Task<TemperatureCalibrationResult> PreAdjustment_TemperatureCalibrationDetailedAsync(
int slot, CalibrationTemperatureInput temperature, CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationDetailedAsync(slot, temperature, token);
}
public bool PreAdjustment_PushTemperature(
double temperature)
{
return _innerMeterAPI.PreAdjustment_PushTemperature(temperature);
}
public bool PreAdjustment_PushTemperature(
double temperature,
CancellationToken token)
{
token.ThrowIfCancellationRequested();
return _innerMeterAPI.PreAdjustment_PushTemperature(temperature);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_OffsetTestAsync(
int slot,
CancellationToken token = default)
@@ -883,11 +711,6 @@ namespace GenesisCordonelInterface.API
return _innerMeterAPI.PreAdjustment_OffsetTestDirect();
}
public PreAdjustmentProcessResult PreAdjustment_OffsetTestDirect(CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_OffsetTestDirect(token);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_CompletionAsync(
int slot,
CancellationToken token = default)
@@ -900,11 +723,6 @@ namespace GenesisCordonelInterface.API
return _innerMeterAPI.PreAdjustment_CompletionDirect();
}
public PreAdjustmentProcessResult PreAdjustment_CompletionDirect(CancellationToken token)
{
return _innerMeterAPI.PreAdjustment_CompletionDirect(token);
}
#endregion
#region ================================== UNI DATA STORAGE READER ==================================
@@ -964,4 +782,4 @@ namespace GenesisCordonelInterface.API
#endregion
}
}
}
@@ -49,87 +49,6 @@ namespace GenesisCordonelInterface.API
/// </summary>
public class PublicModels
{
/// <summary>Persisted values for one meter and one preadjustment attempt.</summary>
public sealed class PreadjustmentCompletionRecord
{
// Diagnostic routing only. Not a database column or SQL parameter.
public int? Slot { get; set; }
public int PcbId { get; set; }
public int? MeterSize { get; set; }
public int? ZeroOffset1 { get; set; }
public int? ZeroOffset2 { get; set; }
public int? ZeroOffset3 { get; set; }
public int? FirstHitUpdatePeriod { get; set; }
public int? FirstHitShift { get; set; }
public int? FirstHitPercent1 { get; set; }
public int? FirstHitPercent2 { get; set; }
public int? FirstHitPercent3 { get; set; }
public int? ToFTempOffset1 { get; set; }
public int? ToFTempOffset2 { get; set; }
public int? ToFTempOffset3 { get; set; }
// Timestamp of the attempt, not the time of a possible later write retry.
public DateTime Date { get; set; }
public bool Successful { get; set; }
}
/// <summary>Q3 values calculated and confirmed by the caller; no port access.</summary>
public sealed class Q3CalibrationRecord
{
public int PcbId { get; set; }
// Preserve the existing TBF meaning/unit of each offset (do not rescale here).
public double? P1Offset { get; set; }
public int? P1Cal { get; set; }
public double? P2Offset { get; set; }
public int? P2Cal { get; set; }
public double? P3Offset { get; set; }
public int? P3Cal { get; set; }
public DateTime Date { get; set; }
}
/// <summary>Completion outcome plus detached SQL inputs and separate write outcomes.</summary>
public sealed class CompletionProcessResult : PreAdjustmentProcessResult
{
public List<PreadjustmentCompletionRecord> DatabaseRecords { get; set; } =
new List<PreadjustmentCompletionRecord>();
public List<CompletionDatabaseWriteResult> DatabaseWrites { get; set; } =
new List<CompletionDatabaseWriteResult>();
public override string ToString()
{
return base.ToString() + ", SQL=[" + string.Join("; ",
DatabaseWrites.Select(w => "Slot=" + w.Slot + ", " + w.Result.Status)) + "]";
}
}
public sealed class CompletionDatabaseWriteResult
{
public int Slot { get; set; }
public CalibrationWriteResult Result { get; set; }
}
public enum CalibrationWriteStatus
{
Failed = 0, Stored = 1, Disabled = 2, Simulated = 3,
// The connection failed during commit; verify the DB before retrying.
OutcomeUnknown = 4
}
/// <summary>Database outcome, independent of the physical calibration outcome.</summary>
public sealed class CalibrationWriteResult
{
public CalibrationWriteStatus Status { get; set; }
public bool Success { get { return Status == CalibrationWriteStatus.Stored; } }
public int PcbId { get; set; }
public long? RecordId { get; set; }
public string ErrorMessage { get; set; }
public override string ToString()
{
return string.Format("Status={0}, PcbId={1}, RecordId={2}, Error={3}",
Status, PcbId, RecordId, ErrorMessage ?? "None");
}
}
public static bool HideSensitiveValues { get; set; } = true;
private static string FormatPassword(string password)
@@ -547,57 +466,6 @@ namespace GenesisCordonelInterface.API
}
}
public class TemperatureCalibrationSlotResult : PreAdjustmentProcessResult
{
public decimal? ReferenceTemperatureCelsius { get; set; }
public decimal? MeterTemperatureCelsius { get; set; }
public decimal? AbsoluteDifferenceCelsius { get; set; }
// The effective existing process limit, reported for diagnostics only.
public decimal? DeviationLimitCelsius { get; set; }
public int SampleCount { get; set; }
public bool? TemperatureCheckPassed { get; set; }
public override string ToString()
{
var culture = System.Globalization.CultureInfo.InvariantCulture;
return base.ToString() + string.Format(culture,
", Reference={0} C, Meter={1} C, Difference={2} C, Limit={3} C, Samples={4}, Check={5}",
ReferenceTemperatureCelsius?.ToString(culture) ?? "N/A",
MeterTemperatureCelsius?.ToString(culture) ?? "N/A",
AbsoluteDifferenceCelsius?.ToString(culture) ?? "N/A",
DeviationLimitCelsius?.ToString(culture) ?? "N/A", SampleCount,
TemperatureCheckPassed.HasValue ? (TemperatureCheckPassed.Value ? "PASSED" : "FAILED") : "NOT_EVALUATED");
}
}
public class TemperatureCalibrationResult : PreAdjustmentProcessResult
{
public List<TemperatureCalibrationSlotResult> Slots { get; set; } =
new List<TemperatureCalibrationSlotResult>();
public bool IsSimulated { get; set; }
public override string ToString()
{
return base.ToString() + ", Simulated=" + IsSimulated +
", TemperatureResults=[" + string.Join("; ", Slots.Select(s => s.ToString())) + "]";
}
// Preserve status for failures before process creation and for simulation.
// Missing measurements must never be represented as a measured zero.
public static TemperatureCalibrationResult FromProcessResult(
PreAdjustmentProcessResult result, bool simulated = false)
{
if (result == null) throw new ArgumentNullException(nameof(result));
var detailed = result as TemperatureCalibrationResult;
if (detailed != null) return detailed;
return new TemperatureCalibrationResult
{
Success = result.Success, Slot = result.Slot, ProcessName = result.ProcessName,
ErrorMessage = result.ErrorMessage, IsSimulated = simulated
};
}
}
public class PreadjustmentDetectResult
{
public bool Success { get; set; }
@@ -31,48 +31,6 @@
"Type": "LocalDatabase",
"DataSource": "Server=(localdb)\\SensusLocalDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"QueryTemplate": "SELECT [ParameterName], [ParameterValue] FROM [dbo].[PreAdjustmentCalibrationParams] WHERE [MeterSize] = QUERYPARAM"
},
"PreadjustmentCompletionWriter": {
"Enabled": true,
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"CommandText": "INSERT INTO [dbo].[PreadjustmentCompletion] ([PcbId], [ZeroOffset1], [ZeroOffset2], [ZeroOffset3], [FirstHitUpdatePeriod], [FirstHitShift], [FirstHitPercent1], [FirstHitPercent2], [FirstHitPercent3], [ToFTempOffset1], [ToFTempOffset2], [ToFTempOffset3], [MeterSize], [Date], [Successful]) OUTPUT INSERTED.[Id] VALUES (@PcbId, @ZeroOffset1, @ZeroOffset2, @ZeroOffset3, @FirstHitUpdatePeriod, @FirstHitShift, @FirstHitPercent1, @FirstHitPercent2, @FirstHitPercent3, @ToFTempOffset1, @ToFTempOffset2, @ToFTempOffset3, @MeterSize, @Date, @Successful);",
"UseStoredProcedure": false,
"CommandTimeoutSeconds": 30,
"ParameterMappings": {
"PcbId": "@PcbId",
"ZeroOffset1": "@ZeroOffset1",
"ZeroOffset2": "@ZeroOffset2",
"ZeroOffset3": "@ZeroOffset3",
"FirstHitUpdatePeriod": "@FirstHitUpdatePeriod",
"FirstHitShift": "@FirstHitShift",
"FirstHitPercent1": "@FirstHitPercent1",
"FirstHitPercent2": "@FirstHitPercent2",
"FirstHitPercent3": "@FirstHitPercent3",
"ToFTempOffset1": "@ToFTempOffset1",
"ToFTempOffset2": "@ToFTempOffset2",
"ToFTempOffset3": "@ToFTempOffset3",
"MeterSize": "@MeterSize",
"Date": "@Date",
"Successful": "@Successful"
}
},
"Q3CalibrationWriter": {
"Enabled": true,
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"CommandText": "INSERT INTO [dbo].[Q3Calibration] ([PcbId], [P1Offset], [P1Cal], [P2Offset], [P2Cal], [P3Offset], [P3Cal], [Date]) OUTPUT INSERTED.[Id] VALUES (@PcbId, @P1Offset, @P1Cal, @P2Offset, @P2Cal, @P3Offset, @P3Cal, @Date);",
"UseStoredProcedure": false,
"CommandTimeoutSeconds": 30,
"ParameterMappings": {
"PcbId": "@PcbId",
"P1Offset": "@P1Offset",
"P1Cal": "@P1Cal",
"P2Offset": "@P2Offset",
"P2Cal": "@P2Cal",
"P3Offset": "@P3Offset",
"P3Cal": "@P3Cal",
"Date": "@Date"
}
}
}
}
@@ -17,9 +17,6 @@ namespace GenesisCordonelInterface.Core.DataStorage.Config
/// </summary>
public class GciDataStorageConfig
{
public GciDatabaseWriterConfig PreadjustmentCompletionWriter { get; set; }
public GciDatabaseWriterConfig Q3CalibrationWriter { get; set; }
/// <summary>
/// Configuration for meter login password lookup.
/// </summary>
@@ -30,4 +27,4 @@ namespace GenesisCordonelInterface.Core.DataStorage.Config
/// </summary>
public DataStorageConfig PreAdjustmentCalibrationParams { get; set; }
}
}
}
@@ -1,22 +0,0 @@
using System.Collections.Generic;
namespace GenesisCordonelInterface.Core.DataStorage.Config
{
/// <summary>SQL Server destination for one logical result type.</summary>
public sealed class GciDatabaseWriterConfig
{
// Missing sections in existing configuration files leave writes disabled.
public bool Enabled { get; set; }
public string DataSource { get; set; }
public string CommandText { get; set; }
public bool UseStoredProcedure { get; set; }
public int CommandTimeoutSeconds { get; set; } = 30;
/// <summary>
/// Optional complete mapping: DTO property name -> SQL parameter name.
/// Null/empty uses the documented default parameter names.
/// Commands must return the persisted positive identity as their first scalar.
/// </summary>
public Dictionary<string, string> ParameterMappings { get; set; }
}
}
@@ -1,232 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GenesisCordonelInterface.Core.DataStorage.Config;
using static GenesisCordonelInterface.API.PublicModels;
namespace GenesisCordonelInterface.Core.DataStorage.Writing
{
/// <summary>
/// Stores detached result records. Each call owns its connection and transaction;
/// slot workers, request sessions and streaming ownership are never involved.
/// </summary>
public sealed class CalibrationResultWriter
{
private readonly GciDatabaseWriterConfig completion;
private readonly GciDatabaseWriterConfig q3;
public CalibrationResultWriter(GciDataStorageConfig config)
{
completion = Snapshot(config?.PreadjustmentCompletionWriter);
q3 = Snapshot(config?.Q3CalibrationWriter);
}
private static GciDatabaseWriterConfig Snapshot(GciDatabaseWriterConfig config)
{
if (config == null) return null;
return new GciDatabaseWriterConfig
{
Enabled = config.Enabled, DataSource = config.DataSource,
CommandText = config.CommandText, UseStoredProcedure = config.UseStoredProcedure,
CommandTimeoutSeconds = config.CommandTimeoutSeconds,
ParameterMappings = config.ParameterMappings == null ? null :
new Dictionary<string, string>(config.ParameterMappings, StringComparer.Ordinal)
};
}
public Task<CalibrationWriteResult> StorePreadjustmentCompletionAsync(
PreadjustmentCompletionRecord record, CancellationToken token)
{
return StorePreadjustmentCompletionAsync(record, token, null);
}
public Task<CalibrationWriteResult> StorePreadjustmentCompletionAsync(
PreadjustmentCompletionRecord record, CancellationToken token, Action<string> diagnostic)
{
token.ThrowIfCancellationRequested();
if (record == null) throw new ArgumentNullException(nameof(record));
// Materialize all values before the first await; no mutable record is retained.
var values = new Dictionary<string, object>
{
{ "PcbId", record.PcbId }, { "MeterSize", record.MeterSize },
{ "ZeroOffset1", record.ZeroOffset1 }, { "ZeroOffset2", record.ZeroOffset2 },
{ "ZeroOffset3", record.ZeroOffset3 },
{ "FirstHitUpdatePeriod", record.FirstHitUpdatePeriod },
{ "FirstHitShift", record.FirstHitShift },
{ "FirstHitPercent1", record.FirstHitPercent1 },
{ "FirstHitPercent2", record.FirstHitPercent2 },
{ "FirstHitPercent3", record.FirstHitPercent3 },
{ "ToFTempOffset1", record.ToFTempOffset1 },
{ "ToFTempOffset2", record.ToFTempOffset2 },
{ "ToFTempOffset3", record.ToFTempOffset3 },
{ "Date", record.Date }, { "Successful", record.Successful }
};
string error = !record.MeterSize.HasValue || record.MeterSize.Value < 0
? "MeterSize is required and must not be negative." : null;
if (record.Successful && values.Any(v => v.Value == null))
error = "Successful preadjustment requires all calibration values.";
return WriteAsync(completion, record.PcbId, values, error, token, diagnostic);
}
public Task<CalibrationWriteResult> StoreQ3CalibrationAsync(
Q3CalibrationRecord record, CancellationToken token)
{
return StoreQ3CalibrationAsync(record, token, null);
}
public Task<CalibrationWriteResult> StoreQ3CalibrationAsync(
Q3CalibrationRecord record, CancellationToken token, Action<string> diagnostic)
{
token.ThrowIfCancellationRequested();
if (record == null) throw new ArgumentNullException(nameof(record));
var values = new Dictionary<string, object>
{
{ "PcbId", record.PcbId }, { "P1Offset", record.P1Offset },
{ "P1Cal", record.P1Cal }, { "P2Offset", record.P2Offset },
{ "P2Cal", record.P2Cal }, { "P3Offset", record.P3Offset },
{ "P3Cal", record.P3Cal }, { "Date", record.Date }
};
string error = values.Any(v => v.Value == null)
? "Q3 requires the offset and confirmed calibration factor for all three paths."
: null;
if (values.Values.OfType<double>().Any(v => double.IsNaN(v) || double.IsInfinity(v)))
error = "Q3 offsets must be finite numbers.";
return WriteAsync(q3, record.PcbId, values, error, token, diagnostic);
}
private static async Task<CalibrationWriteResult> WriteAsync(
GciDatabaseWriterConfig config, int pcbId, IDictionary<string, object> values,
string validationError, CancellationToken token, Action<string> diagnostic = null)
{
string operation = values.ContainsKey("Successful") ? "Completion" : "Q3 calibration";
Report(diagnostic, operation + " database write: START.");
var result = new CalibrationWriteResult { PcbId = pcbId };
if (config == null || !config.Enabled)
{
result.Status = CalibrationWriteStatus.Disabled;
result.ErrorMessage = "Database writer is not enabled.";
Report(diagnostic, operation + " database write skipped: writer is disabled or not configured.");
return result;
}
bool commitStarted = false;
bool connectionOpened = false;
string stage = "validation";
try
{
if (validationError != null) throw new ArgumentException(validationError);
if (pcbId <= 0) throw new ArgumentException("PcbId must be positive.");
if ((DateTime)values["Date"] == default(DateTime))
throw new ArgumentException("The calibration timestamp is required.");
if (string.IsNullOrWhiteSpace(config.DataSource) || string.IsNullOrWhiteSpace(config.CommandText))
throw new ArgumentException("Writer DataSource and CommandText are required.");
if (config.CommandTimeoutSeconds < 1)
throw new ArgumentException("CommandTimeoutSeconds must be positive.");
using (var connection = new SqlConnection(config.DataSource))
using (var command = new SqlCommand(config.CommandText, connection))
{
command.CommandType = config.UseStoredProcedure ? CommandType.StoredProcedure : CommandType.Text;
command.CommandTimeout = config.CommandTimeoutSeconds;
AddParameters(command, config, values);
stage = "server connection";
// Only log the destination, never credentials or the full connection string.
var destination = new SqlConnectionStringBuilder(config.DataSource);
Report(diagnostic, "Connecting to server '" + destination.DataSource +
"', database '" + destination.InitialCatalog + "'.");
await connection.OpenAsync(token).ConfigureAwait(false);
connectionOpened = true;
Report(diagnostic, "Connection opened.");
token.ThrowIfCancellationRequested();
stage = "transaction start";
using (var transaction = connection.BeginTransaction())
{
command.Transaction = transaction;
stage = "data write";
Report(diagnostic, "Writing " + operation + " data (" +
(config.UseStoredProcedure ? "stored procedure" : "SQL command") + ").");
// The command/procedure must return the stored Id (OUTPUT INSERTED.Id).
object scalar = await command.ExecuteScalarAsync(token).ConfigureAwait(false);
stage = "returned record Id validation";
if (scalar == null || scalar == DBNull.Value)
throw new InvalidOperationException("Writer did not return a record Id; transaction rolled back.");
decimal id = Convert.ToDecimal(scalar, CultureInfo.InvariantCulture);
if (id <= 0 || id != decimal.Truncate(id) || id > long.MaxValue)
throw new InvalidOperationException("Writer returned an invalid record Id; transaction rolled back.");
token.ThrowIfCancellationRequested();
Report(diagnostic, "Write command completed; confirming transaction.");
stage = "transaction commit";
// Once commit starts, cancellation must not hide a committed record.
commitStarted = true;
transaction.Commit();
result.RecordId = (long)id;
result.Status = CalibrationWriteStatus.Stored;
Report(diagnostic, "Transaction committed; RecordId=" + result.RecordId + ".");
}
}
}
catch (OperationCanceledException) when (!commitStarted)
{
Report(diagnostic, operation + " database write cancelled during " + stage + ".");
// Disposing the uncommitted transaction rolls back its write.
throw;
}
catch (Exception ex)
{
result.Status = commitStarted ? CalibrationWriteStatus.OutcomeUnknown : CalibrationWriteStatus.Failed;
result.ErrorMessage = ex.Message;
// Server messages can contain record values or login names. Keep
// the slot log free of these; retain the original error in the result.
var sqlError = ex as SqlException;
string detail = sqlError == null ? ex.GetType().Name :
"SQL error " + sqlError.Number + ", state " + sqlError.State;
Report(diagnostic, operation + " database write failed during " + stage + " (" + detail + ").");
if (commitStarted)
Report(diagnostic, "Commit outcome is unknown. Verify the database before retrying.");
}
finally
{
// The SQL using scopes have already disposed their resources here.
if (connectionOpened) Report(diagnostic, "SQL connection released.");
}
return result;
}
private static void Report(Action<string> diagnostic, string message)
{
if (diagnostic == null) return;
try { diagnostic(message); }
catch (Exception)
{
// A closed/failing diagnostic subscriber must not change the DB outcome.
}
}
private static void AddParameters(SqlCommand command, GciDatabaseWriterConfig config,
IDictionary<string, object> values)
{
var mappings = config.ParameterMappings;
bool custom = mappings != null && mappings.Count > 0;
if (custom && (mappings.Count != values.Count || mappings.Keys.Any(k => !values.ContainsKey(k))))
throw new ArgumentException("ParameterMappings must map every documented record field exactly once.");
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var value in values)
{
string name = custom ? mappings[value.Key] : "@" + value.Key;
if (string.IsNullOrWhiteSpace(name) || name.Length < 2 || name.Length > 128 ||
name[0] != '@' || !(char.IsLetter(name[1]) || name[1] == '_') ||
name.Skip(2).Any(c => !char.IsLetterOrDigit(c) && c != '_') || !names.Add(name))
throw new ArgumentException("Invalid or duplicate SQL parameter name for " + value.Key + ".");
SqlDbType type = value.Key == "Date" ? SqlDbType.DateTime2 :
value.Key == "Successful" ? SqlDbType.Bit :
value.Key == "P1Offset" || value.Key == "P2Offset" || value.Key == "P3Offset"
? SqlDbType.Float : SqlDbType.Int;
command.Parameters.Add(name, type).Value = value.Value ?? DBNull.Value;
}
}
}
}
@@ -67,8 +67,7 @@ namespace GenesisCordonelInterface.Core
gciExternalInterface = new InterfaceOutsideToGCI(
new InterfaceGCIToLaatzen(),
loginPasswordsDataStorageReader,
calibrationParamsStorageReader,
gciConfig.DataStorageSection);
calibrationParamsStorageReader);
}
}
}
@@ -174,8 +174,7 @@ namespace GenesisCordonelInterface.Core.Threading
public sealed class ApiWorker : IDisposable
{
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface.SlotWorker");
private static long nextRequestId;
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface-threading");
/// <summary>
/// Thread-safe FIFO queue holding work items.
/// </summary>
@@ -233,12 +232,6 @@ namespace GenesisCordonelInterface.Core.Threading
};
thread.Start();
Logger.Info(
"[SLOT-WORKER] CREATED Worker={0}, WorkerThread={1}, CallerThread={2}",
Name,
thread.ManagedThreadId,
Thread.CurrentThread.ManagedThreadId);
}
/// <summary>
@@ -258,93 +251,31 @@ namespace GenesisCordonelInterface.Core.Threading
var tcs = new TaskCompletionSource<T>(
TaskCreationOptions.RunContinuationsAsynchronously);
long requestId = Interlocked.Increment(ref nextRequestId);
string operation = string.IsNullOrWhiteSpace(operationName) ? action.Method.Name : operationName;
int callerThreadId = Thread.CurrentThread.ManagedThreadId;
Stopwatch queueWait = Stopwatch.StartNew();
try
{
Logger.Debug(
"[SLOT-REQUEST] ENQUEUE RequestId={0}, Worker={1}, Operation={2}, CallerThread={3}, QueueLengthBefore={4}",
requestId,
Name,
operation,
callerThreadId,
QueueLength);
Debug.WriteLine($"ENQUEUE {operationName} slot worker={Name} time={DateTime.Now:HH:mm:ss.fff}");
queue.Add(() =>
{
if (token.IsCancellationRequested)
{
queueWait.Stop();
LastActivity = DateTime.Now;
Logger.Warn(
"[SLOT-REQUEST] CANCELLED_BEFORE_START RequestId={0}, Worker={1}, Operation={2}, WorkerThread={3}, QueueWaitMs={4}, QueueLength={5}",
requestId,
Name,
operation,
Thread.CurrentThread.ManagedThreadId,
queueWait.ElapsedMilliseconds,
QueueLength);
tcs.TrySetCanceled();
return;
}
Stopwatch execution = Stopwatch.StartNew();
try
{
IsBusy = true;
CurrentOperation = operation;
CurrentOperation = operationName ?? action.Method.Name;
LastActivity = DateTime.Now;
LastError = null;
queueWait.Stop();
Logger.Debug(
"[SLOT-REQUEST] START RequestId={0}, Worker={1}, Operation={2}, WorkerThread={3}, CallerThread={4}, QueueWaitMs={5}, QueueLength={6}",
requestId,
Name,
operation,
Thread.CurrentThread.ManagedThreadId,
callerThreadId,
queueWait.ElapsedMilliseconds,
QueueLength);
var result = action();
tcs.TrySetResult(result);
execution.Stop();
Logger.Debug(
"[SLOT-REQUEST] COMPLETE RequestId={0}, Worker={1}, Operation={2}, WorkerThread={3}, DurationMs={4}, QueueLength={5}",
requestId,
Name,
operation,
Thread.CurrentThread.ManagedThreadId,
execution.ElapsedMilliseconds,
QueueLength);
}
catch (Exception ex)
{
execution.Stop();
LastError = ex.Message;
Logger.Error(
ex,
"[SLOT-REQUEST] FAILED RequestId={0}, Worker={1}, Operation={2}, WorkerThread={3}, DurationMs={4}, QueueLength={5}, Error={6}",
requestId,
Name,
operation,
Thread.CurrentThread.ManagedThreadId,
execution.ElapsedMilliseconds,
QueueLength,
ex.Message);
tcs.TrySetException(ex);
}
finally
@@ -355,47 +286,13 @@ namespace GenesisCordonelInterface.Core.Threading
}
}, token);
}
catch (OperationCanceledException)
{
queueWait.Stop();
Logger.Warn(
"[SLOT-REQUEST] ENQUEUE_CANCELLED RequestId={0}, Worker={1}, Operation={2}, CallerThread={3}, QueueWaitMs={4}",
requestId,
Name,
operation,
callerThreadId,
queueWait.ElapsedMilliseconds);
tcs.TrySetCanceled();
}
catch (ObjectDisposedException ex)
{
queueWait.Stop();
Logger.Error(
ex,
"[SLOT-REQUEST] ENQUEUE_FAILED RequestId={0}, Worker={1}, Operation={2}, Reason=WorkerDisposed",
requestId,
Name,
operation);
tcs.TrySetException(ex);
}
catch (InvalidOperationException ex)
{
queueWait.Stop();
var disposedException = new ObjectDisposedException(nameof(ApiWorker), ex);
Logger.Error(
disposedException,
"[SLOT-REQUEST] ENQUEUE_FAILED RequestId={0}, Worker={1}, Operation={2}, Reason=QueueCompleted",
requestId,
Name,
operation);
tcs.TrySetException(disposedException);
tcs.TrySetException(new ObjectDisposedException(nameof(ApiWorker), ex));
}
return tcs.Task;
@@ -430,11 +327,6 @@ namespace GenesisCordonelInterface.Core.Threading
/// </summary>
private void WorkerLoop()
{
Logger.Info(
"[SLOT-WORKER] THREAD_STARTED Worker={0}, WorkerThread={1}",
Name,
Thread.CurrentThread.ManagedThreadId);
try
{
foreach (var item in queue.GetConsumingEnumerable())
@@ -447,26 +339,7 @@ namespace GenesisCordonelInterface.Core.Threading
}
catch (ObjectDisposedException)
{
Logger.Debug(
"[SLOT-WORKER] QUEUE_DISPOSED Worker={0}, WorkerThread={1}",
Name,
Thread.CurrentThread.ManagedThreadId);
}
catch (Exception ex)
{
Logger.Error(
ex,
"[SLOT-WORKER] THREAD_FAILED Worker={0}, WorkerThread={1}, Error={2}",
Name,
Thread.CurrentThread.ManagedThreadId,
ex.Message);
}
finally
{
Logger.Info(
"[SLOT-WORKER] THREAD_STOPPED Worker={0}, WorkerThread={1}",
Name,
Thread.CurrentThread.ManagedThreadId);
// worker is shutting down
}
}
@@ -478,41 +351,14 @@ namespace GenesisCordonelInterface.Core.Threading
if (disposed)
return;
Logger.Info(
"[SLOT-WORKER] DISPOSE_BEGIN Worker={0}, CallerThread={1}, WorkerThread={2}, IsBusy={3}, CurrentOperation={4}, QueueLength={5}",
Name,
Thread.CurrentThread.ManagedThreadId,
thread.ManagedThreadId,
IsBusy,
CurrentOperation ?? "<none>",
QueueLength);
disposed = true;
queue.CompleteAdding();
if (Thread.CurrentThread != thread && thread.IsAlive)
{
bool stopped = thread.Join(1000);
if (!stopped)
{
Logger.Warn(
"[SLOT-WORKER] DISPOSE_WAIT_TIMEOUT Worker={0}, WorkerThread={1}, CurrentOperation={2}, QueueLength={3}",
Name,
thread.ManagedThreadId,
CurrentOperation ?? "<none>",
QueueLength);
}
}
thread.Join(1000);
queue.Dispose();
Logger.Info(
"[SLOT-WORKER] DISPOSE_END Worker={0}, CallerThread={1}, WorkerThread={2}, ThreadAlive={3}",
Name,
Thread.CurrentThread.ManagedThreadId,
thread.ManagedThreadId,
thread.IsAlive);
}
}
}
}
@@ -123,8 +123,6 @@
<Compile Include="Core\Config\GciConfig.cs" />
<Compile Include="Core\Config\GciConfigLoader.cs" />
<Compile Include="Core\DataStorage\Config\GciDataStorageConfig.cs" />
<Compile Include="Core\DataStorage\Config\GciDatabaseWriterConfig.cs" />
<Compile Include="Core\DataStorage\Writing\CalibrationResultWriter.cs" />
<Compile Include="API\Enums.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Providers\CsvDataStorageReader.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Providers\DatabaseDataStorageReader.cs" />
@@ -319,10 +317,10 @@
<WCFMetadata Include="Connected Services\" />
</ItemGroup>
<ItemGroup>
<Reference Include="Xylem.Common.Ui.CordonelPreadjustmentUi">
<HintPath>..\packages\Common\Xylem.Common.Ui.CordonelPreadjustmentUi.dll</HintPath>
<Private>True</Private>
</Reference>
<ProjectReference Include="..\ExternalProjects\Laatzen\Genesis\Common\CordonelPreadjustmentUi\CordonelPreadjustmentUi.csproj">
<Project>{D0C8D887-ED52-40AB-A069-90BCE0E801E2}</Project>
<Name>CordonelPreadjustmentUi</Name>
</ProjectReference>
<ProjectReference Include="..\ExternalProjects\Laatzen\Genesis\Common\Ui\GenesisToolBox\GenesisToolBox.csproj">
<Project>{95A50087-99EE-4349-9A17-C547290DB42F}</Project>
<Name>GenesisToolBox</Name>
@@ -331,4 +329,4 @@
<!-- Import custom GCI runtime packaging logic -->
<Import Project="RuntimePackage\Build\Copy.targets.xml" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
@@ -2,48 +2,44 @@
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="Xylem.Common.Ui.GenesisToolBox.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
<section name="Xylem.Common.Ui.GenesisToolBox.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<userSettings>
<Xylem.Common.Ui.GenesisToolBox.Properties.Settings>
<setting name="InitialBinaryPath" serializeAs="String">
<value />
<value/>
</setting>
<setting name="InitialPackageFile" serializeAs="String">
<value />
<value/>
</setting>
</Xylem.Common.Ui.GenesisToolBox.Properties.Settings>
</userSettings>
<appSettings>
<add key="AdminName" value="My Name" />
<add key="AdminEMail" value="MyEMailAddress" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
<add key="AdminName" value="My Name"/>
<add key="AdminEMail" value="MyEMailAddress"/>
<add key="ClientSettingsProvider.ServiceUri" value=""/>
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Xylem.Common.CommonCore" publicKeyToken="null" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.8.18.29406" newVersion="2.8.18.29406" />
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
<providers>
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri=""/>
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400"/>
</providers>
</roleManager>
</system.web>
@@ -31,48 +31,6 @@
"Type": "LocalDatabase",
"DataSource": "Server=(localdb)\\SensusLocalDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"QueryTemplate": "SELECT [ParameterName], [ParameterValue] FROM [dbo].[PreAdjustmentCalibrationParams] WHERE [MeterSize] = QUERYPARAM"
},
"PreadjustmentCompletionWriter": {
"Enabled": true,
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"CommandText": "INSERT INTO [dbo].[PreadjustmentCompletion] ([PcbId], [ZeroOffset1], [ZeroOffset2], [ZeroOffset3], [FirstHitUpdatePeriod], [FirstHitShift], [FirstHitPercent1], [FirstHitPercent2], [FirstHitPercent3], [ToFTempOffset1], [ToFTempOffset2], [ToFTempOffset3], [MeterSize], [Date], [Successful]) OUTPUT INSERTED.[Id] VALUES (@PcbId, @ZeroOffset1, @ZeroOffset2, @ZeroOffset3, @FirstHitUpdatePeriod, @FirstHitShift, @FirstHitPercent1, @FirstHitPercent2, @FirstHitPercent3, @ToFTempOffset1, @ToFTempOffset2, @ToFTempOffset3, @MeterSize, @Date, @Successful);",
"UseStoredProcedure": false,
"CommandTimeoutSeconds": 30,
"ParameterMappings": {
"PcbId": "@PcbId",
"ZeroOffset1": "@ZeroOffset1",
"ZeroOffset2": "@ZeroOffset2",
"ZeroOffset3": "@ZeroOffset3",
"FirstHitUpdatePeriod": "@FirstHitUpdatePeriod",
"FirstHitShift": "@FirstHitShift",
"FirstHitPercent1": "@FirstHitPercent1",
"FirstHitPercent2": "@FirstHitPercent2",
"FirstHitPercent3": "@FirstHitPercent3",
"ToFTempOffset1": "@ToFTempOffset1",
"ToFTempOffset2": "@ToFTempOffset2",
"ToFTempOffset3": "@ToFTempOffset3",
"MeterSize": "@MeterSize",
"Date": "@Date",
"Successful": "@Successful"
}
},
"Q3CalibrationWriter": {
"Enabled": true,
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"CommandText": "INSERT INTO [dbo].[Q3Calibration] ([PcbId], [P1Offset], [P1Cal], [P2Offset], [P2Cal], [P3Offset], [P3Cal], [Date]) OUTPUT INSERTED.[Id] VALUES (@PcbId, @P1Offset, @P1Cal, @P2Offset, @P2Cal, @P3Offset, @P3Cal, @Date);",
"UseStoredProcedure": false,
"CommandTimeoutSeconds": 30,
"ParameterMappings": {
"PcbId": "@PcbId",
"P1Offset": "@P1Offset",
"P1Cal": "@P1Cal",
"P2Offset": "@P2Offset",
"P2Cal": "@P2Cal",
"P3Offset": "@P3Offset",
"P3Cal": "@P3Cal",
"Date": "@Date"
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More