ge_man/
path.rs

1use std::path::PathBuf;
2use std::{env, fs};
3
4use anyhow::Context;
5use ge_man_lib::tag::TagKind;
6#[cfg(test)]
7use mockall::automock;
8
9pub const STEAM_COMP_DIR: &str = "Steam/compatibilitytools.d";
10pub const LUTRIS_WINE_RUNNERS_DIR: &str = "lutris/runners/wine";
11
12const HOME: &str = "HOME";
13const XDG_DATA_HOME: &str = "XDG_DATA_HOME";
14const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
15const STEAM_PATH_ENV: &str = "STEAM_PATH";
16
17const APP_NAME: &str = "ge_man";
18
19pub fn xdg_data_home() -> Option<String> {
20    env::var(XDG_DATA_HOME).ok()
21}
22
23pub fn xdg_config_home() -> Option<String> {
24    env::var(XDG_CONFIG_HOME).ok()
25}
26
27pub fn steam_path() -> Option<String> {
28    env::var(STEAM_PATH_ENV).ok()
29}
30
31#[cfg_attr(test, automock)]
32pub trait PathConfiguration {
33    fn xdg_data_dir(&self, xdg_data_home: Option<String>) -> PathBuf {
34        let data_dir = xdg_data_home
35            .or_else(|| env::var(HOME).ok().map(|home| format!("{}/.local/share", home)))
36            .unwrap();
37
38        PathBuf::from(data_dir)
39    }
40
41    fn xdg_config_dir(&self, xdg_config_home: Option<String>) -> PathBuf {
42        let config_dir = xdg_config_home
43            .or_else(|| env::var(HOME).ok().map(|home| format!("{}/.config", home)))
44            .unwrap();
45
46        PathBuf::from(config_dir)
47    }
48
49    fn steam(&self, steam_root_path_override: Option<String>) -> PathBuf {
50        let steam_root_symlink = env::var(HOME)
51            .ok()
52            .map(|home| format!("{}/.steam/root", home))
53            .map(PathBuf::from)
54            .unwrap();
55
56        steam_root_path_override
57            .map(PathBuf::from)
58            .unwrap_or_else(|| steam_root_symlink)
59    }
60
61    fn lutris_local(&self, xdg_data_home: Option<String>) -> PathBuf {
62        self.xdg_data_dir(xdg_data_home).join("lutris")
63    }
64
65    fn lutris_config(&self, xdg_config_home: Option<String>) -> PathBuf {
66        self.xdg_config_dir(xdg_config_home).join("lutris")
67    }
68
69    fn steam_config(&self, steam_root_path_override: Option<String>) -> PathBuf {
70        self.steam(steam_root_path_override).join("config/config.vdf")
71    }
72
73    fn steam_compatibility_tools_dir(&self, steam_root_path_override: Option<String>) -> PathBuf {
74        self.steam(steam_root_path_override).join("compatibilitytools.d")
75    }
76
77    fn lutris_runners_config_dir(&self, xdg_config_home: Option<String>) -> PathBuf {
78        self.lutris_config(xdg_config_home).join("runners")
79    }
80
81    fn lutris_wine_runner_config(&self, xdg_config_home: Option<String>) -> PathBuf {
82        self.lutris_runners_config_dir(xdg_config_home).join("wine.yml")
83    }
84
85    fn lutris_runners_dir(&self, xdg_data_home: Option<String>) -> PathBuf {
86        self.lutris_local(xdg_data_home).join("runners/wine")
87    }
88
89    fn ge_man_data_dir(&self, xdg_data_home: Option<String>) -> PathBuf {
90        self.xdg_data_dir(xdg_data_home).join(APP_NAME)
91    }
92
93    fn ge_man_config_dir(&self, xdg_config_home: Option<String>) -> PathBuf {
94        self.xdg_config_dir(xdg_config_home).join(APP_NAME)
95    }
96
97    fn managed_versions_config(&self, xdg_data_home: Option<String>) -> PathBuf {
98        self.ge_man_data_dir(xdg_data_home).join("managed_versions.json")
99    }
100
101    fn app_config_backup_file(&self, xdg_config_home: Option<String>, kind: &TagKind) -> PathBuf {
102        let config_file = match kind {
103            TagKind::Proton => "steam-config-backup.vdf",
104            TagKind::Wine { .. } => "lutris-wine-runner-config-backup.yml",
105        };
106        self.ge_man_config_dir(xdg_config_home).join(config_file)
107    }
108
109    fn create_ge_man_dirs(&self, xdg_config_home: Option<String>, xdg_data_home: Option<String>) -> anyhow::Result<()> {
110        let ge_config_dir = self.ge_man_config_dir(xdg_config_home);
111        let ge_data_dir = self.ge_man_data_dir(xdg_data_home);
112
113        fs::create_dir_all(&ge_config_dir).context(format!(
114            r#"Failed to create directory "ge_man" in {}"#,
115            ge_config_dir.display()
116        ))?;
117        fs::create_dir_all(&ge_data_dir).context(format!(
118            r#"Failed to create directory "ge_man" in {}"#,
119            ge_data_dir.display()
120        ))?;
121
122        Ok(())
123    }
124
125    fn create_app_dirs(
126        &self,
127        xdg_config_home: Option<String>,
128        xdg_data_home: Option<String>,
129        steam_path: Option<String>,
130    ) -> anyhow::Result<()> {
131        let steam_compat_dir = self.steam_compatibility_tools_dir(steam_path);
132        let lutris_runners_cfg_dir = self.lutris_runners_config_dir(xdg_config_home);
133        let lutris_runners_dir = self.lutris_runners_dir(xdg_data_home);
134
135        fs::create_dir_all(&steam_compat_dir).context(format!(
136            r#"Failed to create directory "compatibilitytools.d" in {}"#,
137            steam_compat_dir.display()
138        ))?;
139        fs::create_dir_all(&lutris_runners_cfg_dir).context(format!(
140            r#"Failed to create directory "runners" in {}"#,
141            lutris_runners_cfg_dir.display()
142        ))?;
143        fs::create_dir_all(&lutris_runners_dir).context(format!(
144            r#"Failed to create directory "wine" in {}"#,
145            lutris_runners_dir.display()
146        ))?;
147
148        Ok(())
149    }
150}
151
152pub struct AppConfigPaths {
153    pub steam: PathBuf,
154    pub lutris: PathBuf,
155}
156
157impl AppConfigPaths {
158    pub fn new<P: Into<PathBuf>>(steam: P, lutris: P) -> Self {
159        AppConfigPaths {
160            steam: steam.into(),
161            lutris: lutris.into(),
162        }
163    }
164}
165
166impl<T: PathConfiguration> From<&T> for AppConfigPaths {
167    fn from(path_cfg: &T) -> Self {
168        AppConfigPaths::new(
169            path_cfg.steam_config(steam_path()),
170            path_cfg.lutris_wine_runner_config(xdg_config_home()),
171        )
172    }
173}
174
175pub struct PathConfig {}
176
177impl PathConfig {
178    pub fn new() -> Self {
179        PathConfig {}
180    }
181}
182
183impl Default for PathConfig {
184    fn default() -> Self {
185        PathConfig::new()
186    }
187}
188
189impl PathConfiguration for PathConfig {}
190
191#[cfg(test)]
192mod tests {
193    use std::path::PathBuf;
194
195    use assert_fs::prelude::{PathAssert, PathChild};
196    use assert_fs::TempDir;
197    use ge_man_lib::tag::TagKind;
198
199    use super::*;
200
201    #[test]
202    fn get_xdg_data_path_with_no_override() {
203        let path_cfg = PathConfig::default();
204        let path = path_cfg.xdg_data_dir(None);
205        assert!(path.to_string_lossy().contains("home"));
206        assert!(path.to_string_lossy().contains(".local/share"));
207    }
208
209    #[test]
210    fn get_xdg_data_path_with_override() {
211        let path_cfg = PathConfig::default();
212        let path = path_cfg.xdg_data_dir(Some(String::from("/tmp/xdg-data")));
213        assert_eq!(path, PathBuf::from("/tmp/xdg-data/"));
214    }
215
216    #[test]
217    fn steam_path_with_no_overrides() {
218        let path_cfg = PathConfig::default();
219        let path = path_cfg.steam(None);
220
221        assert!(path.to_string_lossy().contains("home"));
222        assert!(path.to_string_lossy().contains(".steam/root"));
223    }
224
225    #[test]
226    fn steam_path_with_steam_override() {
227        let path_cfg = PathConfig::default();
228        let path = path_cfg.steam(Some(String::from("/tmp/steam")));
229
230        assert_eq!(path, PathBuf::from("/tmp/steam"));
231    }
232
233    #[test]
234    fn lutris_local_path_with_no_override() {
235        let path_cfg = PathConfig::default();
236        let path = path_cfg.lutris_local(None);
237
238        assert!(path.to_string_lossy().contains("home"));
239        assert!(path.to_string_lossy().contains(".local/share/lutris"));
240    }
241
242    #[test]
243    fn lutris_local_path_with_xdg_data_override() {
244        let path_cfg = PathConfig::default();
245        let path = path_cfg.lutris_local(Some(String::from("/tmp/xdg-data")));
246
247        assert_eq!(path, PathBuf::from("/tmp/xdg-data/lutris"));
248    }
249
250    #[test]
251    fn lutris_config_path_with_no_override() {
252        let path_cfg = PathConfig::default();
253        let path = path_cfg.lutris_config(None);
254
255        assert!(path.to_string_lossy().contains("home"));
256        assert!(path.to_string_lossy().contains(".config/lutris"));
257    }
258
259    #[test]
260    fn lutris_config_path_with_xdg_config_override() {
261        let path_cfg = PathConfig::default();
262        let path = path_cfg.lutris_config(Some(String::from("/tmp/xdg-config")));
263
264        assert_eq!(path, PathBuf::from("/tmp/xdg-config/lutris"));
265    }
266
267    #[test]
268    fn steam_config_with_no_overrides() {
269        let path_cfg = PathConfig::default();
270        let path = path_cfg.steam_config(None);
271
272        assert!(path.to_string_lossy().contains("home"));
273        assert!(path.to_string_lossy().contains(".steam/root/config/config.vdf"));
274    }
275
276    #[test]
277    fn steam_config_with_steam_override() {
278        let path_cfg = PathConfig::default();
279        let path = path_cfg.steam_config(Some(String::from("/tmp/steam")));
280
281        assert_eq!(path, PathBuf::from("/tmp/steam/config/config.vdf"));
282    }
283
284    #[test]
285    fn steam_compatibilitytools_with_no_overrides() {
286        let path_cfg = PathConfig::default();
287        let path = path_cfg.steam_compatibility_tools_dir(None);
288
289        assert!(path.to_string_lossy().contains("home"));
290        assert!(path.to_string_lossy().contains(".steam/root/compatibilitytools.d"));
291    }
292
293    #[test]
294    fn steam_compatibilitytools_with_steam_override() {
295        let path_cfg = PathConfig::default();
296        let path = path_cfg.steam_compatibility_tools_dir(Some(String::from("/tmp/steam")));
297
298        assert_eq!(path, PathBuf::from("/tmp/steam/compatibilitytools.d"));
299    }
300
301    #[test]
302    fn lutris_wine_config_with_no_override() {
303        let path_cfg = PathConfig::default();
304        let path = path_cfg.lutris_wine_runner_config(None);
305
306        assert!(path.to_string_lossy().contains("home"));
307        assert!(path.to_string_lossy().contains(".config/lutris/runners/wine.yml"));
308    }
309
310    #[test]
311    fn lutris_wine_config_with_xdg_config_override() {
312        let path_cfg = PathConfig::default();
313        let path = path_cfg.lutris_wine_runner_config(Some(String::from("/tmp/xdg-config")));
314
315        assert_eq!(path, PathBuf::from("/tmp/xdg-config/lutris/runners/wine.yml"));
316    }
317
318    #[test]
319    fn lutris_runners_dir_with_no_override() {
320        let path_cfg = PathConfig::default();
321        let path = path_cfg.lutris_runners_dir(None);
322
323        assert!(path.to_string_lossy().contains("home"));
324        assert!(path.to_string_lossy().contains(".local/share/lutris/runners/wine"));
325    }
326
327    #[test]
328    fn lutris_runners_dir_with_xdg_data_override() {
329        let path_cfg = PathConfig::default();
330        let path = path_cfg.lutris_runners_dir(Some(String::from("/tmp/xdg-data")));
331
332        assert_eq!(path, PathBuf::from("/tmp/xdg-data/lutris/runners/wine"));
333    }
334
335    #[test]
336    fn ge_man_data_dir_with_no_override() {
337        let path_cfg = PathConfig::default();
338        let path = path_cfg.ge_man_data_dir(None);
339
340        assert!(path.to_string_lossy().contains("home"));
341        assert!(path.to_string_lossy().contains(".local/share/ge_man"));
342    }
343
344    #[test]
345    fn ge_man_data_dir_with_xdg_data_override() {
346        let path_cfg = PathConfig::default();
347        let path = path_cfg.ge_man_data_dir(Some(String::from("/tmp/xdg-data")));
348
349        assert_eq!(path, PathBuf::from("/tmp/xdg-data/ge_man"));
350    }
351
352    #[test]
353    fn ge_man_config_dir_with_no_override() {
354        let path_cfg = PathConfig::default();
355        let path = path_cfg.ge_man_config_dir(None);
356
357        assert!(path.to_string_lossy().contains("home"));
358        assert!(path.to_string_lossy().contains(".config/ge_man"));
359    }
360
361    #[test]
362    fn ge_man_config_dir_with_xdg_data_override() {
363        let path_cfg = PathConfig::default();
364        let path = path_cfg.ge_man_config_dir(Some(String::from("/tmp/xdg-config")));
365
366        assert_eq!(path, PathBuf::from("/tmp/xdg-config/ge_man"));
367    }
368
369    #[test]
370    fn ge_man_managed_versions_with_no_override() {
371        let path_cfg = PathConfig::default();
372        let path = path_cfg.managed_versions_config(None);
373
374        assert!(path.to_string_lossy().contains("home"));
375        assert!(path
376            .to_string_lossy()
377            .contains(".local/share/ge_man/managed_versions.json"));
378    }
379
380    #[test]
381    fn ge_man_managed_versions_with_xdg_config_override() {
382        let path_cfg = PathConfig::default();
383        let path = path_cfg.managed_versions_config(Some(String::from("/tmp/xdg-config")));
384
385        assert_eq!(path, PathBuf::from("/tmp/xdg-config/ge_man/managed_versions.json"));
386    }
387
388    #[test]
389    fn ge_man_backup_file_for_steam_with_no_override() {
390        let path_cfg = PathConfig::default();
391        let path = path_cfg.app_config_backup_file(None, &TagKind::Proton);
392
393        assert!(path.to_string_lossy().contains("home"));
394        assert!(path
395            .to_string_lossy()
396            .contains(".config/ge_man/steam-config-backup.vdf"));
397    }
398
399    #[test]
400    fn ge_man_backup_file_for_steam_with_xdg_config_override() {
401        let path_cfg = PathConfig::default();
402        let path = path_cfg.app_config_backup_file(Some(String::from("/tmp/xdg-config")), &TagKind::Proton);
403
404        assert_eq!(path, PathBuf::from("/tmp/xdg-config/ge_man/steam-config-backup.vdf"));
405    }
406
407    #[test]
408    fn ge_man_backup_file_for_lutris_with_no_override() {
409        let path_cfg = PathConfig::default();
410        let path = path_cfg.app_config_backup_file(None, &TagKind::wine());
411
412        assert!(path.to_string_lossy().contains("home"));
413        assert!(path
414            .to_string_lossy()
415            .contains(".config/ge_man/lutris-wine-runner-config-backup.yml"));
416    }
417
418    #[test]
419    fn ge_man_backup_file_for_lutris_with_xdg_config_override() {
420        let path_cfg = PathConfig::default();
421        let path = path_cfg.app_config_backup_file(Some(String::from("/tmp/xdg-config")), &TagKind::wine());
422
423        assert_eq!(
424            path,
425            PathBuf::from("/tmp/xdg-config/ge_man/lutris-wine-runner-config-backup.yml")
426        );
427    }
428
429    #[test]
430    fn create_ge_man_dirs_should_create_ge_man_directories() {
431        let tmp_dir = TempDir::new().unwrap();
432        let config_dir = tmp_dir.join("config");
433        let data_dir = tmp_dir.join("local");
434
435        let path_cfg = PathConfig::default();
436        path_cfg
437            .create_ge_man_dirs(
438                Some(config_dir.display().to_string()),
439                Some(data_dir.display().to_string()),
440            )
441            .unwrap();
442
443        tmp_dir.child("config/ge_man").assert(predicates::path::exists());
444        tmp_dir.child("local/ge_man").assert(predicates::path::exists());
445
446        tmp_dir.close().unwrap();
447    }
448
449    #[test]
450    fn create_app_dirs_should_create_steam_and_lutris_directories() {
451        let tmp_dir = TempDir::new().unwrap();
452        let steam_dir = tmp_dir.join("local/Steam");
453        let config_dir = tmp_dir.join("config");
454        let data_dir = tmp_dir.join("local");
455
456        let path_cfg = PathConfig::default();
457        path_cfg
458            .create_app_dirs(
459                Some(config_dir.display().to_string()),
460                Some(data_dir.display().to_string()),
461                Some(steam_dir.display().to_string()),
462            )
463            .unwrap();
464
465        tmp_dir
466            .child("local/Steam/compatibilitytools.d")
467            .assert(predicates::path::exists());
468        tmp_dir
469            .child("config/lutris/runners")
470            .assert(predicates::path::exists());
471        tmp_dir
472            .child("local/lutris/runners/wine")
473            .assert(predicates::path::exists());
474
475        tmp_dir.close().unwrap();
476    }
477}