// Package storage handles cross-platform persistence of configuration and // subscription data, using YAML files under a per-user config directory. package storage import ( "fmt" "os" "path/filepath" "runtime" "gopkg.in/yaml.v3" "gitea.epss.net.cn/klesh/ss/internal/model" "gitea.epss.net.cn/klesh/ss/internal/yamlutil" ) // Manager manages cross-platform data storage for subscriptions and configuration. type Manager struct { ConfigDir string ConfigFile string SubscriptionsFile string } // New creates a Manager, resolving the config directory (SF_CONFIG_DIR env // var, falling back to ~/basicfiles/cli/ss) and ensuring it exists. func New() (*Manager, error) { configDir, err := getConfigDir() if err != nil { return nil, err } m := &Manager{ ConfigDir: configDir, ConfigFile: filepath.Join(configDir, "config.yaml"), SubscriptionsFile: filepath.Join(configDir, "subscriptions.yaml"), } if err := os.MkdirAll(m.ConfigDir, 0o755); err != nil { return nil, fmt.Errorf("failed to create config directory: %w", err) } return m, nil } func getConfigDir() (string, error) { if dir := os.Getenv("SF_CONFIG_DIR"); dir != "" { return dir, nil } home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("failed to determine home directory: %w", err) } return filepath.Join(home, "basicfiles", "cli", "ss"), nil } // LoadSubscriptions loads subscriptions from the YAML file, returning an // empty collection if the file doesn't exist or fails to parse. func (m *Manager) LoadSubscriptions() *model.SubscriptionsData { data := model.NewSubscriptionsData() raw, err := os.ReadFile(m.SubscriptionsFile) if err != nil { return data } if err := yamlutil.Unmarshal(raw, data); err != nil { fmt.Printf("Warning: Failed to load subscriptions: %v\n", err) return model.NewSubscriptionsData() } if data.Subscriptions == nil { data.Subscriptions = make(map[string]*model.Subscription) } return data } // SaveSubscriptions writes subscriptions to the YAML file. func (m *Manager) SaveSubscriptions(data *model.SubscriptionsData) bool { out, err := yaml.Marshal(data) if err != nil { fmt.Printf("Error: Failed to save subscriptions: %v\n", err) return false } if err := os.WriteFile(m.SubscriptionsFile, out, 0o644); err != nil { fmt.Printf("Error: Failed to save subscriptions: %v\n", err) return false } return true } // LoadConfig loads the raw application config as a generic map, matching the // Python version's dict-based (non-strict) handling of config.yaml. func (m *Manager) LoadConfig() map[string]interface{} { raw, err := os.ReadFile(m.ConfigFile) if err != nil { return map[string]interface{}{} } var data map[string]interface{} if err := yamlutil.Unmarshal(raw, &data); err != nil { fmt.Printf("Warning: Failed to load config: %v\n", err) return map[string]interface{}{} } if data == nil { return map[string]interface{}{} } return data } // SaveConfig writes the application config to the YAML file. func (m *Manager) SaveConfig(config map[string]interface{}) bool { out, err := yaml.Marshal(config) if err != nil { fmt.Printf("Error: Failed to save config: %v\n", err) return false } if err := os.WriteFile(m.ConfigFile, out, 0o644); err != nil { fmt.Printf("Error: Failed to save config: %v\n", err) return false } return true } // Info describes the storage location, mirroring get_storage_info(). type Info struct { ConfigDir string ConfigFile string SubscriptionsFile string Platform string Exists bool } // GetStorageInfo returns information about the storage location. func (m *Manager) GetStorageInfo() Info { _, err := os.Stat(m.ConfigDir) return Info{ ConfigDir: m.ConfigDir, ConfigFile: m.ConfigFile, SubscriptionsFile: m.SubscriptionsFile, Platform: platformName(), Exists: err == nil, } } func platformName() string { switch runtime.GOOS { case "darwin": return "Darwin" case "linux": return "Linux" case "windows": return "Windows" default: return runtime.GOOS } }