1use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
2use serde::{de::DeserializeOwned, Serialize};
3use std::{
4 ffi::{OsStr, OsString},
5 fmt,
6 fs::{self, File},
7 io::{self},
8 ops::{Deref, DerefMut},
9 path::{Path, PathBuf},
10};
11use tracing::{error, info};
12
13pub trait DataStore: Default + Serialize {
17 fn open<P>(db: P) -> AtomicDatabase<Self>
19 where
20 P: AsRef<Path>,
21 Self: DeserializeOwned,
22 {
23 let db_path = db.as_ref();
24 if db_path.exists() {
25 AtomicDatabase::load(db_path).unwrap()
26 } else {
27 AtomicDatabase::create(db_path).unwrap()
28 }
29 }
30
31 fn open_in_memory() -> AtomicDatabase<Self>
33 where
34 Self: DeserializeOwned,
35 {
36 AtomicDatabase::load_in_memory()
37 }
38
39 fn load(file: impl io::Read) -> std::io::Result<Self>
41 where
42 Self: Sized,
43 Self: DeserializeOwned,
44 {
45 Ok(serde_json::from_reader(file)?)
46 }
47
48 fn save(&self, mut file: impl io::Write) -> std::io::Result<()> {
50 serde_json::to_writer_pretty(&mut file, self)?;
51 Ok(())
52 }
53}
54
55pub struct AtomicDatabase<T: DataStore> {
57 path: Option<PathBuf>,
58 tmp: Option<PathBuf>,
60 data: RwLock<T>,
61}
62
63impl<T: DataStore + DeserializeOwned> AtomicDatabase<T> {
64 pub fn load_in_memory() -> Self {
66 Self {
67 path: None,
68 tmp: None,
69 data: RwLock::new(T::default()),
70 }
71 }
72
73 pub fn load(path: &Path) -> Result<Self, std::io::Error> {
75 let tmp = Self::tmp_path(path)?;
76 let file = File::open(path)?;
77 let data = T::load(file)?;
79 atomic_write(&tmp, path, &data)?;
80
81 Ok(Self {
82 path: Some(path.into()),
83 tmp: Some(tmp),
84 data: RwLock::new(data),
85 })
86 }
87
88 pub fn create(path: &Path) -> Result<Self, std::io::Error> {
90 let tmp = Self::tmp_path(path)?;
91
92 let data = Default::default();
93 atomic_write(&tmp, path, &data)?;
94
95 Ok(Self {
96 path: Some(path.into()),
97 tmp: Some(tmp),
98 data: RwLock::new(data),
99 })
100 }
101
102 pub fn read(&self) -> AtomicDatabaseRead<'_, T> {
104 AtomicDatabaseRead {
105 data: self.data.read(),
106 }
107 }
108
109 pub fn write(&self) -> AtomicDatabaseWrite<'_, T> {
111 AtomicDatabaseWrite {
112 path: self.path.as_deref(),
113 tmp: self.tmp.as_deref(),
114 data: self.data.write(),
115 }
116 }
117
118 fn tmp_path(path: &Path) -> Result<PathBuf, std::io::Error> {
119 let mut tmp_name = OsString::from(".");
120 tmp_name.push(path.file_name().unwrap_or(OsStr::new("db")));
121 tmp_name.push("~");
122 let tmp = path.with_file_name(tmp_name);
123 if tmp.exists() {
124 error!(
125 "Found orphaned database temporary file '{tmp:?}'. \
126 The server has recently crashed or is already running. \
127 Delete this before continuing!"
128 );
129 return Err(std::io::Error::new(
130 std::io::ErrorKind::AlreadyExists,
131 "orphaned temporary file exists",
132 ));
133 }
134 Ok(tmp)
135 }
136}
137
138fn atomic_write<T: DataStore>(tmp: &Path, path: &Path, data: &T) -> Result<(), std::io::Error> {
142 {
143 let mut tmpfile = File::create(tmp)?;
144 data.save(&mut tmpfile)?;
145 tmpfile.sync_all()?; }
147 fs::rename(tmp, path)?;
148 Ok(())
149}
150
151impl<T: DataStore> fmt::Debug for AtomicDatabase<T> {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 f.debug_struct("AtomicDatabase")
154 .field("file", &self.path)
155 .finish()
156 }
157}
158
159impl<T: DataStore> Drop for AtomicDatabase<T> {
160 fn drop(&mut self) {
161 if let (Some(tmp), Some(path)) = (&self.tmp, &self.path) {
162 info!("Saving database");
163 let guard = self.data.read();
164 if let Err(e) = atomic_write(tmp, path, &*guard) {
165 error!("Failed to save database on drop: {}", e);
166 }
167 }
168 }
169}
170
171pub struct AtomicDatabaseRead<'a, T: DataStore> {
172 data: RwLockReadGuard<'a, T>,
173}
174
175impl<'a, T: DataStore> Deref for AtomicDatabaseRead<'a, T> {
176 type Target = T;
177 fn deref(&self) -> &Self::Target {
178 &self.data
179 }
180}
181
182pub struct AtomicDatabaseWrite<'a, T: DataStore> {
183 tmp: Option<&'a Path>,
184 path: Option<&'a Path>,
185 data: RwLockWriteGuard<'a, T>,
186}
187
188impl<'a, T: DataStore> Deref for AtomicDatabaseWrite<'a, T> {
189 type Target = T;
190 fn deref(&self) -> &Self::Target {
191 &self.data
192 }
193}
194
195impl<'a, T: DataStore> DerefMut for AtomicDatabaseWrite<'a, T> {
196 fn deref_mut(&mut self) -> &mut Self::Target {
197 &mut self.data
198 }
199}
200
201impl<'a, T: DataStore> Drop for AtomicDatabaseWrite<'a, T> {
202 fn drop(&mut self) {
203 if let (Some(tmp), Some(path)) = (self.tmp, self.path) {
204 info!("Saving database");
205 if let Err(e) = atomic_write(tmp, path, &*self.data) {
206 error!("Failed to save database: {}", e);
207 }
208 }
209 }
210}