use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
pub fn user_runtime_dir(product: &str) -> PathBuf {
dirs::data_local_dir()
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
.join(product)
}
pub fn user_state_dir(product: &str) -> PathBuf {
user_runtime_dir(product)
}
pub fn user_run_data_root(product: &str) -> PathBuf {
user_runtime_dir(product)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FileIdentity {
pub device: u64,
pub file: u64,
}
pub fn file_identity(file: &File) -> io::Result<Option<FileIdentity>> {
use std::mem::MaybeUninit;
use std::os::windows::io::AsRawHandle;
use winapi::um::fileapi::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION};
use winapi::um::winnt::HANDLE;
let mut info = MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::uninit();
let result =
unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, info.as_mut_ptr()) };
if result == 0 {
return Err(io::Error::last_os_error());
}
let info = unsafe { info.assume_init() };
Ok(Some(FileIdentity {
device: info.dwVolumeSerialNumber as u64,
file: ((info.nFileIndexHigh as u64) << 32) | info.nFileIndexLow as u64,
}))
}
pub fn path_identity(path: &Path) -> io::Result<Option<FileIdentity>> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.open(path)?;
file_identity(&file)
}
pub fn open_lock_file(path: &Path) -> io::Result<File> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.open(path)
}
pub fn try_lock_exclusive(file: &File) -> io::Result<()> {
use std::mem;
use std::os::windows::io::AsRawHandle as _;
use winapi::um::fileapi::LockFileEx;
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, OVERLAPPED};
use winapi::um::winnt::HANDLE;
let mut overlapped: OVERLAPPED = unsafe { mem::zeroed() };
let result = unsafe {
LockFileEx(
file.as_raw_handle() as HANDLE,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if result == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn unlock(file: &File) -> io::Result<()> {
use std::mem;
use std::os::windows::io::AsRawHandle as _;
use winapi::um::fileapi::UnlockFileEx;
use winapi::um::minwinbase::OVERLAPPED;
use winapi::um::winnt::HANDLE;
let mut overlapped: OVERLAPPED = unsafe { mem::zeroed() };
let result = unsafe {
UnlockFileEx(
file.as_raw_handle() as HANDLE,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if result == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn is_lock_conflict(error: &io::Error) -> bool {
use winapi::shared::winerror::ERROR_LOCK_VIOLATION;
error.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32)
}
pub fn encode_path_bytes(path: &Path) -> Vec<u8> {
use std::os::windows::ffi::OsStrExt as _;
path.as_os_str()
.encode_wide()
.flat_map(u16::to_le_bytes)
.collect()
}
pub fn decode_path_bytes(bytes: &[u8]) -> io::Result<PathBuf> {
use std::os::windows::ffi::OsStringExt as _;
if !bytes.len().is_multiple_of(2) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Windows path bytes must be little-endian UTF-16",
));
}
let wide = bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect::<Vec<_>>();
Ok(PathBuf::from(std::ffi::OsString::from_wide(&wide)))
}
pub fn user_data_dir(product: &str) -> PathBuf {
dirs::data_dir()
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
.join(product)
}
pub fn user_config_dir(product: &str) -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
.join(product)
}
pub fn replace_file(tmp: &Path, target: &Path) -> io::Result<()> {
use std::os::windows::ffi::OsStrExt as _;
use windows_sys::Win32::Storage::FileSystem::{ReplaceFileW, REPLACEFILE_WRITE_THROUGH};
if !target.exists() {
return std::fs::rename(tmp, target);
}
fn wide(path: &Path) -> Vec<u16> {
path.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
let target_w = wide(target);
let tmp_w = wide(tmp);
let ok = unsafe {
ReplaceFileW(
target_w.as_ptr(),
tmp_w.as_ptr(),
std::ptr::null(),
REPLACEFILE_WRITE_THROUGH,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn sync_directory(_directory: &Path) -> io::Result<()> {
Ok(())
}
pub fn create_private_file(path: &Path) -> io::Result<File> {
std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
}