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 Fn(_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
87 let Candidate = CargoTarget.join("Air");
88
89 Candidate.exists().then_some(Candidate)
90 });
91
92 let BinaryPath = match BinaryPath {
93 Some(P) => P,
94
95 None => {
96 dev_log!(
97 "grpc",
98 "warn: [AirStart] Air binary not found in resources or target/debug; running without Air"
99 );
100
101 return Ok(());
102 },
103 };
104
105 dev_log!("grpc", "[AirStart] Spawning Air binary at: {}", BinaryPath.display());
106
107 let SpawnResult = tokio::process::Command::new(&BinaryPath)
111 .env("AIR_GRPC_ADDRESS", AIR_GRPC_ADDRESS)
112 .env(
113 "AIR_LOG_DIR",
114 std::env::var("AIR_LOG_DIR").unwrap_or_else(|_| "/tmp/air-log".to_string()),
115 )
116 .stdin(std::process::Stdio::null())
117 .stdout(std::process::Stdio::piped())
118 .stderr(std::process::Stdio::piped())
119 .spawn();
120
121 let mut Child = match SpawnResult {
122 Ok(C) => C,
123
124 Err(Error) => {
125 dev_log!("grpc", "warn: [AirStart] Failed to spawn Air ({}); running without Air", Error);
126
127 return Ok(());
128 },
129 };
130
131 let AirPid = Child.id();
132
133 dev_log!("grpc", "[AirStart] Air spawned successfully (pid={:?})", AirPid);
134
135 if let Some(Stdout) = Child.stdout.take() {
138 tokio::spawn(async move {
139 use tokio::io::{AsyncBufReadExt, BufReader};
140
141 let mut Reader = BufReader::new(Stdout).lines();
142
143 while let Ok(Some(Line)) = Reader.next_line().await {
144 dev_log!("grpc", "[Air stdout] {}", Line);
145 }
146 });
147 }
148
149 if let Some(Stderr) = Child.stderr.take() {
150 tokio::spawn(async move {
151 use tokio::io::{AsyncBufReadExt, BufReader};
152
153 let mut Reader = BufReader::new(Stderr).lines();
154
155 while let Ok(Some(Line)) = Reader.next_line().await {
156 dev_log!("grpc", "[Air stderr] {}", Line);
157 }
158 });
159 }
160
161 tokio::spawn(async move {
164 match Child.wait().await {
165 Ok(Status) => dev_log!("grpc", "[AirStart] Air exited (status={:?})", Status),
166 Err(Error) => dev_log!("grpc", "warn: [AirStart] Air wait error: {}", Error),
167 }
168 });
169
170 let SideCarIdentifier = "air-main".to_string();
173
174 let Address = format!("http://{}", AIR_GRPC_ADDRESS);
175
176 match ::Vine::Client::ConnectToSideCar::Fn(SideCarIdentifier.clone(), Address.clone()).await {
177 Ok(()) => {
178 dev_log!("grpc", "[AirStart] Air gRPC connection established at {}", Address);
179 },
180
181 Err(Error) => {
182 dev_log!(
183 "grpc",
184 "warn: [AirStart] Air spawned but gRPC connect failed ({}); workbench continues in degraded mode",
185 Error
186 );
187 },
188 }
189
190 Ok(())
191}