//! Listing, creation, and settings management for servers. use chrono::Utc; use uuid::Uuid; use crate::state::remove_log_buffer; use crate::util::io::{self, IOError}; use crate::{ErrorKind, Result, State}; use super::lifecycle::is_running; use super::manifest::{ ServerInfo, ServerManifest, build_server_info, read_manifest, sanitize_folder_name, server_path, type_default_jar_name, write_manifest, }; pub async fn list() -> Result> { let state = State::get().await?; let servers_dir = state.directories.servers_dir(); if !servers_dir.exists() { return Ok(Vec::new()); } let mut servers = Vec::new(); let mut entries = tokio::fs::read_dir(&servers_dir) .await .map_err(|e| IOError::with_path(e, &servers_dir))?; while let Some(entry) = entries .next_entry() .await .map_err(|e| IOError::with_path(e, &servers_dir))? { let path = entry.path(); if !path.is_dir() { continue; } let Ok(manifest) = read_manifest(&path).await else { continue; }; servers.push(build_server_info(&manifest, &path).await); } servers.sort_by(|a, b| { a.manifest .name .to_lowercase() .cmp(&b.manifest.name.to_lowercase()) }); Ok(servers) } pub async fn get(server_id: &str) -> Result { let path = server_path(server_id).await?; let manifest = read_manifest(&path).await?; Ok(build_server_info(&manifest, &path).await) } pub async fn create( name: &str, server_type: &str, game_version: &str, loader_version: Option, java_path: Option, memory_mb: Option, ) -> Result { let name = name.trim(); if name.is_empty() { return Err(ErrorKind::InputError( "Server name cannot be empty".to_string(), ) .as_error()); } let state = State::get().await?; let id = Uuid::new_v4().to_string(); let dir_name = format!("{}-{}", sanitize_folder_name(name), &id[..8]); let dir = state.directories.servers_dir().join(&dir_name); io::create_dir_all(&dir).await?; let manifest = ServerManifest { id: dir_name, name: name.to_string(), server_type: server_type.to_string(), game_version: game_version.to_string(), loader_version, jar_name: type_default_jar_name(server_type), java_path, memory_mb, icon_path: None, modpack: None, install_state: None, install_error: None, jvm_args: Vec::new(), created_at: Utc::now(), last_started_at: None, last_exit_crashed: false, }; write_manifest(&dir, &manifest).await?; Ok(manifest) } /// Sets or clears the server icon. `None` resets to the default icon. pub async fn set_icon( server_id: &str, icon_path: Option, ) -> Result { let path = server_path(server_id).await?; let mut manifest = read_manifest(&path).await?; manifest.icon_path = icon_path; write_manifest(&path, &manifest).await?; Ok(manifest) } pub async fn update_settings( server_id: &str, name: Option, java_path: Option, memory_mb: Option, jvm_args: Option>, ) -> Result { let path = server_path(server_id).await?; let mut manifest = read_manifest(&path).await?; if let Some(name) = name { let name = name.trim().to_string(); if name.is_empty() { return Err(ErrorKind::InputError( "Server name cannot be empty".to_string(), ) .as_error()); } manifest.name = name; } if let Some(java_path) = java_path { manifest.java_path = if java_path.is_empty() { None } else { Some(java_path) }; } if let Some(memory_mb) = memory_mb { manifest.memory_mb = Some(memory_mb); } if let Some(jvm_args) = jvm_args { manifest.jvm_args = jvm_args; } write_manifest(&path, &manifest).await?; Ok(manifest) } pub async fn delete(server_id: &str) -> Result<()> { if is_running(server_id) { return Err(ErrorKind::InputError( "Stop the server before deleting it".to_string(), ) .as_error()); } let path = server_path(server_id).await?; remove_log_buffer(server_id); tokio::fs::remove_dir_all(&path) .await .map_err(|e| IOError::with_path(e, &path))?; Ok(()) }