pub(crate) mod compressed_writer;
pub(crate) mod entry_stream;
pub(crate) mod entry_whole;
pub(crate) mod io;
pub use entry_stream::EntryStreamWriter;
use futures::{AsyncWrite, AsyncWriteExt};
use crate::entry::ZipEntry;
use crate::error::Result;
use crate::spec::extra_field::ExtraFieldAsBytes;
use crate::spec::header::{
CentralDirectoryRecord, EndOfCentralDirectoryHeader, Zip64EndOfCentralDirectoryLocator,
Zip64EndOfCentralDirectoryRecord,
};
use entry_whole::EntryWholeWriter;
use io::offset::AsyncOffsetWriter;
use crate::spec::consts::{NON_ZIP64_MAX_NUM_FILES, NON_ZIP64_MAX_SIZE};
pub(crate) struct CentralDirectoryEntry {
pub header: CentralDirectoryRecord,
pub entry: ZipEntry,
}
pub struct ZipFileWriter<W: AsyncWrite + Unpin> {
pub(crate) writer: AsyncOffsetWriter<W>,
pub(crate) cd_entries: Vec<CentralDirectoryEntry>,
force_no_zip64: bool,
pub(crate) is_zip64: bool,
comment_opt: Option<String>,
}
impl<W: AsyncWrite + Unpin> ZipFileWriter<W> {
pub fn new(writer: W) -> Self {
Self {
writer: AsyncOffsetWriter::new(writer),
cd_entries: Vec::new(),
comment_opt: None,
is_zip64: false,
force_no_zip64: false,
}
}
pub fn force_no_zip64(mut self) -> Self {
self.force_no_zip64 = true;
self
}
pub fn force_zip64(mut self) -> Self {
self.is_zip64 = true;
self
}
pub async fn write_entry_whole<E: Into<ZipEntry>>(&mut self, entry: E, data: &[u8]) -> Result<()> {
EntryWholeWriter::from_raw(self, entry.into(), data).write().await
}
pub async fn write_entry_stream<E: Into<ZipEntry>>(&mut self, entry: E) -> Result<EntryStreamWriter<'_, W>> {
EntryStreamWriter::from_raw(self, entry.into()).await
}
pub fn comment(&mut self, comment: String) {
self.comment_opt = Some(comment);
}
pub fn inner_mut(&mut self) -> &mut W {
self.writer.inner_mut()
}
pub async fn close(mut self) -> Result<W> {
let cd_offset = self.writer.offset();
for entry in &self.cd_entries {
self.writer.write_all(&crate::spec::consts::CDH_SIGNATURE.to_le_bytes()).await?;
self.writer.write_all(&entry.header.as_slice()).await?;
self.writer.write_all(entry.entry.filename().as_bytes()).await?;
self.writer.write_all(&entry.entry.extra_fields().as_bytes()).await?;
self.writer.write_all(entry.entry.comment().as_bytes()).await?;
}
let central_directory_size = (self.writer.offset() - cd_offset) as u64;
let central_directory_size_u32 = if central_directory_size > NON_ZIP64_MAX_SIZE as u64 {
NON_ZIP64_MAX_SIZE
} else {
central_directory_size as u32
};
let num_entries_in_directory = self.cd_entries.len() as u64;
let num_entries_in_directory_u16 = if num_entries_in_directory > NON_ZIP64_MAX_NUM_FILES as u64 {
NON_ZIP64_MAX_NUM_FILES
} else {
num_entries_in_directory as u16
};
let cd_offset = cd_offset as u64;
let cd_offset_u32 = if cd_offset > NON_ZIP64_MAX_SIZE as u64 { NON_ZIP64_MAX_SIZE } else { cd_offset as u32 };
if self.is_zip64 {
let eocdr_offset = self.writer.offset();
let eocdr = Zip64EndOfCentralDirectoryRecord {
size_of_zip64_end_of_cd_record: 44,
version_made_by: crate::spec::version::as_made_by(),
version_needed_to_extract: 46,
disk_number: 0,
disk_number_start_of_cd: 0,
num_entries_in_directory_on_disk: num_entries_in_directory,
num_entries_in_directory,
directory_size: central_directory_size,
offset_of_start_of_directory: cd_offset,
};
self.writer.write_all(&crate::spec::consts::ZIP64_EOCDR_SIGNATURE.to_le_bytes()).await?;
self.writer.write_all(&eocdr.as_bytes()).await?;
let eocdl = Zip64EndOfCentralDirectoryLocator {
number_of_disk_with_start_of_zip64_end_of_central_directory: 0,
relative_offset: eocdr_offset as u64,
total_number_of_disks: 1,
};
self.writer.write_all(&crate::spec::consts::ZIP64_EOCDL_SIGNATURE.to_le_bytes()).await?;
self.writer.write_all(&eocdl.as_bytes()).await?;
}
let header = EndOfCentralDirectoryHeader {
disk_num: 0,
start_cent_dir_disk: 0,
num_of_entries_disk: num_entries_in_directory_u16,
num_of_entries: num_entries_in_directory_u16,
size_cent_dir: central_directory_size_u32,
cent_dir_offset: cd_offset_u32,
file_comm_length: self.comment_opt.as_ref().map(|v| v.len() as u16).unwrap_or_default(),
};
self.writer.write_all(&crate::spec::consts::EOCDR_SIGNATURE.to_le_bytes()).await?;
self.writer.write_all(&header.as_slice()).await?;
if let Some(comment) = self.comment_opt {
self.writer.write_all(comment.as_bytes()).await?;
}
Ok(self.writer.into_inner())
}
}