mod append_only;
mod chunk;
pub(super) mod error;
mod immutable;
mod login_packet;
mod mutable;
#[cfg(test)]
mod tests;
mod used_space;
use crate::{utils, vault::Init};
use chunk::{Chunk, ChunkId};
use error::{Error, Result};
use hex;
use log::trace;
use safe_nd::{AData, IData, LoginPacket, MData};
use std::{
cell::Cell,
fs::{self, DirEntry, File, Metadata},
io::{Read, Write},
marker::PhantomData,
path::{Path, PathBuf},
rc::Rc,
};
use used_space::UsedSpace;
const CHUNK_STORE_DIR: &str = "chunks";
const MAX_CHUNK_FILE_NAME_LENGTH: usize = 104;
pub(crate) type ImmutableChunkStore = ChunkStore<IData>;
pub(crate) type MutableChunkStore = ChunkStore<MData>;
pub(crate) type AppendOnlyChunkStore = ChunkStore<AData>;
pub(crate) type LoginPacketChunkStore = ChunkStore<LoginPacket>;
pub(crate) struct ChunkStore<T: Chunk> {
dir: PathBuf,
max_capacity: u64,
used_space: UsedSpace,
_phantom: PhantomData<T>,
}
impl<T> ChunkStore<T>
where
T: Chunk,
Self: Subdir,
{
pub fn new<P: AsRef<Path>>(
root: P,
max_capacity: u64,
total_used_space: Rc<Cell<u64>>,
init_mode: Init,
) -> Result<Self> {
let dir = root.as_ref().join(CHUNK_STORE_DIR).join(Self::subdir());
match init_mode {
Init::New => Self::create_new_root(&dir)?,
Init::Load => trace!("Loading ChunkStore at {}", dir.display()),
}
let used_space = UsedSpace::new(&dir, total_used_space, init_mode)?;
Ok(ChunkStore {
dir,
max_capacity,
used_space,
_phantom: PhantomData,
})
}
}
impl<T: Chunk> ChunkStore<T> {
fn create_new_root(root: &Path) -> Result<()> {
trace!("Creating ChunkStore at {}", root.display());
fs::create_dir_all(root)?;
let temp_file_path = root.join("0".repeat(MAX_CHUNK_FILE_NAME_LENGTH));
let _ = File::create(&temp_file_path)?;
fs::remove_file(temp_file_path)?;
Ok(())
}
pub fn put(&mut self, chunk: &T) -> Result<()> {
let serialised_chunk = utils::serialise(chunk);
let consumed_space = serialised_chunk.len() as u64;
if self.used_space.total().saturating_add(consumed_space) > self.max_capacity {
return Err(Error::NotEnoughSpace);
}
let file_path = self.file_path(chunk.id())?;
let _ = self.do_delete(&file_path);
let mut file = File::create(&file_path)?;
file.write_all(&serialised_chunk)?;
file.sync_data()?;
self.used_space.increase(consumed_space)
}
pub fn delete(&mut self, id: &T::Id) -> Result<()> {
self.do_delete(&self.file_path(id)?)
}
pub fn get(&self, id: &T::Id) -> Result<T> {
let mut file = File::open(self.file_path(id)?).map_err(|_| Error::NoSuchChunk)?;
let mut contents = vec![];
let _ = file.read_to_end(&mut contents)?;
let chunk = bincode::deserialize::<T>(&contents)?;
if chunk.id() == id {
Ok(chunk)
} else {
Err(Error::NoSuchChunk)
}
}
pub fn has(&self, id: &T::Id) -> bool {
if let Ok(path) = self.file_path(id) {
fs::metadata(path)
.as_ref()
.map(Metadata::is_file)
.unwrap_or(false)
} else {
false
}
}
#[cfg_attr(not(test), allow(unused))]
pub fn keys(&self) -> Vec<T::Id> {
fs::read_dir(&self.dir)
.map(|entries| {
entries
.filter_map(|entry| to_chunk_id(entry.ok()?))
.collect()
})
.unwrap_or_else(|_| Vec::new())
}
fn do_delete(&mut self, file_path: &Path) -> Result<()> {
if let Ok(metadata) = fs::metadata(file_path) {
self.used_space.decrease(metadata.len())?;
fs::remove_file(file_path).map_err(From::from)
} else {
Ok(())
}
}
fn file_path(&self, id: &T::Id) -> Result<PathBuf> {
Ok(self.dir.join(&hex::encode(utils::serialise(id))))
}
}
pub(crate) trait Subdir {
fn subdir() -> &'static Path;
}
impl Subdir for ImmutableChunkStore {
fn subdir() -> &'static Path {
Path::new("immutable")
}
}
impl Subdir for MutableChunkStore {
fn subdir() -> &'static Path {
Path::new("mutable")
}
}
impl Subdir for AppendOnlyChunkStore {
fn subdir() -> &'static Path {
Path::new("append_only")
}
}
impl Subdir for LoginPacketChunkStore {
fn subdir() -> &'static Path {
Path::new("login_packets")
}
}
fn to_chunk_id<T: ChunkId>(entry: DirEntry) -> Option<T> {
let file_name = entry.file_name();
let file_name = file_name.into_string().ok()?;
let bytes = hex::decode(file_name).ok()?;
bincode::deserialize(&bytes).ok()
}