Mountain/ProcessManagement/
CocoonManagement.rs

1// File: Mountain/Source/ProcessManagement/CocoonManagement.rs
2// Role: Contains the logic for launching, managing the lifecycle of, and
3// performing the initial handshake with the Cocoon sidecar process.
4
5//! # CocoonManagement
6//!
7//! Contains the logic for launching, managing the lifecycle of, and performing
8//! the initial handshake with the Cocoon sidecar process.
9
10#![allow(non_snake_case, non_camel_case_types)]
11
12use std::{collections::HashMap, process::Stdio, sync::Arc, time::Duration};
13
14use Common::Error::CommonError::CommonError;
15use log::{info, trace, warn};
16use tauri::{
17	AppHandle,
18	Manager,
19	Wry,
20	path::{BaseDirectory, PathResolver},
21};
22use tokio::{
23	io::{AsyncBufReadExt, BufReader},
24	process::Command,
25	time::sleep,
26};
27
28use super::InitializationData;
29use crate::{Environment::MountainEnvironment::MountainEnvironment, Vine};
30
31/// The main entry point for starting the Cocoon process manager. This function
32/// now returns a Result to indicate if initialization was successful.
33pub async fn InitializeCocoon(
34	ApplicationHandle:&AppHandle,
35
36	Environment:&Arc<MountainEnvironment>,
37) -> Result<(), CommonError> {
38	info!("[CocoonManagement] Initializing Cocoon sidecar manager...");
39
40	#[cfg(feature = "ExtensionHostCocoon")]
41	{
42		// Awaiting this directly now, so the caller knows if it failed.
43		LaunchAndManageCocoonSideCar(ApplicationHandle.clone(), Environment.clone()).await
44	}
45
46	#[cfg(not(feature = "ExtensionHostCocoon"))]
47	{
48		info!("[CocoonManagement] 'ExtensionHostCocoon' feature is disabled. Cocoon will not be launched.");
49
50		Ok(())
51	}
52}
53
54/// Spawns the Cocoon process and manages its communication and handshake.
55async fn LaunchAndManageCocoonSideCar(
56	ApplicationHandle:AppHandle,
57
58	Environment:Arc<MountainEnvironment>,
59) -> Result<(), CommonError> {
60	let SideCarIdentifier = "cocoon-main".to_string();
61
62	let path_resolver:PathResolver<Wry> = ApplicationHandle.path().clone();
63
64	let ScriptPath = path_resolver
65		.resolve("scripts/cocoon/bootstrap-fork.js", BaseDirectory::Resource)
66		.map_err(|Error| CommonError::FileSystemNotFound(Error.to_string().into()))?;
67
68	if !ScriptPath.exists() {
69		return Err(CommonError::FileSystemNotFound(
70			"Cocoon bootstrap-fork.js script not found.".into(),
71		));
72	}
73
74	let mut NodeCommand = Command::new("node");
75
76	let mut EnvironmentVariables = HashMap::new();
77
78	EnvironmentVariables.insert("VSCODE_PIPE_LOGGING".to_string(), "true".to_string());
79
80	EnvironmentVariables.insert("VSCODE_VERBOSE_LOGGING".to_string(), "true".to_string());
81
82	EnvironmentVariables.insert("VSCODE_PARENT_PID".to_string(), std::process::id().to_string());
83
84	EnvironmentVariables.insert("MOUNTAIN_GRPC_PORT".to_string(), "50051".to_string());
85
86	EnvironmentVariables.insert("COCOON_GRPC_PORT".to_string(), "50052".to_string());
87
88	NodeCommand
89		.arg(&ScriptPath)
90		.env_clear()
91		.envs(EnvironmentVariables)
92		.stdin(Stdio::piped())
93		.stdout(Stdio::piped())
94		.stderr(Stdio::piped());
95
96	let mut ChildProcess = NodeCommand
97		.spawn()
98		.map_err(|Error| CommonError::IPCError { Description:format!("Failed to spawn Cocoon: {}", Error) })?;
99
100	info!("[CocoonManagement] Cocoon process spawned [PID: {:?}]", ChildProcess.id());
101
102	if let Some(stdout) = ChildProcess.stdout.take() {
103		tokio::spawn(async move {
104			let Reader = BufReader::new(stdout);
105
106			let mut Lines = Reader.lines();
107
108			while let Ok(Some(Line)) = Lines.next_line().await {
109				trace!("[Cocoon stdout] {}", Line);
110			}
111		});
112	}
113	if let Some(stderr) = ChildProcess.stderr.take() {
114		tokio::spawn(async move {
115			let Reader = BufReader::new(stderr);
116
117			let mut Lines = Reader.lines();
118
119			while let Ok(Some(Line)) = Lines.next_line().await {
120				warn!("[Cocoon stderr] {}", Line);
121			}
122		});
123	}
124
125	info!("[CocoonManagement] Waiting for Cocoon gRPC server to start...");
126
127	sleep(Duration::from_millis(2000)).await;
128
129	Vine::Client::ConnectToSideCar(SideCarIdentifier.clone(), "127.0.0.1:50052".to_string())
130		.await
131		.map_err(|Error| CommonError::IPCError { Description:Error.to_string() })?;
132
133	info!("[CocoonManagement] Cocoon is ready. Sending initialization data...");
134
135	let MainInitializationData = InitializationData::ConstructExtensionHostInitializationData(&Environment).await?;
136
137	let Response = Vine::Client::SendRequest(
138		&SideCarIdentifier,
139		"InitializeExtensionHost".to_string(),
140		MainInitializationData,
141		60000,
142	)
143	.await
144	.map_err(|Error| CommonError::IPCError { Description:Error.to_string() })?;
145
146	if Response.as_str() == Some("initialized") {
147		info!("[CocoonManagement] Cocoon handshake complete.");
148	} else {
149		return Err(CommonError::IPCError {
150			Description:format!("Cocoon initialization failed with response: {}", Response),
151		});
152	}
153
154	Ok(())
155}