use zip::Zip;
use errors::Result;
use errors::ResultExt;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::io::Read;
use std::io::Write;
use std::fs;
use std::fs::DirBuilder;
use std::fs::File;
use std::process::Command;
use tempdir::TempDir;
pub struct ZipCommand {
command: String,
temp_dir: TempDir,
files: Vec<PathBuf>,
}
impl ZipCommand {
pub fn new() -> Result<ZipCommand> {
let temp_dir = TempDir::new("epub").chain_err(|| "could not create temporary directory")?;
let zip = ZipCommand {
command: String::from("zip"),
temp_dir: temp_dir,
files: vec![],
};
Ok(zip)
}
pub fn new_in<P: AsRef<Path>>(temp_path: P) -> Result<ZipCommand> {
let temp_dir = TempDir::new_in(temp_path, "epub")
.chain_err(|| "could not create temporary directory")?;
let zip = ZipCommand {
command: String::from("zip"),
temp_dir: temp_dir,
files: vec![],
};
Ok(zip)
}
pub fn command<S: Into<String>>(&mut self, command: S) -> &mut Self {
self.command = command.into();
self
}
fn add_to_tmp_dir<P: AsRef<Path>, R: Read>(&mut self, path: P, mut content: R) -> Result<()> {
let dest_file = self.temp_dir
.path()
.join(path.as_ref());
let dest_dir = dest_file.parent().unwrap();
if !fs::metadata(dest_dir).is_ok() {
DirBuilder::new().recursive(true)
.create(&dest_dir)
.chain_err(|| {
format!("could not create temporary directory in {path}",
path = dest_dir.display())
})?;
}
let mut f = File::create(&dest_file).chain_err(|| {
format!("could not write to temporary file {file}",
file = path.as_ref().display())
})?;
io::copy(&mut content, &mut f).chain_err(|| {
format!("could not write to temporary file {file}",
file = path.as_ref().display())
})?;
Ok(())
}
}
impl Zip for ZipCommand {
fn write_file<P: AsRef<Path>, R: Read>(&mut self, path: P, content: R) -> Result<()> {
let path = path.as_ref();
if path.starts_with("..") || path.is_absolute() {
bail!("file {file} refers to a path outside the temporary directory. This is \
verbotten!");
}
self.add_to_tmp_dir(path, content)?;
self.files.push(path.to_path_buf());
Ok(())
}
fn generate<W: Write>(&mut self, mut to: W) -> Result<()> {
self.add_to_tmp_dir("mimetype", b"application/epub+zip".as_ref())?;
let output = Command::new(&self.command)
.current_dir(self.temp_dir.path())
.arg("-X0")
.arg("output.epub")
.arg("mimetype")
.output()
.chain_err(|| format!("failed to run command {name}", name = self.command))?;
if !output.status.success() {
bail!("command {name} didn't return succesfully: {output}",
name = self.command,
output = String::from_utf8_lossy(&output.stderr));
}
let mut command = Command::new(&self.command);
command.current_dir(self.temp_dir.path())
.arg("-9")
.arg("output.epub");
for file in &self.files {
command.arg(format!("{}", file.display()));
}
let output = command.output()
.chain_err(|| format!("failed to run command {name}", name = self.command))?;
if output.status.success() {
let mut f = File::open(self.temp_dir.path().join("output.epub"))
.chain_err(|| "error reading temporary epub file")?;
io::copy(&mut f, &mut to)
.chain_err(|| "error writing result of the zip command")?;
Ok(())
} else {
bail!("command {name} didn't return succesfully: {output}",
name = self.command,
output = String::from_utf8_lossy(&output.stderr));
}
}
}