use crate::error::{CascError, Result};
use crate::types::EKey;
use std::collections::HashMap;
use std::io::{Read, Seek};
use std::path::PathBuf;
use tracing::{debug, info};
pub struct LooseFileStorage {
files: HashMap<EKey, PathBuf>,
base_path: PathBuf,
}
impl LooseFileStorage {
fn parse_hex_filename(filename: &str) -> Option<EKey> {
if filename.len() != 32 {
return None;
}
let mut bytes = [0u8; 16];
for i in 0..16 {
let hex_pair = &filename[i * 2..i * 2 + 2];
bytes[i] = u8::from_str_radix(hex_pair, 16).ok()?;
}
EKey::from_slice(&bytes)
}
pub fn new(base_path: PathBuf) -> Result<Self> {
std::fs::create_dir_all(&base_path)?;
Ok(Self {
files: HashMap::new(),
base_path,
})
}
pub fn scan(&mut self) -> Result<()> {
info!("Scanning for loose files in {:?}", self.base_path);
self.files.clear();
for entry in std::fs::read_dir(&self.base_path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
if filename.len() == 32 {
if let Some(ekey) = Self::parse_hex_filename(filename) {
debug!("Found loose file: {}", ekey);
self.files.insert(ekey, path);
}
}
}
}
}
info!("Found {} loose files", self.files.len());
Ok(())
}
pub fn read(&self, ekey: &EKey) -> Result<Vec<u8>> {
let path = self
.files
.get(ekey)
.ok_or_else(|| CascError::EntryNotFound(ekey.to_string()))?;
debug!("Reading loose file {} from {:?}", ekey, path);
let mut file = std::fs::File::open(path)?;
let mut magic = [0u8; 4];
file.read_exact(&mut magic)?;
if magic == blte::BLTE_MAGIC {
debug!(
"Loose file {} is BLTE compressed, using streaming decompression",
ekey
);
file.seek(std::io::SeekFrom::Start(0))?;
let mut stream = blte::create_streaming_reader(file, None)
.map_err(|e| CascError::DecompressionError(e.to_string()))?;
let mut result = Vec::new();
stream
.read_to_end(&mut result)
.map_err(|e| CascError::DecompressionError(e.to_string()))?;
Ok(result)
} else {
debug!("Loose file {} is uncompressed", ekey);
file.seek(std::io::SeekFrom::Start(0))?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(data)
}
}
pub fn write(&mut self, ekey: &EKey, data: &[u8], compress: bool) -> Result<()> {
let filename = format!("{ekey}");
let path = self.base_path.join(&filename);
debug!("Writing loose file {} to {:?}", ekey, path);
let output = if compress {
blte::compress_data_single(data.to_vec(), blte::CompressionMode::ZLib, None)?
} else {
data.to_vec()
};
std::fs::write(&path, output)?;
self.files.insert(*ekey, path);
Ok(())
}
pub fn remove(&mut self, ekey: &EKey) -> Result<()> {
if let Some(path) = self.files.remove(ekey) {
debug!("Removing loose file {} at {:?}", ekey, path);
std::fs::remove_file(path)?;
}
Ok(())
}
pub fn contains(&self, ekey: &EKey) -> bool {
self.files.contains_key(ekey)
}
pub fn len(&self) -> usize {
self.files.len()
}
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&EKey, &PathBuf)> {
self.files.iter()
}
pub fn total_size(&self) -> Result<u64> {
let mut total = 0u64;
for path in self.files.values() {
total += std::fs::metadata(path)?.len();
}
Ok(total)
}
}