mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
redo ptybuffer, move to wshutil to help with stdin processing. change wsh to use cobra
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package wshutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
Mode_Normal = "normal"
|
||||
Mode_Esc = "esc"
|
||||
Mode_WaveEsc = "waveesc"
|
||||
BlockFile_Main = "main" // Assuming this is defined elsewhere
|
||||
)
|
||||
|
||||
const MaxBufferedDataSize = 256 * 1024
|
||||
|
||||
type PtyBuffer struct {
|
||||
CVar *sync.Cond
|
||||
DataBuf *bytes.Buffer
|
||||
EscMode string
|
||||
EscSeqBuf []byte
|
||||
InputReader io.Reader
|
||||
CommandCh chan BlockCommand
|
||||
AtEOF bool
|
||||
Err error
|
||||
}
|
||||
|
||||
func MakePtyBuffer(input io.Reader, commandCh chan BlockCommand) *PtyBuffer {
|
||||
b := &PtyBuffer{
|
||||
CVar: sync.NewCond(&sync.Mutex{}),
|
||||
DataBuf: &bytes.Buffer{},
|
||||
EscMode: Mode_Normal,
|
||||
InputReader: input,
|
||||
CommandCh: commandCh,
|
||||
}
|
||||
go b.run()
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) setErr(err error) {
|
||||
b.CVar.L.Lock()
|
||||
defer b.CVar.L.Unlock()
|
||||
if b.Err == nil {
|
||||
b.Err = err
|
||||
}
|
||||
b.CVar.Broadcast()
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) setEOF() {
|
||||
b.CVar.L.Lock()
|
||||
defer b.CVar.L.Unlock()
|
||||
b.AtEOF = true
|
||||
b.CVar.Broadcast()
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) processWaveEscSeq(escSeq []byte) {
|
||||
jmsg := make(map[string]any)
|
||||
err := json.Unmarshal(escSeq, &jmsg)
|
||||
if err != nil {
|
||||
b.setErr(fmt.Errorf("error unmarshalling Wave OSC sequence data: %w", err))
|
||||
return
|
||||
}
|
||||
cmd, err := ParseCmdMap(jmsg)
|
||||
if err != nil {
|
||||
b.setErr(fmt.Errorf("error parsing Wave OSC command: %w", err))
|
||||
return
|
||||
}
|
||||
b.CommandCh <- cmd
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) run() {
|
||||
defer close(b.CommandCh)
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := b.InputReader.Read(buf)
|
||||
b.processData(buf[:n])
|
||||
if err == io.EOF {
|
||||
b.setEOF()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
b.setErr(fmt.Errorf("error reading input: %w", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) processData(data []byte) {
|
||||
outputBuf := make([]byte, 0, len(data))
|
||||
for _, ch := range data {
|
||||
if b.EscMode == Mode_WaveEsc {
|
||||
if ch == ESC {
|
||||
// terminates the escape sequence (and the rest was invalid)
|
||||
b.EscMode = Mode_Normal
|
||||
outputBuf = append(outputBuf, b.EscSeqBuf...)
|
||||
outputBuf = append(outputBuf, ch)
|
||||
b.EscSeqBuf = nil
|
||||
} else if ch == BEL || ch == ST {
|
||||
// terminates the escpae sequence (is a valid Wave OSC command)
|
||||
b.EscMode = Mode_Normal
|
||||
waveEscSeq := b.EscSeqBuf[len(WaveOSCPrefix):]
|
||||
b.EscSeqBuf = nil
|
||||
b.processWaveEscSeq(waveEscSeq)
|
||||
} else {
|
||||
b.EscSeqBuf = append(b.EscSeqBuf, ch)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if b.EscMode == Mode_Esc {
|
||||
if ch == ESC || ch == BEL || ch == ST {
|
||||
// these all terminate the escape sequence (invalid, not a Wave OSC)
|
||||
b.EscMode = Mode_Normal
|
||||
outputBuf = append(outputBuf, b.EscSeqBuf...)
|
||||
outputBuf = append(outputBuf, ch)
|
||||
} else {
|
||||
if ch == WaveOSCPrefixBytes[len(b.EscSeqBuf)] {
|
||||
// we're still building what could be a Wave OSC sequence
|
||||
b.EscSeqBuf = append(b.EscSeqBuf, ch)
|
||||
} else {
|
||||
// this is not a Wave OSC sequence, just an escape sequence
|
||||
b.EscMode = Mode_Normal
|
||||
outputBuf = append(outputBuf, b.EscSeqBuf...)
|
||||
outputBuf = append(outputBuf, ch)
|
||||
continue
|
||||
}
|
||||
// check to see if we have a full Wave OSC prefix
|
||||
if len(b.EscSeqBuf) == len(WaveOSCPrefixBytes) {
|
||||
b.EscMode = Mode_WaveEsc
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Mode_Normal
|
||||
if ch == ESC {
|
||||
b.EscMode = Mode_Esc
|
||||
b.EscSeqBuf = []byte{ch}
|
||||
continue
|
||||
}
|
||||
outputBuf = append(outputBuf, ch)
|
||||
}
|
||||
if len(outputBuf) > 0 {
|
||||
b.writeData(outputBuf)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) writeData(data []byte) {
|
||||
b.CVar.L.Lock()
|
||||
defer b.CVar.L.Unlock()
|
||||
// only wait if buffer is currently over max size, otherwise allow this append to go through
|
||||
for b.DataBuf.Len() > MaxBufferedDataSize {
|
||||
b.CVar.Wait()
|
||||
}
|
||||
b.DataBuf.Write(data)
|
||||
b.CVar.Broadcast()
|
||||
}
|
||||
|
||||
func (b *PtyBuffer) Read(p []byte) (n int, err error) {
|
||||
b.CVar.L.Lock()
|
||||
defer b.CVar.L.Unlock()
|
||||
for b.DataBuf.Len() == 0 {
|
||||
if b.Err != nil {
|
||||
return 0, b.Err
|
||||
}
|
||||
if b.AtEOF {
|
||||
return 0, io.EOF
|
||||
}
|
||||
b.CVar.Wait()
|
||||
}
|
||||
b.CVar.Broadcast()
|
||||
return b.DataBuf.Read(p)
|
||||
}
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
BlockCommand_Message = "message"
|
||||
BlockCommand_SetView = "setview"
|
||||
BlockCommand_SetMeta = "setmeta"
|
||||
BlockCommand_GetMeta = "getmeta"
|
||||
BlockCommand_Input = "controller:input"
|
||||
BlockCommand_AppendBlockFile = "blockfile:append"
|
||||
BlockCommand_AppendIJson = "blockfile:appendijson"
|
||||
@@ -28,6 +29,7 @@ var CommandToTypeMap = map[string]reflect.Type{
|
||||
BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}),
|
||||
BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}),
|
||||
BlockCommand_SetMeta: reflect.TypeOf(BlockSetMetaCommand{}),
|
||||
BlockCommand_GetMeta: reflect.TypeOf(BlockGetMetaCommand{}),
|
||||
BlockCommand_Message: reflect.TypeOf(BlockMessageCommand{}),
|
||||
BlockCommand_AppendBlockFile: reflect.TypeOf(BlockAppendFileCommand{}),
|
||||
BlockCommand_AppendIJson: reflect.TypeOf(BlockAppendIJsonCommand{}),
|
||||
@@ -98,8 +100,19 @@ func (svc *BlockSetViewCommand) GetCommand() string {
|
||||
return BlockCommand_SetView
|
||||
}
|
||||
|
||||
type BlockGetMetaCommand struct {
|
||||
Command string `json:"command" tstype:"\"getmeta\""`
|
||||
RpcId string `json:"rpcid"`
|
||||
OID string `json:"oid"` // allows oref, 8-char oid, or full uuid
|
||||
}
|
||||
|
||||
func (gmc *BlockGetMetaCommand) GetCommand() string {
|
||||
return BlockCommand_GetMeta
|
||||
}
|
||||
|
||||
type BlockSetMetaCommand struct {
|
||||
Command string `json:"command" tstype:"\"setmeta\""`
|
||||
OID string `json:"oid"` // allows oref, 8-char oid, or full uuid
|
||||
Meta map[string]any `json:"meta"`
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
|
||||
const WaveOSC = "23198"
|
||||
const WaveOSCPrefix = "\x1b]" + WaveOSC + ";"
|
||||
const WaveResponseOSC = "23199"
|
||||
const WaveResponseOSCPrefix = "\x1b]" + WaveResponseOSC + ";"
|
||||
|
||||
const HexChars = "0123456789ABCDEF"
|
||||
const BEL = 0x07
|
||||
const ST = 0x9c
|
||||
@@ -25,9 +28,12 @@ var WaveOSCPrefixBytes = []byte(WaveOSCPrefix)
|
||||
// JSON = must escape all ASCII control characters ([\x00-\x1F\x7F])
|
||||
// we can tell the difference between JSON and base64-JSON by the first character: '{' or not
|
||||
|
||||
// for responses (terminal -> program), we'll use OSC 23199
|
||||
// same json format
|
||||
|
||||
func EncodeWaveOSCMessage(cmd BlockCommand) ([]byte, error) {
|
||||
if cmd.GetCommand() == "" {
|
||||
return nil, fmt.Errorf("Command field not set in struct")
|
||||
return nil, fmt.Errorf("command field not set in struct")
|
||||
}
|
||||
ctype, ok := CommandToTypeMap[cmd.GetCommand()]
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user