Files
elmprodvpn/selective-vpn-api/app/vpn_locations_cache_refresh.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
}