epub_builder/
zip_library.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with
3// this file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::zip::Zip;
6
7use std::fmt;
8use std::io;
9use std::io::Cursor;
10use std::io::Read;
11use std::io::Write;
12use std::path::Path;
13
14use crate::Result;
15use libzip::CompressionMethod;
16use libzip::ZipWriter;
17
18/// Zip files using the [Rust `zip`](https://crates.io/crates/zip) library.
19///
20/// While this has the advantage of not requiring an external `zip` command, I have
21/// run into some issues when trying to export EPUB generated with this method to
22/// ereaders (e.g. Kobo).
23///
24/// Note that these takes care of adding the mimetype (since it must not be deflated), it
25/// should not be added manually.
26pub struct ZipLibrary {
27    writer: ZipWriter<Cursor<Vec<u8>>>,
28}
29
30impl fmt::Debug for ZipLibrary {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        write!(f, "ZipLibrary")
33    }
34}
35
36impl ZipLibrary {
37    /// Creates a new wrapper for zip library
38    ///
39    /// Also add mimetype at the beginning of the EPUB file.
40    pub fn new() -> Result<ZipLibrary> {
41        let mut writer = ZipWriter::new(Cursor::new(vec![]));
42        writer.set_comment(""); // Fix issues with some readers
43        writer.start_file(
44            "mimetype",
45            libzip::write::SimpleFileOptions::default().compression_method(CompressionMethod::Stored),
46        )?;
47        writer
48            .write(b"application/epub+zip")
49            .map_err(|e| crate::Error::IoError {
50                msg: "could not write mimetype in epub".to_string(),
51                cause: e,
52            })?;
53
54        Ok(ZipLibrary { writer })
55    }
56}
57
58impl Zip for ZipLibrary {
59    fn write_file<P: AsRef<Path>, R: Read>(&mut self, path: P, mut content: R) -> Result<()> {
60        let mut file = format!("{}", path.as_ref().display());
61        if cfg!(target_os = "windows") {
62            // Path names should not use backspaces in zip files
63            file = file.replace('\\', "/");
64        }
65        let options = libzip::write::SimpleFileOptions::default();
66        self.writer.start_file(file.clone(), options).map_err(|e| {
67            crate::Error::ZipErrorWithMessage {
68                msg: format!("could not create file '{}' in epub", file),
69                cause: e,
70            }
71        })?;
72        io::copy(&mut content, &mut self.writer).map_err(|e| crate::Error::IoError {
73            msg: format!("could not write file '{}' in epub", file),
74            cause: e,
75        })?;
76        Ok(())
77    }
78
79    fn generate<W: Write>(self, mut to: W) -> Result<()> {
80        let cursor = self
81            .writer
82            .finish()
83            .map_err(|e| crate::Error::ZipErrorWithMessage {
84                msg: "error writing zip file".to_string(),
85                cause: e,
86            })?;
87        let bytes = cursor.into_inner();
88        to.write_all(bytes.as_ref())
89            .map_err(|e| crate::Error::IoError {
90                msg: "error writing to file".to_string(),
91                cause: e,
92            })?;
93        Ok(())
94    }
95}