Skip to main content

voirs_cli/platform/
mod.rs

1//! Cross-platform compatibility and system integration
2//!
3//! This module provides platform-specific optimizations and features for Windows, macOS, and Linux.
4
5use std::path::PathBuf;
6
7pub mod hardware;
8pub mod integration;
9
10/// Platform-specific information and capabilities
11#[derive(Debug, Clone)]
12pub struct PlatformInfo {
13    /// Operating system name
14    pub os: String,
15    /// OS version
16    pub version: String,
17    /// System architecture
18    pub architecture: String,
19    /// Available CPU cores
20    pub cpu_cores: usize,
21    /// Total system memory in bytes
22    pub total_memory: u64,
23    /// Available memory in bytes
24    pub available_memory: u64,
25}
26
27/// Get current platform information
28pub fn get_platform_info() -> PlatformInfo {
29    let os = std::env::consts::OS.to_string();
30    let architecture = std::env::consts::ARCH.to_string();
31    let cpu_cores = num_cpus::get();
32
33    // Get system memory information
34    let (total_memory, available_memory) = get_memory_info();
35
36    PlatformInfo {
37        os: os.clone(),
38        version: get_os_version(),
39        architecture,
40        cpu_cores,
41        total_memory,
42        available_memory,
43    }
44}
45
46/// Get OS version string
47fn get_os_version() -> String {
48    #[cfg(target_os = "windows")]
49    {
50        // Windows version detection
51        "Windows".to_string()
52    }
53    #[cfg(target_os = "macos")]
54    {
55        // macOS version detection
56        use std::process::Command;
57        match Command::new("sw_vers").arg("-productVersion").output() {
58            Ok(output) => String::from_utf8_lossy(&output.stdout).trim().to_string(),
59            Err(_) => "macOS".to_string(),
60        }
61    }
62    #[cfg(target_os = "linux")]
63    {
64        // Linux version detection
65        use std::fs;
66        if let Ok(content) = fs::read_to_string("/etc/os-release") {
67            for line in content.lines() {
68                if line.starts_with("PRETTY_NAME=") {
69                    return line
70                        .trim_start_matches("PRETTY_NAME=")
71                        .trim_matches('"')
72                        .to_string();
73                }
74            }
75        }
76        "Linux".to_string()
77    }
78    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
79    {
80        "Unknown".to_string()
81    }
82}
83
84/// Get system memory information (total, available)
85fn get_memory_info() -> (u64, u64) {
86    #[cfg(target_os = "windows")]
87    {
88        // Windows memory detection using systeminfo command
89        use std::process::Command;
90
91        let output = Command::new("wmic")
92            .args([
93                "OS",
94                "get",
95                "TotalVisibleMemorySize,FreePhysicalMemory",
96                "/Value",
97            ])
98            .output()
99            .ok();
100
101        if let Some(output) = output {
102            if let Ok(text) = String::from_utf8(output.stdout) {
103                let mut total = 0u64;
104                let mut free = 0u64;
105
106                for line in text.lines() {
107                    if let Some(value) = line.strip_prefix("TotalVisibleMemorySize=") {
108                        total = value.trim().parse::<u64>().unwrap_or(0) * 1024;
109                    // KB to bytes
110                    } else if let Some(value) = line.strip_prefix("FreePhysicalMemory=") {
111                        free = value.trim().parse::<u64>().unwrap_or(0) * 1024; // KB to bytes
112                    }
113                }
114
115                if total > 0 {
116                    return (total, free);
117                }
118            }
119        }
120
121        // Fallback values if command fails
122        (8_000_000_000, 4_000_000_000)
123    }
124    #[cfg(target_os = "macos")]
125    {
126        // macOS memory detection
127        use std::process::Command;
128        let total = Command::new("sysctl")
129            .arg("-n")
130            .arg("hw.memsize")
131            .output()
132            .ok()
133            .and_then(|output| String::from_utf8(output.stdout).ok())
134            .and_then(|s| s.trim().parse::<u64>().ok())
135            .unwrap_or(8_000_000_000);
136
137        // For available memory, use vm_stat
138        let available = total / 2; // Simplified estimation
139        (total, available)
140    }
141    #[cfg(target_os = "linux")]
142    {
143        // Linux memory detection from /proc/meminfo
144        use std::fs;
145        let mut total = 8_000_000_000u64;
146        let mut available = 4_000_000_000u64;
147
148        if let Ok(content) = fs::read_to_string("/proc/meminfo") {
149            for line in content.lines() {
150                if line.starts_with("MemTotal:") {
151                    if let Some(kb) = line.split_whitespace().nth(1) {
152                        if let Ok(kb_val) = kb.parse::<u64>() {
153                            total = kb_val * 1024; // Convert KB to bytes
154                        }
155                    }
156                } else if line.starts_with("MemAvailable:") {
157                    if let Some(kb) = line.split_whitespace().nth(1) {
158                        if let Ok(kb_val) = kb.parse::<u64>() {
159                            available = kb_val * 1024; // Convert KB to bytes
160                        }
161                    }
162                }
163            }
164        }
165        (total, available)
166    }
167    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
168    {
169        (8_000_000_000, 4_000_000_000) // Default fallback
170    }
171}
172
173/// Get platform-specific configuration directory
174pub fn get_config_dir() -> Option<PathBuf> {
175    #[cfg(target_os = "windows")]
176    {
177        // Windows: %APPDATA%\VoiRS
178        std::env::var("APPDATA")
179            .ok()
180            .map(|appdata| PathBuf::from(appdata).join("VoiRS"))
181    }
182    #[cfg(target_os = "macos")]
183    {
184        // macOS: ~/Library/Application Support/VoiRS
185        dirs::home_dir().map(|home| {
186            home.join("Library")
187                .join("Application Support")
188                .join("VoiRS")
189        })
190    }
191    #[cfg(target_os = "linux")]
192    {
193        // Linux: ~/.config/voirs or $XDG_CONFIG_HOME/voirs
194        std::env::var("XDG_CONFIG_HOME")
195            .ok()
196            .map(PathBuf::from)
197            .or_else(|| dirs::home_dir().map(|home| home.join(".config")))
198            .map(|config_dir| config_dir.join("voirs"))
199    }
200    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
201    {
202        dirs::home_dir().map(|home| home.join(".voirs"))
203    }
204}
205
206/// Get platform-specific cache directory
207pub fn get_cache_dir() -> Option<PathBuf> {
208    #[cfg(target_os = "windows")]
209    {
210        // Windows: %LOCALAPPDATA%\VoiRS\Cache
211        std::env::var("LOCALAPPDATA")
212            .ok()
213            .map(|localappdata| PathBuf::from(localappdata).join("VoiRS").join("Cache"))
214    }
215    #[cfg(target_os = "macos")]
216    {
217        // macOS: ~/Library/Caches/VoiRS
218        dirs::home_dir().map(|home| home.join("Library").join("Caches").join("VoiRS"))
219    }
220    #[cfg(target_os = "linux")]
221    {
222        // Linux: ~/.cache/voirs or $XDG_CACHE_HOME/voirs
223        std::env::var("XDG_CACHE_HOME")
224            .ok()
225            .map(PathBuf::from)
226            .or_else(|| dirs::home_dir().map(|home| home.join(".cache")))
227            .map(|cache_dir| cache_dir.join("voirs"))
228    }
229    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
230    {
231        dirs::home_dir().map(|home| home.join(".voirs").join("cache"))
232    }
233}
234
235/// Get platform-specific data directory  
236pub fn get_data_dir() -> Option<PathBuf> {
237    #[cfg(target_os = "windows")]
238    {
239        // Windows: %LOCALAPPDATA%\VoiRS\Data
240        std::env::var("LOCALAPPDATA")
241            .ok()
242            .map(|localappdata| PathBuf::from(localappdata).join("VoiRS").join("Data"))
243    }
244    #[cfg(target_os = "macos")]
245    {
246        // macOS: ~/Library/Application Support/VoiRS
247        dirs::home_dir().map(|home| {
248            home.join("Library")
249                .join("Application Support")
250                .join("VoiRS")
251        })
252    }
253    #[cfg(target_os = "linux")]
254    {
255        // Linux: ~/.local/share/voirs or $XDG_DATA_HOME/voirs
256        std::env::var("XDG_DATA_HOME")
257            .ok()
258            .map(PathBuf::from)
259            .or_else(|| dirs::home_dir().map(|home| home.join(".local").join("share")))
260            .map(|data_dir| data_dir.join("voirs"))
261    }
262    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
263    {
264        dirs::home_dir().map(|home| home.join(".voirs").join("data"))
265    }
266}
267
268/// Ensure platform directories exist
269pub fn ensure_platform_dirs() -> Result<(), Box<dyn std::error::Error>> {
270    if let Some(config_dir) = get_config_dir() {
271        std::fs::create_dir_all(&config_dir)?;
272    }
273
274    if let Some(cache_dir) = get_cache_dir() {
275        std::fs::create_dir_all(&cache_dir)?;
276    }
277
278    if let Some(data_dir) = get_data_dir() {
279        std::fs::create_dir_all(&data_dir)?;
280    }
281
282    Ok(())
283}
284
285/// Check if running with administrator/root privileges
286pub fn is_elevated() -> bool {
287    #[cfg(target_os = "windows")]
288    {
289        // Windows: Check if running as administrator using net session command
290        use std::process::Command;
291
292        // The 'net session' command only succeeds when run as administrator
293        Command::new("net")
294            .args(["session"])
295            .output()
296            .map(|output| output.status.success())
297            .unwrap_or(false)
298    }
299    #[cfg(unix)]
300    {
301        // Unix: Check if running as root
302        unsafe { libc::geteuid() == 0 }
303    }
304    #[cfg(not(any(target_os = "windows", unix)))]
305    {
306        false
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_platform_info() {
316        let info = get_platform_info();
317        assert!(!info.os.is_empty());
318        assert!(!info.architecture.is_empty());
319        assert!(info.cpu_cores > 0);
320        assert!(info.total_memory > 0);
321    }
322
323    #[test]
324    fn test_platform_directories() {
325        assert!(get_config_dir().is_some());
326        assert!(get_cache_dir().is_some());
327        assert!(get_data_dir().is_some());
328    }
329
330    #[test]
331    fn test_ensure_directories() {
332        // This test should not fail
333        assert!(ensure_platform_dirs().is_ok());
334    }
335}