diff --git a/src/cli/asciicast.rs b/src/cli/asciicast.rs index f823e56..d7bee29 100644 --- a/src/cli/asciicast.rs +++ b/src/cli/asciicast.rs @@ -1,22 +1,16 @@ -//! Asciicast recorder. +//! Asciicast recorder. Single thread version to bypass inner_ref/mut problem. //! //! The Asciicast recorder is a recorder that records the terminal output //! in the asciicast v2 format. +//! This version may not log the output at the same time as the output goes into the terminal. -use std::{ - any::Any, - collections::HashMap, - error::Error, - mem::replace, - sync::{Arc, Mutex}, - thread::{sleep, spawn, JoinHandle}, - time::{Duration, SystemTime}, -}; + +use std::{any::Any, collections::HashMap, error::Error, mem::replace, time::SystemTime}; use asciicast::{Entry, EventType, Header}; use serde_json::to_string; -use crate::{consts::DURATION, info, util::anybase::AnyBase}; +use crate::{info, util::anybase::AnyBase}; use super::{ recorder::Recorder, @@ -24,23 +18,20 @@ use super::{ }; pub struct Asciicast { - inner: Arc>, - inner_took: Arc>, + inner: DynTty, + logged: Vec, + begin: bool, + begin_time: SystemTime, head: Header, - data: Arc>>, - logged: Arc>>, - begin: Arc>, - begin_time: Arc>, - thread: Option>, } impl Asciicast { pub fn build(inner: DynTty) -> Asciicast { - let inner = Arc::new(Mutex::new(inner)); - - let mut res = Asciicast { - inner: inner.clone(), - inner_took: Arc::new(Mutex::new(false)), + Asciicast { + inner, + logged: Vec::new(), + begin: false, + begin_time: SystemTime::now(), head: Header { version: 2, width: 80, @@ -55,64 +46,7 @@ impl Asciicast { ("TERM".to_string(), "VT100".to_string()), ])), }, - data: Arc::new(Mutex::new(Vec::new())), - logged: Arc::new(Mutex::new(Vec::new())), - begin: Arc::new(Mutex::new(false)), - begin_time: Arc::new(Mutex::new(SystemTime::now())), - thread: None, - }; - - let inner = inner.clone(); - let inner_took = res.inner_took.clone(); - let data = res.data.clone(); - let logged = res.logged.clone(); - let begin = res.begin.clone(); - let begin_time = res.begin_time.clone(); - let process = move || loop { - sleep(Duration::from_millis(DURATION)); - { - let inner_took = inner_took.lock().unwrap(); - if *inner_took { - return; - } - } - let new_data = { - let mut inner = inner.lock().unwrap(); - let new_data = inner.read(); - if new_data.is_err() { - return; - } - new_data.unwrap() - }; - - { - let begin = begin.lock().unwrap(); - if *begin && !new_data.is_empty() { - let time = begin_time.lock().unwrap().elapsed().unwrap(); - let timestamp = time.as_millis(); - let timestamp = timestamp as f64 / 1000.0; - let mut logged = logged.lock().unwrap(); - logged.push(Entry { - time: timestamp, - event_type: EventType::Output, - event_data: String::from_utf8(new_data.clone()).unwrap_or_default(), - }); - } - } - - { - let mut data = data.lock().unwrap(); - data.extend(new_data); - } - }; - - let thread = spawn(process); - - res.thread = Some(thread); - - info!("Create a Asciicast recorder to record."); - - res + } } } @@ -130,136 +64,78 @@ impl AnyBase for Asciicast { impl Tty for Asciicast { fn read(&mut self) -> Result, Box> { - let data = self.data.lock(); - if data.is_err() { - return Err(Box::::from("Read from Asciicast failed.")); + let data = self.inner.read()?; + + if self.begin { + let time = self.begin_time.elapsed().unwrap(); + let timestamp = time.as_millis(); + let timestamp = timestamp as f64 / 1000.0; + self.logged.push(Entry { + time: timestamp, + event_type: EventType::Output, + event_data: String::from_utf8(data.clone()).unwrap_or_default(), + }) } - let mut data = data.unwrap(); - let res = data.clone(); - data.clear(); - Ok(res) + Ok(data) } fn read_line(&mut self) -> Result, Box> { - let mut res = Vec::new(); - loop { - sleep(Duration::from_millis(DURATION)); - let mut data = self.data.lock().unwrap(); - if data.is_empty() { - continue; - } - res.push(data[0]); - data.drain(0..1); - if res.ends_with(&[0x0A]) { - break; - } + let data = self.inner.read_line()?; + + if self.begin { + let time = self.begin_time.elapsed().unwrap(); + let timestamp = time.as_millis(); + let timestamp = timestamp as f64 / 1000.0; + self.logged.push(Entry { + time: timestamp, + event_type: EventType::Output, + event_data: String::from_utf8(data.clone()).unwrap_or_default(), + }) } - Ok(res) + + Ok(data) } fn write(&mut self, data: &[u8]) -> Result<(), Box> { - { - let inner_took = self.inner_took.lock().unwrap(); - if *inner_took { - return Err(Box::::from("You've already exited.")); - } - } - { - let inner = self.inner.clone(); - let mut inner = inner.lock().unwrap(); - inner.write(data) - } - } -} + self.inner.write(data)?; -struct DummyTty {} -impl AnyBase for DummyTty { - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - fn into_any(self: Box) -> Box { - self - } -} -impl Tty for DummyTty { - fn read(&mut self) -> Result, Box> { - Ok(Vec::new()) - } - fn read_line(&mut self) -> Result, Box> { - Ok(Vec::new()) - } - fn write(&mut self, _data: &[u8]) -> Result<(), Box> { Ok(()) } } impl WrapperTty for Asciicast { - fn exit(mut self) -> DynTty { - { - let mut inner_took = self.inner_took.lock().unwrap(); - *inner_took = true; - } - let dummy = DummyTty {}; - self.swap(Box::new(dummy)).unwrap() + fn exit(self) -> DynTty { + self.inner } - fn inner_mut(&mut self) -> &mut DynTty { - panic!("Asciicast recorder does not support inner_mut method... I have no idea how to implement it."); + fn inner_ref(&self) -> &DynTty { + &self.inner } - fn inner_ref(&self) -> &DynTty { - panic!("Asciicast recorder does not support inner_mut method... I have no idea how to implement it."); + fn inner_mut(&mut self) -> &mut DynTty { + &mut self.inner } } impl Recorder for Asciicast { fn begin(&mut self) -> Result<(), Box> { - let logged = self.logged.lock(); - if logged.is_err() { - return Err(Box::::from("Recorder not started.")); - } - let mut logged = logged.unwrap(); - logged.clear(); - - let time = SystemTime::now(); - let begin_time = self.begin_time.lock(); - if begin_time.is_err() { - return Err(Box::::from("Recorder not started.")); - } - let mut begin_time = begin_time.unwrap(); - *begin_time = time; - - let begin = self.begin.lock(); - if begin.is_err() { - return Err(Box::::from("Recorder not started.")); - } - let mut begin = begin.unwrap(); - *begin = true; - - info!("Asciicast begin to record."); + self.logged.clear(); + self.begin = true; + self.begin_time = SystemTime::now(); Ok(()) } fn end(&mut self) -> Result> { - let begin = self.begin.lock(); - if begin.is_err() { - return Err(Box::::from("Recorder not started.")); - } - let mut begin = begin.unwrap(); - if !*begin { - return Err(Box::::from("Recorder not started.")); - } - *begin = false; - let mut res = String::new(); - let logged = self.logged.lock(); - if logged.is_err() { - return Err(Box::::from("Recorder not started.")); + if !self.begin { + return Err(Box::::from("Not started")); } - let logged = logged.unwrap(); + + self.begin = false; + + let logged = self.logged.clone(); + self.logged.clear(); let head = to_string(&self.head).unwrap(); + let mut res = String::new(); res += &head; res += "\n"; for entry in logged.iter() { @@ -270,54 +146,20 @@ impl Recorder for Asciicast { } res += "\n"; - info!("Asciicast end recording..."); - Ok(res) } - - fn start(&mut self) -> Result<(), Box> { - let begin = self.begin.lock(); - if let Err(e) = begin { - return Err(Box::::from(format!( - "Recorder not started. Reason: {}", - e - ))); - } - let mut begin = begin.unwrap(); - *begin = true; - - info!("Asciicast continue recording..."); - + fn pause(&mut self) -> Result<(), Box> { + self.begin = false; + info!("Asciicast pause for recording..."); Ok(()) } - - fn pause(&mut self) -> Result<(), Box> { - let begin = self.begin.lock(); - if let Err(e) = begin { - return Err(Box::::from(format!( - "Recorder not started. Reason: {}", - e - ))); - } - let mut begin = begin.unwrap(); - *begin = false; - - info!("Asciicast pause recording..."); - + fn start(&mut self) -> Result<(), Box> { + self.begin = true; + info!("Asciicast continue for recording..."); Ok(()) } - fn swap(&mut self, target: DynTty) -> Result> { - sleep(Duration::from_micros(DURATION)); - { - let inner_took = self.inner_took.lock().unwrap(); - if *inner_took { - return Err(Box::::from("You've already exited.")); - } - } - let inner = self.inner.clone(); - let mut inner = inner.lock().unwrap(); - let res = replace(&mut *inner, target); - Ok(res) + let inner = replace(&mut self.inner, target); + Ok(inner) } } diff --git a/src/cli/asciicast_multi.rs b/src/cli/asciicast_multi.rs new file mode 100644 index 0000000..1f6dfb3 --- /dev/null +++ b/src/cli/asciicast_multi.rs @@ -0,0 +1,318 @@ +//! Asciicast recorder. Multi thread version to log the output at the same time as the output goes into the terminal. +//! +//! The Asciicast recorder is a recorder that records the terminal output +//! in the asciicast v2 format. +//! This version may have problems with inner_mut/ref method. + +use std::{ + any::Any, collections::HashMap, error::Error, mem::replace, sync::{Arc, Mutex}, thread::{sleep, spawn, JoinHandle}, time::{Duration, SystemTime} +}; + +use asciicast::{Entry, EventType, Header}; +use serde_json::to_string; + +use crate::{consts::DURATION, info, util::anybase::AnyBase}; + +use super::{ + recorder::Recorder, + tty::{DynTty, Tty, WrapperTty}, +}; + +pub struct Asciicast { + inner: Arc>, + inner_took: Arc>, + head: Header, + data: Arc>>, + logged: Arc>>, + begin: Arc>, + begin_time: Arc>, + thread: Option>, +} + +impl Asciicast { + pub fn build(inner: DynTty) -> Asciicast { + let inner = Arc::new(Mutex::new(inner)); + + let mut res = Asciicast { + inner: inner.clone(), + inner_took: Arc::new(Mutex::new(false)), + head: Header { + version: 2, + width: 80, + height: 24, + timestamp: None, + duration: None, + idle_time_limit: None, + command: None, + title: None, + env: Some(HashMap::from([ + ("SHELL".to_string(), "/bin/sh".to_string()), + ("TERM".to_string(), "VT100".to_string()), + ])), + }, + data: Arc::new(Mutex::new(Vec::new())), + logged: Arc::new(Mutex::new(Vec::new())), + begin: Arc::new(Mutex::new(false)), + begin_time: Arc::new(Mutex::new(SystemTime::now())), + thread: None, + }; + + let inner = inner.clone(); + let inner_took = res.inner_took.clone(); + let data = res.data.clone(); + let logged = res.logged.clone(); + let begin = res.begin.clone(); + let begin_time = res.begin_time.clone(); + let process = move || loop { + sleep(Duration::from_millis(DURATION)); + { + let inner_took = inner_took.lock().unwrap(); + if *inner_took { + return; + } + } + let new_data = { + let mut inner = inner.lock().unwrap(); + let new_data = inner.read(); + if new_data.is_err() { + return; + } + new_data.unwrap() + }; + + { + let begin = begin.lock().unwrap(); + if *begin && !new_data.is_empty() { + let time = begin_time.lock().unwrap().elapsed().unwrap(); + let timestamp = time.as_millis(); + let timestamp = timestamp as f64 / 1000.0; + let mut logged = logged.lock().unwrap(); + logged.push(Entry { + time: timestamp, + event_type: EventType::Output, + event_data: String::from_utf8(new_data.clone()).unwrap_or_default(), + }); + } + } + + { + let mut data = data.lock().unwrap(); + data.extend(new_data); + } + }; + + let thread = spawn(process); + + res.thread = Some(thread); + + info!("Create a Asciicast recorder to record."); + + res + } +} + +impl AnyBase for Asciicast { + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn into_any(self: Box) -> Box { + self + } +} + +impl Tty for Asciicast { + fn read(&mut self) -> Result, Box> { + let data = self.data.lock(); + if data.is_err() { + return Err(Box::::from("Read from Asciicast failed.")); + } + let mut data = data.unwrap(); + let res = data.clone(); + data.clear(); + + Ok(res) + } + fn read_line(&mut self) -> Result, Box> { + let mut res = Vec::new(); + loop { + sleep(Duration::from_millis(DURATION)); + let mut data = self.data.lock().unwrap(); + if data.is_empty() { + continue; + } + res.push(data[0]); + data.drain(0..1); + if res.ends_with(&[0x0A]) { + break; + } + } + Ok(res) + } + fn write(&mut self, data: &[u8]) -> Result<(), Box> { + { + let inner_took = self.inner_took.lock().unwrap(); + if *inner_took { + return Err(Box::::from("You've already exited.")); + } + } + { + let inner = self.inner.clone(); + let mut inner = inner.lock().unwrap(); + inner.write(data) + } + } +} + +struct DummyTty {} +impl AnyBase for DummyTty { + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn into_any(self: Box) -> Box { + self + } +} +impl Tty for DummyTty { + fn read(&mut self) -> Result, Box> { + Ok(Vec::new()) + } + fn read_line(&mut self) -> Result, Box> { + Ok(Vec::new()) + } + fn write(&mut self, _data: &[u8]) -> Result<(), Box> { + Ok(()) + } +} + +impl WrapperTty for Asciicast { + fn exit(mut self) -> DynTty { + { + let mut inner_took = self.inner_took.lock().unwrap(); + *inner_took = true; + } + let dummy = DummyTty {}; + self.swap(Box::new(dummy)).unwrap() + } + + fn inner_mut(&mut self) -> &mut DynTty { + panic!("Multi thread Asciicast recorder does not support inner_mut method... I have no idea how to implement it."); + } + + fn inner_ref(&self) -> &DynTty { + panic!("Multi thread Asciicast recorder does not support inner_mut method... I have no idea how to implement it."); + } +} + +impl Recorder for Asciicast { + fn begin(&mut self) -> Result<(), Box> { + let logged = self.logged.lock(); + if logged.is_err() { + return Err(Box::::from("Recorder not started.")); + } + let mut logged = logged.unwrap(); + logged.clear(); + + let time = SystemTime::now(); + let begin_time = self.begin_time.lock(); + if begin_time.is_err() { + return Err(Box::::from("Recorder not started.")); + } + let mut begin_time = begin_time.unwrap(); + *begin_time = time; + + let begin = self.begin.lock(); + if begin.is_err() { + return Err(Box::::from("Recorder not started.")); + } + let mut begin = begin.unwrap(); + *begin = true; + + info!("Asciicast begin to record."); + + Ok(()) + } + + fn end(&mut self) -> Result> { + let begin = self.begin.lock(); + if begin.is_err() { + return Err(Box::::from("Recorder not started.")); + } + let mut begin = begin.unwrap(); + if !*begin { + return Err(Box::::from("Recorder not started.")); + } + *begin = false; + let mut res = String::new(); + let logged = self.logged.lock(); + if logged.is_err() { + return Err(Box::::from("Recorder not started.")); + } + let logged = logged.unwrap(); + let head = to_string(&self.head).unwrap(); + res += &head; + res += "\n"; + for entry in logged.iter() { + let line = to_string(entry).unwrap(); + let line = line.replace("\\n", "\\r\\n"); + res += &line; + res += "\n"; + } + res += "\n"; + + info!("Asciicast end recording..."); + + Ok(res) + } + + fn start(&mut self) -> Result<(), Box> { + let begin = self.begin.lock(); + if let Err(e) = begin { + return Err(Box::::from(format!( + "Recorder not started. Reason: {}", + e + ))); + } + let mut begin = begin.unwrap(); + *begin = true; + + info!("Asciicast continue recording..."); + + Ok(()) + } + + fn pause(&mut self) -> Result<(), Box> { + let begin = self.begin.lock(); + if let Err(e) = begin { + return Err(Box::::from(format!( + "Recorder not started. Reason: {}", + e + ))); + } + let mut begin = begin.unwrap(); + *begin = false; + + info!("Asciicast pause recording..."); + + Ok(()) + } + + fn swap(&mut self, target: DynTty) -> Result> { + sleep(Duration::from_micros(DURATION)); + { + let inner_took = self.inner_took.lock().unwrap(); + if *inner_took { + return Err(Box::::from("You've already exited.")); + } + } + let inner = self.inner.clone(); + let mut inner = inner.lock().unwrap(); + let res = replace(&mut *inner, target); + Ok(res) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d27b127..f06813e 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -27,6 +27,7 @@ pub mod serial; pub mod shell; pub mod ssh; pub mod asciicast; +pub mod asciicast_multi; pub mod recorder; pub mod tee; pub mod deansi; \ No newline at end of file