Update the write API

This change adds a FileOptions struct. This struct can be filled using a builder pattern
to set the options for a file or folder.

With this change, we also introduce the option to set the (unix) permissions of a file or folder.
This commit is contained in:
Mathijs van de Nes 2016-10-30 09:09:50 +01:00
parent 82315c9511
commit d2d19f6539
3 changed files with 91 additions and 15 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "zip" name = "zip"
version = "0.1.19" version = "0.2.0"
authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>"] authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>"]
license = "MIT" license = "MIT"
repository = "https://github.com/mvdnes/zip-rs.git" repository = "https://github.com/mvdnes/zip-rs.git"

View file

@ -1,4 +1,5 @@
use std::io::prelude::*; use std::io::prelude::*;
use zip::write::FileOptions;
extern crate zip; extern crate zip;
@ -32,12 +33,13 @@ fn doit(filename: &str) -> zip::result::ZipResult<()>
let mut zip = zip::ZipWriter::new(file); let mut zip = zip::ZipWriter::new(file);
try!(zip.start_file("test/", zip::CompressionMethod::Stored)); try!(zip.add_directory("test/", FileOptions::default()));
try!(zip.start_file("test/☃.txt", zip::CompressionMethod::Stored)); let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored).unix_permissions(0o755);
try!(zip.start_file("test/☃.txt", options));
try!(zip.write_all(b"Hello, World!\n")); try!(zip.write_all(b"Hello, World!\n"));
try!(zip.start_file("test/lorem_ipsum.txt", zip::CompressionMethod::Deflated)); try!(zip.start_file("test/lorem_ipsum.txt", FileOptions::default()));
try!(zip.write_all(LOREM_IPSUM)); try!(zip.write_all(LOREM_IPSUM));
try!(zip.finish()); try!(zip.finish());

View file

