use std::collections::HashMap;
use std::path::Path;
use std::sync::{LazyLock, Mutex};
use crate::cursor::VolumeKey;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RootCapability {
Fast { volume: VolumeKey },
Fallback { reason: String },
}
pub(crate) fn volume_key(path: &Path) -> Option<VolumeKey> {
use std::path::{Component, Prefix};
match path.components().next()? {
Component::Prefix(prefix) => match prefix.kind() {
Prefix::Disk(letter) | Prefix::VerbatimDisk(letter) => {
Some(format!("{}:", (letter as char).to_ascii_uppercase()))
}
_ => None,
},
_ => None,
}
}
static CAPABILITY_CACHE: LazyLock<Mutex<HashMap<VolumeKey, RootCapability>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
const FORCE_SLOW_LANE_ENV: &str = "DOWSE_FORCE_SLOW_LANE";
pub(crate) fn probe_root_capability(root: &Path) -> RootCapability {
if cfg!(test) || std::env::var_os(FORCE_SLOW_LANE_ENV).is_some() {
return RootCapability::Fallback {
reason: "测试环境(cfg(test) 或 DOWSE_FORCE_SLOW_LANE 逃生舱),强制走慢车道"
.to_string(),
};
}
let Some(vol) = volume_key(root) else {
let capability = platform::probe(root);
log_capability(root, &capability);
return capability;
};
if let Some(cached) = CAPABILITY_CACHE
.lock()
.expect("capability cache mutex poisoned")
.get(&vol)
{
return cached.clone();
}
let capability = platform::probe(root);
log_capability(root, &capability);
CAPABILITY_CACHE
.lock()
.expect("capability cache mutex poisoned")
.insert(vol, capability.clone());
capability
}
fn log_capability(root: &Path, capability: &RootCapability) {
match capability {
RootCapability::Fast { volume } => {
eprintln!("{volume}: NTFS + 管理员权限,启用 MFT/USN 快速路径");
}
RootCapability::Fallback { reason } => {
eprintln!(
"{}: 走现有 walkdir + notify 路径({reason})",
root.display()
);
}
}
}
pub fn ntfs_fast_path_available(root: &Path) -> bool {
matches!(probe_root_capability(root), RootCapability::Fast { .. })
}
#[cfg(windows)]
pub(crate) fn open_volume_handle(
volume: &VolumeKey,
) -> windows::core::Result<windows::Win32::Foundation::HANDLE> {
platform::open_volume_handle(volume)
}
#[cfg(windows)]
mod platform {
use std::path::Path;
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE,
GetVolumeInformationW, GetVolumePathNameW, OPEN_EXISTING,
};
use windows::core::PCWSTR;
use super::{RootCapability, volume_key};
fn volume_root_path(root: &Path) -> Option<Vec<u16>> {
use std::os::windows::ffi::OsStrExt;
let wide: Vec<u16> = root
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
let mut buf = vec![0u16; 261]; unsafe { GetVolumePathNameW(PCWSTR(wide.as_ptr()), &mut buf) }.ok()?;
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
buf.truncate(len);
Some(buf)
}
fn is_ntfs(volume_root: &[u16]) -> bool {
let mut fs_name = vec![0u16; 32];
let ok = unsafe {
GetVolumeInformationW(
PCWSTR(volume_root.as_ptr()),
None,
None,
None,
None,
Some(&mut fs_name),
)
}
.is_ok();
if !ok {
return false;
}
let len = fs_name
.iter()
.position(|&c| c == 0)
.unwrap_or(fs_name.len());
String::from_utf16_lossy(&fs_name[..len]).eq_ignore_ascii_case("NTFS")
}
pub(crate) fn open_volume_handle(volume: &str) -> windows::core::Result<HANDLE> {
let mut device_path: Vec<u16> = r"\\.\".encode_utf16().collect();
device_path.extend(volume.encode_utf16());
device_path.push(0);
unsafe {
CreateFileW(
PCWSTR(device_path.as_ptr()),
windows::Win32::Storage::FileSystem::FILE_GENERIC_READ.0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
None,
)
}
}
pub(super) fn probe(root: &Path) -> RootCapability {
let Some(volume_root) = volume_root_path(root) else {
return RootCapability::Fallback {
reason: "解析不出卷根路径".to_string(),
};
};
if !is_ntfs(&volume_root) {
return RootCapability::Fallback {
reason: "非 NTFS 卷".to_string(),
};
}
let Some(volume) = volume_key(root) else {
return RootCapability::Fallback {
reason: "解析不出盘符".to_string(),
};
};
match open_volume_handle(&volume) {
Ok(handle) => {
unsafe {
let _ = CloseHandle(handle);
}
RootCapability::Fast { volume }
}
Err(err) => RootCapability::Fallback {
reason: format!("未获管理员权限,该卷改用通用索引方式,功能不受影响: {err}"),
},
}
}
}
#[cfg(not(windows))]
mod platform {
use std::path::Path;
use super::RootCapability;
pub(super) fn probe(_root: &Path) -> RootCapability {
RootCapability::Fallback {
reason: "非 Windows 平台".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn volume_key_extracts_uppercase_drive_letter() {
if cfg!(windows) {
assert_eq!(
volume_key(&PathBuf::from(r"C:\Users\foo")),
Some("C:".to_string())
);
assert_eq!(
volume_key(&PathBuf::from(r"d:\data")),
Some("D:".to_string())
);
}
}
#[test]
fn volume_key_of_relative_path_is_none() {
assert_eq!(volume_key(&PathBuf::from("relative/path")), None);
}
#[cfg(not(windows))]
#[test]
fn non_windows_always_falls_back() {
let cap = probe_root_capability(&PathBuf::from("/tmp/watch"));
assert!(matches!(cap, RootCapability::Fallback { .. }));
}
}