mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-01 03:51:59 +01:00
976 lines
31 KiB
Go
976 lines
31 KiB
Go
// Copyright 2025, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package wshserver
|
|
|
|
// this file contains the implementation of the wsh server methods
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/skratchdot/open-golang/open"
|
|
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
|
|
"github.com/wavetermdev/waveterm/pkg/blocklogger"
|
|
"github.com/wavetermdev/waveterm/pkg/filestore"
|
|
"github.com/wavetermdev/waveterm/pkg/genconn"
|
|
"github.com/wavetermdev/waveterm/pkg/panichandler"
|
|
"github.com/wavetermdev/waveterm/pkg/remote"
|
|
"github.com/wavetermdev/waveterm/pkg/remote/awsconn"
|
|
"github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
|
|
"github.com/wavetermdev/waveterm/pkg/remote/fileshare"
|
|
"github.com/wavetermdev/waveterm/pkg/suggestion"
|
|
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
|
"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
|
|
"github.com/wavetermdev/waveterm/pkg/util/envutil"
|
|
"github.com/wavetermdev/waveterm/pkg/util/iochan/iochantypes"
|
|
"github.com/wavetermdev/waveterm/pkg/util/iterfn"
|
|
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
|
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
|
|
"github.com/wavetermdev/waveterm/pkg/util/wavefileutil"
|
|
"github.com/wavetermdev/waveterm/pkg/waveai"
|
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
|
"github.com/wavetermdev/waveterm/pkg/wcloud"
|
|
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
|
"github.com/wavetermdev/waveterm/pkg/wcore"
|
|
"github.com/wavetermdev/waveterm/pkg/wps"
|
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
|
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
|
"github.com/wavetermdev/waveterm/pkg/wsl"
|
|
"github.com/wavetermdev/waveterm/pkg/wslconn"
|
|
"github.com/wavetermdev/waveterm/pkg/wstore"
|
|
)
|
|
|
|
var InvalidWslDistroNames = []string{"docker-desktop", "docker-desktop-data"}
|
|
|
|
type WshServer struct{}
|
|
|
|
func (*WshServer) WshServerImpl() {}
|
|
|
|
var WshServerImpl = WshServer{}
|
|
|
|
// TODO remove this after implementing in multiproxy, just for wsl
|
|
func (ws *WshServer) AuthenticateTokenCommand(ctx context.Context, data wshrpc.CommandAuthenticateTokenData) (wshrpc.CommandAuthenticateRtnData, error) {
|
|
entry := shellutil.GetAndRemoveTokenSwapEntry(data.Token)
|
|
if entry == nil {
|
|
return wshrpc.CommandAuthenticateRtnData{}, fmt.Errorf("invalid token")
|
|
}
|
|
rtn := wshrpc.CommandAuthenticateRtnData{
|
|
Env: entry.Env,
|
|
InitScriptText: entry.ScriptText,
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
func (ws *WshServer) TestCommand(ctx context.Context, data string) error {
|
|
defer func() {
|
|
panichandler.PanicHandler("TestCommand", recover())
|
|
}()
|
|
rpcSource := wshutil.GetRpcSourceFromContext(ctx)
|
|
log.Printf("TEST src:%s | %s\n", rpcSource, data)
|
|
return nil
|
|
}
|
|
|
|
// for testing
|
|
func (ws *WshServer) MessageCommand(ctx context.Context, data wshrpc.CommandMessageData) error {
|
|
log.Printf("MESSAGE: %s | %q\n", data.ORef, data.Message)
|
|
return nil
|
|
}
|
|
|
|
// for testing
|
|
func (ws *WshServer) StreamTestCommand(ctx context.Context) chan wshrpc.RespOrErrorUnion[int] {
|
|
rtn := make(chan wshrpc.RespOrErrorUnion[int])
|
|
go func() {
|
|
defer func() {
|
|
panichandler.PanicHandler("StreamTestCommand", recover())
|
|
}()
|
|
for i := 1; i <= 5; i++ {
|
|
rtn <- wshrpc.RespOrErrorUnion[int]{Response: i}
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
close(rtn)
|
|
}()
|
|
return rtn
|
|
}
|
|
|
|
func (ws *WshServer) StreamWaveAiCommand(ctx context.Context, request wshrpc.WaveAIStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.WaveAIPacketType] {
|
|
return waveai.RunAICommand(ctx, request)
|
|
}
|
|
|
|
func MakePlotData(ctx context.Context, blockId string) error {
|
|
block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
viewName := block.Meta.GetString(waveobj.MetaKey_View, "")
|
|
if viewName != "cpuplot" && viewName != "sysinfo" {
|
|
return fmt.Errorf("invalid view type: %s", viewName)
|
|
}
|
|
return filestore.WFS.MakeFile(ctx, blockId, "cpuplotdata", nil, wshrpc.FileOpts{})
|
|
}
|
|
|
|
func SavePlotData(ctx context.Context, blockId string, history string) error {
|
|
block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
viewName := block.Meta.GetString(waveobj.MetaKey_View, "")
|
|
if viewName != "cpuplot" && viewName != "sysinfo" {
|
|
return fmt.Errorf("invalid view type: %s", viewName)
|
|
}
|
|
// todo: interpret the data being passed
|
|
// for now, this is just to throw an error if the block was closed
|
|
historyBytes, err := json.Marshal(history)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to serialize plot data: %v", err)
|
|
}
|
|
// ignore MakeFile error (already exists is ok)
|
|
return filestore.WFS.WriteFile(ctx, blockId, "cpuplotdata", historyBytes)
|
|
}
|
|
|
|
func (ws *WshServer) GetMetaCommand(ctx context.Context, data wshrpc.CommandGetMetaData) (waveobj.MetaMapType, error) {
|
|
obj, err := wstore.DBGetORef(ctx, data.ORef)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting object: %w", err)
|
|
}
|
|
if obj == nil {
|
|
return nil, fmt.Errorf("object not found: %s", data.ORef)
|
|
}
|
|
return waveobj.GetMeta(obj), nil
|
|
}
|
|
|
|
func (ws *WshServer) SetMetaCommand(ctx context.Context, data wshrpc.CommandSetMetaData) error {
|
|
log.Printf("SetMetaCommand: %s | %v\n", data.ORef, data.Meta)
|
|
oref := data.ORef
|
|
err := wstore.UpdateObjectMeta(ctx, oref, data.Meta, false)
|
|
if err != nil {
|
|
return fmt.Errorf("error updating object meta: %w", err)
|
|
}
|
|
sendWaveObjUpdate(oref)
|
|
return nil
|
|
}
|
|
|
|
func sendWaveObjUpdate(oref waveobj.ORef) {
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancelFn()
|
|
// send a waveobj:update event
|
|
waveObj, err := wstore.DBGetORef(ctx, oref)
|
|
if err != nil {
|
|
log.Printf("error getting object for update event: %v", err)
|
|
return
|
|
}
|
|
wps.Broker.Publish(wps.WaveEvent{
|
|
Event: wps.Event_WaveObjUpdate,
|
|
Scopes: []string{oref.String()},
|
|
Data: waveobj.WaveObjUpdate{
|
|
UpdateType: waveobj.UpdateType_Update,
|
|
OType: waveObj.GetOType(),
|
|
OID: waveobj.GetOID(waveObj),
|
|
Obj: waveObj,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (ws *WshServer) ResolveIdsCommand(ctx context.Context, data wshrpc.CommandResolveIdsData) (wshrpc.CommandResolveIdsRtnData, error) {
|
|
rtn := wshrpc.CommandResolveIdsRtnData{}
|
|
rtn.ResolvedIds = make(map[string]waveobj.ORef)
|
|
var firstErr error
|
|
for _, simpleId := range data.Ids {
|
|
oref, err := resolveSimpleId(ctx, data, simpleId)
|
|
if err != nil {
|
|
if firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
continue
|
|
}
|
|
if oref == nil {
|
|
continue
|
|
}
|
|
rtn.ResolvedIds[simpleId] = *oref
|
|
}
|
|
if firstErr != nil && len(data.Ids) == 1 {
|
|
return rtn, firstErr
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.CommandCreateBlockData) (*waveobj.ORef, error) {
|
|
ctx = waveobj.ContextWithUpdates(ctx)
|
|
tabId := data.TabId
|
|
blockData, err := wcore.CreateBlock(ctx, tabId, data.BlockDef, data.RtOpts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating block: %w", err)
|
|
}
|
|
var layoutAction *waveobj.LayoutActionData
|
|
if data.TargetBlockId != "" {
|
|
switch data.TargetAction {
|
|
case "replace":
|
|
layoutAction = &waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_Replace,
|
|
TargetBlockId: data.TargetBlockId,
|
|
BlockId: blockData.OID,
|
|
Focused: true,
|
|
}
|
|
err = wcore.DeleteBlock(ctx, data.TargetBlockId, false)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error deleting block (trying to do block replace): %w", err)
|
|
}
|
|
case "splitright":
|
|
layoutAction = &waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_SplitHorizontal,
|
|
BlockId: blockData.OID,
|
|
TargetBlockId: data.TargetBlockId,
|
|
Position: "after",
|
|
}
|
|
case "splitleft":
|
|
layoutAction = &waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_SplitHorizontal,
|
|
BlockId: blockData.OID,
|
|
TargetBlockId: data.TargetBlockId,
|
|
Position: "before",
|
|
}
|
|
case "splitup":
|
|
layoutAction = &waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_SplitVertical,
|
|
BlockId: blockData.OID,
|
|
TargetBlockId: data.TargetBlockId,
|
|
Position: "before",
|
|
}
|
|
case "splitdown":
|
|
layoutAction = &waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_SplitVertical,
|
|
BlockId: blockData.OID,
|
|
TargetBlockId: data.TargetBlockId,
|
|
Position: "after",
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("invalid target action: %s", data.TargetAction)
|
|
}
|
|
} else {
|
|
layoutAction = &waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_Insert,
|
|
BlockId: blockData.OID,
|
|
Magnified: data.Magnified,
|
|
Ephemeral: data.Ephemeral,
|
|
Focused: true,
|
|
}
|
|
}
|
|
err = wcore.QueueLayoutActionForTab(ctx, tabId, *layoutAction)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error queuing layout action: %w", err)
|
|
}
|
|
updates := waveobj.ContextGetUpdatesRtn(ctx)
|
|
wps.Broker.SendUpdateEvents(updates)
|
|
return &waveobj.ORef{OType: waveobj.OType_Block, OID: blockData.OID}, nil
|
|
}
|
|
|
|
func (ws *WshServer) CreateSubBlockCommand(ctx context.Context, data wshrpc.CommandCreateSubBlockData) (*waveobj.ORef, error) {
|
|
parentBlockId := data.ParentBlockId
|
|
blockData, err := wcore.CreateSubBlock(ctx, parentBlockId, data.BlockDef)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating block: %w", err)
|
|
}
|
|
blockRef := &waveobj.ORef{OType: waveobj.OType_Block, OID: blockData.OID}
|
|
return blockRef, nil
|
|
}
|
|
|
|
func (ws *WshServer) SetViewCommand(ctx context.Context, data wshrpc.CommandBlockSetViewData) error {
|
|
log.Printf("SETVIEW: %s | %q\n", data.BlockId, data.View)
|
|
ctx = waveobj.ContextWithUpdates(ctx)
|
|
block, err := wstore.DBGet[*waveobj.Block](ctx, data.BlockId)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting block: %w", err)
|
|
}
|
|
block.Meta[waveobj.MetaKey_View] = data.View
|
|
err = wstore.DBUpdate(ctx, block)
|
|
if err != nil {
|
|
return fmt.Errorf("error updating block: %w", err)
|
|
}
|
|
updates := waveobj.ContextGetUpdatesRtn(ctx)
|
|
wps.Broker.SendUpdateEvents(updates)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) ControllerStopCommand(ctx context.Context, blockId string) error {
|
|
bc := blockcontroller.GetBlockController(blockId)
|
|
if bc == nil {
|
|
return nil
|
|
}
|
|
bc.StopShellProc(true)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) ControllerResyncCommand(ctx context.Context, data wshrpc.CommandControllerResyncData) error {
|
|
ctx = genconn.ContextWithConnData(ctx, data.BlockId)
|
|
ctx = termCtxWithLogBlockId(ctx, data.BlockId)
|
|
return blockcontroller.ResyncController(ctx, data.TabId, data.BlockId, data.RtOpts, data.ForceRestart)
|
|
}
|
|
|
|
func (ws *WshServer) ControllerInputCommand(ctx context.Context, data wshrpc.CommandBlockInputData) error {
|
|
bc := blockcontroller.GetBlockController(data.BlockId)
|
|
if bc == nil {
|
|
return fmt.Errorf("block controller not found for block %q", data.BlockId)
|
|
}
|
|
inputUnion := &blockcontroller.BlockInputUnion{
|
|
SigName: data.SigName,
|
|
TermSize: data.TermSize,
|
|
}
|
|
if len(data.InputData64) > 0 {
|
|
inputBuf := make([]byte, base64.StdEncoding.DecodedLen(len(data.InputData64)))
|
|
nw, err := base64.StdEncoding.Decode(inputBuf, []byte(data.InputData64))
|
|
if err != nil {
|
|
return fmt.Errorf("error decoding input data: %w", err)
|
|
}
|
|
inputUnion.InputData = inputBuf[:nw]
|
|
}
|
|
return bc.SendInput(inputUnion)
|
|
}
|
|
|
|
func (ws *WshServer) ControllerAppendOutputCommand(ctx context.Context, data wshrpc.CommandControllerAppendOutputData) error {
|
|
outputBuf := make([]byte, base64.StdEncoding.DecodedLen(len(data.Data64)))
|
|
nw, err := base64.StdEncoding.Decode(outputBuf, []byte(data.Data64))
|
|
if err != nil {
|
|
return fmt.Errorf("error decoding output data: %w", err)
|
|
}
|
|
err = blockcontroller.HandleAppendBlockFile(data.BlockId, wavebase.BlockFile_Term, outputBuf[:nw])
|
|
if err != nil {
|
|
return fmt.Errorf("error appending to block file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) FileCreateCommand(ctx context.Context, data wshrpc.FileData) error {
|
|
data.Data64 = ""
|
|
err := fileshare.PutFile(ctx, data)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) FileMkdirCommand(ctx context.Context, data wshrpc.FileData) error {
|
|
return fileshare.Mkdir(ctx, data.Info.Path)
|
|
}
|
|
|
|
func (ws *WshServer) FileDeleteCommand(ctx context.Context, data wshrpc.CommandDeleteFileData) error {
|
|
return fileshare.Delete(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileInfoCommand(ctx context.Context, data wshrpc.FileData) (*wshrpc.FileInfo, error) {
|
|
return fileshare.Stat(ctx, data.Info.Path)
|
|
}
|
|
|
|
func (ws *WshServer) FileListCommand(ctx context.Context, data wshrpc.FileListData) ([]*wshrpc.FileInfo, error) {
|
|
return fileshare.ListEntries(ctx, data.Path, data.Opts)
|
|
}
|
|
|
|
func (ws *WshServer) FileListStreamCommand(ctx context.Context, data wshrpc.FileListData) <-chan wshrpc.RespOrErrorUnion[wshrpc.CommandRemoteListEntriesRtnData] {
|
|
return fileshare.ListEntriesStream(ctx, data.Path, data.Opts)
|
|
}
|
|
|
|
func (ws *WshServer) FileWriteCommand(ctx context.Context, data wshrpc.FileData) error {
|
|
return fileshare.PutFile(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileReadCommand(ctx context.Context, data wshrpc.FileData) (*wshrpc.FileData, error) {
|
|
return fileshare.Read(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileReadStreamCommand(ctx context.Context, data wshrpc.FileData) <-chan wshrpc.RespOrErrorUnion[wshrpc.FileData] {
|
|
return fileshare.ReadStream(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileCopyCommand(ctx context.Context, data wshrpc.CommandFileCopyData) error {
|
|
return fileshare.Copy(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileMoveCommand(ctx context.Context, data wshrpc.CommandFileCopyData) error {
|
|
return fileshare.Move(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileStreamTarCommand(ctx context.Context, data wshrpc.CommandRemoteStreamTarData) <-chan wshrpc.RespOrErrorUnion[iochantypes.Packet] {
|
|
return fileshare.ReadTarStream(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileAppendCommand(ctx context.Context, data wshrpc.FileData) error {
|
|
return fileshare.Append(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) FileAppendIJsonCommand(ctx context.Context, data wshrpc.CommandAppendIJsonData) error {
|
|
tryCreate := true
|
|
if data.FileName == wavebase.BlockFile_VDom && tryCreate {
|
|
err := filestore.WFS.MakeFile(ctx, data.ZoneId, data.FileName, nil, wshrpc.FileOpts{MaxSize: blockcontroller.DefaultHtmlMaxFileSize, IJson: true})
|
|
if err != nil && err != fs.ErrExist {
|
|
return fmt.Errorf("error creating blockfile[vdom]: %w", err)
|
|
}
|
|
}
|
|
err := filestore.WFS.AppendIJson(ctx, data.ZoneId, data.FileName, data.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("error appending to blockfile(ijson): %w", err)
|
|
}
|
|
wps.Broker.Publish(wps.WaveEvent{
|
|
Event: wps.Event_BlockFile,
|
|
Scopes: []string{waveobj.MakeORef(waveobj.OType_Block, data.ZoneId).String()},
|
|
Data: &wps.WSFileEventData{
|
|
ZoneId: data.ZoneId,
|
|
FileName: data.FileName,
|
|
FileOp: wps.FileOp_Append,
|
|
Data64: base64.StdEncoding.EncodeToString([]byte("{}")),
|
|
},
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) FileJoinCommand(ctx context.Context, paths []string) (*wshrpc.FileInfo, error) {
|
|
if len(paths) < 2 {
|
|
if len(paths) == 0 {
|
|
return nil, fmt.Errorf("no paths provided")
|
|
}
|
|
return fileshare.Stat(ctx, paths[0])
|
|
}
|
|
return fileshare.Join(ctx, paths[0], paths[1:]...)
|
|
}
|
|
|
|
func (ws *WshServer) FileShareCapabilityCommand(ctx context.Context, path string) (wshrpc.FileShareCapability, error) {
|
|
return fileshare.GetCapability(ctx, path)
|
|
}
|
|
|
|
func (ws *WshServer) DeleteSubBlockCommand(ctx context.Context, data wshrpc.CommandDeleteBlockData) error {
|
|
err := wcore.DeleteBlock(ctx, data.BlockId, false)
|
|
if err != nil {
|
|
return fmt.Errorf("error deleting block: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) DeleteBlockCommand(ctx context.Context, data wshrpc.CommandDeleteBlockData) error {
|
|
ctx = waveobj.ContextWithUpdates(ctx)
|
|
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
|
|
if err != nil {
|
|
return fmt.Errorf("error finding tab for block: %w", err)
|
|
}
|
|
if tabId == "" {
|
|
return fmt.Errorf("no tab found for block")
|
|
}
|
|
err = wcore.DeleteBlock(ctx, data.BlockId, true)
|
|
if err != nil {
|
|
return fmt.Errorf("error deleting block: %w", err)
|
|
}
|
|
wcore.QueueLayoutActionForTab(ctx, tabId, waveobj.LayoutActionData{
|
|
ActionType: wcore.LayoutActionDataType_Remove,
|
|
BlockId: data.BlockId,
|
|
})
|
|
updates := waveobj.ContextGetUpdatesRtn(ctx)
|
|
wps.Broker.SendUpdateEvents(updates)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) WaitForRouteCommand(ctx context.Context, data wshrpc.CommandWaitForRouteData) (bool, error) {
|
|
waitCtx, cancelFn := context.WithTimeout(ctx, time.Duration(data.WaitMs)*time.Millisecond)
|
|
defer cancelFn()
|
|
err := wshutil.DefaultRouter.WaitForRegister(waitCtx, data.RouteId)
|
|
return err == nil, nil
|
|
}
|
|
|
|
func (ws *WshServer) EventRecvCommand(ctx context.Context, data wps.WaveEvent) error {
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) EventPublishCommand(ctx context.Context, data wps.WaveEvent) error {
|
|
rpcSource := wshutil.GetRpcSourceFromContext(ctx)
|
|
if rpcSource == "" {
|
|
return fmt.Errorf("no rpc source set")
|
|
}
|
|
if data.Sender == "" {
|
|
data.Sender = rpcSource
|
|
}
|
|
wps.Broker.Publish(data)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) EventSubCommand(ctx context.Context, data wps.SubscriptionRequest) error {
|
|
rpcSource := wshutil.GetRpcSourceFromContext(ctx)
|
|
if rpcSource == "" {
|
|
return fmt.Errorf("no rpc source set")
|
|
}
|
|
wps.Broker.Subscribe(rpcSource, data)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) EventUnsubCommand(ctx context.Context, data string) error {
|
|
rpcSource := wshutil.GetRpcSourceFromContext(ctx)
|
|
if rpcSource == "" {
|
|
return fmt.Errorf("no rpc source set")
|
|
}
|
|
wps.Broker.Unsubscribe(rpcSource, data)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) EventUnsubAllCommand(ctx context.Context) error {
|
|
rpcSource := wshutil.GetRpcSourceFromContext(ctx)
|
|
if rpcSource == "" {
|
|
return fmt.Errorf("no rpc source set")
|
|
}
|
|
wps.Broker.UnsubscribeAll(rpcSource)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) EventReadHistoryCommand(ctx context.Context, data wshrpc.CommandEventReadHistoryData) ([]*wps.WaveEvent, error) {
|
|
events := wps.Broker.ReadEventHistory(data.Event, data.Scope, data.MaxItems)
|
|
return events, nil
|
|
}
|
|
|
|
func (ws *WshServer) SetConfigCommand(ctx context.Context, data wshrpc.MetaSettingsType) error {
|
|
log.Printf("SETCONFIG: %v\n", data)
|
|
return wconfig.SetBaseConfigValue(data.MetaMapType)
|
|
}
|
|
|
|
func (ws *WshServer) SetConnectionsConfigCommand(ctx context.Context, data wshrpc.ConnConfigRequest) error {
|
|
log.Printf("SET CONNECTIONS CONFIG: %v\n", data)
|
|
return wconfig.SetConnectionsConfigValue(data.Host, data.MetaMapType)
|
|
}
|
|
|
|
func (ws *WshServer) GetFullConfigCommand(ctx context.Context) (wconfig.FullConfigType, error) {
|
|
watcher := wconfig.GetWatcher()
|
|
return watcher.GetFullConfig(), nil
|
|
}
|
|
|
|
func (ws *WshServer) ConnStatusCommand(ctx context.Context) ([]wshrpc.ConnStatus, error) {
|
|
rtn := conncontroller.GetAllConnStatus()
|
|
return rtn, nil
|
|
}
|
|
|
|
func (ws *WshServer) WslStatusCommand(ctx context.Context) ([]wshrpc.ConnStatus, error) {
|
|
rtn := wslconn.GetAllConnStatus()
|
|
return rtn, nil
|
|
}
|
|
|
|
func termCtxWithLogBlockId(ctx context.Context, logBlockId string) context.Context {
|
|
if logBlockId == "" {
|
|
return ctx
|
|
}
|
|
block, err := wstore.DBMustGet[*waveobj.Block](ctx, logBlockId)
|
|
if err != nil {
|
|
return ctx
|
|
}
|
|
connDebug := block.Meta.GetString(waveobj.MetaKey_TermConnDebug, "")
|
|
if connDebug == "" {
|
|
return ctx
|
|
}
|
|
return blocklogger.ContextWithLogBlockId(ctx, logBlockId, connDebug == "debug")
|
|
}
|
|
|
|
func (ws *WshServer) ConnEnsureCommand(ctx context.Context, data wshrpc.ConnExtData) error {
|
|
// TODO: if we add proper wsh connections via aws, we'll need to handle that here
|
|
if strings.HasPrefix(data.ConnName, "aws:") {
|
|
profiles := awsconn.ParseProfiles()
|
|
for profile := range profiles {
|
|
if strings.HasPrefix(data.ConnName, profile) {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
ctx = genconn.ContextWithConnData(ctx, data.LogBlockId)
|
|
ctx = termCtxWithLogBlockId(ctx, data.LogBlockId)
|
|
if strings.HasPrefix(data.ConnName, "wsl://") {
|
|
distroName := strings.TrimPrefix(data.ConnName, "wsl://")
|
|
return wslconn.EnsureConnection(ctx, distroName)
|
|
}
|
|
return conncontroller.EnsureConnection(ctx, data.ConnName)
|
|
}
|
|
|
|
func (ws *WshServer) ConnDisconnectCommand(ctx context.Context, connName string) error {
|
|
// TODO: if we add proper wsh connections via aws, we'll need to handle that here
|
|
if strings.HasPrefix(connName, "aws:") {
|
|
return nil
|
|
}
|
|
if strings.HasPrefix(connName, "wsl://") {
|
|
distroName := strings.TrimPrefix(connName, "wsl://")
|
|
conn := wslconn.GetWslConn(distroName)
|
|
if conn == nil {
|
|
return fmt.Errorf("distro not found: %s", connName)
|
|
}
|
|
return conn.Close()
|
|
}
|
|
connOpts, err := remote.ParseOpts(connName)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing connection name: %w", err)
|
|
}
|
|
conn := conncontroller.GetConn(connOpts)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
return conn.Close()
|
|
}
|
|
|
|
func (ws *WshServer) ConnConnectCommand(ctx context.Context, connRequest wshrpc.ConnRequest) error {
|
|
// TODO: if we add proper wsh connections via aws, we'll need to handle that here
|
|
if strings.HasPrefix(connRequest.Host, "aws:") {
|
|
return nil
|
|
}
|
|
ctx = genconn.ContextWithConnData(ctx, connRequest.LogBlockId)
|
|
ctx = termCtxWithLogBlockId(ctx, connRequest.LogBlockId)
|
|
connName := connRequest.Host
|
|
if strings.HasPrefix(connName, "wsl://") {
|
|
distroName := strings.TrimPrefix(connName, "wsl://")
|
|
conn := wslconn.GetWslConn(distroName)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
return conn.Connect(ctx)
|
|
}
|
|
connOpts, err := remote.ParseOpts(connName)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing connection name: %w", err)
|
|
}
|
|
conn := conncontroller.GetConn(connOpts)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
return conn.Connect(ctx, &connRequest.Keywords)
|
|
}
|
|
|
|
func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, data wshrpc.ConnExtData) error {
|
|
// TODO: if we add proper wsh connections via aws, we'll need to handle that here
|
|
if strings.HasPrefix(data.ConnName, "aws:") {
|
|
return nil
|
|
}
|
|
ctx = genconn.ContextWithConnData(ctx, data.LogBlockId)
|
|
ctx = termCtxWithLogBlockId(ctx, data.LogBlockId)
|
|
connName := data.ConnName
|
|
if strings.HasPrefix(connName, "wsl://") {
|
|
distroName := strings.TrimPrefix(connName, "wsl://")
|
|
conn := wslconn.GetWslConn(distroName)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
return conn.InstallWsh(ctx, "")
|
|
}
|
|
connOpts, err := remote.ParseOpts(connName)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing connection name: %w", err)
|
|
}
|
|
conn := conncontroller.GetConn(connOpts)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
return conn.InstallWsh(ctx, "")
|
|
}
|
|
|
|
func (ws *WshServer) ConnUpdateWshCommand(ctx context.Context, remoteInfo wshrpc.RemoteInfo) (bool, error) {
|
|
handler := wshutil.GetRpcResponseHandlerFromContext(ctx)
|
|
if handler == nil {
|
|
return false, fmt.Errorf("could not determine handler from context")
|
|
}
|
|
connName := handler.GetRpcContext().Conn
|
|
if connName == "" {
|
|
return false, fmt.Errorf("invalid remote info: missing connection name")
|
|
}
|
|
|
|
log.Printf("checking wsh version for connection %s (current: %s)", connName, remoteInfo.ClientVersion)
|
|
upToDate, _, _, err := conncontroller.IsWshVersionUpToDate(ctx, remoteInfo.ClientVersion)
|
|
if err != nil {
|
|
return false, fmt.Errorf("unable to compare wsh version: %w", err)
|
|
}
|
|
if upToDate {
|
|
// no need to update
|
|
log.Printf("wsh is already up to date for connection %s", connName)
|
|
return false, nil
|
|
}
|
|
|
|
// todo: need to add user input code here for validation
|
|
|
|
if strings.HasPrefix(connName, "wsl://") {
|
|
return false, fmt.Errorf("connupdatewshcommand is not supported for wsl connections")
|
|
}
|
|
connOpts, err := remote.ParseOpts(connName)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error parsing connection name: %w", err)
|
|
}
|
|
conn := conncontroller.GetConn(connOpts)
|
|
if conn == nil {
|
|
return false, fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
err = conn.UpdateWsh(ctx, connName, &remoteInfo)
|
|
if err != nil {
|
|
return false, fmt.Errorf("wsh update failed for connection %s: %w", connName, err)
|
|
}
|
|
|
|
// todo: need to add code for modifying configs?
|
|
return true, nil
|
|
}
|
|
|
|
func (ws *WshServer) ConnListCommand(ctx context.Context) ([]string, error) {
|
|
return conncontroller.GetConnectionsList()
|
|
}
|
|
|
|
func (ws *WshServer) ConnListAWSCommand(ctx context.Context) ([]string, error) {
|
|
profilesMap := awsconn.ParseProfiles()
|
|
return iterfn.MapKeysToSorted(profilesMap), nil
|
|
}
|
|
|
|
func (ws *WshServer) WslListCommand(ctx context.Context) ([]string, error) {
|
|
distros, err := wsl.RegisteredDistros(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var distroNames []string
|
|
for _, distro := range distros {
|
|
distroName := distro.Name()
|
|
if utilfn.ContainsStr(InvalidWslDistroNames, distroName) {
|
|
continue
|
|
}
|
|
distroNames = append(distroNames, distroName)
|
|
}
|
|
return distroNames, nil
|
|
}
|
|
|
|
func (ws *WshServer) WslDefaultDistroCommand(ctx context.Context) (string, error) {
|
|
distro, ok, err := wsl.DefaultDistro(ctx)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to determine default distro: %w", err)
|
|
}
|
|
if !ok {
|
|
return "", fmt.Errorf("unable to determine default distro")
|
|
}
|
|
return distro.Name(), nil
|
|
}
|
|
|
|
/**
|
|
* Dismisses the WshFail Command in runtime memory on the backend
|
|
*/
|
|
func (ws *WshServer) DismissWshFailCommand(ctx context.Context, connName string) error {
|
|
if strings.HasPrefix(connName, "wsl://") {
|
|
distroName := strings.TrimPrefix(connName, "wsl://")
|
|
conn := wslconn.GetWslConn(distroName)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection not found: %s", connName)
|
|
}
|
|
conn.ClearWshError()
|
|
conn.FireConnChangeEvent()
|
|
return nil
|
|
}
|
|
opts, err := remote.ParseOpts(connName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn := conncontroller.GetConn(opts)
|
|
if conn == nil {
|
|
return fmt.Errorf("connection %s not found", connName)
|
|
}
|
|
conn.ClearWshError()
|
|
conn.FireConnChangeEvent()
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) BlockInfoCommand(ctx context.Context, blockId string) (*wshrpc.BlockInfoData, error) {
|
|
blockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting block: %w", err)
|
|
}
|
|
tabId, err := wstore.DBFindTabForBlockId(ctx, blockId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error finding tab for block: %w", err)
|
|
}
|
|
workspaceId, err := wstore.DBFindWorkspaceForTabId(ctx, tabId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error finding window for tab: %w", err)
|
|
}
|
|
fileList, err := filestore.WFS.ListFiles(ctx, blockId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing blockfiles: %w", err)
|
|
}
|
|
fileInfoList := wavefileutil.WaveFileListToFileInfoList(fileList)
|
|
return &wshrpc.BlockInfoData{
|
|
BlockId: blockId,
|
|
TabId: tabId,
|
|
WorkspaceId: workspaceId,
|
|
Block: blockData,
|
|
Files: fileInfoList,
|
|
}, nil
|
|
}
|
|
|
|
func (ws *WshServer) WaveInfoCommand(ctx context.Context) (*wshrpc.WaveInfoData, error) {
|
|
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting client: %w", err)
|
|
}
|
|
return &wshrpc.WaveInfoData{
|
|
Version: wavebase.WaveVersion,
|
|
ClientId: client.OID,
|
|
BuildTime: wavebase.BuildTime,
|
|
ConfigDir: wavebase.GetWaveConfigDir(),
|
|
DataDir: wavebase.GetWaveDataDir(),
|
|
}, nil
|
|
}
|
|
|
|
func (ws *WshServer) WorkspaceListCommand(ctx context.Context) ([]wshrpc.WorkspaceInfoData, error) {
|
|
workspaceList, err := wcore.ListWorkspaces(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing workspaces: %w", err)
|
|
}
|
|
var rtn []wshrpc.WorkspaceInfoData
|
|
for _, workspaceEntry := range workspaceList {
|
|
workspaceData, err := wcore.GetWorkspace(ctx, workspaceEntry.WorkspaceId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting workspace: %w", err)
|
|
}
|
|
rtn = append(rtn, wshrpc.WorkspaceInfoData{
|
|
WindowId: workspaceEntry.WindowId,
|
|
WorkspaceData: workspaceData,
|
|
})
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
func (ws *WshServer) RecordTEventCommand(ctx context.Context, data telemetrydata.TEvent) error {
|
|
err := telemetry.RecordTEvent(ctx, &data)
|
|
if err != nil {
|
|
log.Printf("error recording telemetry event: %v", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (ws WshServer) SendTelemetryCommand(ctx context.Context) error {
|
|
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("getting client data for telemetry: %v", err)
|
|
}
|
|
return wcloud.SendAllTelemetry(ctx, client.OID)
|
|
}
|
|
|
|
var wshActivityRe = regexp.MustCompile(`^[a-z:#]+$`)
|
|
|
|
func (ws *WshServer) WshActivityCommand(ctx context.Context, data map[string]int) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
props := telemetrydata.TEventProps{}
|
|
for key, value := range data {
|
|
if len(key) > 20 {
|
|
delete(data, key)
|
|
}
|
|
if !wshActivityRe.MatchString(key) {
|
|
delete(data, key)
|
|
}
|
|
if value != 1 {
|
|
delete(data, key)
|
|
}
|
|
if strings.HasSuffix(key, "#error") {
|
|
props.WshHadError = true
|
|
} else {
|
|
props.WshCmd = key
|
|
}
|
|
}
|
|
activityUpdate := wshrpc.ActivityUpdate{
|
|
WshCmds: data,
|
|
}
|
|
telemetry.GoUpdateActivityWrap(activityUpdate, "wsh-activity")
|
|
telemetry.GoRecordTEventWrap(&telemetrydata.TEvent{
|
|
Event: "wsh:run",
|
|
Props: props,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) ActivityCommand(ctx context.Context, activity wshrpc.ActivityUpdate) error {
|
|
telemetry.GoUpdateActivityWrap(activity, "wshrpc-activity")
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) GetVarCommand(ctx context.Context, data wshrpc.CommandVarData) (*wshrpc.CommandVarResponseData, error) {
|
|
_, fileData, err := filestore.WFS.ReadFile(ctx, data.ZoneId, data.FileName)
|
|
if err == fs.ErrNotExist {
|
|
return &wshrpc.CommandVarResponseData{Key: data.Key, Exists: false}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading blockfile: %w", err)
|
|
}
|
|
envMap := envutil.EnvToMap(string(fileData))
|
|
value, ok := envMap[data.Key]
|
|
return &wshrpc.CommandVarResponseData{Key: data.Key, Exists: ok, Val: value}, nil
|
|
}
|
|
|
|
func (ws *WshServer) SetVarCommand(ctx context.Context, data wshrpc.CommandVarData) error {
|
|
_, fileData, err := filestore.WFS.ReadFile(ctx, data.ZoneId, data.FileName)
|
|
if err == fs.ErrNotExist {
|
|
fileData = []byte{}
|
|
err = filestore.WFS.MakeFile(ctx, data.ZoneId, data.FileName, nil, wshrpc.FileOpts{})
|
|
if err != nil {
|
|
return fmt.Errorf("error creating blockfile: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("error reading blockfile: %w", err)
|
|
}
|
|
envMap := envutil.EnvToMap(string(fileData))
|
|
if data.Remove {
|
|
delete(envMap, data.Key)
|
|
} else {
|
|
envMap[data.Key] = data.Val
|
|
}
|
|
envStr := envutil.MapToEnv(envMap)
|
|
return filestore.WFS.WriteFile(ctx, data.ZoneId, data.FileName, []byte(envStr))
|
|
}
|
|
|
|
func (ws *WshServer) PathCommand(ctx context.Context, data wshrpc.PathCommandData) (string, error) {
|
|
pathType := data.PathType
|
|
openInternal := data.Open
|
|
openExternal := data.OpenExternal
|
|
var path string
|
|
switch pathType {
|
|
case "config":
|
|
path = wavebase.GetWaveConfigDir()
|
|
case "data":
|
|
path = wavebase.GetWaveDataDir()
|
|
case "log":
|
|
path = filepath.Join(wavebase.GetWaveDataDir(), "waveapp.log")
|
|
}
|
|
|
|
if openInternal && openExternal {
|
|
return "", fmt.Errorf("open and openExternal cannot both be true")
|
|
}
|
|
|
|
if openInternal {
|
|
_, err := ws.CreateBlockCommand(ctx, wshrpc.CommandCreateBlockData{BlockDef: &waveobj.BlockDef{Meta: map[string]any{
|
|
waveobj.MetaKey_View: "preview",
|
|
waveobj.MetaKey_File: path,
|
|
}}, Ephemeral: true, TabId: data.TabId})
|
|
|
|
if err != nil {
|
|
return path, fmt.Errorf("error opening path: %w", err)
|
|
}
|
|
} else if openExternal {
|
|
err := open.Run(path)
|
|
if err != nil {
|
|
return path, fmt.Errorf("error opening path: %w", err)
|
|
}
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
func (ws *WshServer) FetchSuggestionsCommand(ctx context.Context, data wshrpc.FetchSuggestionsData) (*wshrpc.FetchSuggestionsResponse, error) {
|
|
return suggestion.FetchSuggestions(ctx, data)
|
|
}
|
|
|
|
func (ws *WshServer) DisposeSuggestionsCommand(ctx context.Context, widgetId string) error {
|
|
suggestion.DisposeSuggestions(ctx, widgetId)
|
|
return nil
|
|
}
|
|
|
|
func (ws *WshServer) GetTabCommand(ctx context.Context, tabId string) (*waveobj.Tab, error) {
|
|
tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting tab: %w", err)
|
|
}
|
|
return tab, nil
|
|
}
|