1use serde::{de::DeserializeOwned, Serialize};
2use std::{
3 ffi::{OsStr, OsString},
4 fmt,
5 fs::{self, File},
6 io::{self, BufWriter},
7 ops::{Deref, DerefMut},
8 path::{Path, PathBuf},
9 sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
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, file: impl io::Write) -> std::io::Result<()> {
50 let writer = BufWriter::new(file);
51 serde_json::to_writer_pretty(writer, self)?;
52 Ok(())
53 }
54}
55
56pub struct AtomicDatabase<T: DataStore> {
58 path: Option<PathBuf>,
59 tmp: Option<PathBuf>,
61 data: RwLock<T>,
62}
63
64impl<T: DataStore + DeserializeOwned> AtomicDatabase<T> {
65 pub fn load_in_memory() -> Self {
67 Self {
68 path: None,
69 tmp: None,
70 data: RwLock::new(T::default()),
71 }
72 }
73
74 pub fn load(path: &Path) -> Result<Self, std::io::Error> {
76 let new_path = path.with_extension("json");
77 let tmp = Self::tmp_path(&new_path)?;
78
79 let file = File::open(path)?;
80 let data = T::load(file)?;
82 atomic_write(&tmp, &new_path, &data)?;
83
84 Ok(Self {
85 path: Some(new_path),
86 tmp: Some(tmp),
87 data: RwLock::new(data),
88 })
89 }
90
91 pub fn create(path: &Path) -> Result<Self, std::io::Error> {
93 let tmp = Self::tmp_path(path)?;
94
95 let data = Default::default();
96 atomic_write(&tmp, path, &data)?;
97
98 Ok(Self {
99 path: Some(path.into()),
100 tmp: Some(tmp),
101 data: RwLock::new(data),
102 })
103 }
104
105 pub fn read(&self) -> AtomicDatabaseRead<'_, T> {
107 AtomicDatabaseRead {
108 data: self.data.read().unwrap(),
109 }
110 }
111
112 pub fn write(&self) -> AtomicDatabaseWrite<'_, T> {
114 AtomicDatabaseWrite {
115 path: self.path.as_deref(),
116 tmp: self.tmp.as_deref(),
117 data: self.data.write().unwrap(),
118 }
119 }
120
121 fn tmp_path(path: &Path) -> Result<PathBuf, std::io::Error> {
122 let mut tmp_name = OsString::from(".");
123 tmp_name.push(path.file_name().unwrap_or(OsStr::new("db")));
124 tmp_name.push("~");
125 let tmp = path.with_file_name(tmp_name);
126 if tmp.exists() {
127 error!(
128 "Found orphaned database temporary file '{tmp:?}'. The server has recently crashed or is already running. Delete this before continuing!"
129 );
130 return Err(std::io::Error::last_os_error());
131 }
132 Ok(tmp)
133 }
134}
135
136fn atomic_write<T: DataStore>(tmp: &Path, path: &Path, data: &T) -> Result<(), std::io::Error> {
140 {
141 let mut tmpfile = File::create(tmp)?;
142 data.save(&mut tmpfile)?;
143 tmpfile.sync_all()?; }
145 fs::rename(tmp, path)?;
146 Ok(())
147}
148
149impl<T: DataStore> fmt::Debug for AtomicDatabase<T> {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.debug_struct("AtomicDatabase")
152 .field("file", &self.path)
153 .finish()
154 }
155}
156
157impl<T: DataStore> Drop for AtomicDatabase<T> {
158 fn drop(&mut self) {
159 if let Some(tmp) = &self.tmp {
160 if let Some(path) = &self.path {
161 info!("Saving database");
162 let guard = self.data.read().unwrap();
163 atomic_write(tmp, path, &*guard).unwrap();
164 }
165 }
166 }
167}
168
169pub struct AtomicDatabaseRead<'a, T: DataStore> {
170 data: RwLockReadGuard<'a, T>,
171}
172
173impl<'a, T: DataStore> Deref for AtomicDatabaseRead<'a, T> {
174 type Target = T;
175 fn deref(&self) -> &Self::Target {
176 &self.data
177 }
178}
179
180pub struct AtomicDatabaseWrite<'a, T: DataStore> {
181 tmp: Option<&'a Path>,
182 path: Option<&'a Path>,
183 data: RwLockWriteGuard<'a, T>,
184}
185
186impl<'a, T: DataStore> Deref for AtomicDatabaseWrite<'a, T> {
187 type Target = T;
188 fn deref(&self) -> &Self::Target {
189 &self.data
190 }
191}
192
193impl<'a, T: DataStore> DerefMut for AtomicDatabaseWrite<'a, T> {
194 fn deref_mut(&mut self) -> &mut Self::Target {
195 &mut self.data
196 }
197}
198
199impl<'a, T: DataStore> Drop for AtomicDatabaseWrite<'a, T> {
200 fn drop(&mut self) {
201 if let Some(tmp) = self.tmp {
202 if let Some(path) = self.path {
203 info!("Saving database");
204 atomic_write(tmp, path, &*self.data).unwrap();
205 }
206 }
207 }
208}