133 lines
2.7 KiB
Go
133 lines
2.7 KiB
Go
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)
|
|
)
|