use crate::error::CobbleResult;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tokio::task;
#[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 {
pub name: String,
pub ip: String,
pub accept_textures: AcceptTextures,
pub path: PathBuf,
}
impl Server {
#[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?
}
#[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?
}
#[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?
}
#[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)
}
}
#[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 {
Prompt,
Enabled,
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,
}
}
}