cli/tunnels/
paths.rs

1/*---------------------------------------------------------------------------------------------
2 *  Copyright (c) Microsoft Corporation. All rights reserved.
3 *  Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5
6use 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	// Directory into which the server is downloaded
26	pub server_dir: PathBuf,
27	// Executable path, within the server_id
28	pub executable: PathBuf,
29	// File where logs for the server should be written.
30	pub logfile: PathBuf,
31	// File where the process ID for the server should be written.
32	pub pidfile: PathBuf,
33}
34
35impl ServerPaths {
36	// Queries the system to determine the process ID of the running server.
37	// Returns the process ID, if the server is running.
38	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			// attempt to backfill process ID:
48			self.write_pid(pid).ok();
49			return Some(pid);
50		}
51
52		None
53	}
54
55	/// Delete the server directory
56	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	// VS Code Server pid
66	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	/// Gets path information about where a specific server should be stored.
91	pub fn server_paths(&self, p: &LauncherPaths) -> ServerPaths {
92		let server_dir = self.get_install_folder(p);
93		ServerPaths {
94			// allow using the OSS server in development via an override
95			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
118/// Prunes servers not currently running, and returns the deleted servers.
119pub 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
129// Gets a list of all servers which look like they might be running.
130pub 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}