cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
use crate::error::CobbleResult;
use serde::Deserialize;
use std::path::PathBuf;
use time::OffsetDateTime;
use tokio::fs::{remove_dir_all, File};
use tokio::io::AsyncReadExt;

/// Represents a single save game.
#[cfg_attr(doc_cfg, doc(cfg(feature = "save-games")))]
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct SaveGame {
    /// Name of the save game
    pub name: String,
    /// Path to the save game
    pub path: PathBuf,
    /// Difficulty of the save game.
    /// No value means the difficulty is globally set (older versions).
    pub difficulty: Option<Difficulty>,
    /// Game type for new players
    pub game_type: GameType,
    /// Game version the save game was last saved in
    pub game_version: Option<String>,
    /// The seed of the save game
    pub seed: Option<i64>,
    /// Last played timestamp
    #[cfg_attr(feature = "serde", serde(with = "time::serde::rfc3339"))]
    pub last_played: OffsetDateTime,
}

impl SaveGame {
    /// Exports the save game folder using a gzip compressed tar archive.
    /// The compression is a level is an integer from 0-9 where 0 means "no
    /// compression" and 9 means "take as long as you'd like".
    #[instrument(
        name = "export_save_game",
        level = "trace",
        skip_all,
        fields(
            name = self.name,
            path = %self.path.to_string_lossy(),
            dest,
            compression,
        )
    )]
    #[cfg_attr(doc_cfg, doc(cfg(feature = "backup")))]
    #[cfg(feature = "backup")]
    pub async fn export(
        &self,
        dest: impl AsRef<std::path::Path>,
        compression: u32,
    ) -> CobbleResult<()> {
        crate::utils::archive_entity(self.clone(), dest, compression).await
    }

    /// Imports a save game archive.
    /// The archive needs to contain the marker file.
    /// Validates the imported save game and returns `None` if the save game could not be parsed.
    #[instrument(
        name = "import_save_game",
        level = "trace",
        skip_all,
        fields(saves_path, src,)
    )]
    #[cfg_attr(doc_cfg, doc(cfg(feature = "backup")))]
    #[cfg(feature = "backup")]
    pub async fn import(
        saves_path: impl AsRef<std::path::Path>,
        src: impl AsRef<std::path::Path>,
    ) -> CobbleResult<Option<SaveGame>> {
        let old_save_game =
            crate::utils::unpack_entity_archive::<SaveGame>(src, &saves_path).await?;
        let old_dir_name = old_save_game
            .path
            .file_name()
            .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "Path ends with '..'"))?;

        let mut new_dir_path = PathBuf::from(saves_path.as_ref());
        new_dir_path.push(old_dir_name);

        super::parse_save_game(new_dir_path).await
    }

    /// Removes the whole save game folder.
    ///
    /// **Warning**: This will permanently delete the save game!
    #[instrument(
        name = "remove_save_game",
        level = "trace",
        skip_all,
        fields(
            name = self.name,
            path = %self.path.to_string_lossy(),
        )
    )]
    pub async fn remove(self) -> CobbleResult<()> {
        remove_dir_all(&self.path).await?;
        Ok(())
    }

    /// Loads the icon from the save game.
    #[instrument(
        name = "save_game_icon",
        level = "trace",
        skip_all,
        fields(
            name = self.name,
            path = %self.path.to_string_lossy(),
        )
    )]
    pub async fn icon(&self) -> CobbleResult<Option<Vec<u8>>> {
        let path = self.icon_path();

        if path.is_file() {
            let mut file = File::open(path).await?;
            let mut buf = vec![];
            file.read_to_end(&mut buf).await?;
            return Ok(Some(buf));
        }

        Ok(None)
    }

    /// Gets the path of the icon.png file.
    pub fn icon_path(&self) -> PathBuf {
        let mut icon_path = self.path.clone();
        icon_path.push("icon.png");
        icon_path
    }
}

#[cfg_attr(doc_cfg, doc(cfg(feature = "backup")))]
#[cfg(feature = "backup")]
impl crate::utils::ArchivableEntity for SaveGame {
    const MARKER_NAME: &'static str = "cobble_save_game.json";

    fn path(&self) -> PathBuf {
        self.path.clone()
    }

    fn marker_path(&self) -> PathBuf {
        let mut path = self.path();
        path.push(Self::MARKER_NAME);
        path
    }
}

/// Difficulty of a save game
#[cfg_attr(doc_cfg, doc(cfg(feature = "save-games")))]
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub enum Difficulty {
    /// Peaceful
    Peaceful,
    /// Easy
    Easy,
    /// Normal
    Normal,
    /// Hard
    Hard,
}

/// Game type of a save game
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub enum GameType {
    /// Survival
    Survival,
    /// Creative
    Creative,
    /// Adventure
    Adventure,
    /// Spectator
    Spectator,
}

#[derive(Clone, Debug, Deserialize)]
pub(super) struct LevelDat {
    #[serde(rename = "Data")]
    pub data: LevelData,
}

#[derive(Clone, Debug, Deserialize)]
pub(super) struct LevelData {
    #[serde(rename = "Difficulty")]
    pub difficulty: Option<u8>,
    #[serde(rename = "WorldGenSettings")]
    pub world_gen_settings: Option<WorldGenSettings>,
    #[serde(rename = "GameType", default)]
    pub game_type: i32,
    #[serde(rename = "LastPlayed")]
    pub last_played: i64,
    #[serde(rename = "LevelName")]
    pub level_name: String,
    #[serde(rename = "RandomSeed")]
    pub random_seed: Option<i64>,
    #[serde(rename = "Version")]
    pub version: Option<Version>,
}

#[derive(Clone, Debug, Deserialize)]
pub(super) struct WorldGenSettings {
    pub seed: i64,
}

#[derive(Clone, Debug, Deserialize)]
pub(super) struct Version {
    #[serde(rename = "Name")]
    pub name: String,
}