use std::fs;
use std::path::{Path, PathBuf};
use crate::config::Config;
use crate::core::map::MemoryMap;
use crate::error::{Result, StoreError};
use crate::store::locking::StoreLock;
use crate::store::migration::STORE_FORMAT_VERSION;
use crate::store::persist;
use crate::types::{Entry, Key, KeyPrefix, Namespace, ProjectName, SetRequest, StorePath, Value};
#[derive(Debug)]
pub struct Store {
config: Config,
map: MemoryMap,
lock: Option<StoreLock>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoreInfo {
pub format_version: u32,
pub config_version: u32,
pub project_name: ProjectName,
pub path: StorePath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StoreStats {
pub entry_count: usize,
pub is_empty: bool,
pub locked: bool,
}
impl Store {
pub fn new(config: Config) -> Result<Self> {
config.validate()?;
Ok(Self {
config,
map: MemoryMap::new(),
lock: None,
})
}
pub fn open(config: Config) -> Result<Self> {
config.validate()?;
let path = config.store_path().as_path();
if path.exists() {
let map = persist::load_map(path)?;
Ok(Self {
config,
map,
lock: None,
})
} else {
Self::new(config)
}
}
pub fn open_locked(config: Config) -> Result<Self> {
let mut store = Self::open(config)?;
store.acquire_lock()?;
Ok(store)
}
pub fn open_project(project_name: ProjectName, project_root: impl AsRef<Path>) -> Result<Self> {
let config = Config::for_project_root(project_name, project_root)?;
Self::open(config)
}
#[must_use]
pub const fn config(&self) -> &Config {
&self.config
}
#[must_use]
pub fn info(&self) -> StoreInfo {
StoreInfo {
format_version: STORE_FORMAT_VERSION,
config_version: self.config.version(),
project_name: self.config.project_name().clone(),
path: self.config.store_path().clone(),
}
}
#[must_use]
pub fn stats(&self) -> StoreStats {
StoreStats {
entry_count: self.map.len(),
is_empty: self.map.is_empty(),
locked: self.lock.is_some(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.map.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[must_use]
pub fn contains(&self, key: &Key) -> bool {
self.map.contains_key(key)
}
#[must_use]
pub fn get(&self, key: &Key) -> Option<&Value> {
self.map.get(key)
}
pub fn require(&self, key: &Key) -> Result<&Value> {
self.map.require(key)
}
pub fn set(&mut self, key: Key, value: Value) -> Result<Option<Value>> {
self.map.insert(key, value)
}
pub fn apply(&mut self, request: SetRequest) -> Result<Option<Value>> {
self.map.set(request)
}
pub fn delete(&mut self, key: &Key) -> Option<Value> {
self.map.remove(key)
}
pub fn delete_required(&mut self, key: &Key) -> Result<Value> {
self.map.remove_required(key)
}
pub fn clear(&mut self) {
self.map.clear();
}
#[must_use]
pub fn entries(&self) -> Vec<Entry> {
self.map.entries()
}
#[must_use]
pub fn list_namespace(&self, namespace: &Namespace) -> Vec<Entry> {
let prefix = KeyPrefix::new(namespace.as_str())
.expect("validated namespace must always form a valid prefix");
self.map.entries_with_prefix(&prefix)
}
#[must_use]
pub fn list_prefix(&self, prefix: &KeyPrefix) -> Vec<Entry> {
self.map.entries_with_prefix(prefix)
}
pub fn delete_namespace(&mut self, namespace: &Namespace) -> usize {
let prefix = KeyPrefix::new(namespace.as_str())
.expect("validated namespace must always form a valid prefix");
self.map.remove_prefix(&prefix)
}
pub fn prepare_path(&self) -> Result<()> {
let path = self.config.store_path().as_path();
let parent = path
.parent()
.ok_or_else(|| StoreError::malformed("store path must contain a parent directory"))?;
fs::create_dir_all(parent).map_err(|source| {
StoreError::PreparePath {
path: parent.to_path_buf(),
source,
}
.into()
})
}
pub fn flush(&self) -> Result<()> {
self.prepare_path()?;
let path = self.config.store_path().as_path();
persist::save_map(path, &self.map)
}
pub fn reload(&mut self) -> Result<()> {
let path = self.config.store_path().as_path();
if !path.exists() {
self.map.clear();
return Ok(());
}
self.map = persist::load_map(path)?;
Ok(())
}
pub fn acquire_lock(&mut self) -> Result<()> {
if self.lock.is_some() {
return Ok(());
}
let guard = StoreLock::acquire(self.config.store_path().as_path())?;
self.lock = Some(guard);
Ok(())
}
pub fn release_lock(&mut self) -> Result<()> {
if let Some(lock) = self.lock.take() {
lock.release()?;
}
Ok(())
}
#[must_use]
pub fn is_locked(&self) -> bool {
self.lock.is_some()
}
#[must_use]
pub fn into_config(self) -> Config {
self.config.clone()
}
#[must_use]
pub fn path(&self) -> &Path {
self.config.store_path().as_path()
}
#[must_use]
pub fn path_buf(&self) -> PathBuf {
self.path().to_path_buf()
}
}
impl Drop for Store {
fn drop(&mut self) {
if let Some(lock) = self.lock.take() {
let _ = lock.release();
}
}
}