Implemented a basic ZipWriter
This commit is contained in:
parent
02361b5dd5
commit
ac9301f7d9
6 changed files with 209 additions and 32 deletions
52
src/bin/write_sample.rs
Normal file
52
src/bin/write_sample.rs
Normal 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()
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
48
src/spec.rs
48
src/spec.rs
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
132
src/writer.rs
132
src/writer.rs
|
@ -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,31 +73,35 @@ 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());
|
||||||
};
|
try!(writer.seek(30 + name.len() as i64, io::SeekCur));
|
||||||
let header_start = try!(writer.tell());
|
|
||||||
try!(writer.seek(30 + name.len() as i64, io::SeekCur));
|
|
||||||
self.stats.start = header_start + 30 + name.len() as u64;
|
|
||||||
|
|
||||||
self.files.push(ZipFile
|
self.stats.start = header_start + 30 + name.len() as u64;
|
||||||
{
|
self.stats.bytes_written = 0;
|
||||||
encrypted: false,
|
self.stats.crc32 = 0;
|
||||||
compression_method: compression,
|
|
||||||
last_modified_time: time::now(),
|
self.files.push(ZipFile
|
||||||
crc32: 0,
|
{
|
||||||
compressed_size: 0,
|
encrypted: false,
|
||||||
uncompressed_size: 0,
|
compression_method: compression,
|
||||||
file_name: Vec::from_slice(name),
|
last_modified_time: time::now(),
|
||||||
file_comment: Vec::new(),
|
crc32: 0,
|
||||||
data_start: self.stats.start,
|
compressed_size: 0,
|
||||||
});
|
uncompressed_size: 0,
|
||||||
|
file_name: Vec::from_slice(name),
|
||||||
|
file_comment: Vec::new(),
|
||||||
|
header_start: header_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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue