diff --git a/server/machines/mazak/DataAPI.cs b/server/machines/mazak/db/DataAPI.cs similarity index 98% rename from server/machines/mazak/DataAPI.cs rename to server/machines/mazak/db/DataAPI.cs index f1988df5..cb565168 100644 --- a/server/machines/mazak/DataAPI.cs +++ b/server/machines/mazak/db/DataAPI.cs @@ -33,7 +33,6 @@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT using System; using System.Collections.Generic; -using System.Data; namespace MazakMachineInterface { @@ -373,6 +372,11 @@ public record LogEntry public string FromPosition { get; init; } } + public record MazakAllDataAndLogs : MazakAllData + { + public IReadOnlyList Logs { get; init; } + } + public interface ICurrentLoadActions { IEnumerable CurrentLoadActions(); @@ -382,12 +386,11 @@ public interface IMazakDB { MazakDbType MazakType { get; } MazakAllData LoadAllData(); + MazakAllDataAndLogs LoadAllDataAndLogs(string maxLogID); IEnumerable LoadPrograms(); IEnumerable LoadTools(); void Save(MazakWriteData data, string prefix); - - TResult WithReadDBConnection(Func action); } public record NewMazakProgram diff --git a/server/machines/mazak/sync/LogCSV.cs b/server/machines/mazak/db/LogCSV.cs similarity index 100% rename from server/machines/mazak/sync/LogCSV.cs rename to server/machines/mazak/db/LogCSV.cs diff --git a/server/machines/mazak/db/LogVerE.cs b/server/machines/mazak/db/LogVerE.cs new file mode 100644 index 00000000..842d5fb6 --- /dev/null +++ b/server/machines/mazak/db/LogVerE.cs @@ -0,0 +1,215 @@ +/* Copyright (c) 2024, John Lenz + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of John Lenz, Black Maple Software, SeedTactics, + nor the names of other contributors may be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; + +namespace MazakMachineInterface; + +public static class LogDataVerE +{ + private const string DateTimeFormat = "yyyyMMddHHmmss"; + + public static List LoadLog(string lastForeignID, IDbConnection conn, IDbTransaction trans) + { + if ( + !System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows + ) + ) + { + throw new Exception("VerE only only supported on windows"); + } + + using (System.Data.OleDb.OleDbCommand cmd = (System.Data.OleDb.OleDbCommand)conn.CreateCommand()) + { + ((System.Data.IDbCommand)cmd).Transaction = trans; + + long epoch = 1; + long lastID = 0; + DateTime lastDate = DateTime.MinValue; + bool useDate = false; + string[] s = lastForeignID.Split('-'); + if (s.Length == 1) + { + epoch = 1; + if (!long.TryParse(s[0], out lastID)) + useDate = true; + } + else if (s.Length == 2) + { + if (!long.TryParse(s[0], out epoch)) + useDate = true; + if (!long.TryParse(s[1], out lastID)) + useDate = true; + } + else if (s.Length == 3) + { + if (!long.TryParse(s[0], out epoch)) + useDate = true; + if (!long.TryParse(s[1], out lastID)) + useDate = true; + lastDate = DateTime.ParseExact(s[2], DateTimeFormat, null); + } + else + { + useDate = true; + } + + if (useDate) + { + cmd.CommandText = + "SELECT ID, Date, LogMessageCode, ResourceNumber, PartName, ProcessNumber," + + "FixedQuantity, PalletNumber, ProgramNumber, FromPosition, ToPosition " + + "FROM Log WHERE Date > ? ORDER BY ID ASC"; + var param = cmd.CreateParameter(); + param.OleDbType = System.Data.OleDb.OleDbType.Date; + param.Value = DateTime.Now.AddDays(-7); + cmd.Parameters.Add(param); + } + else + { + CheckIDRollover(trans, conn, ref epoch, ref lastID, lastDate); + + cmd.CommandText = + "SELECT ID, Date, LogMessageCode, ResourceNumber, PartName, ProcessNumber," + + "FixedQuantity, PalletNumber, ProgramNumber, FromPosition, ToPosition " + + "FROM Log WHERE ID > ? ORDER BY ID ASC"; + var param = cmd.CreateParameter(); + param.OleDbType = System.Data.OleDb.OleDbType.Numeric; + param.Value = lastID; + cmd.Parameters.Add(param); + } + + var ret = new List(); + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + if (reader.IsDBNull(0)) + continue; + if (reader.IsDBNull(1)) + continue; + if (reader.IsDBNull(2)) + continue; + if (!Enum.IsDefined(typeof(LogCode), reader.GetInt32(2))) + continue; + + string fullPartName = reader.IsDBNull(4) ? "" : reader.GetString(4); + int idx = fullPartName.IndexOf(':'); + + var e = new LogEntry() + { + ForeignID = + epoch.ToString() + + "-" + + reader.GetInt32(0).ToString() + + "-" + + reader.GetDateTime(1).ToString(DateTimeFormat), + TimeUTC = new DateTime(reader.GetDateTime(1).Ticks, DateTimeKind.Local).ToUniversalTime(), + Code = (LogCode)reader.GetInt32(2), + StationNumber = reader.IsDBNull(3) ? -1 : reader.GetInt32(3), + FullPartName = fullPartName, + JobPartName = (idx > 0) ? fullPartName.Substring(0, idx) : fullPartName, + Process = reader.IsDBNull(5) ? 1 : reader.GetInt32(5), + FixedQuantity = reader.IsDBNull(6) ? 1 : reader.GetInt32(6), + Pallet = reader.IsDBNull(7) ? -1 : reader.GetInt32(7), + Program = reader.IsDBNull(8) ? "" : reader.GetInt32(8).ToString(), + FromPosition = reader.IsDBNull(9) ? "" : reader.GetString(9), + TargetPosition = reader.IsDBNull(10) ? "" : reader.GetString(10), + }; + + ret.Add(e); + } + } + trans.Commit(); + + return ret; + } + } + + private static void CheckIDRollover( + System.Data.IDbTransaction trans, + System.Data.IDbConnection conn, + ref long epoch, + ref long lastID, + DateTime lastDate + ) + { + if ( + !System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows + ) + ) + { + throw new Exception("VerE only only supported on windows"); + } + + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = trans; + cmd.CommandText = "SELECT Date FROM Log WHERE ID = ?"; + var param = (System.Data.OleDb.OleDbParameter)cmd.CreateParameter(); + param.OleDbType = System.Data.OleDb.OleDbType.Numeric; + param.Value = lastID; + cmd.Parameters.Add(param); + + using (var reader = cmd.ExecuteReader()) + { + bool foundLine = false; + while (reader.Read()) + { + foundLine = true; + if (lastDate != DateTime.MinValue && reader.GetDateTime(0) != lastDate) + { + //roll to new epoch since the date for this ID is different + epoch += 1; + lastID = 0; + } + break; + } + + if (!foundLine) + { + //roll to new epoch since no ID is found, the data has been deleted + epoch += 1; + lastID = 0; + } + } + } + } +} diff --git a/server/machines/mazak/OpenDatabaseKit.cs b/server/machines/mazak/db/OpenDatabaseKit.cs similarity index 96% rename from server/machines/mazak/OpenDatabaseKit.cs rename to server/machines/mazak/db/OpenDatabaseKit.cs index 19ad67ba..23c8724d 100644 --- a/server/machines/mazak/OpenDatabaseKit.cs +++ b/server/machines/mazak/db/OpenDatabaseKit.cs @@ -54,8 +54,9 @@ public sealed class OpenDatabaseKitDB : IMazakDB private readonly string _readConnStr; private readonly string _writeConnStr; private readonly ICurrentLoadActions _loadOper; + private readonly MazakConfig _cfg; - public MazakDbType MazakType { get; } + public MazakDbType MazakType => _cfg.DBType; private void CheckReadyForConnect() { @@ -69,7 +70,7 @@ private void CheckReadyForConnect() public OpenDatabaseKitDB(MazakConfig cfg, ICurrentLoadActions loadOper) { - MazakType = cfg.DBType; + _cfg = cfg; _loadOper = loadOper; if (MazakType != MazakDbType.MazakSmooth) @@ -1158,16 +1159,47 @@ public MazakAllData LoadAllData() { var data = WithReadDBConnection(conn => { - using (var trans = conn.BeginTransaction()) - { - var ret = LoadAllData(conn, trans); - trans.Commit(); - return ret; - } + using var trans = conn.BeginTransaction(); + var ret = LoadAllData(conn, trans); + return ret; }); return data; } + + public MazakAllDataAndLogs LoadAllDataAndLogs(string maxLogID) + { + return WithReadDBConnection(conn => + { + using var trans = conn.BeginTransaction(); + var data = LoadAllData(conn, trans); + + IReadOnlyList logs; + + if (MazakType == MazakDbType.MazakVersionE) + { + logs = LogDataVerE.LoadLog(maxLogID, conn, trans); + } + else + { + logs = LogCSVParsing.LoadLog(maxLogID, _cfg.LogCSVPath); + } + + return new MazakAllDataAndLogs() + { + Schedules = data.Schedules, + LoadActions = data.LoadActions, + PalletSubStatuses = data.PalletSubStatuses, + PalletPositions = data.PalletPositions, + Alarms = data.Alarms, + Parts = data.Parts, + Pallets = data.Pallets, + MainPrograms = data.MainPrograms, + Fixtures = data.Fixtures, + Logs = logs, + }; + }); + } #endregion } diff --git a/server/machines/mazak/sync/LogVerE.cs b/server/machines/mazak/sync/LogVerE.cs deleted file mode 100644 index ebd0d655..00000000 --- a/server/machines/mazak/sync/LogVerE.cs +++ /dev/null @@ -1,226 +0,0 @@ -/* Copyright (c) 2024, John Lenz - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of John Lenz, Black Maple Software, SeedTactics, - nor the names of other contributors may be used to endorse or - promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MazakMachineInterface; - -public static class LogDataVerE -{ - private const string DateTimeFormat = "yyyyMMddHHmmss"; - - public static List LoadLog(string lastForeignID, IMazakDB _readDB) - { - return _readDB.WithReadDBConnection(conn => - { - var trans = conn.BeginTransaction(); - try - { - if ( - !System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( - System.Runtime.InteropServices.OSPlatform.Windows - ) - ) - { - throw new Exception("VerE only only supported on windows"); - } - - using (System.Data.OleDb.OleDbCommand cmd = (System.Data.OleDb.OleDbCommand)conn.CreateCommand()) - { - ((System.Data.IDbCommand)cmd).Transaction = trans; - - long epoch = 1; - long lastID = 0; - DateTime lastDate = DateTime.MinValue; - bool useDate = false; - string[] s = lastForeignID.Split('-'); - if (s.Length == 1) - { - epoch = 1; - if (!long.TryParse(s[0], out lastID)) - useDate = true; - } - else if (s.Length == 2) - { - if (!long.TryParse(s[0], out epoch)) - useDate = true; - if (!long.TryParse(s[1], out lastID)) - useDate = true; - } - else if (s.Length == 3) - { - if (!long.TryParse(s[0], out epoch)) - useDate = true; - if (!long.TryParse(s[1], out lastID)) - useDate = true; - lastDate = DateTime.ParseExact(s[2], DateTimeFormat, null); - } - else - { - useDate = true; - } - - if (useDate) - { - cmd.CommandText = - "SELECT ID, Date, LogMessageCode, ResourceNumber, PartName, ProcessNumber," - + "FixedQuantity, PalletNumber, ProgramNumber, FromPosition, ToPosition " - + "FROM Log WHERE Date > ? ORDER BY ID ASC"; - var param = cmd.CreateParameter(); - param.OleDbType = System.Data.OleDb.OleDbType.Date; - param.Value = DateTime.Now.AddDays(-7); - cmd.Parameters.Add(param); - } - else - { - CheckIDRollover(trans, conn, ref epoch, ref lastID, lastDate); - - cmd.CommandText = - "SELECT ID, Date, LogMessageCode, ResourceNumber, PartName, ProcessNumber," - + "FixedQuantity, PalletNumber, ProgramNumber, FromPosition, ToPosition " - + "FROM Log WHERE ID > ? ORDER BY ID ASC"; - var param = cmd.CreateParameter(); - param.OleDbType = System.Data.OleDb.OleDbType.Numeric; - param.Value = lastID; - cmd.Parameters.Add(param); - } - - var ret = new List(); - - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - if (reader.IsDBNull(0)) - continue; - if (reader.IsDBNull(1)) - continue; - if (reader.IsDBNull(2)) - continue; - if (!Enum.IsDefined(typeof(LogCode), reader.GetInt32(2))) - continue; - - string fullPartName = reader.IsDBNull(4) ? "" : reader.GetString(4); - int idx = fullPartName.IndexOf(':'); - - var e = new LogEntry() - { - ForeignID = - epoch.ToString() - + "-" - + reader.GetInt32(0).ToString() - + "-" - + reader.GetDateTime(1).ToString(DateTimeFormat), - TimeUTC = new DateTime(reader.GetDateTime(1).Ticks, DateTimeKind.Local).ToUniversalTime(), - Code = (LogCode)reader.GetInt32(2), - StationNumber = reader.IsDBNull(3) ? -1 : reader.GetInt32(3), - FullPartName = fullPartName, - JobPartName = (idx > 0) ? fullPartName.Substring(0, idx) : fullPartName, - Process = reader.IsDBNull(5) ? 1 : reader.GetInt32(5), - FixedQuantity = reader.IsDBNull(6) ? 1 : reader.GetInt32(6), - Pallet = reader.IsDBNull(7) ? -1 : reader.GetInt32(7), - Program = reader.IsDBNull(8) ? "" : reader.GetInt32(8).ToString(), - FromPosition = reader.IsDBNull(9) ? "" : reader.GetString(9), - TargetPosition = reader.IsDBNull(10) ? "" : reader.GetString(10), - }; - - ret.Add(e); - } - } - trans.Commit(); - - return ret; - } - } - catch - { - trans.Rollback(); - throw; - } - }); - } - - private static void CheckIDRollover( - System.Data.IDbTransaction trans, - System.Data.IDbConnection conn, - ref long epoch, - ref long lastID, - DateTime lastDate - ) - { - if ( - !System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( - System.Runtime.InteropServices.OSPlatform.Windows - ) - ) - { - throw new Exception("VerE only only supported on windows"); - } - - using (var cmd = conn.CreateCommand()) - { - cmd.Transaction = trans; - cmd.CommandText = "SELECT Date FROM Log WHERE ID = ?"; - var param = (System.Data.OleDb.OleDbParameter)cmd.CreateParameter(); - param.OleDbType = System.Data.OleDb.OleDbType.Numeric; - param.Value = lastID; - cmd.Parameters.Add(param); - - using (var reader = cmd.ExecuteReader()) - { - bool foundLine = false; - while (reader.Read()) - { - foundLine = true; - if (lastDate != DateTime.MinValue && reader.GetDateTime(0) != lastDate) - { - //roll to new epoch since the date for this ID is different - epoch += 1; - lastID = 0; - } - break; - } - - if (!foundLine) - { - //roll to new epoch since no ID is found, the data has been deleted - epoch += 1; - lastID = 0; - } - } - } - } -} diff --git a/server/machines/mazak/sync/MazakSync.cs b/server/machines/mazak/sync/MazakSync.cs index 892dd945..c1293f03 100644 --- a/server/machines/mazak/sync/MazakSync.cs +++ b/server/machines/mazak/sync/MazakSync.cs @@ -47,7 +47,7 @@ public record MazakState : ICellState public required TimeSpan TimeUntilNextRefresh { get; init; } public required bool StoppedBecauseRecentMachineEnd { get; init; } public required CurrentStatus CurrentStatus { get; init; } - public required MazakAllData AllData { get; init; } + public required MazakAllDataAndLogs AllData { get; init; } } public delegate void MazakLogEventDel(LogEntry e, IRepository jobDB); @@ -167,19 +167,9 @@ ProgramRevision lookupProg(string prog, long? rev) public MazakState CalculateCellState(IRepository db) { var now = DateTime.UtcNow; - var mazakData = mazakDB.LoadAllData(); + var mazakData = mazakDB.LoadAllDataAndLogs(db.MaxForeignID()); var machineGroupName = BuildCurrentStatus.FindMachineGroupName(db); - List logs; - if (mazakConfig.DBType == MazakDbType.MazakVersionE) - { - logs = LogDataVerE.LoadLog(db.MaxForeignID(), mazakDB); - } - else - { - logs = LogCSVParsing.LoadLog(db.MaxForeignID(), mazakConfig.LogCSVPath); - } - var trans = new LogTranslation( db, mazakData, @@ -192,7 +182,7 @@ public MazakState CalculateCellState(IRepository db) var sendToExternal = new List(); var stoppedBecauseRecentMachineEnd = false; - foreach (var ev in logs) + foreach (var ev in mazakData.Logs) { try { @@ -239,7 +229,7 @@ public MazakState CalculateCellState(IRepository db) return new MazakState() { - StateUpdated = logs.Count > 0 || palStChanged, + StateUpdated = mazakData.Logs.Count > 0 || palStChanged, TimeUntilNextRefresh = mazakConfig?.DBType == MazakDbType.MazakVersionE || stoppedBecauseRecentMachineEnd ? TimeSpan.FromSeconds(15) diff --git a/server/test/mazak/SyncSpec.cs b/server/test/mazak/SyncSpec.cs index bd780287..d13a1774 100644 --- a/server/test/mazak/SyncSpec.cs +++ b/server/test/mazak/SyncSpec.cs @@ -235,7 +235,7 @@ public void LoadsEvents() ); File.WriteAllLines(Path.Combine(_tempDir, "222loadend.csv"), ["2024,6,11,4,5,9,502,,12,,1,6,4,prog,,,,"]); - var allData = new MazakAllData() + var allData = new MazakAllDataAndLogs() { MainPrograms = [], Fixtures = [], @@ -244,8 +244,9 @@ public void LoadsEvents() PalletPositions = [], Schedules = [], LoadActions = [], + Logs = LogCSVParsing.LoadLog(null, _tempDir), }; - _mazakDB.LoadAllData().Returns(allData); + _mazakDB.LoadAllDataAndLogs(Arg.Any()).Returns(allData); _sync .CalculateCellState(db) @@ -301,9 +302,9 @@ public void StopsProcessingOnRecentMachineEnd() ); _mazakDB - .LoadAllData() + .LoadAllDataAndLogs(Arg.Any()) .Returns( - new MazakAllData() + new MazakAllDataAndLogs() { MainPrograms = [], Fixtures = [], @@ -311,6 +312,7 @@ public void StopsProcessingOnRecentMachineEnd() Parts = [], Schedules = [], LoadActions = [], + Logs = LogCSVParsing.LoadLog(null, _tempDir), } ); @@ -358,7 +360,7 @@ public void QuarantinesMaterial() timeUTC: now ); - var allData = new MazakAllData() + var allData = new MazakAllDataAndLogs() { MainPrograms = [], Fixtures = [], @@ -368,8 +370,9 @@ public void QuarantinesMaterial() PalletSubStatuses = [], Schedules = [], LoadActions = [], + Logs = [], }; - _mazakDB.LoadAllData().Returns(allData); + _mazakDB.LoadAllDataAndLogs(Arg.Any()).Returns(allData); _sync .CalculateCellState(db) @@ -444,7 +447,7 @@ public void DownloadsNewJobs() Material = [], Alarms = [], }, - AllData = new MazakAllData() + AllData = new MazakAllDataAndLogs() { MainPrograms = ImmutableList .Create("1001", "1002", "1003", "1004", "1005") @@ -454,6 +457,7 @@ public void DownloadsNewJobs() Parts = [], Schedules = [], LoadActions = [], + Logs = [], }, }; @@ -521,7 +525,7 @@ public void CalculatesQueueChanges() Material = [], Alarms = [], }, - AllData = new MazakAllData() + AllData = new MazakAllDataAndLogs() { Schedules = [ @@ -560,6 +564,7 @@ public void CalculatesQueueChanges() ], }, ], + Logs = [], LoadActions = [], }, };