Skip to main content

Mountain/Air/
AirClient.rs

1#![allow(non_snake_case)]
2
3//! # AirClient
4//!
5//! gRPC client wrapper for the Air daemon service. Mountain reaches Air
6//! through this façade for update management, authentication, file
7//! indexing, and system monitoring. Companion DTOs live in sibling
8//! files declared below; the streaming helper lives in
9//! `DownloadStream::Struct`.
10
11pub mod AirMetrics;
12pub mod AirStatus;
13pub mod DownloadStream;
14pub mod DownloadStreamChunk;
15pub mod ExtendedFileInfo;
16pub mod FileInfo;
17pub mod FileResult;
18pub mod IndexInfo;
19pub mod ResourceUsage;
20pub mod UpdateInfo;
21
22use std::{collections::HashMap, sync::Arc};
23
24use tokio::sync::Mutex;
25use CommonLibrary::Error::CommonError::CommonError;
26#[cfg(feature = "AirIntegration")]
27use AirLibrary::Vine::Generated::air::air_service_client::AirServiceClient;
28use tonic::{Request, transport::Channel};
29
30use crate::dev_log;
31
32/// Default gRPC server address for the Air daemon.
33///
34/// Port Allocation:
35/// - 50051: Mountain Vine server
36/// - 50052: Cocoon Vine server (VS Code extension hosting)
37/// - 50053: Air Vine server (Air daemon services - authentication, updates, and
38///   more)
39pub const DEFAULT_AIR_SERVER_ADDRESS:&str = "[::1]:50053";
40
41/// Air gRPC client wrapper that handles connection to the Air daemon service.
42/// This provides a clean interface for Mountain to interact with Air's
43/// capabilities including update management, authentication, file indexing,
44/// and system monitoring.
45#[derive(Clone)]
46pub struct AirClient {
47	#[cfg(feature = "AirIntegration")]
48	/// The underlying tonic gRPC client wrapped in Arc<Mutex<>> for thread-safe
49	/// access
50	client:Option<Arc<Mutex<AirServiceClient<Channel>>>>,
51	/// Address of the Air daemon
52	address:String,
53}
54
55impl AirClient {
56	/// Creates a new AirClient and connects to the Air daemon service.
57	///
58	/// # Arguments
59	/// * `address` - The gRPC server address (e.g., "http://\\[::1\\]:50053")
60	///
61	/// # Returns
62	/// * `Ok(Self)` - Successfully created client
63	/// * `Err(CommonError)` - Connection failure with descriptive error
64	///
65	/// # Example
66	///
67	/// ```text
68	/// use Mountain::Air::AirClient::{AirClient, DEFAULT_AIR_SERVER_ADDRESS};
69	///
70	/// # #[tokio::main]
71	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
72	/// let client = AirClient::new(DEFAULT_AIR_SERVER_ADDRESS).await?;
73	/// # Ok(())
74	/// # }
75	/// ```
76	pub async fn new(address:&str) -> Result<Self, CommonError> {
77		dev_log!("grpc", "[AirClient] Connecting to Air daemon at: {}", address);
78
79		#[cfg(feature = "AirIntegration")]
80		{
81			let endpoint = address.parse::<tonic::transport::Endpoint>().map_err(|e| {
82				dev_log!("grpc", "error: [AirClient] Failed to parse address '{}': {}", address, e);
83				CommonError::IPCError { Description:format!("Invalid address '{}': {}", address, e) }
84			})?;
85
86			let channel = endpoint.connect().await.map_err(|e| {
87				dev_log!("grpc", "error: [AirClient] Failed to connect to Air daemon: {}", e);
88				CommonError::IPCError { Description:format!("Connection failed: {}", e) }
89			})?;
90
91			dev_log!("grpc", "[AirClient] Successfully connected to Air daemon at: {}", address);
92
93			let client = Arc::new(Mutex::new(AirServiceClient::new(channel)));
94			Ok(Self { client:Some(client), address:address.to_string() })
95		}
96
97		#[cfg(not(feature = "AirIntegration"))]
98		{
99			dev_log!("grpc", "error: [AirClient] AirIntegration feature is not enabled");
100			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
101		}
102	}
103
104	/// Checks if the client is connected to the Air daemon.
105	///
106	/// # Returns
107	/// * `true` - Client is connected
108	/// * `false` - Client is not connected
109	pub fn is_connected(&self) -> bool {
110		#[cfg(feature = "AirIntegration")]
111		{
112			self.client.is_some()
113		}
114
115		#[cfg(not(feature = "AirIntegration"))]
116		{
117			false
118		}
119	}
120
121	/// Gets the address of the Air daemon.
122	///
123	/// # Returns
124	/// The address string
125	pub fn address(&self) -> &str { &self.address }
126
127	// =========================================================================
128	// Authentication Operations
129	// =========================================================================
130
131	/// Authenticates a user with the Air daemon.
132	///
133	/// # Arguments
134	/// * `username` - User's username
135	/// * `password` - User's password
136	/// * `provider` - Authentication provider (e.g., "github", "gitlab",
137	///   "microsoft")
138	///
139	/// # Returns
140	/// * `Ok(token)` - Authentication token if successful
141	/// * `Err(CommonError)` - Authentication failure
142	pub async fn authenticate(
143		&self,
144		request_id:String,
145		username:String,
146		password:String,
147		provider:String,
148	) -> Result<String, CommonError> {
149		dev_log!(
150			"grpc",
151			"[AirClient] Authenticating user '{}' with provider '{}'",
152			username,
153			provider
154		);
155
156		#[cfg(feature = "AirIntegration")]
157		{
158			use AirLibrary::Vine::Generated::air::AuthenticationRequest;
159
160			let username_display = username.clone();
161			let request = AuthenticationRequest { request_id, username, password, provider };
162
163			let client = self
164				.client
165				.as_ref()
166				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
167
168			let mut client_guard = client.lock().await;
169			match client_guard.authenticate(Request::new(request)).await {
170				Ok(response) => {
171					let response = response.into_inner();
172					if response.success {
173						dev_log!("grpc", "[AirClient] Authentication successful for user '{}'", username_display);
174						Ok(response.token)
175					} else {
176						dev_log!(
177							"grpc",
178							"error: [AirClient] Authentication failed for user '{}': {}",
179							username_display,
180							response.error
181						);
182						Err(CommonError::AccessDenied { Reason:response.error })
183					}
184				},
185				Err(e) => {
186					dev_log!("grpc", "error: [AirClient] Authentication RPC error: {}", e);
187					Err(CommonError::IPCError { Description:format!("Authentication RPC error: {}", e) })
188				},
189			}
190		}
191
192		#[cfg(not(feature = "AirIntegration"))]
193		{
194			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
195		}
196	}
197
198	// =========================================================================
199	// Update Operations
200	// =========================================================================
201
202	/// Checks for available updates.
203	///
204	/// # Arguments
205	/// * `current_version` - Current application version
206	/// * `channel` - Update channel (e.g., "stable", "beta", "nightly")
207	///
208	/// # Returns
209	/// * `Ok(update_info)` - Update information if available
210	/// * `Err(CommonError)` - Check failure
211	pub async fn check_for_updates(
212		&self,
213		request_id:String,
214		current_version:String,
215		channel:String,
216	) -> Result<UpdateInfo::Struct, CommonError> {
217		dev_log!("grpc", "[AirClient] Checking for updates for version '{}'", current_version);
218
219		#[cfg(feature = "AirIntegration")]
220		{
221			use AirLibrary::Vine::Generated::air::UpdateCheckRequest;
222
223			let request = UpdateCheckRequest { request_id, current_version, channel };
224
225			let client = self
226				.client
227				.as_ref()
228				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
229			let mut client_guard = client.lock().await;
230
231			match client_guard.check_for_updates(Request::new(request)).await {
232				Ok(response) => {
233					let response:AirLibrary::Vine::Generated::air::UpdateCheckResponse = response.into_inner();
234					dev_log!(
235						"grpc",
236						"[AirClient] Update check completed. Update available: {}",
237						response.update_available
238					);
239					Ok(UpdateInfo::Struct {
240						update_available:response.update_available,
241						version:response.version,
242						download_url:response.download_url,
243						release_notes:response.release_notes,
244					})
245				},
246				Err(e) => {
247					dev_log!("grpc", "error: [AirClient] Check for updates RPC error: {}", e);
248					Err(CommonError::IPCError { Description:format!("Check for updates RPC error: {}", e) })
249				},
250			}
251		}
252
253		#[cfg(not(feature = "AirIntegration"))]
254		{
255			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
256		}
257	}
258
259	/// Downloads an update package.
260	///
261	/// # Arguments
262	/// * `url` - URL of the update package
263	/// * `destination_path` - Local path to save the downloaded file
264	/// * `checksum` - Optional SHA256 checksum for verification
265	/// * `headers` - Optional HTTP headers
266	///
267	/// # Returns
268	/// * `Ok(file_info)` - Downloaded file information
269	/// * `Err(CommonError)` - Download failure
270	pub async fn download_update(
271		&self,
272		request_id:String,
273		url:String,
274		destination_path:String,
275		checksum:String,
276		headers:HashMap<String, String>,
277	) -> Result<FileInfo::Struct, CommonError> {
278		dev_log!("grpc", "[AirClient] Downloading update from: {}", url);
279
280		#[cfg(feature = "AirIntegration")]
281		{
282			use AirLibrary::Vine::Generated::air::DownloadRequest;
283
284			let request = DownloadRequest { request_id, url, destination_path, checksum, headers };
285
286			let client = self
287				.client
288				.as_ref()
289				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
290			let mut client_guard = client.lock().await;
291
292			match client_guard.download_update(Request::new(request)).await {
293				Ok(response) => {
294					let response:AirLibrary::Vine::Generated::air::DownloadResponse = response.into_inner();
295					if response.success {
296						dev_log!("grpc", "[AirClient] Update downloaded successfully to: {}", response.file_path);
297						Ok(FileInfo::Struct {
298							file_path:response.file_path,
299							file_size:response.file_size,
300							checksum:response.checksum,
301						})
302					} else {
303						dev_log!("grpc", "error: [AirClient] Update download failed: {}", response.error);
304						Err(CommonError::IPCError { Description:response.error })
305					}
306				},
307				Err(e) => {
308					dev_log!("grpc", "error: [AirClient] Download update RPC error: {}", e);
309					Err(CommonError::IPCError { Description:format!("Download update RPC error: {}", e) })
310				},
311			}
312		}
313
314		#[cfg(not(feature = "AirIntegration"))]
315		{
316			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
317		}
318	}
319
320	/// Applies an update package.
321	///
322	/// # Arguments
323	/// * `version` - Version of the update
324	/// * `update_path` - Path to the update package
325	///
326	/// # Returns
327	/// * `Ok(())` - Update applied successfully
328	/// * `Err(CommonError)` - Application failure
329	pub async fn apply_update(&self, request_id:String, version:String, update_path:String) -> Result<(), CommonError> {
330		dev_log!("grpc", "[AirClient] Applying update version: {}", version);
331
332		#[cfg(feature = "AirIntegration")]
333		{
334			use AirLibrary::Vine::Generated::air::ApplyUpdateRequest;
335
336			let request = ApplyUpdateRequest { request_id, version, update_path };
337
338			let client = self
339				.client
340				.as_ref()
341				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
342			let mut client_guard = client.lock().await;
343
344			match client_guard.apply_update(Request::new(request)).await {
345				Ok(response) => {
346					let response:AirLibrary::Vine::Generated::air::ApplyUpdateResponse = response.into_inner();
347					if response.success {
348						dev_log!("grpc", "[AirClient] Update applied successfully");
349						Ok(())
350					} else {
351						dev_log!("grpc", "error: [AirClient] Update application failed: {}", response.error);
352						Err(CommonError::IPCError { Description:response.error })
353					}
354				},
355				Err(e) => {
356					dev_log!("grpc", "error: [AirClient] Apply update RPC error: {}", e);
357					Err(CommonError::IPCError { Description:format!("Apply update RPC error: {}", e) })
358				},
359			}
360		}
361
362		#[cfg(not(feature = "AirIntegration"))]
363		{
364			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
365		}
366	}
367
368	// =========================================================================
369	// Download Operations
370	// =========================================================================
371
372	/// Downloads a file.
373	///
374	/// # Arguments
375	/// * `url` - URL of the file to download
376	/// * `destination_path` - Local path to save the downloaded file
377	/// * `checksum` - Optional SHA256 checksum for verification
378	/// * `headers` - Optional HTTP headers
379	///
380	/// # Returns
381	/// * `Ok(file_info)` - Downloaded file information
382	/// * `Err(CommonError)` - Download failure
383	pub async fn download_file(
384		&self,
385		request_id:String,
386		url:String,
387		destination_path:String,
388		checksum:String,
389		headers:HashMap<String, String>,
390	) -> Result<FileInfo::Struct, CommonError> {
391		dev_log!("grpc", "[AirClient] Downloading file from: {}", url);
392
393		#[cfg(feature = "AirIntegration")]
394		{
395			use AirLibrary::Vine::Generated::air::DownloadRequest;
396
397			let request = DownloadRequest { request_id, url, destination_path, checksum, headers };
398
399			let client = self
400				.client
401				.as_ref()
402				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
403			let mut client_guard = client.lock().await;
404
405			match client_guard.download_file(Request::new(request)).await {
406				Ok(response) => {
407					let response:AirLibrary::Vine::Generated::air::DownloadResponse = response.into_inner();
408					if response.success {
409						dev_log!("grpc", "[AirClient] File downloaded successfully to: {}", response.file_path);
410						Ok(FileInfo::Struct {
411							file_path:response.file_path,
412							file_size:response.file_size,
413							checksum:response.checksum,
414						})
415					} else {
416						dev_log!("grpc", "error: [AirClient] File download failed: {}", response.error);
417						Err(CommonError::IPCError { Description:response.error })
418					}
419				},
420				Err(e) => {
421					dev_log!("grpc", "error: [AirClient] Download file RPC error: {}", e);
422					Err(CommonError::IPCError { Description:format!("Download file RPC error: {}", e) })
423				},
424			}
425		}
426
427		#[cfg(not(feature = "AirIntegration"))]
428		{
429			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
430		}
431	}
432
433	/// Downloads a file as a stream.
434	///
435	/// This method initiates a streaming download from the given URL, returning
436	/// a stream of chunks that can be processed incrementally without loading
437	/// the entire file into memory.
438	///
439	/// # Arguments
440	/// * `request_id` - Unique request identifier
441	/// * `url` - URL of the file to download
442	/// * `headers` - Optional HTTP headers
443	///
444	/// # Returns
445	/// * `Ok(stream)` - Stream that yields download chunks
446	/// * `Err(CommonError)` - Download initiation failure
447	///
448	/// # Stream Chunk Information
449	///
450	/// Each chunk contains:
451	/// - `chunk`: The binary data chunk
452	/// - `total_size`: Total file size (if known)
453	/// - `downloaded`: Number of bytes downloaded so far
454	/// - `completed`: Whether this is the final chunk
455	/// - `error`: Error message if download failed
456	///
457	/// # Example
458	///
459	/// ```text
460	/// use Mountain::Air::AirClient::AirClient;
461	/// use CommonLibrary::Error::CommonError::CommonError;
462	///
463	/// # #[tokio::main]
464	/// # async fn main() -> Result<(), CommonError> {
465	/// # let client = AirClient::new("http://[::1]:50053").await?;
466	/// let mut stream = client
467	/// 	.download_stream(
468	/// 		"req-123".to_string(),
469	/// 		"https://example.com/large-file.zip".to_string(),
470	/// 		std::collections::HashMap::new(),
471	/// 	)
472	/// 	.await?;
473	///
474	/// let mut buffer = Vec::new();
475	/// while let Some(chunk) = stream.next().await {
476	/// 	let chunk = chunk?;
477	/// 	buffer.extend_from_slice(&chunk.data);
478	/// 	println!("Downloaded: {} / {} bytes", chunk.downloaded, chunk.total_size);
479	/// 	if chunk.completed {
480	/// 		break;
481	/// 	}
482	/// }
483	/// # Ok(())
484	/// # }
485	/// ```
486	pub async fn download_stream(
487		&self,
488		request_id:String,
489		url:String,
490		headers:HashMap<String, String>,
491	) -> Result<DownloadStream::Struct, CommonError> {
492		dev_log!("grpc", "[AirClient] Starting stream download from: {}", url);
493
494		#[cfg(feature = "AirIntegration")]
495		{
496			use AirLibrary::Vine::Generated::air::DownloadStreamRequest;
497
498			let request = DownloadStreamRequest { request_id, url, headers };
499
500			let client = self
501				.client
502				.as_ref()
503				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
504			let mut client_guard = client.lock().await;
505
506			match client_guard.download_stream(Request::new(request)).await {
507				Ok(response) => {
508					dev_log!("grpc", "[AirClient] Stream download initiated successfully");
509					Ok(DownloadStream::Struct::new(response.into_inner()))
510				},
511				Err(e) => {
512					dev_log!("grpc", "error: [AirClient] Download stream RPC error: {}", e);
513					Err(CommonError::IPCError { Description:format!("Download stream RPC error: {}", e) })
514				},
515			}
516		}
517
518		#[cfg(not(feature = "AirIntegration"))]
519		{
520			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
521		}
522	}
523
524	// =========================================================================
525	// File Indexing Operations
526	// =========================================================================
527
528	/// Indexes files in a directory.
529	///
530	/// # Arguments
531	/// * `path` - Path to the directory to index
532	/// * `patterns` - File patterns to include
533	/// * `exclude_patterns` - File patterns to exclude
534	/// * `max_depth` - Maximum depth for recursion
535	///
536	/// # Returns
537	/// * `Ok(index_info)` - Index information
538	/// * `Err(CommonError)` - Indexing failure
539	pub async fn index_files(
540		&self,
541		request_id:String,
542		path:String,
543		patterns:Vec<String>,
544		exclude_patterns:Vec<String>,
545		max_depth:u32,
546	) -> Result<IndexInfo::Struct, CommonError> {
547		dev_log!("grpc", "[AirClient] Indexing files in: {}", path);
548
549		#[cfg(feature = "AirIntegration")]
550		{
551			use AirLibrary::Vine::Generated::air::IndexRequest;
552
553			let request = IndexRequest { request_id, path, patterns, exclude_patterns, max_depth };
554
555			let client = self
556				.client
557				.as_ref()
558				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
559			let mut client_guard = client.lock().await;
560
561			match client_guard.index_files(Request::new(request)).await {
562				Ok(response) => {
563					let response = response.into_inner();
564					// Use fields that actually exist in IndexResponse
565					dev_log!(
566						"grpc",
567						"[AirClient] Files indexed: {} (total size: {} bytes)",
568						response.files_indexed,
569						response.total_size
570					);
571					Ok(IndexInfo::Struct { files_indexed:response.files_indexed, total_size:response.total_size })
572				},
573				Err(e) => {
574					dev_log!("grpc", "error: [AirClient] Index files RPC error: {}", e);
575					Err(CommonError::IPCError { Description:format!("Index files RPC error: {}", e) })
576				},
577			}
578		}
579
580		#[cfg(not(feature = "AirIntegration"))]
581		{
582			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
583		}
584	}
585
586	/// Searches for files matching a query.
587	///
588	/// # Arguments
589	/// * `query` - Search query string
590	/// * `path` - Path to search in
591	/// * `max_results` - Maximum number of results to return
592	///
593	/// # Returns
594	/// * `Ok(results)` - Search results
595	/// * `Err(CommonError)` - Search failure
596	pub async fn search_files(
597		&self,
598		request_id:String,
599		query:String,
600		path:String,
601		max_results:u32,
602	) -> Result<Vec<FileResult::Struct>, CommonError> {
603		dev_log!("grpc", "[AirClient] Searching for files with query: '{}' in: {}", query, path);
604
605		#[cfg(feature = "AirIntegration")]
606		{
607			use AirLibrary::Vine::Generated::air::SearchRequest;
608
609			let request = SearchRequest { request_id, query, path, max_results };
610
611			let client = self
612				.client
613				.as_ref()
614				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
615			let mut client_guard = client.lock().await;
616
617			match client_guard.search_files(Request::new(request)).await {
618				Ok(_response) => {
619					dev_log!("grpc", "[AirClient] Search completed");
620					// Placeholder implementation - actual response structure may vary
621					Ok(Vec::new())
622				},
623				Err(e) => {
624					dev_log!("grpc", "error: [AirClient] Search files RPC error: {}", e);
625					Err(CommonError::IPCError { Description:format!("Search files RPC error: {}", e) })
626				},
627			}
628		}
629
630		#[cfg(not(feature = "AirIntegration"))]
631		{
632			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
633		}
634	}
635
636	/// Gets file information.
637	///
638	/// # Arguments
639	/// * `path` - Path to the file
640	///
641	/// # Returns
642	/// * `Ok(file_info)` - File information
643	/// * `Err(CommonError)` - Request failure
644	pub async fn get_file_info(&self, request_id:String, path:String) -> Result<ExtendedFileInfo::Struct, CommonError> {
645		let path_display = path.clone();
646		dev_log!("grpc", "[AirClient] Getting file info for: {}", path);
647
648		#[cfg(feature = "AirIntegration")]
649		{
650			use AirLibrary::Vine::Generated::air::FileInfoRequest;
651
652			let request = FileInfoRequest { request_id, path };
653
654			let client = self
655				.client
656				.as_ref()
657				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
658			let mut client_guard = client.lock().await;
659
660			match client_guard.get_file_info(Request::new(request)).await {
661				Ok(response) => {
662					let response:AirLibrary::Vine::Generated::air::FileInfoResponse = response.into_inner();
663					dev_log!(
664						"grpc",
665						"[AirClient] File info retrieved for: {} (exists: {})",
666						path_display,
667						response.exists
668					);
669					Ok(ExtendedFileInfo::Struct {
670						exists:response.exists,
671						size:response.size,
672						mime_type:response.mime_type,
673						checksum:response.checksum,
674						modified_time:response.modified_time,
675					})
676				},
677				Err(e) => {
678					dev_log!("grpc", "error: [AirClient] Get file info RPC error: {}", e);
679					Err(CommonError::IPCError { Description:format!("Get file info RPC error: {}", e) })
680				},
681			}
682		}
683
684		#[cfg(not(feature = "AirIntegration"))]
685		{
686			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
687		}
688	}
689
690	// =========================================================================
691	// Status and Monitoring Operations
692	// =========================================================================
693
694	/// Gets the status of the Air daemon.
695	///
696	/// # Returns
697	/// * `Ok(status)` - Air daemon status
698	/// * `Err(CommonError)` - Request failure
699	pub async fn get_status(&self, request_id:String) -> Result<AirStatus::Struct, CommonError> {
700		dev_log!("grpc", "[AirClient] Getting Air daemon status");
701
702		#[cfg(feature = "AirIntegration")]
703		{
704			use AirLibrary::Vine::Generated::air::StatusRequest;
705
706			let request = StatusRequest { request_id };
707
708			let client = self
709				.client
710				.as_ref()
711				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
712			let mut client_guard = client.lock().await;
713
714			match client_guard.get_status(Request::new(request)).await {
715				Ok(response) => {
716					let response:AirLibrary::Vine::Generated::air::StatusResponse = response.into_inner();
717					dev_log!(
718						"grpc",
719						"[AirClient] Status retrieved. Active requests: {}",
720						response.active_requests
721					);
722					Ok(AirStatus::Struct {
723						version:response.version,
724						uptime_seconds:response.uptime_seconds,
725						total_requests:response.total_requests,
726						successful_requests:response.successful_requests,
727						failed_requests:response.failed_requests,
728						average_response_time:response.average_response_time,
729						memory_usage_mb:response.memory_usage_mb,
730						cpu_usage_percent:response.cpu_usage_percent,
731						active_requests:response.active_requests,
732					})
733				},
734				Err(e) => {
735					dev_log!("grpc", "error: [AirClient] Get status RPC error: {}", e);
736					Err(CommonError::IPCError { Description:format!("Get status RPC error: {}", e) })
737				},
738			}
739		}
740
741		#[cfg(not(feature = "AirIntegration"))]
742		{
743			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
744		}
745	}
746
747	/// Performs a health check on the Air daemon.
748	///
749	/// # Returns
750	/// * `Ok(healthy)` - Health status
751	/// * `Err(CommonError)` - Check failure
752	pub async fn health_check(&self) -> Result<bool, CommonError> {
753		dev_log!("grpc", "[AirClient] Performing health check");
754
755		#[cfg(feature = "AirIntegration")]
756		{
757			use AirLibrary::Vine::Generated::air::HealthCheckRequest;
758
759			let request = HealthCheckRequest {};
760
761			let client = self
762				.client
763				.as_ref()
764				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
765			let mut client_guard = client.lock().await;
766
767			match client_guard.health_check(Request::new(request)).await {
768				Ok(response) => {
769					let response:AirLibrary::Vine::Generated::air::HealthCheckResponse = response.into_inner();
770					dev_log!("grpc", "[AirClient] Health check result: {}", response.healthy);
771					Ok(response.healthy)
772				},
773				Err(e) => {
774					dev_log!("grpc", "error: [AirClient] Health check RPC error: {}", e);
775					Err(CommonError::IPCError { Description:format!("Health check RPC error: {}", e) })
776				},
777			}
778		}
779
780		#[cfg(not(feature = "AirIntegration"))]
781		{
782			// When AirIntegration is not enabled, we return true to allow
783			// the application to function without Air
784			Ok(true)
785		}
786	}
787
788	/// Gets metrics from the Air daemon.
789	///
790	/// # Arguments
791	/// * `metric_type` - Type of metrics (e.g., "performance", "resources",
792	///   "requests")
793	///
794	/// # Returns
795	/// * `Ok(metrics)` - Metrics data
796	/// * `Err(CommonError)` - Request failure
797	pub async fn get_metrics(
798		&self,
799		request_id:String,
800		metric_type:Option<String>,
801	) -> Result<AirMetrics::Struct, CommonError> {
802		dev_log!("grpc", "[AirClient] Getting metrics (type: {:?})", metric_type.as_deref());
803
804		#[cfg(feature = "AirIntegration")]
805		{
806			use AirLibrary::Vine::Generated::air::MetricsRequest;
807
808			let request = MetricsRequest { request_id, metric_type:metric_type.unwrap_or_default() };
809
810			let client = self
811				.client
812				.as_ref()
813				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
814			let mut client_guard = client.lock().await;
815
816			match client_guard.get_metrics(Request::new(request)).await {
817				Ok(response) => {
818					let response:AirLibrary::Vine::Generated::air::MetricsResponse = response.into_inner();
819					dev_log!("grpc", "[AirClient] Metrics retrieved");
820					// Parse metrics from the string map - this is a simplified implementation
821					let metrics = AirMetrics::Struct {
822						memory_usage_mb:response
823							.metrics
824							.get("memory_usage_mb")
825							.and_then(|s| s.parse::<f64>().ok())
826							.unwrap_or(0.0),
827						cpu_usage_percent:response
828							.metrics
829							.get("cpu_usage_percent")
830							.and_then(|s| s.parse::<f64>().ok())
831							.unwrap_or(0.0),
832						network_usage_mbps:response
833							.metrics
834							.get("network_usage_mbps")
835							.and_then(|s| s.parse::<f64>().ok())
836							.unwrap_or(0.0),
837						disk_usage_mb:response
838							.metrics
839							.get("disk_usage_mb")
840							.and_then(|s| s.parse::<f64>().ok())
841							.unwrap_or(0.0),
842						average_response_time:response
843							.metrics
844							.get("average_response_time")
845							.and_then(|s| s.parse::<f64>().ok())
846							.unwrap_or(0.0),
847					};
848					Ok(metrics)
849				},
850				Err(e) => {
851					dev_log!("grpc", "error: [AirClient] Get metrics RPC error: {}", e);
852					Err(CommonError::IPCError { Description:format!("Get metrics RPC error: {}", e) })
853				},
854			}
855		}
856
857		#[cfg(not(feature = "AirIntegration"))]
858		{
859			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
860		}
861	}
862
863	// =========================================================================
864	// Resource Management Operations
865	// =========================================================================
866
867	/// Gets resource usage information.
868	///
869	/// # Arguments
870	/// * `request_id` - Unique request identifier
871	///
872	/// # Returns
873	/// * `Ok(usage)` - Resource usage data
874	/// * `Err(CommonError)` - Request failure
875	pub async fn get_resource_usage(&self, request_id:String) -> Result<ResourceUsage::Struct, CommonError> {
876		dev_log!("grpc", "[AirClient] Getting resource usage");
877
878		#[cfg(feature = "AirIntegration")]
879		{
880			use AirLibrary::Vine::Generated::air::ResourceUsageRequest;
881
882			let request = ResourceUsageRequest { request_id };
883
884			let client = self
885				.client
886				.as_ref()
887				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
888			let mut client_guard = client.lock().await;
889
890			match client_guard.get_resource_usage(Request::new(request)).await {
891				Ok(response) => {
892					let response:AirLibrary::Vine::Generated::air::ResourceUsageResponse = response.into_inner();
893					dev_log!("grpc", "[AirClient] Resource usage retrieved");
894					Ok(ResourceUsage::Struct {
895						memory_usage_mb:response.memory_usage_mb,
896						cpu_usage_percent:response.cpu_usage_percent,
897						disk_usage_mb:response.disk_usage_mb,
898						network_usage_mbps:response.network_usage_mbps,
899						thread_count:0,      // Not provided in ResourceUsageResponse
900						open_file_handles:0, // Not provided in ResourceUsageResponse
901					})
902				},
903				Err(e) => {
904					dev_log!("grpc", "error: [AirClient] Get resource usage RPC error: {}", e);
905					Err(CommonError::IPCError { Description:format!("Get resource usage RPC error: {}", e) })
906				},
907			}
908		}
909
910		#[cfg(not(feature = "AirIntegration"))]
911		{
912			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
913		}
914	}
915
916	/// Sets resource limits.
917	///
918	/// # Arguments
919	/// * `request_id` - Unique request identifier
920	/// * `memory_limit_mb` - Memory limit in MB
921	/// * `cpu_limit_percent` - CPU limit as percentage
922	/// * `disk_limit_mb` - Disk limit in MB
923	///
924	/// # Returns
925	/// * `Ok(())` - Limits set successfully
926	/// * `Err(CommonError)` - Set failure
927	pub async fn set_resource_limits(
928		&self,
929		request_id:String,
930		memory_limit_mb:u32,
931		cpu_limit_percent:u32,
932		disk_limit_mb:u32,
933	) -> Result<(), CommonError> {
934		dev_log!(
935			"grpc",
936			"[AirClient] Setting resource limits: memory={}MB, cpu={}%, disk={}MB",
937			memory_limit_mb,
938			cpu_limit_percent,
939			disk_limit_mb
940		);
941
942		#[cfg(feature = "AirIntegration")]
943		{
944			use AirLibrary::Vine::Generated::air::ResourceLimitsRequest;
945
946			let request = ResourceLimitsRequest { request_id, memory_limit_mb, cpu_limit_percent, disk_limit_mb };
947
948			let client = self
949				.client
950				.as_ref()
951				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
952			let mut client_guard = client.lock().await;
953
954			match client_guard.set_resource_limits(Request::new(request)).await {
955				Ok(response) => {
956					let response:AirLibrary::Vine::Generated::air::ResourceLimitsResponse = response.into_inner();
957					if response.success {
958						dev_log!("grpc", "[AirClient] Resource limits set successfully");
959						Ok(())
960					} else {
961						dev_log!("grpc", "error: [AirClient] Failed to set resource limits: {}", response.error);
962						Err(CommonError::IPCError { Description:response.error })
963					}
964				},
965				Err(e) => {
966					dev_log!("grpc", "error: [AirClient] Set resource limits RPC error: {}", e);
967					Err(CommonError::IPCError { Description:format!("Set resource limits RPC error: {}", e) })
968				},
969			}
970		}
971
972		#[cfg(not(feature = "AirIntegration"))]
973		{
974			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
975		}
976	}
977
978	// =========================================================================
979	// Configuration Management Operations
980	// =========================================================================
981
982	/// Gets configuration.
983	///
984	/// # Arguments
985	/// * `section` - Configuration section (e.g., "grpc", "authentication",
986	///   "updates")
987	///
988	/// # Returns
989	/// * `Ok(config)` - Configuration data
990	/// * `Err(CommonError)` - Request failure
991	pub async fn get_configuration(
992		&self,
993		request_id:String,
994		section:String,
995	) -> Result<HashMap<String, String>, CommonError> {
996		let section_display = section.clone();
997		dev_log!("grpc", "[AirClient] Getting configuration for section: {}", section);
998
999		#[cfg(feature = "AirIntegration")]
1000		{
1001			use AirLibrary::Vine::Generated::air::ConfigurationRequest;
1002
1003			let request = ConfigurationRequest { request_id, section };
1004
1005			let client = self
1006				.client
1007				.as_ref()
1008				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
1009			let mut client_guard = client.lock().await;
1010
1011			match client_guard.get_configuration(Request::new(request)).await {
1012				Ok(response) => {
1013					let response:AirLibrary::Vine::Generated::air::ConfigurationResponse = response.into_inner();
1014					dev_log!(
1015						"grpc",
1016						"[AirClient] Configuration retrieved for section: {} ({} keys)",
1017						section_display,
1018						response.configuration.len()
1019					);
1020					Ok(response.configuration)
1021				},
1022				Err(e) => {
1023					dev_log!("grpc", "error: [AirClient] Get configuration RPC error: {}", e);
1024					Err(CommonError::IPCError { Description:format!("Get configuration RPC error: {}", e) })
1025				},
1026			}
1027		}
1028
1029		#[cfg(not(feature = "AirIntegration"))]
1030		{
1031			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
1032		}
1033	}
1034
1035	/// Updates configuration.
1036	///
1037	/// # Arguments
1038	/// * `section` - Configuration section
1039	/// * `updates` - Configuration updates
1040	///
1041	/// # Returns
1042	/// * `Ok(())` - Configuration updated successfully
1043	/// * `Err(CommonError)` - Update failure
1044	pub async fn update_configuration(
1045		&self,
1046		request_id:String,
1047		section:String,
1048		updates:HashMap<String, String>,
1049	) -> Result<(), CommonError> {
1050		let section_display = section.clone();
1051		dev_log!(
1052			"grpc",
1053			"[AirClient] Updating configuration for section: {} ({} keys)",
1054			section_display,
1055			updates.len()
1056		);
1057
1058		#[cfg(feature = "AirIntegration")]
1059		{
1060			use AirLibrary::Vine::Generated::air::UpdateConfigurationRequest;
1061
1062			let request = UpdateConfigurationRequest { request_id, section, updates };
1063
1064			let client = self
1065				.client
1066				.as_ref()
1067				.ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
1068			let mut client_guard = client.lock().await;
1069
1070			match client_guard.update_configuration(Request::new(request)).await {
1071				Ok(response) => {
1072					let response:AirLibrary::Vine::Generated::air::UpdateConfigurationResponse = response.into_inner();
1073					if response.success {
1074						dev_log!(
1075							"grpc",
1076							"[AirClient] Configuration updated successfully for section: {}",
1077							section_display
1078						);
1079						Ok(())
1080					} else {
1081						dev_log!("grpc", "error: [AirClient] Failed to update configuration: {}", response.error);
1082						Err(CommonError::IPCError { Description:response.error })
1083					}
1084				},
1085				Err(e) => {
1086					dev_log!("grpc", "error: [AirClient] Update configuration RPC error: {}", e);
1087					Err(CommonError::IPCError { Description:format!("Update configuration RPC error: {}", e) })
1088				},
1089			}
1090		}
1091
1092		#[cfg(not(feature = "AirIntegration"))]
1093		{
1094			Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
1095		}
1096	}
1097}
1098
1099// ============================================================================
1100// Response Types
1101// ============================================================================
1102// ============================================================================
1103// Debug Implementation
1104// ============================================================================
1105
1106impl std::fmt::Debug for AirClient {
1107	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AirClient({})", self.address) }
1108}
1109
1110// ============================================================================
1111// tonic::Request Helper
1112// ============================================================================
1113
1114/// Helper trait for converting types to tonic::Request
1115#[allow(dead_code)]
1116trait IntoRequestExt {
1117	fn into_request(self) -> tonic::Request<Self>
1118	where
1119		Self: Sized, {
1120		tonic::Request::new(self)
1121	}
1122}
1123
1124impl<T> IntoRequestExt for T {}