//! Callback and progress display, ported from the callback in main.c. use crate::log::Log; use cdrustanoia_core::callback::*; use cdrustanoia_core::device::CD_FRAMEWORDS; use std::io::{self, Write}; use std::time::{SystemTime, UNIX_EPOCH}; pub const CALLBACK_STRINGS: [&str; 16] = [ "wrote", "finished", "read", "verify", "jitter", "correction", "scratch", "scratch repair", "skip", "drift", "backoff", "overlap", "dropped", "duped", "transport error", "cache error", ]; const GRAPH: usize = 30; pub struct Progress { pub callbegin: i64, pub callend: i64, pub callscript: bool, pub abort_on_skip: bool, pub skipped_flag: bool, c_sector: i64, v_sector: i64, dispcache: [u8; GRAPH], last: i32, lasttime: i64, heartbeat: u8, slevel: i32, slast: i32, stimeout: i32, cacheerr: i32, overlap: i32, printit: i32, } impl Default for Progress { fn default() -> Self { Self { callbegin: 0, callend: 0, callscript: false, abort_on_skip: false, skipped_flag: false, c_sector: 0, v_sector: 0, dispcache: [b' '; GRAPH], last: 0, lasttime: 0, heartbeat: b' ', slevel: 0, slast: 0, stimeout: 0, cacheerr: 0, overlap: 0, printit: -1, } } } impl Progress { pub fn reset_batch(&mut self) { self.c_sector = 0; self.v_sector = 0; self.dispcache = [b' '; GRAPH]; self.last = 0; self.lasttime = 0; self.heartbeat = b' '; self.slevel = 0; self.slast = 0; self.stimeout = 0; self.overlap = 0; self.skipped_flag = false; } fn now_tenths() -> i64 { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(d) => d.as_secs() as i64 * 10 + (d.subsec_micros() / 100_000) as i64, Err(_) => 0, } } pub fn callback(&mut self, inpos: i64, function: i32, log: &mut Log) { if self.callscript { let name = if (-2..=13).contains(&function) { CALLBACK_STRINGS[(function + 2) as usize] } else { "" }; let _ = writeln!(io::stderr(), "##: {} [{}] @ {}", function, name, inpos); } else if function == PARANOIA_CB_CACHEERR { if self.cacheerr == 0 { let _ = write!( io::stderr(), "\rWARNING: The CDROM drive appears to be seeking impossibly quickly.\n\ This could be due to timer bugs, a drive that really is improbably fast,\n\ or, most likely, a bug in cdparanoia's cache modelling.\n\n\ Please consider using the -A option to perform an analysis run, then mail\n\ the cdparanoia.log file produced by the analysis to paranoia-dev@xiph.org\n\ to assist developers in correcting the problem.\n\n" ); } self.cacheerr += 1; } if function == PARANOIA_CB_SKIP { // Keep -X functional even when progress is redirected or quiet. self.skipped_flag = true; } if log.quiet { return; } let osector = inpos; let sector = inpos / CD_FRAMEWORDS; if self.printit == -1 { self.printit = if unsafe { libc::isatty(2) } == 1 { 1 } else { 0 }; } if self.printit == 1 { let position = if self.callend != self.callbegin { ((sector - self.callbegin) as f64 / (self.callend - self.callbegin) as f64 * GRAPH as f64) as i32 } else { 0 }; let aheadposition = if self.callend != self.callbegin { ((self.c_sector - self.callbegin) as f64 / (self.callend - self.callbegin) as f64 * GRAPH as f64) as i32 } else { 0 }; if function == -2 { self.v_sector = sector; return; } if function == -1 { self.last = 8; self.heartbeat = b'*'; self.slevel = 0; self.v_sector = sector; } else if position < GRAPH as i32 && position >= 0 { let p = position as usize; match function { PARANOIA_CB_VERIFY => { if self.stimeout >= 30 { if self.overlap as i64 > CD_FRAMEWORDS { self.slevel = 2; } else { self.slevel = 1; } } } PARANOIA_CB_READ => { if sector > self.c_sector { self.c_sector = sector; } } PARANOIA_CB_FIXUP_EDGE => { if self.stimeout >= 5 { if self.overlap as i64 > CD_FRAMEWORDS { self.slevel = 2; } else { self.slevel = 1; } } if self.dispcache[p] == b' ' { self.dispcache[p] = b'-'; } } PARANOIA_CB_FIXUP_ATOM => { if self.slevel < 3 || self.stimeout > 5 { self.slevel = 3; } if self.dispcache[p] == b' ' || self.dispcache[p] == b'-' { self.dispcache[p] = b'+'; } } PARANOIA_CB_READERR => { self.slevel = 6; if self.dispcache[p] != b'V' && self.dispcache[p] != b'C' { self.dispcache[p] = b'e'; } } PARANOIA_CB_CACHEERR => { self.slevel = 8; self.dispcache[p] = b'C'; } PARANOIA_CB_SKIP => { self.slevel = 8; if self.dispcache[p] != b'C' { self.dispcache[p] = b'V'; } } PARANOIA_CB_OVERLAP => { self.overlap = osector as i32; } PARANOIA_CB_SCRATCH => { self.slevel = 7; } PARANOIA_CB_DRIFT => { if self.slevel < 4 || self.stimeout > 5 { self.slevel = 4; } } PARANOIA_CB_FIXUP_DROPPED | PARANOIA_CB_FIXUP_DUPED => { self.slevel = 5; if matches!(self.dispcache[p], b' ' | b'-' | b'+') { self.dispcache[p] = b'!'; } } _ => {} } } let mut smilie = "= :-)"; match self.slevel { 0 => { smilie = if self.skipped_flag { " 8-X" } else { " :^D" }; } 1 => smilie = " :-)", 2 => smilie = " :-|", 3 => smilie = " :-P", 4 => smilie = " :-/", 5 => smilie = " 8-|", 6 => smilie = " :-0", 7 => smilie = " :-(", 8 => { smilie = " ;-("; self.skipped_flag = true; } _ => {} } let test = Self::now_tenths(); if self.lasttime != test || function == -1 || self.slast != self.slevel { if self.lasttime != test || function == -1 { self.last += 1; self.lasttime = test; if self.last > 7 { self.last = 0; } self.stimeout += 1; self.heartbeat = match self.last { 0 => b' ', 1 | 7 => b'.', 2 | 6 => b'o', 3 | 5 => b'0', 4 => b'O', _ => self.heartbeat, }; if function == -1 { self.heartbeat = b'*'; } } if self.slast != self.slevel { self.stimeout = 0; } self.slast = self.slevel; let mut buffer: Vec = if self.abort_on_skip && self.skipped_flag && function != -1 { format!( "\r (== PROGRESS == [{}| {:06} {:02} ] =={} {} ==) ", " ...aborting; please wait... ", self.v_sector, self.overlap as i64 / CD_FRAMEWORDS, smilie, self.heartbeat as char ) .into_bytes() } else { let disp = String::from_utf8_lossy(&self.dispcache).to_string(); if self.v_sector == 0 { format!( "\r (== PROGRESS == [{}| ...... {:02} ] =={} {} ==) ", disp, self.overlap as i64 / CD_FRAMEWORDS, smilie, self.heartbeat as char ) .into_bytes() } else { format!( "\r (== PROGRESS == [{}| {:06} {:02} ] =={} {} ==) ", disp, self.v_sector, self.overlap as i64 / CD_FRAMEWORDS, smilie, self.heartbeat as char ) .into_bytes() } }; if aheadposition >= 0 && aheadposition < GRAPH as i32 && function != -1 && buffer.len() > 19 + aheadposition as usize { buffer[19 + aheadposition as usize] = b'>'; } let _ = io::stderr().write_all(&buffer); if function == -1 { let mut report_bytes = buffer[1..].to_vec(); report_bytes.extend_from_slice(b"\n\n"); log.write_summary_bytes(&report_bytes); } } } if function == -1 { self.dispcache = [b' '; GRAPH]; } } } #[cfg(test)] mod tests { use super::*; #[test] fn skip_flag_survives_redirected_output() { let mut p = Progress::default(); let mut log = Log::new(); log.quiet = true; p.callback(0, PARANOIA_CB_SKIP, &mut log); assert!(p.skipped_flag); } }