adaptive_pipeline_bootstrap/platform/
windows.rs

1// /////////////////////////////////////////////////////////////////////////////
2// Adaptive Pipeline
3// Copyright (c) 2025 Michael Gardner, A Bit of Help, Inc.
4// SPDX-License-Identifier: BSD-3-Clause
5// See LICENSE file in the project root.
6// /////////////////////////////////////////////////////////////////////////////
7
8//! # Windows Platform Implementation
9//!
10//! Windows API implementation with cross-platform stubs.
11//!
12//! ## Implementation Notes
13//!
14//! - **On Windows**: Uses winapi crate for native Windows API calls
15//! - **On Unix**: Provides stub implementations for cross-compilation
16//!
17//! ## Windows APIs Used (when on Windows)
18//!
19//! - `GlobalMemoryStatusEx` - Memory information
20//! - `GetSystemInfo` - CPU count and page size
21//! - `IsUserAnAdmin` - Privilege checking
22//! - File APIs via tokio (cross-platform)
23
24use super::{Platform, PlatformError};
25use async_trait::async_trait;
26use std::path::{Path, PathBuf};
27
28/// Windows platform implementation
29///
30/// Provides Windows-specific implementations on Windows,
31/// and stub implementations on Unix for cross-compilation.
32pub struct WindowsPlatform;
33
34impl WindowsPlatform {
35    /// Create a new Windows platform instance
36    pub fn new() -> Self {
37        Self
38    }
39
40    #[cfg(windows)]
41    fn get_memory_info_impl() -> Result<(u64, u64), PlatformError> {
42        use std::mem;
43        use winapi::um::sysinfoapi::{GlobalMemoryStatusEx, MEMORYSTATUSEX};
44
45        unsafe {
46            let mut mem_status: MEMORYSTATUSEX = mem::zeroed();
47            mem_status.dwLength = mem::size_of::<MEMORYSTATUSEX>() as u32;
48
49            if GlobalMemoryStatusEx(&mut mem_status) != 0 {
50                Ok((mem_status.ullTotalPhys, mem_status.ullAvailPhys))
51            } else {
52                Err(PlatformError::Other("GlobalMemoryStatusEx failed".to_string()))
53            }
54        }
55    }
56
57    #[cfg(not(windows))]
58    fn get_memory_info_impl() -> Result<(u64, u64), PlatformError> {
59        // Stub for cross-compilation
60        Err(PlatformError::NotSupported(
61            "Windows APIs not available on this platform".to_string(),
62        ))
63    }
64
65    #[cfg(windows)]
66    fn get_page_size_impl() -> usize {
67        use std::mem;
68        use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO};
69
70        unsafe {
71            let mut sys_info: SYSTEM_INFO = mem::zeroed();
72            GetSystemInfo(&mut sys_info);
73            sys_info.dwPageSize as usize
74        }
75    }
76
77    #[cfg(not(windows))]
78    fn get_page_size_impl() -> usize {
79        // Stub returns default page size
80        4096
81    }
82
83    #[cfg(windows)]
84    fn get_cpu_count_impl() -> usize {
85        use std::mem;
86        use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO};
87
88        unsafe {
89            let mut sys_info: SYSTEM_INFO = mem::zeroed();
90            GetSystemInfo(&mut sys_info);
91            sys_info.dwNumberOfProcessors as usize
92        }
93    }
94
95    #[cfg(not(windows))]
96    fn get_cpu_count_impl() -> usize {
97        // Stub returns 1
98        1
99    }
100
101    #[cfg(windows)]
102    fn is_elevated_impl() -> bool {
103        // Manual FFI declaration since winapi doesn't properly expose IsUserAnAdmin
104        #[link(name = "shell32")]
105        extern "system" {
106            fn IsUserAnAdmin() -> i32;
107        }
108        unsafe { IsUserAnAdmin() != 0 }
109    }
110
111    #[cfg(not(windows))]
112    fn is_elevated_impl() -> bool {
113        // Stub returns false
114        false
115    }
116}
117
118impl Default for WindowsPlatform {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124#[async_trait]
125impl Platform for WindowsPlatform {
126    fn page_size(&self) -> usize {
127        Self::get_page_size_impl()
128    }
129
130    fn cpu_count(&self) -> usize {
131        Self::get_cpu_count_impl()
132    }
133
134    fn total_memory(&self) -> Result<u64, PlatformError> {
135        Self::get_memory_info_impl().map(|(total, _)| total)
136    }
137
138    fn available_memory(&self) -> Result<u64, PlatformError> {
139        Self::get_memory_info_impl().map(|(_, available)| available)
140    }
141
142    fn line_separator(&self) -> &'static str {
143        "\r\n"
144    }
145
146    fn path_separator(&self) -> char {
147        ';'
148    }
149
150    fn platform_name(&self) -> &'static str {
151        "windows"
152    }
153
154    fn temp_dir(&self) -> PathBuf {
155        std::env::temp_dir()
156    }
157
158    fn is_elevated(&self) -> bool {
159        Self::is_elevated_impl()
160    }
161
162    fn set_permissions(&self, _path: &Path, _mode: u32) -> Result<(), PlatformError> {
163        // Windows doesn't use Unix-style permission bits
164        // This is a no-op on Windows, returns Ok
165        Ok(())
166    }
167
168    fn is_executable(&self, path: &Path) -> bool {
169        if let Some(ext) = path.extension() {
170            let ext_lower = ext.to_string_lossy().to_lowercase();
171            matches!(ext_lower.as_str(), "exe" | "bat" | "cmd" | "com" | "ps1" | "msi")
172        } else {
173            false
174        }
175    }
176
177    async fn sync_file(&self, file: &tokio::fs::File) -> Result<(), PlatformError> {
178        // tokio's sync_all is cross-platform
179        file.sync_all().await?;
180        Ok(())
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_windows_platform_basics() {
190        let platform = WindowsPlatform::new();
191
192        // CPU count should be at least 1
193        assert!(platform.cpu_count() >= 1);
194
195        // Page size should be reasonable
196        let page_size = platform.page_size();
197        assert!(page_size >= 512);
198        assert!(page_size <= 65536);
199    }
200
201    #[test]
202    fn test_windows_platform_constants() {
203        let platform = WindowsPlatform::new();
204
205        assert_eq!(platform.line_separator(), "\r\n");
206        assert_eq!(platform.path_separator(), ';');
207        assert_eq!(platform.platform_name(), "windows");
208    }
209
210    #[test]
211    fn test_executable_extensions() {
212        let platform = WindowsPlatform::new();
213
214        assert!(platform.is_executable(Path::new("program.exe")));
215        assert!(platform.is_executable(Path::new("script.bat")));
216        assert!(platform.is_executable(Path::new("script.cmd")));
217        assert!(platform.is_executable(Path::new("installer.msi")));
218        assert!(!platform.is_executable(Path::new("document.txt")));
219        assert!(!platform.is_executable(Path::new("noextension")));
220    }
221
222    #[test]
223    fn test_temp_dir() {
224        let platform = WindowsPlatform::new();
225        let temp = platform.temp_dir();
226        // Just verify it returns a path
227        assert!(!temp.as_os_str().is_empty());
228    }
229}