cobble_core/minecraft/servers/
server.rs1use crate::error::CobbleResult;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4use tokio::task;
5
6#[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 pub name: String,
13 pub ip: String,
15 pub accept_textures: AcceptTextures,
17 pub path: PathBuf,
19}
20
21impl Server {
22 #[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 #[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 #[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 #[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#[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 Prompt,
134 Enabled,
136 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}