//! Account manager: loading, saving, migration, and resolution. //! //! Handles `accounts.toml` lifecycle and legacy `./tdlib_data/` migration //! to XDG data directory. use std::fs; use std::path::PathBuf; use super::profile::{account_db_path, validate_account_name, AccountsConfig}; /// Returns the path to `accounts.toml` in the config directory. /// /// `~/.config/tele-tui/accounts.toml` pub fn accounts_config_path() -> Option { dirs::config_dir().map(|mut path| { path.push("tele-tui"); path.push("accounts.toml"); path }) } /// Loads `accounts.toml` or creates it with default values. /// /// On first run, also attempts to migrate legacy `./tdlib_data/` directory /// to the XDG data location. pub fn load_or_create() -> AccountsConfig { let config_path = match accounts_config_path() { Some(path) => path, None => { tracing::warn!("Could not determine config directory for accounts, using defaults"); return AccountsConfig::default_single(); } }; if config_path.exists() { // Load existing config match fs::read_to_string(&config_path) { Ok(content) => match toml::from_str::(&content) { Ok(config) => return config, Err(e) => { tracing::warn!("Could not parse accounts.toml: {}", e); return AccountsConfig::default_single(); } }, Err(e) => { tracing::warn!("Could not read accounts.toml: {}", e); return AccountsConfig::default_single(); } } } // First run: migrate legacy data if present, then create default config migrate_legacy(); let config = AccountsConfig::default_single(); if let Err(e) = save(&config) { tracing::warn!("Could not save initial accounts.toml: {}", e); } config } /// Saves `AccountsConfig` to `accounts.toml`. pub fn save(config: &AccountsConfig) -> Result<(), String> { let config_path = accounts_config_path().ok_or_else(|| "Could not determine config directory".to_string())?; // Ensure parent directory exists if let Some(parent) = config_path.parent() { fs::create_dir_all(parent) .map_err(|e| format!("Could not create config directory: {}", e))?; } let toml_string = toml::to_string_pretty(config) .map_err(|e| format!("Could not serialize accounts config: {}", e))?; fs::write(&config_path, toml_string) .map_err(|e| format!("Could not write accounts.toml: {}", e))?; Ok(()) } /// Migrates legacy `./tdlib_data/` from CWD to XDG data dir. /// /// If `./tdlib_data/` exists in the current working directory, moves it to /// `~/.local/share/tele-tui/accounts/default/tdlib_data/`. fn migrate_legacy() { let legacy_path = PathBuf::from("tdlib_data"); if !legacy_path.exists() || !legacy_path.is_dir() { return; } let target = account_db_path("default"); // Don't overwrite if target already exists if target.exists() { tracing::info!( "Legacy ./tdlib_data/ found but target already exists at {}, skipping migration", target.display() ); return; } // Create parent directories if let Some(parent) = target.parent() { if let Err(e) = fs::create_dir_all(parent) { tracing::error!("Could not create target directory for migration: {}", e); return; } } // Move (rename) the directory match fs::rename(&legacy_path, &target) { Ok(()) => { tracing::info!("Migrated ./tdlib_data/ -> {}", target.display()); } Err(e) => { tracing::error!("Could not migrate ./tdlib_data/ to {}: {}", target.display(), e); } } } /// Resolves which account to use from CLI arg or default. /// /// # Arguments /// /// * `config` - The loaded accounts configuration /// * `account_arg` - Optional account name from `--account` CLI flag /// /// # Returns /// /// The resolved account name and its db_path. /// /// # Errors /// /// Returns an error if the specified account is not found or the name is invalid. pub fn resolve_account( config: &AccountsConfig, account_arg: Option<&str>, ) -> Result<(String, PathBuf), String> { let account_name = account_arg.unwrap_or(&config.default_account); // Validate name validate_account_name(account_name)?; // Find account in config let _account = config.find_account(account_name).ok_or_else(|| { let available: Vec<&str> = config.accounts.iter().map(|a| a.name.as_str()).collect(); format!( "Account '{}' not found. Available accounts: {}", account_name, available.join(", ") ) })?; let db_path = account_db_path(account_name); Ok((account_name.to_string(), db_path)) } /// Adds a new account to `accounts.toml` and creates its data directory. /// /// Validates the name, checks for duplicates, adds the profile to config, /// saves the config, and creates the data directory. /// /// # Returns /// /// The db_path for the new account. /// /// # Errors /// /// Returns an error if the name is invalid, already exists, or I/O fails. pub fn add_account(name: &str, display_name: &str) -> Result { validate_account_name(name)?; let mut config = load_or_create(); // Check for duplicate if config.find_account(name).is_some() { return Err(format!("Account '{}' already exists", name)); } // Add new profile config.accounts.push(super::profile::AccountProfile { name: name.to_string(), display_name: display_name.to_string(), }); // Save config save(&config)?; // Create data directory ensure_account_dir(name) } /// Ensures the account data directory exists. /// /// Creates `~/.local/share/tele-tui/accounts/{name}/tdlib_data/` if needed. pub fn ensure_account_dir(account_name: &str) -> Result { let db_path = account_db_path(account_name); fs::create_dir_all(&db_path) .map_err(|e| format!("Could not create account directory: {}", e))?; Ok(db_path) }