Implement device-to-device template sync service flow
Add the service logic for copying users and face templates from one Hikvision device to another. - Rename DbToDevice sync method to DeviceToDevice - Support source-device face download when source is connected - Fall back to stored DB face templates when source is unavailable - Register transferred users on the target machine - Record Device → Device operation historymain
parent
ab084ebb50
commit
78b8a9bc86
|
|
@ -72,22 +72,22 @@ public sealed class TemplateSyncService(
|
|||
return entry;
|
||||
}
|
||||
|
||||
public async Task<SyncHistoryEntry> DbToDeviceAsync(Device source, Device target, IReadOnlyList<string> serialNumbers,
|
||||
IProgress<UserSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
public async Task<SyncHistoryEntry> DeviceToDeviceAsync(Device source, Device target, IReadOnlyList<DeviceTransferUser> users,
|
||||
bool sourceConnected, IProgress<UserSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
{
|
||||
AppLogger.TraceEnter("TEMPLATE_SYNC", nameof(DbToDeviceAsync),
|
||||
$"source={source.IpAddress} target={target.IpAddress} count={serialNumbers.Count}");
|
||||
AppLogger.TraceEnter("TEMPLATE_SYNC", nameof(DeviceToDeviceAsync),
|
||||
$"source={source.IpAddress} target={target.IpAddress} count={users.Count} sourceConnected={sourceConnected}");
|
||||
if (string.Equals(source.MachineId, target.MachineId, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(source.IpAddress, target.IpAddress, StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("Source and target devices must be different.");
|
||||
|
||||
var entry = new SyncHistoryEntry
|
||||
{
|
||||
Operation = SyncOperationKind.DbToDevice,
|
||||
Operation = SyncOperationKind.DeviceToDevice,
|
||||
SourceDevice = source.DisplayName,
|
||||
TargetDevice = target.DisplayName,
|
||||
Context = $"{serialNumbers.Count} user(s)",
|
||||
Total = serialNumbers.Count,
|
||||
Context = $"{users.Count} user(s)",
|
||||
Total = users.Count,
|
||||
Status = "Running"
|
||||
};
|
||||
await history.AddAsync(entry, cancellationToken);
|
||||
|
|
@ -96,18 +96,18 @@ public sealed class TemplateSyncService(
|
|||
var faces = 0;
|
||||
var skipped = 0;
|
||||
var failed = 0;
|
||||
var registered = 0;
|
||||
|
||||
for (var index = 0; index < serialNumbers.Count; index++)
|
||||
for (var index = 0; index < users.Count; index++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var serial = serialNumbers[index];
|
||||
progress?.Report(new UserSyncProgress(index, serialNumbers.Count, serial, "Creating user on target…", created, 0, faces, 0, skipped, failed));
|
||||
var user = users[index];
|
||||
var serial = user.SerialNumber;
|
||||
var name = user.EmployeeName;
|
||||
progress?.Report(new UserSyncProgress(index, users.Count, serial, "Creating user on target…", created, 0, faces, 0, skipped, failed));
|
||||
try
|
||||
{
|
||||
var dbUsers = await machineUsers.GetActiveByMachineAsync(source.MachineId, cancellationToken);
|
||||
var dbUser = dbUsers.FirstOrDefault(u => string.Equals(u.SerialNumber, serial, StringComparison.OrdinalIgnoreCase));
|
||||
var name = dbUser?.EmployeeName ?? serial;
|
||||
|
||||
var userCreatedNow = false;
|
||||
if (!await hikvision.UserExistsAsync(target, serial, cancellationToken))
|
||||
{
|
||||
var create = await hikvision.CreateUserAsync(target, serial, name, cancellationToken);
|
||||
|
|
@ -116,49 +116,77 @@ public sealed class TemplateSyncService(
|
|||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
created++;
|
||||
userCreatedNow = true;
|
||||
}
|
||||
|
||||
var template = await faceTemplates.TryGetActiveAsync(serial, cancellationToken);
|
||||
if (template?.Template is null or { Length: 0 })
|
||||
byte[]? faceBytes = null;
|
||||
if (sourceConnected)
|
||||
{
|
||||
skipped++;
|
||||
continue;
|
||||
progress?.Report(new UserSyncProgress(index, users.Count, serial, "Downloading face from source device…", created, 0, faces, 0, skipped, failed));
|
||||
var download = await hikvision.DownloadFaceTemplateAsync(source, serial, cancellationToken);
|
||||
if (download.Success && HikvisionIsapiClient.IsUploadableFaceTemplate(download.Bytes))
|
||||
faceBytes = download.Bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
var template = await faceTemplates.TryGetActiveAsync(serial, cancellationToken);
|
||||
if (template?.Template is { Length: > 0 } && HikvisionIsapiClient.IsUploadableFaceTemplate(template.Template))
|
||||
faceBytes = template.Template;
|
||||
}
|
||||
|
||||
if (!HikvisionIsapiClient.IsUploadableFaceTemplate(template.Template))
|
||||
if (faceBytes is null)
|
||||
{
|
||||
skipped++;
|
||||
AppLogger.Info($"DB→Device skipped non-JPEG template for {serial}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
progress?.Report(new UserSyncProgress(index, serialNumbers.Count, serial, "Uploading face template…", created, 0, faces, 0, skipped, failed));
|
||||
var upload = await hikvision.UploadFaceAsync(target, serial, template.Template, cancellationToken);
|
||||
if (upload.Success)
|
||||
{
|
||||
faces++;
|
||||
await machineUsers.UpsertAsync(target.MachineId, serial, name, cancellationToken);
|
||||
registered++;
|
||||
skipped++;
|
||||
AppLogger.Info($"Device→Device skipped face for {serial} (no uploadable template from {(sourceConnected ? "source device" : "DB")}).");
|
||||
continue;
|
||||
}
|
||||
else failed++;
|
||||
|
||||
progress?.Report(new UserSyncProgress(index, users.Count, serial, "Uploading face to target…", created, 0, faces, 0, skipped, failed));
|
||||
var upload = await hikvision.UploadFaceAsync(target, serial, faceBytes, cancellationToken);
|
||||
if (!upload.Success)
|
||||
{
|
||||
if (userCreatedNow)
|
||||
await machineUsers.UpsertAsync(target.MachineId, serial, name, cancellationToken);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
progress?.Report(new UserSyncProgress(index, users.Count, serial, "Verifying face enrollment…", created, 0, faces, 0, skipped, failed));
|
||||
var verify = await hikvision.VerifyFaceAsync(target, serial, cancellationToken);
|
||||
if (!verify.Success)
|
||||
{
|
||||
if (userCreatedNow)
|
||||
await machineUsers.UpsertAsync(target.MachineId, serial, name, cancellationToken);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
faces++;
|
||||
registered++;
|
||||
await machineUsers.UpsertAsync(target.MachineId, serial, name, cancellationToken);
|
||||
await faceTemplates.UpsertAsync(serial, faceBytes, DateTime.Now, true, target.MachineId, target.IpAddress, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failed++;
|
||||
AppLogger.Warning($"DB→Device failed for {serial}: {ex.Message}");
|
||||
AppLogger.Warning($"Device→Device failed for {serial}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
entry.CompletedAt = DateTime.Now;
|
||||
entry.Success = created;
|
||||
entry.Success = registered;
|
||||
entry.Failed = failed;
|
||||
entry.Skipped = skipped;
|
||||
entry.Context = $"{serialNumbers.Count} user(s), {faces} face(s) uploaded";
|
||||
entry.Context = $"{users.Count} selected, {registered} registered on target, {faces} face(s) verified";
|
||||
entry.Status = failed > 0 ? "Completed with errors" : "Completed";
|
||||
await history.UpdateAsync(entry, cancellationToken);
|
||||
progress?.Report(new UserSyncProgress(serialNumbers.Count, serialNumbers.Count, "", "Completed", created, 0, faces, 0, skipped, failed));
|
||||
AppLogger.TraceExit("TEMPLATE_SYNC", nameof(DbToDeviceAsync),
|
||||
$"created={created} faces={faces} failed={failed} skipped={skipped}");
|
||||
progress?.Report(new UserSyncProgress(users.Count, users.Count, "", "Completed", created, 0, faces, 0, skipped, failed));
|
||||
AppLogger.TraceExit("TEMPLATE_SYNC", nameof(DeviceToDeviceAsync),
|
||||
$"registered={registered} created={created} faces={faces} failed={failed} skipped={skipped}");
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue