125 lines
2.6 KiB
Go
125 lines
2.6 KiB
Go
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)
|
|
}
|