package constants import ( "fmt" "os" "path/filepath" "runtime" "sync" ) // ============================================================================ // XDG Base Directory Specification Support (Linux only) // ============================================================================ // XDG environment variable names const ( EnvXDGConfigHome = "XDG_CONFIG_HOME" EnvXDGDataHome = "XDG_DATA_HOME" EnvXDGCacheHome = "XDG_CACHE_HOME" ) // XDG default paths (relative to home directory) const ( XDGConfigHomeDefault = ".config" XDGDataHomeDefault = ".local/share" XDGCacheHomeDefault = ".cache" ) // ============================================================================ // Windows Config Path Migration Support // ============================================================================ // WindowsPathPreference tracks which path the user is using on Windows type WindowsPathPreference int const ( WindowsPathLegacy WindowsPathPreference = iota // Using USER\mpv (legacy) WindowsPathAppData // Using %APPDATA%\mpv (new) ) var ( windowsPathOnce sync.Once windowsPathPreference WindowsPathPreference windowsMPVBaseDir string ) // getWindowsMPVBaseDir determines the MPV config directory on Windows. // It checks for existing installations in this order: // 1. %APPDATA%\mpv - if exists, use it (migrated user) // 2. %USERPROFILE%\mpv - if exists, use it (legacy user) // 3. %APPDATA%\mpv - default for fresh installs func getWindowsMPVBaseDir() string { windowsPathOnce.Do(func() { appData := os.Getenv("APPDATA") homeDir, _ := os.UserHomeDir() appDataPath := filepath.Join(appData, InstallDirName) homePath := filepath.Join(homeDir, InstallDirName) // Check if new AppData path exists if _, err := os.Stat(appDataPath); err == nil { windowsPathPreference = WindowsPathAppData windowsMPVBaseDir = appDataPath return } // Check if legacy home path exists if _, err := os.Stat(homePath); err == nil { windowsPathPreference = WindowsPathLegacy windowsMPVBaseDir = homePath return } // Fresh install - use AppData path windowsPathPreference = WindowsPathAppData windowsMPVBaseDir = appDataPath }) return windowsMPVBaseDir } // GetWindowsPathPreference returns whether the user is using the legacy or AppData path // This is primarily for debugging/logging purposes func GetWindowsPathPreference() WindowsPathPreference { getWindowsMPVBaseDir() // Ensure path detection has run return windowsPathPreference } // GetWindowsMPVBaseDir returns the base MPV directory for Windows // This is the public function for getting the Windows MPV path func GetWindowsMPVBaseDir() string { return getWindowsMPVBaseDir() } // getConfigHome returns XDG_CONFIG_HOME or the default ~/.config // This function only applies XDG on Linux; on macOS it uses the default. func getConfigHome() string { // Only apply XDG on Linux, not on macOS or Windows if runtime.GOOS != OSLinux { homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, XDGConfigHomeDefault) } if xdgConfig := os.Getenv(EnvXDGConfigHome); xdgConfig != "" { return xdgConfig } homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, XDGConfigHomeDefault) } // getDataHome returns XDG_DATA_HOME or the default ~/.local/share // This function only applies XDG on Linux; on macOS it uses the default. func getDataHome() string { // Only apply XDG on Linux, not on macOS or Windows if runtime.GOOS != OSLinux { homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, XDGDataHomeDefault) } if xdgData := os.Getenv(EnvXDGDataHome); xdgData != "" { return xdgData } homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, XDGDataHomeDefault) } // getCacheHome returns XDG_CACHE_HOME or the default ~/.cache // This function only applies XDG on Linux; on macOS it uses the default. func getCacheHome() string { // Only apply XDG on Linux, not on macOS or Windows if runtime.GOOS != OSLinux { homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, XDGCacheHomeDefault) } if xdgCache := os.Getenv(EnvXDGCacheHome); xdgCache != "" { return xdgCache } homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, XDGCacheHomeDefault) } // GetXDGConfigHome returns the XDG_CONFIG_HOME path (public for testing) // On Linux: respects XDG_CONFIG_HOME environment variable, defaults to ~/.config // On macOS/Windows: always uses default ~/.config func GetXDGConfigHome() string { return getConfigHome() } // GetXDGDataHome returns the XDG_DATA_HOME path (public for testing) // On Linux: respects XDG_DATA_HOME environment variable, defaults to ~/.local/share // On macOS/Windows: always uses default ~/.local/share func GetXDGDataHome() string { return getDataHome() } // GetXDGCacheHome returns the XDG_CACHE_HOME path (public for testing) // On Linux: respects XDG_CACHE_HOME environment variable, defaults to ~/.cache // On macOS/Windows: always uses default ~/.cache func GetXDGCacheHome() string { return getCacheHome() } // ============================================================================ // Config Path Handling // ============================================================================ // ConfigPaths provides unified config path handling type ConfigPaths struct { HomeDir string InstallerConfigDir string MPVConfigDir string PortableConfigDir string ConfigBackupsDir string } // GetConfigPaths returns config paths for the current platform // On Linux, respects XDG_CONFIG_HOME environment variable. // On Windows, uses %APPDATA%\mpv for fresh installs, legacy path for existing users. // On macOS, uses traditional hardcoded paths. // Note: The installer config file is stored in the MPV config directory for convenience. func GetConfigPaths(homeDir string) *ConfigPaths { if homeDir == "" { var err error homeDir, err = filepath.Abs(".") if err != nil { homeDir = "." } } cp := &ConfigPaths{ HomeDir: homeDir, } if runtime.GOOS == OSWindows { // Windows: use detected MPV directory (AppData for fresh installs, legacy for existing) mpvBaseDir := getWindowsMPVBaseDir() cp.InstallerConfigDir = filepath.Join(mpvBaseDir, PortableConfigDir) cp.MPVConfigDir = cp.InstallerConfigDir cp.PortableConfigDir = PortableConfigDir cp.ConfigBackupsDir = cp.InstallerConfigDir } else if runtime.GOOS == OSLinux { // Linux: use XDG Base Directory Specification // Installer config goes in the MPV config directory for convenience xdgConfigHome := getConfigHome() cp.InstallerConfigDir = filepath.Join(xdgConfigHome, AppNameMPV) cp.MPVConfigDir = filepath.Join(xdgConfigHome, AppNameMPV) cp.PortableConfigDir = PortableConfigDir cp.ConfigBackupsDir = filepath.Join(cp.MPVConfigDir, ConfigBackupsDir) } else { // macOS and other Unix: use traditional ~/.config/mpv paths // Installer config goes in the MPV config directory for convenience cp.InstallerConfigDir = filepath.Join(homeDir, MpvConfDir) cp.MPVConfigDir = filepath.Join(homeDir, MpvConfDir) cp.PortableConfigDir = PortableConfigDir cp.ConfigBackupsDir = filepath.Join(cp.MPVConfigDir, ConfigBackupsDir) } return cp } // GetMPVConfigPath returns the path to mpv.conf based on platform and base path func GetMPVConfigPath(basePath string) string { if runtime.GOOS == OSWindows { // Windows: portable_config/mpv.conf relative to MPV binary return filepath.Join(basePath, PortableConfigDir, MPVConfigFileName) } // Unix: ~/.config/mpv/mpv.conf return filepath.Join(basePath, MPVConfigFileName) } // GetMPVConfigDir returns the directory containing mpv.conf func GetMPVConfigDir(basePath string) string { if runtime.GOOS == OSWindows { // Windows: portable_config relative to MPV binary return filepath.Join(basePath, PortableConfigDir) } // Unix: ~/.config/mpv return basePath } // GetMPVConfigBackupDir returns the directory for MPV config backups func GetMPVConfigBackupDir(mpvConfigDir string) string { if runtime.GOOS == OSWindows { // Windows: backups in portable_config return mpvConfigDir } // Unix: ~/.config/mpv/conf_backups return filepath.Join(mpvConfigDir, ConfigBackupsDir) } // GetPortableConfigPath returns the portable config directory path func GetPortableConfigPath(installDir string) string { return filepath.Join(installDir, PortableConfigDir) } // GetInstallerConfigPath returns the installer's config directory (same as MPV config directory) // On Linux: uses $XDG_CONFIG_HOME/mpv // On macOS: uses ~/.config/mpv // On Windows: uses detected MPV path (%APPDATA%\mpv for fresh installs, legacy path for existing) func GetInstallerConfigPath(homeDir string) string { if runtime.GOOS == OSWindows { return filepath.Join(getWindowsMPVBaseDir(), PortableConfigDir) } if runtime.GOOS == OSLinux { xdgConfigHome := getConfigHome() return filepath.Join(xdgConfigHome, AppNameMPV) } // macOS and other Unix return filepath.Join(homeDir, MpvConfDir) } // GetInstallerConfigFilePath returns the path to mpv-manager.json // On Linux: uses $XDG_CONFIG_HOME/mpv/mpv-manager.json // On macOS: uses ~/.config/mpv/mpv-manager.json // On Windows: uses ~/mpv/portable_config/mpv-manager.json func GetInstallerConfigFilePath(homeDir string) string { return filepath.Join(GetInstallerConfigPath(homeDir), ConfigFileName) } // FormatBackupFileName creates a backup filename with timestamp func FormatBackupFileName(timestamp string) string { return timestamp + BackupFilePrefix } // FormatFullBackupFileName creates a backup filename with full timestamp func FormatFullBackupFileName(timestamp string) string { return timestamp + BackupDirFilePrefix } // GetTimestampedBackupPath returns the full path to a timestamped backup func GetTimestampedBackupPath(backupDir, filename string) string { return filepath.Join(backupDir, filename) } // GetHomeDir returns the user's home directory with enhanced error handling. // Returns an error if the home directory cannot be determined. func GetHomeDir() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("failed to get home directory: %w", err) } return homeDir, nil } // GetMPVConfigPathWithHome returns the full path to mpv.conf with automatic home directory detection. // For Windows: Uses detected MPV path (%APPDATA%\mpv for fresh installs, legacy path for existing) // For Linux: $XDG_CONFIG_HOME/mpv/mpv.conf (defaults to ~/.config/mpv/mpv.conf) // For macOS: ~/.config/mpv/mpv.conf // Returns an error if the home directory cannot be determined. func GetMPVConfigPathWithHome() (string, error) { homeDir, err := GetHomeDir() if err != nil { return "", err } if runtime.GOOS == OSWindows { return filepath.Join(getWindowsMPVBaseDir(), PortableConfigDir, MPVConfigFileName), nil } if runtime.GOOS == OSLinux { // Linux: use XDG_CONFIG_HOME xdgConfigHome := getConfigHome() return filepath.Join(xdgConfigHome, AppNameMPV, MPVConfigFileName), nil } // macOS and other Unix: use traditional ~/.config/mpv/mpv.conf return filepath.Join(homeDir, MpvConfDir, MPVConfigFileName), nil } // GetMPVConfigBackupDirWithHome returns the directory for MPV config backups with automatic home directory detection. // For Windows: Uses detected MPV path (%APPDATA%\mpv for fresh installs, legacy path for existing) // For Linux: $XDG_CONFIG_HOME/mpv/conf_backups (defaults to ~/.config/mpv/conf_backups) // For macOS: ~/.config/mpv/conf_backups // Returns an error if the home directory cannot be determined. func GetMPVConfigBackupDirWithHome() (string, error) { homeDir, err := GetHomeDir() if err != nil { return "", err } if runtime.GOOS == OSWindows { return filepath.Join(getWindowsMPVBaseDir(), PortableConfigDir, ConfigBackupsDir), nil } if runtime.GOOS == OSLinux { // Linux: use XDG_CONFIG_HOME xdgConfigHome := getConfigHome() return filepath.Join(xdgConfigHome, AppNameMPV, ConfigBackupsDir), nil } // macOS and other Unix: use traditional ~/.config/mpv/conf_backups return filepath.Join(homeDir, MpvConfDir, ConfigBackupsDir), nil } // GetInstallerConfigFilePathWithHome returns the path to mpv-manager.json with automatic home directory detection. // On Linux: uses $XDG_CONFIG_HOME/mpv/mpv-manager.json (defaults to ~/.config/mpv/mpv-manager.json) // On macOS: uses ~/.config/mpv/mpv-manager.json // On Windows: uses ~/mpv/portable_config/mpv-manager.json // Returns an error if the home directory cannot be determined. func GetInstallerConfigFilePathWithHome() (string, error) { homeDir, err := GetHomeDir() if err != nil { return "", err } return GetInstallerConfigFilePath(homeDir), nil }