package state import ( "encoding/json" "os" "path/filepath" "sync" "time" ) // AppState holds persistent client state. type AppState struct { SelectedServer string `json:"selected_server"` SelectedMode string `json:"selected_mode"` LastSync time.Time `json:"last_sync"` AutoConnect bool `json:"auto_connect"` EnabledRuleSets map[string]bool `json:"enabled_rule_sets,omitempty"` CustomBypass []string `json:"custom_bypass_processes,omitempty"` } // Store manages persistent state on disk. type Store struct { mu sync.Mutex path string data AppState } // NewStore creates a state store at the given path. func NewStore(dataDir string) *Store { return &Store{ path: filepath.Join(dataDir, "state.json"), data: AppState{ SelectedMode: "Комбо (приложения + Re-filter)", AutoConnect: false, }, } } // Load reads state from disk. Returns default state if file doesn't exist. func (s *Store) Load() error { s.mu.Lock() defer s.mu.Unlock() data, err := os.ReadFile(s.path) if err != nil { if os.IsNotExist(err) { return nil } return err } return json.Unmarshal(data, &s.data) } // Save writes state to disk. func (s *Store) Save() error { s.mu.Lock() defer s.mu.Unlock() if err := os.MkdirAll(filepath.Dir(s.path), 0o755); err != nil { return err } data, err := json.MarshalIndent(s.data, "", " ") if err != nil { return err } return os.WriteFile(s.path, data, 0o644) } // Get returns a copy of the current state. func (s *Store) Get() AppState { s.mu.Lock() defer s.mu.Unlock() return s.data } // SetServer updates the selected server. func (s *Store) SetServer(tag string) { s.mu.Lock() s.data.SelectedServer = tag s.mu.Unlock() } // SetMode updates the selected routing mode. func (s *Store) SetMode(mode string) { s.mu.Lock() s.data.SelectedMode = mode s.mu.Unlock() } // SetLastSync records the last sync time. func (s *Store) SetLastSync(t time.Time) { s.mu.Lock() s.data.LastSync = t s.mu.Unlock() } // SetAutoConnect updates the auto-connect setting. func (s *Store) SetAutoConnect(v bool) { s.mu.Lock() s.data.AutoConnect = v s.mu.Unlock() } // SetRuleSetEnabled enables/disables an optional rule-set. func (s *Store) SetRuleSetEnabled(tag string, enabled bool) { s.mu.Lock() if s.data.EnabledRuleSets == nil { s.data.EnabledRuleSets = make(map[string]bool) } s.data.EnabledRuleSets[tag] = enabled s.mu.Unlock() } // IsRuleSetEnabled checks if a rule-set is enabled. func (s *Store) IsRuleSetEnabled(tag string) bool { s.mu.Lock() defer s.mu.Unlock() if s.data.EnabledRuleSets == nil { return false } return s.data.EnabledRuleSets[tag] } // SetCustomBypass sets custom bypass processes. func (s *Store) SetCustomBypass(processes []string) { s.mu.Lock() s.data.CustomBypass = processes s.mu.Unlock() } // GetCustomBypass returns custom bypass processes. func (s *Store) GetCustomBypass() []string { s.mu.Lock() defer s.mu.Unlock() return append([]string{}, s.data.CustomBypass...) }