using MySql.Data.MySqlClient; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.IO; using System.Configuration; using System.Linq; using System.Runtime.InteropServices; using System.ServiceProcess; using System.Text; using System.Threading; using System.Threading.Tasks; namespace HanvonF710XAttendanceService { static class Program { /// True when started via console/debug entry (not SCM). internal static bool IsConsoleMode { get; private set; } private static MySqlConnection connection; //public static int TotalEmp; //static List DBusers = new List(); static bool result; private static readonly object _unreachableLock = new object(); private static readonly List<(string MachineId, string MachineIp, int SiteId)> _unreachableThisCycle = new List<(string, string, int)>(); private static void Initialize() { connection = new MySqlConnection(GetConnectionString()); } internal static string GetConnectionString() { var configured = ConfigurationManager.ConnectionStrings["HrmsDb"]?.ConnectionString; if (!string.IsNullOrWhiteSpace(configured)) { return configured; } throw new InvalidOperationException("Connection string 'HrmsDb' is not configured in App.config."); } internal static MySqlConnection OpenDbConnectionWithRetry(string context) { int attempts = GetIntAppSetting("DB_CONNECTION_RETRY_ATTEMPTS", 3); int delayMs = GetIntAppSetting("DB_CONNECTION_RETRY_DELAY_MS", 2000); Exception lastError = null; for (int attempt = 1; attempt <= attempts; attempt++) { try { var conn = new MySqlConnection(GetConnectionString()); conn.Open(); if (attempt > 1) { WriteInternalLog($"[DB] {context}: connection succeeded on attempt {attempt}/{attempts}"); } return conn; } catch (Exception ex) { lastError = ex; WriteInternalLog($"[DB] {context}: connection attempt {attempt}/{attempts} failed: {ex.Message}"); if (attempt < attempts) { Thread.Sleep(delayMs * attempt); } } } throw new InvalidOperationException($"[DB] {context}: unable to open connection after {attempts} attempts.", lastError); } private static int GetIntAppSetting(string key, int defaultValue) { try { var raw = ConfigurationManager.AppSettings[key]; if (string.IsNullOrWhiteSpace(raw)) return defaultValue; return int.TryParse(raw.Trim(), out var v) && v > 0 ? v : defaultValue; } catch { return defaultValue; } } private static bool GetBoolAppSetting(string key, bool defaultValue) { try { var value = ConfigurationManager.AppSettings[key]; if (string.IsNullOrWhiteSpace(value)) return defaultValue; return bool.TryParse(value.Trim(), out var parsed) ? parsed : defaultValue; } catch { return defaultValue; } } public const string HwDevCommDLL = @"HwDevComm.dll"; public const string HDCP_UtilsDLL = @"HDCP_Utils.dll"; [DllImport(HwDevCommDLL, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] public static extern int HwDev_Execute(string pDevInfoBuf, int nDevInfoLen, IntPtr pSendBuf, int nSendLen, ref IntPtr pRecvBuf, ref uint pRecvLen, CallBack pFuncTotalDone); [DllImport(HwDevCommDLL, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] public static extern int HwDev_Finish(ref IntPtr pRecvBuf); [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] public static extern bool GlobalUnlock(IntPtr hMem); [DllImport("kernel32.dll")] public static extern IntPtr GlobalAlloc(int uFlags, int dwBytes); [DllImport("kernel32.dll")] public static extern IntPtr GlobalFree(IntPtr hMem); [DllImport("kernel32.dll")] public static extern IntPtr GlobalLock(IntPtr hMem); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetDllDirectory(string lpPathName); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); /// /// Entry point. Runs as a Windows Service when started by the SCM; /// runs in console/debug mode when interactive or when --console / --debug is passed. /// static void Main(string[] args) { ApplicationPaths.Initialize(); try { SetDllDirectory(ApplicationPaths.BaseDirectory); } catch { } if (ShouldRunAsConsole(args)) { RunAsConsole(args); } else { ServiceBase.Run(new ServiceBase[] { new HanvonF710XWindowsService() }); } } private static bool ShouldRunAsConsole(string[] args) { if (HasArg(args, "--service", "/service")) { return false; } if (HasArg(args, "--console", "/console", "-c", "--debug", "/debug", "-d")) { return true; } #if DEBUG // F5 / debugger: prefer console so OnStart/OnStop can be exercised without InstallUtil. if (Environment.UserInteractive) { return true; } #endif return Environment.UserInteractive; } private static bool HasArg(string[] args, params string[] flags) { if (args == null || args.Length == 0 || flags == null) return false; foreach (var arg in args) { if (string.IsNullOrWhiteSpace(arg)) continue; foreach (var flag in flags) { if (string.Equals(arg.Trim(), flag, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } private static string GetArgValue(string[] args, params string[] keys) { if (args == null || keys == null) return null; foreach (var arg in args) { if (string.IsNullOrWhiteSpace(arg)) continue; foreach (var key in keys) { if (arg.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase)) { return arg.Substring(key.Length + 1); } } } return null; } private static void RunAsConsole(string[] args) { if (HasArg(args, "--test-planning", "/test-planning")) { int failed = DbToDevicePlanningTests.RunAll(); failed += NedoTemplateConverterTests.RunAll(); failed += HrmsEmployeePhotoClientTests.RunAll(); failed += HrmsEmployeeMappingTests.RunAll(); failed += DbToDeviceSummaryTests.RunAll(); failed += DbToDeviceFaceErrorsTests.RunAll(); failed += MachineUserDeleteSyncTests.RunAll(); Environment.ExitCode = failed == 0 ? 0 : 1; return; } if (HasArg(args, "--test-template-load", "/test-template-load")) { RunTemplateLoadTest(); return; } string diagnoseEmp = GetArgValue(args, "--diagnose-emp", "/diagnose-emp"); if (!string.IsNullOrWhiteSpace(diagnoseEmp)) { RunDiagnoseEmployee(diagnoseEmp.Trim()); return; } string inspectPortalPhoto = GetArgValue(args, "--inspect-portal-photo", "/inspect-portal-photo"); if (!string.IsNullOrWhiteSpace(inspectPortalPhoto)) { RunInspectPortalPhoto(inspectPortalPhoto.Trim()); return; } IsConsoleMode = true; try { AllocConsole(); } catch { } Console.Title = "Hanvon F710X Attendance Service (Console Mode)"; Console.WriteLine("========================================================"); Console.WriteLine("Hanvon F710X Attendance Service — CONSOLE / DEBUG MODE"); Console.WriteLine("Press Ctrl+C or Enter to stop."); Console.WriteLine("========================================================"); Console.WriteLine(); var service = new HanvonF710XWindowsService(); var stop = new ManualResetEvent(false); ConsoleCancelEventHandler cancelHandler = (sender, e) => { e.Cancel = true; Console.WriteLine(); Console.WriteLine("Stop requested..."); stop.Set(); }; Console.CancelKeyPress += cancelHandler; try { service.StartForConsole(args ?? Array.Empty()); Console.WriteLine("Service logic started. Waiting for stop signal..."); Console.WriteLine("(Press Enter to stop)"); // Wait for Ctrl+C or Enter on a background thread. var enterWait = Task.Run(() => { try { Console.ReadLine(); } catch { } stop.Set(); }); stop.WaitOne(); } catch (Exception ex) { Console.WriteLine("Fatal error: " + ex); WriteInternalLog("Console mode fatal error: " + ex); } finally { try { Console.CancelKeyPress -= cancelHandler; } catch { } try { Console.WriteLine("Stopping..."); service.StopForConsole(); Console.WriteLine("Stopped."); } catch (Exception ex) { Console.WriteLine("Stop error: " + ex.Message); } } } public static void WriteToFile(string Message) { string filepath = ApplicationPaths.DatedFile(ApplicationPaths.TemplateRawLogs, "ServiceLog"); LogService.EnqueueLine(filepath, Message); } public static void WriteInternalLog(string message) { try { string filepath = ApplicationPaths.DatedFile(ApplicationPaths.InternalLogs, "InternalLog"); LogService.EnqueueLine(filepath, message); } catch { // swallow internal logging failures } } public static void RecordUnreachableMachine(AttendanceMachine machine, string reason) { try { string filepath = ApplicationPaths.DatedFile(ApplicationPaths.InternalLogs, "UnreachableMachines"); LogService.EnqueueLine(filepath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\tmachine_id={machine.MachineId}\tip={machine.MachineIp}\tsite_id={machine.SiteId}\treason={reason}"); lock (_unreachableLock) { _unreachableThisCycle.Add((machine.MachineId, machine.MachineIp, machine.SiteId)); } } catch { // ignore logging failures } } public static void BeginSyncCycle() { lock (_unreachableLock) { _unreachableThisCycle.Clear(); } } public static void EndSyncCycle() { List<(string MachineId, string MachineIp, int SiteId)> snapshot; lock (_unreachableLock) { if (_unreachableThisCycle.Count == 0) { return; } snapshot = new List<(string, string, int)>(_unreachableThisCycle); } try { string filepath = ApplicationPaths.DatedFile(ApplicationPaths.InternalLogs, "UnreachableMachines"); LogService.EnqueueLine(filepath, ""); LogService.EnqueueLine(filepath, "---"); LogService.EnqueueLine(filepath, ""); LogService.EnqueueLine(filepath, "UNREACHABLE MACHINES"); LogService.EnqueueLine(filepath, "Detected At: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); LogService.EnqueueLine(filepath, "--------------------------------"); LogService.EnqueueLine(filepath, ""); var groups = snapshot .GroupBy(e => e.SiteId) .OrderBy(g => g.Key); foreach (var g in groups) { LogService.EnqueueLine(filepath, "SITE " + g.Key); foreach (var m in g.OrderBy(x => x.MachineId)) { LogService.EnqueueLine(filepath, " " + m.MachineId + " " + m.MachineIp); } LogService.EnqueueLine(filepath, ""); } } catch { // ignore logging failures } } public static List syncAttendance() { List responses = new List(); Initialize(); using (var conn = OpenDbConnectionWithRetry("syncAttendance")) { AttendanceMachineDAO attendanceMachineDAO = new AttendanceMachineDAO(); var machines = GetMachinesForAttendanceAndUsers(attendanceMachineDAO, conn, responses); foreach (var machine in machines) { machine.Status = "SYNCING"; attendanceMachineDAO.update(machine, conn); responses.Add(syncAttendance(machine, conn)); if (!string.Equals(machine.Status, "NOT CONNECTED", StringComparison.OrdinalIgnoreCase)) { machine.Status = "IDLE"; } Console.WriteLine(machine.MachineId + " => " + machine.LastSyncDate); attendanceMachineDAO.update(machine, conn); } } return responses; } public static string syncAttendance(AttendanceMachine machine, MySqlConnection conn) { if (DeviceProtocol.UseHttp()) { return syncAttendanceHttp(machine, conn); } return syncAttendanceHdcp(machine, conn); } private static string syncAttendanceHttp(AttendanceMachine machine, MySqlConnection conn) { string response = ""; DateTime prevLastSync = machine.LastSyncDate; DateTime now = DateTime.Now.AddMinutes(-5); var client = HanvonHttpApiClient.ForMachine(machine); WriteInternalLog($"[AttendanceConnect] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} http_port={ConfigurationManager.AppSettings["DEVICE_HTTP_PORT"] ?? "80"} username={MachineScope.GetDeviceUsername()} from={prevLastSync:yyyy-MM-dd HH:mm:ss}"); if (!client.TryLogin(out string loginErr)) { response = machine.MachineId + " is not connected !"; machine.Status = "NOT CONNECTED"; WriteInternalLog($"[AttendanceConnectFail] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} err={loginErr}"); RecordUnreachableMachine(machine, "syncAttendance HTTP: " + loginErr); return response; } var logs = client.GetLogs(prevLastSync, null, out string getErr); if (!string.IsNullOrEmpty(getErr) && (logs == null || logs.Count == 0)) { response = machine.MachineId + " is not connected !"; machine.Status = "NOT CONNECTED"; WriteInternalLog($"[AttendanceConnectFail] protocol=HTTP getlog machine_id={machine.MachineId} ip={machine.MachineIp} err={getErr}"); RecordUnreachableMachine(machine, "syncAttendance HTTP getlog: " + getErr); return response; } var parsedRows = new List(); foreach (var log in logs) { string acNo = !string.IsNullOrWhiteSpace(log.EnrollId) ? log.EnrollId.Trim() : (log.AcNo ?? "").Trim(); if (string.IsNullOrWhiteSpace(acNo)) { WriteInternalLog($"[AttendanceSkip] machine_id={machine.MachineId} ip={machine.MachineIp} reason=missing_enrollid name={log.Name ?? ""} time={log.CheckTime:yyyy-MM-dd HH:mm:ss}"); continue; } WriteInternalLog($"[AttendanceParsed] machine_id={machine.MachineId} ip={machine.MachineIp} enrollid={acNo} name={log.Name ?? ""} checktime={log.CheckTime:yyyy-MM-dd HH:mm:ss}"); parsedRows.Add(new Attendance(acNo, log.CheckTime, false, machine.MachineId, "1", machine.MachineIp, DateTime.Now)); } int totalFetched = parsedRows.Count; response = machine.MachineId + " has " + totalFetched + " records !"; WriteInternalLog($"[AttendanceSync] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} fetched_total={totalFetched}"); var summary = ProcessAttendanceInBatches(parsedRows, machine, conn, prevLastSync); if (GetBoolAppSetting("ATTENDANCE_DELETE_AFTER_INSERT", true) && totalFetched > 0) { if (client.CleanLog(out string cleanErr)) { WriteInternalLog($"[AttendanceDelete] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} cleanlog=OK"); } else { WriteInternalLog($"[AttendanceDelete] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} cleanlog=FAIL err={cleanErr}"); } } machine.LastSyncDate = now; WriteInternalLog( $"[AttendanceSync] machine_id={machine.MachineId} ip={machine.MachineIp} prev_last_sync={prevLastSync:yyyy-MM-dd HH:mm:ss} new_last_sync={machine.LastSyncDate:yyyy-MM-dd HH:mm:ss} fetched_total={totalFetched} first_checktime={(summary.FirstCheckTime.HasValue ? summary.FirstCheckTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "NULL")} last_checktime={(summary.LastCheckTime.HasValue ? summary.LastCheckTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "NULL")}" ); return response; } private static string syncAttendanceHdcp(AttendanceMachine machine, MySqlConnection conn) { string response = ""; DateTime prevLastSync = machine.LastSyncDate; string requestedStartTime = prevLastSync.ToString("yyyy-MM-dd HH:mm:ss"); DateTime now = DateTime.Now.AddMinutes(-5); CallBack myCallBack = new CallBack(Program.BeCalled); string devInfo = machine.GetDeviceInfo(); string str4 = "GetRecord(start_time=\"" + requestedStartTime + "\")"; string str5 = ""; uint num2 = 0; WriteInternalLog($"[AttendanceConnect] protocol=HDCP machine_id={machine.MachineId} ip={machine.MachineIp} port={machine.PortNumber} username={MachineScope.GetDeviceUsername()} devInfo={machine.GetDeviceInfoForLog()} cmd={str4}"); int connectRc = test(devInfo, devInfo.Length, str4, str4.Length, ref str5, ref num2, myCallBack); if (connectRc != 0) { Console.WriteLine("Not Connected"); response = machine.MachineId + " is not connected !"; machine.Status = "NOT CONNECTED"; WriteInternalLog($"[AttendanceConnectFail] machine_id={machine.MachineId} ip={machine.MachineIp} port={machine.PortNumber} rc={connectRc}"); RecordUnreachableMachine(machine, $"syncAttendance: device not connected rc={connectRc} port={machine.PortNumber}"); } else { Console.WriteLine("Connected"); var parsedRows = ParseAttendanceRows(str5, machine); int totalFetched = parsedRows.Count; response = machine.MachineId + " has " + totalFetched + " records !"; Console.WriteLine(response); var summary = ProcessAttendanceInBatches(parsedRows, machine, conn, prevLastSync); machine.LastSyncDate = now; WriteInternalLog( $"[AttendanceSync] machine_id={machine.MachineId} ip={machine.MachineIp} prev_last_sync={prevLastSync:yyyy-MM-dd HH:mm:ss} new_last_sync={machine.LastSyncDate:yyyy-MM-dd HH:mm:ss} requested_start_time={requestedStartTime} fetched_total={totalFetched} first_checktime={(summary.FirstCheckTime.HasValue ? summary.FirstCheckTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "NULL")} last_checktime={(summary.LastCheckTime.HasValue ? summary.LastCheckTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "NULL")}" ); } return response; } public static string getEmployeeInfo(int employeeId) { string response = ""; CallBack myCallBack = new CallBack(Program.BeCalled); Initialize(); if (OpenConnection() == true) { AttendanceMachineDAO attendanceMachineDAO = new AttendanceMachineDAO(); List machines = attendanceMachineDAO.getAttendanceMachines(connection, "2"); foreach (var machine in machines) { if (machine.MachineId.Equals("SITE2-MACHINE-9")) { string devInfo = machine.GetDeviceInfo(); string str4 = "GetEmployee(id=\"" + employeeId + "\")"; string str5 = ""; uint num2 = 0; if (test(devInfo, devInfo.Length, str4, str4.Length, ref str5, ref num2, myCallBack) != 0) { Console.WriteLine("Not Connected"); response = machine.MachineId + " is not connected !"; RecordUnreachableMachine(machine, "getEmployeeInfo: device not connected"); } else { Console.WriteLine("Connected"); Console.WriteLine(str5); Console.WriteLine("Process Completed"); } } } CloseConnection(); } return response; } public static List syncMachineUsers() { List responses = new List(); CallBack myCallBack = new CallBack(Program.BeCalled); Initialize(); using (var conn = OpenDbConnectionWithRetry("syncMachineUsers")) { AttendanceMachineUserDAO attendanceMachineUsersDAO = new AttendanceMachineUserDAO(); List DBusers = null; try { DBusers = attendanceMachineUsersDAO.getActiveEmployees(conn); } catch (Exception ex) { WriteInternalLog("syncMachineUsers: getAllEmployees failed (attempt 1): " + ex.Message); try { Thread.Sleep(2000); Initialize(); using (var retryConn = OpenDbConnectionWithRetry("syncMachineUsers-retry")) { DBusers = attendanceMachineUsersDAO.getActiveEmployees(retryConn); } WriteInternalLog("syncMachineUsers: getAllEmployees succeeded on retry (attempt 2)."); } catch (Exception ex2) { WriteInternalLog("syncMachineUsers: getAllEmployees failed (attempt 2): " + ex2.Message); throw; } } AttendanceMachineDAO attendanceMachineDAO = new AttendanceMachineDAO(); var machines = GetMachinesForAttendanceAndUsers(attendanceMachineDAO, conn, responses); foreach (var machine in machines) { if (DeviceProtocol.UseHttp()) { syncMachineUsersHttp(machine, conn, attendanceMachineDAO, attendanceMachineUsersDAO, DBusers, responses); } else { syncMachineUsersHdcp(machine, conn, attendanceMachineDAO, attendanceMachineUsersDAO, DBusers, responses, myCallBack); } } Console.WriteLine("COMPLETED !"); } return responses; } private static void syncMachineUsersHttp( AttendanceMachine machine, MySqlConnection conn, AttendanceMachineDAO attendanceMachineDAO, AttendanceMachineUserDAO attendanceMachineUsersDAO, List DBusers, List responses) { var client = HanvonHttpApiClient.ForMachine(machine); WriteInternalLog($"[MachineUserConnect] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp}"); if (!client.TryLogin(out string loginErr)) { responses.Add(machine.MachineId + " is not connected !"); WriteInternalLog($"[MachineUserConnectFail] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} err={loginErr}"); RecordUnreachableMachine(machine, "syncMachineUsers HTTP: " + loginErr); return; } var users = client.GetUserList(out string listErr); if (!string.IsNullOrEmpty(listErr) && (users == null || users.Count == 0)) { responses.Add(machine.MachineId + " is not connected !"); WriteInternalLog($"[MachineUserConnectFail] protocol=HTTP getuserlist machine_id={machine.MachineId} err={listErr}"); RecordUnreachableMachine(machine, "syncMachineUsers HTTP getuserlist: " + listErr); return; } int totalEmp = users?.Count ?? 0; attendanceMachineDAO.UpdateTotalEmpInMachines(machine.MachineId, totalEmp, conn); responses.Add(machine.MachineId + " -> " + totalEmp); WriteInternalLog($"[MachineUserSync] protocol=HTTP machine_id={machine.MachineId} ip={machine.MachineIp} total={totalEmp}"); try { var toDelete = attendanceMachineUsersDAO.GetDeletionRequestedSerials(conn, machine.MachineId); if (toDelete.Count > 0) { var pendingItems = new List(); foreach (var empId in toDelete) { if (string.IsNullOrWhiteSpace(empId)) { continue; } string trimmed = empId.Trim(); var match = users.FirstOrDefault(u => string.Equals(u.SerialNumber, trimmed, StringComparison.OrdinalIgnoreCase) || string.Equals(u.DeviceUserId, trimmed, StringComparison.OrdinalIgnoreCase) || string.Equals(u.Name, trimmed, StringComparison.OrdinalIgnoreCase)); string deviceEnrollId = match != null ? match.DeviceUserId : trimmed; pendingItems.Add(new MachineUserDeleteItem(trimmed, deviceEnrollId)); } int batchSize = MachineUserDeleteSettings.GetBatchSize(); var deleteResult = MachineUserDeleteSync.Execute( machine.MachineId, machine.MachineIp, pendingItems, batchSize, (IReadOnlyList deviceIds, out string delErr) => client.DeleteUsers(deviceIds, out delErr), serial => { attendanceMachineUsersDAO.MarkUserDeleted(conn, machine.MachineId, serial); string line = serial + " removed from " + machine.MachineId; responses.Add(line); WriteInternalLog(line); }, WriteInternalLog); if (deleteResult.FailedCount > 0) { WriteInternalLog($"syncMachineUsers HTTP deleteusers completed with failures. machine_id={machine.MachineId} success={deleteResult.SuccessCount} failed={deleteResult.FailedCount}"); } } } catch (Exception exDel) { WriteInternalLog("syncMachineUsers HTTP: deletion-request handling failed: " + exDel); } foreach (var u in users) { bool exists = false; foreach (var dbUser in DBusers) { if (dbUser.SerialNumber != null && dbUser.MachineId != null && dbUser.SerialNumber.Contains(u.SerialNumber) && dbUser.MachineId.Contains(machine.MachineId)) { exists = true; break; } } if (!exists) { attendanceMachineUsersDAO.Add(new AttendanceMachineUser(machine.MachineId, u.SerialNumber, u.Name ?? ""), conn); } } } private static void syncMachineUsersHdcp( AttendanceMachine machine, MySqlConnection conn, AttendanceMachineDAO attendanceMachineDAO, AttendanceMachineUserDAO attendanceMachineUsersDAO, List DBusers, List responses, CallBack myCallBack) { string devInfo = machine.GetDeviceInfo(); string str4 = "GetEmployeeID()"; string str5 = ""; uint num2 = 0; WriteInternalLog($"[MachineUserConnect] protocol=HDCP machine_id={machine.MachineId} ip={machine.MachineIp} port={machine.PortNumber} cmd={str4}"); int userRc = test(devInfo, devInfo.Length, str4, str4.Length, ref str5, ref num2, myCallBack); if (userRc != 0) { Console.WriteLine("Not Connected"); responses.Add(machine.MachineId + " is not connected !"); WriteInternalLog($"[MachineUserConnectFail] machine_id={machine.MachineId} ip={machine.MachineIp} port={machine.PortNumber} rc={userRc}"); RecordUnreachableMachine(machine, $"syncMachineUsers: device not connected rc={userRc} port={machine.PortNumber}"); return; } Console.WriteLine("Connected"); int TotalEmp = GetLoopCnt(str5); attendanceMachineDAO.UpdateTotalEmpInMachines(machine.MachineId, TotalEmp, conn); responses.Add(machine.MachineId + " -> " + TotalEmp); Console.WriteLine(machine.MachineId + " -> " + TotalEmp); try { var toDelete = attendanceMachineUsersDAO.GetDeletionRequestedSerials(conn, machine.MachineId); foreach (var empId in toDelete) { if (string.IsNullOrWhiteSpace(empId)) continue; string delCmd = "DeleteEmployee(id=\"" + empId + "\")"; string delResp = ""; uint delRecv = 0; if (test(devInfo, devInfo.Length, delCmd, delCmd.Length, ref delResp, ref delRecv, myCallBack) == 0) { attendanceMachineUsersDAO.MarkUserDeleted(conn, machine.MachineId, empId); string line = empId + " removed from " + machine.MachineId; responses.Add(line); WriteInternalLog(line); } } } catch (Exception exDel) { WriteInternalLog("syncMachineUsers: deletion-request handling failed: " + exDel.ToString()); } saveMachineUsers(str5, machine, DBusers, conn); } internal static List GetMachinesForAttendanceAndUsers(AttendanceMachineDAO attendanceMachineDAO, MySqlConnection connection, List logs) { if (!TryLoadDeviceSettings(out var settings, logs)) { logs?.Add("DeviceSettings could not be loaded; no machines selected."); return new List(); } var attendanceScope = ConfigurationManager.AppSettings["ATTENDANCE_SCOPE"] ?? "CENTRAL"; if (!string.Equals(attendanceScope, "SITE", StringComparison.OrdinalIgnoreCase)) { // Centralized mode: process all DB machines (no DeviceSettings filtering). var allCentral = attendanceMachineDAO.getAllAttendanceMachines(connection); logs?.Add($"Attendance scope=CENTRAL. Hanvon DB machines fetched={allCentral.Count}"); return allCentral; } // SITE / local mode: fetch machines from DB scoped by config SiteId (like the old SITE_ID behavior), // then optionally apply DeviceSettings IP filtering within that site scope. List dbMachines; if (settings.SiteId.HasValue) { dbMachines = attendanceMachineDAO.getAttendanceMachines(connection, settings.SiteId.Value.ToString()); logs?.Add($"DeviceSettings SiteId={settings.SiteId.Value}. DB machines fetched={dbMachines.Count}"); } else { dbMachines = attendanceMachineDAO.getAllAttendanceMachines(connection); logs?.Add($"DeviceSettings SiteId not set. DB machines fetched={dbMachines.Count}"); } if (dbMachines == null) dbMachines = new List(); // Compare config IPs only against machines of this site scope. string NormalizeIp(string ip) { if (string.IsNullOrWhiteSpace(ip)) return ""; return new string(ip.Trim().Where(c => !char.IsWhiteSpace(c)).ToArray()); } var dbIpSet = new HashSet(dbMachines.Select(m => NormalizeIp(m.MachineIp)), StringComparer.OrdinalIgnoreCase); var matchingRules = settings.Devices .Where(d => dbIpSet.Contains(NormalizeIp(d.IpAddress))) .ToList(); // If ANY matching IP is Enabled="true" => allowlist within this site scope. if (matchingRules.Any(r => r.Enabled)) { var allow = new HashSet(matchingRules.Where(r => r.Enabled).Select(r => NormalizeIp(r.IpAddress)), StringComparer.OrdinalIgnoreCase); var selected = dbMachines.Where(m => allow.Contains(NormalizeIp(m.MachineIp))).ToList(); logs?.Add($"DeviceSettings allowlist active (within site scope). selected={selected.Count}"); return selected; } // Else if matching rules exist and some are Enabled="false" => blocklist within site scope. if (matchingRules.Count > 0) { var block = new HashSet(matchingRules.Where(r => !r.Enabled).Select(r => NormalizeIp(r.IpAddress)), StringComparer.OrdinalIgnoreCase); var filtered = dbMachines.Where(m => !block.Contains(NormalizeIp(m.MachineIp))).ToList(); logs?.Add($"DeviceSettings blocklist active (within site scope). machines={dbMachines.Count} filtered={filtered.Count}"); return filtered; } // If no rules match any DB machine in site scope (or no devices listed) => sync all DB machines in scope. if (settings.Devices.Count > 0) { logs?.Add("DeviceSettings has entries but none match DB machines in scope. Syncing all DB machines in scope."); } else { logs?.Add("DeviceSettings has no entries. Syncing all DB machines in scope."); } return dbMachines; } public static List syncTemplates() { var logs = new List(); if (!GetAppSettingBool("TEMPLATE_SYNC_ENABLED", false)) { return logs; } var modeStr = ConfigurationManager.AppSettings["TRANSFER_MODE"] ?? "DEVICE_TO_DEVICE"; if (!Enum.TryParse(modeStr, true, out TemplateTransferMode mode)) { logs.Add("Invalid TRANSFER_MODE. Expected DEVICE_TO_DB, DB_TO_DEVICE, or DEVICE_TO_DEVICE."); return logs; } if (!TryLoadDeviceSettings(out var settings, logs)) { return logs; } try { Initialize(); using (var templateConn = OpenDbConnectionWithRetry("syncTemplates")) { var runner = new TemplateTransferRunner(new AttendanceMachineDAO(), new AttendanceMachineFaceTemplateDAO()); runner.RunJobs(settings, mode, templateConn, logs); } } catch (Exception ex) { logs.Add("Template sync failed: " + ex.ToString()); } return logs; } private static void RunTemplateLoadTest() { var logs = new List(); try { Initialize(); if (!TryLoadDeviceSettings(out var settings, logs)) { foreach (var line in logs) Console.WriteLine(line); Environment.ExitCode = 1; return; } var job = settings?.TemplateTransfer?.Jobs?.FirstOrDefault(j => j.Enabled); if (job == null) { Console.WriteLine("No enabled template transfer job found."); Environment.ExitCode = 1; return; } using (var conn = OpenDbConnectionWithRetry("testTemplateLoad")) { var employeeIds = DbToDevicePlanning.ResolveEmployeeIds( job, departmentIds => new AttendanceMachineFaceTemplateDAO().GetActiveSerialNumbersByDepartmentIds(conn, departmentIds, new List()), () => new List(job.EmpIds ?? new List())); Console.WriteLine($"[DB_TO_DEVICE] Template load test employees={employeeIds.Count}"); var runner = new TemplateTransferRunner(new AttendanceMachineDAO(), new AttendanceMachineFaceTemplateDAO()); var templates = runner.LoadTemplatesForDbToDeviceTest(conn, employeeIds, logs); int missing = employeeIds.Count - templates.Count; logs.Add($"[DB_TO_DEVICE] Template loading complete requested={employeeIds.Count} loaded={templates.Count} missing={missing}"); } } catch (Exception ex) { logs.Add("Template load test failed: " + ex); } foreach (var line in logs) { Console.WriteLine(line); } Environment.ExitCode = logs.Any(l => l.IndexOf("failed", StringComparison.OrdinalIgnoreCase) >= 0) ? 1 : 0; } private static void RunInspectPortalPhoto(string deviceSerial) { try { Initialize(); string photoEmployeeId = deviceSerial; string mappedName = null; using (var conn = OpenDbConnectionWithRetry("inspectPortalPhoto")) { var employeeDao = new HrmsEmployeeDAO(); if (employeeDao.TryGetByDeviceSerial(conn, deviceSerial, out HrmsEmployeeInfo employee)) { photoEmployeeId = employee.EmployeeId; mappedName = employee.ConcatenatedName; Console.WriteLine($"[PORTAL_PHOTO] device_serial={deviceSerial} hrms_id={employee.EmployeeId} name={employee.ConcatenatedName}"); } else { Console.WriteLine($"[PORTAL_PHOTO] device_serial={deviceSerial} hrms_lookup=MISS (treating argument as employees.id for photo URL)"); } } string url = HrmsEmployeePhotoClient.BuildPhotoUrl(EmployeePhotoSourceSettings.GetBaseUrl(), photoEmployeeId); Console.WriteLine($"[PORTAL_PHOTO] emp={deviceSerial} hrms_id={photoEmployeeId} enabled={EmployeePhotoSourceSettings.IsEnabled()} url={url}"); if (!HrmsEmployeePhotoClient.TryDownloadEmployeePhoto(photoEmployeeId, out HrmsEmployeePhotoDownloadResult result)) { Console.WriteLine($"[PORTAL_PHOTO] emp={deviceSerial} hrms_id={photoEmployeeId} download=FAILED http={result?.StatusCode ?? 0} content_type={result?.ContentType ?? ""} err={result?.Error ?? "unknown"}"); Environment.ExitCode = 1; return; } Console.WriteLine($"[PORTAL_PHOTO] emp={deviceSerial} hrms_id={photoEmployeeId} name={mappedName ?? ""} download=OK http={result.StatusCode} content_type={result.ContentType ?? ""}"); Console.WriteLine($"[PORTAL_PHOTO] emp={deviceSerial} photo_size={result.ImageBytes?.Length ?? 0} width={result.Width} height={result.Height} base64_len={result.Base64Length}"); } catch (Exception ex) { Console.WriteLine("Inspect portal photo failed: " + ex); Environment.ExitCode = 1; } } private static void RunDiagnoseEmployee(string empId) { var logs = new List(); var writer = new StringWriter(); try { Initialize(); if (!TryLoadDeviceSettings(out var settings, logs)) { foreach (var line in logs) Console.WriteLine(line); Environment.ExitCode = 1; return; } var job = settings?.TemplateTransfer?.Jobs?.FirstOrDefault(j => j.Enabled); if (job == null || job.TargetIps == null || job.TargetIps.Count == 0) { Console.WriteLine("No enabled template job with targets found."); Environment.ExitCode = 1; return; } string targetIp = job.TargetIps[0]; using (var conn = OpenDbConnectionWithRetry("diagnoseEmp")) { var machineDao = new AttendanceMachineDAO(); if (!machineDao.TryGetAttendanceMachineByIp(conn, targetIp, out var machine, out _)) { Console.WriteLine("Target machine not found for ip=" + targetIp); Environment.ExitCode = 1; return; } var templateDao = new AttendanceMachineFaceTemplateDAO(); var templates = templateDao.GetActiveTemplatesBySerialNos(conn, new List { empId }); byte[] blob = null; if (!templates.TryGetValue(empId, out blob)) { if (!EmployeePhotoSourceSettings.IsEnabled()) { Console.WriteLine("No active template in DB for emp=" + empId); Environment.ExitCode = 1; return; } blob = Array.Empty(); } var uploadPlan = DbToDeviceUploadPlan.FromBlob(blob); if (uploadPlan.Format == DbToDeviceTemplateFormat.Empty && EmployeePhotoSourceSettings.IsEnabled()) { uploadPlan.Format = DbToDeviceTemplateFormat.NedoXml; uploadPlan.PushMode = "face"; uploadPlan.UseProfileVerification = true; } bool portalPhotoEnabled = uploadPlan.Format == DbToDeviceTemplateFormat.NedoXml && EmployeePhotoSourceSettings.IsEnabled(); if (!uploadPlan.HasUploadPayload && !portalPhotoEnabled) { Console.WriteLine("No uploadable template payload for emp=" + empId + " format=" + uploadPlan.FormatLabel); Environment.ExitCode = 1; return; } var machineUserDao = new AttendanceMachineUserDAO(); var employeeDao = new HrmsEmployeeDAO(); HrmsEmployeeInfo hrmsEmployee = null; try { employeeDao.TryGetByDeviceSerial(conn, empId, out hrmsEmployee); } catch (Exception exEmp) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} hrms_lookup=FAILED err={exEmp.Message}"); } string machineUserName = machineUserDao.GetEmployeeNameForMachine(conn, machine.MachineId, empId); string displayName = !string.IsNullOrWhiteSpace(hrmsEmployee?.ConcatenatedName) ? hrmsEmployee.ConcatenatedName : (!string.IsNullOrWhiteSpace(machineUserName) ? machineUserName : empId); if (hrmsEmployee != null) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} hrms_id={hrmsEmployee.EmployeeId} name={displayName}"); } else { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} hrms_lookup=MISS name={displayName}"); } if (!int.TryParse(empId, out int enrollId)) { Console.WriteLine("empId must be numeric enrollid for Hanvon HTTP API. emp=" + empId); Environment.ExitCode = 1; return; } var client = HanvonHttpApiClient.ForMachine(machine); if (!client.TryLogin(out string loginErr)) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} login=FAILED err={loginErr}"); Environment.ExitCode = 1; return; } writer.WriteLine($"[DB_TO_DEVICE] employee={empId} template=FOUND format={uploadPlan.FormatLabel}"); if (uploadPlan.Format == DbToDeviceTemplateFormat.NedoXml) { bool hadNedoPayload = uploadPlan.HasUploadPayload; if (portalPhotoEnabled) { string photoEmployeeId = hrmsEmployee?.EmployeeId; if (uploadPlan.TryResolveEmployeePhoto(photoEmployeeId, out string resolveErr)) { if (string.Equals(uploadPlan.PhotoSourceLabel, "HRMS_PORTAL", StringComparison.OrdinalIgnoreCase)) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} hrms_id={photoEmployeeId} photo_source=HRMS_PORTAL photo_url={uploadPlan.PhotoSourceUrl} photo_size={uploadPlan.PhotoByteLength} width={uploadPlan.PreparedWidth} height={uploadPlan.PreparedHeight}"); } else if (hadNedoPayload) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} photo_source=HRMS_PORTAL FAILED err={resolveErr ?? "unknown"}"); writer.WriteLine($"[DB_TO_DEVICE] employee={empId} photo_source=NEDO_XML FALLBACK"); writer.WriteLine($"[DB_TO_DEVICE] employee={empId} nedo_photo=FOUND base64_len={uploadPlan.PhotoBase64Length} size={uploadPlan.PhotoByteLength} blob_len={uploadPlan.SourceBlobLength}"); } } else { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} photo_source=HRMS_PORTAL FAILED err={resolveErr ?? "unknown"}"); } } else if (uploadPlan.HasUploadPayload) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} nedo_photo=FOUND base64_len={uploadPlan.PhotoBase64Length} size={uploadPlan.PhotoByteLength} blob_len={uploadPlan.SourceBlobLength}"); } } if (!uploadPlan.HasUploadPayload) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} face_upload=FAILED response=no_photo_available"); Environment.ExitCode = 1; return; } if (!uploadPlan.TryPrepareFacePayload(out string prepareErr)) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} format={uploadPlan.FormatLabel} photo=INVALID err={prepareErr}"); Environment.ExitCode = 1; return; } if (!string.IsNullOrWhiteSpace(uploadPlan.PrepareNote)) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} nedo_photo=PREPARED note={uploadPlan.PrepareNote} width={uploadPlan.PreparedWidth} height={uploadPlan.PreparedHeight} size={uploadPlan.PhotoByteLength}"); } if (!client.TryUploadDbToDeviceTemplate(enrollId, displayName, uploadPlan, out string uploadErr)) { if (DbToDeviceFaceErrors.TryParseDuplicateFace(uploadErr, out string existingDeviceId)) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} face=DUPLICATE existing_device_id={existingDeviceId}"); } else { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} face_upload=FAILED response={uploadErr}"); Environment.ExitCode = 1; } } else if (client.VerifyDbToDeviceTemplate(enrollId, uploadPlan, out string faceflag, out string photourl, out string verifyErr)) { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} target={machine.MachineIp} face_upload=OK photo_source={uploadPlan.PhotoSourceLabel ?? uploadPlan.FormatLabel} push_mode={uploadPlan.PushMode} payload_len={uploadPlan.Payload?.Length ?? 0}"); writer.WriteLine($"[DB_TO_DEVICE] employee={empId} verify faceflag={faceflag ?? ""} photourl={photourl ?? ""} face=OK"); } else { writer.WriteLine($"[DB_TO_DEVICE] employee={empId} verify=FAILED faceflag={faceflag ?? ""} photourl={photourl ?? ""} err={verifyErr}"); Environment.ExitCode = 1; } var users = client.GetUserList(out string listErr); var match = users.FirstOrDefault(u => string.Equals(u.DeviceUserId, empId, StringComparison.OrdinalIgnoreCase) || string.Equals(u.SerialNumber, empId, StringComparison.OrdinalIgnoreCase)); writer.WriteLine($"[DB_TO_DEVICE] emp={empId} getuserlist user_exists={(match != null ? "true" : "false")} list_id={match?.DeviceUserId ?? ""} list_name={match?.Name ?? ""}"); if (!string.IsNullOrWhiteSpace(listErr)) { writer.WriteLine($"[DB_TO_DEVICE] getuserlist_err={listErr}"); } } } catch (Exception ex) { writer.WriteLine("Diagnose failed: " + ex); } string output = writer.ToString(); Console.WriteLine(output); try { string logPath = Path.Combine(ApplicationPaths.InternalLogs, "DbToDeviceDiag_" + empId + "_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".txt"); File.WriteAllText(logPath, output); Console.WriteLine("Diagnostic log written: " + logPath); } catch { // ignore file write failures } } // ApplyDeviceSelectionRulesIfConfigured removed: selection is now config-driven (IP allowlist/blocklist) without SITE_ID env var. private static bool TryLoadDeviceSettings(out DeviceSettingsConfig settings, List logs) { settings = null; string path = ConfigurationManager.AppSettings["DEVICE_SETTINGS_PATH"]; if (string.IsNullOrWhiteSpace(path)) { path = "DeviceSettings.xml"; } path = ApplicationPaths.Resolve(path); if (!DeviceSettingsConfig.TryLoad(path, out settings, out var error)) { logs?.Add(error); return false; } return true; } private static bool GetAppSettingBool(string key, bool defaultValue) { try { var value = ConfigurationManager.AppSettings[key]; if (string.IsNullOrWhiteSpace(value)) { return defaultValue; } if (bool.TryParse(value.Trim(), out var parsed)) { return parsed; } return defaultValue; } catch { return defaultValue; } } public static string deleteEmployeeInfo(int employeeId) { string response = ""; CallBack myCallBack = new CallBack(Program.BeCalled); Initialize(); if (OpenConnection() == true) { AttendanceMachineDAO attendanceMachineDAO = new AttendanceMachineDAO(); List machines = attendanceMachineDAO.getAttendanceMachines(connection, "2"); foreach (var machine in machines) { if (machine.MachineId.Equals("SITE2-MACHINE-2")) { string devInfo = machine.GetDeviceInfo(); string str4 = "DeleteEmployee(id=\"" + employeeId + "\")"; //string str4 = "GetEmployeeID()"; string str5 = ""; uint num2 = 0; if (test(devInfo, devInfo.Length, str4, str4.Length, ref str5, ref num2, myCallBack) != 0) { Console.WriteLine("Not Connected"); response = machine.MachineId + " is not connected !"; RecordUnreachableMachine(machine, "deleteEmployeeInfo: device not connected"); } else { Console.WriteLine("Connected"); Console.WriteLine(str5); } } } CloseConnection(); } return response; } public static int GetLoopCnt(string retstr) { if (string.IsNullOrEmpty(retstr)) return 0; int index = retstr.IndexOf("total=\"", StringComparison.OrdinalIgnoreCase); if (index < 0) return 0; int start = index + 7; if (start >= retstr.Length) return 0; int end = retstr.IndexOf("\"", start, StringComparison.Ordinal); if (end < 0) return 0; string numStr = retstr.Substring(start, end - start).Trim(); if (string.IsNullOrEmpty(numStr)) return 0; return int.TryParse(numStr, out int n) ? n : 0; } private static int GetAttendanceBatchSize(int totalRecords) { if (totalRecords <= 50) return 10; if (totalRecords <= 200) return 25; if (totalRecords <= 500) return 50; if (totalRecords <= 2000) return 100; return 200; } private static List ParseAttendanceRows(string response, AttendanceMachine machine) { var rows = new List(); if (string.IsNullOrWhiteSpace(response)) return rows; string[] allObj = response.Split(new string[] { "time" }, StringSplitOptions.None); for (int i = 0; i < allObj.Length; i++) { string s = allObj[i]; if (!s.Contains("Return")) { string[] objDetail = s.Split(new string[] { "\"" }, StringSplitOptions.None); if (objDetail.Length < 4) continue; DateTime checkTime = Convert.ToDateTime(objDetail[1]); rows.Add(new Attendance(objDetail[3], checkTime, false, machine.MachineId, "1", machine.MachineIp, DateTime.Now)); } } return rows; } private static (DateTime? FirstCheckTime, DateTime? LastCheckTime, int InsertedCount) ProcessAttendanceInBatches( List parsedRows, AttendanceMachine machine, MySqlConnection conn, DateTime prevLastSync) { var attendanceDAO = new AttendanceDAO(); var attendanceMachineDAO = new AttendanceMachineDAO(); int total = parsedRows?.Count ?? 0; if (total == 0) return (null, null, 0); int batchSize = GetAttendanceBatchSize(total); int totalBatches = (int)Math.Ceiling(total / (double)batchSize); WriteInternalLog($"[AttendanceBatchPlan] machine_id={machine.MachineId} ip={machine.MachineIp} total_records={total} batch_size={batchSize} total_batches={totalBatches}"); int inserted = 0; DateTime? firstCheckTime = null; DateTime? lastCheckTime = null; DateTime runningPrevSync = prevLastSync; for (int b = 0; b < totalBatches; b++) { var sw = Stopwatch.StartNew(); int start = b * batchSize; int take = Math.Min(batchSize, total - start); DateTime? batchFirst = null; DateTime? batchLast = null; for (int i = start; i < start + take; i++) { var log = parsedRows[i]; if (!firstCheckTime.HasValue || log.CheckTime < firstCheckTime.Value) firstCheckTime = log.CheckTime; if (!lastCheckTime.HasValue || log.CheckTime > lastCheckTime.Value) lastCheckTime = log.CheckTime; if (!batchFirst.HasValue || log.CheckTime < batchFirst.Value) batchFirst = log.CheckTime; if (!batchLast.HasValue || log.CheckTime > batchLast.Value) batchLast = log.CheckTime; try { int rows = attendanceDAO.Add(log, conn); WriteInternalLog($"[AttendanceInsert] machine_id={machine.MachineId} ip={machine.MachineIp} ac_no={log.AcNo} checktime={log.CheckTime:yyyy-MM-dd HH:mm:ss} rows={rows}"); inserted++; } catch (Exception ex) { WriteInternalLog($"[AttendanceInsertError] machine_id={machine.MachineId} ip={machine.MachineIp} ac_no={log.AcNo} checktime={log.CheckTime:yyyy-MM-dd HH:mm:ss} err={ex.Message}"); throw; } } if (!DeviceProtocol.UseHttp() && GetBoolAppSetting("ATTENDANCE_DELETE_AFTER_INSERT", true) && batchFirst.HasValue && batchLast.HasValue) { TryDeleteAttendanceFromDevice(machine, batchFirst.Value, batchLast.Value, b + 1, totalBatches); } sw.Stop(); WriteInternalLog($"[AttendanceBatch] machine_id={machine.MachineId} ip={machine.MachineIp} batch={b + 1}/{totalBatches} attempted={take} duration_sec={Math.Round(sw.Elapsed.TotalSeconds, 1)}"); if (batchLast.HasValue) { WriteInternalLog($"[AttendanceSyncProgress] machine_id={machine.MachineId} ip={machine.MachineIp} latest_processed_checktime={batchLast.Value:yyyy-MM-dd HH:mm:ss}"); machine.LastSyncDate = batchLast.Value; attendanceMachineDAO.update(machine, conn); WriteInternalLog($"[AttendanceSyncUpdate] machine_id={machine.MachineId} ip={machine.MachineIp} prev_last_sync={runningPrevSync:yyyy-MM-dd HH:mm:ss} new_last_sync={machine.LastSyncDate:yyyy-MM-dd HH:mm:ss}"); runningPrevSync = machine.LastSyncDate; } } return (firstCheckTime, lastCheckTime, inserted); } private static void TryDeleteAttendanceFromDevice(AttendanceMachine machine, DateTime batchFirst, DateTime batchLast, int batchNumber, int totalBatches) { try { var callback = new CallBack(Program.BeCalled); string devInfo = machine.GetDeviceInfo(); string start = batchFirst.ToString("yyyy-MM-dd HH:mm:ss"); string end = batchLast.ToString("yyyy-MM-dd HH:mm:ss"); string cmd = "DeleteRecord(start_time=\"" + start + "\" end_time=\"" + end + "\")"; string response = ""; uint recvLen = 0; int rc = test(devInfo, devInfo.Length, cmd, cmd.Length, ref response, ref recvLen, callback); if (rc == 0) { WriteInternalLog($"[AttendanceDelete] machine_id={machine.MachineId} ip={machine.MachineIp} batch={batchNumber}/{totalBatches} start={start} end={end} status=OK"); } else { WriteInternalLog($"[AttendanceDelete] machine_id={machine.MachineId} ip={machine.MachineIp} batch={batchNumber}/{totalBatches} start={start} end={end} status=FAIL rc={rc}"); } } catch (Exception ex) { WriteInternalLog($"[AttendanceDeleteError] machine_id={machine.MachineId} ip={machine.MachineIp} batch={batchNumber}/{totalBatches} err={ex.Message}"); } } public static void saveMachineUsers(string response, AttendanceMachine machine, List DBusers, MySqlConnection conn) { AttendanceMachineUserDAO attendanceMachineUserDAO = new AttendanceMachineUserDAO(); string[] allObj = response.Split(new string[] { "id" }, StringSplitOptions.None); int count = 0; for (int i = 0; i < allObj.Length; i++) { string s = allObj[i]; if (!s.Contains("Return")) { //Console.WriteLine(s); string[] objDetail = s.Split(new string[] { "\"" }, StringSplitOptions.None); /*Console.WriteLine("id: " + objDetail[1]); Console.WriteLine("time: " + objDetail[1]); Console.WriteLine("id: " + objDetail[3]); Console.WriteLine("name: " + objDetail[5]); Console.WriteLine("workCode: " + objDetail[7]); Console.WriteLine("status: " + objDetail[9]); Console.WriteLine("authority: " + objDetail[11]); Console.WriteLine("cardSrc: " + objDetail[13]);*/ //objDetail[1] = "6286"; AttendanceMachineUser temp = new AttendanceMachineUser(machine.MachineId, objDetail[1], ""); foreach (var users in DBusers) { result = (users.SerialNumber.Contains(objDetail[1]) && users.MachineId.Contains(temp.MachineId)); if (result == true) { break; } } if (result == false) { attendanceMachineUserDAO.Add(temp, conn); } //Attendance log = new Attendance(objDetail[3], Convert.ToDateTime(objDetail[1]), false, machine.MachineId, "1", machine.MachineIp, Convert.ToDateTime(objDetail[1])); //attendanceDAO.Add(log, connection); count++; } } //dbConnect.CloseConnection(); //Console.WriteLine(count); } public static int test(string pDevInfoBuf, int nDevInfoLen, string pSendBuf, int nSendLen, ref string pRecvBuf, ref uint pRecvLen, CallBack pFuncTotalDone) { byte[] bytes; int num = 0; IntPtr zero = IntPtr.Zero; IntPtr ptr2 = IntPtr.Zero; try { bytes = Encoding.Default.GetBytes(pSendBuf); int length = bytes.Length; zero = Program.GlobalAlloc(0, length); if (!(zero == IntPtr.Zero)) { zero = Program.GlobalLock(zero); if (!(zero == IntPtr.Zero)) { Marshal.Copy(bytes, 0, zero, length); num = Program.HwDev_Execute(pDevInfoBuf, pDevInfoBuf.Length, zero, length, ref ptr2, ref pRecvLen, pFuncTotalDone); pRecvBuf = Marshal.PtrToStringAnsi(ptr2); Program.HwDev_Finish(ref ptr2); } else { return -1; } } else { return -1; } } catch (Exception exception1) { num = -1; Console.Write(exception1.ToString()); throw new OperationCanceledException(exception1.Message); } finally { Program.GlobalUnlock(zero); Program.GlobalFree(zero); bytes = null; } return num; } public static int BeCalled(uint nTotal, uint nDone) => 0; public static bool OpenConnection() { try { connection.Open(); Console.WriteLine("Connected !"); return true; } catch (MySqlException ex) { switch (ex.Number) { case 0: Console.WriteLine("Cannot connect to server. Contact administrator"); break; case 1045: Console.WriteLine("Invalid username/password, please try again"); break; } return false; } } //Close connection public static bool CloseConnection() { try { connection.Close(); return true; } catch (MySqlException ex) { Console.WriteLine(ex.Message); return false; } } } public delegate int CallBack(uint nTotal, uint nDone); }