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
use std::{io::{self, Read, Seek, BufReader, Write, BufWriter}, sync::Arc};
use crossbeam_channel::{Sender, SendError};
use flate2::bufread::DeflateEncoder;
use zip::{ZipArchive, ZipWriter, write::FileOptions, CompressionMethod};

use crate::{optimizer::EntryType, fop::FileOp};
use super::{EntryReader, EntrySaverSpec, EntrySaver};

/// An entry reader implementation for ZIP archive. It reads its contents from a provided reader (with seeking).
pub struct ZipEntryReader<R: Read + Seek> {
    r: R
}
impl <R: Read + Seek> ZipEntryReader<R> {
    /// Creates an entry reader with a specified reader.
    pub const fn new(r: R) -> Self {
        Self { r }
    }
}
impl <R: Read + Seek> EntryReader for ZipEntryReader<R> {
    fn read_entries(
        self,
        tx: Sender<EntryType>,
        use_blacklist: bool
    ) -> io::Result<()> {
        const SEND_ERR: fn(SendError<EntryType>) -> io::Error = |e: SendError<EntryType>| {
            io::Error::new(io::ErrorKind::Other, e)
        };
        let mut za = ZipArchive::new(BufReader::new(self.r))?;
        let jfc = za.len() as u64;
        tx.send(EntryType::Count(jfc)).map_err(SEND_ERR)?;
        for i in 0..jfc {
            let mut jf = za.by_index(i as usize)?;
            let fname: Arc<str> = jf.name().into();
            tx.send(if fname.ends_with('/') {
                EntryType::Directory(fname)
            } else {
                let fop = FileOp::by_name(&fname, use_blacklist);
                let mut obuf = Vec::new();
                match fop {
                    FileOp::Ignore => {}
                    _ => {
                        obuf.reserve_exact(jf.size() as usize);
                        jf.read_to_end(&mut obuf)?;
                    }
                }
                EntryType::File(fname, obuf, fop)
            }).map_err(SEND_ERR)?;
        }
        Ok(())
    }
}

/// An entry saver implementation for ZIP archive. It writes entries to it using a provided writer.
pub struct ZipEntrySaver<W: Write + Seek> {
    w: ZipWriter<BufWriter<W>>,
    file_opts: FileOptions
}
impl <W: Write + Seek> ZipEntrySaver<W> {
    /// Creates an entry saver with a seekable writer.
    pub fn new(w: W) -> EntrySaver<Self> {
        EntrySaver(Self {
            w: ZipWriter::new(BufWriter::new(w)),
            file_opts: FileOptions::default().compression_level(Some(9))
        })
    }
    /// Creates an entry saver with custom file options for ZIP archive and seekable writer.
    pub fn custom(w: W, file_opts: FileOptions) -> EntrySaver<Self> {
        EntrySaver(Self {
            w: ZipWriter::new(BufWriter::new(w)), file_opts
        })
    }
}
impl <W: Write + Seek> EntrySaverSpec for ZipEntrySaver<W> {
    fn save_dir(&mut self, dir: &str) -> io::Result<()> {
        if dir != "./cache" {
            self.w.add_directory(dir, self.file_opts)?
        }
        Ok(())
    }
    fn save_file(&mut self, name: &str, data: &[u8], compress_min: usize) -> io::Result<()> {
        let z = &mut self.w;
        z.start_file(name, self.file_opts
            .compression_method(compress_check(data, compress_min))
        )?;
        z.write_all(data)
    }
}

fn compress_check(b: &[u8], compress_min: usize) -> CompressionMethod {
    let lb = b.len();
    let nc = if lb > compress_min {
        let de = DeflateEncoder::new(b, flate2::Compression::best());
        let sum = de.bytes().count();
        sum < lb
    } else { false };
    if nc { CompressionMethod::DEFLATE } else { CompressionMethod::STORE }
}