1use std::{
7 fs::{read_dir, read_to_string, remove_dir_all, write},
8 path::PathBuf,
9};
10
11use serde::{Deserialize, Serialize};
12
13use crate::{
14 options::{self, Quality},
15 state::LauncherPaths,
16 util::{
17 errors::{wrap, AnyError, WrappedError},
18 machine,
19 },
20};
21
22pub const SERVER_FOLDER_NAME: &str = "server";
23
24pub struct ServerPaths {
25 pub server_dir: PathBuf,
27 pub executable: PathBuf,
29 pub logfile: PathBuf,
31 pub pidfile: PathBuf,
33}
34
35impl ServerPaths {
36 pub fn get_running_pid(&self) -> Option<u32> {
39 if let Some(pid) = self.read_pid() {
40 return match machine::process_at_path_exists(pid, &self.executable) {
41 true => Some(pid),
42 false => None,
43 };
44 }
45
46 if let Some(pid) = machine::find_running_process(&self.executable) {
47 self.write_pid(pid).ok();
49 return Some(pid);
50 }
51
52 None
53 }
54
55 pub fn delete(&self) -> Result<(), WrappedError> {
57 remove_dir_all(&self.server_dir).map_err(|e| {
58 wrap(
59 e,
60 format!("error deleting server dir {}", self.server_dir.display()),
61 )
62 })
63 }
64
65 pub fn write_pid(&self, pid: u32) -> Result<(), WrappedError> {
67 write(&self.pidfile, format!("{}", pid)).map_err(|e| {
68 wrap(
69 e,
70 format!("error writing process id into {}", self.pidfile.display()),
71 )
72 })
73 }
74
75 fn read_pid(&self) -> Option<u32> {
76 read_to_string(&self.pidfile)
77 .ok()
78 .and_then(|s| s.parse::<u32>().ok())
79 }
80}
81
82#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
83pub struct InstalledServer {
84 pub quality: options::Quality,
85 pub commit: String,
86 pub headless: bool,
87}
88
89impl InstalledServer {
90 pub fn server_paths(&self, p: &LauncherPaths) -> ServerPaths {
92 let server_dir = self.get_install_folder(p);
93 ServerPaths {
94 executable: if let Some(p) = option_env!("VSCODE_CLI_OVERRIDE_SERVER_PATH") {
96 PathBuf::from(p)
97 } else {
98 server_dir
99 .join(SERVER_FOLDER_NAME)
100 .join("bin")
101 .join(self.quality.server_entrypoint())
102 },
103 logfile: server_dir.join("log.txt"),
104 pidfile: server_dir.join("pid.txt"),
105 server_dir,
106 }
107 }
108
109 fn get_install_folder(&self, p: &LauncherPaths) -> PathBuf {
110 p.server_cache.path().join(if !self.headless {
111 format!("{}-web", get_server_folder_name(self.quality, &self.commit))
112 } else {
113 get_server_folder_name(self.quality, &self.commit)
114 })
115 }
116}
117
118pub fn prune_stopped_servers(launcher_paths: &LauncherPaths) -> Result<Vec<ServerPaths>, AnyError> {
120 get_all_servers(launcher_paths)
121 .into_iter()
122 .map(|s| s.server_paths(launcher_paths))
123 .filter(|s| s.get_running_pid().is_none())
124 .map(|s| s.delete().map(|_| s))
125 .collect::<Result<_, _>>()
126 .map_err(AnyError::from)
127}
128
129pub fn get_all_servers(lp: &LauncherPaths) -> Vec<InstalledServer> {
131 let mut servers: Vec<InstalledServer> = vec![];
132 if let Ok(children) = read_dir(lp.server_cache.path()) {
133 for child in children.flatten() {
134 let fname = child.file_name();
135 let fname = fname.to_string_lossy();
136 let (quality, commit) = match fname.split_once('-') {
137 Some(r) => r,
138 None => continue,
139 };
140
141 let quality = match options::Quality::try_from(quality) {
142 Ok(q) => q,
143 Err(_) => continue,
144 };
145
146 servers.push(InstalledServer {
147 quality,
148 commit: commit.to_string(),
149 headless: true,
150 });
151 }
152 }
153
154 servers
155}
156
157pub fn get_server_folder_name(quality: Quality, commit: &str) -> String {
158 format!("{}-{}", quality, commit)
159}