use std::{
fs::{self, File},
io::{Read, Write},
path::{Path, PathBuf},
};
use crate::{
CheckpointGeneration, CommitSeq, DbError, TransactionId, freeze, state::DatabaseState,
};
const STORE_FILE: &str = "store.oxgdb";
const TEMP_STORE_FILE: &str = "store.oxgdb.tmp";
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct StoredDatabase {
pub(crate) commit_seq: CommitSeq,
pub(crate) transaction_id: TransactionId,
pub(crate) generation: CheckpointGeneration,
pub(crate) state: DatabaseState,
}
impl StoredDatabase {
#[must_use]
pub(crate) const fn empty() -> Self {
Self {
commit_seq: CommitSeq::new(0),
transaction_id: TransactionId::new(0),
generation: CheckpointGeneration::new(0),
state: DatabaseState::empty(),
}
}
}
#[must_use]
pub(crate) fn store_path(root: &Path) -> PathBuf {
root.join(STORE_FILE)
}
pub(crate) fn write_store(root: &Path, stored: &StoredDatabase) -> Result<(), DbError> {
fs::create_dir_all(root).map_err(|error| DbError::io("create database directory", error))?;
let bytes = freeze::freeze(stored)?;
let temp_path = root.join(TEMP_STORE_FILE);
let mut file = File::create(&temp_path).map_err(|error| DbError::io("create store", error))?;
file.write_all(&bytes)
.map_err(|error| DbError::io("write store payload", error))?;
file.flush()
.map_err(|error| DbError::io("flush store", error))?;
file.sync_all()
.map_err(|error| DbError::io("sync store", error))?;
fs::rename(temp_path, store_path(root)).map_err(|error| DbError::io("publish store", error))?;
sync_directory(root)?;
Ok(())
}
pub(crate) fn read_store(root: &Path) -> Result<StoredDatabase, DbError> {
let mut file = File::open(store_path(root)).map_err(|error| match error.kind() {
std::io::ErrorKind::NotFound => DbError::NotFound,
_kind => DbError::io("open store", error),
})?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)
.map_err(|error| DbError::io("read store", error))?;
freeze::open(&bytes)
}
pub(crate) fn validate_store(root: &Path) -> Result<(), DbError> {
read_store(root).map(|_stored| ())
}
#[cfg(unix)]
fn sync_directory(path: &Path) -> Result<(), DbError> {
let directory =
File::open(path).map_err(|error| DbError::io("open database directory", error))?;
directory
.sync_all()
.map_err(|error| DbError::io("sync database directory", error))
}
#[cfg(not(unix))]
fn sync_directory(_path: &Path) -> Result<(), DbError> {
Ok(())
}