iron_os_file_db/
lib.rs

1
2use std::path::PathBuf;
3use std::{io, fs};
4use std::ops::{Deref, DerefMut};
5
6use serde::{de::DeserializeOwned, Serialize};
7
8#[derive(Debug)]
9pub struct FileDb<T> {
10	path: PathBuf,
11	tmp_path: PathBuf,
12	data: T
13}
14
15impl<T> FileDb<T> {
16
17	fn sanitze_paths(mut main: PathBuf) -> (PathBuf, PathBuf) {
18		if main.file_name().is_none() {
19			main.set_file_name("");
20			main.set_extension("fdb");
21		}
22		let mut tmp = main.clone().into_os_string();
23		tmp.push(".tmp");
24		(main, tmp.into())
25	}
26
27	// the file extension of path will be set to fdb
28	pub fn new(path: impl Into<PathBuf>, data: T) -> Self {
29		let (path, tmp_path) = Self::sanitze_paths(path.into());
30
31		Self { path, tmp_path, data }
32	}
33
34	/// if the file does not exists this will return an error.
35	pub fn open_sync(path: impl Into<PathBuf>) -> io::Result<Self>
36	where T: DeserializeOwned {
37		let (path, tmp_path) = Self::sanitze_paths(path.into());
38
39		// because we are lazy just read the whole file
40		let v = fs::read(&path)?;
41		let data = serde_json::from_slice(&v)
42			.map_err(json_err)?;
43
44		Ok(Self { path, tmp_path, data })
45	}
46
47	/// if the file does not exists this will return an error.
48	pub fn read_sync(&mut self) -> io::Result<()>
49	where T: DeserializeOwned {
50		let v = fs::read(&self.path)?;
51		let data = serde_json::from_slice(&v)
52			.map_err(json_err)?;
53
54		self.data = data;
55		Ok(())
56	}
57
58	pub fn write_sync(&self) -> io::Result<()>
59	where T: Serialize {
60		let v = serde_json::to_vec(&self.data)
61			.map_err(json_err)?;
62
63		// write to the tmp file
64		fs::write(&self.tmp_path, v)?;
65		// now rename the file atomically
66		fs::rename(&self.tmp_path, &self.path)
67	}
68
69	pub fn data(&self) -> &T {
70		&self.data
71	}
72
73	pub fn data_mut(&mut self) -> &mut T {
74		&mut self.data
75	}
76
77	pub fn into_data(self) -> T {
78		self.data
79	}
80
81}
82
83impl<T> Deref for FileDb<T> {
84	type Target = T;
85	fn deref(&self) -> &T {
86		self.data()
87	}
88}
89
90impl<T> DerefMut for FileDb<T> {
91	fn deref_mut(&mut self) -> &mut T {
92		self.data_mut()
93	}
94}
95
96#[cfg(any(feature = "async", test))]
97impl<T> FileDb<T> {
98
99	/// if the file does not exists this will return an error.
100	pub async fn open(path: impl Into<PathBuf>) -> io::Result<Self>
101	where T: DeserializeOwned {
102		let (path, tmp_path) = Self::sanitze_paths(path.into());
103
104		// because we are lazy just read the whole file
105		let v = tokio::fs::read(&path).await?;
106		let data = serde_json::from_slice(&v)
107			.map_err(json_err)?;
108
109		Ok(Self { path, tmp_path, data })
110	}
111
112	/// if the file does not exists this will return an error.
113	pub async fn read(&mut self) -> io::Result<()>
114	where T: DeserializeOwned {
115		let v = tokio::fs::read(&self.path).await?;
116		let data = serde_json::from_slice(&v)
117			.map_err(json_err)?;
118
119		self.data = data;
120		Ok(())
121	}
122
123	pub async fn write(&self) -> io::Result<()>
124	where T: Serialize {
125		let v = serde_json::to_vec(&self.data)
126			.map_err(json_err)?;
127
128		// write to the tmp file
129		tokio::fs::write(&self.tmp_path, v).await?;
130		// now rename the file atomically
131		tokio::fs::rename(&self.tmp_path, &self.path).await
132	}
133
134}
135
136fn json_err(e: serde_json::Error) -> io::Error {
137	io::Error::new(io::ErrorKind::Other, e)
138}
139
140
141#[cfg(test)]
142mod tests {
143
144	use super::*;
145	use serde::Deserialize;
146	use std::path::PathBuf;
147	use std::fs;
148
149	#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
150	pub struct Data {
151		inner: Vec<u32>
152	}
153
154	const TEST_TMP: &str = "./tests_tmp";
155
156	fn test_tmp() -> PathBuf {
157		let p = PathBuf::from(TEST_TMP);
158		if !p.is_dir() {
159			let _ = fs::create_dir(&p);
160		}
161		p
162	}
163
164	#[test]
165	fn test_sync() {
166		let mut file = test_tmp();
167		file.push("test_sync.fdb");
168
169		let data = Data {
170			inner: vec![1, 4, 8, 14]
171		};
172
173		let mut db = FileDb::new(file.clone(), data.clone());
174		db.write_sync().unwrap();
175
176		// now open
177		let mut db_2: FileDb<Data> = FileDb::open_sync(file.clone()).unwrap();
178		assert_eq!(db_2.data(), &data);
179
180		// update one
181		db.data_mut().inner.push(420);
182		db.write_sync().unwrap();
183
184		// now read
185		db_2.read_sync().unwrap();
186		assert_ne!(db_2.data(), &data);
187		assert_eq!(db_2.data().inner.last().unwrap(), &420);
188
189	}
190
191	#[tokio::test]
192	async fn test_async() {
193		let mut file = test_tmp();
194		file.push("test_async.fdb");
195
196		let data = Data {
197			inner: vec![1, 4, 8, 14]
198		};
199
200		let mut db = FileDb::new(file.clone(), data.clone());
201		db.write().await.unwrap();
202
203		// now open
204		let mut db_2: FileDb<Data> = FileDb::open(file.clone()).await.unwrap();
205		assert_eq!(db_2.data(), &data);
206
207		// update one
208		db.data_mut().inner.push(420);
209		db.write().await.unwrap();
210
211		// now read
212		db_2.read().await.unwrap();
213		assert_ne!(db_2.data(), &data);
214		assert_eq!(db_2.data().inner.last().unwrap(), &420);
215
216	}
217
218}