diff --git a/examples/stream.rs b/examples/stream.rs new file mode 100644 index 00000000..fad4a2ed --- /dev/null +++ b/examples/stream.rs @@ -0,0 +1,44 @@ +use std::fs::{read_dir, File}; +use std::io::Write; +use std::iter::Peekable; +use std::path::PathBuf; + +struct TarStream> { + files: Peekable, +} + +impl> TarStream { + pub fn new(files: I) -> Self { + Self { + files: files.peekable(), + } + } + + pub fn read(&mut self, buf: &mut Vec) -> Option<()> { + self.files.next().map(|path| { + let new_path = PathBuf::from("archived").join(&path); + let mut builder = tar::Builder::new(buf); + + builder.contiguous(true); + builder.append_path_with_name(path, &new_path).unwrap(); + + if self.files.peek().is_none() { + builder.finish().unwrap(); + } + }) + } +} + +fn main() { + let files = read_dir("examples") + .unwrap() + .map(|p| p.unwrap().path()) + .filter(|p| p.is_file()); + let mut buf = Vec::with_capacity(1024 * 1024 * 4); + let mut tar_stream = TarStream::new(files); + let mut output = File::create("examples.tar").unwrap(); + + while tar_stream.read(&mut buf).is_some() { + output.write(&buf).unwrap(); + } +} diff --git a/src/builder.rs b/src/builder.rs index 9d6ecc69..a7dd14cc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,6 +15,7 @@ pub struct Builder { mode: HeaderMode, follow: bool, finished: bool, + contiguous: bool, obj: Option, } @@ -27,6 +28,7 @@ impl Builder { mode: HeaderMode::Complete, follow: true, finished: false, + contiguous: false, obj: Some(obj), } } @@ -44,6 +46,13 @@ impl Builder { self.follow = follow; } + /// Determines wheter the archive will be automatically finished + /// Contiguous Builder will not finish the archive unless `finish` + /// is explicitly called. Defaults to false. + pub fn contiguous(&mut self, contiguous: bool) { + self.contiguous = contiguous; + } + /// Gets shared reference to the underlying object. pub fn get_ref(&self) -> &W { self.obj.as_ref().unwrap() @@ -61,13 +70,11 @@ impl Builder { /// Unwrap this archive, returning the underlying object. /// - /// This function will finish writing the archive if the `finish` function - /// hasn't yet been called, returning any I/O error which happens during - /// that operation. + /// This function will finish writing the archive if it is not contiguous + /// and the `finish` function hasn't yet been called, returning any + /// I/O error which happens during that operation. pub fn into_inner(mut self) -> io::Result { - if !self.finished { - self.finish()?; - } + self.finalize()?; Ok(self.obj.take().unwrap()) } @@ -204,7 +211,7 @@ impl Builder { /// operation then this may corrupt the archive. /// /// Note if the `path` is a directory. This will just add an entry to the archive, - /// rather than contents of the directory. + /// rather than contents of the directory. /// /// Also note that after all files have been written to an archive the /// `finish` function needs to be called to finish writing the archive. @@ -342,11 +349,11 @@ impl Builder { ) } - /// Finish writing this archive, emitting the termination sections. + /// Finish writing this archive, emitting the termination sections /// /// This function should only be called when the archive has been written /// entirely and if an I/O error happens the underlying object still needs - /// to be acquired. + /// to be acquired or contiguous archive needs to be finished. /// /// In most situations the `into_inner` method should be preferred. pub fn finish(&mut self) -> io::Result<()> { @@ -356,6 +363,14 @@ impl Builder { self.finished = true; self.get_mut().write_all(&[0; 1024]) } + + fn finalize(&mut self) -> io::Result<()> { + if !self.contiguous { + self.finish() + } else { + Ok(()) + } + } } fn append(mut dst: &mut dyn Write, header: &Header, mut data: &mut dyn Read) -> io::Result<()> { @@ -544,6 +559,6 @@ fn append_dir_all( impl Drop for Builder { fn drop(&mut self) { - let _ = self.finish(); + let _ = self.finalize(); } } diff --git a/tests/all.rs b/tests/all.rs index f16ffbc0..48bcd311 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1032,3 +1032,56 @@ fn unpack_path_larger_than_windows_max_path() { // should unpack path greater than windows MAX_PATH length of 260 characters assert!(ar.unpack(td.path()).is_ok()); } + +#[test] +fn contiguous_archive() { + fn append_contiguous( + path: impl AsRef, + data: &[u8], + buf: &mut Vec, + finish: bool, + ) -> io::Result<()> { + let mut ar = Builder::new(buf); + ar.contiguous(true); + ar.append_data(&mut Header::new_gnu(), path, data)?; + if finish { + ar.finish() + } else { + Ok(()) + } + } + + const DATA_A: &[u8] = &[1, 2, 3]; + const DATA_B: &[u8] = &[4, 5, 6]; + + let mut ar = Builder::new(Vec::new()); + + assert!(ar.append_data(&mut Header::new_gnu(), "a", DATA_A).is_ok()); + assert!(ar.append_data(&mut Header::new_gnu(), "b", DATA_B).is_ok()); + + let expected = t!(ar.into_inner()); + + let mut actual = Vec::new(); + + assert!(append_contiguous("a", DATA_A, &mut actual, false).is_ok()); + assert!(append_contiguous("b", DATA_B, &mut actual, true).is_ok()); + + assert_eq!(expected, actual); +} + +#[test] +fn contiguous_into_inner() { + const DATA: &[u8] = &[1, 2, 3]; + + let mut ar = Builder::new(Vec::new()); + assert!(ar.append_data(&mut Header::new_gnu(), "a", DATA).is_ok()); + let expected = t!(ar.into_inner()); + let expected = &expected[0..expected.len() - 1024]; + + let mut ar = Builder::new(Vec::new()); + ar.contiguous(true); + assert!(ar.append_data(&mut Header::new_gnu(), "a", DATA).is_ok()); + let actual = t!(ar.into_inner()); + + assert_eq!(expected, &*actual); +}