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 = match std::env::var_os(crate::paths::CONFIG_DIR_ENV) {
24 Some(dir) if !dir.is_empty() => PathBuf::from(dir),
25 _ => dirs::config_dir()
26 .unwrap_or_else(|| home.join(".config"))
27 .join("services"),
28 };
29 let cache_dir = dirs::cache_dir()
30 .unwrap_or_else(|| home.join(".cache"))
31 .join("services");
32 Ok(Self {
33 config_file: config_dir.join("preferences.toml"),
34 cache_dir,
35 config_dir,
36 })
37 }
38
39 pub fn ensure_cache_dir(&self) -> Result<()> {
40 ensure_dir(&self.cache_dir)
41 }
42
43 pub fn ensure_dirs(&self) -> Result<()> {
44 ensure_dir(&self.config_dir)?;
45 ensure_dir(&self.cache_dir)?;
46 Ok(())
47 }
48}
49
50fn ensure_dir(path: &Path) -> Result<()> {
51 std::fs::create_dir_all(path).map_err(|source| Error::DirCreate {
52 path: path.to_path_buf(),
53 source,
54 })
55}
56
57pub const VERSION: &str = env!("CARGO_PKG_VERSION");
59
60pub fn load_config(path: &Path) -> Result<Config> {
61 if !path.exists() {
62 return Err(Error::ConfigNotFound(path.to_path_buf()));
63 }
64 let contents = std::fs::read_to_string(path).map_err(|source| Error::FileRead {
65 path: path.to_path_buf(),
66 source,
67 })?;
68 let config: Config = toml::from_str(&contents).map_err(|source| Error::TomlParse {
69 path: path.to_path_buf(),
70 source,
71 })?;
72 if let Err(msg) = config.validate() {
73 return Err(Error::ConfigValidation(msg));
74 }
75 check_version(&config);
76 Ok(config)
77}
78
79fn check_version(config: &Config) {
81 let config_version = match &config.version {
82 Some(v) => v,
83 None => return, };
85 let binary = parse_major_minor(VERSION);
86 let config_v = parse_major_minor(config_version);
87 if let (Some((b_major, b_minor)), Some((c_major, c_minor))) = (binary, config_v)
88 && (c_major, c_minor) > (b_major, b_minor)
89 {
90 eprintln!(
91 "Warning: config was written by ryra {config_version}, \
92 but this is ryra {VERSION} — consider upgrading"
93 );
94 }
95}
96
97fn parse_major_minor(version: &str) -> Option<(u32, u32)> {
98 let mut parts = version.split('.');
99 let major = parts.next()?.parse().ok()?;
100 let minor = parts.next()?.parse().ok()?;
101 Some((major, minor))
102}
103
104pub fn load_or_default(path: &Path) -> Result<Config> {
106 if !path.exists() {
107 return Ok(Config::default());
108 }
109 load_config(path)
110}
111
112pub fn save_config(path: &Path, config: &Config) -> Result<()> {
113 let mut config = config.clone();
114 config.version = Some(VERSION.to_string());
115 let contents = toml::to_string_pretty(&config)?;
116 crate::system::atomic_write::atomic_write(path, contents.as_bytes(), 0o600)?;
120 Ok(())
121}