use std::error::Error;
use clap::{CommandFactory, Parser};
use tcslog::{record_trailer, LogError, LogRead, Meta, SegmentHeader};
const MAX_MESSAGE_SIZE: usize = 256;
#[derive(Parser)]
#[command(version, about)]
struct Args {
dirname: String,
prefix: String,
suffix: String,
#[arg(short, long)]
text: bool,
#[arg(short, long)]
verbose: bool,
}
fn main() -> Result<(), Box<dyn Error>> {
let args = Args::try_parse().unwrap_or_else(|e| {
eprintln!("{e}");
let _ = Args::command().print_help();
eprintln!();
std::process::exit(2);
});
let mut log = LogRead::new(&args.dirname, &args.prefix, &args.suffix)?;
log.collect_opened_headers(args.verbose);
let mut printed_header = false;
let mut files_lost = 0u64;
let mut total = 0u64;
let mut buf = vec![0u8; MAX_MESSAGE_SIZE];
loop {
let read_result = log.read(&mut buf);
for h in log.take_opened_headers() {
if printed_header {
println!();
}
printed_header = true;
print_header(&args.prefix, &args.suffix, &h);
}
match read_result {
Ok(res) => {
total += 1;
print_record(args.text, res.meta, &buf[..res.n as usize]);
println!();
}
Err(LogError::Eof) => break,
Err(LogError::ReadTruncated { lost, n }) => {
files_lost += lost;
if n > 0 {
total += 1;
print_truncated_record(args.text, &buf[..n as usize]);
println!();
}
if args.verbose {
if lost > 0 {
println!(
" -- {lost} missing segment file(s); \
resynchronizing --"
);
} else {
println!(
" -- corrupted or truncated segment file; \
resynchronizing --"
);
}
}
}
Err(LogError::ReadOverflow(n)) => {
total += 1;
print_partial_record(args.text, &buf[..n as usize]);
println!();
if args.verbose {
println!(
" (payload larger than {MAX_MESSAGE_SIZE}-byte buffer; \
{n} bytes captured, remainder discarded)"
);
}
}
Err(LogError::SessionEnd) => {
if args.verbose {
println!();
println!("--- End of Session---");
}
}
Err(e) => {
return Err(e.into());
}
}
}
if args.verbose {
println!(
"\nread {total} message(s) across {} file(s)",
log.segments_opened()
);
if files_lost > 0 {
println!("{files_lost} segment file(s) lost");
}
}
Ok(())
}
fn print_record(text: bool, meta: Meta, buf: &[u8]) {
let msg = format_msg(text, buf);
print!(" {msg} {}", record_trailer(buf.len(), meta));
}
fn print_partial_record(text: bool, buf: &[u8]) {
let msg = format_msg(text, buf);
print!(" {msg} ({} bytes captured, record truncated)", buf.len());
}
fn print_truncated_record(text: bool, buf: &[u8]) {
let msg = format_msg(text, buf);
print!(
" {msg} ({} bytes recovered, rest of record lost)",
buf.len()
);
}
fn format_msg(as_text: bool, buf: &[u8]) -> String {
if as_text {
String::from_utf8_lossy(buf).to_string()
} else {
let mut text = String::new();
for item in buf {
text.push(*item as char);
}
text
}
}
fn print_header(prefix: &str, suffix: &str, h: &SegmentHeader) {
println!("=== segment file: {}{}{} ===", prefix, h.segment_id, suffix);
println!(" segment_id: {}", h.segment_id);
println!(" session_id: {}", h.session_id);
println!(" max_size: {}", h.max_size);
println!(" remaining: {}", h.remaining);
println!(" format: {:?}", h.format);
println!(" sequence: {}", h.sequence);
}