using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Timers; using System.Configuration; namespace HanvonF710XAttendanceService { public partial class HanvonF710XWindowsService : ServiceBase { private System.Timers.Timer timer = new System.Timers.Timer(); private System.Timers.Timer timer1 = new System.Timers.Timer(); private static readonly object _attendanceLock = new object(); private static readonly object _templateLock = new object(); private static volatile bool _attendanceJobRunning; private static volatile bool _templateJobRunning; private volatile bool _stopping; public HanvonF710XWindowsService() { InitializeComponent(); } public void StartForConsole(string[] args) { OnStart(args ?? Array.Empty()); } public void StopForConsole() { OnStop(); } protected override void OnStart(string[] args) { ApplicationPaths.Initialize(); try { LogService.Start(); } catch (Exception ex) { Program.WriteInternalLog("LogService.Start failed: " + ex.Message); } WriteToFile("--Service is started at " + DateTime.Now); WriteInternalLog("--Service is started at " + DateTime.Now); if (Program.IsConsoleMode) { Console.WriteLine("--Service is started at " + DateTime.Now); } int attendanceMinutes = GetIntConfig("ATTENDANCE_JOB_INTERVAL_MINUTES", 20); int templateMinutes = GetIntConfig("TEMPLATE_JOB_INTERVAL_MINUTES", 240); double attendanceIntervalMs = Math.Max(1, attendanceMinutes) * 60 * 1000; double templateIntervalMs = Math.Max(1, templateMinutes) * 60 * 1000; timer.Elapsed += OnAttendanceElapsed; timer.Interval = attendanceIntervalMs; timer.AutoReset = true; timer.Enabled = true; timer1.Elapsed += OnTemplateElapsed; timer1.Interval = templateIntervalMs; timer1.AutoReset = true; timer1.Enabled = true; WriteInternalLog($"Attendance job interval: {attendanceMinutes} minutes"); WriteInternalLog($"Template job interval: {templateMinutes} minutes"); WriteToFile($"Attendance job interval: {attendanceMinutes} minutes"); WriteToFile($"Template job interval: {templateMinutes} minutes"); try { WriteInternalLog("--Startup trigger: running attendance job immediately at " + DateTime.Now); Task.Run(() => RunStartupAttendanceJob()); } catch (Exception ex) { WriteInternalLog("Startup attendance trigger failed: " + ex.Message); } try { int delaySec = 30; WriteInternalLog("--Startup trigger: running template job after " + delaySec + "s at " + DateTime.Now); Task.Run(() => RunStartupTemplateJob(delaySec)); } catch (Exception ex) { WriteInternalLog("Startup template trigger failed: " + ex.Message); } } protected override void OnStop() { _stopping = true; WriteToFile("--Service is stopping at " + DateTime.Now); WriteInternalLog("--Service is stopping at " + DateTime.Now); try { timer.Enabled = false; timer1.Enabled = false; timer.Elapsed -= OnAttendanceElapsed; timer1.Elapsed -= OnTemplateElapsed; } catch (Exception ex) { WriteInternalLog("OnStop timer shutdown failed: " + ex.Message); } WaitForRunningJobs(); try { timer.Dispose(); timer1.Dispose(); } catch (Exception ex) { WriteInternalLog("OnStop timer dispose failed: " + ex.Message); } try { LogService.StopAndFlush(TimeSpan.FromSeconds(5)); } catch { } WriteToFile("--Service is stopped at " + DateTime.Now); WriteInternalLog("--Service is stopped at " + DateTime.Now); } private void WaitForRunningJobs() { int waitSeconds = GetIntConfig("SERVICE_SHUTDOWN_WAIT_SECONDS", 300); if (waitSeconds <= 0) { return; } var deadline = DateTime.UtcNow.AddSeconds(waitSeconds); while ((_attendanceJobRunning || _templateJobRunning) && DateTime.UtcNow < deadline) { Thread.Sleep(500); } if (_attendanceJobRunning || _templateJobRunning) { WriteInternalLog($"--Service stop: jobs still running after {waitSeconds}s (attendance={_attendanceJobRunning}, template={_templateJobRunning})"); } } private void RunStartupAttendanceJob() { try { if (_stopping) { return; } RunAttendanceJob(); } catch (Exception ex) { WriteInternalLog("Startup attendance job error: " + ex); } } private async Task RunStartupTemplateJob(int delaySec) { try { await Task.Delay(Math.Max(0, delaySec) * 1000).ConfigureAwait(false); if (_stopping) { return; } RunTemplateJob(); } catch (Exception ex) { WriteInternalLog("Startup template job error: " + ex); } } private static int GetIntConfig(string key, int defaultValue) { try { var raw = ConfigurationManager.AppSettings[key]; if (string.IsNullOrWhiteSpace(raw)) return defaultValue; return int.TryParse(raw, out var v) && v > 0 ? v : defaultValue; } catch { return defaultValue; } } private void OnAttendanceElapsed(object source, ElapsedEventArgs e) { RunAttendanceJob(); } private void RunAttendanceJob() { if (_stopping) { WriteInternalLog("--Attendance tick skipped (service stopping) at " + DateTime.Now); return; } if (!Monitor.TryEnter(_attendanceLock)) { WriteInternalLog("--Attendance tick skipped (previous attendance job still running) at " + DateTime.Now); return; } try { if (_attendanceJobRunning) { WriteInternalLog("--Attendance tick skipped (previous attendance job still running) at " + DateTime.Now); return; } _attendanceJobRunning = true; WriteSyncCycleHeader(); Program.BeginSyncCycle(); WriteInternalLog("--Attendance job started at " + DateTime.Now); WriteAttendanceServiceLog("--Attendance job started at " + DateTime.Now); try { syncMachineUsers(); } catch (Exception ex) { WriteInternalLog("syncMachineUsers error: " + ex); WriteMachineUserServiceLog("syncMachineUsers error: " + ex.Message); } if (_stopping) { WriteInternalLog("--Attendance job interrupted (service stopping) before attendance sync at " + DateTime.Now); return; } try { syncAttendance(); } catch (Exception ex) { WriteInternalLog("syncAttendance error: " + ex); WriteAttendanceServiceLog("syncAttendance error: " + ex.Message); } WriteInternalLog("--Attendance job finished at " + DateTime.Now); WriteAttendanceServiceLog("--Attendance job finished at " + DateTime.Now); } catch (Exception ex) { WriteInternalLog("--Attendance job failed: " + ex); WriteAttendanceServiceLog("--Attendance job failed: " + ex.Message); } finally { try { Program.EndSyncCycle(); } catch (Exception ex) { WriteInternalLog("EndSyncCycle error: " + ex.Message); } _attendanceJobRunning = false; Monitor.Exit(_attendanceLock); } } private void OnTemplateElapsed(object source, ElapsedEventArgs e) { RunTemplateJob(); } private void RunTemplateJob() { if (_stopping) { WriteInternalLog("--Template tick skipped (service stopping) at " + DateTime.Now); return; } if (!Monitor.TryEnter(_templateLock)) { WriteInternalLog("--Template tick skipped (previous template job still running) at " + DateTime.Now); return; } try { if (_templateJobRunning) { WriteInternalLog("--Template tick skipped (previous template job still running) at " + DateTime.Now); return; } _templateJobRunning = true; WriteJobHeaderToSimpleLogs("TEMPLATE"); WriteInternalLog("--Template job started at " + DateTime.Now); WriteSaveFaceTemplateLog("--Template job started at " + DateTime.Now); WriteFaceTemplateTransferLog("--Template job started at " + DateTime.Now); try { syncTemplatesIfEnabled(); } catch (Exception ex) { WriteInternalLog("syncTemplatesIfEnabled error: " + ex); WriteFaceTemplateTransferLog("syncTemplatesIfEnabled error: " + ex); WriteSaveFaceTemplateLog("syncTemplatesIfEnabled error: " + ex); } WriteInternalLog("--Template job finished at " + DateTime.Now); WriteSaveFaceTemplateLog("--Template job finished at " + DateTime.Now); WriteFaceTemplateTransferLog("--Template job finished at " + DateTime.Now); } catch (Exception ex) { WriteInternalLog("--Template job failed: " + ex); WriteFaceTemplateTransferLog("--Template job failed: " + ex.Message); WriteSaveFaceTemplateLog("--Template job failed: " + ex.Message); } finally { _templateJobRunning = false; Monitor.Exit(_templateLock); } } public void syncAttendance() { WriteToFile("--Sync Attendance is recall at " + DateTime.Now); WriteInternalLog("--Sync Attendance is recall at " + DateTime.Now); WriteAttendanceServiceLog("--Sync Attendance is recall at " + DateTime.Now); List responses = Program.syncAttendance(); foreach (var response in responses) { WriteToFile(response); WriteInternalLog(response); if (IsAttendanceSimpleLine(response)) { WriteAttendanceServiceLog(response); } } } public void syncMachineUsers() { WriteToFile("--Sync Machine users is recall at " + DateTime.Now); WriteInternalLog("--Sync Machine users is recall at " + DateTime.Now); WriteMachineUserServiceLog("--Sync Machine users is recall at " + DateTime.Now); List responses = Program.syncMachineUsers(); foreach (var response in responses) { WriteToFile(response); WriteInternalLog(response); if (IsMachineUserSimpleLine(response)) { WriteMachineUserServiceLog(response); } } } public void syncTemplatesIfEnabled() { WriteToFile("--Template sync is recall at " + DateTime.Now); WriteInternalLog("--Template sync is recall at " + DateTime.Now); List responses = Program.syncTemplates(); foreach (var response in responses) { WriteToFile(response); WriteInternalLog(response); if (IsSaveFaceTemplateLine(response)) { WriteSaveFaceTemplateLog(response); } if (IsFaceTransferLine(response)) { WriteFaceTemplateTransferLog(response); } } } public void WriteToFile(string Message) { string filepath = ApplicationPaths.DatedFile(ApplicationPaths.Logs, "ServiceLog"); LogService.EnqueueLine(filepath, Message); } private void WriteSyncCycleHeader() { string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string line1 = "========================================================"; string line2 = "SYNC CYCLE START"; string line3 = "Time: " + time; string line4 = "========================="; WriteToFile(line1); WriteToFile(line2); WriteToFile(line3); WriteToFile(line4); WriteInternalLog(line1); WriteInternalLog(line2); WriteInternalLog(line3); WriteInternalLog(line4); WriteAttendanceServiceLog(line1); WriteAttendanceServiceLog(line2); WriteAttendanceServiceLog(line3); WriteAttendanceServiceLog(line4); WriteMachineUserServiceLog(line1); WriteMachineUserServiceLog(line2); WriteMachineUserServiceLog(line3); WriteMachineUserServiceLog(line4); } private void WriteJobHeaderToSimpleLogs(string jobName) { string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string line1 = "========================================================"; string line2 = "SYNC CYCLE START"; string line3 = "Time: " + time; string line4 = "========================="; string line5 = "JOB: " + jobName; WriteSaveFaceTemplateLog(line1); WriteSaveFaceTemplateLog(line2); WriteSaveFaceTemplateLog(line3); WriteSaveFaceTemplateLog(line4); WriteSaveFaceTemplateLog(line5); WriteFaceTemplateTransferLog(line1); WriteFaceTemplateTransferLog(line2); WriteFaceTemplateTransferLog(line3); WriteFaceTemplateTransferLog(line4); WriteFaceTemplateTransferLog(line5); } private static bool IsAttendanceSimpleLine(string line) { if (string.IsNullOrWhiteSpace(line)) return false; if (line.IndexOf(" has ", StringComparison.OrdinalIgnoreCase) >= 0 && line.IndexOf(" records", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf(" is not connected", StringComparison.OrdinalIgnoreCase) >= 0) return true; return false; } private static bool IsMachineUserSimpleLine(string line) { if (string.IsNullOrWhiteSpace(line)) return false; if (line.IndexOf(" -> ", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf(" removed from ", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf(" is not connected", StringComparison.OrdinalIgnoreCase) >= 0) return true; return false; } private static bool IsSaveFaceTemplateLine(string line) { if (string.IsNullOrWhiteSpace(line)) return false; if (line.StartsWith("Serial number--", StringComparison.OrdinalIgnoreCase)) return true; if (line.IndexOf("SaveTemplateDB", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("DEVICE_TO_DB", StringComparison.OrdinalIgnoreCase) >= 0) return true; return false; } private static bool IsFaceTransferLine(string line) { if (string.IsNullOrWhiteSpace(line)) return false; if (line.IndexOf("DB_TO_DEVICE", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("DEVICE_TO_DEVICE", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("[DB_TO_DEVICE]", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("TemplateDistribution", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("TemplateRegistrationTargets", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("TemplateDist:", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf("SetEmployee", StringComparison.OrdinalIgnoreCase) >= 0) return true; if (line.IndexOf(" is not connected", StringComparison.OrdinalIgnoreCase) >= 0) return true; return false; } private static void AppendLineToDatedLog(string folderName, string filePrefix, string message) { string folderPath = Path.Combine(ApplicationPaths.Logs, folderName); string datePart = DateTime.Now.ToString("yyyy-MM-dd"); string filePath = Path.Combine(folderPath, filePrefix + "_" + datePart + ".txt"); LogService.EnqueueLine(filePath, message); } private static void WriteAttendanceServiceLog(string message) { AppendLineToDatedLog("Attendance", "AttendanceServiceLogs", message); } private static void WriteMachineUserServiceLog(string message) { AppendLineToDatedLog("Machine User service", "MachineServiceLogs", message); } private static void WriteSaveFaceTemplateLog(string message) { AppendLineToDatedLog("SaveFaceTemplate logs", "SaveFaceTemplateLogs", message); } private static void WriteFaceTemplateTransferLog(string message) { AppendLineToDatedLog("FaceTransferLog", "FaceTransferLogs", message); } public void WriteInternalLog(string Message) { Program.WriteInternalLog(Message); } } }