Skip to main content

ecr_store/
settings.rs

1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5#[serde(default)]
6pub struct ServerSettings {
7    pub notmuch_config: Option<PathBuf>,
8    pub mbsync_config: Option<PathBuf>,
9    pub msmtp_config: Option<PathBuf>,
10    pub maildir_root: Option<PathBuf>,
11    pub notmuch_bin: Option<PathBuf>,
12    pub mbsync_bin: Option<PathBuf>,
13    pub msmtp_bin: Option<PathBuf>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Binaries {
18    pub notmuch: PathBuf,
19    pub mbsync: PathBuf,
20    pub msmtp: PathBuf,
21}
22
23impl Default for Binaries {
24    fn default() -> Self {
25        Self {
26            notmuch: PathBuf::from(crate::tools::NOTMUCH),
27            mbsync: PathBuf::from(crate::tools::MBSYNC),
28            msmtp: PathBuf::from(crate::tools::MSMTP),
29        }
30    }
31}
32
33impl Binaries {
34    pub fn from_settings(settings: &ServerSettings) -> Self {
35        let defaults = Self::default();
36        Self {
37            notmuch: settings.notmuch_bin.clone().unwrap_or(defaults.notmuch),
38            mbsync: settings.mbsync_bin.clone().unwrap_or(defaults.mbsync),
39            msmtp: settings.msmtp_bin.clone().unwrap_or(defaults.msmtp),
40        }
41    }
42}
43
44impl ServerSettings {
45    pub fn default_path() -> Option<PathBuf> {
46        dirs::config_dir().map(|d| d.join("ecr").join("server.toml"))
47    }
48
49    pub fn load() -> Self {
50        match Self::default_path() {
51            Some(path) => Self::load_from(&path),
52            None => Self::default(),
53        }
54    }
55
56    pub fn load_from(path: &Path) -> Self {
57        let Ok(text) = std::fs::read_to_string(path) else {
58            return Self::default();
59        };
60        match toml::from_str::<ServerSettings>(&text) {
61            Ok(settings) => settings,
62            Err(err) => {
63                tracing::warn!(path = %path.display(), %err, "ignoring unreadable server settings");
64                Self::default()
65            }
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use std::io::Write;
74
75    #[test]
76    fn missing_file_yields_defaults() {
77        let settings = ServerSettings::load_from(Path::new("/nonexistent/ecr/server.toml"));
78        assert!(settings.notmuch_config.is_none());
79    }
80
81    #[test]
82    fn explicit_paths_are_read() {
83        let dir = tempfile::tempdir().unwrap();
84        let path = dir.path().join("server.toml");
85        let mut f = std::fs::File::create(&path).unwrap();
86        writeln!(f, r#"notmuch_config = "/etc/notmuch/config""#).unwrap();
87
88        let settings = ServerSettings::load_from(&path);
89        assert_eq!(
90            settings.notmuch_config,
91            Some(PathBuf::from("/etc/notmuch/config"))
92        );
93        assert!(settings.mbsync_config.is_none());
94    }
95
96    #[test]
97    fn malformed_file_degrades_to_defaults() {
98        let dir = tempfile::tempdir().unwrap();
99        let path = dir.path().join("server.toml");
100        std::fs::write(&path, "this is not toml {{{").unwrap();
101        assert!(ServerSettings::load_from(&path).notmuch_config.is_none());
102    }
103}