-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeflate_zip.rs
More file actions
102 lines (87 loc) · 2.93 KB
/
Copy pathdeflate_zip.rs
File metadata and controls
102 lines (87 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Write a ZIP file with DEFLATE compression using the sans-IO API.
//!
//! Run with: `cargo run --example deflate_zip -- file1.txt file2.txt`
//!
//! Compresses each input file with flate2 and writes the result to `output.zip`.
//! Files are read and compressed in streaming chunks, never fully loaded into memory.
use std::{
env,
fs::File,
io::{self, BufRead, BufReader, Write},
process,
};
use bytes::BytesMut;
use cerniera::{CompressionMethod, MsDosDateTime, ZipArchive, ZipPath};
use flate2::{Compression, write::DeflateEncoder};
use jiff::Timestamp;
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().skip(1).collect();
if args.is_empty() {
eprintln!("usage: deflate_zip <file>...");
process::exit(1);
}
let mut archive = ZipArchive::new();
let mut buf = BytesMut::new();
let mut out = File::create("output.zip")?;
for path in args {
let src = File::open(&path)?;
let metadata = src.metadata()?;
let modified: MsDosDateTime = Timestamp::try_from(metadata.modified()?)
.expect("modification time out of range")
.to_zoned(jiff::tz::TimeZone::system())
.datetime()
.try_into()
.expect("modification time out of MS-DOS range");
let path = ZipPath::new(path).expect("file name too long");
archive.start_file(path, modified, CompressionMethod::Deflate, &mut buf);
out.write_all(&buf)?;
buf.clear();
// Stream through the file: feed uncompressed chunks to the archive
// for CRC tracking, and to the DEFLATE encoder for compression.
// The encoder writes compressed data directly to the output file.
let mut encoder =
DeflateEncoder::new(CountingWriter::new(&mut out), Compression::default());
let mut reader = BufReader::new(src);
loop {
let buf = reader.fill_buf()?;
if buf.is_empty() {
break;
}
archive.file_data(buf);
encoder.write_all(buf)?;
let len = buf.len();
reader.consume(len);
}
let compressed_size = encoder.finish()?.bytes_written;
archive.end_file_compressed(compressed_size, &mut buf);
out.write_all(&buf)?;
buf.clear();
}
archive.finish(&mut buf);
out.write_all(&buf)?;
println!("wrote output.zip");
Ok(())
}
/// A writer wrapper that counts the number of bytes written.
struct CountingWriter<W> {
inner: W,
bytes_written: u64,
}
impl<W> CountingWriter<W> {
fn new(inner: W) -> Self {
Self {
inner,
bytes_written: 0,
}
}
}
impl<W: Write> Write for CountingWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.inner.write(buf)?;
self.bytes_written += n as u64;
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}