use std::path::{Path, PathBuf};
use crate::dirs::CONFIG;
#[derive(PartialEq, Eq, Debug)]
pub enum UserDir {
Desktop,
Download,
Documents,
Music,
Pictures,
Videos,
Templates,
Public,
Projects,
}
impl UserDir {
#[cfg(target_os = "linux")]
fn key(&self) -> &'static str {
match self {
UserDir::Desktop => "XDG_DESKTOP_DIR",
UserDir::Download => "XDG_DOWNLOAD_DIR",
UserDir::Documents => "XDG_DOCUMENTS_DIR",
UserDir::Music => "XDG_MUSIC_DIR",
UserDir::Pictures => "XDG_PICTURES_DIR",
UserDir::Videos => "XDG_VIDEOS_DIR",
UserDir::Templates => "XDG_TEMPLATES_DIR",
UserDir::Public => "XDG_PUBLICSHARE_DIR",
UserDir::Projects => "XDG_PROJECTS_DIR",
}
}
#[cfg(target_os = "macos")]
fn mac_rel(&self) -> Option<&'static str> {
match self {
UserDir::Desktop => Some("Desktop"),
UserDir::Download => Some("Downloads"),
UserDir::Documents => Some("Documents"),
UserDir::Music => Some("Music"),
UserDir::Pictures => Some("Pictures"),
UserDir::Videos => Some("Movies"),
UserDir::Templates => None,
UserDir::Public => None,
UserDir::Projects => None,
}
}
#[cfg(target_os = "linux")]
fn linux_rel(&self) -> Option<&'static str> {
match self {
UserDir::Desktop => Some("Desktop"),
UserDir::Download => Some("Downloads"),
UserDir::Documents => Some("Documents"),
UserDir::Music => Some("Music"),
UserDir::Pictures => Some("Pictures"),
UserDir::Videos => Some("Videos"),
UserDir::Templates => Some("Templates"),
UserDir::Public => Some("Public"),
UserDir::Projects => Some("Projects"),
}
}
fn resolve(&self) -> Option<PathBuf> {
#[cfg(target_os = "linux")]
{
parse_user_dirs(self.key(), self.linux_rel())
}
#[cfg(target_os = "macos")]
{
Some(crate::home().ok()?.join(self.mac_rel()?))
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
None
}
}
}
#[cfg(target_os = "linux")]
fn parse_user_dirs(key: &str, default: Option<&str>) -> Option<PathBuf> {
let home = super::home().ok()?;
let file = CONFIG.resolve()?.join("user-dirs.dirs");
if let Ok(contents) = std::fs::read_to_string(&file) {
for line in contents.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let Some((name, value)) = line.split_once('=') else {
continue;
};
if name.trim() != key {
continue;
}
let value = value.trim().trim_matches('"');
let path = if let Some(rest) = value.strip_prefix("$HOME") {
let rest = rest.trim_start_matches('/');
if rest.is_empty() {
return None; }
home.join(rest)
} else if Path::new(value).is_absolute() {
PathBuf::from(value)
} else {
continue;
};
return Some(path);
}
}
Some(home.join(default?))
}
impl From<UserDir> for Option<PathBuf> {
fn from(value: UserDir) -> Self {
value.resolve()
}
}