137 lines
3.4 KiB
Go
137 lines
3.4 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
func getVPNLocationsSnapshot(force bool) vpnLocationsSnapshot {
|
|
vpnLocationCache.ensureLoaded()
|
|
vpnLocationCache.maybeStartRefresh(force)
|
|
return vpnLocationCache.snapshot(time.Now())
|
|
}
|
|
|
|
func (s *vpnLocationsStore) ensureLoaded() {
|
|
s.mu.Lock()
|
|
if s.loaded {
|
|
s.mu.Unlock()
|
|
return
|
|
}
|
|
s.loaded = true
|
|
s.mu.Unlock()
|
|
|
|
locs, updatedAt, err := loadVPNLocationsCache(vpnLocationsCachePath)
|
|
if err != nil {
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
log.Printf("vpn locations cache load warning: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
if len(locs) > 0 {
|
|
s.locations = cloneVPNLocations(locs)
|
|
}
|
|
if !updatedAt.IsZero() {
|
|
s.swr.setUpdatedAt(updatedAt)
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *vpnLocationsStore) maybeStartRefresh(force bool) {
|
|
now := time.Now()
|
|
|
|
s.mu.Lock()
|
|
if !s.swr.beginRefresh(now, force, len(s.locations) > 0) {
|
|
s.mu.Unlock()
|
|
return
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
go s.refresh()
|
|
}
|
|
|
|
func (s *vpnLocationsStore) snapshot(now time.Time) vpnLocationsSnapshot {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
meta := s.swr.snapshot(now)
|
|
|
|
out := vpnLocationsSnapshot{
|
|
Locations: cloneVPNLocations(s.locations),
|
|
UpdatedAt: meta.UpdatedAt,
|
|
Stale: meta.Stale,
|
|
RefreshInProgress: meta.RefreshInProgress,
|
|
LastError: meta.LastError,
|
|
NextRetryAt: meta.NextRetryAt,
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *vpnLocationsStore) refresh() {
|
|
start := time.Now()
|
|
stdout, _, exitCode, err := runCommandTimeout(vpnLocationsCommandTimeout, adgvpnCLI, "list-locations")
|
|
if err != nil || exitCode != 0 {
|
|
s.finishError(fmt.Sprintf("list-locations failed: err=%v exit=%d", err, exitCode), time.Now())
|
|
log.Printf("vpn locations refresh failed in %s: exit=%d err=%v", time.Since(start), exitCode, err)
|
|
return
|
|
}
|
|
|
|
locs, err := parseVPNLocationsOutput(stdout)
|
|
if err != nil {
|
|
s.finishError(fmt.Sprintf("list-locations parse error: %v", err), time.Now())
|
|
log.Printf("vpn locations parse failed in %s: %v", time.Since(start), err)
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
changed, snapshot := s.finishSuccess(locs, now)
|
|
if err := saveVPNLocationsCache(vpnLocationsCachePath, snapshot.Locations, now); err != nil {
|
|
log.Printf("vpn locations cache save warning: %v", err)
|
|
}
|
|
|
|
events.push("vpn_locations_changed", map[string]any{
|
|
"ok": true,
|
|
"count": len(snapshot.Locations),
|
|
"changed": changed,
|
|
"updated_at": snapshot.UpdatedAt,
|
|
})
|
|
log.Printf("vpn locations refresh ok in %s: count=%d changed=%v", time.Since(start), len(snapshot.Locations), changed)
|
|
}
|
|
|
|
func (s *vpnLocationsStore) finishError(msg string, now time.Time) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.swr.finishError(msg, now)
|
|
meta := s.swr.snapshot(now)
|
|
|
|
events.push("vpn_locations_changed", map[string]any{
|
|
"ok": false,
|
|
"error": meta.LastError,
|
|
"next_retry_at": meta.NextRetryAt,
|
|
"cached_count": len(s.locations),
|
|
})
|
|
}
|
|
|
|
func (s *vpnLocationsStore) finishSuccess(locs []vpnLocationItem, now time.Time) (bool, vpnLocationsSnapshot) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
changed := !equalVPNLocations(s.locations, locs)
|
|
s.locations = cloneVPNLocations(locs)
|
|
s.swr.finishSuccess(now)
|
|
meta := s.swr.snapshot(now)
|
|
|
|
snap := vpnLocationsSnapshot{
|
|
Locations: cloneVPNLocations(s.locations),
|
|
UpdatedAt: meta.UpdatedAt,
|
|
Stale: meta.Stale,
|
|
RefreshInProgress: meta.RefreshInProgress,
|
|
LastError: meta.LastError,
|
|
NextRetryAt: meta.NextRetryAt,
|
|
}
|
|
return changed, snap
|
|
}
|