novel_cli/cmd/
unzip.rs

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