platform_dirs/
lib.rs

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            // TODO document why we need to check data_dir and data_local_dir
61            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}