teg/backend/milestone_devices.go
2026-05-29 16:18:28 +02:00

491 lines
9.5 KiB
Go

// backend\milestone_devices.go
package main
import (
"context"
"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.Port = strings.TrimSpace(hardwareDevice.Port)
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 err := syncMilestoneHardwareChildDevices(ctx, tx, hardwareDevice); err != nil {
return syncedCount, err
}
if synced {
syncedCount++
}
}
return syncedCount, nil
}
func syncMilestoneHardwareChildDevices(
ctx context.Context,
tx pgx.Tx,
hardwareDevice milestoneHardwareDevice,
) error {
hardwareID := strings.TrimSpace(hardwareDevice.HardwareID)
if hardwareID == "" {
return nil
}
_, err := tx.Exec(
ctx,
`
DELETE FROM milestone_hardware_child_devices
WHERE milestone_hardware_id = $1
`,
hardwareID,
)
if err != nil {
return err
}
for _, camera := range hardwareDevice.Cameras {
if err := upsertMilestoneHardwareChildDevice(
ctx,
tx,
hardwareID,
"camera",
camera,
); err != nil {
return err
}
}
for _, microphone := range hardwareDevice.Microphones {
if err := upsertMilestoneHardwareChildDevice(
ctx,
tx,
hardwareID,
"microphone",
microphone,
); err != nil {
return err
}
}
for _, input := range hardwareDevice.Inputs {
if err := upsertMilestoneHardwareChildDevice(
ctx,
tx,
hardwareID,
"input",
input,
); err != nil {
return err
}
}
for _, output := range hardwareDevice.Outputs {
if err := upsertMilestoneHardwareChildDevice(
ctx,
tx,
hardwareID,
"output",
output,
); err != nil {
return err
}
}
for _, speaker := range hardwareDevice.Speakers {
if err := upsertMilestoneHardwareChildDevice(
ctx,
tx,
hardwareID,
"speaker",
speaker,
); err != nil {
return err
}
}
for _, metadata := range hardwareDevice.Metadata {
if err := upsertMilestoneHardwareChildDevice(
ctx,
tx,
hardwareID,
"metadata",
metadata,
); err != nil {
return err
}
}
return nil
}
func upsertMilestoneHardwareChildDevice(
ctx context.Context,
tx pgx.Tx,
hardwareID string,
deviceType string,
childDevice milestoneNamedDevice,
) error {
milestoneDeviceID := strings.TrimSpace(childDevice.ID)
if milestoneDeviceID == "" {
return nil
}
name := strings.TrimSpace(childDevice.Name)
displayName := strings.TrimSpace(childDevice.DisplayName)
shortName := strings.TrimSpace(childDevice.ShortName)
if name == "" {
name = displayName
}
if name == "" {
name = shortName
}
if displayName == "" {
displayName = name
}
if shortName == "" {
shortName = name
}
_, err := tx.Exec(
ctx,
`
INSERT INTO milestone_hardware_child_devices (
milestone_hardware_id,
milestone_device_id,
device_type,
name,
display_name,
short_name,
enabled
)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (milestone_hardware_id, milestone_device_id, device_type)
DO UPDATE SET
name = EXCLUDED.name,
display_name = EXCLUDED.display_name,
short_name = EXCLUDED.short_name,
enabled = EXCLUDED.enabled,
updated_at = now()
`,
hardwareID,
milestoneDeviceID,
deviceType,
name,
displayName,
shortName,
childDevice.Enabled,
)
return err
}
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,
milestone_port,
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,
$7,
'',
'Verfügbar',
false,
'',
$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,
milestone_port = EXCLUDED.milestone_port,
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,
hardwareDevice.Port,
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
}