1use std::fs::File;
25use std::io::Write;
26use std::path::Path;
27
28use anyhow::{Context, Result};
29use flate2::Compression;
30use flate2::write::GzEncoder;
31use pkgsrc::archive::{BinaryPackage, SummaryOptions};
32use rayon::prelude::*;
33use tracing::{debug, warn};
34
35use crate::config::PkgsrcEnv;
36use crate::db::Database;
37
38pub fn generate_pkg_summary(db: &Database, threads: usize) -> Result<()> {
46 let pkgsrc_env = db.load_pkgsrc_env()?;
47 let pkgnames = db.get_successful_packages()?;
48
49 if pkgnames.is_empty() {
50 debug!("No successful packages to include in pkg_summary");
51 return Ok(());
52 }
53
54 let packages_dir = pkgsrc_env.packages.join("All");
55
56 debug!(
57 count = pkgnames.len(),
58 dir = %packages_dir.display(),
59 "Generating pkg_summary for packages"
60 );
61
62 let pool = rayon::ThreadPoolBuilder::new()
63 .num_threads(threads)
64 .build()
65 .context("Failed to build thread pool for pkg_summary generation")?;
66
67 let results: Vec<String> = pool.install(|| {
68 pkgnames
69 .par_iter()
70 .filter_map(|pkgname| {
71 let pkgfile = packages_dir.join(format!("{}.tgz", pkgname));
72 generate_summary_entry(&pkgfile)
73 })
74 .collect()
75 });
76
77 write_pkg_summary(&pkgsrc_env, &results)
78}
79
80fn generate_summary_entry(pkgfile: &Path) -> Option<String> {
81 if !pkgfile.exists() {
82 warn!(path = %pkgfile.display(), "Package file not found");
83 return None;
84 }
85
86 let opts = SummaryOptions { compute_file_cksum: true };
87
88 match BinaryPackage::open(pkgfile) {
89 Ok(pkg) => match pkg.to_summary_with_opts(&opts) {
90 Ok(summary) => Some(format!("{}\n", summary)),
91 Err(e) => {
92 warn!(
93 path = %pkgfile.display(),
94 error = %e,
95 "Failed to generate summary"
96 );
97 None
98 }
99 },
100 Err(e) => {
101 warn!(
102 path = %pkgfile.display(),
103 error = %e,
104 "Failed to open package"
105 );
106 None
107 }
108 }
109}
110
111fn write_pkg_summary(pkgsrc_env: &PkgsrcEnv, entries: &[String]) -> Result<()> {
112 let summary_path = pkgsrc_env.packages.join("All/pkg_summary.gz");
113 let file = File::create(&summary_path).with_context(|| {
114 format!("Failed to create {}", summary_path.display())
115 })?;
116 let mut encoder = GzEncoder::new(file, Compression::default());
117
118 for entry in entries {
119 encoder.write_all(entry.as_bytes())?;
120 }
121
122 encoder.finish()?;
123
124 debug!(
125 path = %summary_path.display(),
126 count = entries.len(),
127 "pkg_summary.gz written"
128 );
129
130 Ok(())
131}