Mountain/IPC/WindAirCommands.rs
1//! # Wind-Air Commands - Air Daemon Delegation Layer
2//!
3//! **File Responsibilities:**
4//! This module provides the Tauri IPC commands that enable Wind (the TypeScript
5//! frontend) to delegate background operations to Air (the Rust daemon). All
6//! commands use the gRPC-based AirClient for communication and return
7//! structured DTOs or detailed error messages.
8//!
9//! **Architectural Role in Wind-Mountain-Air Connection:**
10//!
11//! The WindAirCommands module forms the delegation layer that:
12//!
13//! 1. **Bridge to Air Daemon:** Provides a Tauri IPC interface to Air's gRPC
14//! services
15//! 2. **Background Operations:** Offloads long-running tasks from Wind to Air
16//! 3. **Type Translation:** Converts between Tauri JSON and gRPC protobuf
17//! messages
18//! 4. **Error Handling:** Translates gRPC errors to user-friendly error
19//! messages
20//! 5. **Connection Management:** Manages Air client lifecycle and reconnections
21//!
22//! **Three-Tier Architecture:**
23//! ```
24//! Wind (Frontend - TypeScript)
25//! |
26//! | Tauri IPC Commands
27//! v
28//! WindAirCommands (Mountain - Rust)
29//! |
30//! | gRPC Calls
31//! v
32//! AirClient (gRPC Client)
33//! |
34//! | Network Communication
35//! v
36//! Air Daemon (gRPC Server)
37//! ```
38//!
39//! **Available Commands (Tauri IPC):**
40//!
41//! **1. Update Management:**
42//! - `CheckForUpdates` - Check for application updates
43//! - `DownloadUpdate` - Download update package
44//! - `ApplyUpdate` - Apply downloaded update
45//!
46//! **2. File Operations:**
47//! - `DownloadFile` - Download any file from URL
48//!
49//! **3. Authentication:**
50//! - `AuthenticateUser` - Authenticate with various providers
51//!
52//! **4. Indexing & Search:**
53//! - `IndexFiles` - Index directory contents
54//! - `SearchFiles` - Search indexed files
55//!
56//! **5. Monitoring:**
57//! - `GetAirStatus` - Get Air daemon status
58//! - `GetAirMetrics` - Get performance & resource metrics
59//!
60//! **Data Transfer Objects (DTOs):**
61//!
62//! **UpdateInfoDTO:**
63//! ```rust
64//! struct UpdateInfoDTO {
65//! update_available:bool,
66//! version:String,
67//! download_url:String,
68//! release_notes:String,
69//! }
70//! ```
71//!
72//! **DownloadResultDTO:**
73//! ```rust
74//! struct DownloadResultDTO {
75//! success:bool,
76//! file_path:String,
77//! file_size:u64,
78//! checksum:String,
79//! }
80//! ```
81//!
82//! **AuthResponseDTO:**
83//! ```rust
84//! struct AuthResponseDTO {
85//! success:bool,
86//! token:String,
87//! error:Option<String>,
88//! }
89//! ```
90//!
91//! **IndexResultDTO:**
92//! ```rust
93//! struct IndexResultDTO {
94//! success:bool,
95//! files_indexed:u32,
96//! total_size:u64,
97//! }
98//! ```
99//!
100//! **SearchResultsDTO:**
101//! ```rust
102//! struct SearchResultsDTO {
103//! results:Vec<FileResultDTO>,
104//! total_results:u32,
105//! }
106//! ```
107//!
108//! **FileResultDTO:**
109//! ```rust
110//! struct FileResultDTO {
111//! path:String,
112//! size:u64,
113//! line:Option<u32>,
114//! content:Option<String>,
115//! }
116//! ```
117//!
118//! **AirServiceStatusDTO:**
119//! ```rust
120//! struct AirServiceStatusDTO {
121//! version:String,
122//! uptime_seconds:u64,
123//! total_requests:u64,
124//! successful_requests:u64,
125//! failed_requests:u64,
126//! active_requests:u32,
127//! healthy:bool,
128//! }
129//! ```
130//!
131//! **AirMetricsDTO:**
132//! ```rust
133//! struct AirMetricsDTO {
134//! memory_usage_mb:f64,
135//! cpu_usage_percent:f64,
136//! average_response_time:f64,
137//! disk_usage_mb:f64,
138//! network_usage_mbps:f64,
139//! }
140//! ```
141//!
142//! **Command Registration:**
143//!
144//! All commands are registered with Tauri's invoke_handler:
145//!
146//! ```rust
147//! builder.invoke_handler(tauri::generate_handler![
148//! CheckForUpdates,
149//! DownloadUpdate,
150//! ApplyUpdate,
151//! DownloadFile,
152//! AuthenticateUser,
153//! IndexFiles,
154//! SearchFiles,
155//! GetAirStatus,
156//! GetAirMetrics,
157//! ])
158//! ```
159//!
160//! **Client Connection Management:**
161//!
162//! **AirClientWrapper:**
163//! - Wraps the gRPC AirClient
164//! - Manages reconnections
165//! - Default address: `DEFAULT_AIR_SERVER_ADDRESS`
166//!
167//! **Connection Flow:**
168//! ```rust
169//! // 1. Get Air address from config
170//! let air_address = get_air_address(&app_handle)?;
171//!
172//! // 2. Create or reuse client
173//! let client = get_or_create_air_client(&app_handle, air_address).await?;
174//!
175//! // 3. Call Air's gRPC method
176//! let response = client.CheckForUpdates(request).await?;
177//!
178//! // 4. Check for errors
179//! if !response.error.is_empty() {
180//! return Err(response.error);
181//! }
182//!
183//! // 5. Convert to DTO
184//! let result = UpdateInfoDTO { ... };
185//! ```
186//!
187//! **Error Handling Strategy:**
188//!
189//! **gRPC Errors:**
190//! - Catch all gRPC errors
191//! - Translate to user-friendly messages
192//! - Include context about what operation failed
193//!
194//! **Response Errors:**
195//! - Check `response.error` field
196//! - Return error instead of DTO if present
197//! - Preserve original error message
198//!
199//! **Client Errors:**
200//! - Connection failures -> "Failed to connect to Air daemon"
201//! - Timeout errors -> "Operation timed out"
202//! - Parse errors -> "Failed to parse response"
203//!
204//! **Usage Examples from Wind:**
205//!
206//! **Check for Updates:**
207//! ```typescript
208//! const updates = await invoke('CheckForUpdates', {
209//! currentVersion: '1.0.0',
210//! channel: 'stable'
211//! });
212//!
213//! if (updates.updateAvailable) {
214//! console.log(`New version: ${updates.version}`);
215//! }
216//! ```
217//!
218//! **Download Update:**
219//! ```typescript
220//! const result = await invoke('DownloadUpdate', {
221//! url: 'https://example.com/update.zip',
222//! destination: '/tmp/update.zip',
223//! checksum: 'abc123...'
224//! });
225//!
226//! if (result.success) {
227//! console.log(`Downloaded: ${result.filePath}`);
228//! }
229//! ```
230//!
231//! **Authenticate:**
232//! ```typescript
233//! const auth = await invoke('AuthenticateUser', {
234//! username: 'user@example.com',
235//! password: 'secret',
236//! provider: 'github'
237//! });
238//!
239//! if (auth.success) {
240//! localStorage.setItem('token', auth.token);
241//! }
242//! ```
243//!
244//! **Index Files:**
245//! ```typescript
246//! const indexResult = await invoke('IndexFiles', {
247//! path: '/project',
248//! patterns: ['*.ts', '*.rs'],
249//! excludePatterns: ['node_modules', 'target'],
250//! maxDepth: 10
251//! });
252//!
253//! console.log(`Indexed ${indexResult.filesIndexed} files`);
254//! ```
255//!
256//! **Search Files:**
257//! ```typescript
258//! const searchResults = await invoke('SearchFiles', {
259//! query: 'TODO:',
260//! indexId: '/project',
261//! maxResults: 100
262//! });
263//!
264//! for (const file of searchResults.results) {
265//! console.log(`${file.path}:${file.line} - ${file.content}`);
266//! }
267//! ```
268//!
269//! **Integration with Other Modules:**
270//!
271//! **TauriIPCServer:**
272//! - Commands registered in same invoke handler
273//! - Both provide Tauri IPC interfaces
274//!
275//! **Configuration:**
276//! - Air address configurable via Mountain settings
277//! - Uses default if not specified
278//!
279//! **StatusReporter:**
280//! - Air status can be reported to Sky
281//! - Metrics collected for monitoring
282//!
283//! **Security Considerations:**
284//!
285//! - Passwords never logged
286//! - Checksums verified for downloads
287//! - File paths validated
288//! - Provider authentication handled securely by Air
289//!
290//! **Performance Considerations:**
291//!
292//! - Client connections are created fresh each call (current implementation)
293//! - Could cache clients for better performance in production
294//! - Large file downloads streamed via Air
295//! - Indexing operations run asynchronously in Air
296
297use serde::{Deserialize, Serialize};
298use tauri::Manager;
299
300// Import Air types from the new AirClient implementation.
301// These provide actual gRPC connectivity to the Air daemon service.
302use crate::Air::AirClient as AirClientModule;
303use crate::{Air::AirClient::DEFAULT_AIR_SERVER_ADDRESS, dev_log};
304
305/// Data Transfer Objects for Wind-Air communication
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct UpdateInfoDTO {
309 pub update_available:bool,
310 pub version:String,
311 pub download_url:String,
312 pub release_notes:String,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct DownloadResultDTO {
317 pub success:bool,
318 pub file_path:String,
319 pub file_size:u64,
320 pub checksum:String,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct AuthResponseDTO {
325 pub success:bool,
326 pub token:String,
327 pub error:Option<String>,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct IndexResultDTO {
332 pub success:bool,
333 pub files_indexed:u32,
334 pub total_size:u64,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct SearchResultsDTO {
339 pub results:Vec<FileResultDTO>,
340 pub total_results:u32,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct FileResultDTO {
345 pub path:String,
346 pub size:u64,
347 pub line:Option<u32>,
348 pub content:Option<String>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct AirServiceStatusDTO {
353 pub version:String,
354 pub uptime_seconds:u64,
355 pub total_requests:u64,
356 pub successful_requests:u64,
357 pub failed_requests:u64,
358 pub active_requests:u32,
359 pub healthy:bool,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct AirMetricsDTO {
364 pub memory_usage_mb:f64,
365 pub cpu_usage_percent:f64,
366 pub average_response_time:f64,
367 pub disk_usage_mb:f64,
368 pub network_usage_mbps:f64,
369}
370
371/// Air Client - Wrapper for the gRPC client connection to Air daemon
372#[derive(Debug, Clone)]
373pub struct AirClientWrapper {
374 client:AirClientModule::AirClient,
375}
376
377impl AirClientWrapper {
378 /// Create a new AirClient connected to the Air daemon
379 pub async fn new(address:String) -> Result<Self, String> {
380 dev_log!("grpc", "[WindAirCommands] Connecting to Air daemon at: {}", address);
381
382 let client = AirClientModule::AirClient::new(&address)
383 .await
384 .map_err(|e| format!("Failed to connect to Air daemon: {:?}", e))?;
385
386 dev_log!("grpc", "[WindAirCommands] Successfully connected to Air daemon");
387 Ok(Self { client })
388 }
389
390 /// Reconnect to Air daemon
391 pub async fn reconnect(&mut self, address:String) -> Result<(), String> {
392 dev_log!("grpc", "[WindAirCommands] Reconnecting to Air daemon at: {}", address);
393
394 let client = AirClientModule::AirClient::new(&address)
395 .await
396 .map_err(|e| format!("Failed to reconnect to Air daemon: {:?}", e))?;
397
398 self.client = client;
399 dev_log!("grpc", "[WindAirCommands] Successfully reconnected to Air daemon");
400 Ok(())
401 }
402}
403
404// ============================================================================
405// Tauri IPC Commands for Wind-Air Communication
406// ============================================================================
407
408/// Command: Check for Updates
409///
410/// Checks if a newer version of the application is available.
411/// Delegates to Air's update checking service.
412///
413/// # Arguments
414/// * `app_handle` - Tauri application handle
415/// * `current_version` - Current application version
416/// * `channel` - Update channel ("stable", "beta", "nightly")
417///
418/// # Returns
419/// `UpdateInfoDTO` with update information or error message
420#[tauri::command]
421pub async fn CheckForUpdates(current_version:Option<String>, channel:Option<String>) -> Result<UpdateInfoDTO, String> {
422 dev_log!(
423 "grpc",
424 "[WindAirCommands] CheckForUpdates called with version: {:?}, channel: {:?}",
425 current_version,
426 channel
427 );
428
429 // Get the Air client from app state or configuration
430 let air_address = get_air_address()?;
431 let client = get_or_create_air_client(air_address).await?;
432
433 // Use the new AirClient API
434 let request_id = uuid::Uuid::new_v4().to_string();
435 let current_version = current_version.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
436 let channel = channel.unwrap_or_else(|| "stable".to_string());
437
438 // Delegate to Air via gRPC
439 let update_info = client
440 .check_for_updates(request_id, current_version, channel)
441 .await
442 .map_err(|e| format!("Update check failed: {:?}", e))?;
443
444 let result = UpdateInfoDTO {
445 update_available:update_info.update_available,
446 version:update_info.version,
447 download_url:update_info.download_url,
448 release_notes:update_info.release_notes,
449 };
450
451 dev_log!(
452 "grpc",
453 "[WindAirCommands] Update check completed: available={}",
454 result.update_available
455 );
456 Ok(result)
457}
458
459/// Command: Download Update
460///
461/// Downloads an application update from the specified URL.
462/// Delegates to Air's download service.
463///
464/// # Arguments
465/// * `app_handle` - Tauri application handle
466/// * `url` - URL to download the update from
467/// * `destination` - Local destination path for the download
468/// * `checksum` - Optional SHA256 checksum for verification
469///
470/// # Returns
471/// `DownloadResultDTO` with download status
472#[tauri::command]
473pub async fn DownloadUpdate(
474 url:String,
475 destination:String,
476 checksum:Option<String>,
477) -> Result<DownloadResultDTO, String> {
478 dev_log!("grpc", "[WindAirCommands] DownloadUpdate called: {} -> {}", url, destination);
479
480 let air_address = get_air_address()?;
481 let client = get_or_create_air_client(air_address).await?;
482
483 let request_id = uuid::Uuid::new_v4().to_string();
484
485 // Delegate to Air via gRPC
486 let file_info = client
487 .download_update(
488 request_id,
489 url,
490 destination,
491 checksum.unwrap_or_default(),
492 std::collections::HashMap::new(),
493 )
494 .await
495 .map_err(|e| format!("Update download failed: {:?}", e))?;
496
497 let result = DownloadResultDTO {
498 success:true,
499 file_path:file_info.file_path,
500 file_size:file_info.file_size,
501 checksum:file_info.checksum,
502 };
503
504 dev_log!(
505 "grpc",
506 "[WindAirCommands] Update download completed: success={}",
507 result.success
508 );
509 Ok(result)
510}
511
512/// Command: Apply Update
513///
514/// Applies a downloaded update to the application.
515/// Delegates to Air's update installation service.
516///
517/// # Arguments
518/// * `app_handle` - Tauri application handle
519/// * `update_id` - Identifier of the update to apply
520/// * `update_path` - Path to the update package
521///
522/// # Returns
523/// Success status or error message
524#[tauri::command]
525pub async fn ApplyUpdate(update_id:String, update_path:String) -> Result<bool, String> {
526 dev_log!(
527 "grpc",
528 "[WindAirCommands] ApplyUpdate called: id={}, path={}",
529 update_id,
530 update_path
531 );
532
533 let air_address = get_air_address()?;
534 let client = get_or_create_air_client(air_address).await?;
535
536 let request_id = uuid::Uuid::new_v4().to_string();
537
538 // Apply downloaded updates by sending ApplyUpdateRequest to the Air service.
539 // The Air service handles platform-specific installation (replacing binaries,
540 // restarting the application, cleaning up old versions).
541 client
542 .apply_update(request_id, update_id, update_path)
543 .await
544 .map_err(|e| format!("Update application failed: {:?}", e))?;
545
546 dev_log!("grpc", "[WindAirCommands] Update applied successfully");
547 Ok(true)
548}
549
550/// Command: Download File
551///
552/// Downloads any file from a URL.
553/// Delegates to Air's download service.
554///
555/// # Arguments
556/// * `app_handle` - Tauri application handle
557/// * `url` - URL to download from
558/// * `destination` - Local destination path
559///
560/// # Returns
561/// `DownloadResultDTO` with download status
562#[tauri::command]
563pub async fn DownloadFile(url:String, destination:String) -> Result<DownloadResultDTO, String> {
564 dev_log!("grpc", "[WindAirCommands] DownloadFile called: {} -> {}", url, destination);
565
566 let air_address = get_air_address()?;
567 let client = get_or_create_air_client(air_address).await?;
568
569 let request_id = uuid::Uuid::new_v4().to_string();
570
571 let file_info = client
572 .download_file(request_id, url, destination, String::new(), std::collections::HashMap::new())
573 .await
574 .map_err(|e| format!("File download failed: {:?}", e))?;
575
576 let result = DownloadResultDTO {
577 success:true,
578 file_path:file_info.file_path,
579 file_size:file_info.file_size,
580 checksum:file_info.checksum,
581 };
582
583 dev_log!("grpc", "[WindAirCommands] File download completed");
584 Ok(result)
585}
586
587/// Command: Authenticate User
588///
589/// Authenticates a user with the specified provider.
590/// Delegates to Air's authentication service.
591///
592/// # Arguments
593/// * `app_handle` - Tauri application handle
594/// * `username` - User's username/email
595/// * `password` - User's password (or auth token)
596/// * `provider` - Auth provider ("github", "gitlab", "microsoft", etc.)
597///
598/// # Returns
599/// `AuthResponseDTO` with authentication token
600#[tauri::command]
601pub async fn AuthenticateUser(username:String, password:String, provider:String) -> Result<AuthResponseDTO, String> {
602 dev_log!(
603 "grpc",
604 "[WindAirCommands] AuthenticateUser called: {} via {}",
605 username,
606 provider
607 );
608
609 let air_address = get_air_address()?;
610 let client = get_or_create_air_client(air_address).await?;
611
612 let request_id = uuid::Uuid::new_v4().to_string();
613
614 let token = client
615 .authenticate(request_id, username, password, provider)
616 .await
617 .map_err(|e| format!("Authentication failed: {:?}", e))?;
618
619 let result = AuthResponseDTO { success:true, token, error:None };
620
621 dev_log!("grpc", "[WindAirCommands] Authentication completed: success={}", result.success);
622 Ok(result)
623}
624
625/// Command: Index Files
626///
627/// Initiates file indexing for a directory.
628/// Delegates to Air's file indexing service.
629///
630/// # Arguments
631/// * `app_handle` - Tauri application handle
632/// * `path` - Path to directory to index
633/// * `patterns` - File patterns to include
634/// * `exclude_patterns` - File patterns to exclude
635/// * `max_depth` - Maximum directory depth to traverse
636///
637/// # Returns
638/// `IndexResultDTO` with indexing results
639#[tauri::command]
640pub async fn IndexFiles(
641 path:String,
642 patterns:Vec<String>,
643 exclude_patterns:Option<Vec<String>>,
644 max_depth:Option<u32>,
645) -> Result<IndexResultDTO, String> {
646 dev_log!(
647 "grpc",
648 "[WindAirCommands] IndexFiles called: {} with patterns: {:?}",
649 path,
650 patterns
651 );
652
653 let air_address = get_air_address()?;
654 let client = get_or_create_air_client(air_address).await?;
655
656 let request_id = uuid::Uuid::new_v4().to_string();
657
658 let index_info = client
659 .index_files(
660 request_id,
661 path,
662 patterns,
663 exclude_patterns.unwrap_or_default(),
664 max_depth.unwrap_or(100),
665 )
666 .await
667 .map_err(|e| format!("File indexing failed: {:?}", e))?;
668
669 let result = IndexResultDTO {
670 success:true,
671 files_indexed:index_info.files_indexed,
672 total_size:index_info.total_size,
673 };
674
675 dev_log!(
676 "grpc",
677 "[WindAirCommands] File indexing completed: {} files",
678 result.files_indexed
679 );
680 Ok(result)
681}
682
683/// Command: Search Files
684///
685/// Searches previously indexed files.
686/// Delegates to Air's search service.
687///
688/// # Arguments
689/// * `app_handle` - Tauri application handle
690/// * `query` - Search query string
691/// * `index_id` - Index identifier (or path for backward compatibility)
692/// * `max_results` - Maximum number of results to return
693///
694/// # Returns
695/// `SearchResultsDTO` with matching files
696#[tauri::command]
697pub async fn SearchFiles(
698 query:String,
699 file_patterns:Vec<String>,
700 max_results:Option<u32>,
701) -> Result<SearchResultsDTO, String> {
702 dev_log!(
703 "grpc",
704 "[WindAirCommands] SearchFiles called: query={}, patterns={:?}",
705 query,
706 file_patterns
707 );
708
709 let air_address = get_air_address()?;
710 let client = get_or_create_air_client(air_address).await?;
711
712 let request_id = uuid::Uuid::new_v4().to_string();
713 let max_results_count = max_results.unwrap_or(100);
714
715 let search_results = client
716 .search_files(
717 request_id,
718 query,
719 file_patterns.first().map(|s| s.as_str()).unwrap_or("").to_string(),
720 max_results_count,
721 )
722 .await
723 .map_err(|e| format!("File search failed: {:?}", e))?;
724
725 let results:Vec<FileResultDTO> = search_results
726 .into_iter()
727 .map(|r| {
728 FileResultDTO {
729 path:r.path,
730 size:r.size,
731 line:Some(r.line_number),
732 content:Some(r.match_preview),
733 }
734 })
735 .collect();
736
737 let total_results = results.len() as u32;
738 let result = SearchResultsDTO { results, total_results };
739
740 dev_log!(
741 "grpc",
742 "[WindAirCommands] File search completed: {} results",
743 result.total_results
744 );
745 Ok(result)
746}
747
748/// Command: Get Air Status
749///
750/// Retrieves the current status of the Air daemon.
751/// Delegates to Air's status service.
752///
753/// # Arguments
754/// * `app_handle` - Tauri application handle
755///
756/// # Returns
757/// `AirServiceStatusDTO` with service status information
758#[tauri::command]
759pub async fn GetAirStatus() -> Result<AirServiceStatusDTO, String> {
760 dev_log!("grpc", "[WindAirCommands] GetAirStatus called");
761
762 let air_address = get_air_address()?;
763 let client = get_or_create_air_client(air_address).await?;
764
765 let request_id = uuid::Uuid::new_v4().to_string();
766
767 let status = client
768 .get_status(request_id)
769 .await
770 .map_err(|e| format!("Failed to get Air status: {:?}", e))?;
771
772 // Use the health check RPC to determine service availability
773 let healthy = client.health_check().await.unwrap_or(false);
774
775 let result = AirServiceStatusDTO {
776 version:status.version,
777 uptime_seconds:status.uptime_seconds,
778 total_requests:status.total_requests,
779 successful_requests:status.successful_requests,
780 failed_requests:status.failed_requests,
781 active_requests:status.active_requests,
782 healthy,
783 };
784
785 dev_log!("grpc", "[WindAirCommands] Air status retrieved: healthy={}", result.healthy);
786 Ok(result)
787}
788
789/// Command: Get Air Metrics
790///
791/// Retrieves performance and resource metrics from Air.
792/// Delegates to Air's metrics service.
793///
794/// # Arguments
795/// * `app_handle` - Tauri application handle
796/// * `metric_type` - Type of metrics ("all", "performance", "resources",
797/// "requests")
798///
799/// # Returns
800/// `AirMetricsDTO` with metrics data
801#[tauri::command]
802pub async fn GetAirMetrics(metric_type:Option<String>) -> Result<AirMetricsDTO, String> {
803 dev_log!("grpc", "[WindAirCommands] GetAirMetrics called with type: {:?}", metric_type);
804
805 let air_address = get_air_address()?;
806 let client = get_or_create_air_client(air_address).await?;
807
808 let request_id = uuid::Uuid::new_v4().to_string();
809
810 let metrics = client
811 .get_metrics(request_id, metric_type)
812 .await
813 .map_err(|e| format!("Failed to get Air metrics: {:?}", e))?;
814
815 let result = AirMetricsDTO {
816 memory_usage_mb:metrics.memory_usage_mb,
817 cpu_usage_percent:metrics.cpu_usage_percent,
818 average_response_time:metrics.average_response_time,
819 disk_usage_mb:metrics.disk_usage_mb,
820 network_usage_mbps:metrics.network_usage_mbps,
821 };
822
823 dev_log!("grpc", "[WindAirCommands] Air metrics retrieved");
824 Ok(result)
825}
826
827// ============================================================================
828// Helper Functions
829// ============================================================================
830
831/// Get the Air daemon address from configuration
832fn get_air_address() -> Result<String, String> {
833 // Return default Air address
834 Ok(DEFAULT_AIR_SERVER_ADDRESS.to_string())
835}
836
837/// Get or create the Air client instance
838async fn get_or_create_air_client(address:String) -> Result<AirClientModule::AirClient, String> {
839 // Create a new client each time
840 // In production, you'd use a state management pattern
841 AirClientModule::AirClient::new(&address)
842 .await
843 .map_err(|e| format!("Failed to create Air client: {:?}", e))
844}
845
846/// Register all Wind-Air commands with Tauri
847pub fn register_wind_air_commands<R:tauri::Runtime>(builder:tauri::Builder<R>) -> tauri::Builder<R> {
848 builder.invoke_handler(tauri::generate_handler![
849 CheckForUpdates,
850 DownloadUpdate,
851 ApplyUpdate,
852 DownloadFile,
853 AuthenticateUser,
854 IndexFiles,
855 SearchFiles,
856 GetAirStatus,
857 GetAirMetrics,
858 ])
859}