cross_path/platform/
windows.rs1use crate::PathError;
9use crate::platform::{DiskInfo, FileAttributes, PathExt, PlatformPath};
10use alloc::format;
11use alloc::string::{String, ToString};
12use alloc::vec::Vec;
13use core::iter::Iterator;
14use core::option::Option;
15use std::ffi::OsString;
16use std::os::windows::ffi::OsStringExt;
17use std::os::windows::fs::MetadataExt;
18use std::path::{Path, PathBuf};
19use windows::Win32::Foundation::GetLastError;
20use windows::Win32::Storage::FileSystem::{
21 FILE_ATTRIBUTE_HIDDEN, GetDiskFreeSpaceExW, GetFileAttributesW, GetVolumeInformationW,
22};
23use windows::core::PCWSTR;
24
25pub struct WindowsPathExt {
27 path: PathBuf,
28}
29
30impl WindowsPathExt {
31 pub fn new<P: AsRef<Path>>(path: P) -> Self {
33 Self {
34 path: path.as_ref().to_path_buf(),
35 }
36 }
37}
38
39impl PlatformPath for WindowsPathExt {
40 fn separator(&self) -> char {
41 '\\'
42 }
43
44 fn is_absolute(&self) -> bool {
45 self.path.is_absolute()
46 }
47
48 fn to_platform_specific(&self) -> String {
49 self.path.to_string_lossy().into_owned()
50 }
51}
52
53impl PathExt for WindowsPathExt {
54 fn get_attributes(&self) -> Option<FileAttributes> {
55 let metadata = std::fs::metadata(&self.path).ok()?;
56
57 let size = metadata.len();
58 let is_directory = metadata.is_dir();
59 let is_readonly = metadata.permissions().readonly();
60
61 let attrs = metadata.file_attributes();
63 let is_hidden = (attrs & FILE_ATTRIBUTE_HIDDEN.0) != 0;
64
65 let creation_time = metadata
66 .created()
67 .ok()
68 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
69 .map(|d| d.as_secs());
70
71 let modification_time = metadata
72 .modified()
73 .ok()
74 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
75 .map(|d| d.as_secs());
76
77 Some(FileAttributes {
78 size,
79 is_directory,
80 is_hidden,
81 is_readonly,
82 creation_time,
83 modification_time,
84 })
85 }
86
87 fn is_accessible(&self) -> bool {
88 self.path.exists()
89 }
90
91 fn get_disk_info(&self) -> Option<DiskInfo> {
92 let root = self.path.components().next().and_then(|c| match c {
94 std::path::Component::Prefix(prefix) => {
95 let mut s = prefix.as_os_str().to_os_string();
96 s.push("\\");
97 Some(s)
98 }
99 std::path::Component::RootDir => Some(std::path::PathBuf::from("\\").into_os_string()),
100 _ => None,
101 })?;
102
103 let root_str = root.to_string_lossy();
104 let wide_root = to_windows_path(&root_str).ok()?;
105
106 let mut total_bytes = 0u64;
107 let mut free_bytes_caller = 0u64;
108 let mut total_free_bytes = 0u64;
109
110 unsafe {
111 let result = GetDiskFreeSpaceExW(
112 PCWSTR(wide_root.as_ptr()),
113 Some(&mut free_bytes_caller),
114 Some(&mut total_bytes),
115 Some(&mut total_free_bytes),
116 );
117
118 if result.is_err() {
119 return None;
120 }
121 }
122
123 let mut fs_name_buf = [0u16; 256];
125 let fs_type = unsafe {
126 let res = GetVolumeInformationW(
127 PCWSTR(wide_root.as_ptr()),
128 None,
129 None,
130 None,
131 None,
132 Some(&mut fs_name_buf),
133 );
134
135 if res.is_ok() {
136 let len = fs_name_buf
137 .iter()
138 .position(|&c| c == 0)
139 .unwrap_or(fs_name_buf.len());
140 String::from_utf16_lossy(&fs_name_buf[..len])
141 } else {
142 "Unknown".to_string()
143 }
144 };
145
146 Some(DiskInfo {
147 total_space: total_bytes,
148 free_space: free_bytes_caller,
149 filesystem_type: fs_type,
150 })
151 }
152}
153
154pub fn to_windows_path(path: &str) -> Result<Vec<u16>, PathError> {
156 let mut wide: Vec<u16> = path.encode_utf16().collect();
157 wide.push(0); for ch in &mut wide {
161 if *ch == b'/' as u16 {
162 *ch = b'\\' as u16;
163 }
164 }
165
166 Ok(wide)
167}
168
169pub fn from_windows_path(wide: &[u16]) -> Result<String, PathError> {
171 let null_pos = wide.iter().position(|&c| c == 0).unwrap_or(wide.len());
173 let slice = &wide[..null_pos];
174
175 let mut result = OsString::from_wide(slice)
177 .into_string()
178 .map_err(|e| PathError::encoding_error(e.to_string_lossy().into_owned()))?;
179
180 result = result.replace('\\', "/");
182
183 Ok(result)
184}
185
186pub fn is_valid_windows_path(path: &str) -> bool {
188 if path.len() >= 2 {
190 let first_char = path.chars().next().unwrap();
191 let second_char = path.chars().nth(1).unwrap();
192
193 if first_char.is_ascii_alphabetic() && second_char == ':' {
194 return true;
195 }
196 }
197
198 if path.starts_with(r"\\") {
200 return true;
201 }
202
203 false
204}
205
206pub fn get_drive_letter(path: &str) -> Option<char> {
208 if path.len() >= 2 {
209 let first_char = path.chars().next().unwrap();
210 let second_char = path.chars().nth(1).unwrap();
211
212 if first_char.is_ascii_alphabetic() && second_char == ':' {
213 return Some(first_char.to_ascii_uppercase());
214 }
215 }
216
217 None
218}
219
220pub fn get_windows_file_attributes(path: &str) -> Result<u32, PathError> {
222 let wide_path = to_windows_path(path)?;
223
224 unsafe {
225 let attrs = GetFileAttributesW(PCWSTR(wide_path.as_ptr()));
226
227 if attrs == 0xFFFFFFFF {
228 let error = GetLastError();
229 return Err(PathError::platform_error(format!(
230 "Failed to get file attributes: {:?}",
231 error
232 )));
233 }
234
235 Ok(attrs)
236 }
237}
238
239pub fn windows_path_exists(path: &str) -> Result<bool, PathError> {
241 let attrs = get_windows_file_attributes(path)?;
242 Ok(attrs != 0xFFFFFFFF)
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_is_valid_windows_path() {
251 assert!(is_valid_windows_path(r"C:\Windows"));
252 assert!(is_valid_windows_path(r"D:\Data\file.txt"));
253 assert!(is_valid_windows_path(r"\\Server\Share"));
254 assert!(!is_valid_windows_path(r"/usr/bin"));
255 assert!(!is_valid_windows_path(r"relative\path"));
256 }
257
258 #[test]
259 fn test_get_drive_letter() {
260 assert_eq!(get_drive_letter(r"C:\Windows"), Some('C'));
261 assert_eq!(get_drive_letter(r"d:\data"), Some('D'));
262 assert_eq!(get_drive_letter(r"\\Server\Share"), None);
263 assert_eq!(get_drive_letter(r"/usr/bin"), None);
264 }
265
266 #[test]
267 fn test_to_windows_path() {
268 let path = "C:/Windows/System32";
269 let wide = to_windows_path(path).unwrap();
270
271 assert_eq!(*wide.last().unwrap(), 0);
273
274 let backslash = b'\\' as u16;
276 assert!(wide.contains(&backslash));
277 }
278
279 #[test]
280 fn test_from_windows_path() {
281 let wide = vec![
282 'C' as u16,
283 ':' as u16,
284 '\\' as u16,
285 'T' as u16,
286 'e' as u16,
287 's' as u16,
288 't' as u16,
289 0,
290 ];
291 let path = from_windows_path(&wide).unwrap();
292
293 assert_eq!(path, "C:/Test");
295 }
296}