Menu
Public repository / master
AkurAI-Drive / client/src/config.rs
Latest change feb7ac86b5e60d55b429eacb7146cf0dc32c6628 - Add native Linux sync client (akurai-drive-sync) by olibuijr
//! Persistent configuration under `~/.config/akurai-drive/` (XDG-aware).
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const DEFAULT_API: &str = "https://akurai-drive.olibuijr.com";
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Config {
/// Backend base URL (no trailing slash).
#[serde(default)]
pub api: Option<String>,
/// Device token from the `login` flow.
#[serde(default)]
pub token: Option<String>,
/// Absolute path of the local sync folder.
#[serde(default)]
pub folder: Option<String>,
/// Bucket name to sync against.
#[serde(default)]
pub bucket: Option<String>,
}
/// `~/.config/akurai-drive`, honoring `$XDG_CONFIG_HOME`, falling back to
/// `$HOME/.config`.
pub fn config_dir() -> Result<PathBuf> {
let base = dirs::config_dir()
.or_else(|| dirs::home_dir().map(|h| h.join(".config")))
.context("cannot determine a config directory (no HOME / XDG_CONFIG_HOME)")?;
Ok(base.join("akurai-drive"))
}
pub fn config_path() -> Result<PathBuf> {
Ok(config_dir()?.join("config.toml"))
}
pub fn state_db_path() -> Result<PathBuf> {
Ok(config_dir()?.join("state.sqlite"))
}
impl Config {
/// Load config, returning a default (empty) config if none exists yet.
pub fn load() -> Result<Self> {
let path = config_path()?;
if !path.exists() {
return Ok(Self::default());
}
let text = std::fs::read_to_string(&path)
.with_context(|| format!("reading {}", path.display()))?;
let cfg: Config =
toml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
Ok(cfg)
}
/// Write config back to disk at mode 0600 (owner-only — it holds a token).
pub fn save(&self) -> Result<()> {
let dir = config_dir()?;
std::fs::create_dir_all(&dir).with_context(|| format!("creating {}", dir.display()))?;
let path = config_path()?;
let text = toml::to_string_pretty(self)?;
std::fs::write(&path, text).with_context(|| format!("writing {}", path.display()))?;
set_owner_only(&path)?;
Ok(())
}
/// Resolve the effective API base: `$AKURAI_DRIVE_API` overrides config,
/// which overrides the built-in default.
pub fn resolved_api(&self) -> String {
if let Ok(env) = std::env::var("AKURAI_DRIVE_API") {
if !env.trim().is_empty() {
return env.trim().trim_end_matches('/').to_string();
}
}
self.api
.clone()
.unwrap_or_else(|| DEFAULT_API.to_string())
.trim_end_matches('/')
.to_string()
}
pub fn is_logged_in(&self) -> bool {
self.token
.as_deref()
.map(|t| !t.is_empty())
.unwrap_or(false)
}
}
/// chmod 0600 on Unix; no-op elsewhere.
fn set_owner_only(path: &Path) -> Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path)?.permissions();
perms.set_mode(0o600);
std::fs::set_permissions(path, perms)?;
}
#[cfg(not(unix))]
{
let _ = path;
}
Ok(())
}