Skip to main content

Mountain/Air/
AirClient.rs

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