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