Skip to content

Commit

Permalink
✨ feat: Add single thread version of asciicast
Browse files Browse the repository at this point in the history
Multi-thread version have problems in inner_ref and inner_mut......
  • Loading branch information
wychlw committed Sep 22, 2024
1 parent 59eb644 commit db8288e
Show file tree
Hide file tree
Showing 3 changed files with 385 additions and 224 deletions.
290 changes: 66 additions & 224 deletions src/cli/asciicast.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
//! 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,
tty::{DynTty, Tty, WrapperTty},
};

pub struct Asciicast {
inner: Arc<Mutex<DynTty>>,
inner_took: Arc<Mutex<bool>>,
inner: DynTty,
logged: Vec<Entry>,
begin: bool,
begin_time: SystemTime,
head: Header,
data: Arc<Mutex<Vec<u8>>>,
logged: Arc<Mutex<Vec<Entry>>>,
begin: Arc<Mutex<bool>>,
begin_time: Arc<Mutex<SystemTime>>,
thread: Option<JoinHandle<()>>,
}

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,
Expand All @@ -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
}
}
}

Expand All @@ -130,136 +64,78 @@ impl AnyBase for Asciicast {

impl Tty for Asciicast {
fn read(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
let data = self.data.lock();
if data.is_err() {
return Err(Box::<dyn Error>::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<Vec<u8>, Box<dyn Error>> {
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<dyn Error>> {
{
let inner_took = self.inner_took.lock().unwrap();
if *inner_took {
return Err(Box::<dyn Error>::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<Self>) -> Box<dyn Any> {
self
}
}
impl Tty for DummyTty {
fn read(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
Ok(Vec::new())
}
fn read_line(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
Ok(Vec::new())
}
fn write(&mut self, _data: &[u8]) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
let logged = self.logged.lock();
if logged.is_err() {
return Err(Box::<dyn Error>::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::<dyn Error>::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::<dyn Error>::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<String, Box<dyn Error>> {
let begin = self.begin.lock();
if begin.is_err() {
return Err(Box::<dyn Error>::from("Recorder not started."));
}
let mut begin = begin.unwrap();
if !*begin {
return Err(Box::<dyn Error>::from("Recorder not started."));
}
*begin = false;
let mut res = String::new();
let logged = self.logged.lock();
if logged.is_err() {
return Err(Box::<dyn Error>::from("Recorder not started."));
if !self.begin {
return Err(Box::<dyn Error>::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() {
Expand All @@ -270,54 +146,20 @@ impl Recorder for Asciicast {
}
res += "\n";

info!("Asciicast end recording...");

Ok(res)
}

fn start(&mut self) -> Result<(), Box<dyn Error>> {
let begin = self.begin.lock();
if let Err(e) = begin {
return Err(Box::<dyn Error>::from(format!(
"Recorder not started. Reason: {}",
e
)));
}
let mut begin = begin.unwrap();
*begin = true;

info!("Asciicast continue recording...");

fn pause(&mut self) -> Result<(), Box<dyn Error>> {
self.begin = false;
info!("Asciicast pause for recording...");
Ok(())
}

fn pause(&mut self) -> Result<(), Box<dyn Error>> {
let begin = self.begin.lock();
if let Err(e) = begin {
return Err(Box::<dyn Error>::from(format!(
"Recorder not started. Reason: {}",
e
)));
}
let mut begin = begin.unwrap();
*begin = false;

info!("Asciicast pause recording...");

fn start(&mut self) -> Result<(), Box<dyn Error>> {
self.begin = true;
info!("Asciicast continue for recording...");
Ok(())
}

fn swap(&mut self, target: DynTty) -> Result<DynTty, Box<dyn Error>> {
sleep(Duration::from_micros(DURATION));
{
let inner_took = self.inner_took.lock().unwrap();
if *inner_took {
return Err(Box::<dyn Error>::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)
}
}
Loading

0 comments on commit db8288e

Please sign in to comment.