Skip to content

Commit

Permalink
add finish_into_readable()
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed May 2, 2024
1 parent 01a8ff4 commit 68f7f5d
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 14 deletions.
49 changes: 40 additions & 9 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ pub(crate) mod zip_archive {
pub(crate) names_map: super::HashMap<Box<str>, usize>,
pub(super) offset: u64,
pub(super) dir_start: u64,
pub(super) dir_end: u64,
}

/// ZIP archive reader
Expand Down Expand Up @@ -331,6 +330,39 @@ pub(crate) struct CentralDirectoryInfo {
pub(crate) disk_with_central_directory: u32,
}

impl<R> ZipArchive<R> {
pub(crate) fn from_finalized_writer(
files: Vec<ZipFileData>,
comment: Vec<u8>,
reader: R,
central_start: u64,
) -> ZipResult<Self> {
if files.is_empty() {
return Err(ZipError::InvalidArchive(
"attempt to finalize empty zip writer into readable",
));
}
/* This is where the whole file starts. */
let initial_offset = files.first().unwrap().header_start;
let names_map: HashMap<Box<str>, usize> = files
.iter()
.enumerate()
.map(|(i, d)| (d.file_name.clone(), i))
.collect();
let shared = Arc::new(zip_archive::Shared {
files: files.into_boxed_slice(),
names_map,
offset: initial_offset,
dir_start: central_start,
});
Ok(Self {
reader,
shared,
comment: comment.into_boxed_slice().into(),
})
}
}

impl<R: Read + Seek> ZipArchive<R> {
fn get_directory_info_zip32(
footer: &spec::CentralDirectoryEnd,
Expand Down Expand Up @@ -482,11 +514,12 @@ impl<R: Read + Seek> ZipArchive<R> {
result.and_then(|dir_info| {
// If the parsed number of files is greater than the offset then
// something fishy is going on and we shouldn't trust number_of_files.
let file_capacity = if dir_info.number_of_files > cde_start_pos as usize {
0
} else {
dir_info.number_of_files
};
let file_capacity =
if dir_info.number_of_files > dir_info.directory_start as usize {
0
} else {
dir_info.number_of_files
};
let mut files = Vec::with_capacity(file_capacity);
let mut names_map = HashMap::with_capacity(file_capacity);
reader.seek(io::SeekFrom::Start(dir_info.directory_start))?;
Expand All @@ -495,7 +528,6 @@ impl<R: Read + Seek> ZipArchive<R> {
names_map.insert(file.file_name.clone(), files.len());
files.push(file);
}
let dir_end = reader.seek(io::SeekFrom::Start(dir_info.directory_start))?;
if dir_info.disk_number != dir_info.disk_with_central_directory {
unsupported_zip_error("Support for multi-disk files is not implemented")
} else {
Expand All @@ -504,7 +536,6 @@ impl<R: Read + Seek> ZipArchive<R> {
names_map,
offset: dir_info.archive_offset,
dir_start: dir_info.directory_start,
dir_end,
})
}
})
Expand All @@ -524,7 +555,7 @@ impl<R: Read + Seek> ZipArchive<R> {
}
let shared = ok_results
.into_iter()
.max_by_key(|shared| shared.dir_end)
.max_by_key(|shared| shared.dir_start)
.unwrap();
reader.seek(io::SeekFrom::Start(shared.dir_start))?;
Ok(shared)
Expand Down
44 changes: 39 additions & 5 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,39 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
) -> ZipResult<()> {
self.deep_copy_file(&path_to_string(src_path), &path_to_string(dest_path))
}

/// Write the zip file into the backing stream, then produce a readable archive of that data.
///
/// This method avoids parsing the central directory records at the end of the stream for
/// a slight performance improvement over running [`ZipArchive::new()`] on the output of
/// [`Self::finish()`].
///
///```
/// # fn main() -> Result<(), zip::result::ZipError> {
/// use std::io::{Cursor, prelude::*};
/// use zip::{ZipArchive, ZipWriter, write::SimpleFileOptions};
///
/// let buf = Cursor::new(Vec::new());
/// let mut zip = ZipWriter::new(buf);
/// let options = SimpleFileOptions::default();
/// zip.start_file("a.txt", options)?;
/// zip.write_all(b"hello\n")?;
///
/// let mut zip = zip.finish_into_readable()?;
/// let mut s: String = String::new();
/// zip.by_name("a.txt")?.read_to_string(&mut s)?;
/// assert_eq!(s, "hello\n");
/// # Ok(())
/// # }
///```
pub fn finish_into_readable(mut self) -> ZipResult<ZipArchive<A>> {
let central_start = self.finalize()?;
let inner = mem::replace(&mut self.inner, Closed).unwrap();
let comment = mem::take(&mut self.comment);
let files = mem::take(&mut self.files);
let archive = ZipArchive::from_finalized_writer(files, comment, inner, central_start)?;
Ok(archive)
}
}

impl<W: Write + Seek> ZipWriter<W> {
Expand Down Expand Up @@ -1100,7 +1133,7 @@ impl<W: Write + Seek> ZipWriter<W> {
/// This will return the writer, but one should normally not append any data to the end of the file.
/// Note that the zipfile will also be finished on drop.
pub fn finish(&mut self) -> ZipResult<W> {
self.finalize()?;
let _central_start = self.finalize()?;
let inner = mem::replace(&mut self.inner, Closed);
Ok(inner.unwrap())
}
Expand Down Expand Up @@ -1160,10 +1193,10 @@ impl<W: Write + Seek> ZipWriter<W> {
self.add_symlink(path_to_string(path), path_to_string(target), options)
}

fn finalize(&mut self) -> ZipResult<()> {
fn finalize(&mut self) -> ZipResult<u64> {
self.finish_file()?;

{
let central_start = {
let central_start = self.write_central_and_footer()?;
let writer = self.inner.get_plain();
let footer_end = writer.stream_position()?;
Expand All @@ -1175,9 +1208,10 @@ impl<W: Write + Seek> ZipWriter<W> {
writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
self.write_central_and_footer()?;
}
}
central_start
};

Ok(())
Ok(central_start)
}

fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
Expand Down

0 comments on commit 68f7f5d

Please sign in to comment.