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);
}