refactor: complete Phase 13 deep architecture refactoring (etaps 3-7)
Split monolithic files into modular architecture: - ui/messages.rs (893→365 lines): extract modals/, compose_bar.rs - tdlib/messages.rs (836→3 files): split into messages/mod, convert, operations - config/mod.rs (642→3 files): extract validation.rs, loader.rs - Code duplication cleanup: shared components, ~220 lines removed - Documentation: PROJECT_STRUCTURE.md rewrite, 16 files got //! docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
197
src/config/loader.rs
Normal file
197
src/config/loader.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! 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<PathBuf> {
|
||||
dirs::config_dir().map(|mut path| {
|
||||
path.push("tele-tui");
|
||||
path.push("config.toml");
|
||||
path
|
||||
})
|
||||
}
|
||||
|
||||
/// Путь к директории конфигурации
|
||||
pub fn config_dir() -> Option<PathBuf> {
|
||||
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::<Config>(&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<PathBuf> {
|
||||
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<i32> = None;
|
||||
let mut api_hash: Option<String> = 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::<i32>().ok()?;
|
||||
|
||||
Some((api_id, api_hash))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user