Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Air/
AirClient.rs

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