e6710eb19f
- Created sync client for communicating with API server - Implemented conflict resolution strategies (last-write-wins, server-wins, client-wins) - Added offline change queue for queuing changes when server is unreachable - Implemented merge logic for local and remote task lists - Added conflict logging to sync_conflicts.log - Created bidirectional sync with pull/push operations - Extended Config struct with sync settings (URL, API key, client ID, strategy, offline queue) - Added SyncResult display with user-friendly output - Sync handlers already implemented in Phase 1 (GetChanges, PushChanges)
169 lines
4.3 KiB
Go
169 lines
4.3 KiB
Go
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
type Config struct {
|
|
DefaultFilter string `mapstructure:"default_filter"`
|
|
DefaultSort string `mapstructure:"default_sort"`
|
|
ColorOutput bool `mapstructure:"color_output"`
|
|
WeekStartDay string `mapstructure:"week_start_day"`
|
|
DefaultDueTime string `mapstructure:"default_due_time"`
|
|
|
|
// Sync settings
|
|
SyncEnabled bool `mapstructure:"sync_enabled"`
|
|
SyncURL string `mapstructure:"sync_url"`
|
|
SyncAPIKey string `mapstructure:"sync_api_key"`
|
|
SyncClientID string `mapstructure:"sync_client_id"`
|
|
SyncStrategy string `mapstructure:"sync_strategy"`
|
|
SyncQueueOffline bool `mapstructure:"sync_queue_offline"`
|
|
}
|
|
|
|
var globalConfig *Config
|
|
|
|
// GetConfigDir returns the configuration directory path
|
|
func GetConfigDir() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
}
|
|
return filepath.Join(home, ".config", "jade"), nil
|
|
}
|
|
|
|
// GetDBPath returns the path to the SQLite database
|
|
func GetDBPath() (string, error) {
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(configDir, "opal.db"), nil
|
|
}
|
|
|
|
// GetConfigPath returns the path to the config file
|
|
func GetConfigPath() (string, error) {
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(configDir, "opal.yml"), nil
|
|
}
|
|
|
|
// LoadConfig loads the configuration from file or creates default
|
|
func LoadConfig() (*Config, error) {
|
|
if globalConfig != nil {
|
|
return globalConfig, nil
|
|
}
|
|
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ensure config directory exists
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create config directory: %w", err)
|
|
}
|
|
|
|
configPath, err := GetConfigPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := viper.New()
|
|
v.SetConfigFile(configPath)
|
|
v.SetConfigType("yaml")
|
|
|
|
// Set defaults
|
|
v.SetDefault("default_filter", "status:pending")
|
|
v.SetDefault("default_sort", "due,priority")
|
|
v.SetDefault("color_output", true)
|
|
v.SetDefault("week_start_day", "monday")
|
|
v.SetDefault("default_due_time", "")
|
|
|
|
// Sync defaults
|
|
v.SetDefault("sync_enabled", false)
|
|
v.SetDefault("sync_url", "")
|
|
v.SetDefault("sync_api_key", "")
|
|
v.SetDefault("sync_client_id", "")
|
|
v.SetDefault("sync_strategy", "last-write-wins")
|
|
v.SetDefault("sync_queue_offline", true)
|
|
|
|
// Try to read existing config
|
|
err = v.ReadInConfig()
|
|
if err != nil {
|
|
// Config doesn't exist, create it with defaults
|
|
// Write config with defaults (ignoring the error type check for now)
|
|
if err := v.WriteConfigAs(configPath); err != nil {
|
|
return nil, fmt.Errorf("failed to create config file: %w", err)
|
|
}
|
|
// Try reading again
|
|
if err := v.ReadInConfig(); err != nil {
|
|
return nil, fmt.Errorf("failed to read newly created config: %w", err)
|
|
}
|
|
}
|
|
|
|
cfg := &Config{}
|
|
if err := v.Unmarshal(cfg); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
|
|
globalConfig = cfg
|
|
return cfg, nil
|
|
}
|
|
|
|
// SaveConfig saves the configuration to file
|
|
func SaveConfig(cfg *Config) error {
|
|
configPath, err := GetConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v := viper.New()
|
|
v.SetConfigFile(configPath)
|
|
v.SetConfigType("yaml")
|
|
|
|
v.Set("default_filter", cfg.DefaultFilter)
|
|
v.Set("default_sort", cfg.DefaultSort)
|
|
v.Set("color_output", cfg.ColorOutput)
|
|
v.Set("week_start_day", cfg.WeekStartDay)
|
|
v.Set("default_due_time", cfg.DefaultDueTime)
|
|
|
|
// Sync settings
|
|
v.Set("sync_enabled", cfg.SyncEnabled)
|
|
v.Set("sync_url", cfg.SyncURL)
|
|
v.Set("sync_api_key", cfg.SyncAPIKey)
|
|
v.Set("sync_client_id", cfg.SyncClientID)
|
|
v.Set("sync_strategy", cfg.SyncStrategy)
|
|
v.Set("sync_queue_offline", cfg.SyncQueueOffline)
|
|
|
|
return v.WriteConfig()
|
|
}
|
|
|
|
// GetConfig returns the loaded config or loads it if not already loaded
|
|
func GetConfig() (*Config, error) {
|
|
if globalConfig != nil {
|
|
return globalConfig, nil
|
|
}
|
|
return LoadConfig()
|
|
}
|
|
|
|
// GetWeekStart returns the configured week start day
|
|
func (c *Config) GetWeekStart() time.Weekday {
|
|
if strings.ToLower(c.WeekStartDay) == "sunday" {
|
|
return time.Sunday
|
|
}
|
|
return time.Monday // default
|
|
}
|
|
|
|
// GetDefaultDueTime returns the configured default due time (HH:MM format or empty)
|
|
func (c *Config) GetDefaultDueTime() string {
|
|
return c.DefaultDueTime
|
|
}
|