cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
mod resourcepack;

use crate::error::CobbleError;
use crate::error::CobbleResult;
use futures::try_join;
use futures::TryStreamExt;
pub use resourcepack::*;
use std::path::{Path, PathBuf};
use tokio::fs::read_dir;
use tokio_stream::wrappers::ReadDirStream;

/// Loads all resourcepacks from the minecraft folder.
#[cfg_attr(doc_cfg, doc(cfg(feature = "resourcepacks")))]
#[instrument(
    name = "load_resourcepacks",
    level = "debug",
    skip_all,
    fields(minecraft_path)
)]
pub async fn load_resourcepacks(
    minecraft_path: impl AsRef<Path> + Send,
) -> CobbleResult<Vec<Resourcepack>> {
    // Resourcepacks
    let mut resourcepacks_path = PathBuf::from(minecraft_path.as_ref());
    resourcepacks_path.push("resourcepacks");
    let resourcepacks_future = load_packs(resourcepacks_path, ResourcepackType::Resourcepack);

    // Texturepacks
    let mut texturepacks_path = PathBuf::from(minecraft_path.as_ref());
    texturepacks_path.push("texturepacks");
    let texturepacks_future = load_packs(texturepacks_path, ResourcepackType::Texturepack);

    let (resourcepacks, texturepacks) = try_join!(resourcepacks_future, texturepacks_future)?;

    let mut packs = vec![];
    packs.extend(resourcepacks);
    packs.extend(texturepacks);

    Ok(packs)
}

async fn load_packs(path: PathBuf, _type: ResourcepackType) -> CobbleResult<Vec<Resourcepack>> {
    if !path.is_dir() {
        trace!("{:?} directory is empty", _type);
        return Ok(vec![]);
    }

    trace!("Loading {:?}s...", _type);
    let file_stream = ReadDirStream::new(read_dir(path).await?);
    let packs = file_stream
        .map_err(CobbleError::from)
        .try_filter_map(|e| parse_resourcepack(e.path(), _type))
        .try_collect()
        .await?;

    Ok(packs)
}

#[instrument(name = "parse_resourcepack", level = "trace", skip_all, fields(path))]
pub(crate) async fn parse_resourcepack(
    path: impl AsRef<Path>,
    _type: ResourcepackType,
) -> CobbleResult<Option<Resourcepack>> {
    // Check if file
    if !path.as_ref().is_file() {
        trace!("Entry is not a file.");
        return Ok(None);
    }

    // Check if archive
    let zip_mime = "application/zip".parse::<mime_guess::Mime>().unwrap();
    let mime = match mime_guess::from_path(&path).first() {
        Some(mime) => mime,
        None => {
            trace!("Could not get MIME type for file.");
            return Ok(None);
        }
    };
    if mime.type_() != zip_mime.type_() {
        trace!("Entry is not an archive.");
        return Ok(None);
    }

    // Parse
    let name = match path.as_ref().file_name() {
        Some(name) => name.to_string_lossy().to_string(),
        None => return Ok(None),
    };

    Ok(Some(Resourcepack {
        name,
        path: PathBuf::from(path.as_ref()),
        _type,
    }))
}