diff --git a/ApplicationPaths.cs b/ApplicationPaths.cs
new file mode 100644
index 0000000..d8ab15e
--- /dev/null
+++ b/ApplicationPaths.cs
@@ -0,0 +1,67 @@
+using System;
+using System.IO;
+
+namespace HanvonF710XAttendanceService
+{
+ internal static class ApplicationPaths
+ {
+ public static string BaseDirectory { get; private set; } = AppDomain.CurrentDomain.BaseDirectory ?? "";
+
+ public static string InternalLogs => Path.Combine(BaseDirectory, "InternalLogs");
+ public static string Logs => Path.Combine(BaseDirectory, "Logs");
+ public static string TemplateRawLogs => Path.Combine(BaseDirectory, "TemplateRawLogs");
+
+ public static void Initialize()
+ {
+ BaseDirectory = AppDomain.CurrentDomain.BaseDirectory ?? "";
+ try
+ {
+ Environment.CurrentDirectory = BaseDirectory;
+ }
+ catch
+ {
+ // Best effort; paths below are always rooted to BaseDirectory.
+ }
+
+ EnsureDirectory(InternalLogs);
+ EnsureDirectory(Logs);
+ EnsureDirectory(TemplateRawLogs);
+ EnsureDirectory(Path.Combine(Logs, "Attendance"));
+ EnsureDirectory(Path.Combine(Logs, "Machine User service"));
+ EnsureDirectory(Path.Combine(Logs, "SaveFaceTemplate logs"));
+ EnsureDirectory(Path.Combine(Logs, "FaceTransferLog"));
+ }
+
+ public static string Resolve(string relativeOrAbsolutePath)
+ {
+ if (string.IsNullOrWhiteSpace(relativeOrAbsolutePath))
+ {
+ return BaseDirectory;
+ }
+
+ string trimmed = relativeOrAbsolutePath.Trim();
+ return Path.IsPathRooted(trimmed)
+ ? trimmed
+ : Path.Combine(BaseDirectory, trimmed);
+ }
+
+ public static string DatedFile(string directory, string filePrefix)
+ {
+ string datePart = DateTime.Now.Date.ToShortDateString().Replace('/', '_');
+ return Path.Combine(directory, filePrefix + "_" + datePart + ".txt");
+ }
+
+ private static void EnsureDirectory(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return;
+ }
+
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+ }
+ }
+}
diff --git a/HanvonF710XWindowsService.cs b/HanvonF710XWindowsService.cs
index 4881c97..62971a7 100644
--- a/HanvonF710XWindowsService.cs
+++ b/HanvonF710XWindowsService.cs
@@ -16,48 +16,35 @@ 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
+ private System.Timers.Timer timer = new System.Timers.Timer();
+ private System.Timers.Timer timer1 = new System.Timers.Timer();
- // 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;
+ private static volatile bool _attendanceJobRunning;
+ private static volatile bool _templateJobRunning;
+ private volatile bool _stopping;
+
public HanvonF710XWindowsService()
{
InitializeComponent();
}
- /// Starts timers and jobs when running outside the Windows Service Control Manager.
public void StartForConsole(string[] args)
{
OnStart(args ?? Array.Empty());
}
- /// Stops timers and flushes logs when running in console/debug mode.
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 { }
- }
+ ApplicationPaths.Initialize();
- // Start buffered, cross-process-safe logging.
- try { LogService.Start(); } catch { }
+ 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);
@@ -72,12 +59,12 @@ namespace HanvonF710XAttendanceService
double attendanceIntervalMs = Math.Max(1, attendanceMinutes) * 60 * 1000;
double templateIntervalMs = Math.Max(1, templateMinutes) * 60 * 1000;
- timer.Elapsed += new ElapsedEventHandler(OnAttendanceElapsed);
+ timer.Elapsed += OnAttendanceElapsed;
timer.Interval = attendanceIntervalMs;
timer.AutoReset = true;
timer.Enabled = true;
- timer1.Elapsed += new ElapsedEventHandler(OnTemplateElapsed);
+ timer1.Elapsed += OnTemplateElapsed;
timer1.Interval = templateIntervalMs;
timer1.AutoReset = true;
timer1.Enabled = true;
@@ -87,42 +74,117 @@ namespace HanvonF710XAttendanceService
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());
+ Task.Run(() => RunStartupAttendanceJob());
}
- catch { }
+ 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(async () =>
- {
- try
- {
- await Task.Delay(delaySec * 1000).ConfigureAwait(false);
- RunTemplateJob();
- }
- catch { }
- });
+ Task.Run(() => RunStartupTemplateJob(delaySec));
+ }
+ catch (Exception ex)
+ {
+ WriteInternalLog("Startup template trigger failed: " + ex.Message);
}
- catch { }
}
+
protected override void OnStop()
{
- WriteToFile("--Service is stopped at " + DateTime.Now);
+ _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 { }
+ 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)
@@ -139,7 +201,6 @@ namespace HanvonF710XAttendanceService
}
}
- // Attendance + machine-user job
private void OnAttendanceElapsed(object source, ElapsedEventArgs e)
{
RunAttendanceJob();
@@ -147,11 +208,18 @@ namespace HanvonF710XAttendanceService
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)
@@ -159,6 +227,7 @@ namespace HanvonF710XAttendanceService
WriteInternalLog("--Attendance tick skipped (previous attendance job still running) at " + DateTime.Now);
return;
}
+
_attendanceJobRunning = true;
WriteSyncCycleHeader();
@@ -172,32 +241,42 @@ namespace HanvonF710XAttendanceService
}
catch (Exception ex)
{
- WriteInternalLog("syncMachineUsers error: " + ex.Message);
+ 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.Message);
+ 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 { }
+ try { Program.EndSyncCycle(); } catch (Exception ex) { WriteInternalLog("EndSyncCycle error: " + ex.Message); }
_attendanceJobRunning = false;
Monitor.Exit(_attendanceLock);
}
}
- // Template job
private void OnTemplateElapsed(object source, ElapsedEventArgs e)
{
RunTemplateJob();
@@ -205,11 +284,18 @@ namespace HanvonF710XAttendanceService
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)
@@ -217,6 +303,7 @@ namespace HanvonF710XAttendanceService
WriteInternalLog("--Template tick skipped (previous template job still running) at " + DateTime.Now);
return;
}
+
_templateJobRunning = true;
WriteJobHeaderToSimpleLogs("TEMPLATE");
@@ -229,14 +316,21 @@ namespace HanvonF710XAttendanceService
}
catch (Exception ex)
{
- WriteInternalLog("syncTemplatesIfEnabled error: " + ex.ToString());
- WriteFaceTemplateTransferLog("syncTemplatesIfEnabled error: " + ex.ToString());
- WriteSaveFaceTemplateLog("syncTemplatesIfEnabled error: " + ex.ToString());
+ 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;
@@ -244,11 +338,8 @@ namespace HanvonF710XAttendanceService
}
}
-
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);
@@ -258,17 +349,15 @@ namespace HanvonF710XAttendanceService
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);
@@ -278,43 +367,38 @@ namespace HanvonF710XAttendanceService
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 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";
+ string filepath = ApplicationPaths.DatedFile(ApplicationPaths.Logs, "ServiceLog");
LogService.EnqueueLine(filepath, Message);
}
@@ -336,7 +420,6 @@ namespace HanvonF710XAttendanceService
WriteInternalLog(line3);
WriteInternalLog(line4);
- // Simple per-feature logs (keep InternalLogs unchanged).
WriteAttendanceServiceLog(line1);
WriteAttendanceServiceLog(line2);
WriteAttendanceServiceLog(line3);
@@ -348,8 +431,6 @@ namespace HanvonF710XAttendanceService
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");
@@ -375,7 +456,6 @@ namespace HanvonF710XAttendanceService
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;
@@ -385,7 +465,6 @@ namespace HanvonF710XAttendanceService
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;
@@ -404,13 +483,11 @@ namespace HanvonF710XAttendanceService
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:" 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;
@@ -419,10 +496,7 @@ namespace HanvonF710XAttendanceService
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 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);
@@ -447,11 +521,10 @@ namespace HanvonF710XAttendanceService
{
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);
+ Program.WriteInternalLog(Message);
}
}
}