Mountain/Binary/Service/
AirStart.rs1use std::sync::Arc;
25
26use tauri::AppHandle;
27
28use crate::{Environment::MountainEnvironment::MountainEnvironment, dev_log};
29
30const AIR_GRPC_ADDRESS:&str = "[::1]:50053";
33
34pub async fn AirStart(_ApplicationHandle:&AppHandle, _Environment:&Arc<MountainEnvironment>) -> Result<(), String> {
42 if matches!(std::env::var("Spawn").as_deref(), Ok("0") | Ok("false")) {
46 dev_log!("grpc", "[AirStart] Skipping Air spawn (Spawn=false)");
47
48 return Ok(());
49 }
50
51 #[cfg(feature = "AirIntegration")]
52 {
53 LaunchAndConnectAir(_ApplicationHandle.clone(), _Environment.clone()).await
54 }
55
56 #[cfg(not(feature = "AirIntegration"))]
57 {
58 dev_log!(
59 "grpc",
60 "[AirStart] AirIntegration feature disabled; skipping spawn (workbench runs without Air)"
61 );
62
63 Ok(())
64 }
65}
66
67#[cfg(feature = "AirIntegration")]
68async fn LaunchAndConnectAir(ApplicationHandle:AppHandle, _Environment:Arc<MountainEnvironment>) -> Result<(), String> {
69 use std::path::PathBuf;
70
71 use tauri::Manager;
72
73 dev_log!("grpc", "[AirStart] Resolving Air sidecar binary path...");
74
75 let BinaryPath:Option<PathBuf> = ApplicationHandle
78 .path()
79 .resolve("Air", tauri::path::BaseDirectory::Resource)
80 .ok()
81 .filter(|P| P.exists())
82 .or_else(|| {
83 let CargoTarget = std::env::var("CARGO_TARGET_DIR")
84 .map(PathBuf::from)
85 .unwrap_or_else(|_| PathBuf::from("Element/Air/Target/debug"));
86 let Candidate = CargoTarget.join("Air");
87 Candidate.exists().then_some(Candidate)
88 });
89
90 let BinaryPath = match BinaryPath {
91 Some(P) => P,
92
93 None => {
94 dev_log!(
95 "grpc",
96 "warn: [AirStart] Air binary not found in resources or target/debug; running without Air"
97 );
98
99 return Ok(());
100 },
101 };
102
103 dev_log!("grpc", "[AirStart] Spawning Air binary at: {}", BinaryPath.display());
104
105 let SpawnResult = tokio::process::Command::new(&BinaryPath)
109 .env("AIR_GRPC_ADDRESS", AIR_GRPC_ADDRESS)
110 .env(
111 "AIR_LOG_DIR",
112 std::env::var("AIR_LOG_DIR").unwrap_or_else(|_| "/tmp/air-log".to_string()),
113 )
114 .stdin(std::process::Stdio::null())
115 .stdout(std::process::Stdio::piped())
116 .stderr(std::process::Stdio::piped())
117 .spawn();
118
119 let mut Child = match SpawnResult {
120 Ok(C) => C,
121
122 Err(Error) => {
123 dev_log!("grpc", "warn: [AirStart] Failed to spawn Air ({}); running without Air", Error);
124
125 return Ok(());
126 },
127 };
128
129 let AirPid = Child.id();
130
131 dev_log!("grpc", "[AirStart] Air spawned successfully (pid={:?})", AirPid);
132
133 if let Some(Stdout) = Child.stdout.take() {
136 tokio::spawn(async move {
137 use tokio::io::{AsyncBufReadExt, BufReader};
138 let mut Reader = BufReader::new(Stdout).lines();
139 while let Ok(Some(Line)) = Reader.next_line().await {
140 dev_log!("grpc", "[Air stdout] {}", Line);
141 }
142 });
143 }
144
145 if let Some(Stderr) = Child.stderr.take() {
146 tokio::spawn(async move {
147 use tokio::io::{AsyncBufReadExt, BufReader};
148 let mut Reader = BufReader::new(Stderr).lines();
149 while let Ok(Some(Line)) = Reader.next_line().await {
150 dev_log!("grpc", "[Air stderr] {}", Line);
151 }
152 });
153 }
154
155 tokio::spawn(async move {
158 match Child.wait().await {
159 Ok(Status) => dev_log!("grpc", "[AirStart] Air exited (status={:?})", Status),
160 Err(Error) => dev_log!("grpc", "warn: [AirStart] Air wait error: {}", Error),
161 }
162 });
163
164 let SideCarIdentifier = "air-main".to_string();
167
168 let Address = format!("http://{}", AIR_GRPC_ADDRESS);
169
170 match crate::Vine::Client::ConnectToSideCar::Fn(SideCarIdentifier.clone(), Address.clone()).await {
171 Ok(()) => {
172 dev_log!("grpc", "[AirStart] Air gRPC connection established at {}", Address);
173 },
174
175 Err(Error) => {
176 dev_log!(
177 "grpc",
178 "warn: [AirStart] Air spawned but gRPC connect failed ({}); workbench continues in degraded mode",
179 Error
180 );
181 },
182 }
183
184 Ok(())
185}