sync/handlers/config.go

206 lines
7.3 KiB
Go

package handlers
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"FanslySync/structs"
"github.com/wailsapp/wails/v2/pkg/logger"
)
// ConfigManager defines methods for accessing and persisting application configuration.
type ConfigManager interface {
// GetConfig returns the current configuration. If forceReload is true,
// it re-reads the file from disk even if a cached copy exists.
GetConfig(forceReload bool) (*structs.Config, error)
// LoadConfigOrCreate loads the config or, if the file doesn't exist,
// creates a default config, saves it, and returns it.
LoadConfigOrCreate() (*structs.Config, error)
// SaveConfig writes the given config to disk and updates the in-memory cache.
SaveConfig(config *structs.Config) error
// GetConfigPath returns the filesystem path where the config is stored.
GetConfigPath() string
// ShouldMigrateOldAppConfig checks if the old app config file exists
// and returns true if it should be migrated.
ShouldMigrateOldAppConfig() (bool, error)
// MigrateOldAppConfig migrates the old app config file to the new format.
MigrateOldAppConfig() error
}
// FileConfigManager implements ConfigManager using a JSON file on disk
// with in-memory caching and custom logging.
type FileConfigManager struct {
path string
config *structs.Config
log logger.Logger
}
func NewFileConfigManager(path string, log logger.Logger) ConfigManager {
return &FileConfigManager{
path: path,
log: log,
}
}
// GetConfigPath returns the path to the config file.
func (mgr *FileConfigManager) GetConfigPath() string {
return mgr.path
}
func GetConfigPathForRuntime() (string, error) {
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "FanslySync", "appconfig.json"), nil
}
// ShouldMigrateOldAppConfig checks for an existing legacy config.json and logs the result.
func (mgr *FileConfigManager) ShouldMigrateOldAppConfig() (bool, error) {
mgr.log.Info("[ConfigManager::ShouldMigrateOldAppConfig] Checking for old config file")
dir, err := os.UserConfigDir()
if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] Error getting user config dir: %v", err))
return false, err
}
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
mgr.log.Info(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] No old config at %s", oldConfigPath))
return false, nil
} else if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] Error checking old config: %v", err))
return false, err
}
mgr.log.Info(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] Old config exists at %s", oldConfigPath))
return true, nil
}
// MigrateOldAppConfig reads the legacy config.json, converts it, saves the new format,
// and removes the old file, logging each step.
func (mgr *FileConfigManager) MigrateOldAppConfig() error {
mgr.log.Info("[ConfigManager::MigrateOldAppConfig] Migrating old config file")
dir, err := os.UserConfigDir()
if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error getting user config dir: %v", err))
return err
}
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
mgr.log.Info("[ConfigManager::MigrateOldAppConfig] No old config to migrate")
return nil
} else if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error checking old config: %v", err))
return err
}
data, err := os.ReadFile(oldConfigPath)
if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error reading old config: %v", err))
return err
}
var oldCfg structs.OldConfig
if err := json.Unmarshal(data, &oldCfg); err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error unmarshaling old config: %v", err))
return err
}
newCfg := structs.NewConfigFromOld(&oldCfg)
if err := mgr.SaveConfig(newCfg); err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error saving new config: %v", err))
return err
}
if err := os.Remove(oldConfigPath); err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error removing old config: %v", err))
return fmt.Errorf("could not remove old config file: %w", err)
}
mgr.log.Info("[ConfigManager::MigrateOldAppConfig] Migration complete; old config removed")
return nil
}
// GetConfig loads the config from disk if forceReload is true or no cache exists.
// It logs each step and errors encountered.
func (mgr *FileConfigManager) GetConfig(forceReload bool) (*structs.Config, error) {
mgr.log.Debug(fmt.Sprintf("[ConfigManager] GetConfig(forceReload=%v)", forceReload))
if mgr.config != nil && !forceReload {
mgr.log.Debug("[ConfigManager::GetConfig] Returning cached config")
return mgr.config, nil
}
data, err := os.ReadFile(mgr.path)
if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::GetConfig] Error reading config file: %v", err))
return nil, err
}
var cfg structs.Config
if err := json.Unmarshal(data, &cfg); err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::GetConfig] Error unmarshaling config: %v", err))
return nil, err
}
mgr.config = &cfg
mgr.log.Info("[ConfigManager::GetConfig] Config loaded from disk. Cache updated.")
mgr.log.Debug(fmt.Sprintf("[ConfigManager::GetConfig] Config: %+v", cfg))
return mgr.config, nil
}
// LoadConfigOrCreate loads an existing config or creates/saves a default if none exists.
func (mgr *FileConfigManager) LoadConfigOrCreate() (*structs.Config, error) {
cfg, err := mgr.GetConfig(false)
if err == nil {
mgr.log.Info("[ConfigManager::LoadConfigOrCreate] Existing config loaded")
return cfg, nil
}
if os.IsNotExist(err) {
mgr.log.Warning("[ConfigManager::LoadConfigOrCreate] Config missing; creating default")
defaultCfg := structs.NewConfig()
if saveErr := mgr.SaveConfig(defaultCfg); saveErr != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::LoadConfigOrCreate] Error saving default config: %v", saveErr))
return nil, saveErr
}
mgr.log.Info("[ConfigManager::LoadConfigOrCreate] Default config created and saved")
return defaultCfg, nil
}
mgr.log.Error(fmt.Sprintf("[ConfigManager::LoadConfigOrCreate] Error loading config: %v", err))
return nil, err
}
// SaveConfig writes the config to disk, updates cache, and logs the process.
func (mgr *FileConfigManager) SaveConfig(cfg *structs.Config) error {
mgr.log.Info(fmt.Sprintf("[ConfigManager::SaveConfig] Saving config to %s", mgr.path))
dir := filepath.Dir(mgr.path)
if err := os.MkdirAll(dir, 0o755); err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::SaveConfig] Error creating config directory: %v", err))
return fmt.Errorf("could not create config directory: %w", err)
}
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::SaveConfig] Error marshaling config: %v", err))
return fmt.Errorf("could not marshal config: %w", err)
}
if err := os.WriteFile(mgr.path, data, 0o644); err != nil {
mgr.log.Error(fmt.Sprintf("[ConfigManager::SaveConfig] Error writing config file: %v", err))
return fmt.Errorf("could not write config file: %w", err)
}
mgr.config = cfg
mgr.log.Info("[ConfigManager::SaveConfig] Config saved and cache updated")
return nil
}