Implemented a basic ZipWriter

This commit is contained in:
Mathijs van de Nes 2014-09-11 10:42:18 +02:00
parent 02361b5dd5
commit ac9301f7d9
6 changed files with 209 additions and 32 deletions

52
src/bin/write_sample.rs Normal file
View file

@ -0,0 +1,52 @@
extern crate zip;
fn main()
{
println!("{}", doit());
}
fn doit() -> std::io::IoResult<()>
{
let args = std::os::args();
let fname = Path::new(args[1].as_slice());
let file = std::io::File::create(&fname).unwrap();
let mut zip = zip::writer::ZipWriter::new(file);
// try!(zip.start_file(b"test", zip::types::Stored));
// try!(zip.write(b""));
try!(zip.start_file(b"test/readme.txt", zip::types::Stored));
try!(zip.write(b"Hello, World!\n"));
try!(zip.start_file(b"test/lorem.txt", zip::types::Deflated));
try!(zip.write(b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tellus elit, tristique vitae mattis egestas, ultricies vitae risus. Quisque sit amet quam ut urna aliquet
molestie. Proin blandit ornare dui, a tempor nisl accumsan in. Praesent a consequat felis. Morbi metus diam, auctor in auctor vel, feugiat id odio. Curabitur ex ex,
dictum quis auctor quis, suscipit id lorem. Aliquam vestibulum dolor nec enim vehicula, porta tristique augue tincidunt. Vivamus ut gravida est. Sed pellentesque, dolor
vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam id lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque.
Phasellus sed nisi in augue sodales pulvinar ut et leo. Pellentesque eget leo vitae massa bibendum sollicitudin. Curabitur erat lectus, congue quis auctor sed, aliquet
bibendum est. Ut porta ultricies turpis at maximus. Cras non lobortis justo. Duis rutrum magna sed velit facilisis, et sagittis metus laoreet. Pellentesque quam ligula,
dapibus vitae mauris quis, dapibus cursus leo. Sed sit amet condimentum eros. Nulla vestibulum enim sit amet lorem pharetra, eu fringilla nisl posuere. Sed tristique non
nibh at viverra. Vivamus sed accumsan lacus, nec pretium eros. Mauris elementum arcu eu risus fermentum, tempor ullamcorper neque aliquam. Sed tempor in erat eu
suscipit. In euismod in libero in facilisis. Donec sagittis, odio et fermentum dignissim, risus justo pretium nibh, eget vestibulum lectus metus vel lacus.
Quisque feugiat, magna ac feugiat ullamcorper, augue justo consequat felis, ut fermentum arcu lorem vitae ligula. Quisque iaculis tempor maximus. In quis eros ac tellus
aliquam placerat quis id tellus. Donec non gravida nulla. Morbi faucibus neque sed faucibus aliquam. Sed accumsan mattis nunc, non interdum justo. Cras vitae facilisis
leo. Fusce sollicitudin ultrices sagittis. Maecenas eget massa id lorem dignissim ultrices non et ligula. Pellentesque aliquam mi ac neque tempus ornare. Morbi non enim
vulputate quam ullamcorper finibus id non neque. Quisque malesuada commodo lorem, ut ornare velit iaculis rhoncus. Mauris vel maximus ex.
Morbi eleifend blandit diam, non vulputate ante iaculis in. Donec pellentesque augue id enim suscipit, eget suscipit lacus commodo. Ut vel ex vitae elit imperdiet
vulputate. Nunc eu mattis orci, ut pretium sem. Nam vitae purus mollis ante tempus malesuada a at magna. Integer mattis lectus non luctus lobortis. In a cursus quam,
eget faucibus sem.
Donec vitae condimentum nisi, non efficitur massa. Praesent sed mi in massa sollicitudin iaculis. Pellentesque a libero ultrices, sodales lacus eu, ornare dui. In
laoreet est nec dolor aliquam consectetur. Integer iaculis felis venenatis libero pulvinar, ut pretium odio interdum. Donec in nisi eu dolor varius vestibulum eget vel
nunc. Morbi a venenatis quam, in vehicula justo. Nam risus dui, auctor eu accumsan at, sagittis ac lectus. Mauris iaculis dignissim interdum. Cras cursus dapibus auctor.
Donec sagittis massa vitae tortor viverra vehicula. Mauris fringilla nunc eu lorem ultrices placerat. Maecenas posuere porta quam at semper. Praesent eu bibendum eros.
Nunc congue sollicitudin ante, sollicitudin lacinia magna cursus vitae.
"));
zip.finalize()
}

View file

@ -1,4 +1,5 @@
#![feature(phase)] #![feature(phase)]
#![feature(unsafe_destructor)]
#[phase(plugin, link)] extern crate log; #[phase(plugin, link)] extern crate log;
extern crate time; extern crate time;

View file

@ -73,6 +73,7 @@ pub fn central_header_to_zip_file<R: Reader+Seek>(reader: &mut R) -> IoResult<Zi
uncompressed_size: uncompressed_size as u64, uncompressed_size: uncompressed_size as u64,
file_name: file_name, file_name: file_name,
file_comment: file_comment, file_comment: file_comment,
header_start: offset as u64,
data_start: data_start, data_start: data_start,
}; };
@ -90,7 +91,38 @@ pub fn write_local_file_header<T: Writer>(writer: &mut T, file: &ZipFile) -> IoR
try!(writer.write_le_u16(flag)); try!(writer.write_le_u16(flag));
try!(writer.write_le_u16(file.compression_method as u16)); try!(writer.write_le_u16(file.compression_method as u16));
try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time))); try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time)));
// ... try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
try!(writer.write_le_u16(file.file_name.len() as u16));
try!(writer.write_le_u16(0));
try!(writer.write(file.file_name.as_slice()));
Ok(())
}
pub fn write_central_directory_header<T: Writer>(writer: &mut T, file: &ZipFile) -> IoResult<()>
{
try!(writer.write_le_u32(CENTRAL_DIRECTORY_HEADER_SIGNATURE));
try!(writer.write_le_u16(1337));
try!(writer.write_le_u16(20));
let flag = if file.encrypted { 1 } else { 0 };
try!(writer.write_le_u16(flag));
try!(writer.write_le_u16(file.compression_method as u16));
try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time)));
try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time)));
try!(writer.write_le_u32(file.crc32));
try!(writer.write_le_u32(file.compressed_size as u32));
try!(writer.write_le_u32(file.uncompressed_size as u32));
try!(writer.write_le_u16(file.file_name.len() as u16));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u16(0));
try!(writer.write_le_u32(0));
try!(writer.write_le_u32(file.header_start as u32));
try!(writer.write(file.file_name.as_slice()));
Ok(()) Ok(())
} }
@ -168,4 +200,18 @@ impl CentralDirectoryEnd
detail: None detail: None
}) })
} }
pub fn write<T: Writer>(&self, writer: &mut T) -> IoResult<()>
{
try!(writer.write_le_u32(CENTRAL_DIRECTORY_END_SIGNATURE));
try!(writer.write_le_u16(self.disk_number));
try!(writer.write_le_u16(self.disk_with_central_directory));
try!(writer.write_le_u16(self.number_of_files_on_this_disk));
try!(writer.write_le_u16(self.number_of_files));
try!(writer.write_le_u32(self.central_directory_size));
try!(writer.write_le_u32(self.central_directory_offset));
try!(writer.write_le_u16(self.zip_file_comment.len() as u16));
try!(writer.write(self.zip_file_comment.as_slice()));
Ok(())
}
} }

