forked from
atscan.net/plcbundle-rs
High-performance implementation of plcbundle written in Rust
1use anyhow::Result;
2use clap::Parser;
3use plcbundle::index::Index;
4use plcbundle::*;
5use std::path::{Path, PathBuf};
6
7use super::utils;
8
9#[derive(Parser)]
10#[command(
11 about = "Show comprehensive repository status",
12 long_about = "Display a comprehensive overview of the repository's current state, including
13bundle statistics, DID index status, mempool state, and actionable health
14recommendations.
15
16This command provides a quick way to understand your repository's health and
17configuration. It shows total bundles, operations, unique DIDs, storage usage,
18compression ratios, and the status of optional components like the DID index
19and mempool.
20
21Use --detailed to see additional information like recent bundles and more
22comprehensive statistics. Use --json for machine-readable output suitable for
23monitoring systems or automated health checks.
24
25The command automatically identifies potential issues (like missing DID index)
26and suggests next steps to improve repository functionality. This makes it an
27excellent starting point for understanding repository state and planning
28maintenance tasks.",
29 alias = "info",
30 help_template = crate::clap_help!(
31 examples: " # Show repository status\n \
32 {bin} status\n\n \
33 # Show detailed status with recent bundles\n \
34 {bin} status --detailed\n\n \
35 # JSON output for scripting\n \
36 {bin} status --json\n\n \
37 # Using legacy 'info' alias\n \
38 {bin} info"
39 )
40)]
41pub struct StatusCommand {
42 /// Show detailed information
43 #[arg(short, long)]
44 pub detailed: bool,
45
46 /// Output as JSON
47 #[arg(long)]
48 pub json: bool,
49}
50
51pub fn run(cmd: StatusCommand, dir: PathBuf) -> Result<()> {
52 let manager = utils::create_manager(dir.clone(), false, false, false)?;
53 let index = manager.get_index();
54
55 if cmd.json {
56 print_json_status(&manager, &index, &dir)?;
57 } else {
58 print_human_status(&manager, &index, &dir, cmd.detailed)?;
59 }
60
61 Ok(())
62}
63
64fn print_human_status(
65 manager: &BundleManager,
66 index: &Index,
67 dir: &Path,
68 detailed: bool,
69) -> Result<()> {
70 let dir_display = utils::display_path(dir);
71
72 // Header
73 println!("📦 PLC Bundle Repository Status");
74 println!("═══════════════════════════════════════════════════════════════");
75 println!();
76
77 // Repository Information
78 println!("📁 Repository");
79 println!("───────────────────────────────────────────────────────────────");
80 println!(" Directory: {}", dir_display.display());
81 println!(" Version: {}", index.version);
82 println!(" Origin: {}", index.origin);
83 println!(" Last Updated: {}", index.updated_at);
84 println!();
85
86 // Bundle Statistics
87 println!("📊 Bundle Statistics");
88 println!("───────────────────────────────────────────────────────────────");
89
90 let is_empty = utils::is_repository_empty(manager);
91
92 if is_empty {
93 println!(" Status: Empty (no bundles)");
94 println!();
95 } else {
96 let total_bundles = index.last_bundle;
97 let total_operations = plcbundle::constants::total_operations_from_bundles(total_bundles);
98
99 println!(
100 " Total Bundles: {}",
101 utils::format_number(total_bundles as u64)
102 );
103 println!(
104 " Operations: ~{}",
105 utils::format_number(total_operations)
106 );
107 println!(
108 " Unique DIDs: {}",
109 utils::format_number(calculate_total_dids(index) as u64)
110 );
111 println!();
112
113 // Storage
114 println!(
115 " Compressed: {}",
116 utils::format_bytes(index.total_size_bytes)
117 );
118 println!(
119 " Uncompressed: {}",
120 utils::format_bytes(index.total_uncompressed_size_bytes)
121 );
122
123 let compression_ratio = if index.total_uncompressed_size_bytes > 0 {
124 (1.0 - index.total_size_bytes as f64 / index.total_uncompressed_size_bytes as f64)
125 * 100.0
126 } else {
127 0.0
128 };
129 println!(" Compression: {:.1}%", compression_ratio);
130 println!();
131
132 // Recent bundles
133 if detailed {
134 let recent_count = 5;
135 let start_idx = index.bundles.len().saturating_sub(recent_count);
136 let recent_bundles = &index.bundles[start_idx..];
137
138 println!(" Recent Bundles:");
139 for meta in recent_bundles {
140 println!(
141 " #{:<6} {} → {} ({} ops, {} DIDs)",
142 meta.bundle_number,
143 &meta.start_time[..19], // Just date and time without timezone
144 &meta.end_time[..19],
145 utils::format_number(meta.operation_count as u64),
146 utils::format_number(meta.did_count as u64)
147 );
148 }
149 println!();
150 }
151 }
152
153 // DID Index Status
154 println!("🔍 DID Index");
155 println!("───────────────────────────────────────────────────────────────");
156
157 let did_index_exists = check_did_index_exists(dir);
158
159 if did_index_exists {
160 println!(" Status: ✓ Built");
161
162 let stats = manager.get_did_index_stats_struct();
163 if stats.total_dids > 0 {
164 println!(
165 " Indexed DIDs: {}",
166 utils::format_number(stats.total_dids as u64)
167 );
168 }
169 } else {
170 println!(" Status: ✗ Not built");
171 println!(" Hint: Run 'plcbundle index build' to create DID index");
172 }
173 println!();
174
175 // Mempool Status
176 println!("⏳ Mempool");
177 println!("───────────────────────────────────────────────────────────────");
178
179 if let Ok(mempool_stats) = manager.get_mempool_stats() {
180 if mempool_stats.count > 0 {
181 println!(
182 " Operations: {}",
183 utils::format_number(mempool_stats.count as u64)
184 );
185 println!(" Target Bundle: #{}", mempool_stats.target_bundle);
186 println!(
187 " Can Bundle: {}",
188 if mempool_stats.can_create_bundle {
189 "Yes"
190 } else {
191 "No"
192 }
193 );
194
195 if let Some(first) = mempool_stats.first_time {
196 println!(" First Op: {}", first);
197 }
198 if let Some(last) = mempool_stats.last_time {
199 println!(" Last Op: {}", last);
200 }
201 } else {
202 println!(" Status: Empty");
203 }
204 } else {
205 println!(" Status: Not initialized");
206 }
207 println!();
208
209 // Health & Recommendations
210 if !is_empty {
211 println!("💡 Status & Recommendations");
212 println!("───────────────────────────────────────────────────────────────");
213
214 let mut suggestions = Vec::new();
215 let mut hints = Vec::new();
216
217 if !did_index_exists {
218 suggestions.push("Build DID index for fast lookups: plcbundle index build");
219 }
220
221 if let Ok(mempool_stats) = manager.get_mempool_stats()
222 && mempool_stats.count >= plcbundle::constants::BUNDLE_SIZE
223 {
224 suggestions.push("Mempool ready to create new bundle: plcbundle sync");
225 }
226
227 // Add general hints
228 hints.push("Verify integrity: plcbundle verify --chain");
229 if did_index_exists {
230 hints.push("Verify DID index: plcbundle index verify");
231 }
232 hints.push("List bundles: plcbundle ls");
233
234 if suggestions.is_empty() {
235 println!(" ✓ Repository is healthy");
236 println!();
237 if detailed {
238 println!(" Useful commands:");
239 for hint in hints {
240 println!(" • {}", hint);
241 }
242 }
243 } else {
244 println!(" Suggestions:");
245 for suggestion in suggestions {
246 println!(" • {}", suggestion);
247 }
248 println!();
249 if detailed {
250 println!(" Useful commands:");
251 for hint in hints {
252 println!(" • {}", hint);
253 }
254 }
255 }
256 println!();
257 }
258
259 Ok(())
260}
261
262fn print_json_status(manager: &BundleManager, index: &Index, dir: &Path) -> Result<()> {
263 use serde_json::json;
264
265 let dir_display = utils::display_path(dir);
266 let is_empty = utils::is_repository_empty(manager);
267
268 let mut status = json!({
269 "repository": {
270 "directory": dir_display.display().to_string(),
271 "version": index.version,
272 "origin": index.origin,
273 "last_updated": index.updated_at,
274 },
275 "bundles": {
276 "count": index.last_bundle,
277 "total_size_bytes": index.total_size_bytes,
278 "total_uncompressed_size_bytes": index.total_uncompressed_size_bytes,
279 "compression_ratio": if index.total_uncompressed_size_bytes > 0 {
280 (1.0 - index.total_size_bytes as f64 / index.total_uncompressed_size_bytes as f64) * 100.0
281 } else {
282 0.0
283 },
284 }
285 });
286
287 // DID Index
288 let did_index_exists = check_did_index_exists(dir);
289 let mut did_index_info = json!({
290 "exists": did_index_exists,
291 });
292
293 if did_index_exists {
294 let stats = manager.get_did_index_stats_struct();
295 if stats.total_dids > 0 {
296 did_index_info["indexed_dids"] = json!(stats.total_dids);
297 }
298 }
299 status["did_index"] = did_index_info;
300
301 // Mempool
302 let mut mempool_info = json!({});
303 if let Ok(mempool_stats) = manager.get_mempool_stats() {
304 mempool_info = json!({
305 "count": mempool_stats.count,
306 "target_bundle": mempool_stats.target_bundle,
307 "can_create_bundle": mempool_stats.can_create_bundle,
308 "validated": mempool_stats.validated,
309 "first_time": mempool_stats.first_time.map(|t| t.to_rfc3339()),
310 "last_time": mempool_stats.last_time.map(|t| t.to_rfc3339()),
311 });
312 }
313 status["mempool"] = mempool_info;
314
315 // Health
316 let mut health = Vec::new();
317 if !is_empty && !did_index_exists {
318 health.push("did_index_not_built");
319 }
320 if let Ok(mempool_stats) = manager.get_mempool_stats()
321 && mempool_stats.count >= plcbundle::constants::BUNDLE_SIZE
322 {
323 health.push("mempool_ready_to_bundle");
324 }
325 status["health"] = json!(health);
326
327 println!("{}", sonic_rs::to_string_pretty(&status)?);
328
329 Ok(())
330}
331
332fn check_did_index_exists(dir: &Path) -> bool {
333 let did_index_dir = dir
334 .join(plcbundle::constants::DID_INDEX_DIR)
335 .join(plcbundle::constants::DID_INDEX_SHARDS);
336 did_index_dir.exists() && did_index_dir.is_dir()
337}
338
339fn calculate_total_dids(index: &Index) -> u32 {
340 index.bundles.iter().map(|b| b.did_count).sum()
341}