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}