1pub mod schema;
2pub mod status;
3
4use std::path::{Path, PathBuf};
5
6use crate::error::{Error, Result};
7use schema::Config;
8
9pub struct ConfigPaths {
11 pub config_dir: PathBuf,
12 pub config_file: PathBuf,
13 pub cache_dir: PathBuf,
14}
15
16impl ConfigPaths {
17 pub fn resolve() -> Result<Self> {
18 let home = dirs::home_dir()
19 .or_else(|| std::env::var("HOME").ok().map(PathBuf::from))
20 .ok_or(Error::HomeDirNotFound)?;
21 let config_dir = dirs::config_dir()
22 .unwrap_or_else(|| home.join(".config"))
23 .join("services");
24 let cache_dir = dirs::cache_dir()
25 .unwrap_or_else(|| home.join(".cache"))
26 .join("services");
27 Ok(Self {
28 config_file: config_dir.join("preferences.toml"),
29 cache_dir,
30 config_dir,
31 })
32 }
33
34 pub fn ensure_cache_dir(&self) -> Result<()> {
35 ensure_dir(&self.cache_dir)
36 }
37
38 pub fn ensure_dirs(&self) -> Result<()> {
39 ensure_dir(&self.config_dir)?;
40 ensure_dir(&self.cache_dir)?;
41 Ok(())
42 }
43}
44
45fn ensure_dir(path: &Path) -> Result<()> {
46 std::fs::create_dir_all(path).map_err(|source| Error::DirCreate {
47 path: path.to_path_buf(),
48 source,
49 })
50}
51
52pub const VERSION: &str = env!("CARGO_PKG_VERSION");
54
55pub fn load_config(path: &Path) -> Result<Config> {
56 if !path.exists() {
57 return Err(Error::ConfigNotFound(path.to_path_buf()));
58 }
59 let contents = std::fs::read_to_string(path).map_err(|source| Error::FileRead {
60 path: path.to_path_buf(),
61 source,
62 })?;
63 let config: Config = toml::from_str(&contents).map_err(|source| Error::TomlParse {
64 path: path.to_path_buf(),
65 source,
66 })?;
67 if let Err(msg) = config.validate() {
68 return Err(Error::ConfigValidation(msg));
69 }
70 check_version(&config);
71 Ok(config)
72}
73
74fn check_version(config: &Config) {
76 let config_version = match &config.version {
77 Some(v) => v,
78 None => return, };
80 let binary = parse_major_minor(VERSION);
81 let config_v = parse_major_minor(config_version);
82 if let (Some((b_major, b_minor)), Some((c_major, c_minor))) = (binary, config_v)
83 && (c_major, c_minor) > (b_major, b_minor)
84 {
85 eprintln!(
86 "Warning: config was written by ryra {config_version}, \
87 but this is ryra {VERSION} — consider upgrading"
88 );
89 }
90}
91
92fn parse_major_minor(version: &str) -> Option<(u32, u32)> {
93 let mut parts = version.split('.');
94 let major = parts.next()?.parse().ok()?;
95 let minor = parts.next()?.parse().ok()?;
96 Some((major, minor))
97}
98
99pub fn load_or_default(path: &Path) -> Result<Config> {
101 if !path.exists() {
102 return Ok(Config::default());
103 }
104 load_config(path)
105}
106
107pub fn save_config(path: &Path, config: &Config) -> Result<()> {
108 let mut config = config.clone();
109 config.version = Some(VERSION.to_string());
110 let contents = toml::to_string_pretty(&config)?;
111 crate::system::atomic_write::atomic_write(path, contents.as_bytes(), 0o600)?;
115 Ok(())
116}