Skip to main content

hypha/config/
files.rs

1use std::path::{Path, PathBuf};
2
3use super::HyphaConfig;
4
5impl HyphaConfig {
6    pub fn load() -> Result<Self, crate::sink::HyphaError> {
7        use crate::sink::HyphaError;
8
9        let path = config_path();
10        match std::fs::read_to_string(&path) {
11            Ok(content) => toml::from_str(&content).map_err(|e| {
12                HyphaError::with_hint(
13                    "config_parse_failed",
14                    format!("Failed to parse {}: {}", path.display(), e),
15                    "fix the file or remove it to use defaults",
16                )
17            }),
18            Err(_) => Ok(Self::default()),
19        }
20    }
21
22    pub fn save(&self) -> Result<(), crate::sink::HyphaError> {
23        use crate::sink::HyphaError;
24
25        let path = config_path();
26        if let Some(parent) = path.parent() {
27            std::fs::create_dir_all(parent).map_err(|e| {
28                HyphaError::new(
29                    "config_save_failed",
30                    format!("Failed to create config directory: {}", e),
31                )
32            })?;
33        }
34        let content = toml::to_string_pretty(self).map_err(|e| {
35            HyphaError::new(
36                "config_save_failed",
37                format!("Failed to serialize config: {}", e),
38            )
39        })?;
40        write_text_file_atomic(&path, &content, 0o600, "config_save_failed", "config.toml")
41    }
42}
43
44pub(super) fn write_text_file_atomic(
45    path: &Path,
46    content: &str,
47    mode: u32,
48    error_code: &str,
49    file_label: &str,
50) -> Result<(), crate::sink::HyphaError> {
51    use crate::sink::HyphaError;
52    use std::io::Write;
53
54    let parent = path.parent().ok_or_else(|| {
55        HyphaError::new(
56            error_code,
57            format!("Failed to determine parent directory for {}", file_label),
58        )
59    })?;
60    let file_name = path
61        .file_name()
62        .and_then(|name| name.to_str())
63        .unwrap_or("tmp");
64    let tmp_path = parent.join(format!(
65        ".{}.tmp.{}.{}",
66        file_name,
67        std::process::id(),
68        crate::time::now_epoch_ms()
69    ));
70
71    #[cfg(unix)]
72    let mut file = {
73        use std::os::unix::fs::OpenOptionsExt;
74
75        std::fs::OpenOptions::new()
76            .create_new(true)
77            .write(true)
78            .mode(mode)
79            .open(&tmp_path)
80            .map_err(|e| {
81                HyphaError::new(
82                    error_code,
83                    format!("Failed to create temp {}: {}", file_label, e),
84                )
85            })?
86    };
87
88    #[cfg(not(unix))]
89    let mut file = std::fs::OpenOptions::new()
90        .create_new(true)
91        .write(true)
92        .open(&tmp_path)
93        .map_err(|e| {
94            HyphaError::new(
95                error_code,
96                format!("Failed to create temp {}: {}", file_label, e),
97            )
98        })?;
99
100    if let Err(e) = file.write_all(content.as_bytes()) {
101        let _ = std::fs::remove_file(&tmp_path);
102        return Err(HyphaError::new(
103            error_code,
104            format!("Failed to write temp {}: {}", file_label, e),
105        ));
106    }
107    if let Err(e) = file.sync_all() {
108        let _ = std::fs::remove_file(&tmp_path);
109        return Err(HyphaError::new(
110            error_code,
111            format!("Failed to sync temp {}: {}", file_label, e),
112        ));
113    }
114    drop(file);
115
116    std::fs::rename(&tmp_path, path).map_err(|e| {
117        let _ = std::fs::remove_file(&tmp_path);
118        HyphaError::new(
119            error_code,
120            format!("Failed to replace {}: {}", file_label, e),
121        )
122    })?;
123
124    // fsync the directory so the rename itself survives a crash.
125    if let Ok(handle) = std::fs::File::open(parent) {
126        let _ = handle.sync_all();
127    }
128    Ok(())
129}
130
131pub fn config_path() -> PathBuf {
132    hypha_dir().join("config.toml")
133}
134
135/// $CMN_HOME/hypha/
136pub fn hypha_dir() -> PathBuf {
137    crate::site::get_cmn_home().join("hypha")
138}