use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use super::FileKind;
use crate::storage::UserDir;
const XDG: &[(UserDir, &str)] = &[
(UserDir::Desktop, "folder-desktop"),
(UserDir::Documents, "folder-documents"),
(UserDir::Downloads, "folder-downloads"),
(UserDir::Music, "folder-music"),
(UserDir::Pictures, "folder-pictures"),
(UserDir::Public, "folder-public"),
(UserDir::Templates, "folder-templates"),
(UserDir::Videos, "folder-videos"),
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserFolders {
home: PathBuf,
names: Vec<(String, &'static str)>,
}
impl UserFolders {
#[must_use]
pub fn english(home: impl Into<PathBuf>) -> Self {
let names = XDG.iter().map(|&(which, icon)| (which.english_name().to_owned(), icon)).collect();
Self { home: home.into(), names }
}
#[must_use]
pub fn parse(home: impl Into<PathBuf>, text: &str) -> Self {
let home = home.into();
let mut names = Vec::new();
for &(which, icon) in XDG {
let Some(path) = crate::storage::user_dir_line(text, which, Some(&home)) else { continue };
let inside = path.strip_prefix(&home).ok().and_then(Path::to_str).map(|name| name.trim_end_matches('/'));
if let Some(name) = inside.filter(|name| is_one_name(name)) {
names.push((name.to_owned(), icon));
}
}
Self { home, names }
}
#[must_use]
pub fn read(home: impl Into<PathBuf>, config: &Path) -> Self {
let home = home.into();
match std::fs::read_to_string(config.join("user-dirs.dirs")) {
Ok(text) => Self::parse(home, &text),
Err(_) => Self::english(home),
}
}
#[must_use]
pub fn current() -> Option<&'static Self> {
static CURRENT: OnceLock<Option<UserFolders>> = OnceLock::new();
CURRENT
.get_or_init(|| {
let home = PathBuf::from(std::env::var_os("HOME").filter(|home| !home.is_empty())?);
let config = std::env::var_os("XDG_CONFIG_HOME")
.filter(|config| !config.is_empty())
.map_or_else(|| home.join(".config"), PathBuf::from);
Some(Self::read(home, &config))
})
.as_ref()
}
#[must_use]
pub fn home(&self) -> &Path {
&self.home
}
#[must_use]
pub fn kind(&self, path: &Path) -> Option<FileKind> {
if path == self.home {
return Some(FileKind::of("folder-home"));
}
let name = path.strip_prefix(&self.home).ok()?.to_str()?;
self.inside(name)
}
pub(crate) fn inside(&self, name: &str) -> Option<FileKind> {
self.names.iter().find(|(own, _)| own == name).map(|&(_, icon)| FileKind::of(icon))
}
}
fn is_one_name(name: &str) -> bool {
!name.is_empty() && !name.contains('/')
}