157 lines
4.0 KiB
Go
157 lines
4.0 KiB
Go
// 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
|
|
}
|
|
}
|