mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
create block using a blockdef. better controller control. preview that takes a file. atom caching per block. lots of updates
This commit is contained in:
@@ -14,17 +14,13 @@ import (
|
||||
const CommandKey = "command"
|
||||
|
||||
const (
|
||||
BlockCommand_Message = "message"
|
||||
BlockCommand_Run = "run"
|
||||
BlockCommand_Input = "input"
|
||||
BlockCommand_RunShell = "runshell"
|
||||
BlockCommand_Message = "message"
|
||||
BlockCommand_Input = "controller:input"
|
||||
)
|
||||
|
||||
var CommandToTypeMap = map[string]reflect.Type{
|
||||
BlockCommand_Message: reflect.TypeOf(MessageCommand{}),
|
||||
BlockCommand_Run: reflect.TypeOf(RunCommand{}),
|
||||
BlockCommand_Input: reflect.TypeOf(InputCommand{}),
|
||||
BlockCommand_RunShell: reflect.TypeOf(RunShellCommand{}),
|
||||
BlockCommand_Message: reflect.TypeOf(MessageCommand{}),
|
||||
BlockCommand_Input: reflect.TypeOf(InputCommand{}),
|
||||
}
|
||||
|
||||
type BlockCommand interface {
|
||||
@@ -61,16 +57,6 @@ func (mc *MessageCommand) GetCommand() string {
|
||||
return BlockCommand_Message
|
||||
}
|
||||
|
||||
type RunCommand struct {
|
||||
Command string `json:"command"`
|
||||
CmdStr string `json:"cmdstr"`
|
||||
TermSize shellexec.TermSize `json:"termsize"`
|
||||
}
|
||||
|
||||
func (rc *RunCommand) GetCommand() string {
|
||||
return BlockCommand_Run
|
||||
}
|
||||
|
||||
type InputCommand struct {
|
||||
Command string `json:"command"`
|
||||
InputData64 string `json:"inputdata64"`
|
||||
@@ -81,12 +67,3 @@ type InputCommand struct {
|
||||
func (ic *InputCommand) GetCommand() string {
|
||||
return BlockCommand_Input
|
||||
}
|
||||
|
||||
type RunShellCommand struct {
|
||||
Command string `json:"command"`
|
||||
TermSize shellexec.TermSize `json:"termsize"`
|
||||
}
|
||||
|
||||
func (rsc *RunShellCommand) GetCommand() string {
|
||||
return BlockCommand_RunShell
|
||||
}
|
||||
|
||||
@@ -5,30 +5,121 @@ package blockcontroller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/google/uuid"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wavetermdev/thenextwave/pkg/eventbus"
|
||||
"github.com/wavetermdev/thenextwave/pkg/shellexec"
|
||||
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
||||
)
|
||||
|
||||
const (
|
||||
BlockController_Shell = "shell"
|
||||
BlockController_Cmd = "cmd"
|
||||
)
|
||||
|
||||
var globalLock = &sync.Mutex{}
|
||||
var blockControllerMap = make(map[string]*BlockController)
|
||||
var blockDataMap = make(map[string]*BlockData)
|
||||
|
||||
type BlockData struct {
|
||||
Lock *sync.Mutex `json:"-"`
|
||||
BlockId string `json:"blockid"`
|
||||
BlockDef *BlockDef `json:"blockdef"`
|
||||
Controller string `json:"controller"`
|
||||
ControllerStatus string `json:"controllerstatus"`
|
||||
View string `json:"view"`
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
RuntimeOpts *RuntimeOpts `json:"runtimeopts,omitempty"`
|
||||
}
|
||||
|
||||
type FileDef struct {
|
||||
FileType string `json:"filetype,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type BlockDef struct {
|
||||
Controller string `json:"controller"`
|
||||
View string `json:"view,omitempty"`
|
||||
Files map[string]*FileDef `json:"files,omitempty"`
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type WinSize struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
type RuntimeOpts struct {
|
||||
TermSize shellexec.TermSize `json:"termsize,omitempty"`
|
||||
WinSize WinSize `json:"winsize,omitempty"`
|
||||
}
|
||||
|
||||
type BlockController struct {
|
||||
Lock *sync.Mutex
|
||||
BlockId string
|
||||
InputCh chan BlockCommand
|
||||
Lock *sync.Mutex
|
||||
BlockId string
|
||||
BlockDef *BlockDef
|
||||
InputCh chan BlockCommand
|
||||
|
||||
ShellProc *shellexec.ShellProc
|
||||
ShellInputCh chan *InputCommand
|
||||
}
|
||||
|
||||
func jsonDeepCopy(val map[string]any) (map[string]any, error) {
|
||||
barr, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rtn map[string]any
|
||||
err = json.Unmarshal(barr, &rtn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
func CreateBlock(bdef *BlockDef, rtOpts *RuntimeOpts) (*BlockData, error) {
|
||||
blockId := uuid.New().String()
|
||||
blockData := &BlockData{
|
||||
Lock: &sync.Mutex{},
|
||||
BlockId: blockId,
|
||||
BlockDef: bdef,
|
||||
Controller: bdef.Controller,
|
||||
View: bdef.View,
|
||||
RuntimeOpts: rtOpts,
|
||||
}
|
||||
var err error
|
||||
blockData.Meta, err = jsonDeepCopy(bdef.Meta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error copying meta: %w", err)
|
||||
}
|
||||
setBlockData(blockData)
|
||||
if blockData.Controller != "" {
|
||||
StartBlockController(blockId, blockData)
|
||||
}
|
||||
return blockData, nil
|
||||
}
|
||||
|
||||
func GetBlockData(blockId string) *BlockData {
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
return blockDataMap[blockId]
|
||||
}
|
||||
|
||||
func setBlockData(bd *BlockData) {
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
blockDataMap[bd.BlockId] = bd
|
||||
}
|
||||
|
||||
func (bc *BlockController) setShellProc(shellProc *shellexec.ShellProc) error {
|
||||
bc.Lock.Lock()
|
||||
defer bc.Lock.Unlock()
|
||||
@@ -45,34 +136,11 @@ func (bc *BlockController) getShellProc() *shellexec.ShellProc {
|
||||
return bc.ShellProc
|
||||
}
|
||||
|
||||
func (bc *BlockController) DoRunCommand(rc *RunCommand) error {
|
||||
cmdStr := rc.CmdStr
|
||||
shellPath := shellutil.DetectLocalShellPath()
|
||||
ecmd := exec.Command(shellPath, "-c", cmdStr)
|
||||
log.Printf("running shell command: %q %q\n", shellPath, cmdStr)
|
||||
barr, err := shellexec.RunSimpleCmdInPty(ecmd, rc.TermSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for len(barr) > 0 {
|
||||
part := barr
|
||||
if len(part) > 4096 {
|
||||
part = part[:4096]
|
||||
}
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
Name: "block:ptydata",
|
||||
Data: map[string]any{
|
||||
"blockid": bc.BlockId,
|
||||
"blockfile": "main",
|
||||
"ptydata": base64.StdEncoding.EncodeToString(part),
|
||||
},
|
||||
})
|
||||
barr = barr[len(part):]
|
||||
}
|
||||
return nil
|
||||
type RunShellOpts struct {
|
||||
TermSize shellexec.TermSize `json:"termsize,omitempty"`
|
||||
}
|
||||
|
||||
func (bc *BlockController) DoRunShellCommand(rc *RunShellCommand) error {
|
||||
func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts) error {
|
||||
if bc.getShellProc() != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -95,15 +163,18 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellCommand) error {
|
||||
bc.ShellProc = nil
|
||||
bc.ShellInputCh = nil
|
||||
}()
|
||||
seqNum := 0
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
nr, err := bc.ShellProc.Pty.Read(buf)
|
||||
seqNum++
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
Name: "block:ptydata",
|
||||
Data: map[string]any{
|
||||
"blockid": bc.BlockId,
|
||||
"blockfile": "main",
|
||||
"ptydata": base64.StdEncoding.EncodeToString(buf[:nr]),
|
||||
"seqnum": seqNum,
|
||||
},
|
||||
})
|
||||
if err == io.EOF {
|
||||
@@ -127,6 +198,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellCommand) error {
|
||||
bc.ShellProc.Pty.Write(inputBuf[:nw])
|
||||
}
|
||||
if ic.TermSize != nil {
|
||||
log.Printf("SETTERMSIZE: %dx%d\n", ic.TermSize.Rows, ic.TermSize.Cols)
|
||||
err := pty.Setsize(bc.ShellProc.Pty, &pty.Winsize{Rows: uint16(ic.TermSize.Rows), Cols: uint16(ic.TermSize.Cols)})
|
||||
if err != nil {
|
||||
log.Printf("error setting term size: %v\n", err)
|
||||
@@ -137,8 +209,14 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockController) Run() {
|
||||
func (bc *BlockController) Run(bdata *BlockData) {
|
||||
defer func() {
|
||||
bdata.WithLock(func() {
|
||||
// if the controller had an error status, don't change it
|
||||
if bdata.ControllerStatus == "running" {
|
||||
bdata.ControllerStatus = "done"
|
||||
}
|
||||
})
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
Name: "block:done",
|
||||
Data: nil,
|
||||
@@ -147,6 +225,17 @@ func (bc *BlockController) Run() {
|
||||
defer globalLock.Unlock()
|
||||
delete(blockControllerMap, bc.BlockId)
|
||||
}()
|
||||
bdata.WithLock(func() {
|
||||
bdata.ControllerStatus = "running"
|
||||
})
|
||||
|
||||
// only controller is "shell" for now
|
||||
go func() {
|
||||
err := bc.DoRunShellCommand(&RunShellOpts{TermSize: bdata.RuntimeOpts.TermSize})
|
||||
if err != nil {
|
||||
log.Printf("error running shell: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
messageCount := 0
|
||||
for genCmd := range bc.InputCh {
|
||||
@@ -162,42 +251,35 @@ func (bc *BlockController) Run() {
|
||||
"ptydata": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("message %d\r\n", messageCount))),
|
||||
},
|
||||
})
|
||||
case *RunCommand:
|
||||
fmt.Printf("RUN: %s | %q\n", bc.BlockId, cmd.CmdStr)
|
||||
go func() {
|
||||
err := bc.DoRunCommand(cmd)
|
||||
if err != nil {
|
||||
log.Printf("error running shell command: %v\n", err)
|
||||
}
|
||||
}()
|
||||
case *InputCommand:
|
||||
fmt.Printf("INPUT: %s | %q\n", bc.BlockId, cmd.InputData64)
|
||||
if bc.ShellInputCh != nil {
|
||||
bc.ShellInputCh <- cmd
|
||||
}
|
||||
|
||||
case *RunShellCommand:
|
||||
fmt.Printf("RUNSHELL: %s\n", bc.BlockId)
|
||||
if bc.ShellProc != nil {
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
err := bc.DoRunShellCommand(cmd)
|
||||
if err != nil {
|
||||
log.Printf("error running shell: %v\n", err)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
fmt.Printf("unknown command type %T\n", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartBlockController(blockId string) *BlockController {
|
||||
func (b *BlockData) WithLock(f func()) {
|
||||
b.Lock.Lock()
|
||||
defer b.Lock.Unlock()
|
||||
f()
|
||||
}
|
||||
|
||||
func StartBlockController(blockId string, bdata *BlockData) {
|
||||
if bdata.Controller != BlockController_Shell {
|
||||
log.Printf("unknown controller %q\n", bdata.Controller)
|
||||
bdata.WithLock(func() {
|
||||
bdata.ControllerStatus = "error"
|
||||
})
|
||||
return
|
||||
}
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
if existingBC, ok := blockControllerMap[blockId]; ok {
|
||||
return existingBC
|
||||
if _, ok := blockControllerMap[blockId]; ok {
|
||||
return
|
||||
}
|
||||
bc := &BlockController{
|
||||
Lock: &sync.Mutex{},
|
||||
@@ -205,8 +287,7 @@ func StartBlockController(blockId string) *BlockController {
|
||||
InputCh: make(chan BlockCommand),
|
||||
}
|
||||
blockControllerMap[blockId] = bc
|
||||
go bc.Run()
|
||||
return bc
|
||||
go bc.Run(bdata)
|
||||
}
|
||||
|
||||
func GetBlockController(blockId string) *BlockController {
|
||||
|
||||
Reference in New Issue
Block a user