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