use std::{env, error, ffi, fmt, fs, io, path};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum DirMode {
Create,
DontCreate,
}
#[derive(Debug)]
pub struct HomeDirError;
impl fmt::Display for HomeDirError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unable to find home directory")
}
}
impl error::Error for HomeDirError {}
impl From<HomeDirError> for io::Error {
fn from(e: HomeDirError) -> Self {
Self::new(io::ErrorKind::Other, e)
}
}
fn get_home_dir() -> Result<path::PathBuf, HomeDirError> {
let home_dir = match home::home_dir() {
Some(home_dir) => home_dir,
None => return Err(HomeDirError),
};
Ok(home_dir)
}
fn get_config_dir() -> Result<path::PathBuf, HomeDirError> {
if let Some(env_var_config_dir) = env::var_os("FEND_CONFIG_DIR") {
return Ok(path::PathBuf::from(env_var_config_dir));
}
if let Some(env_var_xdg_config_dir) = env::var_os("XDG_CONFIG_HOME") {
let mut res = path::PathBuf::from(env_var_xdg_config_dir);
res.push("fend");
return Ok(res);
}
let mut res = get_home_dir()?;
res.push(".config");
res.push("fend");
Ok(res)
}
pub fn get_config_file_location() -> Result<path::PathBuf, HomeDirError> {
let mut config_path = get_config_dir()?;
config_path.push("config.toml");
Ok(config_path)
}
pub fn get_state_dir(mode: DirMode) -> Result<path::PathBuf, io::Error> {
if let Some(env_var_history_dir) = env::var_os("FEND_STATE_DIR") {
let res = path::PathBuf::from(env_var_history_dir);
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
}
return Ok(res);
}
if let Some(env_var_xdg_state_dir) = env::var_os("XDG_STATE_HOME") {
let mut res = path::PathBuf::from(env_var_xdg_state_dir);
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
mark_dir_as_hidden(&res);
}
res.push("fend");
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
}
return Ok(res);
}
let mut res = get_home_dir()?;
res.push(".local");
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
mark_dir_as_hidden(&res);
}
res.push("state");
res.push("fend");
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
}
Ok(res)
}
pub fn get_history_file_location(mode: DirMode) -> Result<path::PathBuf, io::Error> {
let mut history_path = get_state_dir(mode)?;
history_path.push("history");
Ok(history_path)
}
pub fn get_cache_dir(mode: DirMode) -> Result<path::PathBuf, io::Error> {
if let Some(env_var_cache_dir) = env::var_os("FEND_CACHE_DIR") {
let res = path::PathBuf::from(env_var_cache_dir);
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
}
return Ok(res);
}
if let Some(env_var_xdg_cache_dir) = env::var_os("XDG_CACHE_HOME") {
let mut res = path::PathBuf::from(env_var_xdg_cache_dir);
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
mark_dir_as_hidden(&res);
}
res.push("fend");
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
}
return Ok(res);
}
let mut res = get_home_dir()?;
res.push(".cache");
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
mark_dir_as_hidden(&res);
}
res.push("fend");
if mode == DirMode::Create {
fs::create_dir_all(&res)?;
}
Ok(res)
}
fn mark_dir_as_hidden(path: &path::Path) {
let metadata = match fs::metadata(path) {
Ok(metadata) => metadata,
Err(_) => return,
};
if !metadata.is_dir() {
return;
}
let path = {
let mut p = ffi::OsString::from("\\\\?\\");
p.push(path.as_os_str());
p
};
match mark_dir_as_hidden_impl(path.as_os_str()) {
Ok(()) => (),
Err(e) => {
eprintln!("failed to set cache directory as hidden: error code: {e}");
}
}
}
#[cfg(windows)]
#[allow(unsafe_code)]
fn mark_dir_as_hidden_impl(path: &ffi::OsStr) -> Result<(), u32> {
use std::os::windows::prelude::*;
use winapi::um::{
errhandlingapi::GetLastError, fileapi::SetFileAttributesW, winnt::FILE_ATTRIBUTE_HIDDEN,
};
let path = path.encode_wide().chain(Some(0)).collect::<Vec<u16>>();
unsafe {
let return_code = SetFileAttributesW(path.as_slice().as_ptr(), FILE_ATTRIBUTE_HIDDEN);
if return_code == 0 {
return Err(GetLastError());
}
}
Ok(())
}
#[cfg(not(windows))]
#[allow(clippy::unnecessary_wraps)]
fn mark_dir_as_hidden_impl(_path: &ffi::OsStr) -> Result<(), u32> {
Ok(())
}