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 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
135pub fn hypha_dir() -> PathBuf {
137 crate::site::get_cmn_home().join("hypha")
138}