531 lines
19 KiB
C#
531 lines
19 KiB
C#
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<string>());
|
|
}
|
|
|
|
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<string> 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<string> 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<string> 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);
|
|
}
|
|
}
|
|
}
|