use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use std::time::Duration;
pub const MAX_OPEN_TRIES: u32 = 64;
const MAX_PAUSE_MS: u64 = 20;
const ERROR_ACCESS_DENIED: i32 = 5;
const ERROR_SHARING_VIOLATION: i32 = 32;
#[cfg_attr(not(windows), allow(dead_code))]
pub(crate) fn is_replace_refusal(code: Option<i32>) -> bool {
matches!(
code,
Some(ERROR_ACCESS_DENIED) | Some(ERROR_SHARING_VIOLATION)
)
}
#[cfg_attr(not(windows), allow(dead_code))]
pub(crate) fn pause_after(try_index: u32) -> Duration {
Duration::from_millis((u64::from(try_index) + 1).min(MAX_PAUSE_MS))
}
#[cfg_attr(not(windows), allow(dead_code))]
pub(crate) fn open_with_retry<T>(
tries: u32,
mut open: impl FnMut() -> io::Result<T>,
mut pause: impl FnMut(Duration),
) -> io::Result<T> {
let mut attempt = 0u32;
loop {
match open() {
Err(error)
if attempt + 1 < tries.max(1) && is_replace_refusal(error.raw_os_error()) =>
{
pause(pause_after(attempt));
attempt += 1;
}
other => return other,
}
}
}
pub fn open_replaced(path: &Path) -> io::Result<File> {
#[cfg(windows)]
{
open_with_retry(MAX_OPEN_TRIES, || File::open(path), std::thread::sleep)
}
#[cfg(not(windows))]
{
File::open(path)
}
}
pub fn read_replaced(path: &Path) -> io::Result<Vec<u8>> {
let mut file = open_replaced(path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
pub fn read_to_string_replaced(path: &Path) -> io::Result<String> {
let mut file = open_replaced(path)?;
let mut text = String::new();
file.read_to_string(&mut text)?;
Ok(text)
}