teg/backend/milestone_devices.go
2026-05-22 14:11:18 +02:00

355 lines
7.4 KiB
Go

// backend\milestone_devices.go
package main
import (
"context"
"fmt"
"strings"
"unicode"
"github.com/jackc/pgx/v5"
)
func syncMilestoneHardwareDevices(
ctx context.Context,
tx pgx.Tx,
hardwareDevices []milestoneHardwareDevice,
) (int, error) {
syncedCount := 0
for _, hardwareDevice := range hardwareDevices {
hardwareDevice.HardwareID = strings.TrimSpace(hardwareDevice.HardwareID)
hardwareDevice.RecordingServerID = strings.TrimSpace(hardwareDevice.RecordingServerID)
hardwareDevice.DisplayName = strings.TrimSpace(hardwareDevice.DisplayName)
hardwareDevice.DeviceName = strings.TrimSpace(hardwareDevice.DeviceName)
hardwareDevice.Manufacturer = strings.TrimSpace(hardwareDevice.Manufacturer)
hardwareDevice.DeviceModelName = strings.TrimSpace(hardwareDevice.DeviceModelName)
hardwareDevice.SerialNumber = strings.TrimSpace(hardwareDevice.SerialNumber)
hardwareDevice.MacAddress = strings.TrimSpace(hardwareDevice.MacAddress)
hardwareDevice.IPAddress = strings.TrimSpace(hardwareDevice.IPAddress)
hardwareDevice.FirmwareVersion = strings.TrimSpace(hardwareDevice.FirmwareVersion)
if hardwareDevice.HardwareID == "" {
continue
}
if hardwareDevice.Manufacturer != "" {
if err := ensureOptionalDeviceLookupValue(
ctx,
tx,
"device_manufacturers",
hardwareDevice.Manufacturer,
); err != nil {
return syncedCount, err
}
}
synced, err := createOrUpdateMilestoneHardwareDevice(ctx, tx, hardwareDevice)
if err != nil {
return syncedCount, err
}
if synced {
syncedCount++
}
}
return syncedCount, nil
}
func createOrUpdateMilestoneHardwareDevice(
ctx context.Context,
tx pgx.Tx,
hardwareDevice milestoneHardwareDevice,
) (bool, error) {
inventoryNumber, err := buildMilestoneInventoryNumber(ctx, tx, hardwareDevice)
if err != nil {
return false, err
}
commandTag, err := tx.Exec(
ctx,
`
INSERT INTO devices (
inventory_number,
manufacturer,
model,
serial_number,
mac_address,
ip_address,
location,
loan_status,
is_bao_device,
comment,
firmware_version,
milestone_recording_server_id,
milestone_hardware_id,
milestone_display_name,
milestone_enabled,
milestone_synced_at
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
'',
'Verfügbar',
false,
$7,
$8,
$9,
$10,
$11,
$12,
now()
)
ON CONFLICT (milestone_hardware_id)
WHERE milestone_hardware_id <> ''
DO UPDATE SET
inventory_number = EXCLUDED.inventory_number,
manufacturer = EXCLUDED.manufacturer,
model = EXCLUDED.model,
serial_number = EXCLUDED.serial_number,
mac_address = EXCLUDED.mac_address,
ip_address = EXCLUDED.ip_address,
location = EXCLUDED.location,
comment = EXCLUDED.comment,
firmware_version = EXCLUDED.firmware_version,
milestone_recording_server_id = EXCLUDED.milestone_recording_server_id,
milestone_display_name = EXCLUDED.milestone_display_name,
milestone_enabled = EXCLUDED.milestone_enabled,
milestone_synced_at = now(),
updated_at = now()
`,
inventoryNumber,
hardwareDevice.Manufacturer,
hardwareDevice.DeviceModelName,
hardwareDevice.SerialNumber,
hardwareDevice.MacAddress,
hardwareDevice.IPAddress,
buildMilestoneDeviceComment(hardwareDevice),
hardwareDevice.FirmwareVersion,
hardwareDevice.RecordingServerID,
hardwareDevice.HardwareID,
hardwareDevice.DisplayName,
hardwareDevice.Enabled,
)
if err != nil {
return false, err
}
return commandTag.RowsAffected() > 0, nil
}
func normalizeMilestoneManufacturer(value string, model string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
model = strings.TrimSpace(model)
normalizedValue := strings.ToLower(value)
if strings.EqualFold(value, model) ||
strings.HasSuffix(normalizedValue, " device") ||
strings.Contains(normalizedValue, " camera") ||
strings.Contains(normalizedValue, " encoder") ||
strings.Contains(normalizedValue, " video") {
if manufacturer := extractMilestoneManufacturerFromName(value); manufacturer != "" {
return manufacturer
}
}
return value
}
func extractMilestoneManufacturerFromName(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
parts := strings.FieldsFunc(value, func(r rune) bool {
return unicode.IsSpace(r) ||
r == '_' ||
r == '-' ||
r == '/' ||
r == '\\' ||
r == '(' ||
r == ')' ||
r == ','
})
ignoredWords := map[string]bool{
"device": true,
"camera": true,
"video": true,
"encoder": true,
"ip": true,
"network": true,
"hardware": true,
}
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
if isOnlyDigits(part) {
continue
}
if ignoredWords[strings.ToLower(part)] {
continue
}
return part
}
return ""
}
func buildMilestoneInventoryNumber(
ctx context.Context,
tx pgx.Tx,
hardwareDevice milestoneHardwareDevice,
) (string, error) {
inventoryNumber := extractInventoryNumberFromMilestoneDeviceName(
hardwareDevice.DeviceName,
)
if inventoryNumber == "" {
inventoryNumber = extractInventoryNumberFromMilestoneDeviceName(
hardwareDevice.DisplayName,
)
}
if inventoryNumber == "" {
inventoryNumber = buildGeneratedMilestoneInventoryNumber(
hardwareDevice.HardwareID,
)
}
var exists bool
err := tx.QueryRow(
ctx,
`
SELECT EXISTS(
SELECT 1
FROM devices
WHERE lower(inventory_number) = lower($1)
AND milestone_hardware_id <> $2
)
`,
inventoryNumber,
hardwareDevice.HardwareID,
).Scan(&exists)
if err != nil {
return "", err
}
if !exists {
return inventoryNumber, nil
}
suffix := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(hardwareDevice.HardwareID), "-", ""))
if len(suffix) > 8 {
suffix = suffix[:8]
}
if suffix == "" {
suffix = "MS"
}
return inventoryNumber + "-" + suffix, nil
}
func extractInventoryNumberFromMilestoneDeviceName(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
parts := strings.FieldsFunc(value, func(r rune) bool {
return r == '_' || r == '-' || unicode.IsSpace(r)
})
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
if isOnlyDigits(part) {
return part
}
}
return ""
}
func isOnlyDigits(value string) bool {
if value == "" {
return false
}
for _, r := range value {
if r < '0' || r > '9' {
return false
}
}
return true
}
func buildGeneratedMilestoneInventoryNumber(hardwareID string) string {
value := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(hardwareID), "-", ""))
if len(value) > 12 {
value = value[:12]
}
if value == "" {
value = "UNKNOWN"
}
return "MS-" + value
}
func buildMilestoneDeviceComment(hardwareDevice milestoneHardwareDevice) string {
parts := []string{}
if strings.TrimSpace(hardwareDevice.DisplayName) != "" {
parts = append(parts, strings.TrimSpace(hardwareDevice.DisplayName))
}
if strings.TrimSpace(hardwareDevice.DeviceName) != "" &&
strings.TrimSpace(hardwareDevice.DeviceName) != strings.TrimSpace(hardwareDevice.DisplayName) {
parts = append(parts, "DeviceName: "+strings.TrimSpace(hardwareDevice.DeviceName))
}
if hardwareDevice.Enabled {
parts = append(parts, "Status: aktiviert")
} else {
parts = append(parts, "Status: deaktiviert")
}
if len(parts) == 0 {
return "Automatisch aus Milestone synchronisiert."
}
return fmt.Sprintf(
"Automatisch aus Milestone synchronisiert: %s",
strings.Join(parts, " · "),
)
}