pacsea/logic/sandbox/
mod.rs1mod analyze;
4mod fetch;
5mod parse;
6mod types;
7
8#[cfg(test)]
9mod tests;
10
11pub use analyze::extract_package_name;
12pub use parse::{parse_pkgbuild_conflicts, parse_pkgbuild_deps};
13pub use types::{DependencyDelta, SandboxInfo};
14
15use crate::logic::sandbox::analyze::{
16 analyze_package_from_pkgbuild, analyze_package_from_srcinfo, get_installed_packages,
17};
18use crate::logic::sandbox::fetch::fetch_srcinfo_async;
19use crate::state::types::PackageItem;
20use futures::stream::{FuturesUnordered, StreamExt};
21
22const fn create_empty_sandbox_info(name: String) -> SandboxInfo {
33 SandboxInfo {
34 package_name: name,
35 depends: Vec::new(),
36 makedepends: Vec::new(),
37 checkdepends: Vec::new(),
38 optdepends: Vec::new(),
39 }
40}
41
42fn handle_srcinfo_analysis(
56 name: &str,
57 srcinfo_text: &str,
58 installed: &std::collections::HashSet<String>,
59 provided: &std::collections::HashSet<String>,
60) -> SandboxInfo {
61 analyze_package_from_srcinfo(name, srcinfo_text, installed, provided)
62}
63
64fn handle_pkgbuild_analysis(
78 name: &str,
79 pkgbuild_text: &str,
80 installed: &std::collections::HashSet<String>,
81 provided: &std::collections::HashSet<String>,
82) -> SandboxInfo {
83 let info = analyze_package_from_pkgbuild(name, pkgbuild_text, installed, provided);
84 let total_deps = info.depends.len()
85 + info.makedepends.len()
86 + info.checkdepends.len()
87 + info.optdepends.len();
88 tracing::info!(
89 "Parsed PKGBUILD for {}: {} total dependencies (depends={}, makedepends={}, checkdepends={}, optdepends={})",
90 name,
91 total_deps,
92 info.depends.len(),
93 info.makedepends.len(),
94 info.checkdepends.len(),
95 info.optdepends.len()
96 );
97 info
98}
99
100async fn process_sandbox_package(
114 name: String,
115 client: reqwest::Client,
116 installed: std::collections::HashSet<String>,
117 provided: std::collections::HashSet<String>,
118) -> Option<SandboxInfo> {
119 match fetch_srcinfo_async(&client, &name).await {
120 Ok(srcinfo_text) => Some(handle_srcinfo_analysis(
121 &name,
122 &srcinfo_text,
123 &installed,
124 &provided,
125 )),
126 Err(e) => {
127 tracing::debug!(
128 "Failed to fetch .SRCINFO for {}: {}, trying PKGBUILD",
129 name,
130 e
131 );
132 let name_for_fallback = name.clone();
133 let installed_for_fallback = installed.clone();
134 let provided_for_fallback = provided.clone();
135 match tokio::task::spawn_blocking(move || {
136 crate::logic::files::fetch_pkgbuild_sync(&name_for_fallback)
137 })
138 .await
139 {
140 Ok(Ok(pkgbuild_text)) => {
141 tracing::debug!(
142 "Successfully fetched PKGBUILD for {}, parsing dependencies",
143 name
144 );
145 Some(handle_pkgbuild_analysis(
146 &name,
147 &pkgbuild_text,
148 &installed_for_fallback,
149 &provided_for_fallback,
150 ))
151 }
152 Ok(Err(e)) => {
153 tracing::warn!("Failed to fetch PKGBUILD for {}: {}", name, e);
154 tracing::info!(
155 "Creating empty sandbox info for {} (both .SRCINFO and PKGBUILD fetch failed)",
156 name
157 );
158 Some(create_empty_sandbox_info(name))
159 }
160 Err(e) => {
161 tracing::warn!(
162 "Failed to spawn blocking task for PKGBUILD fetch for {}: {}",
163 name,
164 e
165 );
166 tracing::info!(
167 "Creating empty sandbox info for {} (spawn task failed)",
168 name
169 );
170 Some(create_empty_sandbox_info(name))
171 }
172 }
173 }
174 }
175}
176
177pub async fn resolve_sandbox_info_async(items: &[PackageItem]) -> Vec<SandboxInfo> {
190 let aur_items: Vec<_> = items
191 .iter()
192 .filter(|i| matches!(i.source, crate::state::Source::Aur))
193 .collect();
194 let span = tracing::info_span!(
195 "resolve_sandbox_info",
196 stage = "sandbox",
197 item_count = aur_items.len()
198 );
199 let _guard = span.enter();
200 let start_time = std::time::Instant::now();
201
202 let installed = get_installed_packages();
203 let provided = crate::logic::deps::get_provided_packages(&installed);
204
205 let client = reqwest::Client::builder()
207 .timeout(std::time::Duration::from_secs(10))
208 .build()
209 .unwrap_or_else(|_| reqwest::Client::new());
210
211 let mut fetch_futures = FuturesUnordered::new();
212 for item in items {
213 if matches!(item.source, crate::state::Source::Aur) {
214 let name = item.name.clone();
215 let installed_clone = installed.clone();
216 let provided_clone = provided.clone();
217 let client_clone = client.clone();
218 fetch_futures.push(process_sandbox_package(
219 name,
220 client_clone,
221 installed_clone,
222 provided_clone,
223 ));
224 }
225 }
226
227 let mut results = Vec::new();
229 while let Some(result) = fetch_futures.next().await {
230 if let Some(info) = result {
231 results.push(info);
232 }
233 }
234
235 let elapsed = start_time.elapsed();
236 let duration_ms = u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX);
237 tracing::info!(
238 stage = "sandbox",
239 item_count = aur_items.len(),
240 result_count = results.len(),
241 duration_ms = duration_ms,
242 "Sandbox resolution complete"
243 );
244 results
245}
246
247#[must_use]
261pub fn resolve_sandbox_info(items: &[PackageItem]) -> Vec<SandboxInfo> {
262 tokio::runtime::Handle::try_current().map_or_else(
264 |_| {
265 let rt = tokio::runtime::Runtime::new().unwrap_or_else(|e| {
267 tracing::error!(
268 "Failed to create tokio runtime for sandbox resolution: {}",
269 e
270 );
271 panic!("Cannot resolve sandbox info without tokio runtime");
272 });
273 rt.block_on(resolve_sandbox_info_async(items))
274 },
275 |handle| handle.block_on(resolve_sandbox_info_async(items)),
276 )
277}