cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
use crate::error::CobbleResult;
use crate::utils::Either;
use async_zip::read::seek::ZipFileReader;
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::fs::{remove_file, rename, File};

/// Represents a single loader mod.
#[cfg_attr(doc_cfg, doc(cfg(feature = "loader-mods")))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug)]
pub struct LoaderMod {
    /// Mod name
    pub name: String,
    /// Mod version
    pub version: String,
    /// Mod description
    pub description: Option<String>,
    /// Icon path relative to the archive root
    pub icon_path: Option<String>,
    /// Filepath
    pub path: PathBuf,
    /// Whether the mod is enabled
    pub enabled: bool,
}

impl LoaderMod {
    pub(crate) const DISABLED_JAR_SUFFIX: &str = ".jar.disabled";
    pub(crate) const ENABLED_JAR_SUFFIX: &str = ".jar";

    /// Removes the loader mod from disk.
    ///
    /// **Warning**: This will permanently delete the file!
    #[instrument(
        name = "remove_loader_mod",
        level = "trace",
        skip_all,
        fields(
            name,
            path = %self.path.to_string_lossy(),
        )
    )]
    pub async fn remove(self) -> CobbleResult<()> {
        remove_file(&self.path).await?;
        Ok(())
    }

    /// Enables the mod by removing the `.disabled` suffix from the file name.
    #[instrument(
        name = "enable_loader_mod",
        level = "trace",
        skip_all,
        fields(
            name,
            path = %self.path.to_string_lossy(),
        )
    )]
    pub async fn enable(&mut self) -> CobbleResult<()> {
        let new_path = self
            .path
            .to_string_lossy()
            .to_string()
            .replace(Self::DISABLED_JAR_SUFFIX, Self::ENABLED_JAR_SUFFIX);

        rename(&self.path, &new_path).await?;

        self.path = PathBuf::from(new_path);
        self.enabled = true;

        Ok(())
    }

    /// Disables the mod by adding the `.disabled` suffix to the file name.
    #[instrument(
        name = "disable_loader_mod",
        level = "trace",
        skip_all,
        fields(
            name,
            path = %self.path.to_string_lossy(),
        )
    )]
    pub async fn disable(&mut self) -> CobbleResult<()> {
        let new_path = self
            .path
            .to_string_lossy()
            .to_string()
            .replace(Self::ENABLED_JAR_SUFFIX, Self::DISABLED_JAR_SUFFIX);

        rename(&self.path, &new_path).await?;

        self.path = PathBuf::from(new_path);
        self.enabled = false;

        Ok(())
    }

    /// Loads the icon from the loader mod archive.
    #[instrument(
            name = "loader_mod_icon",
            level = "trace",
            skip_all,
            fields(
                name = self.name,
                path = %self.path.to_string_lossy(),
            )
        )]
    pub async fn icon(&self) -> CobbleResult<Option<Vec<u8>>> {
        trace!("Trying to extract icon from archive");
        let mut file = File::open(&self.path).await?;
        let mut archive = ZipFileReader::new(&mut file).await?;

        let icon_path = match &self.icon_path {
            Some(path) => path,
            None => return Ok(None),
        };

        let icon = match archive.entry(icon_path) {
            Some((i, _entry)) => {
                trace!("Found icon file");
                let entry_reader = archive.entry_reader(i).await?;
                let bytes = entry_reader.read_to_end_crc().await?;
                Some(bytes)
            }
            None => {
                trace!("Loader mod does not contain an icon file");
                None
            }
        };

        Ok(icon)
    }
}

#[derive(Clone, Debug, serde::Deserialize)]
pub(crate) struct ModMetadata {
    pub id: String,
    pub version: String,
    pub name: Option<String>,
    pub description: Option<String>,
    pub icon: Option<Either<String, HashMap<String, String>>>,
}