use std::{
fs::{File, OpenOptions},
io::{self, BufReader, BufWriter, Seek, Write},
path::Path,
};
use crate::{
common::{CRCTree, FileMetaPair},
crc::CRC,
};
use super::{
file::{PKEntryData, PKTrailer, MAGIC_SEP, MAGIC_START},
reader::PackFile,
writer::{write_pk_directory_tree, write_pk_trailer},
};
pub struct PKHandle {
file: File,
trailer: PKTrailer,
directory: CRCTree<PKEntryData>,
}
pub trait PKWriter {
fn write<W: Write>(&mut self, writer: &mut W) -> io::Result<()>;
}
impl PKHandle {
pub fn open(path: &Path) -> io::Result<PKHandle> {
let mut file = OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.read(true)
.open(path)?;
let meta = file.metadata()?;
let new = meta.len() == 0;
let (directory, trailer) = if new {
file.write_all(&MAGIC_START)?;
let file_list_base_addr = MAGIC_START.len() as u32;
(
CRCTree::new(),
PKTrailer {
file_list_base_addr,
num_compressed: 0,
},
)
} else {
let buf = BufReader::new(&mut file);
let mut pk = PackFile::open(buf);
pk.check_magic()?;
let trailer = pk.get_header()?;
let mut acc = pk.get_entry_accessor(trailer.file_list_base_addr)?;
(acc.read_all()?, trailer)
};
Ok(PKHandle {
file,
directory,
trailer,
})
}
pub fn put_file<W: PKWriter>(
&mut self,
crc: CRC,
writer: &mut W,
meta: FileMetaPair,
is_compressed: bool,
) -> io::Result<()> {
let mut buf = BufWriter::new(&mut self.file);
let start = buf.stream_position()?;
assert!(start <= u32::MAX.into());
writer.write(&mut buf)?;
buf.write_all(&MAGIC_SEP)?;
let end = buf.stream_position()?;
assert!(end <= u32::MAX.into());
let is_compressed = u32::from(is_compressed);
self.directory.insert(
crc,
PKEntryData {
meta,
file_data_addr: start as u32,
is_compressed,
},
);
self.trailer.file_list_base_addr = end as u32;
self.trailer.num_compressed += is_compressed;
Ok(())
}
pub fn finish(&mut self) -> io::Result<()> {
let mut buf = BufWriter::new(&mut self.file);
write_pk_directory_tree(&mut buf, &self.directory)?;
write_pk_trailer(&mut buf, &self.trailer)?;
Ok(())
}
}