cobble_core/minecraft/servers/
server.rs

1use crate::error::CobbleResult;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4use tokio::task;
5
6/// Represents a single server.
7#[cfg_attr(doc_cfg, doc(cfg(feature = "servers")))]
8#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct Server {
11    /// Name of the server.
12    pub name: String,
13    /// IP address of the server.
14    pub ip: String,
15    /// Whether the user accepted server resource packs.
16    pub accept_textures: AcceptTextures,
17    /// Path of the servers.dat file.
18    pub path: PathBuf,
19}
20
21impl Server {
22    /// Adds a server to the servers.dat file.
23    #[instrument(
24        name = "add_server",
25        level = "trace",
26        skip_all,
27        fields(
28            name = server.name,
29            ip = server.ip,
30            servers_file = %server.path.to_string_lossy(),
31        )
32    )]
33    pub async fn add(server: Self) -> CobbleResult<()> {
34        let servers_file = server.path.clone();
35        task::spawn_blocking(move || -> CobbleResult<()> {
36            let nbt_file = std::fs::File::open(&servers_file)?;
37
38            let mut servers = fastnbt::from_reader::<_, ServersDat>(nbt_file)?;
39            let new_server = NbtServer::from(server);
40            servers.servers.push(new_server);
41
42            let nbt_file = std::fs::File::create(&servers_file)?;
43            fastnbt::to_writer(nbt_file, &servers)?;
44
45            Ok(())
46        })
47        .await?
48    }
49
50    /// Removes the server from its servers.dat file.
51    #[instrument(
52        name = "remove_server",
53        level = "trace",
54        skip_all,
55        fields(
56            name = self.name,
57            ip = self.ip,
58            servers_file = %self.path.to_string_lossy(),
59        )
60    )]
61    pub async fn remove(self) -> CobbleResult<()> {
62        let servers_file = self.path.clone();
63        task::spawn_blocking(move || -> CobbleResult<()> {
64            let nbt_file = std::fs::File::open(&servers_file)?;
65
66            let mut servers = fastnbt::from_reader::<_, ServersDat>(nbt_file)?;
67            let index = servers
68                .servers
69                .iter()
70                .position(|server| server.name == self.name && server.ip == self.ip);
71            if let Some(index) = index {
72                servers.servers.remove(index);
73            }
74
75            let nbt_file = std::fs::File::create(&servers_file)?;
76            fastnbt::to_writer(nbt_file, &servers)?;
77
78            Ok(())
79        })
80        .await?
81    }
82
83    /// Loads the server icon and decodes it into PNG bytes.
84    #[instrument(
85        name = "decode_server_icon",
86        level = "trace",
87        skip_all,
88        fields(
89            server_name = self.name,
90            server_ip = self.ip,
91            servers_file = %self.path.to_string_lossy(),
92        )
93    )]
94    pub async fn decode_icon(&self) -> CobbleResult<Option<Vec<u8>>> {
95        let this = self.clone();
96        task::spawn_blocking(move || match this.load_base64_icon()? {
97            Some(icon_string) => Ok(Some(base64::decode(icon_string)?)),
98            None => Ok(None),
99        })
100        .await?
101    }
102
103    /// Loads the server icon as a base64 string.
104    #[instrument(
105        name = "load_server_base64_icon",
106        level = "trace",
107        skip_all,
108        fields(
109            server_name = self.name,
110            server_ip = self.ip,
111            servers_file = %self.path.to_string_lossy(),
112        )
113    )]
114    pub fn load_base64_icon(&self) -> CobbleResult<Option<String>> {
115        let nbt_file = std::fs::File::open(&self.path)?;
116
117        let icon = fastnbt::from_reader::<_, ServersDat>(nbt_file)?
118            .servers
119            .into_iter()
120            .find(|server| server.name == self.name && server.ip == self.ip)
121            .and_then(|server| server.icon);
122
123        Ok(icon)
124    }
125}
126
127/// Whether to install a server resource pack
128#[cfg_attr(doc_cfg, doc(cfg(feature = "servers")))]
129#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub enum AcceptTextures {
132    /// User gets prompted.
133    Prompt,
134    /// Server resources are enabled.
135    Enabled,
136    /// Server resources are disabled.
137    Disabled,
138}
139
140#[derive(Clone, Debug, Deserialize, Serialize)]
141pub(super) struct ServersDat {
142    pub servers: Vec<NbtServer>,
143}
144
145#[derive(Clone, Debug, Deserialize, Serialize)]
146pub(super) struct NbtServer {
147    pub icon: Option<String>,
148    pub ip: String,
149    pub name: String,
150    #[serde(rename = "acceptTextures")]
151    pub accept_textures: Option<u8>,
152}
153
154impl From<Server> for NbtServer {
155    fn from(value: Server) -> Self {
156        let accept_textures = match value.accept_textures {
157            AcceptTextures::Prompt => None,
158            AcceptTextures::Enabled => Some(1),
159            AcceptTextures::Disabled => Some(0),
160        };
161
162        Self {
163            icon: None,
164            ip: value.ip,
165            name: value.name,
166            accept_textures,
167        }
168    }
169}