1use std::path::PathBuf;
2use std::process::Command;
3
4use anyhow::Result;
5
6use crate::config::{XdgDirConfig, XdgDirValue};
7
8pub struct ResolvedXdgDir {
9 pub path: PathBuf,
10 pub read_write: bool,
11}
12
13pub struct ResolvedXdgDirs {
14 pub documents: Option<ResolvedXdgDir>,
15 pub downloads: Option<ResolvedXdgDir>,
16 pub pictures: Option<ResolvedXdgDir>,
17 pub music: Option<ResolvedXdgDir>,
18 pub videos: Option<ResolvedXdgDir>,
19 pub desktop: Option<ResolvedXdgDir>,
20 pub projects: Option<ResolvedXdgDir>,
21}
22
23pub fn resolve(config: &XdgDirConfig) -> Result<ResolvedXdgDirs> {
31 Ok(ResolvedXdgDirs {
32 documents: resolve_dir(&config.documents, "DOCUMENTS", "Documents"),
33 downloads: resolve_dir(&config.downloads, "DOWNLOADS", "Downloads"),
34 pictures: resolve_dir(&config.pictures, "PICTURES", "Pictures"),
35 music: resolve_dir(&config.music, "MUSIC", "Music"),
36 videos: resolve_dir(&config.videos, "VIDEOS", "Videos"),
37 desktop: resolve_dir(&config.desktop, "DESKTOP", "Desktop"),
38 projects: resolve_dir(&config.projects, "PROJECTS", "Projects"),
39 })
40}
41
42fn resolve_dir(value: &XdgDirValue, xdg_name: &str, fallback_name: &str) -> Option<ResolvedXdgDir> {
43 if !value.is_enabled() {
44 return None;
45 }
46 let read_write = value.is_read_write();
47
48 if let Ok(output) = Command::new("xdg-user-dir").arg(xdg_name).output() {
50 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
51 if !path.is_empty() && path != home_dir_str() {
52 let p = PathBuf::from(path);
53 if p.exists() {
54 return Some(ResolvedXdgDir {
55 path: p,
56 read_write,
57 });
58 }
59 }
60 }
61
62 if let Some(home) = dirs::home_dir() {
64 let p = home.join(fallback_name);
65 if p.exists() {
66 return Some(ResolvedXdgDir {
67 path: p,
68 read_write,
69 });
70 }
71 }
72
73 None
74}
75
76fn home_dir_str() -> String {
77 dirs::home_dir()
78 .map(|p| p.to_string_lossy().to_string())
79 .unwrap_or_default()
80}