first draft
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
//! 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<u8> =
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user