use crate::{
CompressionMethod, Timestamp,
compression::Compressor,
utils::{Counter, Crc32Writer},
};
use std::{fmt, io};
mod raw;
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct FileOptions {
pub compression_method: CompressionMethod,
pub level: Option<i32>,
pub modified_at: Timestamp,
}
#[derive(Debug, Default)]
pub struct ArchiveWriter<W: io::Write> {
writer: W,
raw: raw::RawArchiveWriter,
}
impl ArchiveWriter<std::fs::File> {
pub fn create(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
std::fs::File::create(path).map(Self::new)
}
pub fn create_new(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
std::fs::File::create_new(path).map(Self::new)
}
}
impl<W: io::Write> ArchiveWriter<W> {
#[inline]
pub fn new(writer: W) -> Self {
ArchiveWriter {
writer,
raw: raw::RawArchiveWriter::default(),
}
}
pub fn add_file<R: io::Read>(
&mut self,
name: &str,
mut content: R,
options: &FileOptions,
) -> io::Result<()> {
let mut w = Crc32Writer::new(Compressor::new(
Vec::new(),
options.compression_method,
options.level,
)?);
let uncompressed_size = io::copy(&mut content, &mut w)?;
let crc32 = w.result();
let compressed = w.into_inner().finish()?;
self.raw.write_file_raw(
&mut self.writer,
name,
&compressed,
&raw::Metadata {
compression_method: options.compression_method,
compressed_size: compressed.len() as _,
uncompressed_size,
crc32,
typ: crate::FileType::File,
modified_at: options.modified_at,
},
)?;
Ok(())
}
pub fn stream_file(
&mut self,
name: &str,
options: &FileOptions,
) -> io::Result<FileStreamer<'_, W>> {
let writer = self.raw.start_stream_raw(&mut self.writer, name, options)?;
Ok(FileStreamer {
writer: Counter::new(Crc32Writer::new(Compressor::new(
writer,
options.compression_method,
options.level,
)?)),
})
}
pub fn add_directory(&mut self, name: &str) -> io::Result<()> {
if !name.ends_with('/') {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"directory name must end with '/'",
));
}
self.raw.write_file_raw(
&mut self.writer,
name,
&[],
&raw::Metadata {
compression_method: CompressionMethod::STORE,
compressed_size: 0,
uncompressed_size: 0,
crc32: 0,
typ: crate::FileType::Directory,
modified_at: Timestamp::UNIX_EPOCH,
},
)
}
pub fn add_symlink(&mut self, name: &str, target: &str) -> io::Result<()> {
self.raw.write_file_raw(
&mut self.writer,
name,
target.as_bytes(),
&raw::Metadata {
compression_method: CompressionMethod::STORE,
compressed_size: target.len() as _,
uncompressed_size: target.len() as _,
crc32: crc32fast::hash(target.as_bytes()),
typ: crate::FileType::Symlink,
modified_at: Timestamp::UNIX_EPOCH,
},
)
}
#[inline]
pub fn recover(&mut self) -> io::Result<()>
where
W: io::Seek,
{
self.raw.recover(&mut self.writer)
}
#[inline]
pub fn get_ref(&self) -> &W {
&self.writer
}
#[inline]
pub fn get_mut(&mut self) -> &mut W {
&mut self.writer
}
#[inline]
pub fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
#[inline]
pub fn finish(mut self) -> io::Result<W> {
self.raw.finish(&mut self.writer)?;
Ok(self.writer)
}
}
pub struct FileStreamer<'a, W: io::Write> {
writer: Counter<Crc32Writer<Compressor<raw::RawFileStreamer<'a, &'a mut W>>>>,
}
impl<W: io::Write> io::Write for FileStreamer<'_, W> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.writer.write(buf)
}
#[inline]
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.writer.write_vectored(bufs)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
impl<W: io::Write> FileStreamer<'_, W> {
pub fn finish(self) -> io::Result<()> {
let uncompressed_size = self.writer.amt;
let crc32 = self.writer.inner.result();
let raw_writer = self.writer.inner.into_inner().finish()?;
raw_writer.finish(uncompressed_size, crc32)
}
}
impl<'a, W: io::Write + fmt::Debug> fmt::Debug for FileStreamer<'a, W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FileStreamer { .. }")
}
}