View file

@ -33,6 +33,7 @@ pub struct ZipFile
pub uncompressed_size: u64, pub uncompressed_size: u64,
pub file_name: Vec<u8>, pub file_name: Vec<u8>,
pub file_comment: Vec<u8>, pub file_comment: Vec<u8>,
pub header_start: u64,
pub data_start: u64, pub data_start: u64,
} }

View file

@ -29,7 +29,12 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm
pub fn tm_to_msdos_time(time: Tm) -> u16 pub fn tm_to_msdos_time(time: Tm) -> u16
{ {
((time.tm_sec >> 2) | (time.tm_min << 5) | (time.tm_hour << 11)) as u16 ((time.tm_sec >> 1) | (time.tm_min << 5) | (time.tm_hour << 11)) as u16
}
pub fn tm_to_msdos_date(time: Tm) -> u16
{
(time.tm_mday | ((time.tm_mon + 1) << 5) | ((time.tm_year - 80) << 9)) as u16
} }
pub struct RefMutReader<'a, R:'a> pub struct RefMutReader<'a, R:'a>

View file

@ -12,7 +12,7 @@ use flate2::writer::DeflateEncoder;
enum GenericZipWriter<W> enum GenericZipWriter<W>
{ {
Invalid, Closed,
Storer(W), Storer(W),
Deflater(DeflateEncoder<W>), Deflater(DeflateEncoder<W>),
} }
@ -32,6 +32,11 @@ struct ZipWriterStats
bytes_written: u64, bytes_written: u64,
} }
fn writer_closed_error<T>() -> IoResult<T>
{
Err(IoError { kind: io::Closed, desc: "This writer has been closed", detail: None })
}
impl<W: Writer+Seek> Writer for ZipWriter<W> impl<W: Writer+Seek> Writer for ZipWriter<W>
{ {
fn write(&mut self, buf: &[u8]) -> IoResult<()> fn write(&mut self, buf: &[u8]) -> IoResult<()>
@ -42,7 +47,7 @@ impl<W: Writer+Seek> Writer for ZipWriter<W>
{ {
Storer(ref mut w) => w.write(buf), Storer(ref mut w) => w.write(buf),
Deflater(ref mut w) => w.write(buf), Deflater(ref mut w) => w.write(buf),
Invalid => Err(IoError { kind: io::OtherIoError, desc: "The writer has previously failed", detail: None }), Closed => writer_closed_error(),
} }
} }
} }
@ -68,18 +73,18 @@ impl<W: Writer+Seek> ZipWriter<W>
} }
} }
pub fn start_new_file(&mut self, name: &[u8], compression: types::CompressionMethod) -> IoResult<()> pub fn start_file(&mut self, name: &[u8], compression: types::CompressionMethod) -> IoResult<()>
{ {
try!(self.finish_file()); try!(self.finish_file());
let writer = match self.inner
{ {
Storer(ref mut r) => r, let writer = self.inner.get_plain();
_ => fail!("Should have switched to stored first"),
};
let header_start = try!(writer.tell()); let header_start = try!(writer.tell());
try!(writer.seek(30 + name.len() as i64, io::SeekCur)); try!(writer.seek(30 + name.len() as i64, io::SeekCur));
self.stats.start = header_start + 30 + name.len() as u64; self.stats.start = header_start + 30 + name.len() as u64;
self.stats.bytes_written = 0;
self.stats.crc32 = 0;
self.files.push(ZipFile self.files.push(ZipFile
{ {
@ -91,8 +96,12 @@ impl<W: Writer+Seek> ZipWriter<W>
uncompressed_size: 0, uncompressed_size: 0,
file_name: Vec::from_slice(name), file_name: Vec::from_slice(name),
file_comment: Vec::new(), file_comment: Vec::new(),
header_start: header_start,
data_start: self.stats.start, data_start: self.stats.start,
}); });
}
try!(self.inner.switch_to(compression));
Ok(()) Ok(())
} }
@ -100,11 +109,7 @@ impl<W: Writer+Seek> ZipWriter<W>
fn finish_file(&mut self) -> IoResult<()> fn finish_file(&mut self) -> IoResult<()>
{ {
try!(self.inner.switch_to(types::Stored)); try!(self.inner.switch_to(types::Stored));
let writer = match self.inner let writer = self.inner.get_plain();
{
Storer(ref mut r) => r,
_ => fail!("Should have switched to stored first"),
};
let file = match self.files.mut_last() let file = match self.files.mut_last()
{ {
@ -115,21 +120,70 @@ impl<W: Writer+Seek> ZipWriter<W>
file.uncompressed_size = self.stats.bytes_written; file.uncompressed_size = self.stats.bytes_written;
file.compressed_size = try!(writer.tell()) - self.stats.start; file.compressed_size = try!(writer.tell()) - self.stats.start;
// write header try!(writer.seek(file.header_start as i64, io::SeekSet));
try!(spec::write_local_file_header(writer, file)); try!(spec::write_local_file_header(writer, file));
try!(writer.seek(0, io::SeekEnd));
Ok(()) Ok(())
} }
pub fn finalize(&mut self) -> IoResult<()>
{
try!(self.finish_file());
{
let writer = self.inner.get_plain();
let central_start = try!(writer.tell());
for file in self.files.iter()
{
try!(spec::write_central_directory_header(writer, file));
}
let central_size = try!(writer.tell()) - central_start;
let footer = spec::CentralDirectoryEnd
{
disk_number: 0,
disk_with_central_directory: 0,
number_of_files_on_this_disk: self.files.len() as u16,
number_of_files: self.files.len() as u16,
central_directory_size: central_size as u32,
central_directory_offset: central_start as u32,
zip_file_comment: Vec::from_slice(b"zip-rs"),
};
try!(footer.write(writer));
}
self.inner = Closed;
Ok(())
}
}
#[unsafe_destructor]
impl<W: Writer+Seek> Drop for ZipWriter<W>
{
fn drop(&mut self)
{
if !self.inner.is_closed()
{
match self.finalize()
{
Ok(_) => {},
Err(e) => warn!("ZipWriter drop failed: {}", e),
}
}
}
} }
impl<W: Writer+Seek> GenericZipWriter<W> impl<W: Writer+Seek> GenericZipWriter<W>
{ {
fn switch_to(&mut self, compression: types::CompressionMethod) -> IoResult<()> fn switch_to(&mut self, compression: types::CompressionMethod) -> IoResult<()>
{ {
let bare = match mem::replace(self, Invalid) let bare = match mem::replace(self, Closed)
{ {
Storer(w) => w, Storer(w) => w,
Deflater(w) => try!(w.finish()), Deflater(w) => try!(w.finish()),
Invalid => return Err(IoError { kind: io::OtherIoError, desc: "The writer has previously failed", detail: None }), Closed => return writer_closed_error(),
}; };
*self = match compression *self = match compression
@ -141,4 +195,22 @@ impl<W: Writer+Seek> GenericZipWriter<W>
Ok(()) Ok(())
} }
fn is_closed(&self) -> bool
{
match *self
{
Closed => true,
_ => false,
}
}
fn get_plain(&mut self) -> &mut W
{
match *self
{
Storer(ref mut w) => w,
_ => fail!("Should have switched to stored beforehand"),
}
}
} }