winbrew_windows\deployment\msi/
builder.rs1use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15
16use crate::models::install::engine::InstallScope;
17use crate::models::msi_inventory::records::{
18 MsiComponentRecord, MsiFileRecord, MsiRegistryRecord, MsiShortcutRecord,
19};
20
21use super::{
22 ComponentRow, FileRow, RegistryRow, ShortcutRow,
23 path::{normalize_path, normalize_registry_key_path, resolve_reference_path, select_msi_name},
24};
25
26pub(super) fn build_file_paths(
27 file_rows: &[FileRow],
28 component_rows: &HashMap<String, ComponentRow>,
29 directory_paths: &HashMap<String, PathBuf>,
30 install_root: &Path,
31) -> HashMap<String, PathBuf> {
32 let mut file_paths = HashMap::new();
37
38 for file_row in file_rows {
39 file_paths.insert(
40 file_row.file_key.clone(),
41 resolve_file_row_path(file_row, component_rows, directory_paths, install_root),
42 );
43 }
44
45 file_paths
46}
47
48pub(super) fn build_file_records(
49 package_name: &str,
50 file_rows: &[FileRow],
51 file_paths: &HashMap<String, PathBuf>,
52 component_rows: &HashMap<String, ComponentRow>,
53 directory_paths: &HashMap<String, PathBuf>,
54 install_root: &Path,
55) -> Vec<MsiFileRecord> {
56 file_rows
61 .iter()
62 .map(|file_row| {
63 let path = file_paths
64 .get(&file_row.file_key)
65 .cloned()
66 .unwrap_or_else(|| {
67 resolve_file_row_path(file_row, component_rows, directory_paths, install_root)
68 });
69
70 MsiFileRecord {
71 package_name: package_name.to_string(),
72 path: path.to_string_lossy().into_owned(),
73 normalized_path: normalize_path(&path),
74 hash_algorithm: None,
75 hash_hex: None,
76 is_config_file: false,
77 }
78 })
79 .collect()
80}
81
82fn resolve_file_row_path(
83 file_row: &FileRow,
84 component_rows: &HashMap<String, ComponentRow>,
85 directory_paths: &HashMap<String, PathBuf>,
86 install_root: &Path,
87) -> PathBuf {
88 let base_dir = component_rows
89 .get(&file_row.component_id)
90 .and_then(|component| directory_paths.get(&component.directory_id))
91 .cloned()
92 .unwrap_or_else(|| install_root.to_path_buf());
93
94 let file_name =
95 select_msi_name(&file_row.file_name).unwrap_or_else(|| file_row.file_name.clone());
96 base_dir.join(file_name)
97}
98
99pub(super) fn build_registry_records(
100 package_name: &str,
101 scope: InstallScope,
102 rows: &[RegistryRow],
103) -> Vec<MsiRegistryRecord> {
104 rows.iter()
109 .map(|row| MsiRegistryRecord {
110 package_name: package_name.to_string(),
111 hive: registry_root_name(row.root, scope).to_string(),
112 key_path: row.key_path.clone(),
113 normalized_key_path: normalize_registry_key_path(&row.key_path),
114 value_name: row.name.clone().unwrap_or_default(),
115 value_data: row.value.clone(),
116 previous_value: None,
117 })
118 .collect()
119}
120
121fn registry_root_name(root: i32, scope: InstallScope) -> &'static str {
122 match root {
123 0 => "HKCR",
124 1 => "HKCU",
125 2 => "HKLM",
126 3 => "HKU",
127 -1 => match scope {
128 InstallScope::Installed => "HKLM",
129 InstallScope::Provisioned => "HKCU",
130 },
131 _ => "UNKNOWN",
132 }
133}
134
135pub(super) fn build_shortcut_records(
136 package_name: &str,
137 rows: &[ShortcutRow],
138 directory_paths: &HashMap<String, PathBuf>,
139 file_paths: &HashMap<String, PathBuf>,
140 install_root: &Path,
141) -> Vec<MsiShortcutRecord> {
142 rows.iter()
148 .map(|row| {
149 let directory_path = directory_paths
150 .get(&row.directory_id)
151 .cloned()
152 .unwrap_or_else(|| install_root.to_path_buf());
153 let path =
154 directory_path.join(select_msi_name(&row.name).unwrap_or_else(|| row.name.clone()));
155 let target_path = resolve_reference_path(&row.target, directory_paths, file_paths);
156
157 MsiShortcutRecord {
158 package_name: package_name.to_string(),
159 path: path.to_string_lossy().into_owned(),
160 normalized_path: normalize_path(&path),
161 target_path: target_path
162 .as_ref()
163 .map(|value| value.to_string_lossy().into_owned()),
164 normalized_target_path: target_path
165 .as_ref()
166 .map(|value| normalize_path(value.as_path())),
167 }
168 })
169 .collect()
170}
171
172pub(super) fn build_component_records(
173 package_name: &str,
174 component_rows: &HashMap<String, ComponentRow>,
175 directory_paths: &HashMap<String, PathBuf>,
176 file_paths: &HashMap<String, PathBuf>,
177) -> Vec<MsiComponentRecord> {
178 component_rows
184 .iter()
185 .map(|(component_id, component)| {
186 let path = component
187 .key_path
188 .as_deref()
189 .and_then(|value| resolve_reference_path(value, directory_paths, file_paths));
190
191 MsiComponentRecord {
192 package_name: package_name.to_string(),
193 component_id: component_id.clone(),
194 path: path
195 .as_ref()
196 .map(|value| value.to_string_lossy().into_owned()),
197 normalized_path: path.as_ref().map(|value| normalize_path(value.as_path())),
198 }
199 })
200 .collect()
201}
202
203#[cfg(test)]
204mod tests {
205 use super::registry_root_name;
206 use crate::models::install::engine::InstallScope;
207
208 #[test]
209 fn registry_root_name_uses_scope_for_negative_one() {
210 assert_eq!(registry_root_name(-1, InstallScope::Installed), "HKLM");
211 assert_eq!(registry_root_name(-1, InstallScope::Provisioned), "HKCU");
212 }
213}