loadsmith-manifest 0.2.0

Manifest and lockfile format, resolver, and profile state for the loadsmith mod-manager library
use std::{fs, future::Future, io::Cursor, path::Path};

use loadsmith_core::FileUrl;
use url::Url;

use crate::{Error, Result};

pub async fn download_and_extract<F, Fut, E>(
    file: FileUrl,
    target: impl AsRef<Path>,
    download: F,
) -> Result<()>
where
    F: FnOnce(Url) -> Fut,
    Fut: Future<Output = std::result::Result<Vec<u8>, E>>,
    E: std::error::Error + Send + Sync + 'static,
{
    let target = target.as_ref();

    let zip_bytes = match file {
        FileUrl::Url(url) => download(url)
            .await
            .map_err(|err| Error::Download(Box::new(err)))?,
        FileUrl::Path(path) => {
            let metadata = path.metadata()?;

            if metadata.is_dir() {
                fs::create_dir_all(target)?;
                loadsmith_util::copy_dir(&path, target)?;
                return Ok(());
            }

            if path.extension().is_none_or(|ext| ext != "zip") {
                let canon = path.canonicalize()?;
                let file_name = canon
                    .file_name()
                    .expect("file name should exist for a file path");

                fs::create_dir_all(target)?;
                fs::copy(&canon, target.join(file_name))?;
                return Ok(());
            }

            fs::read(&path)?
        }
    };

    loadsmith_install::extract(Cursor::new(zip_bytes), target)?;

    Ok(())
}