use crate::digest::ValueDigest;
use crate::node::ProllyNode;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use super::{NodeStorage, StorageError};
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
fn unique_suffix() -> String {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let n = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{}-{}-{}", std::process::id(), nanos, n)
}
#[derive(Clone, Debug)]
pub struct FileNodeStorage<const N: usize> {
storage_dir: PathBuf,
}
impl<const N: usize> FileNodeStorage<N> {
pub fn new(storage_dir: PathBuf) -> Result<Self, StorageError> {
fs::create_dir_all(&storage_dir)?;
Ok(FileNodeStorage { storage_dir })
}
fn node_path(&self, hash: &ValueDigest<N>) -> PathBuf {
self.storage_dir.join(format!("{hash:x}"))
}
fn config_path(&self, key: &str) -> PathBuf {
self.storage_dir.join(format!("config_{key}"))
}
fn blobs_dir(&self) -> PathBuf {
self.storage_dir.join("blobs")
}
fn blob_path(&self, hash: &ValueDigest<N>) -> PathBuf {
self.blobs_dir().join(format!("{hash:x}"))
}
}
impl<const N: usize> NodeStorage<N> for FileNodeStorage<N> {
fn get_node_by_hash(&self, hash: &ValueDigest<N>) -> Option<Arc<ProllyNode<N>>> {
let path = self.node_path(hash);
if path.exists() {
let mut file = File::open(path).ok()?;
let mut data = Vec::new();
file.read_to_end(&mut data).ok()?;
let node: ProllyNode<N> = bincode::deserialize(&data).ok()?;
Some(Arc::new(node))
} else {
None
}
}
fn insert_node(
&mut self,
hash: ValueDigest<N>,
node: ProllyNode<N>,
) -> Result<(), StorageError> {
let path = self.node_path(&hash);
let data = bincode::serialize(&node)?;
let mut file = File::create(path)?;
file.write_all(&data)?;
Ok(())
}
fn delete_node(&mut self, hash: &ValueDigest<N>) -> Result<(), StorageError> {
let path = self.node_path(hash);
if path.exists() {
fs::remove_file(path)?;
}
Ok(())
}
fn save_config(&self, key: &str, config: &[u8]) {
let path = self.config_path(key);
if let Ok(mut file) = File::create(path) {
let _ = file.write_all(config);
}
}
fn get_config(&self, key: &str) -> Option<Vec<u8>> {
let path = self.config_path(key);
if path.exists() {
let mut file = File::open(path).ok()?;
let mut data = Vec::new();
file.read_to_end(&mut data).ok()?;
Some(data)
} else {
None
}
}
fn insert_blob(&mut self, hash: ValueDigest<N>, bytes: &[u8]) -> Result<(), StorageError> {
let blobs_dir = self.blobs_dir();
fs::create_dir_all(&blobs_dir)?;
let path = self.blob_path(&hash);
if path.exists() {
return Ok(());
}
let tmp_path = blobs_dir.join(format!("{:x}.partial.{}", hash, unique_suffix()));
let mut file = File::create(&tmp_path)?;
file.write_all(bytes)?;
file.sync_all()?;
drop(file);
match fs::rename(&tmp_path, &path) {
Ok(()) => Ok(()),
Err(e) => {
if path.exists() {
let _ = fs::remove_file(&tmp_path);
Ok(())
} else {
let _ = fs::remove_file(&tmp_path);
Err(StorageError::Io(e))
}
}
}
}
fn get_blob(&self, hash: &ValueDigest<N>) -> Option<Vec<u8>> {
let path = self.blob_path(hash);
if !path.exists() {
return None;
}
let mut file = File::open(path).ok()?;
let mut data = Vec::new();
file.read_to_end(&mut data).ok()?;
Some(data)
}
fn delete_blob(&mut self, hash: &ValueDigest<N>) -> Result<(), StorageError> {
let path = self.blob_path(hash);
if path.exists() {
fs::remove_file(path)?;
}
Ok(())
}
fn list_blobs(&self) -> Result<Vec<ValueDigest<N>>, StorageError> {
let blobs_dir = self.blobs_dir();
if !blobs_dir.exists() {
return Ok(Vec::new());
}
let mut out = Vec::new();
for entry in fs::read_dir(blobs_dir)? {
let entry = entry?;
let name = entry.file_name();
let Some(name_str) = name.to_str() else {
continue;
};
if name_str.ends_with(".partial") {
continue;
}
if name_str.len() != N * 2 {
continue;
}
let mut arr = [0u8; N];
let mut ok = true;
for i in 0..N {
let byte_str = &name_str[i * 2..i * 2 + 2];
match u8::from_str_radix(byte_str, 16) {
Ok(b) => arr[i] = b,
Err(_) => {
ok = false;
break;
}
}
}
if ok {
out.push(ValueDigest(arr));
}
}
Ok(out)
}
}