458 lines
18 KiB
C#
458 lines
18 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
|
|
{
|
|
// Separate timers for attendance/users and template sync.
|
|
System.Timers.Timer timer = new System.Timers.Timer(); // attendance + users
|
|
System.Timers.Timer timer1 = new System.Timers.Timer(); // templates
|
|
|
|
// Independent locks/flags so jobs don't block each other.
|
|
private static readonly object _attendanceLock = new object();
|
|
private static readonly object _templateLock = new object();
|
|
private static bool _attendanceJobRunning = false;
|
|
private static bool _templateJobRunning = false;
|
|
public HanvonF710XWindowsService()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
/// <summary>Starts timers and jobs when running outside the Windows Service Control Manager.</summary>
|
|
public void StartForConsole(string[] args)
|
|
{
|
|
OnStart(args ?? Array.Empty<string>());
|
|
}
|
|
|
|
/// <summary>Stops timers and flushes logs when running in console/debug mode.</summary>
|
|
public void StopForConsole()
|
|
{
|
|
OnStop();
|
|
}
|
|
|
|
|
|
protected override void OnStart(string[] args)
|
|
{
|
|
// So the service finds HwDevComm.dll (and sibling DLLs) in the EXE folder
|
|
string exeDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
try
|
|
{
|
|
Environment.CurrentDirectory = exeDir;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try { WriteInternalLog("OnStart set CurrentDirectory failed: " + ex.Message); } catch { }
|
|
}
|
|
|
|
// Start buffered, cross-process-safe logging.
|
|
try { LogService.Start(); } catch { }
|
|
|
|
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 += new ElapsedEventHandler(OnAttendanceElapsed);
|
|
timer.Interval = attendanceIntervalMs;
|
|
timer.AutoReset = true;
|
|
timer.Enabled = true;
|
|
|
|
timer1.Elapsed += new ElapsedEventHandler(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");
|
|
|
|
// Run both jobs once immediately on startup (non-blocking).
|
|
// Uses the same guarded execution path as the timer callbacks.
|
|
try
|
|
{
|
|
WriteInternalLog("--Startup trigger: running attendance job immediately at " + DateTime.Now);
|
|
Task.Run(() => RunAttendanceJob());
|
|
}
|
|
catch { }
|
|
try
|
|
{
|
|
int delaySec = 30;
|
|
WriteInternalLog("--Startup trigger: running template job after " + delaySec + "s at " + DateTime.Now);
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(delaySec * 1000).ConfigureAwait(false);
|
|
RunTemplateJob();
|
|
}
|
|
catch { }
|
|
});
|
|
}
|
|
catch { }
|
|
}
|
|
protected override void OnStop()
|
|
{
|
|
WriteToFile("--Service is stopped at " + DateTime.Now);
|
|
try
|
|
{
|
|
timer.Enabled = false;
|
|
timer1.Enabled = false;
|
|
timer.Dispose();
|
|
timer1.Dispose();
|
|
}
|
|
catch { }
|
|
try { LogService.StopAndFlush(TimeSpan.FromSeconds(5)); } catch { }
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Attendance + machine-user job
|
|
private void OnAttendanceElapsed(object source, ElapsedEventArgs e)
|
|
{
|
|
RunAttendanceJob();
|
|
}
|
|
|
|
private void RunAttendanceJob()
|
|
{
|
|
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.Message);
|
|
WriteMachineUserServiceLog("syncMachineUsers error: " + ex.Message);
|
|
}
|
|
|
|
try
|
|
{
|
|
syncAttendance();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteInternalLog("syncAttendance error: " + ex.Message);
|
|
WriteAttendanceServiceLog("syncAttendance error: " + ex.Message);
|
|
}
|
|
|
|
WriteInternalLog("--Attendance job finished at " + DateTime.Now);
|
|
WriteAttendanceServiceLog("--Attendance job finished at " + DateTime.Now);
|
|
}
|
|
finally
|
|
{
|
|
try { Program.EndSyncCycle(); } catch { }
|
|
_attendanceJobRunning = false;
|
|
Monitor.Exit(_attendanceLock);
|
|
}
|
|
}
|
|
|
|
// Template job
|
|
private void OnTemplateElapsed(object source, ElapsedEventArgs e)
|
|
{
|
|
RunTemplateJob();
|
|
}
|
|
|
|
private void RunTemplateJob()
|
|
{
|
|
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.ToString());
|
|
WriteFaceTemplateTransferLog("syncTemplatesIfEnabled error: " + ex.ToString());
|
|
WriteSaveFaceTemplateLog("syncTemplatesIfEnabled error: " + ex.ToString());
|
|
}
|
|
WriteInternalLog("--Template job finished at " + DateTime.Now);
|
|
WriteSaveFaceTemplateLog("--Template job finished at " + DateTime.Now);
|
|
WriteFaceTemplateTransferLog("--Template job finished at " + DateTime.Now);
|
|
}
|
|
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);
|
|
|
|
// Attendance simple log: only machine-level results and connectivity.
|
|
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);
|
|
|
|
// Machine user simple log: only machine totals and connectivity.
|
|
if (IsMachineUserSimpleLine(response))
|
|
{
|
|
WriteMachineUserServiceLog(response);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public void syncTemplatesIfEnabled()
|
|
{
|
|
WriteToFile("--Template sync is recall at " + DateTime.Now);
|
|
WriteInternalLog("--Template sync is recall at " + DateTime.Now);
|
|
// We'll route template logs into either SaveFaceTemplate or FaceTransfer logs based on TRANSFER_MODE.
|
|
List<string> responses = Program.syncTemplates();
|
|
foreach (var response in responses)
|
|
{
|
|
WriteToFile(response);
|
|
WriteInternalLog(response);
|
|
|
|
// SaveFaceTemplate logs: DEVICE_TO_DB and any "Serial number--" lines.
|
|
if (IsSaveFaceTemplateLine(response))
|
|
{
|
|
WriteSaveFaceTemplateLog(response);
|
|
}
|
|
|
|
// FaceTransfer logs: DB_TO_DEVICE / DEVICE_TO_DEVICE and connectivity issues.
|
|
if (IsFaceTransferLine(response))
|
|
{
|
|
WriteFaceTemplateTransferLog(response);
|
|
}
|
|
}
|
|
}
|
|
public void WriteToFile(string Message)
|
|
{
|
|
string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\Logs\\ServiceLog_" +
|
|
DateTime.Now.Date.ToShortDateString().Replace('/', '_') + ".txt";
|
|
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);
|
|
|
|
// Simple per-feature logs (keep InternalLogs unchanged).
|
|
WriteAttendanceServiceLog(line1);
|
|
WriteAttendanceServiceLog(line2);
|
|
WriteAttendanceServiceLog(line3);
|
|
WriteAttendanceServiceLog(line4);
|
|
|
|
WriteMachineUserServiceLog(line1);
|
|
WriteMachineUserServiceLog(line2);
|
|
WriteMachineUserServiceLog(line3);
|
|
WriteMachineUserServiceLog(line4);
|
|
}
|
|
|
|
// Ensures per-feature simple logs have a header even when a job doesn't call WriteSyncCycleHeader.
|
|
// Used by template job.
|
|
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;
|
|
// Keep only the machine summary lines.
|
|
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;
|
|
// Keep only "MACHINE X -> N", "X removed from MACHINE Y", and connectivity lines.
|
|
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;
|
|
// Any transfer mode other than DEVICE_TO_DB and connectivity issues.
|
|
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;
|
|
// Peer distribution uses job id "TemplateDist:<ip>" in [TemplateJob ...] lines from GetEmployeeID on targets.
|
|
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 baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
string logsRoot = Path.Combine(baseDir, "Logs");
|
|
string folderPath = Path.Combine(logsRoot, 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)
|
|
{
|
|
string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\InternalLogs\\InternalLog_" +
|
|
DateTime.Now.Date.ToShortDateString().Replace('/', '_') + ".txt";
|
|
LogService.EnqueueLine(filepath, Message);
|
|
}
|
|
}
|
|
}
|