feat: migrate to golang
This commit is contained in:
156
internal/storage/storage.go
Normal file
156
internal/storage/storage.go
Normal file
@ -0,0 +1,156 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user