platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
136
selective-vpn-api/app/vpn_locations_cache_refresh.go
Normal file
136
selective-vpn-api/app/vpn_locations_cache_refresh.go
Normal file
@@ -0,0 +1,136 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user