use std::io::Write;
use std::sync::mpsc::{channel, Sender};
use std::thread::JoinHandle;
use std::time::Duration;
#[must_use]
pub(crate) fn print_interval<W>(
mut buffer: W,
interval: Duration,
start: String,
tick: String,
end: String,
on_drop_msg: String,
) -> PrintGuard<W>
where
W: Write + Send + 'static,
{
let (sender, receiver) = channel::<()>();
let join_handle = std::thread::spawn(move || {
write!(buffer, "{start}").expect("Writer should not be closed");
buffer.flush().expect("Writer should not be closed");
loop {
write!(buffer, "{tick}").expect("Writer should not be closed");
buffer.flush().expect("Writer should not be closed");
if receiver.recv_timeout(interval).is_ok() {
break;
}
}
write!(buffer, "{end}").expect("Writer should not be closed");
buffer.flush().expect("Writer should not be closed");
buffer
});
PrintGuard::new(join_handle, sender, on_drop_msg)
}
#[derive(Debug)]
pub(crate) struct PrintGuard<W>
where
W: Write + Send + 'static,
{
on_drop_msg: String,
join_handle: Option<JoinHandle<W>>,
stop_signal: Sender<()>,
}
impl<W> Drop for PrintGuard<W>
where
W: Write + Send + 'static,
{
fn drop(&mut self) {
if let Some(join_handle) = self.join_handle.take() {
let _ = self.stop_signal.send(());
if let Ok(mut buffer) = join_handle.join() {
writeln!(buffer, "{}", self.on_drop_msg).expect("Writer should not be closed")
}
}
}
}
impl<W> PrintGuard<W>
where
W: Write + Send + 'static,
{
fn new(join_handle: JoinHandle<W>, sender: Sender<()>, on_drop_msg: String) -> Self {
let guard = PrintGuard {
join_handle: Some(join_handle),
stop_signal: sender,
on_drop_msg,
};
debug_assert!(guard.join_handle.is_some());
guard
}
#[allow(clippy::panic_in_result_fn)]
pub(crate) fn stop(mut self) -> std::thread::Result<W> {
match self.join_handle.take() {
Some(join_handle) => {
let _ = self.stop_signal.send(());
join_handle.join()
}
None => panic!("Internal error: Dot print internal state should never be None"),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::{File, OpenOptions};
use tempfile::NamedTempFile;
#[test]
fn does_stop_does_not_panic() {
let mut buffer: Vec<u8> = vec![];
write!(buffer, "before").unwrap();
let dot = print_interval(
buffer,
Duration::from_millis(1),
String::from(" ."),
String::from("."),
String::from(". "),
String::from("(Error)"),
);
let mut writer = dot.stop().unwrap();
write!(writer, "after").unwrap();
writer.flush().unwrap();
assert_eq!("before ... after", String::from_utf8_lossy(&writer));
}
#[test]
fn test_drop_stops_timer() {
let tempfile = NamedTempFile::new().unwrap();
let mut log = File::create(tempfile.path()).unwrap();
write!(log, "before").unwrap();
let dot = print_interval(
log,
Duration::from_millis(1),
String::from(" ."),
String::from("."),
String::from(". "),
"(Error)".to_string(),
);
drop(dot);
let mut log = OpenOptions::new()
.append(true)
.open(tempfile.path())
.unwrap();
write!(log, "after").unwrap();
log.flush().unwrap();
assert_eq!(
String::from("before ... (Error)\nafter"),
std::fs::read_to_string(tempfile.path()).unwrap()
);
}
}