novel_cli/cmd/
unzip.rs

1use std::fs::{self, File};
2use std::path::{Path, PathBuf};
3use std::{env, io};
4
5use clap::Args;
6use color_eyre::eyre::Result;
7use fluent_templates::Loader;
8use novel_api::Timing;
9use zip::ZipArchive;
10
11use crate::{LANG_ID, LOCALES, utils};
12
13#[must_use]
14#[derive(Args)]
15#[command(arg_required_else_help = true,
16    about = LOCALES.lookup(&LANG_ID, "unzip_command"))]
17pub struct Unzip {
18    #[arg(help = LOCALES.lookup(&LANG_ID, "epub_path"))]
19    pub epub_path: PathBuf,
20
21    #[arg(short, long, default_value_t = false,
22        help = LOCALES.lookup(&LANG_ID, "delete"))]
23    pub delete: bool,
24}
25
26pub fn execute(config: Unzip) -> Result<()> {
27    let mut timing = Timing::new();
28
29    utils::ensure_epub_file(&config.epub_path)?;
30
31    unzip(&config.epub_path)?;
32
33    if config.delete {
34        utils::remove_file_or_dir(&config.epub_path)?;
35    }
36
37    tracing::debug!("Time spent on `unzip`: {}", timing.elapsed()?);
38
39    Ok(())
40}
41
42fn unzip<T>(path: T) -> Result<()>
43where
44    T: AsRef<Path>,
45{
46    let path = path.as_ref();
47
48    let output_dir = env::current_dir()?.join(path.file_stem().unwrap());
49    if output_dir.try_exists()? {
50        tracing::warn!("The epub output directory already exists and will be deleted");
51        utils::remove_file_or_dir(&output_dir)?;
52    }
53
54    let file = File::open(path)?;
55    let mut archive = ZipArchive::new(file)?;
56
57    for i in 0..archive.len() {
58        let mut file = archive.by_index(i)?;
59        let outpath = match file.enclosed_name() {
60            Some(path) => path.to_owned(),
61            None => continue,
62        };
63        let outpath = output_dir.join(outpath);
64
65        if (*file.name()).ends_with('/') {
66            fs::create_dir_all(&outpath)?;
67        } else {
68            if let Some(p) = outpath.parent() {
69                if !p.try_exists()? {
70                    fs::create_dir_all(p)?;
71                }
72            }
73            let mut outfile = fs::File::create(&outpath)?;
74            io::copy(&mut file, &mut outfile)?;
75        }
76
77        #[cfg(unix)]
78        {
79            use std::fs::Permissions;
80            use std::os::unix::fs::PermissionsExt;
81
82            if let Some(mode) = file.unix_mode() {
83                fs::set_permissions(&outpath, Permissions::from_mode(mode))?;
84            }
85        }
86    }
87
88    Ok(())
89}