forked from
atscan.net/plcbundle-rs
High-performance implementation of plcbundle written in Rust
1// Rebuild plc_bundles.json from existing bundle files
2use super::progress::ProgressBar;
3use super::utils::{HasGlobalFlags, format_bytes};
4use anyhow::Result;
5use clap::{Args, ValueHint};
6use plcbundle::BundleManager;
7use std::path::PathBuf;
8use std::time::Instant;
9
10#[derive(Args)]
11#[command(
12 about = "Rebuild bundle index from existing files",
13 long_about = "Reconstruct the bundle index (plc_bundles.json) by scanning existing bundle
14files and extracting metadata from their embedded metadata frames. This is essential
15when the index file has been lost, corrupted, or needs to be regenerated.
16
17The command scans all .jsonl.zst files in the directory, reads the metadata frame
18from each bundle (stored in a zstd skippable frame), and reconstructs the complete
19index with proper chain hashes and bundle relationships. The index is written
20atomically to ensure data integrity.
21
22This only works with bundles that have embedded metadata frames (the modern format).
23Legacy bundles without metadata frames cannot be reconstructed this way. Use
24--dry-run to preview what would be rebuilt without actually writing the index.
25
26After rebuilding, verify the repository with 'verify' to ensure chain integrity
27is correct.",
28 help_template = crate::clap_help!(
29 examples: " # Rebuild index from current directory\n \
30 {bin} rebuild\n\n \
31 # Rebuild from specific directory\n \
32 {bin} rebuild -C /path/to/bundles\n\n \
33 # Show verbose output\n \
34 {bin} rebuild -v\n\n \
35 # Dry-run (just scan, don't write)\n \
36 {bin} rebuild --dry-run"
37 )
38)]
39pub struct RebuildCommand {
40 /// Show what would be done without writing index
41 #[arg(short = 'n', long)]
42 pub dry_run: bool,
43
44 /// Set origin URL (default: auto-detect from first bundle)
45 #[arg(long, value_hint = ValueHint::Url)]
46 pub origin: Option<String>,
47}
48
49impl HasGlobalFlags for RebuildCommand {
50 fn verbose(&self) -> bool {
51 false
52 }
53 fn quiet(&self) -> bool {
54 false
55 }
56}
57
58pub fn run(cmd: RebuildCommand, dir: PathBuf, _global_verbose: bool) -> Result<()> {
59 eprintln!(
60 "Rebuilding bundle index from: {}\n",
61 super::utils::display_path(&dir).display()
62 );
63
64 let start = Instant::now();
65
66 // Progress tracking
67 eprintln!("Scanning for bundle files...");
68
69 // We don't know the total upfront, so we'll create progress bar in the callback
70 use std::sync::{Arc, Mutex};
71 let progress_bar: Arc<Mutex<Option<ProgressBar>>> = Arc::new(Mutex::new(None));
72 let progress_bar_clone = progress_bar.clone();
73
74 // Rebuild index using BundleManager API
75 let index = BundleManager::rebuild_index(
76 &dir,
77 cmd.origin,
78 Some(move |current, total, bytes_processed, total_bytes| {
79 let mut pb_guard = progress_bar_clone.lock().unwrap();
80 if pb_guard.is_none() {
81 *pb_guard = Some(ProgressBar::with_bytes(total, total_bytes));
82 }
83 if let Some(ref pb) = *pb_guard {
84 pb.set_with_bytes(current, bytes_processed);
85 }
86 }),
87 )?;
88
89 if let Some(pb) = progress_bar.lock().unwrap().take() {
90 pb.finish();
91 }
92
93 let elapsed = start.elapsed();
94
95 // Display summary
96 eprintln!();
97 eprintln!("Rebuild Summary");
98 eprintln!("═══════════════");
99 eprintln!(" Bundles: {}", index.bundles.len());
100 eprintln!(
101 " Range: {} - {}",
102 index.bundles.first().map(|b| b.bundle_number).unwrap_or(0),
103 index.last_bundle
104 );
105 eprintln!(" Origin: {}", index.origin);
106 eprintln!(
107 " Compressed size: {}",
108 format_bytes(index.total_size_bytes)
109 );
110 eprintln!(
111 " Uncompressed size: {}",
112 format_bytes(index.total_uncompressed_size_bytes)
113 );
114 eprintln!(" Scan time: {:?}", elapsed);
115 eprintln!();
116
117 if cmd.dry_run {
118 eprintln!("💡 Dry-run mode - index not written");
119 return Ok(());
120 }
121
122 // Save index atomically
123 eprintln!("Writing plc_bundles.json...");
124 index.save(&dir)?;
125
126 eprintln!("✓ Index rebuilt successfully");
127 eprintln!(" Location: {}/plc_bundles.json", dir.display());
128
129 Ok(())
130}