use std::fs::File;
use std::io::{BufReader, Read, Seek, Write};
use std::path::Path;
use crate::{ArchivePath, Error, Result};
use super::options::EntryMeta;
use super::{PendingEntry, Writer};
const HEAD_CHUNK: usize = 256 * 1024;
fn reserve_within(buffer: &mut Vec<u8>, want: usize, ceiling: usize) {
let needed = buffer.len().saturating_add(want);
if buffer.capacity() >= needed {
return;
}
let target = buffer.capacity().saturating_mul(2).clamp(needed, ceiling);
buffer.reserve_exact(target - buffer.len());
}
enum EntryBytes {
Whole(Vec<u8>),
Streaming(Vec<u8>),
}
impl<W: Write + Seek + Send> Writer<W> {
fn read_entry_head(&self, source: &mut dyn Read) -> Result<EntryBytes> {
let limit = if super::streaming_entry::can_stream(&self.options) {
usize::try_from(super::streaming_entry::STREAMING_THRESHOLD).unwrap_or(usize::MAX)
} else {
usize::MAX
};
let mut head = Vec::new();
while head.len() < limit {
let filled = head.len();
let want = (limit - filled).min(HEAD_CHUNK);
reserve_within(&mut head, want, limit);
head.resize(filled + want, 0);
let read = super::streaming_entry::read_some(source, &mut head[filled..])?;
head.truncate(filled + read);
if read < want {
return Ok(EntryBytes::Whole(head));
}
}
Ok(EntryBytes::Streaming(head))
}
fn check_order(&self, archive_path: &ArchivePath) -> Result<()> {
if !self.options.deterministic {
return Ok(());
}
let path = archive_path.as_str();
if let Some(previous) = &self.last_path {
if path < previous.as_str() {
return Err(Error::InvalidArchivePath(format!(
"deterministic mode requires entries in sorted order, \
but '{path}' was added after '{previous}'"
)));
}
}
Ok(())
}
fn record_order(&mut self, archive_path: &ArchivePath) {
match &mut self.last_path {
Some(last) => {
last.clear();
last.push_str(archive_path.as_str());
}
None => self.last_path = Some(archive_path.as_str().to_string()),
}
}
pub fn add_path(
&mut self,
disk_path: impl AsRef<Path>,
archive_path: ArchivePath,
) -> Result<()> {
self.ensure_accepting_entries()?;
self.check_order(&archive_path)?;
let disk_path = disk_path.as_ref();
let meta = EntryMeta::from_path(disk_path)?;
if meta.is_directory {
return self.add_directory(archive_path, meta);
}
let file = File::open(disk_path).map_err(Error::Io)?;
let mut reader = BufReader::new(file);
self.add_stream(archive_path, &mut reader, meta)
}
pub fn add_directory(&mut self, archive_path: ArchivePath, meta: EntryMeta) -> Result<()> {
self.ensure_accepting_entries()?;
self.settle_stale_buffers()?;
#[cfg(feature = "aes")]
{
let options = self.options.clone();
self.check_password(&options)?;
}
self.check_order(&archive_path)?;
let recorded = archive_path.clone();
let entry = PendingEntry {
path: archive_path,
meta: EntryMeta {
is_directory: true,
..meta
},
uncompressed_size: 0,
};
self.flush_buffered_entries()?;
self.record_entry(entry);
self.record_order(&recorded);
Ok(())
}
pub fn add_anti_item(&mut self, archive_path: ArchivePath) -> Result<()> {
self.ensure_accepting_entries()?;
self.settle_stale_buffers()?;
#[cfg(feature = "aes")]
{
let options = self.options.clone();
self.check_password(&options)?;
}
self.check_order(&archive_path)?;
let recorded = archive_path.clone();
let entry = PendingEntry {
path: archive_path,
meta: EntryMeta::anti_item(),
uncompressed_size: 0,
};
self.flush_buffered_entries()?;
self.record_entry(entry);
self.record_order(&recorded);
Ok(())
}
pub fn add_anti_directory(&mut self, archive_path: ArchivePath) -> Result<()> {
self.ensure_accepting_entries()?;
self.settle_stale_buffers()?;
#[cfg(feature = "aes")]
{
let options = self.options.clone();
self.check_password(&options)?;
}
self.check_order(&archive_path)?;
let recorded = archive_path.clone();
let entry = PendingEntry {
path: archive_path,
meta: EntryMeta::anti_directory(),
uncompressed_size: 0,
};
self.flush_buffered_entries()?;
self.record_entry(entry);
self.record_order(&recorded);
Ok(())
}
pub fn add_stream(
&mut self,
archive_path: ArchivePath,
source: &mut dyn Read,
meta: EntryMeta,
) -> Result<()> {
self.ensure_accepting_entries()?;
self.settle_stale_buffers()?;
#[cfg(feature = "aes")]
{
let options = self.options.clone();
self.check_password(&options)?;
}
self.check_order(&archive_path)?;
let recorded = archive_path.clone();
let added = match self.read_entry_head(source)? {
EntryBytes::Streaming(prefix) => {
self.compress_entry_streaming(archive_path, prefix, source, meta)
}
EntryBytes::Whole(data) if self.options.solid.is_solid() => {
self.buffer_entry_solid(archive_path, data, meta)
}
EntryBytes::Whole(data) => self.compress_entry_non_solid(archive_path, data, meta),
};
if added.is_ok() {
self.record_order(&recorded);
}
added
}
pub fn add_bytes(&mut self, archive_path: ArchivePath, data: &[u8]) -> Result<()> {
let meta = EntryMeta::file(data.len() as u64);
let mut cursor = std::io::Cursor::new(data);
self.add_stream(archive_path, &mut cursor, meta)
}
}