package handlers import ( "encoding/json" "fmt" "os" "path/filepath" "FanslySync/structs" "FanslySync/utils" "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) (ConfigManager, error) { // Create our logger fileLogger, loggerCreateErr := utils.NewLogger("ConfigManager") if loggerCreateErr != nil { // Log the error and return nil return nil, loggerCreateErr } return &FileConfigManager{ path: path, log: fileLogger, }, nil } // 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("Checking for old config file") dir, err := os.UserConfigDir() if err != nil { mgr.log.Error(fmt.Sprintf("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("No old config at %s", oldConfigPath)) return false, nil } else if err != nil { mgr.log.Error(fmt.Sprintf("Error checking old config: %v", err)) return false, err } mgr.log.Info(fmt.Sprintf("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("Migrating old config file") dir, err := os.UserConfigDir() if err != nil { mgr.log.Error(fmt.Sprintf("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("No old config to migrate") return nil } else if err != nil { mgr.log.Error(fmt.Sprintf("Error checking old config: %v", err)) return err } data, err := os.ReadFile(oldConfigPath) if err != nil { mgr.log.Error(fmt.Sprintf("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("Error unmarshaling old config: %v", err)) return err } newCfg := structs.NewConfigFromOld(&oldCfg) if err := mgr.SaveConfig(newCfg); err != nil { mgr.log.Error(fmt.Sprintf("Error saving new config: %v", err)) return err } if err := os.Remove(oldConfigPath); err != nil { mgr.log.Error(fmt.Sprintf("Error removing old config: %v", err)) return fmt.Errorf("could not remove old config file: %w", err) } mgr.log.Info("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("GetConfig(forceReload=%v)", forceReload)) if mgr.config != nil && !forceReload { mgr.log.Debug("Returning cached config") return mgr.config, nil } data, err := os.ReadFile(mgr.path) if err != nil { mgr.log.Error(fmt.Sprintf("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("Error unmarshaling config: %v", err)) return nil, err } mgr.config = &cfg mgr.log.Info("Config loaded from disk. Cache updated.") mgr.log.Debug(fmt.Sprintf("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("Existing config loaded") return cfg, nil } if os.IsNotExist(err) { mgr.log.Warning("Config missing; creating default") defaultCfg := structs.NewConfig() if saveErr := mgr.SaveConfig(defaultCfg); saveErr != nil { mgr.log.Error(fmt.Sprintf("Error saving default config: %v", saveErr)) return nil, saveErr } mgr.log.Info("Default config created and saved") return defaultCfg, nil } mgr.log.Error(fmt.Sprintf("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("Saving config to %s", mgr.path)) dir := filepath.Dir(mgr.path) if err := os.MkdirAll(dir, 0o755); err != nil { mgr.log.Error(fmt.Sprintf("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("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("Error writing config file: %v", err)) return fmt.Errorf("could not write config file: %w", err) } mgr.config = cfg mgr.log.Info("Config saved and cache updated") return nil }