1use std::env;
2use std::path::{Path, PathBuf};
3
4use dirs_next::home_dir;
5
6#[derive(Clone, Debug)]
7pub struct AppDirs {
8 pub cache_dir: PathBuf,
9 pub config_dir: PathBuf,
10 pub data_dir: PathBuf,
11 pub state_dir: PathBuf,
12}
13
14#[derive(Clone, Debug)]
15pub struct UserDirs {
16 pub desktop_dir: PathBuf,
17 pub document_dir: PathBuf,
18 pub download_dir: PathBuf,
19 pub music_dir: PathBuf,
20 pub picture_dir: PathBuf,
21 pub public_dir: PathBuf,
22 pub video_dir: PathBuf,
23}
24
25fn is_absolute_path(path: impl AsRef<Path>) -> Option<PathBuf> {
26 let path = path.as_ref();
27
28 if path.is_absolute() {
29 Some(path.to_path_buf())
30 } else {
31 None
32 }
33}
34
35impl AppDirs {
36 pub fn new(name: Option<&str>, use_xdg_on_macos: bool) -> Option<Self> {
37 if cfg!(target_os = "macos") && !use_xdg_on_macos {
38 if home_dir().is_some() {
39 let mut cache_dir = dirs_next::cache_dir().expect("home directory is set");
40 let mut data_dir = dirs_next::data_dir().expect("home directory is set");
41
42 if let Some(name) = name {
43 cache_dir.push(&name);
44 data_dir.push(&name);
45 }
46
47 let config_dir = data_dir.clone();
48 let state_dir = data_dir.clone();
49
50 Some(AppDirs {
51 cache_dir,
52 config_dir,
53 data_dir,
54 state_dir,
55 })
56 } else {
57 None
58 }
59 } else if cfg!(target_os = "windows") {
60 if let (Some(_home_dir), Some(data_dir), Some(data_local_dir)) = (
62 home_dir(),
63 dirs_next::data_dir(),
64 dirs_next::data_local_dir(),
65 ) {
66 let mut cache_dir = data_local_dir.clone();
67 let mut config_dir = data_dir.clone();
68 let mut data_dir = data_local_dir.clone();
69
70 if let Some(name) = name {
71 cache_dir.push(&name);
72 config_dir.push(&name);
73 data_dir.push(&name);
74 }
75
76 let state_dir = data_dir.clone();
77
78 Some(AppDirs {
79 cache_dir,
80 config_dir,
81 data_dir,
82 state_dir,
83 })
84 } else {
85 None
86 }
87 } else if let Some(home_dir) = home_dir() {
88 let mut cache_dir = env::var_os("XDG_CACHE_HOME")
89 .and_then(is_absolute_path)
90 .unwrap_or_else(|| home_dir.join(".cache"));
91 let mut config_dir = env::var_os("XDG_CONFIG_HOME")
92 .and_then(is_absolute_path)
93 .unwrap_or_else(|| home_dir.join(".config"));
94 let mut data_dir = env::var_os("XDG_DATA_HOME")
95 .and_then(is_absolute_path)
96 .unwrap_or_else(|| home_dir.join(".local/share"));
97 let mut state_dir = env::var_os("XDG_STATE_HOME")
98 .and_then(is_absolute_path)
99 .unwrap_or_else(|| home_dir.join(".local/state"));
100
101 if let Some(name) = name {
102 cache_dir.push(&name);
103 config_dir.push(&name);
104 data_dir.push(&name);
105 state_dir.push(&name);
106 }
107
108 Some(AppDirs {
109 cache_dir,
110 config_dir,
111 data_dir,
112 state_dir,
113 })
114 } else {
115 None
116 }
117 }
118}
119
120impl UserDirs {
121 pub fn new() -> Option<Self> {
122 if let Some(home_dir) = home_dir() {
123 Some(UserDirs {
124 desktop_dir: dirs_next::desktop_dir().unwrap_or_else(|| home_dir.join("Desktop")),
125 document_dir: dirs_next::document_dir()
126 .unwrap_or_else(|| home_dir.join("Documents")),
127 download_dir: dirs_next::download_dir()
128 .unwrap_or_else(|| home_dir.join("Downloads")),
129 music_dir: dirs_next::audio_dir().unwrap_or_else(|| home_dir.join("Music")),
130 picture_dir: dirs_next::picture_dir().unwrap_or_else(|| home_dir.join("Pictures")),
131 public_dir: dirs_next::public_dir().unwrap_or_else(|| home_dir.join("Public")),
132 video_dir: dirs_next::video_dir().unwrap_or_else(|| {
133 if cfg!(target_os = "macos") {
134 home_dir.join("Movies")
135 } else {
136 home_dir.join("Videos")
137 }
138 }),
139 })
140 } else {
141 None
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn test_xdg() {
151 let home_dir = home_dir().unwrap();
152 let config_env = env::var_os("XDG_CONFIG_HOME");
153
154 env::set_var("XDG_CONFIG_HOME", "");
155 let app_dirs = AppDirs::new(None, AppUI::CommandLine).unwrap();
156 assert!(app_dirs.config_dir == home_dir.join(".config"));
157
158 env::set_var("XDG_CONFIG_HOME", "/home/cjbassi/foo");
159 let app_dirs = AppDirs::new(Some("bar"), AppUI::CommandLine).unwrap();
160 assert!(app_dirs.config_dir == home_dir.join("/home/cjbassi/foo/bar"));
161
162 if let Some(config_env) = config_env {
163 env::set_var("XDG_CONFIG_HOME", config_env);
164 }
165 }
166
167 #[test]
168 fn test_config_dir() {
169 if cfg!(target_os = "macos") {
170 let home_dir = home_dir().unwrap();
171 let app_dirs = AppDirs::new(Some("foo"), AppUI::Graphical).unwrap();
172 assert_eq!(
173 app_dirs.config_dir,
174 home_dir
175 .join("Library")
176 .join("Application Support")
177 .join("foo"),
178 );
179 test_xdg();
180 } else if cfg!(target_os = "windows") {
181 let home_dir = home_dir().unwrap();
182 let app_dirs = AppDirs::new(Some("foo"), AppUI::Graphical).unwrap();
183 assert_eq!(
184 app_dirs.config_dir,
185 home_dir.join("AppData").join("Roaming").join("foo")
186 );
187 } else {
188 test_xdg();
189 }
190 }
191
192 #[test]
193 fn test_music_dir() {
194 let music_dir = home_dir().unwrap().join("Music");
195 let user_dirs = UserDirs::new().unwrap();
196 assert!(user_dirs.music_dir == music_dir);
197 }
198}