pacenotes_installer\tree/
build.rs1impl super::Tree {
2 pub fn build(
6 game: &str,
7 packages: &[pacenotes_shared::PackageInfo],
8 cached: &[super::CachedPackage],
9 installed: &[String],
10 ) -> super::Tree {
11 let mut rows = listed_rows(game, packages, cached, installed);
12 rows.extend(local_only_rows(game, cached, installed, &rows));
13 rows.extend(unavailable_rows(game, installed, &rows));
14 rows.sort_by(|a, b| {
15 category_rank(game, &a.segments[0])
16 .cmp(&category_rank(game, &b.segments[0]))
17 .then_with(|| a.segments.cmp(&b.segments))
18 });
19
20 let mut tree = super::Tree::default();
21 for row in rows {
22 tree.insert(row);
23 }
24 tree
25 }
26
27 fn insert(&mut self, row: Row) {
28 let mut parent: Option<usize> = None;
29 let levels = &row.segments[..row.segments.len().saturating_sub(1)];
30 for level in levels {
31 parent = Some(self.child_named(parent, level));
32 }
33 let node = super::Node {
34 label: row.label,
35 parent,
36 children: Vec::new(),
37 key: Some(row.key),
38 state: Some(row.state),
39 installed: row.installed,
40 checked: row.installed || row.state.has_payload(),
41 };
42 self.push(node, parent);
43 }
44
45 fn child_named(&mut self, parent: Option<usize>, label: &str) -> usize {
47 let siblings = match parent {
48 Some(parent) => &self.nodes[parent].children,
49 None => &self.roots,
50 };
51 let existing = siblings
52 .iter()
53 .copied()
54 .find(|&index| !self.nodes[index].is_leaf() && self.nodes[index].label == label);
55 if let Some(existing) = existing {
56 return existing;
57 }
58 let node = super::Node {
59 label: label.to_string(),
60 parent,
61 children: Vec::new(),
62 key: None,
63 state: None,
64 installed: false,
65 checked: false,
66 };
67 self.push(node, parent)
68 }
69}
70
71struct Row {
73 segments: Vec<String>,
74 label: String,
75 key: String,
76 state: super::LeafState,
77 installed: bool,
78}
79
80fn listed_rows(
84 game: &str,
85 packages: &[pacenotes_shared::PackageInfo],
86 cached: &[super::CachedPackage],
87 installed: &[String],
88) -> Vec<Row> {
89 packages
90 .iter()
91 .filter(|package| package.game == game && pacenotes_shared::valid_name(&package.name))
92 .map(|package| {
93 let key = package.download_path();
94 let state = match cached.iter().find(|entry| entry.key() == key) {
95 Some(entry) if entry.sha256 == package.sha256 => super::LeafState::Downloaded,
96 Some(_) => super::LeafState::UpdateAvailable,
97 None => super::LeafState::NotCached,
98 };
99 Row {
100 segments: package.segments().map(str::to_string).collect(),
101 label: package.label().to_string(),
102 installed: installed.contains(&key),
103 key,
104 state,
105 }
106 })
107 .collect()
108}
109
110fn local_only_rows(
112 game: &str,
113 cached: &[super::CachedPackage],
114 installed: &[String],
115 listed: &[Row],
116) -> Vec<Row> {
117 cached
118 .iter()
119 .filter(|entry| entry.game == game && pacenotes_shared::valid_name(&entry.name))
120 .filter(|entry| !listed.iter().any(|row| row.key == entry.key()))
121 .map(|entry| {
122 let key = entry.key();
123 Row {
124 segments: entry.name.split('/').map(str::to_string).collect(),
125 label: entry
126 .display_name
127 .clone()
128 .unwrap_or_else(|| leaf_of(&entry.name).to_string()),
129 installed: installed.contains(&key),
130 key,
131 state: super::LeafState::LocalOnly,
132 }
133 })
134 .collect()
135}
136
137fn unavailable_rows(game: &str, installed: &[String], known: &[Row]) -> Vec<Row> {
139 installed
140 .iter()
141 .filter(|key| !known.iter().any(|row| &row.key == *key))
142 .filter_map(|key| {
143 let (package_game, _, name) = pacenotes_shared::split_download_path(key)?;
144 (package_game == game).then(|| Row {
145 segments: name.split('/').map(str::to_string).collect(),
146 label: leaf_of(name).to_string(),
147 key: key.clone(),
148 state: super::LeafState::Unavailable,
149 installed: true,
150 })
151 })
152 .collect()
153}
154
155fn leaf_of(name: &str) -> &str {
156 name.rsplit('/').next().unwrap_or(name)
157}
158
159fn category_rank(game: &str, category: &str) -> usize {
162 let declared = crate::schema::categories(game);
163 declared
164 .iter()
165 .position(|declared| declared.id == category)
166 .unwrap_or(declared.len())
167}