1use std::io::Read;
2use std::os::unix::ffi::OsStrExt;
3use std::path::Path;
4use std::{fs, io};
5
6use anyhow::{anyhow, bail, Context};
7use ge_man_lib::archive;
8use ge_man_lib::config::{LutrisConfig, SteamConfig};
9use ge_man_lib::tag::TagKind;
10#[cfg(test)]
11use mockall::{automock, predicate::*};
12
13use crate::data::ManagedVersion;
14use crate::path::{
15 steam_path, xdg_config_home, xdg_data_home, PathConfiguration, LUTRIS_WINE_RUNNERS_DIR, STEAM_COMP_DIR,
16};
17use crate::version::{Version, Versioned};
18
19const USER_SETTINGS_PY: &str = "user_settings.py";
20const LUTRIS_INITIAL_WINE_RUNNER_CONFIG: &str = r#"
21wine:
22 version: VERSION
23"#;
24
25#[cfg_attr(test, automock)]
26pub trait FilesystemManager {
27 fn setup_version(&self, version: Version, compressed_tar: Box<dyn Read>) -> anyhow::Result<ManagedVersion>;
28 fn remove_version(&self, version: &ManagedVersion) -> anyhow::Result<()>;
29 fn migrate_folder(&self, version: Version, source_path: &Path) -> anyhow::Result<ManagedVersion>;
30 fn apply_to_app_config(&self, version: &ManagedVersion) -> anyhow::Result<()>;
31 fn copy_user_settings(&self, src_version: &ManagedVersion, dst_version: &ManagedVersion) -> anyhow::Result<()>;
32}
33
34pub struct FsMng<'a> {
37 path_config: &'a dyn PathConfiguration,
38}
39
40impl<'a> FsMng<'a> {
41 pub fn new(path_config: &'a dyn PathConfiguration) -> Self {
42 FsMng { path_config }
43 }
44
45 fn copy_directory(&self, src: &Path, dst: &Path) -> anyhow::Result<()> {
46 fs::create_dir_all(dst).unwrap();
47 for entry in src.read_dir()? {
48 let dir_entry = entry?;
49 let dst = dst.join(dir_entry.file_name());
50
51 if dir_entry.path().is_dir() {
52 self.copy_directory(&dir_entry.path(), &dst)?;
53 } else {
54 fs::copy(&dir_entry.path(), &dst)?;
55 }
56 }
57
58 Ok(())
59 }
60
61 fn move_or_copy_directory(&self, version: &ManagedVersion, src_path: &Path) -> anyhow::Result<()> {
62 let dst_path = match version.kind() {
63 TagKind::Proton => self.path_config.steam_compatibility_tools_dir(steam_path()),
64 TagKind::Wine { .. } => self.path_config.lutris_runners_dir(xdg_data_home()),
65 };
66 let dst_path = dst_path.join(version.directory_name());
67
68 if let Err(err) = fs::rename(src_path, &dst_path) {
71 match err.raw_os_error() {
72 Some(18) => {
75 self.copy_directory(src_path, &dst_path).context(format!(
76 "Failed to copy source to destination.\n\
77 Source: {}\n\
78 Destination: {}\n",
79 src_path.display(),
80 dst_path.display(),
81 ))?;
82 }
83 _ => bail!(err),
84 }
85 }
86
87 Ok(())
88 }
89}
90
91impl<'a> FilesystemManager for FsMng<'a> {
92 fn setup_version(&self, version: Version, compressed_tar: Box<dyn Read>) -> anyhow::Result<ManagedVersion> {
93 let dst_path = match version.kind() {
94 TagKind::Proton => self.path_config.steam_compatibility_tools_dir(steam_path()),
95 TagKind::Wine { .. } => self.path_config.lutris_runners_dir(xdg_data_home()),
96 };
97 let extracted_location = archive::extract_compressed(version.kind(), compressed_tar, &dst_path)
98 .context("Failed to extract compressed archive")?;
99
100 let directory_name = String::from_utf8_lossy(extracted_location.file_name().unwrap().as_bytes()).into_owned();
101
102 let mut version = ManagedVersion::from(version);
103 version.set_directory_name(directory_name);
104
105 Ok(version)
106 }
107
108 fn remove_version(&self, version: &ManagedVersion) -> anyhow::Result<()> {
109 let path = match version.kind() {
110 TagKind::Proton => self.path_config.steam_compatibility_tools_dir(steam_path()),
111 TagKind::Wine { .. } => self.path_config.lutris_runners_dir(xdg_data_home()),
112 };
113 let path = path.join(version.directory_name());
114
115 fs::remove_dir_all(&path).context(format!("Could not remove directory '{}'", path.display()))
116 }
117
118 fn migrate_folder(&self, version: Version, source_path: &Path) -> anyhow::Result<ManagedVersion> {
119 let mut managed_version = ManagedVersion::from(version);
120 let dir_name = format!("GEH_{}_{}", managed_version.kind(), managed_version.tag());
121 managed_version.set_directory_name(dir_name);
122
123 match source_path.parent() {
124 Some(parent) => {
125 if parent.ends_with(STEAM_COMP_DIR) || parent.ends_with(LUTRIS_WINE_RUNNERS_DIR) {
126 managed_version
127 .set_directory_name(String::from_utf8_lossy(source_path.file_name().unwrap().as_bytes()));
128 return Ok(managed_version);
129 } else {
130 self.move_or_copy_directory(&managed_version, source_path)?;
131 }
132 }
133 None => self.move_or_copy_directory(&managed_version, source_path)?,
134 }
135
136 Ok(managed_version)
137 }
138
139 fn apply_to_app_config(&self, version: &ManagedVersion) -> anyhow::Result<()> {
140 match version.kind() {
141 TagKind::Proton => {
142 let steam_cfg_path = self.path_config.steam_config(steam_path());
143 let backup_path = self
144 .path_config
145 .app_config_backup_file(xdg_config_home(), version.kind());
146
147 fs::copy(&steam_cfg_path, &backup_path).context(format!(
148 r#"Could not create backup of Steam config from "{}" to "{}" "#,
149 steam_cfg_path.display(),
150 backup_path.display()
151 ))?;
152
153 let mut config = SteamConfig::create_copy(&steam_cfg_path)?;
154 config.set_proton_version(version.directory_name());
155
156 let new_config: Vec<u8> = config.into();
157 fs::write(steam_cfg_path, new_config)?;
158 }
159 TagKind::Wine { .. } => {
160 let runner_cfg_path = self.path_config.lutris_wine_runner_config(xdg_config_home());
161 let backup_path = self
162 .path_config
163 .app_config_backup_file(xdg_config_home(), version.kind());
164
165 let copy_result = fs::copy(&runner_cfg_path, &backup_path);
166
167 if let Err(io_err) = copy_result {
168 if let io::ErrorKind::NotFound = io_err.kind() {
169 fs::write(
170 runner_cfg_path,
171 LUTRIS_INITIAL_WINE_RUNNER_CONFIG.replace("VERSION", version.directory_name()),
172 )
173 .context("Failed to create initial Wine runner configuration for Lutris")?;
174 } else {
175 return Err(anyhow!(io_err)).context(format!(
176 r#"Could not create backup of Wine runner config from "{}" to "{}""#,
177 runner_cfg_path.display(),
178 backup_path.display()
179 ));
180 }
181 } else {
182 let mut config = LutrisConfig::create_copy(&runner_cfg_path)?;
183 config.set_wine_version(version.directory_name());
184
185 let new_config: Vec<u8> = config.into();
186 fs::write(runner_cfg_path, new_config)?;
187 };
188 }
189 }
190
191 Ok(())
192 }
193
194 fn copy_user_settings(&self, src_version: &ManagedVersion, dst_version: &ManagedVersion) -> anyhow::Result<()> {
195 let src_path = self
196 .path_config
197 .steam_compatibility_tools_dir(steam_path())
198 .join(src_version.directory_name())
199 .join(USER_SETTINGS_PY);
200 let dst_path = self
201 .path_config
202 .steam_compatibility_tools_dir(steam_path())
203 .join(dst_version.directory_name())
204 .join(USER_SETTINGS_PY);
205
206 fs::copy(src_path, dst_path).context(format!(
207 "Could not copy user_settings.py from {} to {}",
208 src_version, dst_version
209 ))?;
210 Ok(())
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use std::fs::File;
217 use std::io::BufReader;
218 use std::path::PathBuf;
219
220 use assert_fs::prelude::{PathAssert, PathChild};
221 use assert_fs::TempDir;
222 use ge_man_lib::tag::Tag;
223
224 use super::*;
225
226 struct MockPathConfig {
227 pub tmp_dir: PathBuf,
228 }
229
230 impl MockPathConfig {
231 pub fn new(tmp_dir: PathBuf) -> Self {
232 MockPathConfig { tmp_dir }
233 }
234 }
235
236 impl PathConfiguration for MockPathConfig {
237 fn xdg_data_dir(&self, _xdg_data_path: Option<String>) -> PathBuf {
238 self.tmp_dir.join(".local/share")
239 }
240
241 fn xdg_config_dir(&self, _xdg_config_path: Option<String>) -> PathBuf {
242 self.tmp_dir.join(".config")
243 }
244
245 fn steam(&self, _steam_root_path_override: Option<String>) -> PathBuf {
246 self.tmp_dir.join(".steam/root")
247 }
248 }
249
250 #[test]
251 fn setup_proton_version() {
252 let tag = String::from("6.20-GE-1");
253 let kind = TagKind::Proton;
254 let dir_name = "Proton-6.20-GE-1";
255
256 let tmp_dir = TempDir::new().unwrap();
257 let path_config = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
258 fs::create_dir_all(path_config.steam_compatibility_tools_dir(None)).unwrap();
259
260 let fs_manager = FsMng::new(&path_config);
261
262 let compressed_tar = BufReader::new(File::open("test_resources/assets/Proton-6.20-GE-1.tar.gz").unwrap());
263 let version = Version::new(tag.clone(), kind.clone());
264 let managed_version = fs_manager.setup_version(version, Box::new(compressed_tar)).unwrap();
265
266 assert_eq!(managed_version.tag(), &Tag::from(tag));
267 assert_eq!(managed_version.kind(), &kind);
268 assert_eq!(managed_version.directory_name(), &dir_name);
269 tmp_dir
270 .child(".steam/root/compatibilitytools.d")
271 .child(&dir_name)
272 .assert(predicates::path::exists());
273
274 drop(fs_manager);
275 tmp_dir.close().unwrap();
276 }
277
278 #[test]
279 fn setup_wine_version() {
280 let tag = String::from("6.20-GE-1");
281 let kind = TagKind::wine();
282 let dir_name = "Wine-6.20-GE-1";
283
284 let tmp_dir = TempDir::new().unwrap();
285 let path_config = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
286 fs::create_dir_all(path_config.lutris_runners_dir(None)).unwrap();
287
288 let fs_manager = FsMng::new(&path_config);
289
290 let compressed_tar = BufReader::new(File::open("test_resources/assets/Wine-6.20-GE-1.tar.xz").unwrap());
291 let version = Version::new(tag.clone(), kind.clone());
292 let managed_version = fs_manager.setup_version(version, Box::new(compressed_tar)).unwrap();
293
294 assert_eq!(managed_version.tag(), &Tag::from(tag));
295 assert_eq!(managed_version.kind(), &kind);
296 assert_eq!(managed_version.directory_name(), &dir_name);
297 tmp_dir
298 .child(".local/share/lutris/runners/wine")
299 .child(&dir_name)
300 .assert(predicates::path::exists());
301
302 drop(fs_manager);
303 tmp_dir.close().unwrap();
304 }
305
306 #[test]
307 fn setup_wine_lol_version() {
308 let tag = String::from("6.20-GE-1");
309 let kind = TagKind::lol();
310 let dir_name = "Wine-6.20-GE-1-LoL";
311
312 let tmp_dir = TempDir::new().unwrap();
313 let path_config = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
314 fs::create_dir_all(path_config.lutris_runners_dir(None)).unwrap();
315
316 let fs_manager = FsMng::new(&path_config);
317
318 let compressed_tar = BufReader::new(File::open("test_resources/assets/Wine-6.20-GE-1-LoL.tar.xz").unwrap());
319 let version = Version::new(tag.clone(), kind.clone());
320 let managed_version = fs_manager.setup_version(version, Box::new(compressed_tar)).unwrap();
321
322 assert_eq!(managed_version.tag(), &Tag::from(tag));
323 assert_eq!(managed_version.kind(), &kind);
324 assert_eq!(managed_version.directory_name(), &dir_name);
325 tmp_dir
326 .child(".local/share/lutris/runners/wine")
327 .child(&dir_name)
328 .assert(predicates::path::exists());
329
330 drop(fs_manager);
331 tmp_dir.close().unwrap();
332 }
333
334 #[test]
335 fn remove_proton_version() {
336 let tag = String::from("6.20-GE-1");
337 let dir_name = String::from("Proton-6.20-GE-1");
338 let kind = TagKind::Proton;
339
340 let tmp_dir = TempDir::new().unwrap();
341 let path_config = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
342 std::fs::create_dir_all(path_config.steam_compatibility_tools_dir(None).join(&dir_name)).unwrap();
343
344 let fs_manager = FsMng::new(&path_config);
345
346 let version = ManagedVersion::new(Tag::from(tag), kind, dir_name.clone());
347 fs_manager.remove_version(&version).unwrap();
348
349 tmp_dir
350 .child(".local/share/game-compatibility-manager/versions/proton-ge")
351 .child(&dir_name)
352 .assert(predicates::path::missing());
353
354 drop(fs_manager);
355 tmp_dir.close().unwrap();
356 }
357
358 #[test]
359 fn remove_wine_version() {
360 let tag = String::from("6.20-GE-1");
361 let dir_name = String::from("Wine-6.20-GE-1");
362 let kind = TagKind::wine();
363
364 let tmp_dir = TempDir::new().unwrap();
365 let path_config = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
366 std::fs::create_dir_all(path_config.lutris_runners_dir(None).join(&dir_name)).unwrap();
367
368 let fs_manager = FsMng::new(&path_config);
369
370 let version = ManagedVersion::new(Tag::from(tag), kind, dir_name.clone());
371 fs_manager.remove_version(&version).unwrap();
372
373 tmp_dir
374 .child(".local/share/game-compatibility-manager/versions/wine-ge")
375 .child(&dir_name)
376 .assert(predicates::path::missing());
377
378 drop(fs_manager);
379 tmp_dir.close().unwrap();
380 }
381
382 #[test]
383 fn remove_wine_lol_version() {
384 let tag = String::from("6.20-GE-1-LoL");
385 let dir_name = String::from("Wine-6.20-GE-1-LoL");
386 let kind = TagKind::lol();
387
388 let tmp_dir = TempDir::new().unwrap();
389 let path_config = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
390 std::fs::create_dir_all(path_config.lutris_runners_dir(None).join(&dir_name)).unwrap();
391
392 let fs_manager = FsMng::new(&path_config);
393
394 let version = ManagedVersion::new(Tag::from(tag), kind, dir_name.clone());
395 fs_manager.remove_version(&version).unwrap();
396
397 tmp_dir
398 .child(".local/share/game-compatibility-manager/versions/wine-ge")
399 .child(&dir_name)
400 .assert(predicates::path::missing());
401
402 drop(fs_manager);
403 tmp_dir.close().unwrap();
404 }
405
406 #[test]
407 fn migrate_proton_version_in_steam_dir() {
408 let tmp_dir = TempDir::new().unwrap();
409 let version = Version::new("6.20-GE-1", TagKind::Proton);
410 let source_path = PathBuf::from(tmp_dir.join(".local/share/Steam/compatibilitytools.d/Proton-6.20-GE-1"));
411 fs::create_dir_all(&source_path).unwrap();
412
413 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
414 let fs_mng = FsMng::new(&path_cfg);
415
416 let version = fs_mng.migrate_folder(version, &source_path).unwrap();
417 assert_eq!(version.tag(), &Tag::from("6.20-GE-1"));
418 assert_eq!(version.kind(), &TagKind::Proton);
419 assert_eq!(version.directory_name(), &String::from("Proton-6.20-GE-1"));
420
421 tmp_dir
422 .child(".local/share/Steam/compatibilitytools.d/GEH_PROTON_6.20-GE-1")
423 .assert(predicates::path::missing());
424 tmp_dir
425 .child(".local/share/Steam/compatibilitytools.d/Proton-6.20-GE-1")
426 .assert(predicates::path::exists());
427
428 drop(fs_mng);
429 tmp_dir.close().unwrap();
430 }
431
432 #[test]
433 fn migrate_proton_version_present_in_random_directory() {
434 let tmp_dir = TempDir::new().unwrap();
435 let source_path = PathBuf::from(tmp_dir.join("some/dir/Proton-6.20-GE-1"));
436 let version = Version::new("6.20-GE-1", TagKind::Proton);
437 fs::create_dir_all(&source_path).unwrap();
438 fs::create_dir_all(tmp_dir.join(".steam/root/compatibilitytools.d")).unwrap();
439
440 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
441 let fs_mng = FsMng::new(&path_cfg);
442
443 let version = fs_mng.migrate_folder(version, &source_path).unwrap();
444 assert_eq!(version.tag(), &Tag::from("6.20-GE-1"));
445 assert_eq!(version.kind(), &TagKind::Proton);
446 assert_eq!(version.directory_name(), &String::from("GEH_PROTON_6.20-GE-1"));
447
448 tmp_dir
449 .child(".steam/root/compatibilitytools.d/Proton-6.20-GE-1")
450 .assert(predicates::path::missing());
451 tmp_dir
452 .child(".steam/root/compatibilitytools.d/GEH_PROTON_6.20-GE-1")
453 .assert(predicates::path::exists());
454
455 drop(fs_mng);
456 tmp_dir.close().unwrap();
457 }
458
459 #[test]
460 fn migrate_wine_version_in_lutris_directory() {
461 let tmp_dir = TempDir::new().unwrap();
462 let source_path = PathBuf::from(tmp_dir.join(".local/share/lutris/runners/wine/Wine-6.20-GE-1"));
463 let version = Version::new("6.20-GE-1", TagKind::wine());
464 fs::create_dir_all(&source_path).unwrap();
465 fs::create_dir_all(tmp_dir.join(".local/share/lutris/runners/wine")).unwrap();
466
467 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
468 let fs_mng = FsMng::new(&path_cfg);
469
470 let version = fs_mng.migrate_folder(version, &source_path).unwrap();
471 assert_eq!(version.tag(), &Tag::from("6.20-GE-1"));
472 assert_eq!(version.kind(), &TagKind::wine());
473 assert_eq!(version.directory_name(), &String::from("Wine-6.20-GE-1"));
474
475 tmp_dir
476 .child(".local/share/lutris/runners/wine/GEH_Wine_6.20-GE-1")
477 .assert(predicates::path::missing());
478 tmp_dir
479 .child(".local/share/lutris/runners/wine/Wine-6.20-GE-1")
480 .assert(predicates::path::exists());
481
482 drop(fs_mng);
483 tmp_dir.close().unwrap();
484 }
485
486 #[test]
487 fn migrate_wine_version_in_random_directory() {
488 let tmp_dir = TempDir::new().unwrap();
489 let source_path = PathBuf::from(tmp_dir.join("some/dir/Wine-6.20-GE-1"));
490 let version = Version::new("6.20-GE-1", TagKind::wine());
491 fs::create_dir_all(&source_path).unwrap();
492 fs::create_dir_all(tmp_dir.join(".local/share/lutris/runners/wine")).unwrap();
493
494 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
495 let fs_mng = FsMng::new(&path_cfg);
496
497 let version = fs_mng.migrate_folder(version, &source_path).unwrap();
498 assert_eq!(version.tag(), &Tag::from("6.20-GE-1"));
499 assert_eq!(version.kind(), &TagKind::wine());
500 assert_eq!(version.directory_name(), &String::from("GEH_WINE_6.20-GE-1"));
501
502 tmp_dir
503 .child(".local/share/lutris/runners/wine/Wine-6.20-GE-1")
504 .assert(predicates::path::missing());
505 tmp_dir
506 .child(".local/share/lutris/runners/wine/GEH_WINE_6.20-GE-1")
507 .assert(predicates::path::exists());
508
509 drop(fs_mng);
510 tmp_dir.close().unwrap();
511 }
512
513 #[test]
514 fn migrate_lol_version_in_lutris_directory() {
515 let tmp_dir = TempDir::new().unwrap();
516 let source_path = PathBuf::from(tmp_dir.join(".local/share/lutris/runners/wine/Wine-LoL-6.20-GE-1"));
517 let version = Version::new("6.20-GE-1", TagKind::lol());
518 fs::create_dir_all(&source_path).unwrap();
519 fs::create_dir_all(tmp_dir.join(".local/share/lutris/runners/wine")).unwrap();
520
521 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
522 let fs_mng = FsMng::new(&path_cfg);
523
524 let version = fs_mng.migrate_folder(version, &source_path).unwrap();
525 assert_eq!(version.tag(), &Tag::from("6.20-GE-1"));
526 assert_eq!(version.kind(), &TagKind::lol());
527 assert_eq!(version.directory_name(), &String::from("Wine-LoL-6.20-GE-1"));
528
529 tmp_dir
530 .child(".local/share/lutris/runners/wine/GEH_LoL_Wine_6.20-GE-1")
531 .assert(predicates::path::missing());
532 tmp_dir
533 .child(".local/share/lutris/runners/wine/Wine-LoL-6.20-GE-1")
534 .assert(predicates::path::exists());
535
536 drop(fs_mng);
537 tmp_dir.close().unwrap();
538 }
539
540 #[test]
541 fn migrate_lol_version_in_random_directory() {
542 let tmp_dir = TempDir::new().unwrap();
543 let source_path = PathBuf::from(tmp_dir.join("some/dir/Wine-LoL-6.20-GE-1"));
544 let version = Version::new("6.20-GE-1", TagKind::lol());
545 fs::create_dir_all(&source_path).unwrap();
546 fs::create_dir_all(tmp_dir.join(".local/share/lutris/runners/wine")).unwrap();
547
548 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
549 let fs_mng = FsMng::new(&path_cfg);
550
551 let version = fs_mng.migrate_folder(version, &source_path).unwrap();
552 assert_eq!(version.tag(), &Tag::from("6.20-GE-1"));
553 assert_eq!(version.kind(), &TagKind::lol());
554 assert_eq!(version.directory_name(), &String::from("GEH_LOL_WINE_6.20-GE-1"));
555
556 tmp_dir
557 .child(".local/share/lutris/runners/wine/Wine-LoL-6.20-GE-1")
558 .assert(predicates::path::missing());
559 tmp_dir
560 .child(".local/share/lutris/runners/wine/GEH_LOL_WINE_6.20-GE-1")
561 .assert(predicates::path::exists());
562
563 drop(fs_mng);
564 tmp_dir.close().unwrap();
565 }
566
567 #[test]
568 fn apply_proton_ge_version_to_steam_config() {
569 let tmp_dir = TempDir::new().unwrap();
570 let steam_cfg_dir = tmp_dir.join(".steam/root/config");
571 let steam_cfg_file = steam_cfg_dir.join("config.vdf");
572 let proton_dir_name = "Proton-6.20-GE-1";
573 fs::create_dir_all(&steam_cfg_dir).unwrap();
574 fs::copy("test_resources/assets/config.vdf", &steam_cfg_file).unwrap();
575
576 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
577 fs::create_dir_all(
578 path_cfg
579 .app_config_backup_file(None, &TagKind::Proton)
580 .parent()
581 .unwrap(),
582 )
583 .unwrap();
584 let fs_mng = FsMng::new(&path_cfg);
585
586 let version = ManagedVersion::new("6.20-GE-1", TagKind::Proton, proton_dir_name);
587 fs_mng.apply_to_app_config(&version).unwrap();
588
589 let modified_config = SteamConfig::create_copy(&steam_cfg_file).unwrap();
590 assert_eq!(modified_config.proton_version(), proton_dir_name);
591
592 tmp_dir
593 .child(path_cfg.app_config_backup_file(None, &TagKind::Proton))
594 .assert(predicates::path::exists());
595
596 drop(fs_mng);
597 tmp_dir.close().unwrap();
598 }
599
600 #[test]
601 fn apply_wine_ge_version_to_lutris_config_when_runner_config_already_exists() {
602 let tmp_dir = TempDir::new().unwrap();
603 let cfg_dir = tmp_dir.join(".config/lutris/runners");
604 let cfg_file = cfg_dir.join("wine.yml");
605 let dir_name = "Wine-6.20-GE-1";
606 fs::create_dir_all(&cfg_dir).unwrap();
607 fs::copy("test_resources/assets/wine.yml", &cfg_file).unwrap();
608
609 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
610 fs::create_dir_all(
611 path_cfg
612 .app_config_backup_file(None, &TagKind::wine())
613 .parent()
614 .unwrap(),
615 )
616 .unwrap();
617 let fs_mng = FsMng::new(&path_cfg);
618
619 let version = ManagedVersion::new("6.20-GE-1", TagKind::wine(), dir_name);
620 fs_mng.apply_to_app_config(&version).unwrap();
621
622 let modified_config = LutrisConfig::create_copy(&cfg_file).unwrap();
623 assert_eq!(modified_config.wine_version(), dir_name);
624
625 tmp_dir
626 .child(path_cfg.app_config_backup_file(None, &TagKind::wine()))
627 .assert(predicates::path::exists());
628
629 drop(fs_mng);
630 tmp_dir.close().unwrap();
631 }
632
633 #[test]
634 fn apply_wine_ge_version_to_lutris_config_when_no_runner_config_exists() {
635 let tmp_dir = TempDir::new().unwrap();
636 let cfg_dir = tmp_dir.join(".config/lutris/runners");
637 let cfg_file = cfg_dir.join("wine.yml");
638 let dir_name = "Wine-6.21-GE-1";
639 fs::create_dir_all(&cfg_dir).unwrap();
640
641 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
642 fs::create_dir_all(
643 path_cfg
644 .app_config_backup_file(None, &TagKind::wine())
645 .parent()
646 .unwrap(),
647 )
648 .unwrap();
649 let fs_mng = FsMng::new(&path_cfg);
650
651 let version = ManagedVersion::new("6.21-GE-1", TagKind::wine(), dir_name);
652 fs_mng.apply_to_app_config(&version).unwrap();
653
654 let modified_config = LutrisConfig::create_copy(&cfg_file).unwrap();
655 assert_eq!(modified_config.wine_version(), dir_name);
656
657 tmp_dir
658 .child(path_cfg.app_config_backup_file(None, &TagKind::wine()))
659 .assert(predicates::path::missing());
660
661 drop(fs_mng);
662 tmp_dir.close().unwrap();
663 }
664
665 #[test]
666 fn copy_proton_settings() {
667 let tmp_dir = TempDir::new().unwrap();
668 fs::create_dir_all(tmp_dir.join(".steam/root/compatibilitytools.d")).unwrap();
669 fs::create_dir_all(tmp_dir.join(".steam/root/config")).unwrap();
670
671 let path_cfg = MockPathConfig::new(PathBuf::from(tmp_dir.path()));
672 let fs_mng = FsMng::new(&path_cfg);
673
674 let src = Version::new("6.19-GE-1", TagKind::Proton);
675 let src_tar = File::open("test_resources/assets/Proton-6.19-GE-1.tar.gz").unwrap();
676 let dst = Version::new("6.20-GE-2", TagKind::Proton);
677 let dst_tar = File::open("test_resources/assets/Proton-6.20-GE-2.tar.gz").unwrap();
678
679 let src = fs_mng.setup_version(src, Box::new(src_tar)).unwrap();
680 let dst = fs_mng.setup_version(dst, Box::new(dst_tar)).unwrap();
681
682 tmp_dir
683 .child(".steam/root/compatibilitytools.d/Proton-6.19-GE-1")
684 .assert(predicates::path::exists());
685 tmp_dir
686 .child(".steam/root/compatibilitytools.d/Proton-6.20-GE-2")
687 .assert(predicates::path::exists());
688
689 fs::copy(
690 tmp_dir.join(".steam/root/compatibilitytools.d/Proton-6.19-GE-1/hello-world.txt"),
691 tmp_dir.join(".steam/root/compatibilitytools.d/Proton-6.19-GE-1/user_settings.py"),
692 )
693 .unwrap();
694
695 tmp_dir
696 .child(".steam/root/compatibilitytools.d/Proton-6.19-GE-1/user_settings.py")
697 .assert(predicates::path::exists());
698
699 fs_mng.copy_user_settings(&src, &dst).unwrap();
700
701 tmp_dir
702 .child(".steam/root/compatibilitytools.d/Proton-6.20-GE-2/user_settings.py")
703 .assert(predicates::path::exists());
704
705 tmp_dir.close().unwrap();
706 }
707}