#[cfg(not(feature = "opfs"))]
fn main() {
eprintln!("weapon-scope requires the 'opfs' feature to be enabled");
eprintln!("Please run with: cargo run --bin weapon-scope --features opfs");
std::process::exit(1);
}
#[cfg(feature = "opfs")]
fn main() {
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use weapon::opfs::{EventLogRecord, parse_device_counts, parse_event_log_records};
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <path-to-event-log-file>", args[0]);
eprintln!("\nExample: {} ./events.blob", args[0]);
std::process::exit(1);
}
let file_path = PathBuf::from(&args[1]);
if !file_path.exists() {
eprintln!("Error: File '{}' does not exist", file_path.display());
std::process::exit(1);
}
let mut file = match File::open(&file_path) {
Ok(f) => f,
Err(e) => {
eprintln!("Error opening file '{}': {}", file_path.display(), e);
std::process::exit(1);
}
};
let mut bytes = Vec::new();
if let Err(e) = file.read_to_end(&mut bytes) {
eprintln!("Error reading file '{}': {}", file_path.display(), e);
std::process::exit(1);
}
println!("WeaponScope - OPFS Event Log Analyzer");
println!("=====================================");
println!("File: {}", file_path.display());
println!(
"Size: {} bytes ({:.2} KB)",
bytes.len(),
bytes.len() as f64 / 1024.0
);
println!();
println!("Device Counts:");
println!("--------------");
let device_counts = parse_device_counts(&bytes);
if device_counts.is_empty() {
println!(" No devices found or invalid file format");
} else {
let total_events: usize = device_counts.values().sum();
println!(" Total devices: {}", device_counts.len());
println!(" Total events: {total_events}");
println!();
for (device_id, count) in &device_counts {
println!(" Device: {device_id}");
println!(" Events: {count}");
}
}
println!();
println!("Event Details:");
println!("--------------");
let records = parse_event_log_records(&bytes);
if records.is_empty() {
println!(" No events found or invalid file format");
} else {
let mut events_by_device: BTreeMap<String, Vec<&EventLogRecord>> = BTreeMap::new();
for record in &records {
events_by_device
.entry(record.device_id.clone())
.or_default()
.push(record);
}
println!(" Total parsed events: {}", records.len());
println!();
for (device_id, device_records) in events_by_device {
println!(" Device: {device_id}");
println!(" -------");
let mut expected_index = 0;
let mut has_gaps = false;
let mut has_backtracking = false;
for (i, record) in device_records.iter().enumerate() {
let index = record.within_device_events_index;
if index < expected_index {
has_backtracking = true;
println!(
" ⚠️ Event {i}: Index {index} (BACKTRACKING - expected >= {expected_index})"
);
} else if index > expected_index {
has_gaps = true;
println!(" ⚠️ Event {i}: Index {index} (GAP - expected {expected_index})");
} else {
println!(" Event {i}: Index {index}");
}
println!(" Timestamp: {}", record.event.timestamp);
let event_str = serde_json::to_string(&record.event.event)
.unwrap_or_else(|_| "Invalid JSON".to_string());
let preview = if event_str.len() > 100 {
let mut end = 100;
while !event_str.is_char_boundary(end) && end > 0 {
end -= 1;
}
format!("{}...", &event_str[..end])
} else {
event_str
};
println!(" Data: {preview}");
expected_index = index + 1;
}
if has_gaps {
println!(" ⚠️ WARNING: This device has gaps in event indices");
}
if has_backtracking {
println!(" ❌ ERROR: This device has backtracking (indices going backwards)");
}
if !has_gaps && !has_backtracking {
println!(" ✅ All indices are sequential");
}
println!();
}
}
println!();
println!("Summary:");
println!("--------");
let mut parsed_device_counts: BTreeMap<String, usize> = BTreeMap::new();
for record in &records {
*parsed_device_counts
.entry(record.device_id.clone())
.or_default() += 1;
}
let mut has_mismatch = false;
for (device_id, expected_count) in &device_counts {
let actual_count = parsed_device_counts.get(device_id).copied().unwrap_or(0);
if actual_count != *expected_count {
println!(
" ❌ Count mismatch for device {device_id}: header says {expected_count} but found {actual_count} events"
);
has_mismatch = true;
}
}
if !has_mismatch {
println!(" ✅ All device counts match between header and parsed events");
}
for (device_id, count) in &parsed_device_counts {
if !device_counts.contains_key(device_id) {
println!(" ⚠️ Device {device_id} has {count} events but is not in device counts");
}
}
}