Files
telegram-tui/src/accounts/manager.rs
Mikhail Kilin 8bd08318bb
Some checks failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
fixes
2026-02-14 17:57:37 +03:00

210 lines
6.3 KiB
Rust

//! 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<PathBuf> {
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::<AccountsConfig>(&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<std::path::PathBuf, String> {
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<PathBuf, String> {
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)
}