use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use crate::db::{Database, DatabaseConfig};
use crate::tree::Hash;
use crate::wal::WalRecovery;
use super::{VacuumOptions, VacuumReport, vacuum_stats};
pub fn config_for(path: &Path, shard_count: usize) -> DatabaseConfig {
DatabaseConfig {
data_dir: path.to_path_buf(),
shard_count,
distributed: None,
}
}
pub fn build_store(
data_dir: &Path,
shard_count: usize,
keys: &[&[u8]],
versions: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let db = Database::create(config_for(data_dir, shard_count))?;
for version in 0..versions {
for key in keys {
db.append(
key.to_vec(),
vec![format!("v{version}").into_bytes()],
version as u64,
)?;
}
}
drop(db);
Ok(())
}
pub fn stats(data_dir: &Path) -> Result<VacuumReport, super::VacuumError> {
vacuum_stats(&VacuumOptions::new(data_dir.to_path_buf()))
}
pub fn expect_refusal(
result: Result<VacuumReport, super::VacuumError>,
) -> Result<super::VacuumError, Box<dyn std::error::Error>> {
match result {
Err(error) => Ok(error),
Ok(_report) => Err("expected a typed refusal, got a completed report".into()),
}
}
type EntryIdentity = (Option<Vec<u8>>, SystemTime);
pub fn snapshot_tree(root: &Path) -> Result<BTreeMap<PathBuf, EntryIdentity>, std::io::Error> {
let mut snapshot = BTreeMap::new();
walk_into(root, root, &mut snapshot)?;
Ok(snapshot)
}
fn walk_into(
root: &Path,
dir: &Path,
snapshot: &mut BTreeMap<PathBuf, EntryIdentity>,
) -> Result<(), std::io::Error> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let relative = path
.strip_prefix(root)
.unwrap_or(path.as_path())
.to_path_buf();
let metadata = fs::symlink_metadata(&path)?;
if metadata.is_dir() {
snapshot.insert(relative, (None, metadata.modified()?));
walk_into(root, &path, snapshot)?;
} else {
let bytes = fs::read(&path)?;
snapshot.insert(relative, (Some(bytes), metadata.modified()?));
}
}
Ok(())
}
pub fn committed_root(data_dir: &Path, shard_id: usize) -> Option<Hash> {
let wal_path = data_dir.join(format!("shard-{shard_id}")).join("shard.wal");
let mut recovery = WalRecovery::open(wal_path).ok()?;
recovery
.recover_unverified()
.ok()
.and_then(|recovered| recovered.committed_root())
}
pub fn store_dir(data_dir: &Path, shard_id: usize) -> PathBuf {
data_dir.join(format!("shard-{shard_id}")).join("store")
}
pub fn node_path(store: &Path, hash: Hash) -> PathBuf {
let hex = hash.to_string();
let (prefix, name) = hex.split_at(2);
store.join(prefix).join(name)
}