1use std::{collections::HashMap, sync::Arc};
82
83use tokio::sync::Mutex;
84use CommonLibrary::Error::CommonError::CommonError;
85#[cfg(feature = "AirIntegration")]
86use AirLibrary::Vine::Generated::air::air_service_client::AirServiceClient;
87use tonic::{Request, transport::Channel};
88
89use crate::dev_log;
90
91pub const DEFAULT_AIR_SERVER_ADDRESS:&str = "[::1]:50053";
99
100#[derive(Clone)]
105pub struct AirClient {
106 #[cfg(feature = "AirIntegration")]
107 client:Option<Arc<Mutex<AirServiceClient<Channel>>>>,
110 address:String,
112}
113
114impl AirClient {
115 pub async fn new(address:&str) -> Result<Self, CommonError> {
136 dev_log!("grpc", "[AirClient] Connecting to Air daemon at: {}", address);
137
138 #[cfg(feature = "AirIntegration")]
139 {
140 let endpoint = address.parse::<tonic::transport::Endpoint>().map_err(|e| {
141 dev_log!("grpc", "error: [AirClient] Failed to parse address '{}': {}", address, e);
142 CommonError::IPCError { Description:format!("Invalid address '{}': {}", address, e) }
143 })?;
144
145 let channel = endpoint.connect().await.map_err(|e| {
146 dev_log!("grpc", "error: [AirClient] Failed to connect to Air daemon: {}", e);
147 CommonError::IPCError { Description:format!("Connection failed: {}", e) }
148 })?;
149
150 dev_log!("grpc", "[AirClient] Successfully connected to Air daemon at: {}", address);
151
152 let client = Arc::new(Mutex::new(AirServiceClient::new(channel)));
153 Ok(Self { client:Some(client), address:address.to_string() })
154 }
155
156 #[cfg(not(feature = "AirIntegration"))]
157 {
158 dev_log!("grpc", "error: [AirClient] AirIntegration feature is not enabled");
159 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
160 }
161 }
162
163 pub fn is_connected(&self) -> bool {
169 #[cfg(feature = "AirIntegration")]
170 {
171 self.client.is_some()
172 }
173
174 #[cfg(not(feature = "AirIntegration"))]
175 {
176 false
177 }
178 }
179
180 pub fn address(&self) -> &str { &self.address }
185
186 pub async fn authenticate(
202 &self,
203 request_id:String,
204 username:String,
205 password:String,
206 provider:String,
207 ) -> Result<String, CommonError> {
208 dev_log!(
209 "grpc",
210 "[AirClient] Authenticating user '{}' with provider '{}'",
211 username,
212 provider
213 );
214
215 #[cfg(feature = "AirIntegration")]
216 {
217 use AirLibrary::Vine::Generated::air::AuthenticationRequest;
218
219 let username_display = username.clone();
220 let request = AuthenticationRequest { request_id, username, password, provider };
221
222 let client = self
223 .client
224 .as_ref()
225 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
226
227 let mut client_guard = client.lock().await;
228 match client_guard.authenticate(Request::new(request)).await {
229 Ok(response) => {
230 let response = response.into_inner();
231 if response.success {
232 dev_log!("grpc", "[AirClient] Authentication successful for user '{}'", username_display);
233 Ok(response.token)
234 } else {
235 dev_log!(
236 "grpc",
237 "error: [AirClient] Authentication failed for user '{}': {}",
238 username_display,
239 response.error
240 );
241 Err(CommonError::AccessDenied { Reason:response.error })
242 }
243 },
244 Err(e) => {
245 dev_log!("grpc", "error: [AirClient] Authentication RPC error: {}", e);
246 Err(CommonError::IPCError { Description:format!("Authentication RPC error: {}", e) })
247 },
248 }
249 }
250
251 #[cfg(not(feature = "AirIntegration"))]
252 {
253 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
254 }
255 }
256
257 pub async fn check_for_updates(
271 &self,
272 request_id:String,
273 current_version:String,
274 channel:String,
275 ) -> Result<UpdateInfo, CommonError> {
276 dev_log!("grpc", "[AirClient] Checking for updates for version '{}'", current_version);
277
278 #[cfg(feature = "AirIntegration")]
279 {
280 use AirLibrary::Vine::Generated::air::UpdateCheckRequest;
281
282 let request = UpdateCheckRequest { request_id, current_version, channel };
283
284 let client = self
285 .client
286 .as_ref()
287 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
288 let mut client_guard = client.lock().await;
289
290 match client_guard.check_for_updates(Request::new(request)).await {
291 Ok(response) => {
292 let response:AirLibrary::Vine::Generated::air::UpdateCheckResponse = response.into_inner();
293 dev_log!(
294 "grpc",
295 "[AirClient] Update check completed. Update available: {}",
296 response.update_available
297 );
298 Ok(UpdateInfo {
299 update_available:response.update_available,
300 version:response.version,
301 download_url:response.download_url,
302 release_notes:response.release_notes,
303 })
304 },
305 Err(e) => {
306 dev_log!("grpc", "error: [AirClient] Check for updates RPC error: {}", e);
307 Err(CommonError::IPCError { Description:format!("Check for updates RPC error: {}", e) })
308 },
309 }
310 }
311
312 #[cfg(not(feature = "AirIntegration"))]
313 {
314 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
315 }
316 }
317
318 pub async fn download_update(
330 &self,
331 request_id:String,
332 url:String,
333 destination_path:String,
334 checksum:String,
335 headers:HashMap<String, String>,
336 ) -> Result<FileInfo, CommonError> {
337 dev_log!("grpc", "[AirClient] Downloading update from: {}", url);
338
339 #[cfg(feature = "AirIntegration")]
340 {
341 use AirLibrary::Vine::Generated::air::DownloadRequest;
342
343 let request = DownloadRequest { request_id, url, destination_path, checksum, headers };
344
345 let client = self
346 .client
347 .as_ref()
348 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
349 let mut client_guard = client.lock().await;
350
351 match client_guard.download_update(Request::new(request)).await {
352 Ok(response) => {
353 let response:AirLibrary::Vine::Generated::air::DownloadResponse = response.into_inner();
354 if response.success {
355 dev_log!("grpc", "[AirClient] Update downloaded successfully to: {}", response.file_path);
356 Ok(FileInfo {
357 file_path:response.file_path,
358 file_size:response.file_size,
359 checksum:response.checksum,
360 })
361 } else {
362 dev_log!("grpc", "error: [AirClient] Update download failed: {}", response.error);
363 Err(CommonError::IPCError { Description:response.error })
364 }
365 },
366 Err(e) => {
367 dev_log!("grpc", "error: [AirClient] Download update RPC error: {}", e);
368 Err(CommonError::IPCError { Description:format!("Download update RPC error: {}", e) })
369 },
370 }
371 }
372
373 #[cfg(not(feature = "AirIntegration"))]
374 {
375 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
376 }
377 }
378
379 pub async fn apply_update(&self, request_id:String, version:String, update_path:String) -> Result<(), CommonError> {
389 dev_log!("grpc", "[AirClient] Applying update version: {}", version);
390
391 #[cfg(feature = "AirIntegration")]
392 {
393 use AirLibrary::Vine::Generated::air::ApplyUpdateRequest;
394
395 let request = ApplyUpdateRequest { request_id, version, update_path };
396
397 let client = self
398 .client
399 .as_ref()
400 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
401 let mut client_guard = client.lock().await;
402
403 match client_guard.apply_update(Request::new(request)).await {
404 Ok(response) => {
405 let response:AirLibrary::Vine::Generated::air::ApplyUpdateResponse = response.into_inner();
406 if response.success {
407 dev_log!("grpc", "[AirClient] Update applied successfully");
408 Ok(())
409 } else {
410 dev_log!("grpc", "error: [AirClient] Update application failed: {}", response.error);
411 Err(CommonError::IPCError { Description:response.error })
412 }
413 },
414 Err(e) => {
415 dev_log!("grpc", "error: [AirClient] Apply update RPC error: {}", e);
416 Err(CommonError::IPCError { Description:format!("Apply update RPC error: {}", e) })
417 },
418 }
419 }
420
421 #[cfg(not(feature = "AirIntegration"))]
422 {
423 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
424 }
425 }
426
427 pub async fn download_file(
443 &self,
444 request_id:String,
445 url:String,
446 destination_path:String,
447 checksum:String,
448 headers:HashMap<String, String>,
449 ) -> Result<FileInfo, CommonError> {
450 dev_log!("grpc", "[AirClient] Downloading file from: {}", url);
451
452 #[cfg(feature = "AirIntegration")]
453 {
454 use AirLibrary::Vine::Generated::air::DownloadRequest;
455
456 let request = DownloadRequest { request_id, url, destination_path, checksum, headers };
457
458 let client = self
459 .client
460 .as_ref()
461 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
462 let mut client_guard = client.lock().await;
463
464 match client_guard.download_file(Request::new(request)).await {
465 Ok(response) => {
466 let response:AirLibrary::Vine::Generated::air::DownloadResponse = response.into_inner();
467 if response.success {
468 dev_log!("grpc", "[AirClient] File downloaded successfully to: {}", response.file_path);
469 Ok(FileInfo {
470 file_path:response.file_path,
471 file_size:response.file_size,
472 checksum:response.checksum,
473 })
474 } else {
475 dev_log!("grpc", "error: [AirClient] File download failed: {}", response.error);
476 Err(CommonError::IPCError { Description:response.error })
477 }
478 },
479 Err(e) => {
480 dev_log!("grpc", "error: [AirClient] Download file RPC error: {}", e);
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 pub async fn download_stream(
546 &self,
547 request_id:String,
548 url:String,
549 headers:HashMap<String, String>,
550 ) -> Result<DownloadStream, CommonError> {
551 dev_log!("grpc", "[AirClient] Starting stream download from: {}", url);
552
553 #[cfg(feature = "AirIntegration")]
554 {
555 use AirLibrary::Vine::Generated::air::DownloadStreamRequest;
556
557 let request = DownloadStreamRequest { request_id, url, headers };
558
559 let client = self
560 .client
561 .as_ref()
562 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
563 let mut client_guard = client.lock().await;
564
565 match client_guard.download_stream(Request::new(request)).await {
566 Ok(response) => {
567 dev_log!("grpc", "[AirClient] Stream download initiated successfully");
568 Ok(DownloadStream::new(response.into_inner()))
569 },
570 Err(e) => {
571 dev_log!("grpc", "error: [AirClient] Download stream RPC error: {}", e);
572 Err(CommonError::IPCError { Description:format!("Download stream RPC error: {}", e) })
573 },
574 }
575 }
576
577 #[cfg(not(feature = "AirIntegration"))]
578 {
579 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
580 }
581 }
582
583 pub async fn index_files(
599 &self,
600 request_id:String,
601 path:String,
602 patterns:Vec<String>,
603 exclude_patterns:Vec<String>,
604 max_depth:u32,
605 ) -> Result<IndexInfo, CommonError> {
606 dev_log!("grpc", "[AirClient] Indexing files in: {}", path);
607
608 #[cfg(feature = "AirIntegration")]
609 {
610 use AirLibrary::Vine::Generated::air::IndexRequest;
611
612 let request = IndexRequest { request_id, path, patterns, exclude_patterns, max_depth };
613
614 let client = self
615 .client
616 .as_ref()
617 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
618 let mut client_guard = client.lock().await;
619
620 match client_guard.index_files(Request::new(request)).await {
621 Ok(response) => {
622 let response = response.into_inner();
623 dev_log!(
625 "grpc",
626 "[AirClient] Files indexed: {} (total size: {} bytes)",
627 response.files_indexed,
628 response.total_size
629 );
630 Ok(IndexInfo { files_indexed:response.files_indexed, total_size:response.total_size })
631 },
632 Err(e) => {
633 dev_log!("grpc", "error: [AirClient] Index files RPC error: {}", e);
634 Err(CommonError::IPCError { Description:format!("Index files RPC error: {}", e) })
635 },
636 }
637 }
638
639 #[cfg(not(feature = "AirIntegration"))]
640 {
641 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
642 }
643 }
644
645 pub async fn search_files(
656 &self,
657 request_id:String,
658 query:String,
659 path:String,
660 max_results:u32,
661 ) -> Result<Vec<FileResult>, CommonError> {
662 dev_log!("grpc", "[AirClient] Searching for files with query: '{}' in: {}", query, path);
663
664 #[cfg(feature = "AirIntegration")]
665 {
666 use AirLibrary::Vine::Generated::air::SearchRequest;
667
668 let request = SearchRequest { request_id, query, path, max_results };
669
670 let client = self
671 .client
672 .as_ref()
673 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
674 let mut client_guard = client.lock().await;
675
676 match client_guard.search_files(Request::new(request)).await {
677 Ok(_response) => {
678 dev_log!("grpc", "[AirClient] Search completed");
679 Ok(Vec::new())
681 },
682 Err(e) => {
683 dev_log!("grpc", "error: [AirClient] Search files RPC error: {}", e);
684 Err(CommonError::IPCError { Description:format!("Search files RPC error: {}", e) })
685 },
686 }
687 }
688
689 #[cfg(not(feature = "AirIntegration"))]
690 {
691 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
692 }
693 }
694
695 pub async fn get_file_info(&self, request_id:String, path:String) -> Result<ExtendedFileInfo, CommonError> {
704 let path_display = path.clone();
705 dev_log!("grpc", "[AirClient] Getting file info for: {}", path);
706
707 #[cfg(feature = "AirIntegration")]
708 {
709 use AirLibrary::Vine::Generated::air::FileInfoRequest;
710
711 let request = FileInfoRequest { request_id, path };
712
713 let client = self
714 .client
715 .as_ref()
716 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
717 let mut client_guard = client.lock().await;
718
719 match client_guard.get_file_info(Request::new(request)).await {
720 Ok(response) => {
721 let response:AirLibrary::Vine::Generated::air::FileInfoResponse = response.into_inner();
722 dev_log!(
723 "grpc",
724 "[AirClient] File info retrieved for: {} (exists: {})",
725 path_display,
726 response.exists
727 );
728 Ok(ExtendedFileInfo {
729 exists:response.exists,
730 size:response.size,
731 mime_type:response.mime_type,
732 checksum:response.checksum,
733 modified_time:response.modified_time,
734 })
735 },
736 Err(e) => {
737 dev_log!("grpc", "error: [AirClient] Get file info RPC error: {}", e);
738 Err(CommonError::IPCError { Description:format!("Get file info RPC error: {}", e) })
739 },
740 }
741 }
742
743 #[cfg(not(feature = "AirIntegration"))]
744 {
745 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
746 }
747 }
748
749 pub async fn get_status(&self, request_id:String) -> Result<AirStatus, CommonError> {
759 dev_log!("grpc", "[AirClient] Getting Air daemon status");
760
761 #[cfg(feature = "AirIntegration")]
762 {
763 use AirLibrary::Vine::Generated::air::StatusRequest;
764
765 let request = StatusRequest { request_id };
766
767 let client = self
768 .client
769 .as_ref()
770 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
771 let mut client_guard = client.lock().await;
772
773 match client_guard.get_status(Request::new(request)).await {
774 Ok(response) => {
775 let response:AirLibrary::Vine::Generated::air::StatusResponse = response.into_inner();
776 dev_log!(
777 "grpc",
778 "[AirClient] Status retrieved. Active requests: {}",
779 response.active_requests
780 );
781 Ok(AirStatus {
782 version:response.version,
783 uptime_seconds:response.uptime_seconds,
784 total_requests:response.total_requests,
785 successful_requests:response.successful_requests,
786 failed_requests:response.failed_requests,
787 average_response_time:response.average_response_time,
788 memory_usage_mb:response.memory_usage_mb,
789 cpu_usage_percent:response.cpu_usage_percent,
790 active_requests:response.active_requests,
791 })
792 },
793 Err(e) => {
794 dev_log!("grpc", "error: [AirClient] Get status RPC error: {}", e);
795 Err(CommonError::IPCError { Description:format!("Get status RPC error: {}", e) })
796 },
797 }
798 }
799
800 #[cfg(not(feature = "AirIntegration"))]
801 {
802 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
803 }
804 }
805
806 pub async fn health_check(&self) -> Result<bool, CommonError> {
812 dev_log!("grpc", "[AirClient] Performing health check");
813
814 #[cfg(feature = "AirIntegration")]
815 {
816 use AirLibrary::Vine::Generated::air::HealthCheckRequest;
817
818 let request = HealthCheckRequest {};
819
820 let client = self
821 .client
822 .as_ref()
823 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
824 let mut client_guard = client.lock().await;
825
826 match client_guard.health_check(Request::new(request)).await {
827 Ok(response) => {
828 let response:AirLibrary::Vine::Generated::air::HealthCheckResponse = response.into_inner();
829 dev_log!("grpc", "[AirClient] Health check result: {}", response.healthy);
830 Ok(response.healthy)
831 },
832 Err(e) => {
833 dev_log!("grpc", "error: [AirClient] Health check RPC error: {}", e);
834 Err(CommonError::IPCError { Description:format!("Health check RPC error: {}", e) })
835 },
836 }
837 }
838
839 #[cfg(not(feature = "AirIntegration"))]
840 {
841 Ok(true)
844 }
845 }
846
847 pub async fn get_metrics(&self, request_id:String, metric_type:Option<String>) -> Result<AirMetrics, CommonError> {
857 dev_log!("grpc", "[AirClient] Getting metrics (type: {:?})", metric_type.as_deref());
858
859 #[cfg(feature = "AirIntegration")]
860 {
861 use AirLibrary::Vine::Generated::air::MetricsRequest;
862
863 let request = MetricsRequest { request_id, metric_type:metric_type.unwrap_or_default() };
864
865 let client = self
866 .client
867 .as_ref()
868 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
869 let mut client_guard = client.lock().await;
870
871 match client_guard.get_metrics(Request::new(request)).await {
872 Ok(response) => {
873 let response:AirLibrary::Vine::Generated::air::MetricsResponse = response.into_inner();
874 dev_log!("grpc", "[AirClient] Metrics retrieved");
875 let metrics = AirMetrics {
877 memory_usage_mb:response
878 .metrics
879 .get("memory_usage_mb")
880 .and_then(|s| s.parse::<f64>().ok())
881 .unwrap_or(0.0),
882 cpu_usage_percent:response
883 .metrics
884 .get("cpu_usage_percent")
885 .and_then(|s| s.parse::<f64>().ok())
886 .unwrap_or(0.0),
887 network_usage_mbps:response
888 .metrics
889 .get("network_usage_mbps")
890 .and_then(|s| s.parse::<f64>().ok())
891 .unwrap_or(0.0),
892 disk_usage_mb:response
893 .metrics
894 .get("disk_usage_mb")
895 .and_then(|s| s.parse::<f64>().ok())
896 .unwrap_or(0.0),
897 average_response_time:response
898 .metrics
899 .get("average_response_time")
900 .and_then(|s| s.parse::<f64>().ok())
901 .unwrap_or(0.0),
902 };
903 Ok(metrics)
904 },
905 Err(e) => {
906 dev_log!("grpc", "error: [AirClient] Get metrics RPC error: {}", e);
907 Err(CommonError::IPCError { Description:format!("Get metrics RPC error: {}", e) })
908 },
909 }
910 }
911
912 #[cfg(not(feature = "AirIntegration"))]
913 {
914 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
915 }
916 }
917
918 pub async fn get_resource_usage(&self, request_id:String) -> Result<ResourceUsage, CommonError> {
931 dev_log!("grpc", "[AirClient] Getting resource usage");
932
933 #[cfg(feature = "AirIntegration")]
934 {
935 use AirLibrary::Vine::Generated::air::ResourceUsageRequest;
936
937 let request = ResourceUsageRequest { request_id };
938
939 let client = self
940 .client
941 .as_ref()
942 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
943 let mut client_guard = client.lock().await;
944
945 match client_guard.get_resource_usage(Request::new(request)).await {
946 Ok(response) => {
947 let response:AirLibrary::Vine::Generated::air::ResourceUsageResponse = response.into_inner();
948 dev_log!("grpc", "[AirClient] Resource usage retrieved");
949 Ok(ResourceUsage {
950 memory_usage_mb:response.memory_usage_mb,
951 cpu_usage_percent:response.cpu_usage_percent,
952 disk_usage_mb:response.disk_usage_mb,
953 network_usage_mbps:response.network_usage_mbps,
954 thread_count:0, open_file_handles:0, })
957 },
958 Err(e) => {
959 dev_log!("grpc", "error: [AirClient] Get resource usage RPC error: {}", e);
960 Err(CommonError::IPCError { Description:format!("Get resource usage RPC error: {}", e) })
961 },
962 }
963 }
964
965 #[cfg(not(feature = "AirIntegration"))]
966 {
967 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
968 }
969 }
970
971 pub async fn set_resource_limits(
983 &self,
984 request_id:String,
985 memory_limit_mb:u32,
986 cpu_limit_percent:u32,
987 disk_limit_mb:u32,
988 ) -> Result<(), CommonError> {
989 dev_log!(
990 "grpc",
991 "[AirClient] Setting resource limits: memory={}MB, cpu={}%, disk={}MB",
992 memory_limit_mb,
993 cpu_limit_percent,
994 disk_limit_mb
995 );
996
997 #[cfg(feature = "AirIntegration")]
998 {
999 use AirLibrary::Vine::Generated::air::ResourceLimitsRequest;
1000
1001 let request = ResourceLimitsRequest { request_id, memory_limit_mb, cpu_limit_percent, disk_limit_mb };
1002
1003 let client = self
1004 .client
1005 .as_ref()
1006 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
1007 let mut client_guard = client.lock().await;
1008
1009 match client_guard.set_resource_limits(Request::new(request)).await {
1010 Ok(response) => {
1011 let response:AirLibrary::Vine::Generated::air::ResourceLimitsResponse = response.into_inner();
1012 if response.success {
1013 dev_log!("grpc", "[AirClient] Resource limits set successfully");
1014 Ok(())
1015 } else {
1016 dev_log!("grpc", "error: [AirClient] Failed to set resource limits: {}", response.error);
1017 Err(CommonError::IPCError { Description:response.error })
1018 }
1019 },
1020 Err(e) => {
1021 dev_log!("grpc", "error: [AirClient] Set resource limits RPC error: {}", e);
1022 Err(CommonError::IPCError { Description:format!("Set resource limits 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 pub async fn get_configuration(
1047 &self,
1048 request_id:String,
1049 section:String,
1050 ) -> Result<HashMap<String, String>, CommonError> {
1051 let section_display = section.clone();
1052 dev_log!("grpc", "[AirClient] Getting configuration for section: {}", section);
1053
1054 #[cfg(feature = "AirIntegration")]
1055 {
1056 use AirLibrary::Vine::Generated::air::ConfigurationRequest;
1057
1058 let request = ConfigurationRequest { request_id, section };
1059
1060 let client = self
1061 .client
1062 .as_ref()
1063 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
1064 let mut client_guard = client.lock().await;
1065
1066 match client_guard.get_configuration(Request::new(request)).await {
1067 Ok(response) => {
1068 let response:AirLibrary::Vine::Generated::air::ConfigurationResponse = response.into_inner();
1069 dev_log!(
1070 "grpc",
1071 "[AirClient] Configuration retrieved for section: {} ({} keys)",
1072 section_display,
1073 response.configuration.len()
1074 );
1075 Ok(response.configuration)
1076 },
1077 Err(e) => {
1078 dev_log!("grpc", "error: [AirClient] Get configuration RPC error: {}", e);
1079 Err(CommonError::IPCError { Description:format!("Get configuration RPC error: {}", e) })
1080 },
1081 }
1082 }
1083
1084 #[cfg(not(feature = "AirIntegration"))]
1085 {
1086 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
1087 }
1088 }
1089
1090 pub async fn update_configuration(
1100 &self,
1101 request_id:String,
1102 section:String,
1103 updates:HashMap<String, String>,
1104 ) -> Result<(), CommonError> {
1105 let section_display = section.clone();
1106 dev_log!(
1107 "grpc",
1108 "[AirClient] Updating configuration for section: {} ({} keys)",
1109 section_display,
1110 updates.len()
1111 );
1112
1113 #[cfg(feature = "AirIntegration")]
1114 {
1115 use AirLibrary::Vine::Generated::air::UpdateConfigurationRequest;
1116
1117 let request = UpdateConfigurationRequest { request_id, section, updates };
1118
1119 let client = self
1120 .client
1121 .as_ref()
1122 .ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
1123 let mut client_guard = client.lock().await;
1124
1125 match client_guard.update_configuration(Request::new(request)).await {
1126 Ok(response) => {
1127 let response:AirLibrary::Vine::Generated::air::UpdateConfigurationResponse = response.into_inner();
1128 if response.success {
1129 dev_log!(
1130 "grpc",
1131 "[AirClient] Configuration updated successfully for section: {}",
1132 section_display
1133 );
1134 Ok(())
1135 } else {
1136 dev_log!("grpc", "error: [AirClient] Failed to update configuration: {}", response.error);
1137 Err(CommonError::IPCError { Description:response.error })
1138 }
1139 },
1140 Err(e) => {
1141 dev_log!("grpc", "error: [AirClient] Update configuration RPC error: {}", e);
1142 Err(CommonError::IPCError { Description:format!("Update configuration RPC error: {}", e) })
1143 },
1144 }
1145 }
1146
1147 #[cfg(not(feature = "AirIntegration"))]
1148 {
1149 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
1150 }
1151 }
1152}
1153
1154#[derive(Debug, Clone)]
1160pub struct UpdateInfo {
1161 pub update_available:bool,
1162 pub version:String,
1163 pub download_url:String,
1164 pub release_notes:String,
1165}
1166
1167#[derive(Debug, Clone)]
1169pub struct FileInfo {
1170 pub file_path:String,
1171 pub file_size:u64,
1172 pub checksum:String,
1173}
1174
1175#[derive(Debug, Clone)]
1177pub struct IndexInfo {
1178 pub files_indexed:u32,
1179 pub total_size:u64,
1180}
1181
1182#[derive(Debug, Clone)]
1184pub struct FileResult {
1185 pub path:String,
1186 pub size:u64,
1187 pub match_preview:String,
1188 pub line_number:u32,
1189}
1190
1191#[derive(Debug, Clone)]
1193pub struct ExtendedFileInfo {
1194 pub exists:bool,
1195 pub size:u64,
1196 pub mime_type:String,
1197 pub checksum:String,
1198 pub modified_time:u64,
1199}
1200
1201#[derive(Debug, Clone)]
1203pub struct AirStatus {
1204 pub version:String,
1205 pub uptime_seconds:u64,
1206 pub total_requests:u64,
1207 pub successful_requests:u64,
1208 pub failed_requests:u64,
1209 pub average_response_time:f64,
1210 pub memory_usage_mb:f64,
1211 pub cpu_usage_percent:f64,
1212 pub active_requests:u32,
1213}
1214
1215#[derive(Debug, Clone)]
1217pub struct AirMetrics {
1218 pub memory_usage_mb:f64,
1219 pub cpu_usage_percent:f64,
1220 pub network_usage_mbps:f64,
1221 pub disk_usage_mb:f64,
1222 pub average_response_time:f64,
1223}
1224
1225#[derive(Debug, Clone)]
1227pub struct ResourceUsage {
1228 pub memory_usage_mb:f64,
1229 pub cpu_usage_percent:f64,
1230 pub disk_usage_mb:f64,
1231 pub network_usage_mbps:f64,
1232 pub thread_count:u32,
1233 pub open_file_handles:u32,
1234}
1235
1236#[derive(Debug, Clone)]
1241pub struct DownloadStreamChunk {
1242 pub data:Vec<u8>,
1244 pub total_size:u64,
1246 pub downloaded:u64,
1248 pub completed:bool,
1250 pub error:String,
1252}
1253
1254pub struct DownloadStream {
1280 inner:tonic::codec::Streaming<AirLibrary::Vine::Generated::air::DownloadStreamResponse>,
1281}
1282
1283impl DownloadStream {
1284 pub fn new(stream:tonic::codec::Streaming<AirLibrary::Vine::Generated::air::DownloadStreamResponse>) -> Self {
1286 Self { inner:stream }
1287 }
1288
1289 pub async fn next(&mut self) -> Option<Result<DownloadStreamChunk, CommonError>> {
1293 match futures_util::stream::StreamExt::next(&mut self.inner).await {
1294 Some(Ok(response)) => {
1295 Some(Ok(DownloadStreamChunk {
1296 data:response.chunk,
1297 total_size:response.total_size,
1298 downloaded:response.downloaded,
1299 completed:response.completed,
1300 error:response.error,
1301 }))
1302 },
1303 Some(Err(e)) => {
1304 dev_log!("grpc", "error: [DownloadStream] Stream error: {}", e);
1305 Some(Err(CommonError::IPCError { Description:format!("Stream error: {}", e) }))
1306 },
1307 None => None,
1308 }
1309 }
1310}
1311
1312impl std::fmt::Debug for AirClient {
1317 fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AirClient({})", self.address) }
1318}
1319
1320trait IntoRequestExt {
1326 fn into_request(self) -> tonic::Request<Self>
1327 where
1328 Self: Sized, {
1329 tonic::Request::new(self)
1330 }
1331}
1332
1333impl<T> IntoRequestExt for T {}