loadsmith_loader/
context.rs1use std::{borrow::Cow, fmt::Debug, fs, path::Path};
2
3use camino::{Utf8Path, Utf8PathBuf};
4use globset::GlobSet;
5use loadsmith_core::{Checksum, ChecksumAlgorithm};
6use tracing::trace;
7use walkdir::WalkDir;
8
9use crate::Result;
10
11#[derive(Debug, Clone)]
35pub struct LaunchContext<'a> {
36 profile_path: Cow<'a, Utf8Path>,
37 game_path: Cow<'a, Utf8Path>,
38 is_proton: bool,
39}
40
41impl<'a> LaunchContext<'a> {
42 pub fn new(
49 profile_path: impl Into<Cow<'a, Utf8Path>>,
50 game_path: impl Into<Cow<'a, Utf8Path>>,
51 is_proton: bool,
52 ) -> Self {
53 Self {
54 profile_path: profile_path.into(),
55 game_path: game_path.into(),
56 is_proton,
57 }
58 }
59
60 pub fn profile_path(&self) -> &Utf8Path {
62 &self.profile_path
63 }
64
65 pub fn game_path(&self) -> &Utf8Path {
67 &self.game_path
68 }
69
70 pub fn is_proton(&self) -> bool {
72 self.is_proton
73 }
74
75 #[cfg(test)]
76 pub(crate) fn in_tempdir(tempdir: &tempfile::TempDir, is_proton: bool) -> Result<Self> {
77 use std::fs;
78
79 let path = Utf8Path::from_path(tempdir.path()).expect("temp path should be UTF-8");
80
81 let profile = path.join("profile");
82 let game = path.join("game");
83
84 fs::create_dir_all(&profile)?;
85 fs::create_dir_all(&game)?;
86
87 Ok(Self::new(profile, game, is_proton))
88 }
89
90 pub fn format_proton_path(&self, path: impl Into<Utf8PathBuf>) -> Utf8PathBuf {
93 if self.is_proton {
94 let path = format!("Z:{}", path.into());
95 Utf8PathBuf::from(path)
96 } else {
97 path.into()
98 }
99 }
100
101 pub fn copy_file_to_game(
106 &self,
107 relative_source: impl AsRef<Path>,
108 relative_target: impl AsRef<Path>,
109 ) -> Result<()> {
110 let src = relative_source.as_ref();
111 let target = relative_target.as_ref();
112
113 let profile_path = self.profile_path.as_std_path().join(src);
114 let game_path = self.game_path.as_std_path().join(target);
115
116 if game_path.is_file() {
117 if Checksum::compute_from_path(&profile_path, ChecksumAlgorithm::Blake3)?
118 == Checksum::compute_from_path(&game_path, ChecksumAlgorithm::Blake3)?
119 {
120 trace!(
121 src = %src.display(),
122 target = %target.display(),
123 "skipping copy, file already exists and is identical"
124 );
125
126 return Ok(());
127 }
128 }
129
130 trace!(
131 src = %src.display(),
132 target = %target.display(),
133 "copy file to game directory"
134 );
135
136 loadsmith_util::create_parent_dirs(&game_path)?;
137
138 fs::copy(profile_path, game_path)?;
139
140 Ok(())
141 }
142
143 pub fn copy_glob_to_game(&self, patterns: &GlobSet) -> Result<()> {
146 self.copy_files_to_game(|path| patterns.is_match(path))
147 }
148
149 pub fn copy_files_to_game<F>(&self, filter: F) -> Result<()>
152 where
153 F: Fn(&Path) -> bool,
154 {
155 WalkDir::new(&*self.profile_path)
156 .into_iter()
157 .filter_map(|entry| entry.ok())
158 .filter(|entry| entry.file_type().is_file())
159 .map(|entry| {
160 entry
161 .into_path()
162 .strip_prefix(&*self.profile_path)
163 .expect("profile path should be a prefix of the file path")
164 .to_path_buf()
165 })
166 .filter(|relative_path| filter(relative_path))
167 .try_for_each(|relative_path| self.copy_file_to_game(&relative_path, &relative_path))
168 }
169}