1use std::{
2 env,
3 fs::File,
4 io::{Read, Seek, Write},
5 path::{Path, PathBuf},
6};
7
8use clap::Args;
9use color_eyre::eyre::Result;
10use fluent_templates::Loader;
11use novel_api::Timing;
12use walkdir::{DirEntry, WalkDir};
13use zip::{CompressionMethod, ZipWriter, write::SimpleFileOptions};
14
15use crate::{LANG_ID, LOCALES, utils};
16
17#[must_use]
18#[derive(Args)]
19#[command(arg_required_else_help = true,
20 about = LOCALES.lookup(&LANG_ID, "zip_command"))]
21pub struct Zip {
22 #[arg(help = LOCALES.lookup(&LANG_ID, "epub_dir_path"))]
23 pub epub_dir_path: PathBuf,
24
25 #[arg(short, long, default_value_t = false,
26 help = LOCALES.lookup(&LANG_ID, "delete"))]
27 pub delete: bool,
28}
29
30pub fn execute(config: Zip) -> Result<()> {
31 let mut timing = Timing::new();
32
33 utils::ensure_epub_dir(&config.epub_dir_path)?;
34
35 let epub_file_path = env::current_dir()?
36 .join(config.epub_dir_path.file_stem().unwrap())
37 .with_extension("epub");
38 if epub_file_path.try_exists()? {
39 tracing::warn!("The epub output file already exists and will be deleted");
40 utils::remove_file_or_dir(&epub_file_path)?;
41 }
42
43 let file = File::create(epub_file_path)?;
44 let walkdir = WalkDir::new(&config.epub_dir_path);
45 zip_dir(
46 &mut walkdir.into_iter().filter_map(|e| e.ok()),
47 &config.epub_dir_path,
48 file,
49 )?;
50
51 if config.delete {
52 utils::remove_file_or_dir(&config.epub_dir_path)?;
53 }
54
55 tracing::debug!("Time spent on `zip`: {}", timing.elapsed()?);
56
57 Ok(())
58}
59
60fn zip_dir<T, E>(iter: &mut dyn Iterator<Item = DirEntry>, prefix: T, writer: E) -> Result<()>
61where
62 T: AsRef<Path>,
63 E: Write + Seek,
64{
65 let mut zip = ZipWriter::new(writer);
66 let options = SimpleFileOptions::default()
67 .compression_method(CompressionMethod::Deflated)
68 .compression_level(Some(9));
69
70 let mut buffer = Vec::new();
71 for entry in iter {
72 let path = entry.path();
73 let name = path.strip_prefix(prefix.as_ref())?;
74
75 if path.is_file() {
76 zip.start_file(name.to_str().unwrap(), options)?;
77 let mut f = File::open(path)?;
78
79 f.read_to_end(&mut buffer)?;
80 zip.write_all(&buffer)?;
81 buffer.clear();
82 } else if !name.as_os_str().is_empty() {
83 zip.add_directory(name.to_str().unwrap(), options)?;
84 }
85 }
86 zip.finish()?;
87
88 Ok(())
89}