novel-api 0.19.1

Novel APIs from various sources
Documentation
use std::fs::{self, File};
use std::io::{self, Read, Seek, Write};
use std::path::Path;

use walkdir::WalkDir;
use zip::write::SimpleFileOptions;
use zip::{CompressionMethod, ZipArchive, ZipWriter};

use crate::Error;

pub fn unzip<T, R>(reader: T, out_dir: R) -> Result<(), Error>
where
    T: Read + Seek,
    R: AsRef<Path>,
{
    let mut archive = ZipArchive::new(reader)?;

    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let out_path = file.enclosed_name().ok_or(Error::NovelApi(format!(
            "unable to extract file {} because it has an invalid path",
            file.name()
        )))?;
        let out_path = out_dir.as_ref().join(out_path);

        if file.is_dir() {
            fs::create_dir_all(&out_path)?;
        } else {
            if let Some(p) = out_path.parent()
                && !p.try_exists()?
            {
                fs::create_dir_all(p)?;
            }

            File::create(&out_path).and_then(|mut outfile| 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(&out_path, Permissions::from_mode(mode))?;
            }
        }
    }

    Ok(())
}

pub fn zip_dir<T, R>(src_dir: T, dest_file: R) -> Result<(), Error>
where
    T: AsRef<Path>,
    R: AsRef<Path>,
{
    let file = File::create(dest_file)?;
    let walkdir = WalkDir::new(src_dir.as_ref());

    let mut zip = ZipWriter::new(file);
    let options = SimpleFileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .unix_permissions(0o644);

    let prefix = src_dir.as_ref();
    let mut buffer = Vec::new();
    for entry in walkdir.into_iter().filter_map(|e| e.ok()) {
        let path = entry.path();
        let name = path.strip_prefix(prefix)?;
        let path_as_string = name
            .to_str()
            .map(str::to_owned)
            .ok_or_else(|| Error::NovelApi(format!("{name:?} is a Non UTF-8 Path")))?;

        if path.is_file() {
            zip.start_file(path_as_string, options)?;
            let mut f = File::open(path)?;

            f.read_to_end(&mut buffer)?;
            zip.write_all(&buffer)?;
            buffer.clear();
        } else if !name.as_os_str().is_empty() {
            zip.add_directory(path_as_string, options)?;
        }
    }
    zip.finish()?;

    Ok(())
}