Some checks failed
ci/woodpecker/pr/check Pipeline 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
203 lines
6.1 KiB
Rust
203 lines
6.1 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)
|
|
}
|