use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use doublets::data::{Flow, LinkReference};
use doublets::decorators::{AutomaticUniquenessAndUsagesResolution, DecoratorsExt};
use doublets::unit::{LinkPart, Store as UnitStore};
use doublets::Doublets;
use crate::error::LinkError;
use crate::link::GenericLink;
use crate::storage::file_mem::PersistentFileMapped;
use crate::storage::lock::{lock_file_path, FileLock, LockMode};
use crate::storage::traits::{LinksStorage, StorageRevision};
pub type FileMappedUnitStore<T> = UnitStore<T, PersistentFileMapped<LinkPart<T>>>;
pub type ResolvedFileMappedUnitStore<T> =
AutomaticUniquenessAndUsagesResolution<T, FileMappedUnitStore<T>>;
pub struct DoubletsStorage<T: LinkReference, S: Doublets<T>> {
store: S,
path: Option<PathBuf>,
known_revision: StorageRevision,
lock: Option<FileLock>,
address: PhantomData<T>,
}
impl<T: LinkReference> DoubletsStorage<T, FileMappedUnitStore<T>> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
Self::open_internal(path, None)
}
pub fn open_shared<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
Self::open_internal(path, Some(LockMode::Shared))
}
pub fn open_exclusive<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
Self::open_internal(path, Some(LockMode::Exclusive))
}
pub fn try_open_exclusive<P: AsRef<Path>>(path: P) -> Result<Option<Self>, LinkError> {
let path = path.as_ref();
match FileLock::try_acquire(lock_file_path(path), LockMode::Exclusive)? {
Some(lock) => Ok(Some(Self::open_mapped(path, Some(lock))?)),
None => Ok(None),
}
}
fn open_internal<P: AsRef<Path>>(path: P, mode: Option<LockMode>) -> Result<Self, LinkError> {
let path = path.as_ref();
let lock = match mode {
Some(mode) => Some(FileLock::acquire(lock_file_path(path), mode)?),
None => None,
};
Self::open_mapped(path, lock)
}
fn open_mapped(path: &Path, lock: Option<FileLock>) -> Result<Self, LinkError> {
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() && !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
let mem = PersistentFileMapped::<LinkPart<T>>::from_path(path)?;
let store = FileMappedUnitStore::<T>::new(mem)?;
Ok(Self {
store,
path: Some(path.to_path_buf()),
known_revision: StorageRevision::of(path)?,
lock,
address: PhantomData,
})
}
}
impl<T: LinkReference, S: Doublets<T>> DoubletsStorage<T, S> {
pub fn wrap(store: S) -> Self {
Self {
store,
path: None,
known_revision: StorageRevision::default(),
lock: None,
address: PhantomData,
}
}
pub fn wrap_at<P: AsRef<Path>>(store: S, path: P) -> Result<Self, LinkError> {
let path = path.as_ref().to_path_buf();
let known_revision = StorageRevision::of(&path)?;
Ok(Self {
store,
path: Some(path),
known_revision,
lock: None,
address: PhantomData,
})
}
pub fn map_store<S2, F>(self, map: F) -> DoubletsStorage<T, S2>
where
S2: Doublets<T>,
F: FnOnce(S) -> S2,
{
DoubletsStorage {
store: map(self.store),
path: self.path,
known_revision: self.known_revision,
lock: self.lock,
address: PhantomData,
}
}
pub fn with_automatic_uniqueness_and_usages_resolution(
self,
) -> DoubletsStorage<T, AutomaticUniquenessAndUsagesResolution<T, S>> {
self.map_store(DecoratorsExt::with_automatic_uniqueness_and_usages_resolution)
}
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
pub fn store(&self) -> &S {
&self.store
}
pub fn store_mut(&mut self) -> &mut S {
&mut self.store
}
pub fn into_store(self) -> S {
self.store
}
pub fn lock_shared(&self) -> Result<FileLock, LinkError> {
FileLock::acquire(self.require_lock_path()?, LockMode::Shared)
}
pub fn lock_exclusive(&self) -> Result<FileLock, LinkError> {
FileLock::acquire(self.require_lock_path()?, LockMode::Exclusive)
}
pub fn held_lock(&self) -> Option<&FileLock> {
self.lock.as_ref()
}
fn require_lock_path(&self) -> Result<PathBuf, LinkError> {
self.path.as_ref().map(lock_file_path).ok_or_else(|| {
LinkError::Lock("this doublets storage is not backed by a known file".to_string())
})
}
fn refresh_revision(&mut self) -> Result<(), LinkError> {
if let Some(path) = self.path.as_ref() {
self.known_revision = StorageRevision::of(path)?;
}
Ok(())
}
}
impl<T: LinkReference, S: Doublets<T>> LinksStorage<T> for DoubletsStorage<T, S> {
fn create_link(&mut self, source: T, target: T) -> Result<T, LinkError> {
Ok(Doublets::create_link(&mut self.store, source, target)?)
}
fn ensure_link_created(&mut self, index: T) -> Result<T, LinkError> {
if self.link_exists(index) {
return Ok(index);
}
loop {
let created = Doublets::create(&mut self.store)?;
match created.cmp(&index) {
std::cmp::Ordering::Equal => return Ok(index),
std::cmp::Ordering::Less => continue,
std::cmp::Ordering::Greater => {
return Err(LinkError::StorageError(format!(
"could not reserve link address {index}: the store allocated {created} instead"
)))
}
}
}
}
fn get_link(&self, index: T) -> Option<GenericLink<T>> {
Doublets::get_link(&self.store, index).map(GenericLink::from)
}
fn link_exists(&self, index: T) -> bool {
Doublets::get_link(&self.store, index).is_some()
}
fn update_link(&mut self, index: T, source: T, target: T) -> Result<GenericLink<T>, LinkError> {
let before = self
.get_link(index)
.ok_or_else(|| LinkError::not_found(index))?;
Doublets::update(&mut self.store, index, source, target)?;
Ok(before)
}
fn delete_link(&mut self, index: T) -> Result<GenericLink<T>, LinkError> {
let before = self
.get_link(index)
.ok_or_else(|| LinkError::not_found(index))?;
Doublets::delete(&mut self.store, index)?;
Ok(before)
}
fn all_links(&self) -> Vec<GenericLink<T>> {
let mut links = Vec::new();
Doublets::each(&self.store, |link| {
links.push(GenericLink::from(link));
Flow::Continue
});
links
}
fn query_links(
&self,
index: Option<T>,
source: Option<T>,
target: Option<T>,
) -> Vec<GenericLink<T>> {
let any = self.store.constants().any;
let query = [
index.unwrap_or(any),
source.unwrap_or(any),
target.unwrap_or(any),
];
let mut links = Vec::new();
Doublets::each_by(&self.store, query, |link| {
links.push(GenericLink::from(link));
Flow::Continue
});
links
}
fn search_link(&self, source: T, target: T) -> Option<T> {
Doublets::search(&self.store, source, target)
}
fn get_or_create_link(&mut self, source: T, target: T) -> Result<T, LinkError> {
Ok(Doublets::get_or_create(&mut self.store, source, target)?)
}
fn links_count(&self) -> usize {
TryInto::<usize>::try_into(Doublets::count(&self.store)).unwrap_or(usize::MAX)
}
fn flush(&mut self) -> Result<(), LinkError> {
if let Some(path) = self.path.clone() {
let file = std::fs::File::options().write(true).open(&path)?;
file.sync_all()?;
file.set_modified(std::time::SystemTime::now())?;
self.refresh_revision()?;
}
Ok(())
}
fn has_external_changes(&self) -> Result<bool, LinkError> {
match self.path.as_ref() {
Some(path) => Ok(StorageRevision::of(path)? != self.known_revision),
None => Ok(false),
}
}
fn reload(&mut self) -> Result<(), LinkError> {
self.refresh_revision()
}
}