cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
use crate::error::CobbleResult;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tokio::task;

/// Represents a single server.
#[cfg_attr(doc_cfg, doc(cfg(feature = "servers")))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Server {
    /// Name of the server.
    pub name: String,
    /// IP address of the server.
    pub ip: String,
    /// Whether the user accepted server resource packs.
    pub accept_textures: AcceptTextures,
    /// Path of the servers.dat file.
    pub path: PathBuf,
}

impl Server {
    /// Adds a server to the servers.dat file.
    #[instrument(
        name = "add_server",
        level = "trace",
        skip_all,
        fields(
            name = server.name,
            ip = server.ip,
            servers_file = %server.path.to_string_lossy(),
        )
    )]
    pub async fn add(server: Self) -> CobbleResult<()> {
        let servers_file = server.path.clone();
        task::spawn_blocking(move || -> CobbleResult<()> {
            let nbt_file = std::fs::File::open(&servers_file)?;

            let mut servers = fastnbt::from_reader::<_, ServersDat>(nbt_file)?;
            let new_server = NbtServer::from(server);
            servers.servers.push(new_server);

            let nbt_file = std::fs::File::create(&servers_file)?;
            fastnbt::to_writer(nbt_file, &servers)?;

            Ok(())
        })
        .await?
    }

    /// Removes the server from its servers.dat file.
    #[instrument(
        name = "remove_server",
        level = "trace",
        skip_all,
        fields(
            name = self.name,
            ip = self.ip,
            servers_file = %self.path.to_string_lossy(),
        )
    )]
    pub async fn remove(self) -> CobbleResult<()> {
        let servers_file = self.path.clone();
        task::spawn_blocking(move || -> CobbleResult<()> {
            let nbt_file = std::fs::File::open(&servers_file)?;

            let mut servers = fastnbt::from_reader::<_, ServersDat>(nbt_file)?;
            let index = servers
                .servers
                .iter()
                .position(|server| server.name == self.name && server.ip == self.ip);
            if let Some(index) = index {
                servers.servers.remove(index);
            }

            let nbt_file = std::fs::File::create(&servers_file)?;
            fastnbt::to_writer(nbt_file, &servers)?;

            Ok(())
        })
        .await?
    }

    /// Loads the server icon and decodes it into PNG bytes.
    #[instrument(
        name = "decode_server_icon",
        level = "trace",
        skip_all,
        fields(
            server_name = self.name,
            server_ip = self.ip,
            servers_file = %self.path.to_string_lossy(),
        )
    )]
    pub async fn decode_icon(&self) -> CobbleResult<Option<Vec<u8>>> {
        let this = self.clone();
        task::spawn_blocking(move || match this.load_base64_icon()? {
            Some(icon_string) => Ok(Some(base64::decode(icon_string)?)),
            None => Ok(None),
        })
        .await?
    }

    /// Loads the server icon as a base64 string.
    #[instrument(
        name = "load_server_base64_icon",
        level = "trace",
        skip_all,
        fields(
            server_name = self.name,
            server_ip = self.ip,
            servers_file = %self.path.to_string_lossy(),
        )
    )]
    pub fn load_base64_icon(&self) -> CobbleResult<Option<String>> {
        let nbt_file = std::fs::File::open(&self.path)?;

        let icon = fastnbt::from_reader::<_, ServersDat>(nbt_file)?
            .servers
            .into_iter()
            .find(|server| server.name == self.name && server.ip == self.ip)
            .and_then(|server| server.icon);

        Ok(icon)
    }
}

/// Whether to install a server resource pack
#[cfg_attr(doc_cfg, doc(cfg(feature = "servers")))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AcceptTextures {
    /// User gets prompted.
    Prompt,
    /// Server resources are enabled.
    Enabled,
    /// Server resources are disabled.
    Disabled,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub(super) struct ServersDat {
    pub servers: Vec<NbtServer>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub(super) struct NbtServer {
    pub icon: Option<String>,
    pub ip: String,
    pub name: String,
    #[serde(rename = "acceptTextures")]
    pub accept_textures: Option<u8>,
}

impl From<Server> for NbtServer {
    fn from(value: Server) -> Self {
        let accept_textures = match value.accept_textures {
            AcceptTextures::Prompt => None,
            AcceptTextures::Enabled => Some(1),
            AcceptTextures::Disabled => Some(0),
        };

        Self {
            icon: None,
            ip: value.ip,
            name: value.name,
            accept_textures,
        }
    }
}