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
24pub fn ensure_dirs(dirs: &[PathBuf]) -> Result<()> {
26 for dir in dirs {
27 fs::create_dir_all(dir)?;
28 }
29 Ok(())
30}
31
32pub 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
44pub 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
64pub 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}