rd_util/
json_file.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2use anyhow::{bail, Context, Result};
3use log::{debug, info};
4use serde::{de::DeserializeOwned, Serialize};
5use std::default::Default;
6use std::fs;
7use std::io::{self, prelude::*};
8use std::path::{Path, PathBuf};
9use std::time::{SystemTime, UNIX_EPOCH};
10
11fn read_json<P: AsRef<Path>>(path: P) -> Result<(String, String)> {
12    let mut f = fs::OpenOptions::new().read(true).open(path)?;
13    let mut buf = String::new();
14    f.read_to_string(&mut buf)?;
15
16    let mut preamble = String::new();
17    let mut body = String::new();
18    let mut seen_body = false;
19
20    for line in buf.lines() {
21        let trimmed = line.trim();
22        if trimmed.starts_with("//") || trimmed.starts_with("#") {
23            if !seen_body {
24                preamble = preamble + line + "\n";
25            }
26            body = body + "\n";
27        } else {
28            seen_body = true;
29            body = body + line + "\n"
30        }
31    }
32    Ok((preamble, body))
33}
34
35pub trait JsonLoad
36where
37    Self: DeserializeOwned,
38{
39    fn loaded(&mut self, _prev: Option<&mut Self>) -> Result<()> {
40        Ok(())
41    }
42
43    fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
44        let (_, body) = read_json(path)?;
45        Ok(serde_json::from_str::<Self>(&body)?)
46    }
47}
48
49pub trait JsonSave
50where
51    Self: Default + Serialize,
52{
53    fn preamble() -> Option<String> {
54        None
55    }
56
57    fn maybe_create_dfl<P: AsRef<Path>>(path_in: P) -> Result<bool> {
58        let path = path_in.as_ref();
59
60        if let Some(parent) = path.parent() {
61            fs::create_dir_all(&parent)?;
62        }
63
64        match fs::OpenOptions::new()
65            .write(true)
66            .create_new(true)
67            .open(&path)
68        {
69            Ok(mut f) => {
70                let data: Self = Default::default();
71                f.write_all(data.as_json()?.as_ref())?;
72                Ok(true)
73            }
74            Err(e) => match e.kind() {
75                io::ErrorKind::AlreadyExists => Ok(false),
76                _ => Err(e.into()),
77            },
78        }
79    }
80
81    fn as_json(&self) -> Result<String> {
82        let mut serialized = serde_json::to_string_pretty(&self)?;
83        if !serialized.ends_with("\n") {
84            serialized += "\n";
85        }
86        match Self::preamble() {
87            Some(pre) => Ok(pre + &serialized),
88            None => Ok(serialized),
89        }
90    }
91
92    fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
93        let path: &Path = path.as_ref();
94        let fname = match path.file_name() {
95            Some(v) => v,
96            None => bail!("can't save to null path"),
97        };
98
99        let mut tmp_path = PathBuf::from(path);
100        tmp_path.pop();
101        tmp_path.push(format!(".{}.json-save-staging", &fname.to_string_lossy()));
102
103        let mut f = fs::OpenOptions::new()
104            .write(true)
105            .create(true)
106            .truncate(true)
107            .open(&tmp_path)
108            .with_context(|| format!("opening staging file {:?}", &tmp_path))?;
109        f.write_all(self.as_json()?.as_ref())
110            .with_context(|| format!("writing staging file {:?}", &tmp_path))?;
111        fs::rename(&tmp_path, path)
112            .with_context(|| format!("moving {:?} to {:?}", &tmp_path, path))?;
113        Ok(())
114    }
115}
116
117#[derive(Clone, Debug)]
118pub struct JsonConfigFile<T: JsonLoad + JsonSave> {
119    pub path: Option<PathBuf>,
120    pub loaded_mod: SystemTime,
121    pub data: T,
122}
123
124impl<T: JsonLoad + JsonSave + Default> Default for JsonConfigFile<T> {
125    fn default() -> Self {
126        Self {
127            path: None,
128            loaded_mod: UNIX_EPOCH,
129            data: Default::default(),
130        }
131    }
132}
133
134impl<T: JsonLoad + JsonSave> JsonConfigFile<T> {
135    pub fn load<P: AsRef<Path>>(path_in: P) -> Result<Self> {
136        let path = AsRef::<Path>::as_ref(&path_in);
137
138        let modified = path.metadata()?.modified()?;
139        let mut data = T::load(&path)?;
140        data.loaded(None)?;
141
142        Ok(Self {
143            path: Some(PathBuf::from(path)),
144            loaded_mod: modified,
145            data,
146        })
147    }
148
149    pub fn load_or_create<P: AsRef<Path>>(path_opt: Option<P>) -> Result<Self> {
150        match path_opt {
151            Some(path_in) => {
152                let path = AsRef::<Path>::as_ref(&path_in);
153
154                if T::maybe_create_dfl(&path)? {
155                    info!("cfg: Created {:?}", &path);
156                }
157
158                Self::load(path)
159            }
160            None => {
161                let mut data: T = Default::default();
162                data.loaded(None)?;
163
164                Ok(Self {
165                    path: None,
166                    loaded_mod: UNIX_EPOCH,
167                    data,
168                })
169            }
170        }
171    }
172
173    pub fn save(&self) -> Result<()> {
174        if let Some(path) = self.path.as_deref() {
175            self.data.save(&path)
176        } else {
177            Ok(())
178        }
179    }
180
181    pub fn maybe_reload(&mut self) -> Result<bool> {
182        let path = match self.path.as_ref() {
183            Some(p) => p,
184            None => return Ok(false),
185        };
186
187        let modified = fs::metadata(&path)?.modified()?;
188        // Consider the file iff it stayed the same for at least 10ms.
189        match SystemTime::now().duration_since(modified) {
190            Ok(dur) if dur.as_millis() < 10 => return Ok(false),
191            _ => {}
192        }
193
194        // The same as loaded?
195        if self.loaded_mod == modified {
196            return Ok(false);
197        }
198
199        self.loaded_mod = modified;
200        let mut data = T::load(&path)?;
201        data.loaded(Some(&mut self.data))?;
202        self.data = data;
203        Ok(true)
204    }
205}
206
207pub trait JsonArgs
208where
209    Self: JsonLoad + JsonSave,
210{
211    fn match_cmdline() -> clap::ArgMatches<'static>;
212    fn verbosity(matches: &clap::ArgMatches) -> u32;
213    fn log_file(matches: &clap::ArgMatches) -> String;
214    fn system_configuration_overrides(
215        _matches: &clap::ArgMatches,
216    ) -> (Option<usize>, Option<usize>, Option<usize>) {
217        (None, None, None)
218    }
219    fn process_cmdline(&mut self, matches: &clap::ArgMatches) -> bool;
220}
221
222pub trait JsonArgsHelper
223where
224    Self: JsonArgs,
225{
226    fn init_args_and_logging_nosave() -> Result<(JsonConfigFile<Self>, bool)>;
227    fn save_args(args_file: &JsonConfigFile<Self>) -> Result<()>;
228    fn init_args_and_logging() -> Result<JsonConfigFile<Self>>;
229}
230
231impl<T> JsonArgsHelper for T
232where
233    T: JsonArgs,
234{
235    fn init_args_and_logging_nosave() -> Result<(JsonConfigFile<T>, bool)> {
236        let matches = T::match_cmdline();
237        super::init_logging(T::verbosity(&matches), T::log_file(&matches));
238        let overrides = T::system_configuration_overrides(&matches);
239        super::override_system_configuration(overrides.0, overrides.1, overrides.2);
240
241        let mut args_file = JsonConfigFile::<T>::load_or_create(matches.value_of("args").as_ref())?;
242        let updated = args_file.data.process_cmdline(&matches);
243
244        Ok((args_file, updated))
245    }
246
247    fn save_args(args_file: &JsonConfigFile<T>) -> Result<()> {
248        if args_file.path.is_some() {
249            debug!(
250                "Updating command line arguments file {:?}",
251                &args_file.path.as_deref().unwrap()
252            );
253            args_file.save()?;
254        }
255        Ok(())
256    }
257
258    fn init_args_and_logging() -> Result<JsonConfigFile<T>> {
259        let (args_file, updated) = Self::init_args_and_logging_nosave()?;
260        if updated {
261            Self::save_args(&args_file)?;
262        }
263        Ok(args_file)
264    }
265}
266
267#[derive(Debug)]
268pub struct JsonReportFile<T: JsonSave> {
269    pub path: Option<PathBuf>,
270    pub staging: PathBuf,
271    pub data: T,
272}
273
274impl<T: JsonSave> JsonReportFile<T> {
275    pub fn new<P: AsRef<Path>>(path_opt: Option<P>) -> Self {
276        let (path, staging) = match path_opt {
277            Some(p) => {
278                let pb = PathBuf::from(p.as_ref());
279                let mut st = pb.clone().into_os_string();
280                st.push(".staging");
281                (Some(pb), PathBuf::from(st))
282            }
283            None => (None, PathBuf::new()),
284        };
285
286        Self {
287            path,
288            staging,
289            data: Default::default(),
290        }
291    }
292
293    pub fn commit(&self) -> Result<()> {
294        let path = match self.path.as_ref() {
295            Some(v) => v,
296            None => return Ok(()),
297        };
298
299        self.data.save(&self.staging)?;
300        fs::rename(&self.staging, &path)?;
301        Ok(())
302    }
303}
304
305pub struct JsonRawFile {
306    pub path: PathBuf,
307    pub preamble: String,
308    pub value: serde_json::Value,
309}
310
311impl JsonRawFile {
312    pub fn load<P: AsRef<Path>>(path_in: P) -> Result<Self> {
313        let path = PathBuf::from(path_in.as_ref());
314        let (preamble, body) = read_json(&path)?;
315
316        Ok(Self {
317            path,
318            preamble,
319            value: serde_json::from_str(&body)?,
320        })
321    }
322
323    pub fn save(&self) -> Result<()> {
324        let output = self.preamble.clone() + &serde_json::ser::to_string_pretty(&self.value)?;
325        let mut f = fs::OpenOptions::new()
326            .write(true)
327            .create(true)
328            .truncate(true)
329            .open(&self.path)?;
330        f.write_all(output.as_ref())?;
331        Ok(())
332    }
333}