package recorder import ( "encoding/json" "fmt" "io" "strconv" "sync" "time" ) type asciiCastRecorder struct { wc io.WriteCloser mu sync.Mutex header asciiCastHeader last time.Time closed bool } type asciiCastHeader struct { Version int `json:"version"` Term asciiCastTerm `json:"term"` Timestamp int64 `json:"timestamp"` Title string `json:"title,omitempty"` Env map[string]string `json:"env,omitempty"` } type asciiCastTerm struct { Columns int `json:"cols"` Rows int `json:"rows"` Type string `json:"type"` } type asciiCastDuration float64 func (d asciiCastDuration) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatFloat(float64(d), 'f', 6, 64)), nil } type asciiCastFrame struct { Delay float64 Data []byte } func (f asciiCastFrame) MarshalJSON() ([]byte, error) { s, _ := json.Marshal(string(f.Data)) return []byte(fmt.Sprintf(`[%.6f, %s]`, f.Delay, s)), nil } func newAsciiCastRecorder(wc io.WriteCloser, info Info) (*asciiCastRecorder, error) { now := time.Now() if err := json.NewEncoder(wc).Encode(asciiCastHeader{ Version: 3, Term: asciiCastTerm{ Columns: info.Columns, Rows: info.Rows, Type: info.TerminalType, }, Timestamp: now.Unix(), Title: info.Title, }); err != nil { return nil, err } if _, err := io.WriteString(wc, "\n"); err != nil { return nil, err } return &asciiCastRecorder{ wc: wc, last: now, }, nil } func (r *asciiCastRecorder) Close() error { return r.wc.Close() } func (r *asciiCastRecorder) writeFrame(kind rune, p []byte) (int, error) { r.mu.Lock() defer r.mu.Unlock() if r.closed { return 0, io.ErrClosedPipe } var ( now = time.Now() delay = now.Sub(r.last) ) v, _ := json.Marshal(string(p)) _, err := io.WriteString(r.wc, "["+strconv.FormatFloat(delay.Seconds(), 'f', 6, 64)+", \""+string(kind)+"\", "+string(v)+"]\n") r.last = now return len(p), err } type asciiCastWriter struct { r *asciiCastRecorder kind rune } func (w *asciiCastWriter) Close() error { return w.r.Close() } func (w *asciiCastWriter) Write(p []byte) (int, error) { return w.r.writeFrame(w.kind, p) } func (r *asciiCastRecorder) Reads() io.WriteCloser { return &asciiCastWriter{r, 'o'} } func (r *asciiCastRecorder) Writes() io.WriteCloser { return &asciiCastWriter{r, 'i'} } func (r *asciiCastRecorder) Resize(columns, rows int) { var ( now = time.Now() delay = now.Sub(r.last) ) r.mu.Lock() _, _ = fmt.Fprintf(r.wc, "[%.6f, \"r\", \"%dx%d\"]\n", delay.Seconds(), columns, rows) r.last = now r.mu.Unlock() } var ( _ Recorder = (*asciiCastRecorder)(nil) _ Resizer = (*asciiCastRecorder)(nil) )