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 {
87 compute_file_cksum: true,
88 };
89
90 match BinaryPackage::open(pkgfile) {
91 Ok(pkg) => match pkg.to_summary_with_opts(&opts) {
92 Ok(summary) => Some(format!("{}\n", summary)),
93 Err(e) => {
94 warn!(
95 path = %pkgfile.display(),
96 error = %e,
97 "Failed to generate summary"
98 );
99 None
100 }
101 },
102 Err(e) => {
103 warn!(
104 path = %pkgfile.display(),
105 error = %e,
106 "Failed to open package"
107 );
108 None
109 }
110 }
111}
112
113fn write_pkg_summary(pkgsrc_env: &PkgsrcEnv, entries: &[String]) -> Result<()> {
114 let summary_path = pkgsrc_env.packages.join("All/pkg_summary.gz");
115 let file = File::create(&summary_path)
116 .with_context(|| format!("Failed to create {}", summary_path.display()))?;
117 let mut encoder = GzEncoder::new(file, Compression::default());
118
119 for entry in entries {
120 encoder.write_all(entry.as_bytes())?;
121 }
122
123 encoder.finish()?;
124
125 debug!(
126 path = %summary_path.display(),
127 count = entries.len(),
128 "pkg_summary.gz written"
129 );
130
131 Ok(())
132}