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}