package app import ( "sort" "strings" "time" ) func (s *egressIdentityService) getSnapshot(scopeRaw string, refresh bool) (EgressIdentity, error) { target, err := parseEgressScope(scopeRaw) if err != nil { return EgressIdentity{}, err } if refresh { s.queueScopeRefresh(target, false) } return s.snapshot(target, time.Now()), nil } func (s *egressIdentityService) queueRefresh(scopes []string, force bool) (EgressIdentityRefreshResponse, error) { rawTargets := make([]string, 0, len(scopes)) for _, raw := range scopes { v := strings.TrimSpace(raw) if v == "" { continue } rawTargets = append(rawTargets, v) } if len(rawTargets) == 0 { rawTargets = s.knownScopes() } if len(rawTargets) == 0 { rawTargets = []string{"adguardvpn", "system"} } targets := make([]egressScopeTarget, 0, len(rawTargets)) seen := map[string]struct{}{} for _, raw := range rawTargets { target, err := parseEgressScope(raw) if err != nil { return EgressIdentityRefreshResponse{}, err } if _, ok := seen[target.Scope]; ok { continue } seen[target.Scope] = struct{}{} targets = append(targets, target) } resp := EgressIdentityRefreshResponse{ OK: true, Message: "refresh queued", Items: make([]EgressIdentityRefreshItem, 0, len(targets)), } for _, target := range targets { queued, reason := s.queueScopeRefresh(target, force) item := EgressIdentityRefreshItem{ Scope: target.Scope, Queued: queued, } if queued { item.Status = "queued" resp.Queued++ } else { item.Status = "skipped" item.Reason = strings.TrimSpace(reason) if item.Reason == "" { item.Reason = "throttled or already fresh" } resp.Skipped++ } resp.Items = append(resp.Items, item) } resp.Count = len(resp.Items) if resp.Queued == 0 { resp.Message = "refresh skipped" } return resp, nil } func (s *egressIdentityService) knownScopes() []string { outSet := map[string]struct{}{ "adguardvpn": {}, "system": {}, } transportMu.Lock() st := loadTransportClientsState() transportMu.Unlock() for _, it := range st.Items { id := sanitizeID(it.ID) if id == "" { continue } outSet["transport:"+id] = struct{}{} } s.mu.Lock() for scope := range s.entries { outSet[scope] = struct{}{} } s.mu.Unlock() out := make([]string, 0, len(outSet)) for scope := range outSet { out = append(out, scope) } sort.Strings(out) return out } func (s *egressIdentityService) queueScopeRefresh(target egressScopeTarget, force bool) (bool, string) { now := time.Now() s.mu.Lock() entry := s.ensureEntryLocked(target) hasData := strings.TrimSpace(entry.item.IP) != "" switch { case entry.swr.refreshInProgress(): s.mu.Unlock() return false, "already in progress" case !force && !entry.swr.nextRetryAt().IsZero() && now.Before(entry.swr.nextRetryAt()): s.mu.Unlock() return false, "backoff in progress" case !force && hasData && !entry.swr.isStale(now): s.mu.Unlock() return false, "already fresh" } if force { entry.swr.clearBackoff() } if !entry.swr.beginRefresh(now, force, hasData) { s.mu.Unlock() return false, "throttled or already fresh" } s.mu.Unlock() go s.refreshScope(target, force) return true, "" }