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