Mountain/IPC/
WindServiceHandlers.rs

1//! # Wind Service Handlers - Cross-Language Service Bridge
2//!
3//! **File Responsibilities:**
4//! This module provides the direct mapping layer between Wind's TypeScript
5//! service invocations and Mountain's Rust service implementations. It acts as
6//! the critical translation layer that enables Wind to request operations from
7//! Mountain through Tauri's IPC mechanism.
8//!
9//! **Architectural Role in Wind-Mountain Connection:**
10//!
11//! The WindServiceHandlers module implements the concrete command handlers that
12//! process IPC invocations from Wind. It serves as the single entry point for
13//! all Wind->Mountain service requests:
14//!
15//! 1. **Command Mapping:** Maps Wind's TypeScript service methods to Rust
16//!    implementations
17//! 2. **Type Conversion:** Converts between JSON/TypeScript types and Rust
18//!    types
19//! 3. **Validation:** Validates all inputs before forwarding to Mountain
20//!    services
21//! 4. **Error Handling:** Provides comprehensive error messages back to Wind
22//! 5. **Service Integration:** Connects to Mountain's internal service
23//!    architecture
24//!
25//! **Handled Command Categories:**
26//!
27//! **1. Configuration Commands:**
28//! - `configuration:get` - Retrieve configuration values
29//! - `configuration:update` - Update configuration values
30//!
31//! **2. File System Commands:**
32//! - `file:read` - Read file contents
33//! - `file:write` - Write to files
34//! - `file:stat` - Get file metadata
35//! - `file:exists` - Check file existence
36//! - `file:delete` - Delete files or directories
37//! - `file:copy` - Copy files
38//! - `file:move` - Move/rename files
39//! - `file:mkdir` - Create directories
40//! - `file:readdir` - Read directory contents
41//! - `file:readBinary` - Read binary files
42//! - `file:writeBinary` - Write binary files
43//!
44//! **3. Storage Commands:**
45//! - `storage:get` - Retrieve persistent storage values
46//! - `storage:set` - Store persistent values
47//!
48//! **4. Environment Commands:**
49//! - `environment:get` - Get environment variables
50//!
51//! **5. Native Host Commands:**
52//! - `native:showItemInFolder` - Reveal file in system file manager
53//! - `native:openExternal` - Open URLs in external browser
54//!
55//! **6. Workbench Commands:**
56//! - `workbench:getConfiguration` - Get complete workbench configuration
57//!
58//! **7. IPC Status Commands:**
59//! - `mountain_get_status` - Get overall IPC system status
60//! - `mountain_get_configuration` - Get Mountain configuration snapshot
61//! - `mountain_get_services_status` - Get status of all Mountain services
62//! - `mountain_get_state` - Get current application state
63//!
64//! **Communication Pattern:**
65//!
66//! ```
67//! Wind (TypeScript)
68//!   |
69//!   | app.handle.invoke('command', args)
70//!   v
71//! Tauri Bridge (IPC)
72//!   |
73//!   | mountain_ipc_invoke(command, args)
74//!   v
75//! WindServiceHandlers
76//!   |
77//!   | Type conversion + validation
78//!   v
79//! Mountain Services (Rust)
80//!   |
81//!   | Execute operation
82//!   v
83//! Return Result<serde_json::Value>
84//! ```
85//!
86//! **Type Conversion Strategy (TypeScript <-> Rust):**
87//!
88//! **Primitive Types:**
89//! - TypeScript `string` ↔ Rust `String` / `&str`
90//! - TypeScript `number` ↔ Rust `f64` / `i32` / `u32`
91//! - TypeScript `boolean` ↔ Rust `bool`
92//! - TypeScript `null` ↔ Rust `Option::<T>::None`
93//!
94//! **Complex Types:**
95//! - TypeScript `object` ↔ Rust `serde_json::Value` / `HashMap`
96//! - TypeScript `Array<T>` ↔ Rust `Vec<T>`
97//! - TypeScript custom interfaces ↔ Rust structs with Serialize/Deserialize
98//!
99//! **Example Type Conversion:**
100//! ```typescript
101//! // Wind (TypeScript)
102//! interface FileReadOptions {
103//!   encoding: 'utf8' | 'binary';
104//!   withBOM: boolean;
105//! }
106//! const result = await invoke('file:read', {
107//!   path: '/path/to/file.txt',
108//!   options: { encoding: 'utf8', withBOM: false }
109//! });
110//! ```
111//!
112//! ```text
113//! // Mountain (Rust)
114//! args.get(0).and_then(|v| v.as_str()) // Extract path
115//! args.get(1).and_then(|v| v.as_object()) // Extract options
116//! // ... validation, processing, return Result
117//! ```
118//!
119//! **Defensive Error Handling:**
120//!
121//! Each handler implements comprehensive error handling:
122//!
123//! 1. **Input Validation:**
124//!    - Check parameter presence
125//!    - Validate parameter types
126//!    - Validate value ranges and formats
127//!
128//! 2. **Service Error Handling:**
129//!    - Catch and translate service errors
130//!    - Provide detailed error messages
131//!    - Include context for debugging
132//!
133//! 3. **Error Response Format:**
134//! ```rust
135//! Error("Failed to read file: Permission denied (path: /etc/passwd)") 
136//! ```
137//!
138//! **Comprehensive Error Messages:**
139//! - Include operation that failed
140//! - Include relevant parameters (paths, keys, etc.)
141//! - Include the underlying cause
142//! - Format: `"Failed to <operation>: <cause> (context: <value>)"`
143//!
144//! **Service Integration Pattern:**
145//!
146//! Handlers use Mountain's dependency injection system via `Requires` trait:
147//!
148//! ```text
149//! let provider: Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
150//! provider.GetConfigurationValue(...).await?;
151//! ```
152//!
153//! This provides:
154//! - Loose coupling between handlers and services
155//! - Testable architecture (can mock services)
156//! - Centralized service lifecycle management
157//!
158//! **Command Registration:**
159//!
160//! All handlers are automatically registered when included in Tauri's
161//! invoke_handler:
162//!
163//! ```rust
164//! .invoke_handler(tauri::generate_handler![
165//!     mountain_ipc_invoke,
166//!     // ... other commands
167//! ])
168//! ```
169
170use std::{path::PathBuf, sync::Arc};
171
172use log::{debug, error, info};
173use serde_json::{Value, json};
174use tauri::{AppHandle, Manager, command};
175// Type aliases for Configuration DTOs to simplify usage
176use CommonLibrary::Configuration::DTO::{
177	ConfigurationOverridesDTO as ConfigurationOverridesDTOModule,
178	ConfigurationTarget as ConfigurationTargetModule,
179};
180type ConfigurationOverridesDTO = ConfigurationOverridesDTOModule::ConfigurationOverridesDTO;
181type ConfigurationTarget = ConfigurationTargetModule::ConfigurationTarget;
182
183use CommonLibrary::{
184	Configuration::ConfigurationProvider::ConfigurationProvider,
185	Environment::Requires::Requires,
186	Error::CommonError::CommonError,
187	FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
188	Storage::StorageProvider::StorageProvider,
189};
190
191use crate::{ApplicationState::ApplicationState, RunTime::ApplicationRunTime::ApplicationRunTime};
192
193/// Handler for Wind's MainProcessService.invoke() calls
194/// Maps Tauri IPC commands to Mountain's internal command system
195#[tauri::command]
196pub async fn mountain_ipc_invoke(app_handle:AppHandle, command:String, args:Vec<Value>) -> Result<Value, String> {
197	debug!("[WindServiceHandlers] IPC Invoke command: {}, args: {:?}", command, args);
198
199	// Get the application runtime
200	let runtime = app_handle.state::<Arc<ApplicationRunTime>>();
201
202	// Route the command based on the command name
203	match command.as_str() {
204		// Configuration commands
205		"configuration:get" => handle_configuration_get(runtime.inner().clone(), args).await,
206		"configuration:update" => handle_configuration_update(runtime.inner().clone(), args).await,
207
208		// File system commands
209		"file:read" => handle_file_read(runtime.inner().clone(), args).await,
210		"file:write" => handle_file_write(runtime.inner().clone(), args).await,
211		"file:stat" => handle_file_stat(runtime.inner().clone(), args).await,
212		"file:exists" => handle_file_exists(runtime.inner().clone(), args).await,
213		"file:delete" => handle_file_delete(runtime.inner().clone(), args).await,
214		"file:copy" => handle_file_copy(runtime.inner().clone(), args).await,
215		"file:move" => handle_file_move(runtime.inner().clone(), args).await,
216		"file:mkdir" => handle_file_mkdir(runtime.inner().clone(), args).await,
217		"file:readdir" => handle_file_readdir(runtime.inner().clone(), args).await,
218		"file:readBinary" => handle_file_read_binary(runtime.inner().clone(), args).await,
219		"file:writeBinary" => handle_file_write_binary(runtime.inner().clone(), args).await,
220
221		// Storage commands
222		"storage:get" => handle_storage_get(runtime.inner().clone(), args).await,
223		"storage:set" => handle_storage_set(runtime.inner().clone(), args).await,
224
225		// Environment commands
226		"environment:get" => handle_environment_get(runtime.inner().clone(), args).await,
227
228		// Native host commands
229		"native:showItemInFolder" => handle_show_item_in_folder(runtime.inner().clone(), args).await,
230		"native:openExternal" => handle_open_external(runtime.inner().clone(), args).await,
231
232		// Workbench commands
233		"workbench:getConfiguration" => handle_workbench_configuration(runtime.inner().clone(), args).await,
234
235		// IPC status commands
236		"mountain_get_status" => {
237			let status = json!({
238				"connected": true,
239				"version": "1.0.0"
240			});
241			Ok(status)
242		},
243		"mountain_get_configuration" => {
244			let config = json!({
245				"editor": { "theme": "dark" },
246				"extensions": { "installed": [] }
247			});
248			Ok(config)
249		},
250		"mountain_get_services_status" => {
251			let services = json!({
252				"editor": { "status": "running" },
253				"extensionHost": { "status": "running" }
254			});
255			Ok(services)
256		},
257		"mountain_get_state" => {
258			let state = json!({
259				"ui": {},
260				"editor": {},
261				"workspace": {}
262			});
263			Ok(state)
264		},
265
266		// Default handler for unknown commands
267		_ => {
268			error!("[WindServiceHandlers] Unknown IPC command: {}", command);
269			Err(format!("Unknown IPC command: {}", command))
270		},
271	}
272}
273
274/// Handler for configuration get requests
275async fn handle_configuration_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
276	let key = args
277		.get(0)
278		.ok_or("Missing configuration key".to_string())?
279		.as_str()
280		.ok_or("Configuration key must be a string".to_string())?;
281
282	// Use Mountain's configuration system
283	let provider:Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
284
285	let value = provider
286		.GetConfigurationValue(Some(key.to_string()), ConfigurationOverridesDTO::default())
287		.await
288		.map_err(|e| format!("Failed to get configuration: {}", e))?;
289
290	debug!("[WindServiceHandlers] Configuration get: {} = {:?}", key, value);
291	Ok(value)
292}
293
294/// Handler for configuration update requests
295async fn handle_configuration_update(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
296	let key = args
297		.get(0)
298		.ok_or("Missing configuration key".to_string())?
299		.as_str()
300		.ok_or("Configuration key must be a string".to_string())?;
301
302	let value = args.get(1).ok_or("Missing configuration value".to_string())?.clone();
303
304	// Use Mountain's configuration system
305	let provider:Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
306
307	provider
308		.UpdateConfigurationValue(
309			key.to_string(),
310			value,
311			ConfigurationTarget::User,
312			ConfigurationOverridesDTO::default(),
313			None,
314		)
315		.await
316		.map_err(|e| format!("Failed to update configuration: {}", e))?;
317
318	debug!("[WindServiceHandlers] Configuration updated: {}", key);
319	Ok(Value::Null)
320}
321
322/// Handler for file read requests
323async fn handle_file_read(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
324	let path = args
325		.get(0)
326		.ok_or("Missing file path".to_string())?
327		.as_str()
328		.ok_or("File path must be a string".to_string())?;
329
330	// Use Mountain's file system provider
331	let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
332
333	let content = provider
334		.ReadFile(&PathBuf::from(path))
335		.await
336		.map_err(|e| format!("Failed to read file: {}", e))?;
337
338	debug!("[WindServiceHandlers] File read: {} ({} bytes)", path, content.len());
339	Ok(json!(content))
340}
341
342/// Handler for file write requests
343async fn handle_file_write(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
344	let path = args
345		.get(0)
346		.ok_or("Missing file path".to_string())?
347		.as_str()
348		.ok_or("File path must be a string".to_string())?;
349
350	let content = args
351		.get(1)
352		.ok_or("Missing file content".to_string())?
353		.as_str()
354		.ok_or("File content must be a string".to_string())?;
355
356	// Use Mountain's file system provider
357	let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
358
359	provider
360		.WriteFile(&PathBuf::from(path), content.as_bytes().to_vec(), true, true)
361		.await
362		.map_err(|e:CommonError| format!("Failed to write file: {}", e))?;
363
364	debug!("[WindServiceHandlers] File written: {} ({} bytes)", path, content.len());
365	Ok(Value::Null)
366}
367
368/// Handler for file stat requests
369async fn handle_file_stat(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
370	let path = args
371		.get(0)
372		.ok_or("Missing file path".to_string())?
373		.as_str()
374		.ok_or("File path must be a string".to_string())?;
375
376	// Use Mountain's file system provider
377	let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
378
379	let stats = provider
380		.StatFile(&PathBuf::from(path))
381		.await
382		.map_err(|e| format!("Failed to stat file: {}", e))?;
383
384	debug!("[WindServiceHandlers] File stat: {}", path);
385	Ok(json!(stats))
386}
387
388/// Handler for file exists requests
389async fn handle_file_exists(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
390	let path = args
391		.get(0)
392		.ok_or("Missing file path".to_string())?
393		.as_str()
394		.ok_or("File path must be a string".to_string())?;
395
396	// Use Mountain's file system provider
397	let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
398
399	let exists = provider.StatFile(&PathBuf::from(path)).await.is_ok();
400
401	debug!("[WindServiceHandlers] File exists check: {} = {}", path, exists);
402	Ok(json!(exists))
403}
404
405/// Handler for file delete requests
406async fn handle_file_delete(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
407	let path = args
408		.get(0)
409		.ok_or("Missing file path".to_string())?
410		.as_str()
411		.ok_or("File path must be a string".to_string())?;
412
413	// Use Mountain's file system provider
414	let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
415
416	provider
417		.Delete(&PathBuf::from(path), false, false)
418		.await
419		.map_err(|e:CommonError| format!("Failed to delete file: {}", e))?;
420
421	debug!("[WindServiceHandlers] File deleted: {}", path);
422	Ok(Value::Null)
423}
424
425/// Handler for file copy requests
426async fn handle_file_copy(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
427	let source = args
428		.get(0)
429		.ok_or("Missing source path".to_string())?
430		.as_str()
431		.ok_or("Source path must be a string".to_string())?;
432
433	let destination = args
434		.get(1)
435		.ok_or("Missing destination path".to_string())?
436		.as_str()
437		.ok_or("Destination path must be a string".to_string())?;
438
439	// Use Mountain's file system provider
440	let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
441
442	provider
443		.Copy(&PathBuf::from(source), &PathBuf::from(destination), false)
444		.await
445		.map_err(|e:CommonError| format!("Failed to copy file: {} -> {}", source, destination))?;
446
447	debug!("[WindServiceHandlers] File copied: {} -> {}", source, destination);
448	Ok(Value::Null)
449}
450
451/// Handler for file move requests
452async fn handle_file_move(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
453	let source = args
454		.get(0)
455		.ok_or("Missing source path".to_string())?
456		.as_str()
457		.ok_or("Source path must be a string".to_string())?;
458
459	let destination = args
460		.get(1)
461		.ok_or("Missing destination path".to_string())?
462		.as_str()
463		.ok_or("Destination path must be a string".to_string())?;
464
465	// Use Mountain's file system provider
466	let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
467
468	provider
469		.Rename(&PathBuf::from(source), &PathBuf::from(destination), false)
470		.await
471		.map_err(|e:CommonError| format!("Failed to move file: {} -> {}", source, destination))?;
472
473	debug!("[WindServiceHandlers] File moved: {} -> {}", source, destination);
474	Ok(Value::Null)
475}
476
477/// Handler for directory creation requests
478async fn handle_file_mkdir(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
479	let path = args
480		.get(0)
481		.ok_or("Missing directory path".to_string())?
482		.as_str()
483		.ok_or("Directory path must be a string".to_string())?;
484
485	let recursive = args.get(1).and_then(|v| v.as_bool()).unwrap_or(true);
486
487	// Use Mountain's file system provider
488	let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
489
490	provider
491		.CreateDirectory(&PathBuf::from(path), recursive)
492		.await
493		.map_err(|e:CommonError| format!("Failed to create directory: {}", e))?;
494
495	debug!("[WindServiceHandlers] Directory created: {} (recursive: {})", path, recursive);
496	Ok(Value::Null)
497}
498
499/// Handler for directory reading requests
500async fn handle_file_readdir(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
501	let path = args
502		.get(0)
503		.ok_or("Missing directory path".to_string())?
504		.as_str()
505		.ok_or("Directory path must be a string".to_string())?;
506
507	// Use Mountain's file system provider
508	let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
509
510	let entries = provider
511		.ReadDirectory(&PathBuf::from(path))
512		.await
513		.map_err(|e| format!("Failed to read directory: {}", e))?;
514
515	debug!("[WindServiceHandlers] Directory read: {} ({} entries)", path, entries.len());
516	Ok(json!(entries))
517}
518
519/// Handler for binary file read requests
520async fn handle_file_read_binary(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
521	let path = args
522		.get(0)
523		.ok_or("Missing file path".to_string())?
524		.as_str()
525		.ok_or("File path must be a string".to_string())?;
526
527	// Use Mountain's file system provider
528	let provider:Arc<dyn FileSystemReader> = runtime.Environment.Require();
529
530	let content = provider
531		.ReadFile(&PathBuf::from(path))
532		.await
533		.map_err(|e| format!("Failed to read binary file: {}", e))?;
534
535	debug!("[WindServiceHandlers] Binary file read: {} ({} bytes)", path, content.len());
536	Ok(json!(content))
537}
538
539/// Handler for binary file write requests
540async fn handle_file_write_binary(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
541	let path = args
542		.get(0)
543		.ok_or("Missing file path".to_string())?
544		.as_str()
545		.ok_or("File path must be a string".to_string())?;
546
547	let content = args
548		.get(1)
549		.ok_or("Missing file content".to_string())?
550		.as_str()
551		.ok_or("File content must be a string".to_string())?;
552
553	// Convert string content to bytes
554	let content_bytes = content.as_bytes().to_vec();
555	let content_len = content_bytes.len();
556
557	// Use Mountain's file system provider
558	let provider:Arc<dyn FileSystemWriter> = runtime.Environment.Require();
559
560	provider
561		.WriteFile(&PathBuf::from(path), content_bytes.clone(), true, true)
562		.await
563		.map_err(|e:CommonError| format!("Failed to write binary file: {}", e))?;
564
565	debug!("[WindServiceHandlers] Binary file written: {} ({} bytes)", path, content_len);
566	Ok(Value::Null)
567}
568
569/// Handler for storage get requests
570async fn handle_storage_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
571	let key = args
572		.get(0)
573		.ok_or("Missing storage key".to_string())?
574		.as_str()
575		.ok_or("Storage key must be a string".to_string())?;
576
577	// Use Mountain's storage provider
578	let provider:Arc<dyn StorageProvider> = runtime.Environment.Require();
579
580	let value = provider
581		.GetStorageValue(false, key)
582		.await
583		.map_err(|e| format!("Failed to get storage item: {}", e))?;
584
585	debug!("[WindServiceHandlers] Storage get: {}", key);
586	Ok(value.unwrap_or(Value::Null))
587}
588
589/// Handler for storage set requests
590async fn handle_storage_set(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
591	let key = args
592		.get(0)
593		.ok_or("Missing storage key".to_string())?
594		.as_str()
595		.ok_or("Storage key must be a string".to_string())?;
596
597	let value = args.get(1).ok_or("Missing storage value".to_string())?.clone();
598
599	// Use Mountain's storage provider
600	let provider:Arc<dyn StorageProvider> = runtime.Environment.Require();
601
602	provider
603		.UpdateStorageValue(false, key.to_string(), Some(value))
604		.await
605		.map_err(|e| format!("Failed to set storage item: {}", e))?;
606
607	debug!("[WindServiceHandlers] Storage set: {}", key);
608	Ok(Value::Null)
609}
610
611/// Handler for environment get requests
612async fn handle_environment_get(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
613	let key = args
614		.get(0)
615		.ok_or("Missing environment key".to_string())?
616		.as_str()
617		.ok_or("Environment key must be a string".to_string())?;
618
619	// Use std::env for environment variables
620	let value = std::env::var(key).map_err(|e| format!("Failed to get environment variable: {}", e))?;
621
622	debug!("[WindServiceHandlers] Environment get: {}", key);
623	Ok(json!(value))
624}
625
626/// Handler for showing items in folder
627async fn handle_show_item_in_folder(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
628	let path_str = args
629		.get(0)
630		.ok_or("Missing file path".to_string())?
631		.as_str()
632		.ok_or("File path must be a string".to_string())?;
633
634	// IMPLEMENTATION: Microsoft-inspired native file system integration
635	debug!("[WindServiceHandlers] Show item in folder: {}", path_str);
636
637	let path = std::path::PathBuf::from(path_str);
638
639	// Validate path exists
640	if !path.exists() {
641		return Err(format!("Path does not exist: {}", path_str));
642	}
643
644	#[cfg(target_os = "macos")]
645	{
646		use std::process::Command;
647
648		// Use macOS's open command with -R flag to reveal in Finder
649		let result = Command::new("open")
650			.arg("-R")
651			.arg(&path)
652			.output()
653			.map_err(|e| format!("Failed to execute open command: {}", e))?;
654
655		if !result.status.success() {
656			return Err(format!(
657				"Failed to show item in folder: {}",
658				String::from_utf8_lossy(&result.stderr)
659			));
660		}
661	}
662
663	#[cfg(target_os = "windows")]
664	{
665		use std::process::Command;
666
667		// Use Windows Explorer with /select flag
668		let result = Command::new("explorer")
669			.arg("/select,")
670			.arg(&path)
671			.output()
672			.map_err(|e| format!("Failed to execute explorer command: {}", e))?;
673
674		if !result.status.success() {
675			return Err(format!(
676				"Failed to show item in folder: {}",
677				String::from_utf8_lossy(&result.stderr)
678			));
679		}
680	}
681
682	#[cfg(target_os = "linux")]
683	{
684		use std::process::Command;
685
686		// Try common Linux file managers
687		let file_managers = ["nautilus", "dolphin", "thunar", "pcmanfm", "nemo"];
688		let mut last_error = String::new();
689
690		for manager in file_managers.iter() {
691			let result = Command::new(manager).arg(&path).output();
692
693			match result {
694				Ok(output) if output.status.success() => {
695					debug!("[WindServiceHandlers] Successfully opened with {}", manager);
696					break;
697				},
698				Err(e) => {
699					last_error = e.to_string();
700					continue;
701				},
702				_ => continue,
703			}
704		}
705
706		if !last_error.is_empty() {
707			return Err(format!("Failed to show item in folder with any file manager: {}", last_error));
708		}
709	}
710
711	info!("[WindServiceHandlers] Successfully showed item in folder: {}", path_str);
712	Ok(Value::Bool(true))
713}
714
715/// Handler for opening external URLs
716async fn handle_open_external(runtime:Arc<ApplicationRunTime>, args:Vec<Value>) -> Result<Value, String> {
717	let url_str = args
718		.get(0)
719		.ok_or("Missing URL".to_string())?
720		.as_str()
721		.ok_or("URL must be a string".to_string())?;
722
723	// IMPLEMENTATION: Microsoft-inspired URL validation and opening
724	debug!("[WindServiceHandlers] Open external: {}", url_str);
725
726	// Validate URL format
727	if !url_str.starts_with("http://") && !url_str.starts_with("https://") {
728		return Err(format!("Invalid URL format. Must start with http:// or https://: {}", url_str));
729	}
730
731	#[cfg(target_os = "macos")]
732	{
733		use std::process::Command;
734
735		// Use macOS's open command
736		let result = Command::new("open")
737			.arg(url_str)
738			.output()
739			.map_err(|e| format!("Failed to execute open command: {}", e))?;
740
741		if !result.status.success() {
742			return Err(format!("Failed to open URL: {}", String::from_utf8_lossy(&result.stderr)));
743		}
744	}
745
746	#[cfg(target_os = "windows")]
747	{
748		use std::process::Command;
749
750		// Use Windows start command
751		let result = Command::new("cmd")
752			.arg("/c")
753			.arg("start")
754			.arg(url_str)
755			.output()
756			.map_err(|e| format!("Failed to execute start command: {}", e))?;
757
758		if !result.status.success() {
759			return Err(format!("Failed to open URL: {}", String::from_utf8_lossy(&result.stderr)));
760		}
761	}
762
763	#[cfg(target_os = "linux")]
764	{
765		use std::process::Command;
766
767		// Try common Linux URL handlers
768		let handlers = ["xdg-open", "gnome-open", "kde-open", "x-www-browser"];
769		let mut last_error = String::new();
770
771		for handler in handlers.iter() {
772			let result = Command::new(handler).arg(url_str).output();
773
774			match result {
775				Ok(output) if output.status.success() => {
776					debug!("[WindServiceHandlers] Successfully opened with {}", handler);
777					break;
778				},
779				Err(e) => {
780					last_error = e.to_string();
781					continue;
782				},
783				_ => continue,
784			}
785		}
786
787		if !last_error.is_empty() {
788			return Err(format!("Failed to open URL with any handler: {}", last_error));
789		}
790	}
791
792	info!("[WindServiceHandlers] Successfully opened external URL: {}", url_str);
793	Ok(Value::Bool(true))
794}
795
796/// Handler for workbench configuration requests
797async fn handle_workbench_configuration(runtime:Arc<ApplicationRunTime>, _args:Vec<Value>) -> Result<Value, String> {
798	// Get the complete workbench configuration
799	let provider:Arc<dyn ConfigurationProvider> = runtime.Environment.Require();
800
801	let config = provider
802		.GetConfigurationValue(None, ConfigurationOverridesDTO::default())
803		.await
804		.map_err(|e| format!("Failed to get workbench configuration: {}", e))?;
805
806	debug!("[WindServiceHandlers] Workbench configuration retrieved");
807	Ok(config)
808}
809
810/// Register all Wind IPC command handlers
811pub fn register_wind_ipc_handlers(app_handle:&tauri::AppHandle) -> Result<(), String> {
812	info!("[WindServiceHandlers] Registering Wind IPC command handlers");
813
814	// Note: These handlers are automatically registered when included in the
815	// Tauri invoke_handler macro in the main binary
816
817	Ok(())
818}