use crate::{agents::Agent, errors::AtomicResult};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub shared: SharedConfig,
pub client: Option<ClientConfig>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SharedConfig {
pub agent_secret: String,
#[serde(rename = "initialDrive")]
pub initial_drive: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ClientConfig {
pub server_url: String,
}
pub fn default_config_dir_path() -> AtomicResult<PathBuf> {
if let Some(dirs) = directories::UserDirs::new() {
let atomic_config_dir = dirs.home_dir().join(".config/atomic");
return Ok(atomic_config_dir);
}
Ok(PathBuf::from(".config/atomic"))
}
pub fn default_config_file_path() -> AtomicResult<PathBuf> {
let mut default_dir = default_config_dir_path()?;
default_dir.push("config.toml");
Ok(default_dir)
}
pub fn read_config(path: Option<&Path>) -> AtomicResult<Config> {
let default = default_config_file_path()?;
let path = path.unwrap_or(&default);
let config_string = std::fs::read_to_string(path)
.map_err(|e| format!("Error reading config from {:?}. {}", path, e))?;
let config = parse_and_migrate_if_needed(&config_string)
.map_err(|e| format!("Could not parse toml in config file {:?}. {}", path, e))?;
Ok(config)
}
fn write_config(path: &Path, config: Config) -> AtomicResult<String> {
let out =
toml::to_string_pretty(&config).map_err(|e| format!("Error serializing config. {}", e))?;
let prefix = path
.parent()
.ok_or("Could not get parent dir of config file")?;
std::fs::create_dir_all(prefix)
.map_err(|e| format!("Could not create config directory {:?} . {}", prefix, e))?;
std::fs::write(path, out.clone())
.map_err(|e| format!("Error writing config file to {:?}. {}", path, e))?;
Ok(out)
}
impl Config {
pub fn save(&self, path: &Path) -> AtomicResult<()> {
write_config(path, self.clone())?;
Ok(())
}
pub fn to_string(&self) -> AtomicResult<String> {
let out =
toml::to_string_pretty(self).map_err(|e| format!("Error serializing config. {}", e))?;
Ok(out)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct ConfigV0 {
agent: String,
private_key: String,
server: String,
}
fn parse_and_migrate_if_needed(config_str: &str) -> AtomicResult<Config> {
if let Ok(config) = toml::from_str::<Config>(config_str) {
return Ok(config);
}
if let Ok(config) = toml::from_str::<ConfigV0>(config_str) {
return config_v0_to_v1(&config);
}
Err("Could not parse config".into())
}
fn config_v0_to_v1(config_v0: &ConfigV0) -> AtomicResult<Config> {
let ConfigV0 {
agent,
private_key,
server,
} = config_v0;
let new_agent = Agent::from_private_key_and_subject(private_key, agent)?;
let config = Config {
shared: SharedConfig {
agent_secret: new_agent.build_secret()?,
initial_drive: None,
},
client: Some(ClientConfig {
server_url: server.clone(),
}),
};
Ok(config)
}