extract/
extract.rs

1use chmlib::{ChmFile, Filter, UnitInfo};
2use std::{
3    env,
4    error::Error,
5    fs::{self, File},
6    io::Write,
7    path::{Path, PathBuf},
8};
9
10fn main() {
11    let args: Vec<_> = env::args().skip(1).collect();
12    if args.len() != 2 || args.iter().any(|arg| arg.contains("-h")) {
13        println!("Usage: extract <chm-file> <out-dir>");
14        return;
15    }
16
17    let mut file = ChmFile::open(&args[0]).expect("Unable to open the file");
18
19    let out_dir = PathBuf::from(&args[1]);
20
21    file.for_each(Filter::all(), |file, item| extract(&out_dir, file, &item))
22        .unwrap();
23}
24
25fn extract(
26    root_dir: &Path,
27    file: &mut ChmFile,
28    item: &UnitInfo,
29) -> Result<(), Box<dyn Error>> {
30    if !item.is_file() || !item.is_normal() {
31        // we only care about normal files
32        return Ok(());
33    }
34    let path = match item.path() {
35        Some(p) => p,
36        // if we can't get the path, ignore it and continue
37        None => return Ok(()),
38    };
39
40    let mut dest = root_dir.to_path_buf();
41    // Note: by design, the path for a normal file is absolute (starts with "/")
42    // so when joining it with the root_dir we need to drop the initial "/".
43    dest.extend(path.components().skip(1));
44
45    // make sure the parent directory exists
46    if let Some(parent) = dest.parent() {
47        fs::create_dir_all(parent)?;
48    }
49
50    let mut f = File::create(dest)?;
51    let mut start_offset = 0;
52    // CHMLib doesn't give us a &[u8] with the file contents directly (e.g.
53    // because it may be compressed) so we need to copy chunks to an
54    // intermediate buffer
55    let mut buffer = vec![0; 1 << 16];
56
57    loop {
58        let bytes_read = file.read(item, start_offset, &mut buffer)?;
59        if bytes_read == 0 {
60            // we've reached the end of the file
61            break;
62        } else {
63            // write this chunk to the file and continue
64            start_offset += bytes_read as u64;
65            f.write_all(&buffer)?;
66        }
67    }
68
69    Ok(())
70}