package app import ( "encoding/json" "os" "strings" "time" ) func cloneVPNLocations(in []vpnLocationItem) []vpnLocationItem { if len(in) == 0 { return []vpnLocationItem{} } out := make([]vpnLocationItem, 0, len(in)) for _, row := range in { item, ok := normalizeVPNLocationItem(row) if !ok { continue } out = append(out, item) } return out } func equalVPNLocations(a, b []vpnLocationItem) bool { if len(a) != len(b) { return false } for i := range a { if a[i].ISO != b[i].ISO || a[i].Label != b[i].Label || a[i].Target != b[i].Target { return false } } return true } func loadVPNLocationsCache(path string) ([]vpnLocationItem, time.Time, error) { data, err := os.ReadFile(path) if err != nil { return nil, time.Time{}, err } var disk vpnLocationsCacheDisk if err := json.Unmarshal(data, &disk); err != nil { return nil, time.Time{}, err } var updatedAt time.Time if ts := strings.TrimSpace(disk.UpdatedAt); ts != "" { parsed, parseErr := time.Parse(time.RFC3339, ts) if parseErr == nil { updatedAt = parsed } } return cloneVPNLocations(disk.Locations), updatedAt, nil } func saveVPNLocationsCache(path string, locs []vpnLocationItem, updatedAt time.Time) error { if err := os.MkdirAll(stateDir, 0o755); err != nil { return err } disk := vpnLocationsCacheDisk{ Locations: cloneVPNLocations(locs), } if !updatedAt.IsZero() { disk.UpdatedAt = updatedAt.UTC().Format(time.RFC3339) } data, err := json.MarshalIndent(disk, "", " ") if err != nil { return err } tmp := path + ".tmp" if err := os.WriteFile(tmp, data, 0o644); err != nil { return err } return os.Rename(tmp, path) } func normalizeVPNLocationItem(in vpnLocationItem) (vpnLocationItem, bool) { iso := strings.ToUpper(strings.TrimSpace(in.ISO)) label := strings.TrimSpace(in.Label) target := strings.TrimSpace(in.Target) if iso == "" || label == "" { return vpnLocationItem{}, false } if target == "" { target = inferVPNLocationTargetFromLabel(label, iso) } if target == "" { target = iso } return vpnLocationItem{ Label: label, ISO: iso, Target: target, }, true } func inferVPNLocationTargetFromLabel(label, iso string) string { trimmed := strings.TrimSpace(label) if trimmed == "" { return "" } if idx := strings.LastIndex(trimmed, "("); idx > 0 && strings.HasSuffix(trimmed, " ms)") { trimmed = strings.TrimSpace(trimmed[:idx]) } prefix := strings.ToUpper(strings.TrimSpace(iso)) if prefix != "" { withSpace := prefix + " " if strings.HasPrefix(strings.ToUpper(trimmed), withSpace) { trimmed = strings.TrimSpace(trimmed[len(withSpace):]) } } return strings.TrimSpace(trimmed) }