1use std::{collections::HashMap, sync::Arc};
82use tokio::sync::Mutex;
83
84use CommonLibrary::Error::CommonError::CommonError;
85#[cfg(feature = "AirIntegration")]
86use AirLibrary::Vine::Generated::air::air_service_client::AirServiceClient;
87use futures_util::StreamExt;
88use log::{debug, error, info, warn};
89use tonic::{Request, transport::Channel};
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>>>>,
109 address:String,
111}
112
113impl AirClient {
114 pub async fn new(address:&str) -> Result<Self, CommonError> {
135 info!("[AirClient] Connecting to Air daemon at: {}", address);
136
137 #[cfg(feature = "AirIntegration")]
138 {
139 let endpoint = address.parse::<tonic::transport::Endpoint>().map_err(|e| {
140 error!("[AirClient] Failed to parse address '{}': {}", address, e);
141 CommonError::IPCError { Description:format!("Invalid address '{}': {}", address, e) }
142 })?;
143
144 let channel = endpoint.connect().await.map_err(|e| {
145 error!("[AirClient] Failed to connect to Air daemon: {}", e);
146 CommonError::IPCError { Description:format!("Connection failed: {}", e) }
147 })?;
148
149 info!("[AirClient] Successfully connected to Air daemon at: {}", address);
150
151 let client = Arc::new(Mutex::new(AirServiceClient::new(channel)));
152 Ok(Self { client:Some(client), address:address.to_string() })
153 }
154
155 #[cfg(not(feature = "AirIntegration"))]
156 {
157 error!("[AirClient] AirIntegration feature is not enabled");
158 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
159 }
160 }
161
162 pub fn is_connected(&self) -> bool {
168 #[cfg(feature = "AirIntegration")]
169 {
170 self.client.is_some()
171 }
172
173 #[cfg(not(feature = "AirIntegration"))]
174 {
175 false
176 }
177 }
178
179 pub fn address(&self) -> &str { &self.address }
184
185 pub async fn authenticate(
201 &self,
202 request_id:String,
203 username:String,
204 password:String,
205 provider:String,
206 ) -> Result<String, CommonError> {
207 debug!("[AirClient] Authenticating user '{}' with provider '{}'", username, provider);
208
209 #[cfg(feature = "AirIntegration")]
210 {
211 use AirLibrary::Vine::Generated::air::AuthenticationRequest;
212
213 let username_display = username.clone();
214 let request = AuthenticationRequest { request_id, username, password, provider };
215
216 let client = self.client.as_ref().ok_or_else(|| {
217 CommonError::IPCError { Description:"Air client not initialized".to_string() }
218 })?;
219
220 let mut client_guard = client.lock().await;
221 match client_guard.authenticate(Request::new(request)).await {
222 Ok(response) => {
223 let response = response.into_inner();
224 if response.success {
225 info!("[AirClient] Authentication successful for user '{}'", username_display);
226 Ok(response.token)
227 } else {
228 error!("[AirClient] Authentication failed for user '{}': {}", username_display, response.error);
229 Err(CommonError::AccessDenied { Reason:response.error })
230 }
231 },
232 Err(e) => {
233 error!("[AirClient] Authentication RPC error: {}", e);
234 Err(CommonError::IPCError { Description:format!("Authentication RPC error: {}", e) })
235 },
236 }
237 }
238
239 #[cfg(not(feature = "AirIntegration"))]
240 {
241 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
242 }
243 }
244
245 pub async fn check_for_updates(
259 &self,
260 request_id:String,
261 current_version:String,
262 channel:String,
263 ) -> Result<UpdateInfo, CommonError> {
264 debug!("[AirClient] Checking for updates for version '{}'", current_version);
265
266 #[cfg(feature = "AirIntegration")]
267 {
268 use AirLibrary::Vine::Generated::air::UpdateCheckRequest;
269
270 let request = UpdateCheckRequest { request_id, current_version, channel };
271
272 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
273 let mut client_guard = client.lock().await;
274
275 match client_guard.check_for_updates(Request::new(request)).await {
276 Ok(response) => {
277 let response: AirLibrary::Vine::Generated::air::UpdateCheckResponse = response.into_inner();
278 info!(
279 "[AirClient] Update check completed. Update available: {}",
280 response.update_available
281 );
282 Ok(UpdateInfo {
283 update_available:response.update_available,
284 version:response.version,
285 download_url:response.download_url,
286 release_notes:response.release_notes,
287 })
288 },
289 Err(e) => {
290 error!("[AirClient] Check for updates RPC error: {}", e);
291 Err(CommonError::IPCError { Description:format!("Check for updates RPC error: {}", e) })
292 },
293 }
294 }
295
296 #[cfg(not(feature = "AirIntegration"))]
297 {
298 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
299 }
300 }
301
302 pub async fn download_update(
314 &self,
315 request_id:String,
316 url:String,
317 destination_path:String,
318 checksum:String,
319 headers:HashMap<String, String>,
320 ) -> Result<FileInfo, CommonError> {
321 debug!("[AirClient] Downloading update from: {}", url);
322
323 #[cfg(feature = "AirIntegration")]
324 {
325 use AirLibrary::Vine::Generated::air::DownloadRequest;
326
327 let request = DownloadRequest { request_id, url, destination_path, checksum, headers };
328
329 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
330 let mut client_guard = client.lock().await;
331
332 match client_guard.download_update(Request::new(request)).await {
333 Ok(response) => {
334 let response: AirLibrary::Vine::Generated::air::DownloadResponse = response.into_inner();
335 if response.success {
336 info!("[AirClient] Update downloaded successfully to: {}", response.file_path);
337 Ok(FileInfo {
338 file_path:response.file_path,
339 file_size:response.file_size,
340 checksum:response.checksum,
341 })
342 } else {
343 error!("[AirClient] Update download failed: {}", response.error);
344 Err(CommonError::IPCError { Description:response.error })
345 }
346 },
347 Err(e) => {
348 error!("[AirClient] Download update RPC error: {}", e);
349 Err(CommonError::IPCError { Description:format!("Download update RPC error: {}", e) })
350 },
351 }
352 }
353
354 #[cfg(not(feature = "AirIntegration"))]
355 {
356 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
357 }
358 }
359
360 pub async fn apply_update(&self, request_id:String, version:String, update_path:String) -> Result<(), CommonError> {
370 debug!("[AirClient] Applying update version: {}", version);
371
372 #[cfg(feature = "AirIntegration")]
373 {
374 use AirLibrary::Vine::Generated::air::ApplyUpdateRequest;
375
376 let request = ApplyUpdateRequest { request_id, version, update_path };
377
378 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
379 let mut client_guard = client.lock().await;
380
381 match client_guard.apply_update(Request::new(request)).await {
382 Ok(response) => {
383 let response: AirLibrary::Vine::Generated::air::ApplyUpdateResponse = response.into_inner();
384 if response.success {
385 info!("[AirClient] Update applied successfully");
386 Ok(())
387 } else {
388 error!("[AirClient] Update application failed: {}", response.error);
389 Err(CommonError::IPCError { Description:response.error })
390 }
391 },
392 Err(e) => {
393 error!("[AirClient] Apply update RPC error: {}", e);
394 Err(CommonError::IPCError { Description:format!("Apply update RPC error: {}", e) })
395 },
396 }
397 }
398
399 #[cfg(not(feature = "AirIntegration"))]
400 {
401 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
402 }
403 }
404
405 pub async fn download_file(
421 &self,
422 request_id:String,
423 url:String,
424 destination_path:String,
425 checksum:String,
426 headers:HashMap<String, String>,
427 ) -> Result<FileInfo, CommonError> {
428 debug!("[AirClient] Downloading file from: {}", url);
429
430 #[cfg(feature = "AirIntegration")]
431 {
432 use AirLibrary::Vine::Generated::air::DownloadRequest;
433
434 let request = DownloadRequest { request_id, url, destination_path, checksum, headers };
435
436 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
437 let mut client_guard = client.lock().await;
438
439 match client_guard.download_file(Request::new(request)).await {
440 Ok(response) => {
441 let response: AirLibrary::Vine::Generated::air::DownloadResponse = response.into_inner();
442 if response.success {
443 info!("[AirClient] File downloaded successfully to: {}", response.file_path);
444 Ok(FileInfo {
445 file_path:response.file_path,
446 file_size:response.file_size,
447 checksum:response.checksum,
448 })
449 } else {
450 error!("[AirClient] File download failed: {}", response.error);
451 Err(CommonError::IPCError { Description:response.error })
452 }
453 },
454 Err(e) => {
455 error!("[AirClient] Download file RPC error: {}", e);
456 Err(CommonError::IPCError { Description:format!("Download file RPC error: {}", e) })
457 },
458 }
459 }
460
461 #[cfg(not(feature = "AirIntegration"))]
462 {
463 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
464 }
465 }
466
467 pub async fn download_stream(
521 &self,
522 request_id:String,
523 url:String,
524 headers:HashMap<String, String>,
525 ) -> Result<DownloadStream, CommonError> {
526 debug!("[AirClient] Starting stream download from: {}", url);
527
528 #[cfg(feature = "AirIntegration")]
529 {
530 use AirLibrary::Vine::Generated::air::DownloadStreamRequest;
531
532 let request = DownloadStreamRequest { request_id, url, headers };
533
534 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
535 let mut client_guard = client.lock().await;
536
537 match client_guard.download_stream(Request::new(request)).await {
538 Ok(response) => {
539 info!("[AirClient] Stream download initiated successfully");
540 Ok(DownloadStream::new(response.into_inner()))
541 },
542 Err(e) => {
543 error!("[AirClient] Download stream RPC error: {}", e);
544 Err(CommonError::IPCError { Description:format!("Download stream RPC error: {}", e) })
545 },
546 }
547 }
548
549 #[cfg(not(feature = "AirIntegration"))]
550 {
551 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
552 }
553 }
554
555 pub async fn index_files(
571 &self,
572 request_id:String,
573 path:String,
574 patterns:Vec<String>,
575 exclude_patterns:Vec<String>,
576 max_depth:u32,
577 ) -> Result<IndexInfo, CommonError> {
578 debug!("[AirClient] Indexing files in: {}", path);
579
580 #[cfg(feature = "AirIntegration")]
581 {
582 use AirLibrary::Vine::Generated::air::IndexRequest;
583
584 let request = IndexRequest { request_id, path, patterns, exclude_patterns, max_depth };
585
586 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
587 let mut client_guard = client.lock().await;
588
589 match client_guard.index_files(Request::new(request)).await {
590 Ok(response) => {
591 let response = response.into_inner();
592 info!(
594 "[AirClient] Files indexed: {} (total size: {} bytes)",
595 response.files_indexed, response.total_size
596 );
597 Ok(IndexInfo { files_indexed:response.files_indexed, total_size:response.total_size })
598 },
599 Err(e) => {
600 error!("[AirClient] Index files RPC error: {}", e);
601 Err(CommonError::IPCError { Description:format!("Index files RPC error: {}", e) })
602 },
603 }
604 }
605
606 #[cfg(not(feature = "AirIntegration"))]
607 {
608 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
609 }
610 }
611
612 pub async fn search_files(
623 &self,
624 request_id:String,
625 query:String,
626 path:String,
627 max_results:u32,
628 ) -> Result<Vec<FileResult>, CommonError> {
629 debug!("[AirClient] Searching for files with query: '{}' in: {}", query, path);
630
631 #[cfg(feature = "AirIntegration")]
632 {
633 use AirLibrary::Vine::Generated::air::SearchRequest;
634
635 let request = SearchRequest { request_id, query, path, max_results };
636
637 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
638 let mut client_guard = client.lock().await;
639
640 match client_guard.search_files(Request::new(request)).await {
641 Ok(_response) => {
642 info!("[AirClient] Search completed");
643 Ok(Vec::new())
645 },
646 Err(e) => {
647 error!("[AirClient] Search files RPC error: {}", e);
648 Err(CommonError::IPCError { Description:format!("Search files RPC error: {}", e) })
649 },
650 }
651 }
652
653 #[cfg(not(feature = "AirIntegration"))]
654 {
655 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
656 }
657 }
658
659 pub async fn get_file_info(&self, request_id:String, path:String) -> Result<ExtendedFileInfo, CommonError> {
668 let path_display = path.clone();
669 debug!("[AirClient] Getting file info for: {}", path);
670
671 #[cfg(feature = "AirIntegration")]
672 {
673 use AirLibrary::Vine::Generated::air::FileInfoRequest;
674
675 let request = FileInfoRequest { request_id, path };
676
677 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
678 let mut client_guard = client.lock().await;
679
680 match client_guard.get_file_info(Request::new(request)).await {
681 Ok(response) => {
682 let response: AirLibrary::Vine::Generated::air::FileInfoResponse = response.into_inner();
683 info!("[AirClient] File info retrieved for: {} (exists: {})", path_display, response.exists);
684 Ok(ExtendedFileInfo {
685 exists:response.exists,
686 size:response.size,
687 mime_type:response.mime_type,
688 checksum:response.checksum,
689 modified_time:response.modified_time,
690 })
691 },
692 Err(e) => {
693 error!("[AirClient] Get file info RPC error: {}", e);
694 Err(CommonError::IPCError { Description:format!("Get file info RPC error: {}", e) })
695 },
696 }
697 }
698
699 #[cfg(not(feature = "AirIntegration"))]
700 {
701 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
702 }
703 }
704
705 pub async fn get_status(&self, request_id:String) -> Result<AirStatus, CommonError> {
715 debug!("[AirClient] Getting Air daemon status");
716
717 #[cfg(feature = "AirIntegration")]
718 {
719 use AirLibrary::Vine::Generated::air::StatusRequest;
720
721 let request = StatusRequest { request_id };
722
723 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
724 let mut client_guard = client.lock().await;
725
726 match client_guard.get_status(Request::new(request)).await {
727 Ok(response) => {
728 let response: AirLibrary::Vine::Generated::air::StatusResponse = response.into_inner();
729 info!("[AirClient] Status retrieved. Active requests: {}", response.active_requests);
730 Ok(AirStatus {
731 version:response.version,
732 uptime_seconds:response.uptime_seconds,
733 total_requests:response.total_requests,
734 successful_requests:response.successful_requests,
735 failed_requests:response.failed_requests,
736 average_response_time:response.average_response_time,
737 memory_usage_mb:response.memory_usage_mb,
738 cpu_usage_percent:response.cpu_usage_percent,
739 active_requests:response.active_requests,
740 })
741 },
742 Err(e) => {
743 error!("[AirClient] Get status RPC error: {}", e);
744 Err(CommonError::IPCError { Description:format!("Get status RPC error: {}", e) })
745 },
746 }
747 }
748
749 #[cfg(not(feature = "AirIntegration"))]
750 {
751 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
752 }
753 }
754
755 pub async fn health_check(&self) -> Result<bool, CommonError> {
761 debug!("[AirClient] Performing health check");
762
763 #[cfg(feature = "AirIntegration")]
764 {
765 use AirLibrary::Vine::Generated::air::HealthCheckRequest;
766
767 let request = HealthCheckRequest {};
768
769 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
770 let mut client_guard = client.lock().await;
771
772 match client_guard.health_check(Request::new(request)).await {
773 Ok(response) => {
774 let response: AirLibrary::Vine::Generated::air::HealthCheckResponse = response.into_inner();
775 debug!("[AirClient] Health check result: {}", response.healthy);
776 Ok(response.healthy)
777 },
778 Err(e) => {
779 error!("[AirClient] Health check RPC error: {}", e);
780 Err(CommonError::IPCError { Description:format!("Health check RPC error: {}", e) })
781 },
782 }
783 }
784
785 #[cfg(not(feature = "AirIntegration"))]
786 {
787 Ok(true)
790 }
791 }
792
793 pub async fn get_metrics(&self, request_id:String, metric_type:Option<String>) -> Result<AirMetrics, CommonError> {
803 debug!("[AirClient] Getting metrics (type: {:?})", metric_type.as_deref());
804
805 #[cfg(feature = "AirIntegration")]
806 {
807 use AirLibrary::Vine::Generated::air::MetricsRequest;
808
809 let request = MetricsRequest { request_id, metric_type:metric_type.unwrap_or_default() };
810
811 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
812 let mut client_guard = client.lock().await;
813
814 match client_guard.get_metrics(Request::new(request)).await {
815 Ok(response) => {
816 let response: AirLibrary::Vine::Generated::air::MetricsResponse = response.into_inner();
817 info!("[AirClient] Metrics retrieved");
818 let metrics = AirMetrics {
820 memory_usage_mb:response
821 .metrics
822 .get("memory_usage_mb")
823 .and_then(|s| s.parse::<f64>().ok())
824 .unwrap_or(0.0),
825 cpu_usage_percent:response
826 .metrics
827 .get("cpu_usage_percent")
828 .and_then(|s| s.parse::<f64>().ok())
829 .unwrap_or(0.0),
830 network_usage_mbps:response
831 .metrics
832 .get("network_usage_mbps")
833 .and_then(|s| s.parse::<f64>().ok())
834 .unwrap_or(0.0),
835 disk_usage_mb:response
836 .metrics
837 .get("disk_usage_mb")
838 .and_then(|s| s.parse::<f64>().ok())
839 .unwrap_or(0.0),
840 average_response_time:response
841 .metrics
842 .get("average_response_time")
843 .and_then(|s| s.parse::<f64>().ok())
844 .unwrap_or(0.0),
845 };
846 Ok(metrics)
847 },
848 Err(e) => {
849 error!("[AirClient] Get metrics RPC error: {}", e);
850 Err(CommonError::IPCError { Description:format!("Get metrics RPC error: {}", e) })
851 },
852 }
853 }
854
855 #[cfg(not(feature = "AirIntegration"))]
856 {
857 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
858 }
859 }
860
861 pub async fn get_resource_usage(&self, request_id:String) -> Result<ResourceUsage, CommonError> {
874 debug!("[AirClient] Getting resource usage");
875
876 #[cfg(feature = "AirIntegration")]
877 {
878 use AirLibrary::Vine::Generated::air::ResourceUsageRequest;
879
880 let request = ResourceUsageRequest { request_id };
881
882 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
883 let mut client_guard = client.lock().await;
884
885 match client_guard.get_resource_usage(Request::new(request)).await {
886 Ok(response) => {
887 let response: AirLibrary::Vine::Generated::air::ResourceUsageResponse = response.into_inner();
888 info!("[AirClient] Resource usage retrieved");
889 Ok(ResourceUsage {
890 memory_usage_mb:response.memory_usage_mb,
891 cpu_usage_percent:response.cpu_usage_percent,
892 disk_usage_mb:response.disk_usage_mb,
893 network_usage_mbps:response.network_usage_mbps,
894 thread_count:0, open_file_handles:0, })
897 },
898 Err(e) => {
899 error!("[AirClient] Get resource usage RPC error: {}", e);
900 Err(CommonError::IPCError { Description:format!("Get resource usage RPC error: {}", e) })
901 },
902 }
903 }
904
905 #[cfg(not(feature = "AirIntegration"))]
906 {
907 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
908 }
909 }
910
911 pub async fn set_resource_limits(
923 &self,
924 request_id:String,
925 memory_limit_mb:u32,
926 cpu_limit_percent:u32,
927 disk_limit_mb:u32,
928 ) -> Result<(), CommonError> {
929 debug!(
930 "[AirClient] Setting resource limits: memory={}MB, cpu={}%, disk={}MB",
931 memory_limit_mb, cpu_limit_percent, disk_limit_mb
932 );
933
934 #[cfg(feature = "AirIntegration")]
935 {
936 use AirLibrary::Vine::Generated::air::ResourceLimitsRequest;
937
938 let request = ResourceLimitsRequest { request_id, memory_limit_mb, cpu_limit_percent, disk_limit_mb };
939
940 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
941 let mut client_guard = client.lock().await;
942
943 match client_guard.set_resource_limits(Request::new(request)).await {
944 Ok(response) => {
945 let response: AirLibrary::Vine::Generated::air::ResourceLimitsResponse = response.into_inner();
946 if response.success {
947 info!("[AirClient] Resource limits set successfully");
948 Ok(())
949 } else {
950 error!("[AirClient] Failed to set resource limits: {}", response.error);
951 Err(CommonError::IPCError { Description:response.error })
952 }
953 },
954 Err(e) => {
955 error!("[AirClient] Set resource limits RPC error: {}", e);
956 Err(CommonError::IPCError { Description:format!("Set resource limits RPC error: {}", e) })
957 },
958 }
959 }
960
961 #[cfg(not(feature = "AirIntegration"))]
962 {
963 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
964 }
965 }
966
967 pub async fn get_configuration(
981 &self,
982 request_id:String,
983 section:String,
984 ) -> Result<HashMap<String, String>, CommonError> {
985 let section_display = section.clone();
986 debug!("[AirClient] Getting configuration for section: {}", section);
987
988 #[cfg(feature = "AirIntegration")]
989 {
990 use AirLibrary::Vine::Generated::air::ConfigurationRequest;
991
992 let request = ConfigurationRequest { request_id, section };
993
994 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
995 let mut client_guard = client.lock().await;
996
997 match client_guard.get_configuration(Request::new(request)).await {
998 Ok(response) => {
999 let response: AirLibrary::Vine::Generated::air::ConfigurationResponse = response.into_inner();
1000 info!(
1001 "[AirClient] Configuration retrieved for section: {} ({} keys)",
1002 section_display,
1003 response.configuration.len()
1004 );
1005 Ok(response.configuration)
1006 },
1007 Err(e) => {
1008 error!("[AirClient] Get configuration RPC error: {}", e);
1009 Err(CommonError::IPCError { Description:format!("Get configuration RPC error: {}", e) })
1010 },
1011 }
1012 }
1013
1014 #[cfg(not(feature = "AirIntegration"))]
1015 {
1016 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
1017 }
1018 }
1019
1020 pub async fn update_configuration(
1030 &self,
1031 request_id:String,
1032 section:String,
1033 updates:HashMap<String, String>,
1034 ) -> Result<(), CommonError> {
1035 let section_display = section.clone();
1036 debug!(
1037 "[AirClient] Updating configuration for section: {} ({} keys)",
1038 section_display,
1039 updates.len()
1040 );
1041
1042 #[cfg(feature = "AirIntegration")]
1043 {
1044 use AirLibrary::Vine::Generated::air::UpdateConfigurationRequest;
1045
1046 let request = UpdateConfigurationRequest { request_id, section, updates };
1047
1048 let client = self.client.as_ref().ok_or_else(|| CommonError::IPCError { Description:"Air client not initialized".to_string() })?;
1049 let mut client_guard = client.lock().await;
1050
1051 match client_guard.update_configuration(Request::new(request)).await {
1052 Ok(response) => {
1053 let response: AirLibrary::Vine::Generated::air::UpdateConfigurationResponse = response.into_inner();
1054 if response.success {
1055 info!("[AirClient] Configuration updated successfully for section: {}", section_display);
1056 Ok(())
1057 } else {
1058 error!("[AirClient] Failed to update configuration: {}", response.error);
1059 Err(CommonError::IPCError { Description:response.error })
1060 }
1061 },
1062 Err(e) => {
1063 error!("[AirClient] Update configuration RPC error: {}", e);
1064 Err(CommonError::IPCError { Description:format!("Update configuration RPC error: {}", e) })
1065 },
1066 }
1067 }
1068
1069 #[cfg(not(feature = "AirIntegration"))]
1070 {
1071 Err(CommonError::FeatureNotAvailable { FeatureName:"AirIntegration".to_string() })
1072 }
1073 }
1074}
1075
1076#[derive(Debug, Clone)]
1082pub struct UpdateInfo {
1083 pub update_available:bool,
1084 pub version:String,
1085 pub download_url:String,
1086 pub release_notes:String,
1087}
1088
1089#[derive(Debug, Clone)]
1091pub struct FileInfo {
1092 pub file_path:String,
1093 pub file_size:u64,
1094 pub checksum:String,
1095}
1096
1097#[derive(Debug, Clone)]
1099pub struct IndexInfo {
1100 pub files_indexed:u32,
1101 pub total_size:u64,
1102}
1103
1104#[derive(Debug, Clone)]
1106pub struct FileResult {
1107 pub path:String,
1108 pub size:u64,
1109 pub match_preview:String,
1110 pub line_number:u32,
1111}
1112
1113#[derive(Debug, Clone)]
1115pub struct ExtendedFileInfo {
1116 pub exists:bool,
1117 pub size:u64,
1118 pub mime_type:String,
1119 pub checksum:String,
1120 pub modified_time:u64,
1121}
1122
1123#[derive(Debug, Clone)]
1125pub struct AirStatus {
1126 pub version:String,
1127 pub uptime_seconds:u64,
1128 pub total_requests:u64,
1129 pub successful_requests:u64,
1130 pub failed_requests:u64,
1131 pub average_response_time:f64,
1132 pub memory_usage_mb:f64,
1133 pub cpu_usage_percent:f64,
1134 pub active_requests:u32,
1135}
1136
1137#[derive(Debug, Clone)]
1139pub struct AirMetrics {
1140 pub memory_usage_mb:f64,
1141 pub cpu_usage_percent:f64,
1142 pub network_usage_mbps:f64,
1143 pub disk_usage_mb:f64,
1144 pub average_response_time:f64,
1145}
1146
1147#[derive(Debug, Clone)]
1149pub struct ResourceUsage {
1150 pub memory_usage_mb:f64,
1151 pub cpu_usage_percent:f64,
1152 pub disk_usage_mb:f64,
1153 pub network_usage_mbps:f64,
1154 pub thread_count:u32,
1155 pub open_file_handles:u32,
1156}
1157
1158#[derive(Debug, Clone)]
1163pub struct DownloadStreamChunk {
1164 pub data:Vec<u8>,
1166 pub total_size:u64,
1168 pub downloaded:u64,
1170 pub completed:bool,
1172 pub error:String,
1174}
1175
1176pub struct DownloadStream {
1202 inner:tonic::codec::Streaming<AirLibrary::Vine::Generated::air::DownloadStreamResponse>,
1203}
1204
1205impl DownloadStream {
1206 pub fn new(stream:tonic::codec::Streaming<AirLibrary::Vine::Generated::air::DownloadStreamResponse>) -> Self {
1208 Self { inner:stream }
1209 }
1210
1211 pub async fn next(&mut self) -> Option<Result<DownloadStreamChunk, CommonError>> {
1215 match futures_util::stream::StreamExt::next(&mut self.inner).await {
1216 Some(Ok(response)) => {
1217 Some(Ok(DownloadStreamChunk {
1218 data:response.chunk,
1219 total_size:response.total_size,
1220 downloaded:response.downloaded,
1221 completed:response.completed,
1222 error:response.error,
1223 }))
1224 },
1225 Some(Err(e)) => {
1226 error!("[DownloadStream] Stream error: {}", e);
1227 Some(Err(CommonError::IPCError { Description:format!("Stream error: {}", e) }))
1228 },
1229 None => None,
1230 }
1231 }
1232}
1233
1234impl std::fmt::Debug for AirClient {
1239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1240 write!(f, "AirClient({})", self.address)
1241 }
1242}
1243
1244trait IntoRequestExt {
1250 fn into_request(self) -> tonic::Request<Self>
1251 where
1252 Self: Sized, {
1253 tonic::Request::new(self)
1254 }
1255}
1256
1257impl<T> IntoRequestExt for T {}