cdbk 0.3.1

a way to bundle apps for linux
use std::{
    fs::{self, File, remove_file},
    io::Write,
    path::{Component, Path, PathBuf},
};

use cmdparsing::define;
use zip::{CompressionMethod, ZipWriter, write::SimpleFileOptions};

use crate::{locations::rename, manifest::Manifest};

fn first_real_component(p: &Path) -> Option<&std::ffi::OsStr> {
    p.components()
        .find(|c| matches!(c, Component::Normal(_)))
        .map(|c| match c {
            Component::Normal(s) => s,
            _ => unreachable!(),
        })
}

const HELP: &str = r#"usage: cdbk package [file path]
===== FLAGS ===== 
-o|--out [path]: the output path
-c|--compress: an option to compress the bundle
"#;

define! {
    Arguments;
    help: HELP;
    flags {
        ouput: PathBuf = "o"|"out",
        compress: bool = "c" | "compress",
    };
    args {
        input: PathBuf,
    };
}

pub fn package(mut v: Vec<String>) {
    let mut bak = false;
    v.remove(0);
    let args = Arguments::from(v);
    let out = args.ouput.unwrap_or_else(|| {
        let mut out = args.input.clone();
        out.set_extension("cdbk");
        return out;
    });
    let mut new = out.clone();
    new.add_extension("bak");
    if out.exists() {
        rename(&out, &new).unwrap();
        bak = true;
    }

    let Ok(mut file) = File::create(&out) else {
        eprintln!("error creating file");
        remove_file(&out).unwrap();
        if bak {
            rename(&new, &out).unwrap();
        }
        return;
    };
    file.write(b"CDBK").unwrap();
    let options;
    if args.compress {
        options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
    } else {
        options = SimpleFileOptions::default().compression_method(CompressionMethod::Stored);
    }
    let mut zip = ZipWriter::new(file);
    let mut path = args.input;
    path.push("manifest.toml");
    let Ok(data) = fs::read_to_string(&path) else {
        eprintln!("file {:?} does not exist", path);
        remove_file(&out).unwrap();
        if bak {
            rename(&new, &out).unwrap();
        }
        return;
    };
    zip.start_file("manifest.toml", options).unwrap();
    zip.write_all(data.as_bytes()).unwrap();
    let Ok(manifest) = toml::from_str::<Manifest>(&data) else {
        eprintln!("error parsing manifest.toml");
        remove_file(&out).unwrap();
        if bak {
            rename(&new, &out).unwrap();
        }
        return;
    };
    let payload = manifest.payload;
    if payload.components().count() <= 0 {
        eprintln!("incorrect manifest");
        remove_file(&out).unwrap();
        if bak {
            rename(&new, &out).unwrap();
        }
        return;
    } else if payload.components().count() > 1 {
        path.pop();
        path.push(first_real_component(&payload).expect("incorrect manifest"));
        zip.add_directory_from_path(&path, options).unwrap();
    } else {
        path.pop();
        path.push(&payload);
        let Ok(data) = fs::read(&path) else {
            eprintln!("file {:?} does not exist", path);
            remove_file(&out).unwrap();
            if bak {
                rename(&new, &out).unwrap();
            }
            return;
        };
        zip.start_file(payload.to_str().unwrap(), options).unwrap();
        zip.write_all(data.as_slice()).unwrap();
    }

    if let Some(icon) = manifest.icon {
        if icon.components().count() != 1 {
            eprintln!("incorrect manifest");
            remove_file(&out).unwrap();
            if bak {
                rename(&new, &out).unwrap();
            }
            return;
        }
        path.pop();
        path.push(&icon);
        let Ok(data) = fs::read(&path) else {
            eprintln!("file {:?} does not exist", path);
            remove_file(&out).unwrap();
            if bak {
                rename(&new, &out).unwrap();
            }
            return;
        };

        zip.start_file(icon.to_str().unwrap(), options).unwrap();
        zip.write_all(data.as_slice()).unwrap();
    }

    zip.finish().unwrap();

    if bak {
        remove_file(&new).unwrap();
    }
}