dscfg_cached_file_storage/
lib.rs

1extern crate dscfg_server;
2extern crate serde_json;
3extern crate void;
4
5use dscfg_server::{IsFatalError, Storage};
6use std::path::{Path, PathBuf};
7use std::io;
8use std::fs::File;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Eq, PartialEq, Hash)]
12enum IoOperation {
13    Open(PathBuf),
14    Write(PathBuf),
15    Move(PathBuf, PathBuf),
16}
17
18#[derive(Debug)]
19pub struct StorageError {
20    operation: IoOperation,
21    error: io::Error,
22}
23
24impl StorageError {
25    fn open_error(file: impl Into<PathBuf>, error: io::Error) -> Self {
26        StorageError {
27            operation: IoOperation::Open(file.into()),
28            error,
29        }
30    }
31
32    fn write_error(file: impl Into<PathBuf>, error: impl Into<io::Error>) -> Self {
33        StorageError {
34            operation: IoOperation::Write(file.into()),
35            error: error.into(),
36        }
37    }
38
39    fn move_error(from: impl Into<PathBuf>, to: impl Into<PathBuf>, error: io::Error) -> Self {
40        StorageError {
41            operation: IoOperation::Move(from.into(), to.into()),
42            error,
43        }
44    }
45}
46
47impl IsFatalError for StorageError {
48    fn is_fatal(&self) -> bool {
49        if let IoOperation::Write(_) = self.operation {
50            true
51        } else {
52            self.error.kind() != io::ErrorKind::Interrupted && 
53            self.error.kind() != io::ErrorKind::WouldBlock
54        }
55    }
56}
57
58pub struct CachedFileStorage {
59    file_path: PathBuf,
60    temp_file: PathBuf,
61    data: HashMap<String, serde_json::Value>,
62}
63
64impl CachedFileStorage {
65    pub fn load_or_create<P: AsRef<Path> + Into<PathBuf>>(file: P) -> io::Result<Self> {
66        let data = match File::open(file.as_ref()) {
67            Ok(file) => serde_json::from_reader(file)?,
68            Err(ref err) if err.kind() == io::ErrorKind::NotFound => Default::default(),
69            Err(err) => return Err(err),
70        };
71
72        let temp_file = Self::temp_file_path(file.as_ref())?;
73        let file_path = file.into();
74
75        Ok(CachedFileStorage {
76            file_path,
77            temp_file,
78            data,
79        })
80    }
81
82    fn temp_file_path(original_path: &Path) -> io::Result<PathBuf> {
83        use std::ffi::OsString;
84
85        let file_name = original_path.file_name().ok_or(io::ErrorKind::InvalidInput)?;
86        let mut temp_file_name: OsString = ".".into();
87        temp_file_name.push(file_name);
88        temp_file_name.push(".tmp");
89        let mut temp_file = original_path.parent().map(PathBuf::from).unwrap_or_else(PathBuf::new);
90        temp_file.push(temp_file_name);
91
92        Ok(temp_file)
93    }
94}
95
96impl Storage for CachedFileStorage {
97    type SetError = StorageError;
98    type GetError = void::Void;
99
100    fn set(&mut self, key: String, value: serde_json::Value) -> Result<(), Self::SetError> {
101        // TODO: restore original state on failure
102        self.data.insert(key, value);
103        // Make sure the file is closed before renaming.
104        {
105            let mut file = File::create(&self.temp_file).map_err(|err| StorageError::open_error(&self.temp_file, err))?;
106            serde_json::to_writer(&mut file, &self.data).map_err(|err| StorageError::write_error(&self.temp_file, err))?;
107            file.sync_data().map_err(|err| StorageError::write_error(&self.temp_file, err))?;
108        }
109        std::fs::rename(&self.temp_file, &self.file_path).map_err(|err| StorageError::move_error(&self.temp_file, &self.file_path, err))?;
110        Ok(())
111    }
112
113    fn get(&mut self, key: &str) -> Result<Option<serde_json::Value>, Self::GetError> {
114        Ok(self.data.get(key).map(Clone::clone))
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    #[test]
121    fn it_works() {
122        assert_eq!(2 + 2, 4);
123    }
124}