//! Config file loading, saving, and credentials management. //! //! Searches for config at `~/.config/tele-tui/config.toml`. //! Credentials loaded from file or environment variables. use std::fs; use std::path::PathBuf; use super::Config; impl Config { /// Возвращает путь к конфигурационному файлу. /// /// # Returns /// /// `Some(PathBuf)` - `~/.config/tele-tui/config.toml` /// `None` - Не удалось определить директорию конфигурации pub fn config_path() -> Option { dirs::config_dir().map(|mut path| { path.push("tele-tui"); path.push("config.toml"); path }) } /// Путь к директории конфигурации pub fn config_dir() -> Option { dirs::config_dir().map(|mut path| { path.push("tele-tui"); path }) } /// Загружает конфигурацию из файла. /// /// Ищет конфиг в `~/.config/tele-tui/config.toml`. /// Если файл не существует, создаёт дефолтный. /// Если файл невалиден, возвращает дефолтные значения. /// /// # Returns /// /// Всегда возвращает валидную конфигурацию. pub fn load() -> Self { let config_path = match Self::config_path() { Some(path) => path, None => { tracing::warn!("Could not determine config directory, using defaults"); return Self::default(); } }; if !config_path.exists() { // Создаём дефолтный конфиг при первом запуске let default_config = Self::default(); if let Err(e) = default_config.save() { tracing::warn!("Could not create default config: {}", e); } return default_config; } match fs::read_to_string(&config_path) { Ok(content) => match toml::from_str::(&content) { Ok(config) => { // Валидируем загруженный конфиг if let Err(e) = config.validate() { tracing::error!("Config validation error: {}", e); tracing::warn!("Using default configuration instead"); Self::default() } else { config } } Err(e) => { tracing::warn!("Could not parse config file: {}", e); Self::default() } }, Err(e) => { tracing::warn!("Could not read config file: {}", e); Self::default() } } } /// Сохраняет конфигурацию в файл. /// /// Создаёт директорию `~/.config/tele-tui/` если её нет. /// /// # Returns /// /// * `Ok(())` - Конфиг сохранен /// * `Err(String)` - Ошибка сохранения pub fn save(&self) -> Result<(), String> { let config_dir = Self::config_dir().ok_or_else(|| "Could not determine config directory".to_string())?; // Создаём директорию если её нет fs::create_dir_all(&config_dir) .map_err(|e| format!("Could not create config directory: {}", e))?; let config_path = config_dir.join("config.toml"); let toml_string = toml::to_string_pretty(self) .map_err(|e| format!("Could not serialize config: {}", e))?; fs::write(&config_path, toml_string) .map_err(|e| format!("Could not write config file: {}", e))?; Ok(()) } /// Путь к файлу credentials pub fn credentials_path() -> Option { Self::config_dir().map(|dir| dir.join("credentials")) } /// Загружает API_ID и API_HASH для Telegram. /// /// Ищет credentials в следующем порядке: /// 1. `~/.config/tele-tui/credentials` файл /// 2. Переменные окружения `API_ID` и `API_HASH` /// /// # Returns /// /// * `Ok((api_id, api_hash))` - Учетные данные найдены /// * `Err(String)` - Ошибка с инструкциями по настройке pub fn load_credentials() -> Result<(i32, String), String> { // 1. Пробуем загрузить из ~/.config/tele-tui/credentials if let Some(credentials) = Self::load_credentials_from_file() { return Ok(credentials); } // 2. Пробуем загрузить из переменных окружения (.env) if let Some(credentials) = Self::load_credentials_from_env() { return Ok(credentials); } // 3. Не нашли credentials - возвращаем инструкции let credentials_path = Self::credentials_path() .map(|p| p.display().to_string()) .unwrap_or_else(|| "~/.config/tele-tui/credentials".to_string()); Err(format!( "Telegram API credentials not found!\n\n\ Please create a file at:\n {}\n\n\ With the following content:\n\ API_ID=your_api_id\n\ API_HASH=your_api_hash\n\n\ You can get API credentials at: https://my.telegram.org/apps\n\n\ Alternatively, you can create a .env file in the current directory.", credentials_path )) } /// Загружает credentials из файла ~/.config/tele-tui/credentials fn load_credentials_from_file() -> Option<(i32, String)> { let cred_path = Self::credentials_path()?; if !cred_path.exists() { return None; } let content = fs::read_to_string(&cred_path).ok()?; let mut api_id: Option = None; let mut api_hash: Option = None; for line in content.lines() { let line = line.trim(); if line.is_empty() || line.starts_with('#') { continue; } let (key, value) = line.split_once('=')?; let key = key.trim(); let value = value.trim(); match key { "API_ID" => api_id = value.parse().ok(), "API_HASH" => api_hash = Some(value.to_string()), _ => {} } } Some((api_id?, api_hash?)) } /// Загружает credentials из переменных окружения (.env) fn load_credentials_from_env() -> Option<(i32, String)> { use std::env; let api_id_str = env::var("API_ID").ok()?; let api_hash = env::var("API_HASH").ok()?; let api_id = api_id_str.parse::().ok()?; Some((api_id, api_hash)) } }