pub mod schema;
pub mod status;
use std::path::{Path, PathBuf};
use crate::error::{Error, Result};
use schema::Config;
pub struct ConfigPaths {
pub config_dir: PathBuf,
pub config_file: PathBuf,
pub cache_dir: PathBuf,
}
impl ConfigPaths {
pub fn resolve() -> Result<Self> {
let home = dirs::home_dir()
.or_else(|| std::env::var("HOME").ok().map(PathBuf::from))
.ok_or(Error::HomeDirNotFound)?;
let config_dir = dirs::config_dir()
.unwrap_or_else(|| home.join(".config"))
.join("services");
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| home.join(".cache"))
.join("services");
Ok(Self {
config_file: config_dir.join("preferences.toml"),
cache_dir,
config_dir,
})
}
pub fn ensure_cache_dir(&self) -> Result<()> {
ensure_dir(&self.cache_dir)
}
pub fn ensure_dirs(&self) -> Result<()> {
ensure_dir(&self.config_dir)?;
ensure_dir(&self.cache_dir)?;
Ok(())
}
}
fn ensure_dir(path: &Path) -> Result<()> {
std::fs::create_dir_all(path).map_err(|source| Error::DirCreate {
path: path.to_path_buf(),
source,
})
}
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn load_config(path: &Path) -> Result<Config> {
if !path.exists() {
return Err(Error::ConfigNotFound(path.to_path_buf()));
}
let contents = std::fs::read_to_string(path).map_err(|source| Error::FileRead {
path: path.to_path_buf(),
source,
})?;
let config: Config = toml::from_str(&contents).map_err(|source| Error::TomlParse {
path: path.to_path_buf(),
source,
})?;
if let Err(msg) = config.validate() {
return Err(Error::ConfigValidation(msg));
}
check_version(&config);
Ok(config)
}
fn check_version(config: &Config) {
let config_version = match &config.version {
Some(v) => v,
None => return, };
let binary = parse_major_minor(VERSION);
let config_v = parse_major_minor(config_version);
if let (Some((b_major, b_minor)), Some((c_major, c_minor))) = (binary, config_v)
&& (c_major, c_minor) > (b_major, b_minor)
{
eprintln!(
"Warning: config was written by ryra {config_version}, \
but this is ryra {VERSION} — consider upgrading"
);
}
}
fn parse_major_minor(version: &str) -> Option<(u32, u32)> {
let mut parts = version.split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next()?.parse().ok()?;
Some((major, minor))
}
pub fn load_or_default(path: &Path) -> Result<Config> {
if !path.exists() {
return Ok(Config::default());
}
load_config(path)
}
pub fn save_config(path: &Path, config: &Config) -> Result<()> {
let mut config = config.clone();
config.version = Some(VERSION.to_string());
let contents = toml::to_string_pretty(&config)?;
crate::system::atomic_write::atomic_write(path, contents.as_bytes(), 0o600)?;
Ok(())
}