@ -43,7 +43,8 @@ enum GenericZipWriter<W: Write + io::Seek>
/// let mut w = std::io::Cursor::new(buf); /// let mut w = std::io::Cursor::new(buf);
/// let mut zip = zip::ZipWriter::new(w); /// let mut zip = zip::ZipWriter::new(w);
/// ///
/// try!(zip.start_file("hello_world.txt", zip::CompressionMethod::Stored)); /// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
/// try!(zip.start_file("hello_world.txt", options));
/// try!(zip.write(b"Hello, World!")); /// try!(zip.write(b"Hello, World!"));
/// ///
/// // Optionally finish the zip. (this is also done on drop) /// // Optionally finish the zip. (this is also done on drop)
@ -69,6 +70,50 @@ struct ZipWriterStats
bytes_written: u64, bytes_written: u64,
} }
/// Metadata for a file to be written
pub struct FileOptions {
compression_method: CompressionMethod,
last_modified_time: time::Tm,
permissions: Option<u32>,
}
impl FileOptions {
/// Construct a new FileOptions object
pub fn default() -> FileOptions {
FileOptions {
compression_method: CompressionMethod::Deflated,
last_modified_time: time::now(),
permissions: None,
}
}
/// Set the compression method for the new file
///
/// The default is `CompressionMethod::Deflated`
pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
self.compression_method = method;
self
}
/// Set the last modified time
///
/// The default is the current timestamp
pub fn last_modified_time(mut self, mod_time: time::Tm) -> FileOptions {
self.last_modified_time = mod_time;
self
}
/// Set the permissions for the new file.
///
/// The format is represented with unix-style permissions.
/// The default is `0o644`, which represents `rw-r--r--` for files,
/// and `0o755`, which represents `rwxr-xr-x` for directories
pub fn unix_permissions(mut self, mode: u32) -> FileOptions {
self.permissions = Some(mode & 0o777);
self
}
}
impl<W: Write+io::Seek> Write for ZipWriter<W> impl<W: Write+io::Seek> Write for ZipWriter<W>
{ {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> fn write(&mut self, buf: &[u8]) -> io::Result<usize>
@ -122,8 +167,8 @@ impl<W: Write+io::Seek> ZipWriter<W>
} }
} }
/// Start a new file for with the requested compression method. /// Start a new file for with the requested options.
pub fn start_file<S>(&mut self, name: S, compression: CompressionMethod) -> ZipResult<()> fn start_entry<S>(&mut self, name: S, options: FileOptions) -> ZipResult<()>
where S: Into<String> where S: Into<String>
{ {
try!(self.finish_file()); try!(self.finish_file());
@ -132,13 +177,14 @@ impl<W: Write+io::Seek> ZipWriter<W>
let writer = self.inner.get_plain(); let writer = self.inner.get_plain();
let header_start = try!(writer.seek(io::SeekFrom::Current(0))); let header_start = try!(writer.seek(io::SeekFrom::Current(0)));
let permissions = options.permissions.unwrap_or(0o100644);
let mut file = ZipFileData let mut file = ZipFileData
{ {
system: System::Dos, system: System::Unix,
version_made_by: DEFAULT_VERSION, version_made_by: DEFAULT_VERSION,
encrypted: false, encrypted: false,
compression_method: compression, compression_method: options.compression_method,
last_modified_time: time::now(), last_modified_time: options.last_modified_time,
crc32: 0, crc32: 0,
compressed_size: 0, compressed_size: 0,
uncompressed_size: 0, uncompressed_size: 0,
@ -146,7 +192,7 @@ impl<W: Write+io::Seek> ZipWriter<W>
file_comment: String::new(), file_comment: String::new(),
header_start: header_start, header_start: header_start,
data_start: 0, data_start: 0,
external_attributes: 0, external_attributes: permissions << 16,
}; };
try!(write_local_file_header(writer, &file)); try!(write_local_file_header(writer, &file));
@ -160,7 +206,7 @@ impl<W: Write+io::Seek> ZipWriter<W>
self.files.push(file); self.files.push(file);
} }
try!(self.inner.switch_to(compression)); try!(self.inner.switch_to(options.compression_method));
Ok(()) Ok(())
} }
@ -184,9 +230,36 @@ impl<W: Write+io::Seek> ZipWriter<W>
Ok(()) Ok(())
} }
/// Starts a file.
pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
where S: Into<String>
{
if options.permissions.is_none() {
options.permissions = Some(0o644);
}
*options.permissions.as_mut().unwrap() |= 0o100000;
try!(self.start_entry(name, options));
Ok(())
}
/// Add a directory entry.
///
/// You should not write data to the file afterwards.
pub fn add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
where S: Into<String>
{
if options.permissions.is_none() {
options.permissions = Some(0o755);
}
*options.permissions.as_mut().unwrap() |= 0o40000;
options.compression_method = CompressionMethod::Stored;
try!(self.start_entry(name, options));
Ok(())
}
/// Finish the last file and write all other zip-structures /// Finish the last file and write all other zip-structures
/// ///
/// This will return the writer, but one should normally not append any data to the end of the file. /// 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. /// Note that the zipfile will also be finished on drop.
pub fn finish(&mut self) -> ZipResult<W> pub fn finish(&mut self) -> ZipResult<W>
{ {
@ -369,7 +442,8 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
// central file header signature // central file header signature
try!(writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)); try!(writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE));
// version made by // version made by
try!(writer.write_u16::<LittleEndian>(0x14FF)); let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16);
try!(writer.write_u16::<LittleEndian>(version_made_by));
// version needed to extract // version needed to extract
try!(writer.write_u16::<LittleEndian>(20)); try!(writer.write_u16::<LittleEndian>(20));
// general puprose bit flag // general puprose bit flag
@ -399,7 +473,7 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
// internal file attribytes // internal file attribytes
try!(writer.write_u16::<LittleEndian>(0)); try!(writer.write_u16::<LittleEndian>(0));
// external file attributes // external file attributes
try!(writer.write_u32::<LittleEndian>(0)); try!(writer.write_u32::<LittleEndian>(file.external_attributes));
// relative offset of local header // relative offset of local header
try!(writer.write_u32::<LittleEndian>(file.header_start as u32)); try!(writer.write_u32::<LittleEndian>(file.header_start as u32));
// file name // file name