use std::{
cmp::max,
ffi::OsString,
fs,
io::{self, Result, prelude::*},
path::PathBuf,
};
use crate::{hash::hash, uint32};
#[derive(Clone, Copy, Debug)]
struct HashPos {
hash: u32,
pos: u32,
}
impl HashPos {
fn pack(&self, buf: &mut [u8]) {
uint32::pack2(buf, self.hash, self.pos);
}
}
fn err_toobig<T>() -> Result<T> {
Err(io::Error::other("File too big"))
}
pub struct CDBMake {
entries: Vec<Vec<HashPos>>,
pos: u32,
file: io::BufWriter<fs::File>,
}
impl CDBMake {
pub fn new(file: fs::File) -> Result<CDBMake> {
let mut w = io::BufWriter::new(file);
let buf = [0; 2048];
w.seek(io::SeekFrom::Start(0))?;
w.write_all(&buf)?;
Ok(CDBMake {
entries: std::iter::repeat_n(vec![], 256).collect::<Vec<_>>(),
pos: 2048,
file: w,
})
}
fn pos_plus(&mut self, len: u32) -> Result<()> {
if self.pos.checked_add(len).is_none() {
err_toobig()
} else {
self.pos += len;
Ok(())
}
}
fn add_end(&mut self, keylen: u32, datalen: u32, hash: u32) -> Result<()> {
self.entries[(hash & 0xff) as usize].push(HashPos {
hash,
pos: self.pos,
});
self.pos_plus(8)?;
self.pos_plus(keylen)?;
self.pos_plus(datalen)?;
Ok(())
}
fn add_begin(&mut self, keylen: u32, datalen: u32) -> Result<()> {
let mut buf = [0; 8];
uint32::pack2(&mut buf[0..8], keylen, datalen);
self.file.write_all(&buf)?;
Ok(())
}
pub fn add(&mut self, key: &[u8], data: &[u8]) -> Result<()> {
if key.len() >= 0xffffffff || data.len() >= 0xffffffff {
return Err(io::Error::other("Key or data too big"));
}
self.add_begin(key.len() as u32, data.len() as u32)?;
self.file.write_all(key)?;
self.file.write_all(data)?;
self.add_end(key.len() as u32, data.len() as u32, hash(key))
}
pub fn set_permissions(&self, perm: fs::Permissions) -> Result<()> {
self.file.get_ref().set_permissions(perm)
}
pub fn finish(mut self) -> Result<()> {
let mut buf = [0; 8];
let maxsize = self.entries.iter().fold(1, |acc, e| max(acc, e.len() * 2));
let count = self.entries.iter().fold(0, |acc, e| acc + e.len());
if maxsize + count > (0xffffffff / 8) {
return err_toobig();
}
let mut table = vec![HashPos { hash: 0, pos: 0 }; maxsize];
let mut header = [0_u8; 2048];
for i in 0..256 {
let len = self.entries[i].len() * 2;
let j = i * 8;
uint32::pack2(&mut header[j..j + 8], self.pos, len as u32);
for e in self.entries[i].iter() {
let mut wh = (e.hash as usize >> 8) % len;
while table[wh].pos != 0 {
wh += 1;
if wh == len {
wh = 0;
}
}
table[wh] = *e;
}
for hp in table.iter_mut().take(len) {
hp.pack(&mut buf);
self.file.write_all(&buf)?;
self.pos_plus(8)?;
*hp = HashPos { hash: 0, pos: 0 };
}
}
self.file.flush()?;
self.file.seek(io::SeekFrom::Start(0))?;
self.file.write_all(&header)?;
self.file.flush()?;
Ok(())
}
}
pub struct CDBWriter {
dstname: PathBuf,
tmpname: PathBuf,
cdb: Option<CDBMake>,
}
impl CDBWriter {
pub fn create<P: Into<PathBuf>>(filename: P) -> Result<CDBWriter> {
CDBWriter::with_suffix(filename, ".tmp")
}
pub fn with_suffix<P: Into<PathBuf>>(filename: P, suffix: &str) -> Result<CDBWriter> {
let filename = filename.into();
let mut tmpname = filename.clone();
let new_extension = match tmpname.extension() {
Some(ext) => {
let mut ext = ext.to_os_string();
ext.push(suffix);
ext
}
None => OsString::from(suffix),
};
tmpname.set_extension(new_extension);
CDBWriter::with_filenames(filename, &tmpname)
}
pub fn with_filenames<P: Into<PathBuf>, Q: Into<PathBuf>>(
filename: P,
tmpname: Q,
) -> Result<CDBWriter> {
let dstname = filename.into();
let tmpname = tmpname.into();
let file = fs::File::create(&tmpname)?;
let cdb = CDBMake::new(file)?;
Ok(CDBWriter {
dstname,
tmpname,
cdb: Some(cdb),
})
}
pub fn add(&mut self, key: &[u8], data: &[u8]) -> Result<()> {
self.cdb.as_mut().unwrap().add(key, data)
}
pub fn set_permissions(&self, perm: fs::Permissions) -> Result<()> {
self.cdb.as_ref().unwrap().set_permissions(perm)
}
pub fn finish(mut self) -> Result<()> {
self.cdb.take().unwrap().finish()?;
fs::rename(&self.tmpname, &self.dstname)?;
Ok(())
}
}
impl Drop for CDBWriter {
#[allow(unused_must_use)]
fn drop(&mut self) {
if self.cdb.is_some() {
fs::remove_file(&self.tmpname);
}
}
}