using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; namespace HanvonF710XAttendanceService { internal sealed class DeviceSettingsConfig { public string Mode { get; private set; } public int? SiteId { get; private set; } public List Devices { get; } = new List(); public TemplateTransferConfig TemplateTransfer { get; private set; } public static bool TryLoad(string path, out DeviceSettingsConfig config, out string error) { config = null; error = null; try { if (string.IsNullOrWhiteSpace(path)) { error = "Device settings path is empty."; return false; } if (!File.Exists(path)) { config = new DeviceSettingsConfig(); return true; } var doc = XDocument.Load(path); var root = doc.Root; if (root == null || !string.Equals(root.Name.LocalName, "DeviceSettings", StringComparison.OrdinalIgnoreCase)) { error = $"Invalid device settings root element. Expected , found <{root?.Name.LocalName ?? "null"}>."; return false; } var parsed = new DeviceSettingsConfig { Mode = (string)root.Attribute("Mode") }; var rootSiteIdStr = (string)root.Attribute("SiteId") ?? (string)root.Attribute("SITE_ID") ?? (string)root.Attribute("SiteID"); if (!string.IsNullOrWhiteSpace(rootSiteIdStr) && int.TryParse(rootSiteIdStr.Trim(), out var rootSiteId)) { parsed.SiteId = rootSiteId; } foreach (var deviceEl in root.Elements().Where(e => string.Equals(e.Name.LocalName, "Device", StringComparison.OrdinalIgnoreCase))) { var ip = (string)deviceEl.Attribute("IpAddress") ?? (string)deviceEl.Attribute("Ip") ?? ""; var enabledStr = (string)deviceEl.Attribute("Enabled"); if (string.IsNullOrWhiteSpace(ip) || string.IsNullOrWhiteSpace(enabledStr)) { continue; } if (!TryParseBool(enabledStr, out var enabled)) { continue; } int? siteId = null; var siteIdStr = (string)deviceEl.Attribute("SiteId") ?? (string)deviceEl.Attribute("SITE_ID") ?? (string)deviceEl.Attribute("SiteID"); if (!string.IsNullOrWhiteSpace(siteIdStr) && int.TryParse(siteIdStr.Trim(), out var parsedSiteId)) { siteId = parsedSiteId; } parsed.Devices.Add(new DeviceRule(ip.Trim(), enabled, siteId)); } // If SiteId is not specified at root, but devices specify a single consistent SiteId, use it. if (!parsed.SiteId.HasValue) { var distinct = parsed.Devices.Where(d => d.SiteId.HasValue).Select(d => d.SiteId.Value).Distinct().ToList(); if (distinct.Count == 1) { parsed.SiteId = distinct[0]; } } var templateTransferEl = root.Elements().FirstOrDefault(e => string.Equals(e.Name.LocalName, "TemplateTransfer", StringComparison.OrdinalIgnoreCase)); if (templateTransferEl != null) { var ttEnabled = TryParseBool((string)templateTransferEl.Attribute("Enabled"), out var tt) && tt; var ttConfig = new TemplateTransferConfig(ttEnabled); foreach (var jobEl in templateTransferEl.Elements().Where(e => string.Equals(e.Name.LocalName, "Job", StringComparison.OrdinalIgnoreCase))) { var id = (string)jobEl.Attribute("Id") ?? ""; var enabled = TryParseBool((string)jobEl.Attribute("Enabled"), out var jEnabled) && jEnabled; var sourceIp = (string)jobEl.Attribute("SourceIp") ?? (string)jobEl.Attribute("SourceIP") ?? ""; if (string.IsNullOrWhiteSpace(id)) { continue; } var targets = new List(); foreach (var targetEl in jobEl.Elements().Where(e => string.Equals(e.Name.LocalName, "Target", StringComparison.OrdinalIgnoreCase))) { var tip = (string)targetEl.Attribute("Ip") ?? (string)targetEl.Attribute("IpAddress") ?? ""; if (!string.IsNullOrWhiteSpace(tip)) { targets.Add(tip.Trim()); } } // Optional comma-separated list of employee IDs for DB_TO_DEVICE filtering var empIdsAttr = (string)jobEl.Attribute("EmpIds") ?? (string)jobEl.Attribute("EmpIDs"); List empIds = null; if (!string.IsNullOrWhiteSpace(empIdsAttr)) { empIds = empIdsAttr .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(v => v.Trim()) .Where(v => v.Length > 0) .ToList(); } var departmentIdsAttr = (string)jobEl.Attribute("DepartmentIds") ?? (string)jobEl.Attribute("DepartmentIDs") ?? (string)jobEl.Attribute("DeptIds"); List departmentIds = null; if (!string.IsNullOrWhiteSpace(departmentIdsAttr)) { departmentIds = departmentIdsAttr .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(v => v.Trim()) .Where(v => v.Length > 0) .ToList(); } ttConfig.Jobs.Add(new TemplateTransferJob(id.Trim(), enabled, sourceIp.Trim(), targets, empIds, departmentIds)); } parsed.TemplateTransfer = ttConfig; } config = parsed; return true; } catch (Exception ex) { error = $"Failed to load device settings: {ex.Message}"; return false; } } public bool HasAnyAllowlistedDeviceIp() => Devices.Any(d => d.Enabled); public HashSet GetAllowlistedIps() => new HashSet(Devices.Where(d => d.Enabled).Select(d => d.IpAddress), StringComparer.OrdinalIgnoreCase); public HashSet GetBlocklistedIps() => new HashSet(Devices.Where(d => !d.Enabled).Select(d => d.IpAddress), StringComparer.OrdinalIgnoreCase); private static bool TryParseBool(string value, out bool result) { if (string.IsNullOrWhiteSpace(value)) { result = false; return false; } return bool.TryParse(value.Trim(), out result); } } internal sealed class DeviceRule { public DeviceRule(string ipAddress, bool enabled, int? siteId) { IpAddress = ipAddress ?? ""; Enabled = enabled; SiteId = siteId; } public string IpAddress { get; } public bool Enabled { get; } public int? SiteId { get; } } internal sealed class TemplateTransferConfig { public TemplateTransferConfig(bool enabled) { Enabled = enabled; } public bool Enabled { get; } public List Jobs { get; } = new List(); } internal sealed class TemplateTransferJob { public TemplateTransferJob(string id, bool enabled, string sourceIp, List targetIps, List empIds, List departmentIds = null) { Id = id ?? ""; Enabled = enabled; SourceIp = sourceIp ?? ""; TargetIps = targetIps ?? new List(); EmpIds = empIds ?? new List(); DepartmentIds = departmentIds ?? new List(); } public string Id { get; } public bool Enabled { get; } public string SourceIp { get; } public List TargetIps { get; } public List EmpIds { get; } public List DepartmentIds { get; } } }