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
use std::{
    env,
    fs::{self, File},
    io,
    path::{Path, PathBuf},
};

use clap::Args;
use color_eyre::eyre::Result;
use fluent_templates::Loader;
use novel_api::Timing;
use tracing::{info, warn};
use zip::ZipArchive;

use crate::{utils, LANG_ID, LOCALES};

#[must_use]
#[derive(Args)]
#[command(arg_required_else_help = true,
    about = LOCALES.lookup(&LANG_ID, "unzip_command").unwrap())]
pub struct Unzip {
    #[arg(help = LOCALES.lookup(&LANG_ID, "epub_path").unwrap())]
    pub epub_path: PathBuf,

    #[arg(short, long, default_value_t = false,
        help = LOCALES.lookup(&LANG_ID, "delete").unwrap())]
    pub delete: bool,
}

pub fn execute(config: Unzip) -> Result<()> {
    let mut timing = Timing::new();

    utils::ensure_epub_file(&config.epub_path)?;

    unzip(&config.epub_path)?;

    if config.delete {
        utils::remove_file_or_dir(&config.epub_path)?;
    }

    info!("Time spent on `unzip`: {}", timing.elapsed()?);

    Ok(())
}

fn unzip<T>(path: T) -> Result<()>
where
    T: AsRef<Path>,
{
    let path = path.as_ref();

    let output_dir = env::current_dir()?.join(path.file_stem().unwrap());
    if output_dir.try_exists()? {
        warn!("The epub output directory already exists and will be deleted");
        utils::remove_file_or_dir(&output_dir)?;
    }

    let file = File::open(path)?;
    let mut archive = ZipArchive::new(file)?;

    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let outpath = match file.enclosed_name() {
            Some(path) => path.to_owned(),
            None => continue,
        };
        let outpath = output_dir.join(outpath);

        if (*file.name()).ends_with('/') {
            fs::create_dir_all(&outpath)?;
        } else {
            if let Some(p) = outpath.parent() {
                if !p.try_exists()? {
                    fs::create_dir_all(p)?;
                }
            }
            let mut outfile = fs::File::create(&outpath)?;
            io::copy(&mut file, &mut outfile)?;
        }

        #[cfg(unix)]
        {
            use std::fs::Permissions;
            use std::os::unix::fs::PermissionsExt;

            if let Some(mode) = file.unix_mode() {
                fs::set_permissions(&outpath, Permissions::from_mode(mode))?;
            }
        }
    }

    Ok(())
}