Compare commits
460
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae787622d2 | ||
|
|
d248630ddc | ||
|
|
b15d0a0a08 | ||
|
|
683ca25aaf | ||
|
|
d3cc50b6e8 | ||
|
|
8c3c866eff | ||
|
|
78907de455 | ||
|
|
500adfa789 | ||
|
|
9658fc28bd | ||
|
|
42ad98ab1f | ||
|
|
bd0a876b5b | ||
|
|
806cf0d670 | ||
|
|
82a51293b9 | ||
|
|
4cd7867e04 | ||
|
|
6560278568 | ||
|
|
08a233b2c5 | ||
|
|
7aa92d4806 | ||
|
|
468bcf786e | ||
|
|
21324985db | ||
|
|
4529602d97 | ||
|
|
bce5af56e0 | ||
|
|
f8643c4de1 | ||
|
|
161b20fea7 | ||
|
|
882e894fba | ||
|
|
79c87bedfd | ||
|
|
b1324c567c | ||
|
|
d2627be55b | ||
|
|
2ba6092385 | ||
|
|
fee0651595 | ||
|
|
58fc16d70b | ||
|
|
ddf8996f01 | ||
|
|
0c2cc0dad2 | ||
|
|
1e0c65f213 | ||
|
|
c53a2d9078 | ||
|
|
6223bc925f | ||
|
|
aca1d79023 | ||
|
|
45d12dea6f | ||
|
|
2cee6cb0f2 | ||
|
|
e9cda221bb | ||
|
|
c6181a76ec | ||
|
|
cd24373c80 | ||
|
|
a464b1fc21 | ||
|
|
b8f6af7112 | ||
|
|
1c8d01562f | ||
|
|
2608ee4de6 | ||
|
|
e63f706f41 | ||
|
|
94cd07cd3c | ||
|
|
967ca817ba | ||
|
|
979637cc07 | ||
|
|
5c7ff08a78 | ||
|
|
a64d1a82ed | ||
|
|
bf1766f90a | ||
|
|
1b789ad5d5 | ||
|
|
a7404d0e24 | ||
|
|
a64327ac36 | ||
|
|
c886303458 | ||
|
|
38003592df | ||
|
|
82c2b17a1f | ||
|
|
2660c564a6 | ||
|
|
7e2b4ed908 | ||
|
|
c1960f2150 | ||
|
|
4124443f36 | ||
|
|
6bab62a062 | ||
|
|
c426df990c | ||
|
|
20614fb9e6 | ||
|
|
302431b64c | ||
|
|
db34c579d0 | ||
|
|
83afddc918 | ||
|
|
cf77faf972 | ||
|
|
684513ee59 | ||
|
|
45a6d59b5e | ||
|
|
73c8fecdf2 | ||
|
|
8c77df207a | ||
|
|
1c6f9af2bb | ||
|
|
0cfde7f06b | ||
|
|
d3c361b544 | ||
|
|
b35b7d92f9 | ||
|
|
52bd0d01ce | ||
|
|
eb4f7ad971 | ||
|
|
8677e49680 | ||
|
|
29faef86e3 | ||
|
|
6ef42fcc21 | ||
|
|
d2d41df155 | ||
|
|
6d2477be4a | ||
|
|
57152ae749 | ||
|
|
88b8bc5534 | ||
|
|
41408b30da | ||
|
|
136be96e1b | ||
|
|
0258e54b4e | ||
|
|
d3c8813012 | ||
|
|
b8e54e1fc9 | ||
|
|
054075a341 | ||
|
|
8d42232589 | ||
|
|
e4d4d75642 | ||
|
|
2b5a0ffe14 | ||
|
|
a0d53f9ec9 | ||
|
|
dfa8693be9 | ||
|
|
8157a752bd | ||
|
|
4dd805bb1c | ||
|
|
1e45f48c75 | ||
|
|
d833eca584 | ||
|
|
c966a7034c | ||
|
|
f051d51ae0 | ||
|
|
267159e0a9 | ||
|
|
26db172153 | ||
|
|
2e75f65b4a | ||
|
|
432cb91e5a | ||
|
|
b0083afa10 | ||
|
|
955b59a195 | ||
|
|
b904d0ca41 | ||
|
|
f34046df2a | ||
|
|
889782068e | ||
|
|
d12ee008db | ||
|
|
6c3c6cd0ef | ||
|
|
5803f06bdb | ||
|
|
a4acf30771 | ||
|
|
a4ef208233 | ||
|
|
be6b40b5cd | ||
|
|
8738edd0c5 | ||
|
|
c71fe75446 | ||
|
|
c619c7d3b8 | ||
|
|
f3e5527145 | ||
|
|
39941842d4 | ||
|
|
3488939995 | ||
|
|
0f2ef5a5bf | ||
|
|
55d2381e8a | ||
|
|
126322584f | ||
|
|
4d9ddef3ca | ||
|
|
7139cdbe80 | ||
|
|
93d7456ccd | ||
|
|
7a8ce7b6ab | ||
|
|
865528e344 | ||
|
|
02bb9ee9fa | ||
|
|
79de40515a | ||
|
|
e38c6c1199 | ||
|
|
dbb35a203e | ||
|
|
a88a094e86 | ||
|
|
ffaebbdf07 | ||
|
|
e7d04b55b3 | ||
|
|
8301971906 | ||
|
|
693cf7e4e2 | ||
|
|
2fa212e6ff | ||
|
|
a210a0d1f2 | ||
|
|
4cd191fcb2 | ||
|
|
e21833fb5f | ||
|
|
b746ceb34a | ||
|
|
83c1f0e817 | ||
|
|
a88a5a8db2 | ||
|
|
271200a8da | ||
|
|
b703297f5e | ||
|
|
637d1d021d | ||
|
|
dbe424b17a | ||
|
|
be0525a319 | ||
|
|
0ce130f0ef | ||
|
|
c1ee29cd06 | ||
|
|
28607e2729 | ||
|
|
f1bc71fb92 | ||
|
|
69b607f963 | ||
|
|
6ac3ea87ef | ||
|
|
38973e0030 | ||
|
|
37c89a4c9c | ||
|
|
0075362f92 | ||
|
|
38d28599ad | ||
|
|
c9a4b0f3ce | ||
|
|
b74f1fd644 | ||
|
|
26b7aa7e8b | ||
|
|
92f63e744d | ||
|
|
b0581d81ef | ||
|
|
540b9af871 | ||
|
|
d11f7107fe | ||
|
|
3c5a28f69d | ||
|
|
110b880304 | ||
|
|
7867044c0a | ||
|
|
88903f3dac | ||
|
|
4e223549ee | ||
|
|
2ea0df8faf | ||
|
|
061e85ca6b | ||
|
|
269e650b38 | ||
|
|
a3bfc7a43e | ||
|
|
c2ff40d816 | ||
|
|
eb09ae2eca | ||
|
|
d1b6247bb7 | ||
|
|
c5f52e06fa | ||
|
|
9700ba7900 | ||
|
|
bc72130d09 | ||
|
|
ad208f9633 | ||
|
|
ad2bfd3639 | ||
|
|
5e637ae02e | ||
|
|
63f8dd7467 | ||
|
|
aac3af8714 | ||
|
|
378917f59d | ||
|
|
76e7ccb4a6 | ||
|
|
953b02a6e8 | ||
|
|
71a66dca2d | ||
|
|
978c620287 | ||
|
|
8489dafdf7 | ||
|
|
cfc52f195b | ||
|
|
68a1eb9941 | ||
|
|
c1573390b5 | ||
|
|
5d60523e87 | ||
|
|
91c63e0792 | ||
|
|
0940df2f1a | ||
|
|
16a76720b8 | ||
|
|
7101fd679e | ||
|
|
86299da682 | ||
|
|
39b951b709 | ||
|
|
a475bd80f3 | ||
|
|
4e64bcd117 | ||
|
|
fbd0219a4a | ||
|
|
104a68d9dc | ||
|
|
33a5e479a9 | ||
|
|
5482d4ac96 | ||
|
|
0d984a77b3 | ||
|
|
7e95e18207 | ||
|
|
38fa86d243 | ||
|
|
4c1ce73de9 | ||
|
|
eea80d39c0 | ||
|
|
15dc325275 | ||
|
|
ca333d1772 | ||
|
|
a4ece06b05 | ||
|
|
992452052e | ||
|
|
75192bbd09 | ||
|
|
6199bd48c5 | ||
|
|
25434d70d4 | ||
|
|
de6dd45dcb | ||
|
|
74f2e99ed3 | ||
|
|
99b1f4a171 | ||
|
|
8c10aa3752 | ||
|
|
7170da4a70 | ||
|
|
842f009b42 | ||
|
|
7f30ca0cd1 | ||
|
|
93e207c211 | ||
|
|
64cafc088a | ||
|
|
8a0da27a07 | ||
|
|
5a8478e782 | ||
|
|
368cd95114 | ||
|
|
33ef914984 | ||
|
|
3eb0b87f0d | ||
|
|
c13cb725f0 | ||
|
|
c6d4a016b5 | ||
|
|
eda0644b4b | ||
|
|
711dfc03ef | ||
|
|
93459ccfab | ||
|
|
5b73835d94 | ||
|
|
009aefb7c9 | ||
|
|
d2e18d615d | ||
|
|
69b475eb77 | ||
|
|
9848b97082 | ||
|
|
0b429ffbc2 | ||
|
|
ef146efaf0 | ||
|
|
431061a685 | ||
|
|
b6edd9c0a0 | ||
|
|
2210bb8240 | ||
|
|
8d7c1adf37 | ||
|
|
3376ac41b4 | ||
|
|
f9181fee74 | ||
|
|
4c77d6c06e | ||
|
|
b7db39853b | ||
|
|
66157ba988 | ||
|
|
65ab19290f | ||
|
|
e7e6fc7920 | ||
|
|
94c7279da1 | ||
|
|
2136695b80 | ||
|
|
c0f9b0ffa0 | ||
|
|
c4ef22436f | ||
|
|
0b73871518 | ||
|
|
f7dc6fee5e | ||
|
|
ba848a167d | ||
|
|
dcb0bf9a88 | ||
|
|
f6433134c1 | ||
|
|
7e3df77b5b | ||
|
|
ff5954d85b | ||
|
|
08dff1272b | ||
|
|
c09e266b2c | ||
|
|
d5c8fd1c8f | ||
|
|
a34740e838 | ||
|
|
836ba5a41f | ||
|
|
545a747ebc | ||
|
|
23c83ab452 | ||
|
|
bc9e583c29 | ||
|
|
18cef617da | ||
|
|
4ee736cff5 | ||
|
|
4e40dc8601 | ||
|
|
a5a68ec457 | ||
|
|
e26d88d437 | ||
|
|
98ba7d2dee | ||
|
|
78835c1af6 | ||
|
|
2522d1da9f | ||
|
|
8983c01591 | ||
|
|
61b1320d4f | ||
|
|
2c8a0922d1 | ||
|
|
b078d9586d | ||
|
|
6e35de5f4d | ||
|
|
2dbe4cded4 | ||
|
|
10865c6878 | ||
|
|
ee1353fe54 | ||
|
|
5cefd7fc3a | ||
|
|
ab0e5ea82a | ||
|
|
b2276c086c | ||
|
|
4fa462661c | ||
|
|
2b30d0cf34 | ||
|
|
df2c56e6c4 | ||
|
|
a997c3d7be | ||
|
|
3c964f2aab | ||
|
|
c445b3e098 | ||
|
|
2ea2a2592e | ||
|
|
27072598cd | ||
|
|
4a2d4772d7 | ||
|
|
2bf00606d3 | ||
|
|
3773e1d96c | ||
|
|
19ddc753c7 | ||
|
|
0c41c750e8 | ||
|
|
4bfd5c8e11 | ||
|
|
f0fb70e38b | ||
|
|
6e5bcafec0 | ||
|
|
d5cb18c910 | ||
|
|
3a2069b255 | ||
|
|
80c651dbfd | ||
|
|
5a3b9b57c4 | ||
|
|
ead350eef3 | ||
|
|
b43657a4db | ||
|
|
8fffb9e366 | ||
|
|
0fdd229141 | ||
|
|
2f1ada3f5b | ||
|
|
988a8c6642 | ||
|
|
42ee9eaee3 | ||
|
|
3f28051d79 | ||
|
|
65f13cc868 | ||
|
|
cd8ba3cda3 | ||
|
|
a5936c8f19 | ||
|
|
2842425673 | ||
|
|
06a401a1ff | ||
|
|
3a2fc50fec | ||
|
|
38a58ed8b7 | ||
|
|
c7cba20de0 | ||
|
|
6311df49c3 | ||
|
|
f1d32a39ba | ||
|
|
224b53f774 | ||
|
|
1e2777c5e7 | ||
|
|
c3c21649fb | ||
|
|
99ccabc142 | ||
|
|
f1e6e94450 | ||
|
|
320cddb177 | ||
|
|
42002e3fa4 | ||
|
|
77cabfd712 | ||
|
|
c933e2d759 | ||
|
|
f3f0a46627 | ||
|
|
b5fb4c2947 | ||
|
|
f6cce9df3c | ||
|
|
c0385c0782 | ||
|
|
db43bdb652 | ||
|
|
6bdc10f4a7 | ||
|
|
9692dc7f4f | ||
|
|
6795793858 | ||
|
|
a2c564d789 | ||
|
|
3bc9e731e8 | ||
|
|
95c5f09238 | ||
|
|
0a45d66c0b | ||
|
|
96408eaef3 | ||
|
|
cae3ca7cdf | ||
|
|
c8adc093e0 | ||
|
|
4395516216 | ||
|
|
9281b52464 | ||
|
|
6571de7c32 | ||
|
|
a75d66e028 | ||
|
|
e839148a6f | ||
|
|
65b502a9b1 | ||
|
|
9cde62b2e2 | ||
|
|
cc86c38246 | ||
|
|
357df87f49 | ||
|
|
4b33da565c | ||
|
|
d11f855325 | ||
|
|
eef1f31a21 | ||
|
|
b878ee96a5 | ||
|
|
97eed27458 | ||
|
|
fe08550dc3 | ||
|
|
8a0ae8080f | ||
|
|
ab4025e55f | ||
|
|
22fffe602c | ||
|
|
e4c0219783 | ||
|
|
3362fdfaff | ||
|
|
6093db4502 | ||
|
|
5e7598667b | ||
|
|
4fe8729b85 | ||
|
|
b1dd6666f1 | ||
|
|
a3613889e1 | ||
|
|
7b87d63531 | ||
|
|
d838c78a8a | ||
|
|
7a8bef501a | ||
|
|
9863b54bc8 | ||
|
|
e57c6a6a02 | ||
|
|
afa215534c | ||
|
|
780d44166e | ||
|
|
35e4078dda | ||
|
|
47e09584f2 | ||
|
|
fc8e82c0e8 | ||
|
|
b729b08da2 | ||
|
|
3209ff303c | ||
|
|
41693d9c83 | ||
|
|
8f02f4ff31 | ||
|
|
1525bb7c1a | ||
|
|
2aeaf42325 | ||
|
|
bf4fb469da | ||
|
|
efd0515295 | ||
|
|
be8ac28bc7 | ||
|
|
56dc89ac82 | ||
|
|
62ffc94c49 | ||
|
|
50a38bc557 | ||
|
|
3e643854bc | ||
|
|
0daefb93bb | ||
|
|
f5731c67a0 | ||
|
|
d2ae5f3e83 | ||
|
|
c4d332b68b | ||
|
|
1daa0212ef | ||
|
|
1748a83ec8 | ||
|
|
b3d6b5a59f | ||
|
|
891795b101 | ||
|
|
a20b0761fd | ||
|
|
3b9e8d754d | ||
|
|
fed3281525 | ||
|
|
c1d966d86a | ||
|
|
b2d2f8c13b | ||
|
|
4e5e3d63e1 | ||
|
|
25602fb914 | ||
|
|
602d0d5308 | ||
|
|
f95198e40e | ||
|
|
d2b2bbb94b | ||
|
|
a4246c7ea4 | ||
|
|
498fc21412 | ||
|
|
3f07a506ad | ||
|
|
fb74e2d785 | ||
|
|
3a2290570d | ||
|
|
3b68e3f9fd | ||
|
|
3184b1da8e | ||
|
|
b70a10e697 | ||
|
|
3433bbbebe | ||
|
|
4c0a797e98 | ||
|
|
203c2498ac | ||
|
|
2023ea5221 | ||
|
|
cbb3d4f735 | ||
|
|
8acf219ba2 | ||
|
|
68884cd7df | ||
|
|
1629d82d65 | ||
|
|
17ab11bdf1 | ||
|
|
d2ef959f15 | ||
|
|
21b00e9b83 | ||
|
|
3d7aaaaa3d | ||
|
|
705f7760af | ||
|
|
3b385d592d | ||
|
|
cf6a16656b | ||
|
|
f50f9bfa5b | ||
|
|
77d8c95930 | ||
|
|
57c2019405 | ||
|
|
46432bc61b | ||
|
|
a4360f6993 | ||
|
|
97e0a764b8 | ||
|
|
7409af70ac | ||
|
|
783d51e71a | ||
|
|
a710ab7457 | ||
|
|
6a428e3c76 |
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Root Type="DevExpress.CodeRush.Foundation.CodePlaces.Options.FavoritesListContainer">
|
||||||
|
<Options Language="Neutral">
|
||||||
|
<Groups />
|
||||||
|
</Options>
|
||||||
|
</Root>
|
||||||
+99
-4
@@ -1,3 +1,12 @@
|
|||||||
|
# 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/
|
Common/bin/
|
||||||
Common/obj/
|
Common/obj/
|
||||||
Config/bin/
|
Config/bin/
|
||||||
@@ -30,6 +39,10 @@ LabelPrinting/bin/
|
|||||||
LabelPrinting/obj/
|
LabelPrinting/obj/
|
||||||
MergeResultsDBs/bin/
|
MergeResultsDBs/bin/
|
||||||
MergeResultsDBs/obj/
|
MergeResultsDBs/obj/
|
||||||
|
NfcC7_Dll/bin/
|
||||||
|
NfcC7_Dll/obj/
|
||||||
|
NfcC7_DLL.Tests/bin/
|
||||||
|
NfcC7_DLL.Tests/obj/
|
||||||
OrderManagement/bin/
|
OrderManagement/bin/
|
||||||
OrderManagement/obj/
|
OrderManagement/obj/
|
||||||
ProductionTracing/bin/
|
ProductionTracing/bin/
|
||||||
@@ -48,14 +61,18 @@ ResultsParser/bin/
|
|||||||
ResultsParser/obj/
|
ResultsParser/obj/
|
||||||
S640TestApp/bin/
|
S640TestApp/bin/
|
||||||
S640TestApp/obj/
|
S640TestApp/obj/
|
||||||
SharedDatabase/bin
|
SharedComponents/bin/
|
||||||
SharedDatabase/obj
|
SharedComponents/obj/
|
||||||
SchematicDrawing/bin
|
SharedDatabase/bin/
|
||||||
SchematicDrawing/obj
|
SharedDatabase/obj/
|
||||||
|
SchematicDrawing/bin/
|
||||||
|
SchematicDrawing/obj/
|
||||||
Statistics/bin/
|
Statistics/bin/
|
||||||
Statistics/obj/
|
Statistics/obj/
|
||||||
TBF/bin/
|
TBF/bin/
|
||||||
TBF/obj/
|
TBF/obj/
|
||||||
|
TBFTests/bin/
|
||||||
|
TBFTests/obj/
|
||||||
ToFirstMonitor/bin/
|
ToFirstMonitor/bin/
|
||||||
ToFirstMonitor/obj/
|
ToFirstMonitor/obj/
|
||||||
ToSecondMonitor/bin/
|
ToSecondMonitor/bin/
|
||||||
@@ -66,6 +83,84 @@ WorkflowConfigurator/bin/
|
|||||||
WorkflowConfigurator/obj/
|
WorkflowConfigurator/obj/
|
||||||
Workplace/bin/
|
Workplace/bin/
|
||||||
Workplace/obj/
|
Workplace/obj/
|
||||||
|
DataStreamInterfaceTest/bin/
|
||||||
|
DataStreamInterfaceTest/obj/
|
||||||
|
DataStreamMeter/bin/
|
||||||
|
DataStreamMeter/obj/
|
||||||
|
Events/bin/
|
||||||
|
Events/obj/
|
||||||
|
TracingDB/bin/
|
||||||
|
TracingDB/obj/
|
||||||
|
Users/bin/
|
||||||
|
Users/obj/
|
||||||
.vs/
|
.vs/
|
||||||
|
.idea/
|
||||||
*.suo
|
*.suo
|
||||||
*.bak
|
*.bak
|
||||||
|
TBF.sln.DotSettings.user
|
||||||
|
/ExternalProjects
|
||||||
|
/GenesisCordonelTester
|
||||||
|
/packages/*
|
||||||
|
!/packages/Common/
|
||||||
|
/GenesisCordonelInterface/bin/Debug/GenesisCordonelInterfaceLogger.log
|
||||||
|
/GenesisCordonelInterface/bin/Debug/GenesisCordonelTesterLogger.log
|
||||||
|
/GenesisCordonelInterface/bin/Debug/SirtConfig.json
|
||||||
|
/GenesisCordonelInterface/bin/Release
|
||||||
|
/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
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 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.
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||||
|
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||||
|
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||||
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||||
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\MFRNIAK\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||||
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
|
<SourceRoot Include="C:\Users\MFRNIAK\.nuget\packages\" />
|
||||||
|
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
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 =
|
||||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
551ba94934db30a277a1a9cec2dacbe03f8393107e5d0b2436695f9bd62bd569
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
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 =
|
||||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
a53d59482cad49b656138b1af5cf3e55edf2108969aedc574c362be6c671436b
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<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>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>AppDiagnostic</RootNamespace>
|
||||||
|
<AssemblyName>AppDiagnostic</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Web" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Deployment" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="ColorRuleEditorForm.cs">
|
||||||
|
<SubType>Form</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="MainForm.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="MainForm.Designer.cs">
|
||||||
|
<DependentUpon>MainForm.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<EmbeddedResource Include="MainForm.resx">
|
||||||
|
<DependentUpon>MainForm.cs</DependentUpon>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="Properties\Resources.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
</Compile>
|
||||||
|
<None Include="Properties\Settings.settings">
|
||||||
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<Compile Include="Properties\Settings.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<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>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Resources\TBF_icon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
public class BufferedListView : ListView
|
||||||
|
{
|
||||||
|
public BufferedListView()
|
||||||
|
{
|
||||||
|
// Zapne dvojité bufferovanie pre ListView
|
||||||
|
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
|
||||||
|
this.UpdateStyles();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace AppDiagnostic
|
||||||
|
{
|
||||||
|
public class DiagApi
|
||||||
|
{
|
||||||
|
private static MainForm diagWindow; // Udržiava referenciu na existujúce okno
|
||||||
|
|
||||||
|
public DiagApi()
|
||||||
|
{
|
||||||
|
if (diagWindow == null || diagWindow.IsDisposed) // Ak okno neexistuje, vytvoríme ho
|
||||||
|
{
|
||||||
|
diagWindow = new MainForm();
|
||||||
|
diagWindow.FormClosing += (s, e) =>
|
||||||
|
{
|
||||||
|
diagWindow = null; // Keď sa zavrie, vyčistíme referenciu
|
||||||
|
};
|
||||||
|
diagWindow.Show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
diagWindow.BringToFront(); // Ak už beží, len ho presunieme na vrch
|
||||||
|
diagWindow.WindowState = FormWindowState.Normal; // Ak je minimalizované, obnovíme ho
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
using SharedComponents;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace AppDiagnostic
|
||||||
|
{
|
||||||
|
public class LogAdapter
|
||||||
|
{
|
||||||
|
private ListView _listView;
|
||||||
|
public string _filterText = string.Empty;
|
||||||
|
private bool _isUserScrolling = false; // Indikátor, že používateľ manuálne scrolluje
|
||||||
|
private bool _autoScrollEnabled = true; // Indikátor, že automatické scrollovanie je povolené
|
||||||
|
private Timer _refreshTimer;
|
||||||
|
private const int RefreshInterval = 1000;
|
||||||
|
|
||||||
|
public LogAdapter(ListView listView)
|
||||||
|
{
|
||||||
|
_listView = listView;
|
||||||
|
|
||||||
|
// Nastavenie ListView
|
||||||
|
_listView.View = View.Details;
|
||||||
|
_listView.Columns.Clear();
|
||||||
|
_listView.Columns.Add("Logs");
|
||||||
|
|
||||||
|
_listView.Scrollable = true;
|
||||||
|
|
||||||
|
// Nastavenie šírky stĺpca na celú šírku ListView
|
||||||
|
AdjustColumnWidth();
|
||||||
|
|
||||||
|
// Udalosť pre dynamickú zmenu veľkosti stĺpca pri zmene veľkosti ListView
|
||||||
|
_listView.Resize += (sender, args) => AdjustColumnWidth();
|
||||||
|
|
||||||
|
// Pridanie udalosti pre manuálne skrolovanie
|
||||||
|
_listView.MouseWheel += ListView_MouseWheel;
|
||||||
|
|
||||||
|
// Inicializácia časovača na pravidelný refresh
|
||||||
|
_refreshTimer = new Timer { Interval = RefreshInterval };
|
||||||
|
_refreshTimer.Tick += (sender, args) => RefreshListView();
|
||||||
|
_refreshTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListView_MouseWheel(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
_isUserScrolling = true;
|
||||||
|
|
||||||
|
// Ak sa manuálnym scrollovaním používateľ dostane na koniec, obnovíme automatické scrollovanie
|
||||||
|
if (IsOnLastItem())
|
||||||
|
{
|
||||||
|
_isUserScrolling = false;
|
||||||
|
_autoScrollEnabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_autoScrollEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshListView()
|
||||||
|
{
|
||||||
|
//LiveLogCache.Instance.AddLog(string.Format("-------------------------------RunDeviceAfter()-------------------------------"));
|
||||||
|
|
||||||
|
if (_listView.InvokeRequired)
|
||||||
|
{
|
||||||
|
_listView.Invoke(new Action(RefreshListView));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_listView.BeginUpdate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Získanie pozície prvého viditeľného prvku pre stabilizáciu scrollovania
|
||||||
|
int topIndexBeforeRefresh = _listView.TopItem?.Index ?? 0;
|
||||||
|
|
||||||
|
// Pred pridaním nových položiek si pamätáme, či bol používateľ na poslednej položke
|
||||||
|
bool wasOnLastItem = IsOnLastItem();
|
||||||
|
|
||||||
|
// Uchovanie aktuálne označených položiek (indexov)
|
||||||
|
var selectedIndices = _listView.SelectedIndices.Cast<int>().ToList();
|
||||||
|
|
||||||
|
// Načítanie logov
|
||||||
|
var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.GetCount())
|
||||||
|
.Where(log => string.IsNullOrEmpty(_filterText) ||
|
||||||
|
log.IndexOf(_filterText, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Vymazanie existujúcich položiek a pridanie nových
|
||||||
|
_listView.Items.Clear();
|
||||||
|
|
||||||
|
foreach (var log in logs)
|
||||||
|
{
|
||||||
|
var item = new ListViewItem(log)
|
||||||
|
{
|
||||||
|
BackColor = _listView.Items.Count % 2 == 0 ? Color.White : Color.FromArgb(240, 240, 240)
|
||||||
|
};
|
||||||
|
_listView.Items.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obnovenie označených položiek
|
||||||
|
foreach (var index in selectedIndices)
|
||||||
|
{
|
||||||
|
if (index < _listView.Items.Count)
|
||||||
|
{
|
||||||
|
_listView.Items[index].Selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ak bol používateľ na poslednom prvku, nastavíme focus a scroll na posledný prvok
|
||||||
|
if (_autoScrollEnabled && wasOnLastItem && _listView.Items.Count > 0)
|
||||||
|
{
|
||||||
|
var lastItemIndex = _listView.Items.Count - 1;
|
||||||
|
_listView.EnsureVisible(lastItemIndex);
|
||||||
|
_listView.Items[lastItemIndex].Focused = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ak používateľ nebol na spodku, vrátime sa na predchádzajúcu pozíciu scrollu
|
||||||
|
if (topIndexBeforeRefresh < _listView.Items.Count)
|
||||||
|
{
|
||||||
|
_listView.TopItem = _listView.Items[topIndexBeforeRefresh];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_listView.EndUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void ApplyFilter(string filterText)
|
||||||
|
{
|
||||||
|
_filterText = filterText?.Trim() ?? string.Empty;
|
||||||
|
RefreshListView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOnLastItem()
|
||||||
|
{
|
||||||
|
if (_listView.Items.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Získame poslednú položku
|
||||||
|
int lastItemIndex = _listView.Items.Count - 1;
|
||||||
|
var lastItem = _listView.Items[lastItemIndex];
|
||||||
|
|
||||||
|
// Overíme, či je posledná položka úplne viditeľná
|
||||||
|
return lastItem.Bounds.Bottom <= _listView.ClientRectangle.Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableAutoScroll()
|
||||||
|
{
|
||||||
|
_autoScrollEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableAutoScroll()
|
||||||
|
{
|
||||||
|
_autoScrollEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdjustColumnWidth()
|
||||||
|
{
|
||||||
|
if (_listView.Columns.Count > 0)
|
||||||
|
{
|
||||||
|
_listView.Columns[0].Width = _listView.ClientSize.Width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using SharedComponents;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace AppDiagnostic
|
||||||
|
{
|
||||||
|
public class LogFilter
|
||||||
|
{
|
||||||
|
private ListView _listView;
|
||||||
|
private string _filterText = string.Empty; // Pre uloženie aktuálneho filtrovaného reťazca
|
||||||
|
|
||||||
|
// Konstruktor
|
||||||
|
public LogFilter(ListView listView)
|
||||||
|
{
|
||||||
|
_listView = listView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metóda na aplikovanie filtra na ListView
|
||||||
|
public void ApplyFilter(string filterText)
|
||||||
|
{
|
||||||
|
_filterText = filterText.ToLower(); // Ukladáme text filtra bez ohľadu na veľkosť písmen
|
||||||
|
|
||||||
|
// Po aplikovaní filtra obnovíme zobrazenie
|
||||||
|
RefreshListView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshListView()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Skontrolujeme, či sme na hlavnom vlákne a v prípade potreby použijeme Invoke
|
||||||
|
if (_listView.InvokeRequired)
|
||||||
|
{
|
||||||
|
// Použijeme Invoke, ak nie sme na hlavnom vlákne
|
||||||
|
_listView.Invoke(new Action(RefreshListView));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Vymazanie existujúcich položiek
|
||||||
|
_listView.Items.Clear();
|
||||||
|
|
||||||
|
// Načítame všetky logy (ideálne z cache alebo databázy)
|
||||||
|
var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.Logs.Count);
|
||||||
|
|
||||||
|
// Pridáme len tie logy, ktoré spĺňajú filter
|
||||||
|
foreach (var log in logs)
|
||||||
|
{
|
||||||
|
if (log.ToLower().Contains(_filterText)) // Kontrola, či log obsahuje filter text
|
||||||
|
{
|
||||||
|
var listViewItem = new ListViewItem(log);
|
||||||
|
|
||||||
|
// Striedanie farieb riadkov
|
||||||
|
if (_listView.Items.Count % 2 == 0) // Párny index => biela
|
||||||
|
{
|
||||||
|
listViewItem.BackColor = Color.White;
|
||||||
|
}
|
||||||
|
else // Nepárny index => svetlá sivá
|
||||||
|
{
|
||||||
|
listViewItem.BackColor = Color.FromArgb(240, 240, 240); // Veľmi svetlá sivá
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pridanie efektu pre novú položku
|
||||||
|
ApplyNewItemEffect(listViewItem);
|
||||||
|
|
||||||
|
// Pridanie položky do ListView
|
||||||
|
_listView.Items.Add(listViewItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Zachytíme výnimku, ak sa niečo pokazí, a zobrazíme ju užívateľovi
|
||||||
|
MessageBox.Show($"An error occurred while refreshing the log view: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Môžete pridať ďalšie metódy na zobrazenie alebo efekt pre nové položky, ak je to potrebné
|
||||||
|
private void ApplyNewItemEffect(ListViewItem item)
|
||||||
|
{
|
||||||
|
// Prípadný efekt pre nový pridaný log
|
||||||
|
// Môžete pridať animáciu alebo zmenu farby atď.
|
||||||
|
item.ForeColor = Color.Green; // Napríklad zmeníme farbu písma na zelenú
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+309
@@ -0,0 +1,309 @@
|
|||||||
|
namespace AppDiagnostic
|
||||||
|
{
|
||||||
|
partial class MainForm
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && components != null)
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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.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";
|
||||||
|
//
|
||||||
|
// 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.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.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
using AppDiagnostic.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public MainForm() : this(null, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainForm(Func<int> connectedClientCountProvider, Func<bool> activationRequestProvider)
|
||||||
|
{
|
||||||
|
this.connectedClientCountProvider = connectedClientCountProvider;
|
||||||
|
this.activationRequestProvider = activationRequestProvider;
|
||||||
|
InitializeComponent();
|
||||||
|
Icon = Properties.Resources.TBF_icon;
|
||||||
|
|
||||||
|
regularLogFont = new Font(logView.Font, FontStyle.Regular);
|
||||||
|
boldLogFont = new Font(logView.Font, FontStyle.Bold);
|
||||||
|
colorRules = LogColorRuleRepository.Load();
|
||||||
|
|
||||||
|
InitializeFilters();
|
||||||
|
|
||||||
|
refreshTimer = new Timer { Interval = 250 };
|
||||||
|
refreshTimer.Tick += refreshTimer_Tick;
|
||||||
|
refreshTimer.Start();
|
||||||
|
|
||||||
|
RefreshView(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
refreshTimer.Stop();
|
||||||
|
refreshTimer.Dispose();
|
||||||
|
regularLogFont.Dispose();
|
||||||
|
boldLogFont.Dispose();
|
||||||
|
base.OnFormClosing(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeFilters()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshTimer_Tick(object sender, EventArgs 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);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, $"Unable to copy text: {exception.Message}", "Clipboard error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wrapButton_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
if (editor.ShowDialog(this) != DialogResult.OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
colorRules = editor.Rules;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogColorRuleRepository.Save(colorRules);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace AppDiagnostic
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The main entry point for the application.
|
||||||
|
/// </summary>
|
||||||
|
[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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("AppDiagnostic")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("AppDiagnostic")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2024")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("fa9abad1-7184-4295-ade8-d44f2e3de6b2")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace AppDiagnostic.Properties {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AppDiagnostic.Properties.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||||
|
/// </summary>
|
||||||
|
internal static System.Drawing.Icon TBF_icon {
|
||||||
|
get {
|
||||||
|
object obj = ResourceManager.GetObject("TBF_icon", resourceCulture);
|
||||||
|
return ((System.Drawing.Icon)(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||||
|
<data name="TBF_icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
|
<value>..\Resources\TBF_icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace AppDiagnostic.Properties
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||||
|
<Profiles>
|
||||||
|
<Profile Name="(Default)" />
|
||||||
|
</Profiles>
|
||||||
|
<Settings />
|
||||||
|
</SettingsFile>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="log4net" version="3.0.3" targetFramework="net472" />
|
||||||
|
</packages>
|
||||||
+10
-3
@@ -17,7 +17,7 @@
|
|||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
<DefineConstants>TRACE;DEBUG;LANG_PL</DefineConstants>
|
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
<DefineConstants>TRACE;LANG_PL</DefineConstants>
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -33,9 +33,14 @@
|
|||||||
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
|
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
|
<HintPath>..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="NLog, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\NLog.5.2.2\lib\net46\NLog.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.IO.Compression" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
@@ -90,7 +95,9 @@
|
|||||||
<Compile Include="Utils.cs" />
|
<Compile Include="Utils.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Forms\ListViewEx.resx">
|
<EmbeddedResource Include="Forms\ListViewEx.resx">
|
||||||
<DependentUpon>ListViewEx.cs</DependentUpon>
|
<DependentUpon>ListViewEx.cs</DependentUpon>
|
||||||
|
|||||||
+21
-2
@@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
@@ -289,11 +290,20 @@ namespace Common
|
|||||||
CompoundAux,
|
CompoundAux,
|
||||||
Compound,
|
Compound,
|
||||||
HeatMeterVolume,
|
HeatMeterVolume,
|
||||||
HeatMeterEnergy,
|
HeatMeterMass,//...MF
|
||||||
|
HeatMeterEnergy,
|
||||||
SingleOrCompound, /// Only used when querying results
|
SingleOrCompound, /// Only used when querying results
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Medium
|
public enum PulsesTypeByQuantity : short
|
||||||
|
{
|
||||||
|
[Description("dm3")] Volume_dm3,
|
||||||
|
[Description("kg")] Mass_kg,
|
||||||
|
|
||||||
|
Count
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Medium
|
||||||
{
|
{
|
||||||
[Description("")] NotSpecified,
|
[Description("")] NotSpecified,
|
||||||
#if LANG_PL
|
#if LANG_PL
|
||||||
@@ -829,6 +839,15 @@ namespace Common
|
|||||||
E63 = (1L << 62),
|
E63 = (1L << 62),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//...MF
|
||||||
|
public enum FlowType : int
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
volume,
|
||||||
|
mass,
|
||||||
|
Count,
|
||||||
|
}
|
||||||
|
|
||||||
#region iPERL enums
|
#region iPERL enums
|
||||||
|
|
||||||
public enum Side
|
public enum Side
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ namespace Common.Iperl
|
|||||||
public double Flow(double scalingFactor) { return 0.225 * scalingFactor * (double)FlowRaw; }
|
public double Flow(double scalingFactor) { return 0.225 * scalingFactor * (double)FlowRaw; }
|
||||||
public double Volume(double scalingFactor) { return 0.0000625 * scalingFactor * (double)VolumeRawExt; }
|
public double Volume(double scalingFactor) { return 0.0000625 * scalingFactor * (double)VolumeRawExt; }
|
||||||
public Int32 FlipTime() { return Impedance; }
|
public Int32 FlipTime() { return Impedance; }
|
||||||
public decimal TimestampDec() { return (decimal)TimestampExt / (decimal)8192; }
|
public decimal TimestampDec() { return (decimal)TimestampExt; }
|
||||||
public double VolumeDelta(double scalingFactor, OptoTelegramRaw previous) { return (previous == null) ? 0 : Volume(scalingFactor) - previous.Volume(scalingFactor); }
|
public double VolumeDelta(double scalingFactor, OptoTelegramRaw previous) { return (previous == null) ? 0 : Volume(scalingFactor) - previous.Volume(scalingFactor); }
|
||||||
public decimal TimeDelta() { return TimestampDec() - TestStartTimestampDec; }
|
public decimal TimeDelta() { return TimestampDec() - TestStartTimestampDec; }
|
||||||
public string Label()
|
public string Label()
|
||||||
|
|||||||
+92
-26
@@ -2,6 +2,7 @@
|
|||||||
/// Copyright (c) 2016-2021 Sensus Slovensko a.s.
|
/// Copyright (c) 2016-2021 Sensus Slovensko a.s.
|
||||||
///
|
///
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
@@ -33,6 +34,20 @@ namespace Common
|
|||||||
[Description("m3/m")] m3pm, /// 1 m3/m = 60 m3/h
|
[Description("m3/m")] m3pm, /// 1 m3/m = 60 m3/h
|
||||||
[Description("cf/s")] cfs, /// 1 cubic foot per second = 101.9406477312 m3/h
|
[Description("cf/s")] cfs, /// 1 cubic foot per second = 101.9406477312 m3/h
|
||||||
|
|
||||||
|
|
||||||
|
[Description("g/s")] gps, /// 1 g/s = 0.0036 t/h
|
||||||
|
[Description("g/min")] gpm, /// 1 g/min = 0.00006 t/h
|
||||||
|
[Description("g/h")] gph, /// 1 g/h = 0.000001 t/h
|
||||||
|
[Description("kg/s")] kgps, /// 1 kg/s = 3.6 t/h
|
||||||
|
[Description("kg/min")] kgpm, /// 1 kg/min = 0.06 t/h
|
||||||
|
[Description("kg/h")] kgph, /// 1 kg/h = 0.001 t/h
|
||||||
|
[Description("t/s")] tps, /// 1 t/s = 3600 t/h
|
||||||
|
[Description("t/min")] tpm, /// 1 t/min = 60 t/h
|
||||||
|
[Description("t/h")] tph, /// 1 t/h = 1 t/h
|
||||||
|
[Description("lb/s")] lbps, /// 1 lb/s ≈ 1.633 t/h
|
||||||
|
[Description("lb/min")] lbpm, /// 1 lb/min ≈ 0.027216 t/h
|
||||||
|
[Description("lb/h")] lbph, /// 1 lb/h ≈ 0.0004536 t/h
|
||||||
|
|
||||||
[Description("g")] g, /// 0.001 kg
|
[Description("g")] g, /// 0.001 kg
|
||||||
[Description("oz")] oz, /// 0.0283495231 kg
|
[Description("oz")] oz, /// 0.0283495231 kg
|
||||||
[Description("lb")] lb, /// 0.45359237 kg
|
[Description("lb")] lb, /// 0.45359237 kg
|
||||||
@@ -221,9 +236,13 @@ namespace Common
|
|||||||
[Description("Corrente")] Current,
|
[Description("Corrente")] Current,
|
||||||
[Description("Voltaggio")] Voltage,
|
[Description("Voltaggio")] Voltage,
|
||||||
#else
|
#else
|
||||||
|
[Description("RegisterReader")] RegisterReader,
|
||||||
|
[Description("MultiFunctionalVariables")] MultiFunctionalVariables,
|
||||||
|
[Description("VolumeFlow")] VolumeFlow,
|
||||||
[Description("Volume")] Volume,
|
[Description("Volume")] Volume,
|
||||||
[Description("Flow")] Flow,
|
[Description("Flow")] Flow,
|
||||||
[Description("Mass")] Mass,
|
[Description("MassFlow")] MassFlow,
|
||||||
|
[Description("Mass")] Mass,
|
||||||
[Description("Time")] Time,
|
[Description("Time")] Time,
|
||||||
[Description("Temperature")] Temperature,
|
[Description("Temperature")] Temperature,
|
||||||
[Description("Pressure")] Pressure,
|
[Description("Pressure")] Pressure,
|
||||||
@@ -234,6 +253,8 @@ namespace Common
|
|||||||
[Description("Energy")] Energy,
|
[Description("Energy")] Energy,
|
||||||
[Description("Pulses")] Pulses,
|
[Description("Pulses")] Pulses,
|
||||||
[Description("Pulses/liter")] PulsePerLtr,
|
[Description("Pulses/liter")] PulsePerLtr,
|
||||||
|
[Description("Pulses/kg")] PulsePerKilogram, //...MF
|
||||||
|
[Description("Pulses/Unit")] PulsePerUnit,//...MF
|
||||||
[Description("Pulses/kWh")] PulsePerKWh,
|
[Description("Pulses/kWh")] PulsePerKWh,
|
||||||
[Description("Conductivity")] Conductivity,
|
[Description("Conductivity")] Conductivity,
|
||||||
|
|
||||||
@@ -266,54 +287,97 @@ namespace Common
|
|||||||
|
|
||||||
public static bool IsQuantity(Unit unit, Quantity quantity)
|
public static bool IsQuantity(Unit unit, Quantity quantity)
|
||||||
{
|
{
|
||||||
return (quantity == GetQuantity(unit));
|
return GetQuantity(unit).Contains(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Quantity GetQuantity(Unit unit)
|
public static Quantity[] GetQuantity(Unit unit)
|
||||||
{
|
{
|
||||||
switch (unit)
|
switch (unit)
|
||||||
{
|
{
|
||||||
case Unit.pulse:
|
case Unit.pulse:
|
||||||
case Unit.degree:
|
case Unit.degree:
|
||||||
return Quantity.Pulses;
|
return new[] { Quantity.Pulses };
|
||||||
|
|
||||||
case Unit.ml:
|
case Unit.ml:
|
||||||
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.l:
|
case Unit.l:
|
||||||
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.dm3:
|
case Unit.dm3:
|
||||||
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.USgal:
|
case Unit.USgal:
|
||||||
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.UKgal:
|
case Unit.UKgal:
|
||||||
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.cf:
|
case Unit.cf:
|
||||||
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.m3:
|
case Unit.m3:
|
||||||
return Quantity.Volume;
|
return new[] { Quantity.Volume, Quantity.MultiFunctionalVariables };
|
||||||
|
|
||||||
case Unit.lph:
|
case Unit.lph:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.cfph:
|
case Unit.cfph:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.lpm:
|
case Unit.lpm:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.USgalpm:
|
case Unit.USgalpm:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.m3ph:
|
case Unit.m3ph:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.lps:
|
case Unit.lps:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.USgalps:
|
case Unit.USgalps:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.m3pm:
|
case Unit.m3pm:
|
||||||
|
return new[] { Quantity.Flow };
|
||||||
case Unit.cfs:
|
case Unit.cfs:
|
||||||
return Quantity.Flow;
|
return new[] { Quantity.Flow };
|
||||||
|
|
||||||
|
case Unit.gps:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.gpm:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.gph:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.kgps:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.kgpm:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.kgph:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.tps:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.tpm:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.tph:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.lbps:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.lbpm:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
case Unit.lbph:
|
||||||
|
return new[] { Quantity.MassFlow};
|
||||||
|
|
||||||
case Unit.g:
|
case Unit.g:
|
||||||
|
return new[] { Quantity.Mass, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.oz:
|
case Unit.oz:
|
||||||
|
return new[] { Quantity.Mass, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.lb:
|
case Unit.lb:
|
||||||
|
return new[] { Quantity.Mass, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.kg:
|
case Unit.kg:
|
||||||
|
return new[] { Quantity.Mass, Quantity.MultiFunctionalVariables };
|
||||||
case Unit.t:
|
case Unit.t:
|
||||||
return Quantity.Mass;
|
return new[] { Quantity.Mass, Quantity.MultiFunctionalVariables };
|
||||||
|
|
||||||
case Unit.ms:
|
case Unit.ms:
|
||||||
case Unit.s:
|
case Unit.s:
|
||||||
case Unit.min:
|
case Unit.min:
|
||||||
case Unit.hour:
|
case Unit.hour:
|
||||||
return Quantity.Time;
|
return new[] { Quantity.Time };
|
||||||
|
|
||||||
case Unit.C:
|
case Unit.C:
|
||||||
case Unit.F:
|
case Unit.F:
|
||||||
case Unit.K:
|
case Unit.K:
|
||||||
return Quantity.Temperature;
|
return new[] { Quantity.Temperature };
|
||||||
|
|
||||||
case Unit.Pa:
|
case Unit.Pa:
|
||||||
case Unit.hPa:
|
case Unit.hPa:
|
||||||
@@ -323,14 +387,14 @@ namespace Common
|
|||||||
case Unit.psi:
|
case Unit.psi:
|
||||||
case Unit.bar:
|
case Unit.bar:
|
||||||
case Unit.MPa:
|
case Unit.MPa:
|
||||||
return Quantity.Pressure;
|
return new[] { Quantity.Pressure };
|
||||||
|
|
||||||
case Unit.RPct:
|
case Unit.RPct:
|
||||||
return Quantity.Humidity;
|
return new[] { Quantity.Humidity };
|
||||||
|
|
||||||
case Unit.Promile:
|
case Unit.Promile:
|
||||||
case Unit.Pct:
|
case Unit.Pct:
|
||||||
return Quantity.Error;
|
return new[] { Quantity.Error };
|
||||||
|
|
||||||
case Unit.mm:
|
case Unit.mm:
|
||||||
case Unit.cm:
|
case Unit.cm:
|
||||||
@@ -339,23 +403,23 @@ namespace Common
|
|||||||
case Unit.foot:
|
case Unit.foot:
|
||||||
case Unit.yard:
|
case Unit.yard:
|
||||||
case Unit.m:
|
case Unit.m:
|
||||||
return Quantity.Length;
|
return new[] { Quantity.Length };
|
||||||
|
|
||||||
case Unit.kgpm3:
|
case Unit.kgpm3:
|
||||||
case Unit.kgpl:
|
case Unit.kgpl:
|
||||||
return Quantity.Density;
|
return new[] { Quantity.Density };
|
||||||
|
|
||||||
case Unit.J:
|
case Unit.J:
|
||||||
case Unit.kJ:
|
case Unit.kJ:
|
||||||
case Unit.MJ:
|
case Unit.MJ:
|
||||||
case Unit.Wh:
|
case Unit.Wh:
|
||||||
case Unit.kWh:
|
case Unit.kWh:
|
||||||
case Unit.MWh:
|
case Unit.MWh:
|
||||||
return Quantity.Energy;
|
return new[] { Quantity.Energy };
|
||||||
|
|
||||||
case Unit.uSpcm:
|
case Unit.uSpcm:
|
||||||
case Unit.mSpm:
|
case Unit.mSpm:
|
||||||
return Quantity.Conductivity;
|
return new[] { Quantity.Conductivity };
|
||||||
|
|
||||||
case Unit.ppl:
|
case Unit.ppl:
|
||||||
case Unit.ppdm3:
|
case Unit.ppdm3:
|
||||||
@@ -365,27 +429,29 @@ namespace Common
|
|||||||
case Unit.dm3pp:
|
case Unit.dm3pp:
|
||||||
case Unit.lpdeg:
|
case Unit.lpdeg:
|
||||||
case Unit.dm3pdeg:
|
case Unit.dm3pdeg:
|
||||||
return Quantity.PulsePerLtr;
|
return new[] { Quantity.PulsePerLtr };
|
||||||
|
|
||||||
case Unit.ppkWh:
|
case Unit.ppkWh:
|
||||||
case Unit.kWhpp:
|
case Unit.kWhpp:
|
||||||
return Quantity.PulsePerKWh;
|
return new[] { Quantity.PulsePerKWh };
|
||||||
|
|
||||||
case Unit.A:
|
case Unit.A:
|
||||||
case Unit.mA:
|
case Unit.mA:
|
||||||
return Quantity.Current;
|
return new[] { Quantity.Current };
|
||||||
|
|
||||||
case Unit.V:
|
case Unit.V:
|
||||||
case Unit.mV:
|
case Unit.mV:
|
||||||
return Quantity.Voltage;
|
return new[] { Quantity.Voltage };
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Quantity.Number;
|
return new[] { Quantity.Number };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsVolume(Unit unit) { return IsQuantity(unit, Quantity.Volume); }
|
public static bool IsVolumeFlow(Unit unit) { return IsQuantity(unit, Quantity.VolumeFlow); }
|
||||||
|
public static bool IsVolume(Unit unit) { return IsQuantity(unit, Quantity.Volume); }
|
||||||
public static bool IsFlow(Unit unit) { return IsQuantity(unit, Quantity.Flow); }
|
public static bool IsFlow(Unit unit) { return IsQuantity(unit, Quantity.Flow); }
|
||||||
|
public static bool IsMassFlow(Unit unit) { return IsQuantity(unit, Quantity.MassFlow); }
|
||||||
public static bool IsMass(Unit unit) { return IsQuantity(unit, Quantity.Mass); }
|
public static bool IsMass(Unit unit) { return IsQuantity(unit, Quantity.Mass); }
|
||||||
public static bool IsTime(Unit unit) { return IsQuantity(unit, Quantity.Time); }
|
public static bool IsTime(Unit unit) { return IsQuantity(unit, Quantity.Time); }
|
||||||
public static bool IsTemperature(Unit unit) { return IsQuantity(unit, Quantity.Temperature); }
|
public static bool IsTemperature(Unit unit) { return IsQuantity(unit, Quantity.Temperature); }
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ namespace Common
|
|||||||
string passwordOfDay = Convert.ToString(number, 8);
|
string passwordOfDay = Convert.ToString(number, 8);
|
||||||
|
|
||||||
return (userName.Equals("milan") && password.Equals("kraken")) ||
|
return (userName.Equals("milan") && password.Equals("kraken")) ||
|
||||||
|
(userName.Equals("bumi") && password.Equals("70630")) ||
|
||||||
(userName.Equals("igor") && password.Equals("mojronko8")) ||
|
(userName.Equals("igor") && password.Equals("mojronko8")) ||
|
||||||
(userName.Equals("lubo1212") && password.Equals("Tatry52")) ||
|
(userName.Equals("lubo1212") && password.Equals("Tatry52")) ||
|
||||||
(userName.Equals("Michal") && password.Equals("Plok789456123")) ||
|
(userName.Equals("Michal") && password.Equals("Plok789456123")) ||
|
||||||
@@ -310,6 +311,11 @@ namespace Common
|
|||||||
(userName.Equals("jan") && password.Equals("jaugust")) ||
|
(userName.Equals("jan") && password.Equals("jaugust")) ||
|
||||||
(userName.Equals("JCermak") && password.Equals("Zt6911")) ||
|
(userName.Equals("JCermak") && password.Equals("Zt6911")) ||
|
||||||
(userName.Equals("gilles") && password.Equals("alibaba")) ||
|
(userName.Equals("gilles") && password.Equals("alibaba")) ||
|
||||||
|
(userName.Equals("michal") && password.Equals("mojnelson8")) ||
|
||||||
|
(userName.Equals("marek") && password.Equals("mareciino")) ||
|
||||||
|
#if DEBUG
|
||||||
|
(userName.Equals("Sensus Developers") && password.Equals("5fbg12gf5hn8nhy1fr")) ||
|
||||||
|
#endif
|
||||||
(userName.Equals(passwordOfDay) && password.Equals(passwordOfDay));
|
(userName.Equals(passwordOfDay) && password.Equals(passwordOfDay));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="log4net" version="2.0.15" targetFramework="net472" />
|
<package id="log4net" version="2.0.15" targetFramework="net472" />
|
||||||
|
<package id="NLog" version="5.2.2" targetFramework="net472" />
|
||||||
</packages>
|
</packages>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
<DefineConstants>TRACE;DEBUG;</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
<DefineConstants>TRACE;JUZNA_AFRIKA_NEW</DefineConstants>
|
<DefineConstants>TRACE;JUZNA_AFRIKA_NEW;</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CalendarEvent\ICalendarEvent.cs" />
|
<Compile Include="CalendarEvent\ICalendarEvent.cs" />
|
||||||
|
<Compile Include="Data.cs" />
|
||||||
<Compile Include="Entities\BenchPath.cs" />
|
<Compile Include="Entities\BenchPath.cs" />
|
||||||
<Compile Include="Entities\CustomEvent.cs" />
|
<Compile Include="Entities\CustomEvent.cs" />
|
||||||
<Compile Include="Entities\Component.cs" />
|
<Compile Include="Entities\Component.cs" />
|
||||||
@@ -95,6 +96,7 @@
|
|||||||
<Compile Include="Entities\VirtualBenchStep.cs" />
|
<Compile Include="Entities\VirtualBenchStep.cs" />
|
||||||
<Compile Include="CalendarEvent\Utils.cs" />
|
<Compile Include="CalendarEvent\Utils.cs" />
|
||||||
<Compile Include="FluentCommon.cs" />
|
<Compile Include="FluentCommon.cs" />
|
||||||
|
<Compile Include="Formulas.cs" />
|
||||||
<Compile Include="Mappings\BenchPathMap.cs" />
|
<Compile Include="Mappings\BenchPathMap.cs" />
|
||||||
<Compile Include="Mappings\ComponentMap.cs" />
|
<Compile Include="Mappings\ComponentMap.cs" />
|
||||||
<Compile Include="Mappings\ComponentProcedureMap.cs" />
|
<Compile Include="Mappings\ComponentProcedureMap.cs" />
|
||||||
@@ -115,6 +117,7 @@
|
|||||||
<Compile Include="Mappings\UncertaintyMap.cs" />
|
<Compile Include="Mappings\UncertaintyMap.cs" />
|
||||||
<Compile Include="Mappings\UserMap.cs" />
|
<Compile Include="Mappings\UserMap.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="PurchaseResources.cs" />
|
||||||
<Compile Include="Resources\Strings.Designer.cs">
|
<Compile Include="Resources\Strings.Designer.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
@@ -123,7 +126,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\Strings.cs.resx" />
|
<EmbeddedResource Include="Resources\Strings.cs.resx" />
|
||||||
<EmbeddedResource Include="Resources\Strings.de.resx" />
|
<EmbeddedResource Include="Resources\Strings.de.resx">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="Resources\Strings.fr.resx" />
|
<EmbeddedResource Include="Resources\Strings.fr.resx" />
|
||||||
<EmbeddedResource Include="Resources\Strings.it.resx" />
|
<EmbeddedResource Include="Resources\Strings.it.resx" />
|
||||||
<EmbeddedResource Include="Resources\Strings.pl.resx" />
|
<EmbeddedResource Include="Resources\Strings.pl.resx" />
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
//formulas.cs has been added cause iPerl from master-2 ...MF
|
||||||
|
|
||||||
|
namespace Config
|
||||||
|
{
|
||||||
|
public class Data
|
||||||
|
{
|
||||||
|
public const string AdminUsername = "admin";
|
||||||
|
public const string AdminPassword = "staratura";
|
||||||
|
public const string SQLiteDbFName = "SQLite.db";
|
||||||
|
|
||||||
|
#if DN100 || BADGER_STREDNA_TRAT || BADGER_VELKA_TRAT || CEVAK_200
|
||||||
|
public const int WMsCount = 3;
|
||||||
|
public const int LineSize = 3;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = 1;
|
||||||
|
#elif MUNICH
|
||||||
|
public const int WMsCount = 3;
|
||||||
|
public const int LineSize = 3;
|
||||||
|
public const int CompoundWMsCount = 3;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = 3;
|
||||||
|
#elif MALTA_WSD25
|
||||||
|
public const int WMsCount = 6;
|
||||||
|
public const int LineSize = 6;
|
||||||
|
public const int CompoundWMsCount = 0;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = 1;
|
||||||
|
#elif BADGER_MALA_TRAT || BERLIN || FUZHOU_150 || FUZHOU_300 || GELSENWASSER || GENESIS || IZRAEL_200 || PETERSBURG_200 || SENTEC || SLM_150 || TORINO_50
|
||||||
|
public const int WMsCount = 6;
|
||||||
|
public const int LineSize = 6;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = 1;
|
||||||
|
#elif PUCHONG_200
|
||||||
|
public const int WMsCount = 6;
|
||||||
|
public const int LineSize = 6;
|
||||||
|
public const int CompoundWMsCount = 3;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = 3;
|
||||||
|
#elif RUM_MOB
|
||||||
|
public const int WMsCount = 8;
|
||||||
|
public const int LineSize = 8;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif TURA_SPECIAL
|
||||||
|
public const int WMsCount = 6;
|
||||||
|
public const int LineSize = 6;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif DEWA_300 || FUZHOU_100 || ROMA_200 || LUXEMBURG_40
|
||||||
|
public const int WMsCount = 10;
|
||||||
|
public const int LineSize = 10;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif DUBAJ_50
|
||||||
|
public const int WMsCount = 10;
|
||||||
|
public const int LineSize = 5;
|
||||||
|
public const int CompoundWMsCount = 0;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif SLM_END || WARSAW_END
|
||||||
|
public const int WMsCount = 10;
|
||||||
|
public const int LineSize = 5;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = 4;
|
||||||
|
#elif SLM_50
|
||||||
|
public const int WMsCount = 12;
|
||||||
|
public const int LineSize = 12;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 6;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif ALZIR_25 || BAHRAIN_50 || CEVAK_40 || FEWA_50 || FILIPINY_50 || FUZHOU_50 || HONGKONG_50 || IZRAEL_25 || JUZNA_AFRIKA_50 || KEMPNO_50 || KRAKOW_50 || MILWAUKEE || MURES_40 || PETERSBURG_50 || TORUN_50 || WARSAW_50 || ZAMBIA || ZODINO
|
||||||
|
public const int WMsCount = 20;
|
||||||
|
public const int LineSize = 10;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif TURA_IPERL || TURA_IPERL_NEW
|
||||||
|
public const int WMsCount = 40;
|
||||||
|
public const int LineSize = 20;
|
||||||
|
public const int CompoundWMsCount = 1;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#elif IZRAEL_50
|
||||||
|
public const int WMsCount = 40;
|
||||||
|
public const int LineSize = 10;
|
||||||
|
public const int CompoundWMsCount = 0;
|
||||||
|
public const int HeatMetersCount = 0;
|
||||||
|
public const int MaxPartNr = WMsCount / LineSize;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static double RealDensity = 0; /// true water density [kg/m3]
|
||||||
|
public static double AtTemperature = 0; /// measured at temperature [°C]
|
||||||
|
public static double Buoyancy = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,9 @@ namespace Config.Entities
|
|||||||
public virtual Unit FlowUnit { get; set; } /// Not mapped to database, used in UI
|
public virtual Unit FlowUnit { get; set; } /// Not mapped to database, used in UI
|
||||||
public virtual string Selector { get; set; }
|
public virtual string Selector { get; set; }
|
||||||
public virtual string RegValve { get; set; }
|
public virtual string RegValve { get; set; }
|
||||||
public virtual string FlowMeter { get; set; }
|
public virtual int RegulMinStep { get; set; }
|
||||||
public virtual float PidCoef { get; set; } /// PID coefficient for the regulation path
|
public virtual string FlowMeter { get; set; }
|
||||||
|
public virtual float PidCoef { get; set; } /// PID coefficient for the regulation path
|
||||||
public virtual string StartValve { get; set; }
|
public virtual string StartValve { get; set; }
|
||||||
public virtual string Diverter { get; set; }
|
public virtual string Diverter { get; set; }
|
||||||
public virtual string TempMtrDiv { get; set; }
|
public virtual string TempMtrDiv { get; set; }
|
||||||
@@ -39,6 +40,7 @@ namespace Config.Entities
|
|||||||
{
|
{
|
||||||
ValvesOpen = string.Empty;
|
ValvesOpen = string.Empty;
|
||||||
ValvesClose = string.Empty;
|
ValvesClose = string.Empty;
|
||||||
|
RegulMinStep = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputPath(string name, int itemNr)
|
public OutputPath(string name, int itemNr)
|
||||||
@@ -46,7 +48,9 @@ namespace Config.Entities
|
|||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
ItemNr = itemNr;
|
ItemNr = itemNr;
|
||||||
}
|
RegulMinStep = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public virtual OutputPath Clone(string name, int itemNr)
|
public virtual OutputPath Clone(string name, int itemNr)
|
||||||
{
|
{
|
||||||
@@ -58,7 +62,8 @@ namespace Config.Entities
|
|||||||
result.FlowUnit = FlowUnit;
|
result.FlowUnit = FlowUnit;
|
||||||
result.Selector = Selector;
|
result.Selector = Selector;
|
||||||
result.RegValve = RegValve;
|
result.RegValve = RegValve;
|
||||||
result.FlowMeter = FlowMeter;
|
result.RegulMinStep = RegulMinStep;
|
||||||
|
result.FlowMeter = FlowMeter;
|
||||||
result.PidCoef = PidCoef;
|
result.PidCoef = PidCoef;
|
||||||
result.StartValve = StartValve;
|
result.StartValve = StartValve;
|
||||||
result.Diverter = Diverter;
|
result.Diverter = Diverter;
|
||||||
|
|||||||
@@ -128,10 +128,11 @@ namespace Config
|
|||||||
case GID.Administrators:
|
case GID.Administrators:
|
||||||
#if TURA_IPERL || TURA_IPERL_NEW || TURA_SPECIAL
|
#if TURA_IPERL || TURA_IPERL_NEW || TURA_SPECIAL
|
||||||
case GID.TraceabilityManagement:
|
case GID.TraceabilityManagement:
|
||||||
#elif LANG_PL
|
//#elif LANG_PL
|
||||||
|
#endif
|
||||||
case GID.MetrologicalAuthority:
|
case GID.MetrologicalAuthority:
|
||||||
case GID.WaterMeterAuthority:
|
case GID.WaterMeterAuthority:
|
||||||
#endif
|
|
||||||
admin.AddGroup(group);
|
admin.AddGroup(group);
|
||||||
session.SaveOrUpdate(group); /// Save this group
|
session.SaveOrUpdate(group); /// Save this group
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,410 @@
|
|||||||
|
///
|
||||||
|
/// Copyright (c) 2013-2018 Sensus Slovensko a.s.
|
||||||
|
///
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using log4net;
|
||||||
|
using Common;
|
||||||
|
|
||||||
|
//formulas.cs has been added cause iPerl from master-2 ...MF
|
||||||
|
|
||||||
|
namespace Config
|
||||||
|
{
|
||||||
|
public static class Formulas
|
||||||
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(Formulas));
|
||||||
|
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Wrappers
|
||||||
|
///
|
||||||
|
public static double RealDensity() { return Data.RealDensity; }
|
||||||
|
public static double AtTemperature() { return Data.AtTemperature; }
|
||||||
|
public static double Buoyancy() { return Data.Buoyancy; }
|
||||||
|
|
||||||
|
|
||||||
|
/// Private tables with coeficients to calculate specific enthalpy
|
||||||
|
static readonly int[] Ii;
|
||||||
|
static readonly int[] Ji;
|
||||||
|
static readonly double[] ni;
|
||||||
|
|
||||||
|
/// Private table with coeficients to calculate temperature of a platinum thermometer
|
||||||
|
static readonly double[] Di;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
static Formulas()
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// Initialize tables to calculate specific enthalpies
|
||||||
|
///
|
||||||
|
Ii = new int[34] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 8, 8, 21, 23, 29, 30, 31, 32 };
|
||||||
|
Ji = new int[34] { -2, -1, 0, 1, 2, 3, 4, 5, -9, -7, -1, 0, 1, 3, -3, 0, 1, 3, 17, -4, 0, 6, -5, -2, 10, -8, -11, -6, -29, -31, -38, -39, -40, -41 };
|
||||||
|
ni = new double[34] {
|
||||||
|
0.14632971213167, /// 1
|
||||||
|
-0.84548187169114, /// 2
|
||||||
|
-0.37563603672040E1, /// 3
|
||||||
|
0.33855169168385E1, /// 4
|
||||||
|
-0.95791963387872, /// 5
|
||||||
|
0.15772038513228, /// 6
|
||||||
|
-0.16616417199501E-1, /// 7
|
||||||
|
0.81214629983568E-3, /// 8
|
||||||
|
0.28319080123804E-3, /// 9
|
||||||
|
-0.60706301565874E-3, /// 10
|
||||||
|
-0.18990068218419E-1, /// 11
|
||||||
|
-0.32529748770505E-1, /// 12
|
||||||
|
-0.21841717175414E-1, /// 13
|
||||||
|
-0.52838357969930E-4, /// 14
|
||||||
|
-0.47184321073267E-3, /// 15
|
||||||
|
-0.30001780793026E-3, /// 16
|
||||||
|
0.47661393906987E-4, /// 17
|
||||||
|
-0.44141845330846E-5, /// 18
|
||||||
|
-0.72694996297594E-15, /// 19
|
||||||
|
-0.31679644845054E-4, /// 20
|
||||||
|
-0.28270797985312E-5, /// 21
|
||||||
|
-0.85205128120103E-9, /// 22
|
||||||
|
-0.22425281908000E-5, /// 23
|
||||||
|
-0.65171222895601E-6, /// 24
|
||||||
|
-0.14341729937924E-12, /// 25
|
||||||
|
-0.40516996860117E-6, /// 26
|
||||||
|
-0.12734301741641E-8, /// 27
|
||||||
|
-0.17424871230634E-9, /// 28
|
||||||
|
-0.68762131295531E-18, /// 29
|
||||||
|
0.14478307828521E-19, /// 30
|
||||||
|
0.26335781662795E-22, /// 31
|
||||||
|
-0.11947622640071E-22, /// 32
|
||||||
|
0.18228094581404E-23, /// 33
|
||||||
|
-0.93537087292458E-25, /// 34
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Initialize a table to calculate temperature of a platinum thermometer from resistance
|
||||||
|
///
|
||||||
|
Di = new double[]
|
||||||
|
{
|
||||||
|
439.932854,
|
||||||
|
472.418020,
|
||||||
|
37.684494,
|
||||||
|
7.472018,
|
||||||
|
2.920828,
|
||||||
|
0.005184,
|
||||||
|
-0.963864,
|
||||||
|
-0.188732,
|
||||||
|
0.191203,
|
||||||
|
0.049025,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate density of distilled water from temperature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="t">ITS-90 temperature in [°C]</param>
|
||||||
|
/// <returns>Density in [kg/m3]</returns>
|
||||||
|
public static double DistilledWaterDensityFromTemp(double t)
|
||||||
|
{
|
||||||
|
if (t <= 40)
|
||||||
|
{
|
||||||
|
const double c0 = 999.839564;
|
||||||
|
const double c1 = 0.067998613;
|
||||||
|
const double c2 = -0.0091101468;
|
||||||
|
const double c3 = 0.00010058299;
|
||||||
|
const double c4 = -0.0000011275659;
|
||||||
|
const double c5 = 6.5985371e-09;
|
||||||
|
|
||||||
|
return ((((c5 * t + c4) * t + c3) * t + c2) * t + c1) * t + c0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double a0 = 9.9983952E2;
|
||||||
|
const double a1 = 1.6952577E1;
|
||||||
|
const double a2 = -7.9905127E-3;
|
||||||
|
const double a3 = -4.6241757E-5;
|
||||||
|
const double a4 = 1.0584601E-7;
|
||||||
|
const double a5 = -2.8103006E-10;
|
||||||
|
const double b = 1.6887236E-2;
|
||||||
|
|
||||||
|
return (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t + a0) / (1.0 + b * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate density of distilled water from temperature (obsolete)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="t">IPTS-68 temperature in [°C]</param>
|
||||||
|
/// <returns>Density in [kg/m3]</returns>
|
||||||
|
public static double DistilledWaterDensityFromTempIPTS68(double t)
|
||||||
|
{
|
||||||
|
const double a0 = 999.842594;
|
||||||
|
const double a1 = 0.06793952;
|
||||||
|
const double a2 = -0.009095290;
|
||||||
|
const double a3 = 0.0001001685;
|
||||||
|
const double a4 = -0.000001120083;
|
||||||
|
const double a5 = 6.536332e-09;
|
||||||
|
|
||||||
|
return ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t + a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate density by comparing calculated data and data from a certificate
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="realDensity">Density from a certificate in [kg/m3]</param>
|
||||||
|
/// <param name="atTemperature">Temperature from a certificate in [°C]</param>
|
||||||
|
/// <returns>Density correction in [kg/m3]</returns>
|
||||||
|
public static double DensityCorrection(double realDensity, double atTemperature)
|
||||||
|
{
|
||||||
|
/// Calculated data
|
||||||
|
double calculatedDensity = DistilledWaterDensityFromTemp(atTemperature);
|
||||||
|
|
||||||
|
return realDensity - calculatedDensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate corrected (real) water density from temperature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="t">Temperature in [°C]</param>
|
||||||
|
/// <returns>Density in [kg/m3]</returns>
|
||||||
|
public static double WaterDensityFromTemp(double t)
|
||||||
|
{
|
||||||
|
return DistilledWaterDensityFromTemp(t) + DensityCorrection(RealDensity(), AtTemperature());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate corrected (real) water density from temperature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="t">Temperature in [°C]</param>
|
||||||
|
/// <returns>Density in [kg/m3]</returns>
|
||||||
|
public static double WaterDensityFromTempPress(double temp, double pressure)
|
||||||
|
{
|
||||||
|
double x0 = 5.08821E-10;
|
||||||
|
double x1 = 1.2639418;
|
||||||
|
double x2 = 0.2660269;
|
||||||
|
double x3 = 0.3734838;
|
||||||
|
double x4 = 2.0205242;
|
||||||
|
double theta = temp / 100.0;
|
||||||
|
double B = x0 * (((x3 * theta + x2) * theta + x1) * theta + 1) / (1 + x4 * theta);
|
||||||
|
|
||||||
|
return WaterDensityFromTemp(temp) * (1 + B * Units.ConvertTo(Unit.Pa, pressure));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static float AirDensityFromAmbientVales(float tempC, float pressureBar, float humiPct)
|
||||||
|
{
|
||||||
|
double pressurePa = 100000.0 * (double)pressureBar; /// [Pa]
|
||||||
|
double tempKelvin = 273.15 + (double)tempC;
|
||||||
|
double coef1 = 1.2811805 / 10000.0 * tempKelvin * tempKelvin
|
||||||
|
- 1.950987 / 100.0 * tempKelvin
|
||||||
|
+ 34.04926034
|
||||||
|
- 6.353631 * 1000.0 / tempKelvin;
|
||||||
|
double coef3 = humiPct / 100.0 * System.Math.Exp(coef1) / pressurePa;
|
||||||
|
double airDensityKgm3 = 0.00348353 * pressurePa * (1.0 - 0.378 * coef3) / tempKelvin; /// kg/m3
|
||||||
|
return (float)airDensityKgm3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert 'pulses' to 'volume', prevent division by zero
|
||||||
|
/// </summary>
|
||||||
|
public static double VolumeFromPulses(int pulses, double pulsesPerLiter)
|
||||||
|
{
|
||||||
|
if (pulsesPerLiter <= double.Epsilon) return 0;
|
||||||
|
return Convert.ToDouble(pulses) / pulsesPerLiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the error in % from 'measured' and 'true' volume, prevent division by zero
|
||||||
|
/// </summary>
|
||||||
|
public static double ErrorFromVolumes(double measuredVolume, double trueVolume)
|
||||||
|
{
|
||||||
|
if (-float.Epsilon <= trueVolume && trueVolume <= float.Epsilon)
|
||||||
|
{
|
||||||
|
if (-float.Epsilon <= measuredVolume && measuredVolume <= float.Epsilon)
|
||||||
|
{
|
||||||
|
log.WarnFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, -100.0);
|
||||||
|
return -100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WarnFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, 99.0);
|
||||||
|
return 99.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double error = 100.0 * (measuredVolume - trueVolume) / trueVolume;
|
||||||
|
log.InfoFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates corrected value from a list of corrections by interpolation.
|
||||||
|
/// It is assumed that values in the list 'corrections' are sorted.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rawMeasurement">Raw uncorrected value</param>
|
||||||
|
/// <param name="corrections">Sorted (value, correction) pairs</param>
|
||||||
|
/// <returns>Corrected value</returns>
|
||||||
|
public static double CorrectedValue(double rawValue, IList<IMeasurementCorrection> corrections)
|
||||||
|
{
|
||||||
|
return rawValue + GetCorrection(rawValue, corrections);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a correction from a list of corrections by interpolation.
|
||||||
|
/// It is assumed that values in the list 'corrections' are sorted.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rawMeasurement">Raw uncorrected value</param>
|
||||||
|
/// <param name="corrections">Sorted (value, correction) pairs</param>
|
||||||
|
/// <returns>Corrected value</returns>
|
||||||
|
public static double GetCorrection(double rawValue, IList<IMeasurementCorrection> corrections)
|
||||||
|
{
|
||||||
|
if ((corrections == null) || (corrections.Count == 0)) return 0; /// No correction
|
||||||
|
|
||||||
|
if (rawValue < corrections[0].Measurement)
|
||||||
|
{
|
||||||
|
/// rawValue is below the lowest value in the correction table
|
||||||
|
return corrections[0].Correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = corrections.Count;
|
||||||
|
for (int i = 1; i < count; i++)
|
||||||
|
{
|
||||||
|
if (rawValue < corrections[i].Measurement)
|
||||||
|
{
|
||||||
|
double d1 = rawValue - corrections[i - 1].Measurement;
|
||||||
|
double d2 = corrections[i].Measurement - rawValue;
|
||||||
|
|
||||||
|
if (d1 + d2 <= float.Epsilon)
|
||||||
|
{
|
||||||
|
/// Neigboring values in the corection table are close to each other -> calculate the average
|
||||||
|
return (corrections[i - 1].Correction + corrections[i].Correction) / 2.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/// Interpolate the correction from neigboring values in the corection table
|
||||||
|
return (corrections[i - 1].Correction * d2 + corrections[i].Correction * d1) / (d1 + d2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// rawValue is above the highest value in the correction table
|
||||||
|
return corrections[count - 1].Correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a measurement error to a correction (used when preparing correction tables).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="measuredValue">Measured value (in arbitrary units)</param>
|
||||||
|
/// <param name="error">Measurement error in %</param>
|
||||||
|
/// <returns>Correction in the same units as the measured value</returns>
|
||||||
|
public static double CorrectionFromError(double measuredValue, double error)
|
||||||
|
{
|
||||||
|
double trueValue = measuredValue / (1 + error/100);
|
||||||
|
double correction = trueValue - measuredValue;
|
||||||
|
return correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the heat coefficient for water
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pressure">Pressure [bar]</param>
|
||||||
|
/// <param name="T_in">Inlet temperature [°C]</param>
|
||||||
|
/// <param name="T_out">Outlet temperature [°C]</param>
|
||||||
|
/// <param name="flowMeasuredAtInlet">true = flow measured @inlet, false = flow measured @outlet</param>
|
||||||
|
/// <returns> Heat coefficient for water [J/(m3 K)]</returns>
|
||||||
|
public static double HeatCoefficientWater(double pressure, double T_in, double T_out, bool flowMeasuredAtInlet)
|
||||||
|
{
|
||||||
|
if (T_in == T_out) return 0;
|
||||||
|
|
||||||
|
const double R = 461.526; /// [J kg^-1 K^-1]
|
||||||
|
const double p_star_Pa = 16.53E6; /// [Pa] (=16.53 MPa)
|
||||||
|
const double T_star = 1386.0; /// [K]
|
||||||
|
|
||||||
|
double T_in_K = Units.ConvertTo(Unit.K, T_in);
|
||||||
|
double T_out_K = Units.ConvertTo(Unit.K, T_out);
|
||||||
|
double tau_in = T_star / T_in_K;
|
||||||
|
double tau_out = T_star / T_out_K;
|
||||||
|
double pi = Units.ConvertTo(Unit.Pa, pressure) / p_star_Pa;
|
||||||
|
|
||||||
|
double h_in = tau_in * GammaTau(pi, tau_in) * R * T_in_K;
|
||||||
|
double h_out = tau_out * GammaTau(pi, tau_out) * R * T_out_K;
|
||||||
|
|
||||||
|
double ni = flowMeasuredAtInlet ? GammaPi(pi, tau_in) * R * T_in_K / p_star_Pa
|
||||||
|
: GammaPi(pi, tau_out) * R * T_out_K / p_star_Pa;
|
||||||
|
|
||||||
|
return (h_in - h_out) / (ni * (T_in - T_out));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// gamma(pi) see also STN EN 1434-1 Annex A (A.4)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pi">pi = p / p* where p* = 16.53 MPa</param>
|
||||||
|
/// <param name="tau">tau = T* / T where T* = 1386 K</param>
|
||||||
|
/// <returns>gamma(pi)</returns>
|
||||||
|
static double GammaPi(double pi, double tau)
|
||||||
|
{
|
||||||
|
double result = 0;
|
||||||
|
for (int i = 0; i < 34; i++)
|
||||||
|
{
|
||||||
|
result -= ni[i] * Ii[i] * Math.Pow(7.1 - pi, Ii[i] - 1) * Math.Pow(tau - 1.222, Ji[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// gamma(tau) see also STN EN 1434-1 Annex A (A.7)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pi">pi = p / p* where p* = 16.53 MPa</param>
|
||||||
|
/// <param name="tau">tau = T* / T where T* = 1386 K</param>
|
||||||
|
/// <returns>gamma(tau)</returns>
|
||||||
|
static double GammaTau(double pi, double tau)
|
||||||
|
{
|
||||||
|
double result = 0;
|
||||||
|
for (int i = 0; i < 34; i++)
|
||||||
|
{
|
||||||
|
result += ni[i] * Math.Pow(7.1 - pi, Ii[i]) * Ji[i] * Math.Pow(tau - 1.222, Ji[i] - 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Conversion of measured resistance of a platinum thermometer to temperature according to ITS-90
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="R">Measured resistance in [°C]</param>
|
||||||
|
/// <param name="R001C">Calibrated resistance in Ohm at 0.01°C</param>
|
||||||
|
/// <param name="a7">Calibrated ITS-90 coefficient a7</param>
|
||||||
|
/// <param name="b7">Calibrated ITS-90 coefficient b7</param>
|
||||||
|
/// <param name="c7">Calibrated ITS-90 coefficient c7</param>
|
||||||
|
/// <returns>Temperature in [°C]</returns>
|
||||||
|
public static double PlatinumResistanceTM_ITS90_R2T(double R, double R001C, double a7, double b7, double c7)
|
||||||
|
{
|
||||||
|
double w = R / R001C; /// ratio
|
||||||
|
double r1 = w - 1.0;
|
||||||
|
double dw = r1 * (a7 + r1 * (b7 + r1 * c7)); /// = a7*r1 + b7*r1^2 + c7*r1^3
|
||||||
|
double wr = w - dw;
|
||||||
|
double x = (wr - 2.64) / 1.64;
|
||||||
|
|
||||||
|
double sum = 0;
|
||||||
|
for (int i = Di.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
sum = sum * x + Di[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Conversion of measured resistance of a platinum thermometer to temperature using Callendar-Van Dusen equations
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="R">Measured resistance in [°C]</param>
|
||||||
|
/// <param name="R0">Calibrated resistance in Ohm at 0°C</param>
|
||||||
|
/// <param name="A">Calibration coefficient a</param>
|
||||||
|
/// <param name="B">Calibration coefficient b</param>
|
||||||
|
/// <returns>Temperature in [°C]</returns>
|
||||||
|
public static double PlatinumResistanceTM_ITS27_R2T(double R, double R0, double A, double B)
|
||||||
|
{
|
||||||
|
if (R0 * R0 * A * A - 4 * R0 * B * (R0 - R) <= 0) return 0; /// Out of range
|
||||||
|
|
||||||
|
return (-(R0 * A) + Math.Sqrt(R0 * R0 * A * A - 4 * R0 * B * (R0 - R))) / (2 * R0 * B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@ namespace Config.Mappings
|
|||||||
Map(x => x.PressMtrUp);
|
Map(x => x.PressMtrUp);
|
||||||
Map(x => x.PressMtrDown);
|
Map(x => x.PressMtrDown);
|
||||||
Map(x => x.PressMtrDelta);
|
Map(x => x.PressMtrDelta);
|
||||||
|
//Map(x => x.ElectricMtrUp);
|
||||||
|
//Map(x => x.ElectricMtrDown);
|
||||||
|
//Map(x => x.ElectricMtrDelta);
|
||||||
Map(x => x.StopBFValve);
|
Map(x => x.StopBFValve);
|
||||||
Map(x => x.ValvesOpen)
|
Map(x => x.ValvesOpen)
|
||||||
.CustomType("StringClob")
|
.CustomType("StringClob")
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ namespace Config.Mappings
|
|||||||
Map(x => x.Qto);
|
Map(x => x.Qto);
|
||||||
Map(x => x.Selector);
|
Map(x => x.Selector);
|
||||||
Map(x => x.RegValve);
|
Map(x => x.RegValve);
|
||||||
Map(x => x.FlowMeter);
|
Map(x => x.RegulMinStep).Nullable();//older databases may not have this column
|
||||||
|
Map(x => x.FlowMeter);
|
||||||
Map(x => x.PidCoef);
|
Map(x => x.PidCoef);
|
||||||
Map(x => x.StartValve);
|
Map(x => x.StartValve);
|
||||||
Map(x => x.Diverter);
|
Map(x => x.Diverter);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Config
|
||||||
|
{
|
||||||
|
public class PurchaseResources
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
|
||||||
Binary file not shown.
+1
-1
@@ -18,7 +18,7 @@ namespace Decrypt
|
|||||||
{
|
{
|
||||||
if ((args.Length < 1) || !File.Exists(args[0]))
|
if ((args.Length < 1) || !File.Exists(args[0]))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Usage: Decode <file>");
|
Console.WriteLine(@"Usage: Decode <file>");
|
||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-2
@@ -1,3 +1,28 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/></startup></configuration>
|
<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>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ namespace Encrypt
|
|||||||
{
|
{
|
||||||
if ((args.Length < 1) || !File.Exists(args[0]))
|
if ((args.Length < 1) || !File.Exists(args[0]))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Usage: Encode <file>");
|
Console.WriteLine(@"Usage: Encode <file>");
|
||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Ports;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|||||||
+27
-2
@@ -1,3 +1,28 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
|
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /></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>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.API
|
||||||
|
{
|
||||||
|
public class Enums
|
||||||
|
{
|
||||||
|
public enum DataStorageReaderTypes : sbyte
|
||||||
|
{
|
||||||
|
LoginPasswordsReader,
|
||||||
|
CalibrationParamsReader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,967 @@
|
|||||||
|
using CordonelPreadjustmentUi;
|
||||||
|
using CordonelPreadjustmentUi.Processes.Itinerary;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Config;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
||||||
|
using Xylem.Common.Logic.ProductionOrderCore.OrderData;
|
||||||
|
using Xylem.Common.Ui.CordonelPreadjustmentUi;
|
||||||
|
using static GenesisCordonelInterface.API.PublicModels;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.API
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Public-facing facade for external applications integrating with
|
||||||
|
/// Genesis Cordonel Interface.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This class exposes a simplified and controlled API for external callers.
|
||||||
|
/// It validates public input, maps public request models to internal models,
|
||||||
|
/// forwards operations to the internal GCI implementation and exposes status
|
||||||
|
/// notifications for meter batch changes.
|
||||||
|
///
|
||||||
|
/// This layer should stay thin. Business logic and meter communication are
|
||||||
|
/// handled by <see cref="InterfaceGCIToLaatzen"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public class InterfaceOutsideToGCI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Internal GCI implementation used by this public facade.
|
||||||
|
/// </summary>
|
||||||
|
public readonly InterfaceGCIToLaatzen _innerMeterAPI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when meter batch status information changes.
|
||||||
|
/// </summary>
|
||||||
|
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.
|
||||||
|
/// </summary>
|
||||||
|
public InterfaceOutsideToGCI()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the public GCI facade.
|
||||||
|
/// </summary>
|
||||||
|
public InterfaceOutsideToGCI(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RequestedCalibrationParamsFromDb(
|
||||||
|
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);
|
||||||
|
|
||||||
|
lock (progress)
|
||||||
|
{
|
||||||
|
if (!token.IsCancellationRequested && ReferenceEquals(progress.CancellationSource, source))
|
||||||
|
progress.PushedCalibrationParams = calibrationParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!token.IsCancellationRequested && ReferenceEquals(progress.CancellationSource, source))
|
||||||
|
progress.DebugMessage($"Calibration params reading failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (progress)
|
||||||
|
{
|
||||||
|
if (!token.IsCancellationRequested && ReferenceEquals(progress.CancellationSource, source))
|
||||||
|
progress.CalibrationParamsReadEvent.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laatzen ToolBox actions
|
||||||
|
|
||||||
|
#region ================================== PORT DETECTION ==================================
|
||||||
|
|
||||||
|
public PortDetectionResult DetectStreamingPort(int slot)
|
||||||
|
{
|
||||||
|
var result = _innerMeterAPI?.DetectStreamingPort(slot);
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PortDetectionResult> DetectStreamingPortAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default(CancellationToken))
|
||||||
|
{
|
||||||
|
var result = await _innerMeterAPI?.DetectStreamingPortAsync(slot, token);
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PortDetectionResult DetectRequestPort(int slot)
|
||||||
|
{
|
||||||
|
var result = _innerMeterAPI?.DetectRequestPort(slot);
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PortDetectionResult> DetectRequestPortAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default(CancellationToken))
|
||||||
|
{
|
||||||
|
var result = await _innerMeterAPI?.DetectRequestPortAsync(slot, token);
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#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>
|
||||||
|
/// <param name="request">
|
||||||
|
/// Slot initialization request containing slot id, configuration source,
|
||||||
|
/// password source, request port and streaming port.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Result describing whether the slot was created, updated, already existed or failed.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Thrown when <paramref name="request"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciInitSlotResult> InitSlotAsync(
|
||||||
|
GciInitSlotRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.InitSlotAsync(
|
||||||
|
request.SlotId,
|
||||||
|
ModelsMapping.MapConfigSource(request.ConfigSource),
|
||||||
|
ModelsMapping.MapPasswordSource(request.PasswordSource),
|
||||||
|
ModelsMapping.MapPort(request.RequestPort),
|
||||||
|
ModelsMapping.MapPort(request.StreamingPort),
|
||||||
|
token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates configuration of an existing meter slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">
|
||||||
|
/// Slot configuration request containing updated configuration source,
|
||||||
|
/// password source and port settings.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Result describing whether the slot update succeeded or failed.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Thrown when <paramref name="request"/> is null.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciInitSlotResult> UpdateSlotAsync(
|
||||||
|
GciInitSlotRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.UpdateSlotAsync(
|
||||||
|
request.SlotId,
|
||||||
|
ModelsMapping.MapConfigSource(request.ConfigSource),
|
||||||
|
ModelsMapping.MapPasswordSource(request.PasswordSource),
|
||||||
|
ModelsMapping.MapPort(request.RequestPort),
|
||||||
|
ModelsMapping.MapPort(request.StreamingPort),
|
||||||
|
token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about one meter slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slotId">Slot id to query. Must be greater than zero.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Slot information including existence, connection state, login state,
|
||||||
|
/// PCB id and configured communication ports.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when <paramref name="slotId"/> is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciSlotInfo> GetSlotAsync(
|
||||||
|
int slotId,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slotId <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.");
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.GetOneMeterInfo(slotId, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about all currently initialized meter slots.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Collection of slot information records for all known meters.
|
||||||
|
/// </returns>
|
||||||
|
public async Task<GciAllSlotsInfo> GetAllSlotsAsync(
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var result = await _innerMeterAPI.GetAllMetersInfo(token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans one meter slot and releases its runtime resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id to clean. Must be greater than zero.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Result describing whether the slot cleanup succeeded or failed.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when <paramref name="slot"/> is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciCleanSlotResult> CleanSlotAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.CleanSlotAsync(slot, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans all initialized meter slots and releases related runtime resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Result describing whether cleanup of all slots succeeded or failed.
|
||||||
|
/// </returns>
|
||||||
|
public async Task<GciCleanAllSlotsResult> CleanAllSlotsAsync(
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var result = await _innerMeterAPI.CleanAllSlotsAsync(token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== PASSWORD ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets runtime password for the meter assigned to the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="password">Password to assign to the meter.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>Result containing password update status.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when slot id is invalid or password is empty.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciSetPasswordResult> SetPasswordAsync(
|
||||||
|
int slot,
|
||||||
|
string password,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
throw new ArgumentException("Password is empty.");
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.SetMeterPasswordAsync(slot, password, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== LOGIN ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs in to the meter assigned to the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>Login result containing login state and status message.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when <paramref name="slot"/> is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<PublicModels.GciLoginResult> LoginOneSlotAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.");
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.LoginOneSlotAsync(slot, token).ConfigureAwait(false);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== CONNECTION ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connects the meter assigned to the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>Connection result containing connection state and status message.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when <paramref name="slot"/> is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciConnectResult> ConnectOneSlotAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.");
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.ConnectOneSlotAsync(slot, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects the meter assigned to the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>Disconnect result containing final connection state and status message.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when <paramref name="slot"/> is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciDisconnectResult> DisconnectAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.");
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.DisconnectAsync(slot, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== PCB ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads PCB identifier from the meter assigned to the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>Result containing PCB id and read status.</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when <paramref name="slot"/> is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciGetPcbIdResult> GetPcbIdAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.");
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI.GetPcbIdAsync(slot, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== READ ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a firmware register value from the meter assigned to the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="registerName">Register identifier to read.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Register read result containing raw register value and operation status.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when slot id or register name is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<RegisterReadResult> ReadRegisterAsync(
|
||||||
|
int slot,
|
||||||
|
string registerName,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(registerName))
|
||||||
|
throw new ArgumentException("Register name is empty.", nameof(registerName));
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI
|
||||||
|
.ReadRegisterAsync(slot, registerName, token)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== WRITE ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a value to a firmware register.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="registerName">Register identifier to write.</param>
|
||||||
|
/// <param name="value">Value to write.</param>
|
||||||
|
/// <param name="storeToDevice">
|
||||||
|
/// Indicates whether configuration should be permanently stored.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="refreshSystemState">
|
||||||
|
/// Indicates whether firmware system state should be refreshed after write.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Register write result describing write status.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when slot id or register name is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<RegisterWriteResult> WriteRegisterAsync(
|
||||||
|
int slot,
|
||||||
|
string registerName,
|
||||||
|
object value,
|
||||||
|
bool storeToDevice = false,
|
||||||
|
bool refreshSystemState = false,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(registerName))
|
||||||
|
throw new ArgumentException("Register name is empty.", nameof(registerName));
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI
|
||||||
|
.WriteRegisterAsync(
|
||||||
|
slot,
|
||||||
|
registerName,
|
||||||
|
value,
|
||||||
|
storeToDevice,
|
||||||
|
refreshSystemState,
|
||||||
|
token)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== Password ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates meter password for the specified slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
||||||
|
/// <param name="password">New password.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Password update result.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when slot id or password is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public async Task<GciSetPasswordResult> SetMeterPasswordAsync(
|
||||||
|
int slot,
|
||||||
|
string password,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (slot <= 0)
|
||||||
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
throw new ArgumentException("Password is empty.", nameof(password));
|
||||||
|
|
||||||
|
var result = await _innerMeterAPI
|
||||||
|
.SetMeterPasswordAsync(slot, password, token)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
//RaiseMeterBatchStatusChanged();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== DEBUG STATUS ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets runtime diagnostic information for all active workers.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Collection containing worker state, queue information,
|
||||||
|
/// current operation and activity timestamps.
|
||||||
|
/// </returns>
|
||||||
|
public List<WorkerDebugStatus> GetWorkerDebugStatuses()
|
||||||
|
{
|
||||||
|
return _innerMeterAPI?.GetWorkerDebugStatuses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets runtime diagnostic information for all meter slots.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Collection containing slot state, connection state,
|
||||||
|
/// selected state and communication configuration.
|
||||||
|
/// </returns>
|
||||||
|
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
|
||||||
|
{
|
||||||
|
return _innerMeterAPI?.GetMeterBatchDebugStatuses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises meter batch status change notification.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Intended to notify external consumers after changes in meter state.
|
||||||
|
/// </remarks>
|
||||||
|
public void RaiseMeterBatchStatusChanged()
|
||||||
|
{
|
||||||
|
var statuses = GetMeterBatchDebugStatuses();
|
||||||
|
|
||||||
|
var handler = MeterBatchStatusChanged;
|
||||||
|
if (handler != null)
|
||||||
|
handler(statuses);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== SLOT SELECTION ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets selection state for a slot.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id.</param>
|
||||||
|
/// <param name="selected">Selection state.</param>
|
||||||
|
public void SetSlotSelected(int slot, bool selected)
|
||||||
|
{
|
||||||
|
_innerMeterAPI?.SetSlotSelected(slot, selected);
|
||||||
|
RaiseMeterBatchStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified slot is selected.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slot">Slot id.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if slot is selected; otherwise false.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsSlotSelected(int slot)
|
||||||
|
{
|
||||||
|
return (bool)(_innerMeterAPI?.IsSlotSelected(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all selected slot identifiers.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Ordered collection of selected slot ids.
|
||||||
|
/// </returns>
|
||||||
|
public List<int> GetSelectedSlots()
|
||||||
|
{
|
||||||
|
return _innerMeterAPI?.GetSelectedSlots();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== Register names ==================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all available firmware register identifiers.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Ordered collection of register names.
|
||||||
|
/// </returns>
|
||||||
|
public List<string> GetAllRegisterNames()
|
||||||
|
{
|
||||||
|
return _innerMeterAPI?.GetAllRegisterNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// Laatzen Preadjustment processes
|
||||||
|
|
||||||
|
#region ================================== PreAdjustment ==================================
|
||||||
|
public PreAdjustmentInitializationResult Preadjustment_Initialization(
|
||||||
|
ProcessProgress pp,
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_DetectAsync(selectedSlots, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreadjustmentDetectResult PreAdjustment_DetectDirect(
|
||||||
|
IEnumerable<PublicModels.MeterBatchDebugStatus> selectedSlots,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_DetectDirect(selectedSlots, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<PreAdjustmentProcessResult> PreAdjustment_PreparationAsync(
|
||||||
|
int slot,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_PreparationAsync(slot, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreAdjustmentProcessResult PreAdjustment_PreparationDirect()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_AmplitudeTestAsync(slot, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreAdjustmentProcessResult PreAdjustment_AmplitudeTestDirect()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationAsync(slot, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_OffsetTestAsync(slot, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreAdjustmentProcessResult PreAdjustment_OffsetTestDirect()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_CompletionAsync(slot, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreAdjustmentProcessResult PreAdjustment_CompletionDirect()
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_CompletionDirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreAdjustmentProcessResult PreAdjustment_CompletionDirect(CancellationToken token)
|
||||||
|
{
|
||||||
|
return _innerMeterAPI.PreAdjustment_CompletionDirect(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ================================== UNI DATA STORAGE READER ==================================
|
||||||
|
|
||||||
|
//LoginPasswords reading
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads meter login password from configured GCI data storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">
|
||||||
|
/// Data query containing PCB ID or another configured lookup value.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="token">
|
||||||
|
/// Cancellation token used to cancel the asynchronous operation.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Password if found; otherwise null.
|
||||||
|
/// </returns>
|
||||||
|
public Task<string> ReadMeterLoginPasswordAsync(
|
||||||
|
DataQuery query,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return loginPasswordsReader.ReadMeterLoginPasswordAsync(
|
||||||
|
query,
|
||||||
|
token);
|
||||||
|
}
|
||||||
|
|
||||||
|
//CalibrationParams reading
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads pre-adjustment calibration parameters
|
||||||
|
/// from configured GCI data storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">
|
||||||
|
/// Data query containing meter size or another configured lookup value.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="token">
|
||||||
|
/// Cancellation token used to cancel the asynchronous operation.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Dictionary:
|
||||||
|
///
|
||||||
|
/// Key:
|
||||||
|
/// Calibration parameter name
|
||||||
|
///
|
||||||
|
/// Value:
|
||||||
|
/// Calibration parameter value
|
||||||
|
/// </returns>
|
||||||
|
public Task<Dictionary<string, UInt32>> ReadPreAdjustmentCalibrationParamsAsync(
|
||||||
|
DataQuery query,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return calibrationParamsReader.ReadCalibrationParamsAsync(
|
||||||
|
query,
|
||||||
|
token);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||||||
|
using static GenesisCordonelInterface.API.InterfaceOutsideToGCI;
|
||||||
|
using static GenesisCordonelInterface.API.PublicModels;
|
||||||
|
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.API
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Public data contract layer for the Genesis Cordonel Interface (GCI).
|
||||||
|
///
|
||||||
|
/// This class defines all Data Transfer Objects (DTOs) that are exposed
|
||||||
|
/// to external consumers (e.g. TBF, UI, or other integration layers).
|
||||||
|
///
|
||||||
|
/// Responsibilities:
|
||||||
|
/// - Provide stable, dependency-free models for external usage
|
||||||
|
/// - Decouple internal GCI implementation (GenesisMeter, Xylem libraries)
|
||||||
|
/// from external systems
|
||||||
|
/// - Define request/response contracts for all supported operations
|
||||||
|
/// - Contain mapping methods between public DTOs and internal domain models
|
||||||
|
///
|
||||||
|
/// Architecture:
|
||||||
|
/// External world (TBF / UI)
|
||||||
|
/// ↓
|
||||||
|
/// GciPublicModels (this layer)
|
||||||
|
/// ↓
|
||||||
|
/// Internal GCI API (InterfaceGCIToLaatzen, GenesisMeter, etc.)
|
||||||
|
///
|
||||||
|
/// Notes:
|
||||||
|
/// - Public models must NOT expose internal types (e.g. GenesisMeter, IPort, etc.)
|
||||||
|
/// - All mapping between internal and external representations must be done here
|
||||||
|
/// - DTOs are designed to be simple, serializable, and stable over time
|
||||||
|
/// - Any change in internal implementation should not affect these models
|
||||||
|
///
|
||||||
|
/// Pattern:
|
||||||
|
/// Each operation follows a consistent structure:
|
||||||
|
/// Request → Operation → Result
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// GciInitSlotRequest → InitSlot → GciInitSlotResult
|
||||||
|
/// GetSlot → GciSlotInfo
|
||||||
|
/// GetPcbId → GciGetPcbIdResult
|
||||||
|
///
|
||||||
|
/// This layer acts as a boundary between domain logic and integration logic.
|
||||||
|
/// </summary>
|
||||||
|
public class ModelsMapping
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mapping methods between public DTOs and internal GCI models.
|
||||||
|
/// Ensures separation between external contracts and internal domain objects.
|
||||||
|
/// </summary>
|
||||||
|
#region ================================== OUTERN/INTERN and back models mapping ==================================
|
||||||
|
public static PasswordSource MapPasswordSource(PublicModels.GciPasswordSource src)
|
||||||
|
{
|
||||||
|
return (PasswordSource)src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConfigSource MapConfigSource(PublicModels.GciConfigSource src)
|
||||||
|
{
|
||||||
|
return (ConfigSource)src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Xylem.Common.Hardware.Interfaces.Ports.PortCore.PortConfig? MapPort(PublicModels.GciPortConfig port)
|
||||||
|
{
|
||||||
|
if (port == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Xylem.Common.Hardware.Interfaces.Ports.PortCore.PortConfig result = new Xylem.Common.Hardware.Interfaces.Ports.PortCore.PortConfig();
|
||||||
|
|
||||||
|
result.PortName = port.PortName;
|
||||||
|
result.Type = port.Type;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicModels.GciInitSlotResult MapInitResult(PublicModels.GciInitSlotResult result)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PublicModels.GciInitSlotResult
|
||||||
|
{
|
||||||
|
SlotId = result.SlotId,
|
||||||
|
Success = result.Success,
|
||||||
|
Message = result.Message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicModels.GciPasswordSource MapPasswordSourceBack(PasswordSource src)
|
||||||
|
{
|
||||||
|
return (PublicModels.GciPasswordSource)src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicModels.GciConfigSource MapConfigSourceBack(ConfigSource src)
|
||||||
|
{
|
||||||
|
return (PublicModels.GciConfigSource)src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicModels.GciPortConfig MapPortBack(PortConfig? port)
|
||||||
|
{
|
||||||
|
if (!port.HasValue)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
PortConfig value = port.Value;
|
||||||
|
|
||||||
|
return new PublicModels.GciPortConfig
|
||||||
|
{
|
||||||
|
PortName = value.PortName,
|
||||||
|
Type = value.Type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicModels.GciPortConfig MapPortBack(IPort port)
|
||||||
|
{
|
||||||
|
if (port == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PublicModels.GciPortConfig
|
||||||
|
{
|
||||||
|
PortName = port.GetPortName(),
|
||||||
|
Type = port.GetType().Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicModels.GciPortConfig MapPortBack(string portName, string portTyoe)
|
||||||
|
{
|
||||||
|
return new PublicModels.GciPortConfig
|
||||||
|
{
|
||||||
|
PortName = portName,
|
||||||
|
Type = portTyoe
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MeterBatchDebugStatus ToMeterBatchDebugStatus(GciSlotInfo slot)
|
||||||
|
{
|
||||||
|
if (slot == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new MeterBatchDebugStatus
|
||||||
|
{
|
||||||
|
Slot = slot.SlotId,
|
||||||
|
Selected = true,
|
||||||
|
PcbId = slot.PcbId,
|
||||||
|
IsConnected = false,
|
||||||
|
IsLoggedOn = false,
|
||||||
|
RequestPort = slot.RequestPort == null ? "" : slot.RequestPort.PortName,
|
||||||
|
StreamingPort = slot.StreamingPort == null ? "" : slot.StreamingPort.PortName,
|
||||||
|
FwVersion = "",
|
||||||
|
InterfaceVersion = ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MeterBatchDebugStatus> ToMeterBatchDebugStatusList(
|
||||||
|
IEnumerable<GciSlotInfo> slots)
|
||||||
|
{
|
||||||
|
if (slots == null)
|
||||||
|
return new List<MeterBatchDebugStatus>();
|
||||||
|
|
||||||
|
return slots
|
||||||
|
.Where(x => x != null)
|
||||||
|
.Select(ToMeterBatchDebugStatus)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,636 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||||||
|
using static GenesisCordonelInterface.API.InterfaceOutsideToGCI;
|
||||||
|
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.API
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Public data contract layer for the Genesis Cordonel Interface (GCI).
|
||||||
|
///
|
||||||
|
/// This class defines all Data Transfer Objects (DTOs) that are exposed
|
||||||
|
/// to external consumers (e.g. TBF, UI, or other integration layers).
|
||||||
|
///
|
||||||
|
/// Responsibilities:
|
||||||
|
/// - Provide stable, dependency-free models for external usage
|
||||||
|
/// - Decouple internal GCI implementation (GenesisMeter, Xylem libraries)
|
||||||
|
/// from external systems
|
||||||
|
/// - Define request/response contracts for all supported operations
|
||||||
|
/// - Contain mapping methods between public DTOs and internal domain models
|
||||||
|
///
|
||||||
|
/// Architecture:
|
||||||
|
/// External world (TBF / UI)
|
||||||
|
/// ↓
|
||||||
|
/// GciPublicModels (this layer)
|
||||||
|
/// ↓
|
||||||
|
/// Internal GCI API (InterfaceGCIToLaatzen, GenesisMeter, etc.)
|
||||||
|
///
|
||||||
|
/// Notes:
|
||||||
|
/// - Public models must NOT expose internal types (e.g. GenesisMeter, IPort, etc.)
|
||||||
|
/// - All mapping between internal and external representations must be done here
|
||||||
|
/// - DTOs are designed to be simple, serializable, and stable over time
|
||||||
|
/// - Any change in internal implementation should not affect these models
|
||||||
|
///
|
||||||
|
/// Pattern:
|
||||||
|
/// Each operation follows a consistent structure:
|
||||||
|
/// Request → Operation → Result
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// GciInitSlotRequest → InitSlot → GciInitSlotResult
|
||||||
|
/// GetSlot → GciSlotInfo
|
||||||
|
/// GetPcbId → GciGetPcbIdResult
|
||||||
|
///
|
||||||
|
/// This layer acts as a boundary between domain logic and integration logic.
|
||||||
|
/// </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)
|
||||||
|
{
|
||||||
|
if (!HideSensitiveValues)
|
||||||
|
return password ?? "<null>";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(password))
|
||||||
|
return "<empty>";
|
||||||
|
|
||||||
|
return "********";
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Public DTOs exposed to external systems.
|
||||||
|
/// These models represent the contract of the GCI API.
|
||||||
|
/// They must remain stable and independent of internal implementation.
|
||||||
|
/// </summary>
|
||||||
|
#region ================================== PUBLIC MODELS ===========================================
|
||||||
|
|
||||||
|
public class RegisterReadResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string RegisterName { get; set; }
|
||||||
|
public string RawHex { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"Success={0}, RegisterName={1}, RawHex={2}, ErrorMessage={3}",
|
||||||
|
Success,
|
||||||
|
RegisterName,
|
||||||
|
RawHex,
|
||||||
|
ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkerDebugStatus
|
||||||
|
{
|
||||||
|
public int Slot { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int QueueLength { get; set; }
|
||||||
|
public bool IsBusy { get; set; }
|
||||||
|
public string CurrentOperation { get; set; }
|
||||||
|
public string LastError { get; set; }
|
||||||
|
public DateTime LastActivity { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MeterBatchDebugStatus
|
||||||
|
{
|
||||||
|
public int Slot { get; set; }
|
||||||
|
public bool Selected { get; set; }
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
public bool IsConnected { get; set; }
|
||||||
|
public bool IsLoggedOn { get; set; }
|
||||||
|
public string RequestPort { get; set; }
|
||||||
|
public string StreamingPort { get; set; }
|
||||||
|
public string RequestPortType { get; set; }
|
||||||
|
public string FwVersion { get; set; }
|
||||||
|
public string InterfaceVersion { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegisterWriteResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string RegisterName { get; set; }
|
||||||
|
public object WrittenValue { get; set; }
|
||||||
|
public bool StoreToDevice { get; set; }
|
||||||
|
public bool RefreshSystemState { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Success={Success}, " +
|
||||||
|
$"Register={RegisterName ?? "<null>"}, " +
|
||||||
|
$"Value={WrittenValue ?? "<null>"}, " +
|
||||||
|
$"StoreToDevice={StoreToDevice}, " +
|
||||||
|
$"RefreshSystemState={RefreshSystemState}, " +
|
||||||
|
$"Error={ErrorMessage ?? "<none>"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PortDetectionResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public int Slot { get; set; }
|
||||||
|
public string PortName { get; set; }
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"Slot={0}, Success={1}, PortName={2}, PcbId={3}, ErrorMessage={4}",
|
||||||
|
Slot,
|
||||||
|
Success,
|
||||||
|
PortName,
|
||||||
|
PcbId,
|
||||||
|
ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciSlotInfo
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Exists { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public bool IsConnected { get; set; }
|
||||||
|
public bool IsLoggedOn { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public GciConfigSource ConfigSource { get; set; }
|
||||||
|
public GciPasswordSource PasswordSource { get; set; }
|
||||||
|
public GciPortConfig RequestPort { get; set; }
|
||||||
|
public GciPortConfig StreamingPort { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Exists={1}, Success={2}, IsConnected={3}, IsLoggedOn={4}, Message={5}, PcbId={6}, Password={7}, ConfigSource={8}, PasswordSource={9}, RequestPort={10}, StreamingPort={11}",
|
||||||
|
SlotId,
|
||||||
|
Exists,
|
||||||
|
Success,
|
||||||
|
IsConnected,
|
||||||
|
IsLoggedOn,
|
||||||
|
Message,
|
||||||
|
PcbId,
|
||||||
|
FormatPassword(Password),
|
||||||
|
ConfigSource,
|
||||||
|
PasswordSource,
|
||||||
|
RequestPort,
|
||||||
|
StreamingPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciAllSlotsInfo
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public List<GciSlotInfo> Slots { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Success={Success}, Count={Slots?.Count ?? 0}, Message={Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Result
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, Message={2}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GciPasswordSource
|
||||||
|
{
|
||||||
|
RestApi = 0,
|
||||||
|
OfflineFile = 1,
|
||||||
|
InterfaceInputPassword = 2,
|
||||||
|
}
|
||||||
|
public enum GciConfigSource
|
||||||
|
{
|
||||||
|
FileConfig = 0,
|
||||||
|
InterfaceInputConfig = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of port settings
|
||||||
|
/// </summary>
|
||||||
|
public class GciPortConfig
|
||||||
|
{
|
||||||
|
public string PortName { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("PortName={0}, Type={1}", PortName, Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GciPortConfig CreatePortConfig(string portName, string type)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(portName))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new GciPortConfig
|
||||||
|
{
|
||||||
|
PortName = portName,
|
||||||
|
Type = MapPortType(type)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string MapPortType(string type)
|
||||||
|
{
|
||||||
|
switch ((type ?? "").Trim().ToUpperInvariant())
|
||||||
|
{
|
||||||
|
case "RFID":
|
||||||
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.RfidSerialPort";
|
||||||
|
|
||||||
|
case "UART":
|
||||||
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort";
|
||||||
|
|
||||||
|
case "IRDA":
|
||||||
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciInitSlotRequest
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public GciConfigSource ConfigSource { get; set; }
|
||||||
|
public GciPasswordSource PasswordSource { get; set; }
|
||||||
|
public GciPortConfig RequestPort { get; set; }
|
||||||
|
public GciPortConfig StreamingPort { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, GciConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}, Password={5}",
|
||||||
|
SlotId,
|
||||||
|
ConfigSource,
|
||||||
|
PasswordSource,
|
||||||
|
RequestPort,
|
||||||
|
StreamingPort,
|
||||||
|
FormatPassword(Password));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciInitSlotResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
|
||||||
|
public bool Created { get; set; }
|
||||||
|
|
||||||
|
public bool Updated { get; set; }
|
||||||
|
|
||||||
|
public bool AlreadyExists { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, Message={2}, PcbId={3}, Created={4}, Updated={5}, AlreadyExists={6}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
Message,
|
||||||
|
PcbId,
|
||||||
|
Created,
|
||||||
|
Updated,
|
||||||
|
AlreadyExists);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciCleanAllSlotsResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"Success={0}, Message={1}",
|
||||||
|
Success,
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class GciCleanSlotResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, Message={2}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciGetPcbIdResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, PcbId={2}, Message={3}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
PcbId,
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class GciConnectResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
public bool IsConnected { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, PcbId={2}, IsConnected={3}, Message={4}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
PcbId,
|
||||||
|
IsConnected,
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciLoginResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string PcbId { get; set; }
|
||||||
|
public bool IsLoggedOn { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Slot={SlotId}, Success={Success}, PcbId={PcbId}, IsLoggedOn={IsLoggedOn}, Message={Message ?? "<none>"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciRegisterSnapshot
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string RawValue { get; set; }
|
||||||
|
public string Min { get; set; }
|
||||||
|
public string Max { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
public string IsAvailable { get; set; }
|
||||||
|
public string Privilege { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciDisconnectResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, Message={2}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GciSetPasswordResult
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"SlotId={0}, Success={1}, Password={2}, Message={3}",
|
||||||
|
SlotId,
|
||||||
|
Success,
|
||||||
|
FormatPassword(Password),
|
||||||
|
Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PreAdjustmentProcessResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public int Slot { get; set; }
|
||||||
|
public string ProcessName { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
$"Success={Success}, " +
|
||||||
|
$"Slot={Slot}, " +
|
||||||
|
$"Process={ProcessName}, " +
|
||||||
|
$"Error={ErrorMessage ?? "None"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
public int DetectedMeterCount { get; set; }
|
||||||
|
public int DetectedThermometerCount { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
$"Success={Success}, " +
|
||||||
|
$"Meters={DetectedMeterCount}, " +
|
||||||
|
$"Thermometers={DetectedThermometerCount}, " +
|
||||||
|
$"Error={ErrorMessage ?? "None"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PreAdjustmentInitializationResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Slots { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
$"Success={Success}, " +
|
||||||
|
$"Slots={Slots}, " +
|
||||||
|
$"Error={ErrorMessage ?? "None"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||||
|
</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>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Xylem.Common.CommonCore" publicKeyToken="null" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.8.18.17954" newVersion="2.8.18.17954" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"_Comment": "GCI DataStorage configuration",
|
||||||
|
|
||||||
|
"DataStorage": {
|
||||||
|
|
||||||
|
"MeterLoginPasswords": {
|
||||||
|
|
||||||
|
"___Documentation___": {
|
||||||
|
"Description": "Reads login passwords for meters by PCB ID",
|
||||||
|
"Type": "Supported: LocalDatabase, RemoteDatabase, LocalCsv, LocalJson, RestApi",
|
||||||
|
"DataSource": "Database connection string",
|
||||||
|
"QueryTemplate": "QUERYPARAM is placeholder for runtime value. Example: WHERE [PcbId]=QUERYPARAM -> PCB ID provided during GetPasswordAsync()."
|
||||||
|
},
|
||||||
|
|
||||||
|
"Name": "MeterLoginPasswords",
|
||||||
|
"Type": "LocalDatabase",
|
||||||
|
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
|
||||||
|
"QueryTemplate": "SELECT [Password] FROM [dbo].[SkeletonKeys] WHERE [PcbId] = QUERYPARAM"
|
||||||
|
},
|
||||||
|
|
||||||
|
"PreAdjustmentCalibrationParams": {
|
||||||
|
|
||||||
|
"___Documentation___": {
|
||||||
|
"Description": "Reads pre-adjustment calibration parameters by meter size",
|
||||||
|
"Type": "Supported: LocalDatabase, RemoteDatabase, LocalCsv, LocalJson, RestApi",
|
||||||
|
"DataSource": "Database connection string",
|
||||||
|
"QueryTemplate": "QUERYPARAM is placeholder for runtime value. Example: WHERE [MeterSize]=QUERYPARAM -> meter size provided during ReadCalibrationParamsAsync()."
|
||||||
|
},
|
||||||
|
|
||||||
|
"Name": "PreAdjustmentCalibrationParams",
|
||||||
|
"Type": "LocalDatabase",
|
||||||
|
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
|
||||||
|
"QueryTemplate": "SELECT [ParameterName], [ParameterValue] FROM [dbo].[PreAdjustmentCalibrationParams] WHERE [MeterSize] = QUERYPARAM"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace GenesisCordonelInterface.Core.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Root GCI configuration object loaded from gci_config.json.
|
||||||
|
///
|
||||||
|
/// This class represents the top-level configuration structure
|
||||||
|
/// and serves as an entry point for all configurable GCI features.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// "DataStorageSection":
|
||||||
|
/// {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </summary>
|
||||||
|
public class GciConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data storage configuration section.
|
||||||
|
///
|
||||||
|
/// Contains definitions for all configured readers and writers,
|
||||||
|
/// such as:
|
||||||
|
///
|
||||||
|
/// - MeterLoginPasswords
|
||||||
|
/// - PreAdjustmentCalibrationParams
|
||||||
|
/// - future storage providers
|
||||||
|
/// </summary>
|
||||||
|
public DataStorage.Config.GciDataStorageConfig DataStorageSection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads GCI configuration from gci_config.json.
|
||||||
|
///
|
||||||
|
/// Responsibilities:
|
||||||
|
/// - Locate configuration file
|
||||||
|
/// - Deserialize JSON into strongly typed objects
|
||||||
|
/// - Normalize relative file paths
|
||||||
|
/// - Preserve database connection strings and REST URLs
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// Config/
|
||||||
|
/// gci_config.json
|
||||||
|
///
|
||||||
|
/// Data/
|
||||||
|
/// meter_passwords.csv
|
||||||
|
///
|
||||||
|
/// Relative paths:
|
||||||
|
/// Data\file.csv
|
||||||
|
///
|
||||||
|
/// become:
|
||||||
|
///
|
||||||
|
/// C:\App\bin\Debug\Data\file.csv
|
||||||
|
///
|
||||||
|
/// Database sources remain unchanged:
|
||||||
|
///
|
||||||
|
/// Server=(localdb)\MojaDB;Database=...
|
||||||
|
/// </summary>
|
||||||
|
public static class GciConfigLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads default GCI configuration from:
|
||||||
|
///
|
||||||
|
/// Config\gci_config.json
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Loaded GCI configuration.
|
||||||
|
/// </returns>
|
||||||
|
public static GciConfig LoadDefault()
|
||||||
|
{
|
||||||
|
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
|
||||||
|
string configPath = Path.Combine(
|
||||||
|
baseDirectory,
|
||||||
|
"Config",
|
||||||
|
"gci_config.json");
|
||||||
|
|
||||||
|
return Load(configPath, baseDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads GCI configuration from specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configPath">
|
||||||
|
/// Path to configuration json file.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// Base directory used for resolving relative paths.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Loaded configuration object.
|
||||||
|
/// </returns>
|
||||||
|
public static GciConfig Load(
|
||||||
|
string configPath,
|
||||||
|
string baseDirectory = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(configPath))
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Config path must not be empty.",
|
||||||
|
nameof(configPath));
|
||||||
|
|
||||||
|
if (!File.Exists(configPath))
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
"GCI config file was not found.",
|
||||||
|
configPath);
|
||||||
|
|
||||||
|
string json = File.ReadAllText(configPath);
|
||||||
|
|
||||||
|
GciConfig config =
|
||||||
|
JsonConvert.DeserializeObject<GciConfig>(json);
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"GCI config could not be loaded.");
|
||||||
|
|
||||||
|
NormalizeDataStoragePaths(
|
||||||
|
config,
|
||||||
|
baseDirectory ?? Path.GetDirectoryName(configPath));
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes paths for all configured data storage entries.
|
||||||
|
///
|
||||||
|
/// Converts relative file paths into absolute paths.
|
||||||
|
/// Database connection strings remain untouched.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">
|
||||||
|
/// Loaded configuration object.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// Base path used for relative resolution.
|
||||||
|
/// </param>
|
||||||
|
private static void NormalizeDataStoragePaths(
|
||||||
|
GciConfig config,
|
||||||
|
string baseDirectory)
|
||||||
|
{
|
||||||
|
if (config.DataStorageSection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NormalizePath(
|
||||||
|
config.DataStorageSection.MeterLoginPasswords,
|
||||||
|
baseDirectory);
|
||||||
|
|
||||||
|
NormalizePath(
|
||||||
|
config.DataStorageSection.PreAdjustmentCalibrationParams,
|
||||||
|
baseDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts relative file paths into absolute paths.
|
||||||
|
///
|
||||||
|
/// Applies only to:
|
||||||
|
/// - LocalCsv
|
||||||
|
/// - RemoteCsv
|
||||||
|
/// - LocalJson
|
||||||
|
/// - RemoteJson
|
||||||
|
///
|
||||||
|
/// Does not modify:
|
||||||
|
/// - Database connection strings
|
||||||
|
/// - REST URLs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storageConfig">
|
||||||
|
/// Storage configuration.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// Base path for resolution.
|
||||||
|
/// </param>
|
||||||
|
private static void NormalizePath(
|
||||||
|
DataStorageConfig storageConfig,
|
||||||
|
string baseDirectory)
|
||||||
|
{
|
||||||
|
if (storageConfig == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(storageConfig.DataSource))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Database connection strings and URLs
|
||||||
|
// must never be treated as file paths.
|
||||||
|
if (storageConfig.Type == DataStorageType.LocalDatabase ||
|
||||||
|
storageConfig.Type == DataStorageType.RemoteDatabase ||
|
||||||
|
storageConfig.Type == DataStorageType.RestApi)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(storageConfig.DataSource))
|
||||||
|
return;
|
||||||
|
|
||||||
|
storageConfig.DataSource =
|
||||||
|
Path.GetFullPath(
|
||||||
|
Path.Combine(
|
||||||
|
baseDirectory,
|
||||||
|
storageConfig.DataSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"_Comment": "GCI DataStorage configuration",
|
||||||
|
|
||||||
|
"DataStorageSection": {
|
||||||
|
|
||||||
|
"MeterLoginPasswords": {
|
||||||
|
|
||||||
|
"___Documentation___": {
|
||||||
|
"Description": "Reads login passwords for meters by PCB ID",
|
||||||
|
"Type": "Supported: LocalDatabase, RemoteDatabase, LocalCsv, LocalJson, RestApi",
|
||||||
|
"DataSource": "Database connection string",
|
||||||
|
"QueryTemplate": "QUERYPARAM is placeholder for runtime value. Example: WHERE [PcbId]=QUERYPARAM -> PCB ID provided during GetPasswordAsync()."
|
||||||
|
},
|
||||||
|
|
||||||
|
"Name": "MeterLoginPasswords",
|
||||||
|
"Type": "LocalDatabase",
|
||||||
|
"DataSource": "Server=(localdb)\\SensusLocalDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
|
||||||
|
"QueryTemplate": "SELECT [Password] FROM [dbo].[SkeletonKeys] WHERE [PcbId] = QUERYPARAM"
|
||||||
|
},
|
||||||
|
|
||||||
|
"PreAdjustmentCalibrationParams": {
|
||||||
|
|
||||||
|
"___Documentation___": {
|
||||||
|
"Description": "Reads pre-adjustment calibration parameters by meter size",
|
||||||
|
"Type": "Supported: LocalDatabase, RemoteDatabase, LocalCsv, LocalJson, RestApi",
|
||||||
|
"DataSource": "Database connection string",
|
||||||
|
"QueryTemplate": "QUERYPARAM is placeholder for runtime value. Example: WHERE [MeterSize]=QUERYPARAM -> meter size provided during ReadCalibrationParamsAsync()."
|
||||||
|
},
|
||||||
|
|
||||||
|
"Name": "PreAdjustmentCalibrationParams",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Root GCI configuration object loaded from gci_config.json.
|
||||||
|
///
|
||||||
|
/// This class represents the top-level configuration structure
|
||||||
|
/// and serves as an entry point for all configurable GCI features.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// "DataStorage":
|
||||||
|
/// {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </summary>
|
||||||
|
public class GciConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data storage configuration section.
|
||||||
|
///
|
||||||
|
/// Contains definitions for all configured readers and writers,
|
||||||
|
/// such as:
|
||||||
|
///
|
||||||
|
/// - MeterLoginPasswords
|
||||||
|
/// - PreAdjustmentCalibrationParams
|
||||||
|
/// - future storage providers
|
||||||
|
/// </summary>
|
||||||
|
public GciDataStorageConfig DataStorage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads GCI configuration from gci_config.json.
|
||||||
|
///
|
||||||
|
/// Responsibilities:
|
||||||
|
/// - Locate configuration file
|
||||||
|
/// - Deserialize JSON into strongly typed objects
|
||||||
|
/// - Normalize relative file paths
|
||||||
|
/// - Preserve database connection strings and REST URLs
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// Config/
|
||||||
|
/// gci_config.json
|
||||||
|
///
|
||||||
|
/// Data/
|
||||||
|
/// meter_passwords.csv
|
||||||
|
///
|
||||||
|
/// Relative paths:
|
||||||
|
/// Data\file.csv
|
||||||
|
///
|
||||||
|
/// become:
|
||||||
|
///
|
||||||
|
/// C:\App\bin\Debug\Data\file.csv
|
||||||
|
///
|
||||||
|
/// Database sources remain unchanged:
|
||||||
|
///
|
||||||
|
/// Server=(localdb)\MojaDB;Database=...
|
||||||
|
/// </summary>
|
||||||
|
public static class GciConfigLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads default GCI configuration from:
|
||||||
|
///
|
||||||
|
/// Config\gci_config.json
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Loaded GCI configuration.
|
||||||
|
/// </returns>
|
||||||
|
public static GciConfig LoadDefault()
|
||||||
|
{
|
||||||
|
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
|
||||||
|
string configPath = Path.Combine(
|
||||||
|
baseDirectory,
|
||||||
|
"Config",
|
||||||
|
"gci_config.json");
|
||||||
|
|
||||||
|
return Load(configPath, baseDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads GCI configuration from specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configPath">
|
||||||
|
/// Path to configuration json file.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// Base directory used for resolving relative paths.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Loaded configuration object.
|
||||||
|
/// </returns>
|
||||||
|
public static GciConfig Load(
|
||||||
|
string configPath,
|
||||||
|
string baseDirectory = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(configPath))
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Config path must not be empty.",
|
||||||
|
nameof(configPath));
|
||||||
|
|
||||||
|
if (!File.Exists(configPath))
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
"GCI config file was not found.",
|
||||||
|
configPath);
|
||||||
|
|
||||||
|
string json = File.ReadAllText(configPath);
|
||||||
|
|
||||||
|
GciConfig config =
|
||||||
|
JsonConvert.DeserializeObject<GciConfig>(json);
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"GCI config could not be loaded.");
|
||||||
|
|
||||||
|
NormalizeDataStoragePaths(
|
||||||
|
config,
|
||||||
|
baseDirectory ?? Path.GetDirectoryName(configPath));
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes paths for all configured data storage entries.
|
||||||
|
///
|
||||||
|
/// Converts relative file paths into absolute paths.
|
||||||
|
/// Database connection strings remain untouched.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">
|
||||||
|
/// Loaded configuration object.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// Base path used for relative resolution.
|
||||||
|
/// </param>
|
||||||
|
private static void NormalizeDataStoragePaths(
|
||||||
|
GciConfig config,
|
||||||
|
string baseDirectory)
|
||||||
|
{
|
||||||
|
if (config.DataStorage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NormalizePath(
|
||||||
|
config.DataStorage.MeterLoginPasswords,
|
||||||
|
baseDirectory);
|
||||||
|
|
||||||
|
NormalizePath(
|
||||||
|
config.DataStorage.PreAdjustmentCalibrationParams,
|
||||||
|
baseDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts relative file paths into absolute paths.
|
||||||
|
///
|
||||||
|
/// Applies only to:
|
||||||
|
/// - LocalCsv
|
||||||
|
/// - RemoteCsv
|
||||||
|
/// - LocalJson
|
||||||
|
/// - RemoteJson
|
||||||
|
///
|
||||||
|
/// Does not modify:
|
||||||
|
/// - Database connection strings
|
||||||
|
/// - REST URLs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storageConfig">
|
||||||
|
/// Storage configuration.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// Base path for resolution.
|
||||||
|
/// </param>
|
||||||
|
private static void NormalizePath(
|
||||||
|
DataStorageConfig storageConfig,
|
||||||
|
string baseDirectory)
|
||||||
|
{
|
||||||
|
if (storageConfig == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(storageConfig.DataSource))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Database connection strings and URLs
|
||||||
|
// must never be treated as file paths.
|
||||||
|
if (storageConfig.Type == DataStorageType.LocalDatabase ||
|
||||||
|
storageConfig.Type == DataStorageType.RemoteDatabase ||
|
||||||
|
storageConfig.Type == DataStorageType.RestApi)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(storageConfig.DataSource))
|
||||||
|
return;
|
||||||
|
|
||||||
|
storageConfig.DataSource =
|
||||||
|
Path.GetFullPath(
|
||||||
|
Path.Combine(
|
||||||
|
baseDirectory,
|
||||||
|
storageConfig.DataSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains all configured GCI data storage sources.
|
||||||
|
///
|
||||||
|
/// Each property represents one logical use-case
|
||||||
|
/// and points to its storage configuration.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// MeterLoginPasswords
|
||||||
|
/// -> SQL database
|
||||||
|
///
|
||||||
|
/// PreAdjustmentCalibrationParams
|
||||||
|
/// -> CSV file
|
||||||
|
/// </summary>
|
||||||
|
public class GciDataStorageConfig
|
||||||
|
{
|
||||||
|
public GciDatabaseWriterConfig PreadjustmentCompletionWriter { get; set; }
|
||||||
|
public GciDatabaseWriterConfig Q3CalibrationWriter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for meter login password lookup.
|
||||||
|
/// </summary>
|
||||||
|
public DataStorageConfig MeterLoginPasswords { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for pre-adjustment calibration data lookup.
|
||||||
|
/// </summary>
|
||||||
|
public DataStorageConfig PreAdjustmentCalibrationParams { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines common contract for all GCI data storage readers.
|
||||||
|
///
|
||||||
|
/// Implementations can read data from different storage types:
|
||||||
|
/// - SQL database
|
||||||
|
/// - CSV file
|
||||||
|
/// - JSON file
|
||||||
|
/// - REST API
|
||||||
|
///
|
||||||
|
/// Consumers should use this interface instead of concrete readers.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDataStorageReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads data from configured storage using provided query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">
|
||||||
|
/// Query object containing lookup parameters.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Storage-specific result object.
|
||||||
|
/// </returns>
|
||||||
|
object GetData(Models.DataQuery query);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether configured data source is accessible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enableDiagnostics">
|
||||||
|
/// Enables detailed diagnostic output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Diagnostic result of source test.
|
||||||
|
/// </returns>
|
||||||
|
Models.ReaderDiagnosticResult TestSource(bool enableDiagnostics);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether configured query is valid for the data source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enableDiagnostics">
|
||||||
|
/// Enables detailed diagnostic output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Diagnostic result of query test.
|
||||||
|
/// </returns>
|
||||||
|
Models.ReaderDiagnosticResult TestQuery(bool enableDiagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory responsible for creating appropriate
|
||||||
|
/// data storage reader implementations.
|
||||||
|
///
|
||||||
|
/// The reader type is selected according to:
|
||||||
|
///
|
||||||
|
/// DataStorageConfig.Type
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// LocalDatabase
|
||||||
|
/// -> DatabaseDataStorageReader
|
||||||
|
///
|
||||||
|
/// LocalCsv
|
||||||
|
/// -> CsvDataStorageReader
|
||||||
|
///
|
||||||
|
/// LocalJson
|
||||||
|
/// -> JsonDataStorageReader
|
||||||
|
///
|
||||||
|
/// RestApi
|
||||||
|
/// -> RestApiDataStorageReader
|
||||||
|
///
|
||||||
|
/// This factory hides implementation details from
|
||||||
|
/// higher layers and keeps consumers independent
|
||||||
|
/// of storage technology.
|
||||||
|
///
|
||||||
|
/// Trace:
|
||||||
|
///
|
||||||
|
/// MeterLoginPasswordReader
|
||||||
|
/// -> DataStorageReaderFactory.Create()
|
||||||
|
/// -> DatabaseDataStorageReader
|
||||||
|
/// -> CsvDataStorageReader
|
||||||
|
/// -> JsonDataStorageReader
|
||||||
|
/// -> RestApiDataStorageReader
|
||||||
|
/// </summary>
|
||||||
|
public static class DataStorageReaderFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates appropriate reader implementation
|
||||||
|
/// according to storage configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">
|
||||||
|
/// Data storage configuration loaded from gci_config.json.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Configured storage reader implementation.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Configuration is null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="NotSupportedException">
|
||||||
|
/// Unsupported storage type.
|
||||||
|
/// </exception>
|
||||||
|
public static IDataStorageReader Create(
|
||||||
|
DataStorageConfig config)
|
||||||
|
{
|
||||||
|
if (config == null)
|
||||||
|
throw new ArgumentNullException(nameof(config));
|
||||||
|
|
||||||
|
switch (config.Type)
|
||||||
|
{
|
||||||
|
case DataStorageType.RemoteDatabase:
|
||||||
|
case DataStorageType.LocalDatabase:
|
||||||
|
|
||||||
|
return new Providers.DatabaseDataStorageReader(config);
|
||||||
|
|
||||||
|
case DataStorageType.RemoteCsv:
|
||||||
|
case DataStorageType.LocalCsv:
|
||||||
|
|
||||||
|
return new Providers.CsvDataStorageReader(config);
|
||||||
|
|
||||||
|
case DataStorageType.RemoteJson:
|
||||||
|
case DataStorageType.LocalJson:
|
||||||
|
|
||||||
|
return new Providers.JsonDataStorageReader(config);
|
||||||
|
|
||||||
|
case DataStorageType.RestApi:
|
||||||
|
|
||||||
|
return new Providers.RestApiDataStorageReader(config);
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
throw new NotSupportedException(
|
||||||
|
$"Unsupported DataStorageType: '{config.Type}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents query input for data storage readers.
|
||||||
|
///
|
||||||
|
/// Query parameters are used as lookup values
|
||||||
|
/// during storage search operations.
|
||||||
|
///
|
||||||
|
/// A single parameter:
|
||||||
|
///
|
||||||
|
/// 231630279
|
||||||
|
///
|
||||||
|
/// executes one lookup.
|
||||||
|
///
|
||||||
|
/// Multiple parameters:
|
||||||
|
///
|
||||||
|
/// 231630279
|
||||||
|
/// 231630243
|
||||||
|
/// 231630148
|
||||||
|
///
|
||||||
|
/// can execute batch operations.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// QueryTemplate:
|
||||||
|
///
|
||||||
|
/// SELECT [Password]
|
||||||
|
/// WHERE [PcbId]=QUERYPARAM
|
||||||
|
///
|
||||||
|
/// Query:
|
||||||
|
///
|
||||||
|
/// QueryParams:
|
||||||
|
/// 231630279
|
||||||
|
///
|
||||||
|
/// Result:
|
||||||
|
///
|
||||||
|
/// Password belonging to 231630279.
|
||||||
|
/// </summary>
|
||||||
|
public class DataQuery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of query values.
|
||||||
|
///
|
||||||
|
/// Single item:
|
||||||
|
///
|
||||||
|
/// QueryParams[0]
|
||||||
|
///
|
||||||
|
/// represents one lookup.
|
||||||
|
///
|
||||||
|
/// Multiple items may be processed
|
||||||
|
/// as batch requests.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> QueryParams { get; }
|
||||||
|
= new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for one storage source.
|
||||||
|
/// Loaded from gci_config.json
|
||||||
|
/// </summary>
|
||||||
|
public class DataStorageConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logical storage name.
|
||||||
|
/// Example:
|
||||||
|
/// MeterLoginPasswords
|
||||||
|
/// CalibrationParameters
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Storage implementation type.
|
||||||
|
/// </summary>
|
||||||
|
public DataStorageType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connection string,
|
||||||
|
/// file path,
|
||||||
|
/// URL etc.
|
||||||
|
/// </summary>
|
||||||
|
public string DataSource { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query template.
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// SQL:
|
||||||
|
/// SELECT Password
|
||||||
|
/// FROM Passwords
|
||||||
|
/// WHERE PcbId=QUERYPARAM
|
||||||
|
///
|
||||||
|
/// CSV:
|
||||||
|
/// SELECT [Password]
|
||||||
|
/// WHERE [PcbId]=QUERYPARAM
|
||||||
|
/// </summary>
|
||||||
|
public string QueryTemplate { get; set; }
|
||||||
|
|
||||||
|
public DataStorageConfig(
|
||||||
|
string name,
|
||||||
|
DataStorageType type,
|
||||||
|
string dataSource,
|
||||||
|
string queryTemplate)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
DataSource = dataSource;
|
||||||
|
QueryTemplate = queryTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies supported data storage implementations.
|
||||||
|
///
|
||||||
|
/// The type determines which reader implementation
|
||||||
|
/// will be created by DataStorageReaderFactory.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// LocalDatabase
|
||||||
|
/// -> DatabaseDataStorageReader
|
||||||
|
///
|
||||||
|
/// LocalCsv
|
||||||
|
/// -> CsvDataStorageReader
|
||||||
|
///
|
||||||
|
/// RestApi
|
||||||
|
/// -> RestApiDataStorageReader
|
||||||
|
///
|
||||||
|
/// Source:
|
||||||
|
///
|
||||||
|
/// gci_config.json
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// "Type":"LocalDatabase"
|
||||||
|
/// </summary>
|
||||||
|
public enum DataStorageType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// REST API endpoint.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// https://api.company.com/passwords
|
||||||
|
/// </summary>
|
||||||
|
RestApi,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database located on remote server.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// SQL server on network machine.
|
||||||
|
/// </summary>
|
||||||
|
RemoteDatabase,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database located on local machine.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// SQL LocalDB
|
||||||
|
/// SQLite
|
||||||
|
/// local SQL Server instance
|
||||||
|
/// </summary>
|
||||||
|
LocalDatabase,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// JSON source stored remotely.
|
||||||
|
/// </summary>
|
||||||
|
RemoteJson,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// JSON file stored locally.
|
||||||
|
/// </summary>
|
||||||
|
LocalJson,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSV source stored remotely.
|
||||||
|
/// </summary>
|
||||||
|
RemoteCsv,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSV file stored locally.
|
||||||
|
/// </summary>
|
||||||
|
LocalCsv
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents result returned by database-based
|
||||||
|
/// data storage readers.
|
||||||
|
///
|
||||||
|
/// Supports both:
|
||||||
|
///
|
||||||
|
/// - Single-row lookups
|
||||||
|
/// (e.g. MeterLoginPasswords)
|
||||||
|
///
|
||||||
|
/// - Multi-row queries
|
||||||
|
/// (e.g. PreAdjustmentCalibrationParams)
|
||||||
|
/// </summary>
|
||||||
|
internal class DatabaseSearchResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether at least one record
|
||||||
|
/// was found.
|
||||||
|
/// </summary>
|
||||||
|
public bool Found { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executed SQL query text.
|
||||||
|
/// Mainly intended for diagnostics
|
||||||
|
/// and troubleshooting.
|
||||||
|
/// </summary>
|
||||||
|
public string Query { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First returned row represented as
|
||||||
|
/// column/value pairs.
|
||||||
|
///
|
||||||
|
/// Preserved for backward compatibility
|
||||||
|
/// with existing readers expecting
|
||||||
|
/// a single database record.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object> Values { get; }
|
||||||
|
= new Dictionary<string, object>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All returned rows represented as
|
||||||
|
/// a collection of column/value dictionaries.
|
||||||
|
///
|
||||||
|
/// Intended for queries returning
|
||||||
|
/// multiple records.
|
||||||
|
/// </summary>
|
||||||
|
public List<Dictionary<string, object>> Rows { get; }
|
||||||
|
= new List<Dictionary<string, object>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents diagnostic result of data storage operations.
|
||||||
|
///
|
||||||
|
/// Used for:
|
||||||
|
/// - source validation
|
||||||
|
/// - query validation
|
||||||
|
/// - connection tests
|
||||||
|
/// - detailed diagnostic logging
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// TestSource()
|
||||||
|
/// -> connection successful
|
||||||
|
///
|
||||||
|
/// TestQuery()
|
||||||
|
/// -> column validation
|
||||||
|
///
|
||||||
|
/// Can also carry additional payload data.
|
||||||
|
/// </summary>
|
||||||
|
public class ReaderDiagnosticResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether operation completed successfully.
|
||||||
|
/// </summary>
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Human readable summary message.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// "Connection successful."
|
||||||
|
///
|
||||||
|
/// or:
|
||||||
|
///
|
||||||
|
/// "Column not found."
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detailed diagnostic output lines.
|
||||||
|
///
|
||||||
|
/// Used for debugging and troubleshooting.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Diagnostics { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional operation payload.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// loaded CSV lines
|
||||||
|
/// SQL test value
|
||||||
|
/// parsed content
|
||||||
|
/// </summary>
|
||||||
|
public object Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates empty diagnostic result.
|
||||||
|
/// </summary>
|
||||||
|
public ReaderDiagnosticResult()
|
||||||
|
{
|
||||||
|
Diagnostics = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates successful diagnostic result.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">
|
||||||
|
/// Success message.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Successful result object.
|
||||||
|
/// </returns>
|
||||||
|
public static ReaderDiagnosticResult SuccessResult(
|
||||||
|
string message = "OK")
|
||||||
|
{
|
||||||
|
return new ReaderDiagnosticResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates failed diagnostic result.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">
|
||||||
|
/// Failure message.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Failed result object.
|
||||||
|
/// </returns>
|
||||||
|
public static ReaderDiagnosticResult Failure(
|
||||||
|
string message)
|
||||||
|
{
|
||||||
|
return new ReaderDiagnosticResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts diagnostic information into display text.
|
||||||
|
///
|
||||||
|
/// Output contains:
|
||||||
|
///
|
||||||
|
/// diagnostic lines
|
||||||
|
/// +
|
||||||
|
/// summary message
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Formatted text representation.
|
||||||
|
/// </returns>
|
||||||
|
public string ToDisplayDiag()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (Diagnostics != null &&
|
||||||
|
Diagnostics.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (string line in Diagnostics)
|
||||||
|
sb.AppendLine(line);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Message))
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Message))
|
||||||
|
sb.AppendLine(Message);
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+394
@@ -0,0 +1,394 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Searching;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
|
||||||
|
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
|
||||||
|
|
||||||
|
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads and searches CSV based data sources.
|
||||||
|
///
|
||||||
|
/// Supported query syntax:
|
||||||
|
/// SELECT [ReturnColumn] WHERE [MatchColumn] = QUERYPARAM
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// SELECT [Password] WHERE [PcbId] = QUERYPARAM
|
||||||
|
///
|
||||||
|
/// Supports:
|
||||||
|
/// - column names: [ColumnName]
|
||||||
|
/// - column indexes: COLUMN(n)
|
||||||
|
///
|
||||||
|
/// Trace:
|
||||||
|
/// MeterLoginPasswordReader
|
||||||
|
/// -> CsvDataStorageReader.GetData()
|
||||||
|
/// -> ConnectToSource()
|
||||||
|
/// -> ExecuteQuery()
|
||||||
|
/// </summary>
|
||||||
|
public class CsvDataStorageReader : IDataStorageReader
|
||||||
|
{
|
||||||
|
private readonly DataStorageConfig config;
|
||||||
|
|
||||||
|
private string resolvedPath;
|
||||||
|
private string[] loadedLines;
|
||||||
|
private string[] loadedHeaders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates CSV data storage reader using provided configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">
|
||||||
|
/// Data storage configuration loaded from gci_config.json.
|
||||||
|
/// </param>
|
||||||
|
public CsvDataStorageReader(DataStorageConfig config)
|
||||||
|
{
|
||||||
|
this.config = config ?? throw new ArgumentNullException(nameof(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads values from CSV using configured QueryTemplate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">
|
||||||
|
/// Query parameters used for lookup.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// ReaderDiagnosticResult containing lookup result.
|
||||||
|
/// </returns>
|
||||||
|
public object GetData(DataQuery query)
|
||||||
|
{
|
||||||
|
if (query == null)
|
||||||
|
throw new ArgumentNullException(nameof(query));
|
||||||
|
|
||||||
|
ReaderDiagnosticResult sourceResult = ConnectToSource(true);
|
||||||
|
if (!sourceResult.Success)
|
||||||
|
throw new InvalidOperationException(sourceResult.Message);
|
||||||
|
|
||||||
|
ReaderDiagnosticResult queryResult = ExecuteQuery(query, true);
|
||||||
|
if (!queryResult.Success)
|
||||||
|
throw new InvalidOperationException(queryResult.Message);
|
||||||
|
|
||||||
|
return queryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates CSV source accessibility and content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enableDiagnostics">
|
||||||
|
/// Enables detailed diagnostic output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Diagnostic result.
|
||||||
|
/// </returns>
|
||||||
|
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
|
||||||
|
{
|
||||||
|
return ConnectToSource(enableDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates configured QueryTemplate against CSV header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enableDiagnostics">
|
||||||
|
/// Enables detailed diagnostic output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Diagnostic result.
|
||||||
|
/// </returns>
|
||||||
|
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
|
||||||
|
{
|
||||||
|
ReaderDiagnosticResult sourceResult = ConnectToSource(enableDiagnostics);
|
||||||
|
if (!sourceResult.Success)
|
||||||
|
return sourceResult;
|
||||||
|
|
||||||
|
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log(result, enableDiagnostics, "Starting QueryTemplate validation.");
|
||||||
|
|
||||||
|
SearchOrderDefinition definition =
|
||||||
|
SearchOrderParser.Parse(config.QueryTemplate);
|
||||||
|
|
||||||
|
int selectIndex = ResolveColumnIndex(
|
||||||
|
loadedHeaders,
|
||||||
|
definition.SelectColumn);
|
||||||
|
|
||||||
|
int whereIndex = ResolveColumnIndex(
|
||||||
|
loadedHeaders,
|
||||||
|
definition.WhereColumn);
|
||||||
|
|
||||||
|
Log(result, enableDiagnostics, "QueryTemplate parsed successfully.");
|
||||||
|
Log(result, enableDiagnostics, "Select column index: " + selectIndex);
|
||||||
|
Log(result, enableDiagnostics, "Where column index: " + whereIndex);
|
||||||
|
|
||||||
|
result.Success = true;
|
||||||
|
result.Message = "QueryTemplate validation finished successfully.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
result.Message = ex.Message;
|
||||||
|
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens CSV file and loads its content.
|
||||||
|
/// Validates file existence and header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enableDiagnostics">
|
||||||
|
/// Enables detailed diagnostic output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Source validation result.
|
||||||
|
/// </returns>
|
||||||
|
private ReaderDiagnosticResult ConnectToSource(bool enableDiagnostics)
|
||||||
|
{
|
||||||
|
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log(result, enableDiagnostics, "Starting CSV source connection test.");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(config.DataSource))
|
||||||
|
throw new InvalidOperationException("CSV data source is empty.");
|
||||||
|
|
||||||
|
resolvedPath = Path.GetFullPath(config.DataSource);
|
||||||
|
|
||||||
|
Log(result, enableDiagnostics, "Resolved full path: " + resolvedPath);
|
||||||
|
|
||||||
|
if (!File.Exists(resolvedPath))
|
||||||
|
throw new FileNotFoundException("CSV file was not found.", resolvedPath);
|
||||||
|
|
||||||
|
loadedLines = File.ReadAllLines(resolvedPath);
|
||||||
|
|
||||||
|
if (loadedLines == null || loadedLines.Length == 0)
|
||||||
|
throw new InvalidOperationException("CSV file is empty.");
|
||||||
|
|
||||||
|
loadedHeaders = SplitCsvLine(loadedLines[0]);
|
||||||
|
|
||||||
|
if (loadedHeaders == null || loadedHeaders.Length == 0)
|
||||||
|
throw new InvalidOperationException("CSV header is empty.");
|
||||||
|
|
||||||
|
Log(result, enableDiagnostics, "CSV line count: " + loadedLines.Length);
|
||||||
|
Log(result, enableDiagnostics, "CSV header column count: " + loadedHeaders.Length);
|
||||||
|
|
||||||
|
result.Success = true;
|
||||||
|
result.Message = "CSV source connection finished successfully.";
|
||||||
|
result.Data = loadedLines;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
result.Message = ex.Message;
|
||||||
|
result.Data = null;
|
||||||
|
|
||||||
|
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes configured QueryTemplate against loaded CSV data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">
|
||||||
|
/// Query values used for matching.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="enableDiagnostics">
|
||||||
|
/// Enables detailed diagnostic output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Query result.
|
||||||
|
/// </returns>
|
||||||
|
private ReaderDiagnosticResult ExecuteQuery(
|
||||||
|
DataQuery query,
|
||||||
|
bool enableDiagnostics)
|
||||||
|
{
|
||||||
|
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (query.QueryParams == null || query.QueryParams.Count == 0)
|
||||||
|
throw new InvalidOperationException("DataQuery.QueryParams is empty.");
|
||||||
|
|
||||||
|
SearchOrderDefinition definition =
|
||||||
|
SearchOrderParser.Parse(config.QueryTemplate);
|
||||||
|
|
||||||
|
int selectIndex = ResolveColumnIndex(
|
||||||
|
loadedHeaders,
|
||||||
|
definition.SelectColumn);
|
||||||
|
|
||||||
|
int whereIndex = ResolveColumnIndex(
|
||||||
|
loadedHeaders,
|
||||||
|
definition.WhereColumn);
|
||||||
|
|
||||||
|
List<string> matchedValues = new List<string>();
|
||||||
|
|
||||||
|
foreach (string param in query.QueryParams)
|
||||||
|
{
|
||||||
|
string queryValue = (param ?? string.Empty).Trim();
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (int lineIndex = 1; lineIndex < loadedLines.Length; lineIndex++)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(loadedLines[lineIndex]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string[] values = SplitCsvLine(loadedLines[lineIndex]);
|
||||||
|
|
||||||
|
if (whereIndex >= values.Length)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string currentValue = (values[whereIndex] ?? string.Empty).Trim();
|
||||||
|
|
||||||
|
if (!string.Equals(
|
||||||
|
currentValue,
|
||||||
|
queryValue,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string returnValue =
|
||||||
|
selectIndex < values.Length
|
||||||
|
? values[selectIndex]
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
matchedValues.Add(returnValue);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
Log(result, enableDiagnostics, "No match found for: " + queryValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Success = true;
|
||||||
|
|
||||||
|
if (query.QueryParams.Count == 1)
|
||||||
|
{
|
||||||
|
result.Data = matchedValues.Count > 0 ? matchedValues[0] : null;
|
||||||
|
result.Message = matchedValues.Count > 0
|
||||||
|
? "Value found."
|
||||||
|
: "No match found.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Data = matchedValues;
|
||||||
|
result.Message =
|
||||||
|
"Batch query finished. Matches found: " +
|
||||||
|
matchedValues.Count +
|
||||||
|
" of " +
|
||||||
|
query.QueryParams.Count +
|
||||||
|
".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
result.Message = ex.Message;
|
||||||
|
result.Data = null;
|
||||||
|
|
||||||
|
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves column index either by name or by explicit index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headers">
|
||||||
|
/// CSV header columns.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="columnReference">
|
||||||
|
/// Parsed QueryTemplate column definition.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Zero-based column index.
|
||||||
|
/// </returns>
|
||||||
|
private int ResolveColumnIndex(
|
||||||
|
string[] headers,
|
||||||
|
ColumnReference columnReference)
|
||||||
|
{
|
||||||
|
if (columnReference == null)
|
||||||
|
throw new InvalidOperationException("Column reference is null.");
|
||||||
|
|
||||||
|
if (columnReference.HasIndex)
|
||||||
|
{
|
||||||
|
int index = columnReference.Index.Value;
|
||||||
|
|
||||||
|
if (index < 0 || index >= headers.Length)
|
||||||
|
throw new InvalidOperationException("Column index is out of range.");
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnReference.HasName)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < headers.Length; i++)
|
||||||
|
{
|
||||||
|
if (string.Equals(
|
||||||
|
headers[i]?.Trim(),
|
||||||
|
columnReference.Name?.Trim(),
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Column '" + columnReference.Name + "' was not found in CSV header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Column reference is not defined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits CSV line using common separators.
|
||||||
|
/// Supports:
|
||||||
|
/// ';'
|
||||||
|
/// ','
|
||||||
|
/// '\t'
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">
|
||||||
|
/// Input CSV line.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Parsed columns.
|
||||||
|
/// </returns>
|
||||||
|
private string[] SplitCsvLine(string line)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
return new string[0];
|
||||||
|
|
||||||
|
if (line.Contains(";"))
|
||||||
|
return line.Split(';');
|
||||||
|
|
||||||
|
if (line.Contains(","))
|
||||||
|
return line.Split(',');
|
||||||
|
|
||||||
|
if (line.Contains("\t"))
|
||||||
|
return line.Split('\t');
|
||||||
|
|
||||||
|
return new[] { line };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds diagnostic line when diagnostics are enabled.
|
||||||
|
/// </summary>
|
||||||
|
private void Log(
|
||||||
|
ReaderDiagnosticResult result,
|
||||||
|
bool enableDiagnostics,
|
||||||
|
string message)
|
||||||
|
{
|
||||||
|
if (!enableDiagnostics || result == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
result.Diagnostics.Add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user