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}