Skip to main content

elph_agent/init/
mod.rs

1mod progress;
2
3use std::fs;
4use std::io;
5use std::path::{Path, PathBuf};
6
7use serde::Serialize;
8use thiserror::Error;
9
10pub use progress::InitProgress;
11
12pub type Result<T> = std::result::Result<T, InitError>;
13
14#[derive(Debug, Error)]
15pub enum InitError {
16    #[error("io error: {0}")]
17    Io(#[from] io::Error),
18    #[error("json error: {0}")]
19    Json(#[from] serde_json::Error),
20    #[error("datastore error: {0}")]
21    Datastore(#[from] crate::datastore::DatastoreError),
22}
23
24/// Create every directory in `dirs`, including parents.
25pub fn ensure_dirs(dirs: &[PathBuf]) -> Result<()> {
26    for dir in dirs {
27        fs::create_dir_all(dir)?;
28    }
29    Ok(())
30}
31
32/// Write a pretty-printed JSON file with mode `0600` on Unix.
33pub fn write_json_file<T: Serialize>(path: &Path, value: &T) -> Result<()> {
34    if let Some(parent) = path.parent() {
35        fs::create_dir_all(parent)?;
36    }
37
38    let mut payload = serde_json::to_string_pretty(value)?;
39    payload.push('\n');
40
41    write_private_file(path, payload.as_bytes())
42}
43
44/// Write a private file with mode `0600` on Unix.
45pub fn write_private_file(path: &Path, contents: &[u8]) -> Result<()> {
46    #[cfg(unix)]
47    {
48        use std::fs::OpenOptions;
49        use std::io::Write;
50        use std::os::unix::fs::OpenOptionsExt;
51
52        let mut file = OpenOptions::new().write(true).create_new(true).mode(0o600).open(path)?;
53        file.write_all(contents)?;
54        return Ok(());
55    }
56
57    #[cfg(not(unix))]
58    {
59        fs::write(path, contents)?;
60        Ok(())
61    }
62}
63
64/// Write a file only when it does not already exist.
65pub fn write_file_if_missing(path: &Path, contents: &[u8]) -> Result<()> {
66    if path.exists() {
67        return Ok(());
68    }
69
70    if let Some(parent) = path.parent() {
71        fs::create_dir_all(parent)?;
72    }
73
74    fs::write(path, contents)?;
75    Ok(())
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn write_file_if_missing_is_idempotent() {
84        let tmp = tempfile::tempdir().expect("tempdir");
85        let path = tmp.path().join("marker.txt");
86
87        write_file_if_missing(&path, b"first").expect("first write");
88        write_file_if_missing(&path, b"second").expect("second write");
89
90        assert_eq!(fs::read_to_string(path).expect("read marker"), "first");
91    }
92}