1use std::marker::PhantomData;
35use std::path::{Path, PathBuf};
36
37use doublets::data::{Flow, LinkReference};
38use doublets::unit::{LinkPart, Store as UnitStore};
39use doublets::Doublets;
40
41use crate::error::LinkError;
42use crate::link::GenericLink;
43use crate::storage::file_mem::PersistentFileMapped;
44use crate::storage::lock::{lock_file_path, FileLock, LockMode};
45use crate::storage::traits::{LinksStorage, StorageRevision};
46
47pub type FileMappedUnitStore<T> = UnitStore<T, PersistentFileMapped<LinkPart<T>>>;
49
50pub struct DoubletsStorage<T: LinkReference, S: Doublets<T>> {
52 store: S,
53 path: Option<PathBuf>,
54 known_revision: StorageRevision,
55 lock: Option<FileLock>,
56 address: PhantomData<T>,
57}
58
59impl<T: LinkReference> DoubletsStorage<T, FileMappedUnitStore<T>> {
60 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
63 Self::open_internal(path, None)
64 }
65
66 pub fn open_shared<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
69 Self::open_internal(path, Some(LockMode::Shared))
70 }
71
72 pub fn open_exclusive<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
76 Self::open_internal(path, Some(LockMode::Exclusive))
77 }
78
79 pub fn try_open_exclusive<P: AsRef<Path>>(path: P) -> Result<Option<Self>, LinkError> {
82 let path = path.as_ref();
83 match FileLock::try_acquire(lock_file_path(path), LockMode::Exclusive)? {
84 Some(lock) => Ok(Some(Self::open_mapped(path, Some(lock))?)),
85 None => Ok(None),
86 }
87 }
88
89 fn open_internal<P: AsRef<Path>>(path: P, mode: Option<LockMode>) -> Result<Self, LinkError> {
90 let path = path.as_ref();
91 let lock = match mode {
92 Some(mode) => Some(FileLock::acquire(lock_file_path(path), mode)?),
93 None => None,
94 };
95 Self::open_mapped(path, lock)
96 }
97
98 fn open_mapped(path: &Path, lock: Option<FileLock>) -> Result<Self, LinkError> {
99 if let Some(parent) = path.parent() {
100 if !parent.as_os_str().is_empty() && !parent.exists() {
101 std::fs::create_dir_all(parent)?;
102 }
103 }
104 let mem = PersistentFileMapped::<LinkPart<T>>::from_path(path)?;
105 let store = FileMappedUnitStore::<T>::new(mem)?;
106 Ok(Self {
107 store,
108 path: Some(path.to_path_buf()),
109 known_revision: StorageRevision::of(path)?,
110 lock,
111 address: PhantomData,
112 })
113 }
114}
115
116impl<T: LinkReference, S: Doublets<T>> DoubletsStorage<T, S> {
117 pub fn wrap(store: S) -> Self {
124 Self {
125 store,
126 path: None,
127 known_revision: StorageRevision::default(),
128 lock: None,
129 address: PhantomData,
130 }
131 }
132
133 pub fn wrap_at<P: AsRef<Path>>(store: S, path: P) -> Result<Self, LinkError> {
137 let path = path.as_ref().to_path_buf();
138 let known_revision = StorageRevision::of(&path)?;
139 Ok(Self {
140 store,
141 path: Some(path),
142 known_revision,
143 lock: None,
144 address: PhantomData,
145 })
146 }
147
148 pub fn path(&self) -> Option<&Path> {
150 self.path.as_deref()
151 }
152
153 pub fn store(&self) -> &S {
155 &self.store
156 }
157
158 pub fn store_mut(&mut self) -> &mut S {
160 &mut self.store
161 }
162
163 pub fn into_store(self) -> S {
165 self.store
166 }
167
168 pub fn lock_shared(&self) -> Result<FileLock, LinkError> {
170 FileLock::acquire(self.require_lock_path()?, LockMode::Shared)
171 }
172
173 pub fn lock_exclusive(&self) -> Result<FileLock, LinkError> {
175 FileLock::acquire(self.require_lock_path()?, LockMode::Exclusive)
176 }
177
178 pub fn held_lock(&self) -> Option<&FileLock> {
180 self.lock.as_ref()
181 }
182
183 fn require_lock_path(&self) -> Result<PathBuf, LinkError> {
184 self.path.as_ref().map(lock_file_path).ok_or_else(|| {
185 LinkError::Lock("this doublets storage is not backed by a known file".to_string())
186 })
187 }
188
189 fn refresh_revision(&mut self) -> Result<(), LinkError> {
190 if let Some(path) = self.path.as_ref() {
191 self.known_revision = StorageRevision::of(path)?;
192 }
193 Ok(())
194 }
195}
196
197impl<T: LinkReference, S: Doublets<T>> LinksStorage<T> for DoubletsStorage<T, S> {
198 fn create_link(&mut self, source: T, target: T) -> Result<T, LinkError> {
199 Ok(Doublets::create_link(&mut self.store, source, target)?)
200 }
201
202 fn ensure_link_created(&mut self, index: T) -> Result<T, LinkError> {
203 if self.link_exists(index) {
204 return Ok(index);
205 }
206 loop {
210 let created = Doublets::create(&mut self.store)?;
211 match created.cmp(&index) {
212 std::cmp::Ordering::Equal => return Ok(index),
213 std::cmp::Ordering::Less => continue,
214 std::cmp::Ordering::Greater => {
215 return Err(LinkError::StorageError(format!(
216 "could not reserve link address {index}: the store allocated {created} instead"
217 )))
218 }
219 }
220 }
221 }
222
223 fn get_link(&self, index: T) -> Option<GenericLink<T>> {
224 Doublets::get_link(&self.store, index).map(GenericLink::from)
225 }
226
227 fn link_exists(&self, index: T) -> bool {
228 Doublets::get_link(&self.store, index).is_some()
229 }
230
231 fn update_link(&mut self, index: T, source: T, target: T) -> Result<GenericLink<T>, LinkError> {
232 let before = self
233 .get_link(index)
234 .ok_or_else(|| LinkError::not_found(index))?;
235 Doublets::update(&mut self.store, index, source, target)?;
236 Ok(before)
237 }
238
239 fn delete_link(&mut self, index: T) -> Result<GenericLink<T>, LinkError> {
240 let before = self
241 .get_link(index)
242 .ok_or_else(|| LinkError::not_found(index))?;
243 Doublets::delete(&mut self.store, index)?;
244 Ok(before)
245 }
246
247 fn all_links(&self) -> Vec<GenericLink<T>> {
248 let mut links = Vec::new();
249 Doublets::each(&self.store, |link| {
250 links.push(GenericLink::from(link));
251 Flow::Continue
252 });
253 links
254 }
255
256 fn query_links(
257 &self,
258 index: Option<T>,
259 source: Option<T>,
260 target: Option<T>,
261 ) -> Vec<GenericLink<T>> {
262 let any = self.store.constants().any;
263 let query = [
264 index.unwrap_or(any),
265 source.unwrap_or(any),
266 target.unwrap_or(any),
267 ];
268 let mut links = Vec::new();
269 Doublets::each_by(&self.store, query, |link| {
270 links.push(GenericLink::from(link));
271 Flow::Continue
272 });
273 links
274 }
275
276 fn search_link(&self, source: T, target: T) -> Option<T> {
277 Doublets::search(&self.store, source, target)
278 }
279
280 fn get_or_create_link(&mut self, source: T, target: T) -> Result<T, LinkError> {
281 Ok(Doublets::get_or_create(&mut self.store, source, target)?)
282 }
283
284 fn links_count(&self) -> usize {
285 TryInto::<usize>::try_into(Doublets::count(&self.store)).unwrap_or(usize::MAX)
286 }
287
288 fn flush(&mut self) -> Result<(), LinkError> {
299 if let Some(path) = self.path.clone() {
300 let file = std::fs::File::options().write(true).open(&path)?;
301 file.sync_all()?;
302 file.set_modified(std::time::SystemTime::now())?;
303 self.refresh_revision()?;
304 }
305 Ok(())
306 }
307
308 fn has_external_changes(&self) -> Result<bool, LinkError> {
318 match self.path.as_ref() {
319 Some(path) => Ok(StorageRevision::of(path)? != self.known_revision),
320 None => Ok(false),
321 }
322 }
323
324 fn reload(&mut self) -> Result<(), LinkError> {
327 self.refresh_revision()
328 }
329}