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