use crate::entry::Entry;
use crate::error::Error;
use crate::walk::StorageHint;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use windows::Win32::Foundation::FILETIME;
use windows::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_REPARSE_POINT,
FIND_FIRST_EX_LARGE_FETCH, FindClose, FindExInfoBasic, FindExSearchNameMatch, FindFirstFileExW,
FindNextFileW, WIN32_FIND_DATAW,
};
use windows::core::PCWSTR;
pub fn scan_dir_platform(
path: &Path,
prefix: &str,
hint: StorageHint,
) -> Result<Vec<Entry>, Error> {
let mut entries = Vec::with_capacity(32);
let wide_path: Vec<u16> = path.as_os_str().encode_wide().collect();
let mut wide_pattern = Vec::with_capacity(wide_path.len() + 7);
if wide_path.len() > 248 {
wide_pattern.extend_from_slice(&[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]);
}
wide_pattern.extend_from_slice(&wide_path);
wide_pattern.extend_from_slice(&[b'\\' as u16, b'*' as u16, 0]);
unsafe {
let mut find_data: WIN32_FIND_DATAW = std::mem::zeroed();
let flags = if hint == StorageHint::Network {
FIND_FIRST_EX_LARGE_FETCH
} else {
Default::default()
};
let handle = FindFirstFileExW(
PCWSTR(wide_pattern.as_ptr()),
FindExInfoBasic,
&mut find_data as *mut WIN32_FIND_DATAW as *mut _,
FindExSearchNameMatch,
None,
flags,
)
.map_err(|_| Error::Io {
path: path.to_path_buf(),
source: std::io::Error::last_os_error(),
})?;
loop {
if !is_dot_or_dotdot(&find_data.cFileName) {
let attrs = find_data.dwFileAttributes;
let is_dir = (attrs & FILE_ATTRIBUTE_DIRECTORY.0) != 0;
let is_symlink = (attrs & FILE_ATTRIBUTE_REPARSE_POINT.0) != 0;
let is_hidden = (attrs & FILE_ATTRIBUTE_HIDDEN.0) != 0;
let size = if is_symlink {
0
} else {
((find_data.nFileSizeHigh as u64) << 32) | (find_data.nFileSizeLow as u64)
};
let modified = filetime_to_unix(&find_data.ftLastWriteTime);
let relative_path = wchar_to_string(&find_data.cFileName, prefix);
entries.push(Entry {
relative_path,
depth: 0,
size,
is_dir,
is_symlink,
is_hidden,
modified,
});
}
if FindNextFileW(handle, &mut find_data).is_err() {
break;
}
}
let _ = FindClose(handle);
}
Ok(entries)
}
fn is_dot_or_dotdot(wchars: &[u16]) -> bool {
(wchars[0] == b'.' as u16 && wchars[1] == 0)
|| (wchars[0] == b'.' as u16 && wchars[1] == b'.' as u16 && wchars[2] == 0)
}
fn wchar_to_string(wchars: &[u16], prefix: &str) -> String {
let name_len = wchars.iter().position(|&c| c == 0).unwrap_or(wchars.len());
let wchars = &wchars[..name_len];
if prefix.is_empty() {
return String::from_utf16_lossy(wchars);
}
let mut s = String::with_capacity(prefix.len() + 1 + name_len);
s.push_str(prefix);
s.push(std::path::MAIN_SEPARATOR);
for c in char::decode_utf16(wchars.iter().copied()) {
s.push(c.unwrap_or(char::REPLACEMENT_CHARACTER));
}
s
}
fn filetime_to_unix(ft: &FILETIME) -> i64 {
const EPOCH_DIFF: i64 = 11644473600;
const TICKS_PER_SECOND: i64 = 10_000_000;
let ticks = (((ft.dwHighDateTime as u64) << 32) | ft.dwLowDateTime as u64) as i64;
(ticks / TICKS_PER_SECOND) - EPOCH_DIFF
}