use std::time::Duration;
pub(crate) const POSIX_UNLINK_RENAME: u32 = 0x400;
pub(crate) const PERSISTENT_ACLS: u32 = 0x8;
const ERROR_ACCESS_DENIED: i32 = 5;
const ERROR_SHARING_VIOLATION: i32 = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Route {
Posix,
MoveFile,
}
pub(crate) fn route(flags: Option<u32>) -> Route {
match flags {
Some(flags) if flags & POSIX_UNLINK_RENAME != 0 => Route::Posix,
_ => Route::MoveFile,
}
}
pub(crate) fn waited_out(error: Option<i32>) -> bool {
matches!(error, Some(ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION))
}
pub(crate) fn waits() -> impl Iterator<Item = Duration> {
(0..50u32).map(|attempt| Duration::from_millis((1u64 << attempt.min(6)).min(50)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_kind_of_volume_takes_the_route_its_flags_allow() {
for (volume, flags, wanted) in [
("NTFS", 0x03e7_2eff, Route::Posix),
("ReFS and a Dev Drive", 0x5fc6_36cf, Route::Posix),
("exFAT and FAT32", 0x0002_0206, Route::MoveFile),
(
"an SMB share mapped as a drive",
0x00c7_00fe,
Route::MoveFile,
),
] {
assert_eq!(route(Some(flags)), wanted, "{volume}");
}
assert_eq!(route(None), Route::MoveFile, "flags not read");
assert_eq!(0x03e7_2eff & PERSISTENT_ACLS, PERSISTENT_ACLS, "NTFS");
assert_eq!(0x0002_0206 & PERSISTENT_ACLS, 0, "exFAT");
}
#[test]
fn only_a_sharing_violation_and_access_denied_are_waited_out() {
assert!(waited_out(Some(32)));
assert!(waited_out(Some(5)));
for other in [None, Some(2), Some(3), Some(87), Some(145), Some(183)] {
assert!(!waited_out(other), "{other:?}");
}
}
#[test]
fn the_waits_double_to_fifty_milliseconds_and_end() {
let waits: Vec<u128> = waits().map(|wait| wait.as_millis()).collect();
assert_eq!(waits[..8], [1, 2, 4, 8, 16, 32, 50, 50]);
assert_eq!(waits.len(), 50);
assert_eq!(waits.iter().sum::<u128>(), 2263);
}
}