libsubconverter/utils/
system.rs

1//! System utilities for cross-platform functionality
2
3use std::env;
4use std::thread;
5use std::time::{Duration, SystemTime, UNIX_EPOCH};
6
7#[cfg(target_arch = "wasm32")]
8use js_sys::Date;
9
10#[cfg(target_os = "windows")]
11use winapi::shared::winerror::IS_ERROR;
12#[cfg(target_os = "windows")]
13use winapi::{
14    shared::minwindef::{BYTE, DWORD, HKEY},
15    um::{
16        winnt::KEY_ALL_ACCESS,
17        winreg::{RegEnumValueW, RegOpenKeyExW, RegQueryInfoKeyW, HKEY_CURRENT_USER},
18    },
19};
20
21/// Get the current system time safely, even in WebAssembly environments
22///
23/// This function handles the limitations of WebAssembly regarding system time access
24/// and provides a fallback mechanism to prevent panics.
25///
26/// # Returns
27///
28/// The current system time or a default time if it can't be determined
29pub fn safe_system_time() -> SystemTime {
30    #[cfg(target_arch = "wasm32")]
31    {
32        // In WebAssembly, use the JavaScript Date API
33        let now_ms = Date::now();
34        let seconds = (now_ms / 1000.0) as u64;
35        let nanos = ((now_ms % 1000.0) * 1_000_000.0) as u32;
36
37        // Create a SystemTime from the UNIX_EPOCH plus the calculated duration
38        UNIX_EPOCH
39            .checked_add(Duration::new(seconds, nanos))
40            .unwrap_or(UNIX_EPOCH) // Fallback to UNIX_EPOCH if addition overflows
41    }
42
43    #[cfg(not(target_arch = "wasm32"))]
44    {
45        // In native environments, use the standard SystemTime::now()
46        SystemTime::now()
47    }
48}
49
50/// Get the current timestamp in seconds since UNIX epoch
51///
52/// This function safely gets the current time and converts it to seconds
53/// since the UNIX epoch, handling potential errors gracefully.
54///
55/// # Returns
56///
57/// The number of seconds since the UNIX epoch, or 0 if it can't be determined
58pub fn safe_unix_timestamp() -> u64 {
59    safe_system_time()
60        .duration_since(UNIX_EPOCH)
61        .unwrap_or_default()
62        .as_secs()
63}
64
65/// Sleep for a specified number of milliseconds
66///
67/// # Arguments
68///
69/// * `interval` - The number of milliseconds to sleep
70pub fn sleep_ms(interval: u64) {
71    thread::sleep(Duration::from_millis(interval));
72}
73
74/// Get environment variable value
75///
76/// # Arguments
77///
78/// * `name` - The name of the environment variable
79///
80/// # Returns
81///
82/// The value of the environment variable or empty string if not found
83pub fn get_env(name: &str) -> String {
84    env::var(name).unwrap_or_default()
85}
86
87/// Get system proxy settings
88///
89/// # Returns
90///
91/// The system proxy server string or empty string if not found
92pub fn get_system_proxy() -> String {
93    #[cfg(target_arch = "wasm32")]
94    {
95        // In WASM environment, we can't access system proxy settings
96        // Return empty string to indicate no proxy
97        return String::new();
98    }
99
100    #[cfg(not(target_arch = "wasm32"))]
101    #[cfg(target_os = "windows")]
102    {
103        use std::ffi::OsString;
104        use std::os::windows::ffi::OsStringExt;
105        use std::ptr;
106
107        unsafe {
108            // Open registry key
109            let subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
110            let mut hkey: HKEY = ptr::null_mut();
111            let wide_subkey: Vec<u16> = subkey.encode_utf16().chain(std::iter::once(0)).collect();
112
113            let ret = RegOpenKeyExW(
114                HKEY_CURRENT_USER,
115                wide_subkey.as_ptr(),
116                0,
117                KEY_ALL_ACCESS,
118                &mut hkey,
119            );
120
121            if IS_ERROR(ret) {
122                return String::new();
123            }
124
125            // Query key info
126            let mut values_count: DWORD = 0;
127            let mut max_value_name_len: DWORD = 0;
128            let mut max_value_len: DWORD = 0;
129
130            let ret = RegQueryInfoKeyW(
131                hkey,
132                ptr::null_mut(),
133                ptr::null_mut(),
134                ptr::null_mut(),
135                ptr::null_mut(),
136                ptr::null_mut(),
137                ptr::null_mut(),
138                &mut values_count,
139                &mut max_value_name_len,
140                &mut max_value_len,
141                ptr::null_mut(),
142                ptr::null_mut(),
143            );
144
145            if IS_ERROR(ret) {
146                return String::new();
147            }
148
149            // max_value_name_len does not include null terminator
150            max_value_name_len += 1;
151
152            // Extract registry values
153            let mut proxy_enable: DWORD = 0;
154            let mut proxy_server = String::new();
155
156            for i in 0..values_count {
157                let mut name_buf = vec![0u16; max_value_name_len as usize];
158                let mut name_len = max_value_name_len;
159                let mut value_type: DWORD = 0;
160                let mut value_len: DWORD = 0;
161
162                // First call to get the size
163                let ret = RegEnumValueW(
164                    hkey,
165                    i,
166                    name_buf.as_mut_ptr(),
167                    &mut name_len,
168                    ptr::null_mut(),
169                    &mut value_type,
170                    ptr::null_mut(),
171                    &mut value_len,
172                );
173
174                if IS_ERROR(ret) {
175                    continue;
176                }
177
178                let mut value_buf = vec![0u8; value_len as usize];
179                name_len = max_value_name_len;
180
181                // Second call to get the value
182                let ret = RegEnumValueW(
183                    hkey,
184                    i,
185                    name_buf.as_mut_ptr(),
186                    &mut name_len,
187                    ptr::null_mut(),
188                    &mut value_type,
189                    value_buf.as_mut_ptr() as *mut BYTE,
190                    &mut value_len,
191                );
192
193                if IS_ERROR(ret) {
194                    continue;
195                }
196
197                // Convert name to string
198                let name_len = name_len as usize;
199                let name_os_string = OsString::from_wide(&name_buf[0..name_len]);
200                let name_string = name_os_string.to_string_lossy().to_string();
201
202                // Check if this is the ProxyEnable or ProxyServer entry
203                if name_string == "ProxyEnable" && value_len >= 4 {
204                    proxy_enable = u32::from_ne_bytes([
205                        value_buf[0],
206                        value_buf[1],
207                        value_buf[2],
208                        value_buf[3],
209                    ]);
210                } else if name_string == "ProxyServer" {
211                    // For REG_SZ strings, convert the buffer to a string
212                    if value_len > 0 {
213                        // Ensure the buffer is valid UTF-8
214                        if let Ok(s) =
215                            String::from_utf8(value_buf[0..(value_len as usize)].to_vec())
216                        {
217                            proxy_server = s.trim_end_matches('\0').to_string();
218                        }
219                    }
220                }
221            }
222
223            if proxy_enable != 0 && !proxy_server.is_empty() {
224                return proxy_server;
225            }
226
227            String::new()
228        }
229    }
230
231    #[cfg(not(target_arch = "wasm32"))]
232    #[cfg(not(target_os = "windows"))]
233    {
234        let proxy_env = [
235            "all_proxy",
236            "ALL_PROXY",
237            "http_proxy",
238            "HTTP_PROXY",
239            "https_proxy",
240            "HTTPS_PROXY",
241        ];
242
243        for var in &proxy_env {
244            if let Ok(proxy) = env::var(var) {
245                if !proxy.is_empty() {
246                    return proxy;
247                }
248            }
249        }
250
251        String::new()
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_sleep_ms() {
261        // This is more of a smoke test
262        sleep_ms(1);
263    }
264
265    #[test]
266    fn test_get_env() {
267        // Test against a common environment variable
268        let path = get_env("PATH");
269        assert!(!path.is_empty());
270    }
271}