1use std::path::{Path, PathBuf};
8#[cfg(windows)]
10use winbrew_windows::host::search_path_file;
11
12#[derive(Debug, Clone)]
14pub struct ResolvedPaths {
15 pub root: PathBuf,
17 pub packages: PathBuf,
19 pub data: PathBuf,
21 pub logs: PathBuf,
23 pub package_logs: PathBuf,
25 pub cache: PathBuf,
27 pub pkgdb: PathBuf,
29 pub shims: PathBuf,
31 pub db: PathBuf,
33 pub catalog_db: PathBuf,
35 pub config: PathBuf,
37 pub log: PathBuf,
39}
40
41#[derive(Debug, Clone)]
42struct ManagedRootLayout {
43 root: PathBuf,
44 packages: PathBuf,
45 data: PathBuf,
46 logs: PathBuf,
47 package_logs: PathBuf,
48 cache: PathBuf,
49 pkgdb: PathBuf,
50 shims: PathBuf,
51 db: PathBuf,
52 catalog_db: PathBuf,
53 config: PathBuf,
54 log: PathBuf,
55}
56
57impl ManagedRootLayout {
58 fn resolve(root: &Path, packages: &str, data: &str, logs: &str, cache: &str) -> Self {
59 let root = PathBuf::from(root);
60 let packages = resolve_template(&root, packages);
61 let data = resolve_template(&root, data);
62 let logs = resolve_template(&root, logs);
63 let cache = resolve_template(&root, cache);
64 let pkgdb = data.join("pkgdb");
65 let package_logs = logs.join("packages");
66 let shims = root.join("shims");
67 let db = data.join("db");
68
69 Self {
70 catalog_db: db.join("catalog.db"),
71 config: data.join("winbrew.toml"),
72 db: db.join("winbrew.db"),
73 log: logs.join("winbrew.log"),
74 package_logs,
75 packages,
76 pkgdb,
77 root,
78 cache,
79 data,
80 logs,
81 shims,
82 }
83 }
84
85 fn into_resolved_paths(self) -> ResolvedPaths {
86 ResolvedPaths {
87 root: self.root,
88 packages: self.packages,
89 data: self.data,
90 logs: self.logs,
91 package_logs: self.package_logs,
92 cache: self.cache,
93 pkgdb: self.pkgdb,
94 shims: self.shims,
95 db: self.db,
96 catalog_db: self.catalog_db,
97 config: self.config,
98 log: self.log,
99 }
100 }
101}
102
103pub fn config_file_at(root: &Path) -> PathBuf {
105 root.join("data").join("winbrew.toml")
106}
107
108pub fn packages_dir_at(root: &Path) -> PathBuf {
110 root.join("packages")
111}
112
113pub fn data_dir_at(root: &Path) -> PathBuf {
115 root.join("data")
116}
117
118pub fn pkgdb_dir_at(root: &Path) -> PathBuf {
120 data_dir_at(root).join("pkgdb")
121}
122
123pub fn db_dir_at(root: &Path) -> PathBuf {
125 data_dir_at(root).join("db")
126}
127
128pub fn db_path_at(root: &Path) -> PathBuf {
130 db_dir_at(root).join("winbrew.db")
131}
132
133pub fn catalog_db_at(root: &Path) -> PathBuf {
135 db_dir_at(root).join("catalog.db")
136}
137
138pub fn log_dir_at(root: &Path) -> PathBuf {
140 root.join("data").join("logs")
141}
142
143pub fn log_file_at(root: &Path) -> PathBuf {
145 log_dir_at(root).join("winbrew.log")
146}
147
148pub fn cache_dir_at(root: &Path) -> PathBuf {
150 root.join("data").join("cache")
151}
152
153pub fn cache_file_at(root: &Path, name: &str, version: &str, ext: &str) -> PathBuf {
155 cache_dir_at(root).join(cache_filename(name, version, ext))
156}
157
158pub fn sevenz_runtime_dir_from_runtime_root(runtime_root: &Path) -> PathBuf {
160 runtime_root.join("bin/7zip")
161}
162
163pub fn sevenz_bin_path_from_runtime_root(runtime_root: &Path) -> PathBuf {
165 sevenz_runtime_dir_from_runtime_root(runtime_root).join("7z.exe")
166}
167
168pub fn sevenz_dll_path_from_runtime_root(runtime_root: &Path) -> PathBuf {
170 sevenz_runtime_dir_from_runtime_root(runtime_root).join("7z.dll")
171}
172
173#[cfg(windows)]
175pub fn system_sevenz_binary_path() -> Option<PathBuf> {
176 search_path_file("7z.exe").and_then(|binary_path| {
177 let runtime_root = binary_path.parent()?;
178
179 if runtime_root.join("7z.dll").exists() {
180 Some(binary_path)
181 } else {
182 None
183 }
184 })
185}
186
187#[cfg(not(windows))]
189pub fn system_sevenz_binary_path() -> Option<PathBuf> {
190 None
191}
192
193pub fn package_journal_file_at(root: &Path, package_key: &str) -> PathBuf {
195 pkgdb_dir_at(root).join(package_key).join("journal.jsonl")
196}
197
198fn cache_filename(name: &str, version: &str, ext: &str) -> String {
199 let mut filename = String::with_capacity(name.len() + version.len() + ext.len() + 2);
200 filename.push_str(name);
201 filename.push('-');
202 filename.push_str(version);
203 filename.push('.');
204 filename.push_str(ext);
205 filename
206}
207
208pub fn resolve_template(root: &Path, template: &str) -> PathBuf {
210 let root_text = root.to_string_lossy();
211
212 if template.contains("${root}") {
213 PathBuf::from(template.replace("${root}", &root_text))
214 } else {
215 PathBuf::from(template)
216 }
217}
218
219pub fn resolved_paths(
221 root: &Path,
222 packages: &str,
223 data: &str,
224 logs: &str,
225 cache: &str,
226) -> ResolvedPaths {
227 ManagedRootLayout::resolve(root, packages, data, logs, cache).into_resolved_paths()
228}
229
230impl ResolvedPaths {
231 pub fn package_install_dir(&self, package_name: &str) -> PathBuf {
233 self.packages.join(package_name)
234 }
235
236 pub fn package_journal_dir(&self, package_key: &str) -> PathBuf {
238 self.pkgdb.join(package_key)
239 }
240
241 pub fn package_journal_file(&self, package_key: &str) -> PathBuf {
243 self.package_journal_dir(package_key).join("journal.jsonl")
244 }
245
246 pub fn package_log_dir(&self, package_key: &str) -> PathBuf {
248 self.package_logs.join(package_key)
249 }
250
251 pub fn package_shim_dir(&self, package_key: &str) -> PathBuf {
253 self.shims.join(package_key)
254 }
255}
256
257pub fn install_root_from_package_dir(install_dir: &Path) -> PathBuf {
259 install_dir
260 .parent()
261 .and_then(|path| path.parent())
262 .map(PathBuf::from)
263 .unwrap_or_default()
264}
265
266#[cfg(test)]
267mod tests {
268 use super::{
269 cache_dir_at, catalog_db_at, config_file_at, data_dir_at, db_path_at, log_dir_at,
270 log_file_at, package_journal_file_at, packages_dir_at, pkgdb_dir_at, resolved_paths,
271 sevenz_bin_path_from_runtime_root, sevenz_dll_path_from_runtime_root,
272 sevenz_runtime_dir_from_runtime_root,
273 };
274 use std::path::PathBuf;
275 use tempfile::tempdir;
276
277 #[test]
278 fn package_journal_file_lives_under_pkgdb() {
279 let root = tempdir().expect("temp dir");
280 let package_key = "winget_Contoso.App-c47f5b18b8a430e6";
281
282 let journal_file = package_journal_file_at(root.path(), package_key);
283
284 assert_eq!(
285 journal_file,
286 pkgdb_dir_at(root.path())
287 .join(package_key)
288 .join("journal.jsonl")
289 );
290 }
291
292 #[test]
293 fn sevenz_runtime_layout_uses_expected_relative_paths() {
294 let runtime_root = PathBuf::from("C:/winbrew");
295
296 assert_eq!(
297 sevenz_runtime_dir_from_runtime_root(&runtime_root),
298 PathBuf::from("C:/winbrew/bin/7zip")
299 );
300 assert_eq!(
301 sevenz_bin_path_from_runtime_root(&runtime_root),
302 PathBuf::from("C:/winbrew/bin/7zip/7z.exe")
303 );
304 assert_eq!(
305 sevenz_dll_path_from_runtime_root(&runtime_root),
306 PathBuf::from("C:/winbrew/bin/7zip/7z.dll")
307 );
308 }
309
310 #[test]
311 fn resolved_paths_derive_managed_layout_and_package_scopes() {
312 let root = tempdir().expect("temp dir");
313 let package_key = "winget_Contoso.App-c47f5b18b8a430e6";
314 let paths = resolved_paths(
315 root.path(),
316 "${root}\\packages",
317 "${root}\\data",
318 "${root}\\data\\logs",
319 "${root}\\data\\cache",
320 );
321
322 assert_eq!(paths.root, root.path());
323 assert_eq!(paths.packages, packages_dir_at(root.path()));
324 assert_eq!(
325 paths.package_install_dir("Contoso.App"),
326 paths.packages.join("Contoso.App")
327 );
328 assert_eq!(paths.data, data_dir_at(root.path()));
329 assert_eq!(paths.logs, log_dir_at(root.path()));
330 assert_eq!(paths.package_logs, paths.logs.join("packages"));
331 assert_eq!(paths.cache, cache_dir_at(root.path()));
332 assert_eq!(paths.pkgdb, pkgdb_dir_at(root.path()));
333 assert_eq!(paths.shims, root.path().join("shims"));
334 assert_eq!(paths.db, db_path_at(root.path()));
335 assert_eq!(paths.catalog_db, catalog_db_at(root.path()));
336 assert_eq!(paths.config, config_file_at(root.path()));
337 assert_eq!(paths.log, log_file_at(root.path()));
338 assert_eq!(
339 paths.package_journal_dir(package_key),
340 paths.pkgdb.join(package_key)
341 );
342 assert_eq!(
343 paths.package_journal_file(package_key),
344 package_journal_file_at(root.path(), package_key)
345 );
346 assert_eq!(
347 paths.package_log_dir(package_key),
348 paths.package_logs.join(package_key)
349 );
350 assert_eq!(
351 paths.package_shim_dir(package_key),
352 paths.shims.join(package_key)
353 );
354 }
355}