use crate::Result; use anyhow::Context; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, fs::{self, OpenOptions}, io::Write, path::{Path, PathBuf}, }; const DEFAULT_PROFILE_NAME: &str = "default"; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigProfile { #[serde(default)] pub username: Option, #[serde(default = "ConfigProfile::default_base_url")] pub base_url: String, #[serde(default = "ConfigProfile::default_jsonapi_path")] pub jsonapi_path: String, #[serde(default)] pub basic_auth_b64: Option, #[serde(default)] pub download_root: Option, #[serde(default = "ConfigProfile::default_max_concurrent_downloads")] pub max_concurrent_downloads: usize, } impl Default for ConfigProfile { fn default() -> Self { Self { username: None, base_url: Self::default_base_url(), jsonapi_path: Self::default_jsonapi_path(), basic_auth_b64: None, download_root: None, max_concurrent_downloads: Self::default_max_concurrent_downloads(), } } } impl ConfigProfile { fn default_base_url() -> String { "https://studip.uni-trier.de".to_string() } fn default_jsonapi_path() -> String { "/jsonapi.php/v1".to_string() } fn default_max_concurrent_downloads() -> usize { 3 } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigFile { #[serde(default = "default_profile_name")] pub default_profile: String, #[serde(default)] pub profiles: HashMap, } impl Default for ConfigFile { fn default() -> Self { let mut profiles = HashMap::new(); profiles.insert(DEFAULT_PROFILE_NAME.to_string(), ConfigProfile::default()); Self { default_profile: DEFAULT_PROFILE_NAME.to_string(), profiles, } } } fn default_profile_name() -> String { DEFAULT_PROFILE_NAME.to_string() } impl ConfigFile { pub fn load_or_default(path: &Path) -> Result { match fs::read_to_string(path) { Ok(contents) => { let file: Self = toml::from_str(&contents).context("Failed to parse config.toml")?; Ok(file) } Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()), Err(err) => Err(err.into()), } } pub fn save(&self, path: &Path) -> Result<()> { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } let contents = toml::to_string_pretty(self)?; let mut file = OpenOptions::new() .create(true) .write(true) .truncate(true) .open(path)?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = file.metadata()?.permissions(); perms.set_mode(0o600); file.set_permissions(perms)?; } file.write_all(contents.as_bytes())?; Ok(()) } }