waveterm/pkg/wshrpc/wshserver/wshserver.go
2024-11-20 18:05:13 -08:00

801 lines
25 KiB
Go

// Copyright 2024, 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"
"regexp"
"strings"
"time"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/filestore"
"github.com/wavetermdev/waveterm/pkg/panichandler"
"github.com/wavetermdev/waveterm/pkg/remote"
"github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
"github.com/wavetermdev/waveterm/pkg/telemetry"
"github.com/wavetermdev/waveterm/pkg/util/envutil"
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
"github.com/wavetermdev/waveterm/pkg/waveai"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wconfig"
"github.com/wavetermdev/waveterm/pkg/wcore"
"github.com/wavetermdev/waveterm/pkg/wlayout"
"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/wstore"
)
var InvalidWslDistroNames = []string{"docker-desktop", "docker-desktop-data"}
type WshServer struct{}
func (*WshServer) WshServerImpl() {}
var WshServerImpl = WshServer{}
func (ws *WshServer) TestCommand(ctx context.Context, data string) error {
defer panichandler.PanicHandler("TestCommand")
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 panichandler.PanicHandler("StreamTestCommand")
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.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
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, filestore.FileOptsType{})
}
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)
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)
}
windowId, err := wstore.DBFindWindowForTabId(ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error finding window for tab: %w", err)
}
if windowId == "" {
return nil, fmt.Errorf("no window found for tab")
}
err = wlayout.QueueLayoutActionForTab(ctx, tabId, waveobj.LayoutActionData{
ActionType: wlayout.LayoutActionDataType_Insert,
BlockId: blockData.OID,
Magnified: data.Magnified,
Focused: true,
})
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 {
if data.ForceRestart {
blockcontroller.StopBlockController(data.BlockId)
}
return blockcontroller.ResyncController(ctx, data.TabId, data.BlockId, data.RtOpts)
}
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) FileCreateCommand(ctx context.Context, data wshrpc.CommandFileCreateData) error {
var fileOpts filestore.FileOptsType
if data.Opts != nil {
fileOpts = *data.Opts
}
err := filestore.WFS.MakeFile(ctx, data.ZoneId, data.FileName, data.Meta, fileOpts)
if err != nil {
return fmt.Errorf("error creating blockfile: %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_Create,
},
})
return nil
}
func (ws *WshServer) FileDeleteCommand(ctx context.Context, data wshrpc.CommandFileData) error {
err := filestore.WFS.DeleteFile(ctx, data.ZoneId, data.FileName)
if err != nil {
return fmt.Errorf("error deleting blockfile: %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_Delete,
},
})
return nil
}
func waveFileToWaveFileInfo(wf *filestore.WaveFile) *wshrpc.WaveFileInfo {
return &wshrpc.WaveFileInfo{
ZoneId: wf.ZoneId,
Name: wf.Name,
Opts: wf.Opts,
Size: wf.Size,
CreatedTs: wf.CreatedTs,
ModTs: wf.ModTs,
Meta: wf.Meta,
}
}
func (ws *WshServer) FileInfoCommand(ctx context.Context, data wshrpc.CommandFileData) (*wshrpc.WaveFileInfo, error) {
fileInfo, err := filestore.WFS.Stat(ctx, data.ZoneId, data.FileName)
if err != nil {
if err == fs.ErrNotExist {
return nil, fmt.Errorf("NOTFOUND: %w", err)
}
return nil, fmt.Errorf("error getting file info: %w", err)
}
return waveFileToWaveFileInfo(fileInfo), nil
}
func (ws *WshServer) FileListCommand(ctx context.Context, data wshrpc.CommandFileListData) ([]*wshrpc.WaveFileInfo, error) {
fileListOrig, err := filestore.WFS.ListFiles(ctx, data.ZoneId)
if err != nil {
return nil, fmt.Errorf("error listing blockfiles: %w", err)
}
var fileList []*wshrpc.WaveFileInfo
for _, wf := range fileListOrig {
fileList = append(fileList, waveFileToWaveFileInfo(wf))
}
if data.Prefix != "" {
var filteredList []*wshrpc.WaveFileInfo
for _, file := range fileList {
if strings.HasPrefix(file.Name, data.Prefix) {
filteredList = append(filteredList, file)
}
}
fileList = filteredList
}
if !data.All {
var filteredList []*wshrpc.WaveFileInfo
dirMap := make(map[string]int64) // the value is max modtime
for _, file := range fileList {
// if there is an extra "/" after the prefix, don't include it
// first strip the prefix
relPath := strings.TrimPrefix(file.Name, data.Prefix)
// then check if there is a "/" after the prefix
if strings.Contains(relPath, "/") {
dirPath := strings.Split(relPath, "/")[0]
modTime := dirMap[dirPath]
if file.ModTs > modTime {
dirMap[dirPath] = file.ModTs
}
continue
}
filteredList = append(filteredList, file)
}
for dir := range dirMap {
filteredList = append(filteredList, &wshrpc.WaveFileInfo{
ZoneId: data.ZoneId,
Name: data.Prefix + dir + "/",
Size: 0,
Meta: nil,
ModTs: dirMap[dir],
CreatedTs: dirMap[dir],
IsDir: true,
})
}
fileList = filteredList
}
if data.Offset > 0 {
if data.Offset >= len(fileList) {
fileList = nil
} else {
fileList = fileList[data.Offset:]
}
}
if data.Limit > 0 {
if data.Limit < len(fileList) {
fileList = fileList[:data.Limit]
}
}
return fileList, nil
}
func (ws *WshServer) FileWriteCommand(ctx context.Context, data wshrpc.CommandFileData) error {
dataBuf, err := base64.StdEncoding.DecodeString(data.Data64)
if err != nil {
return fmt.Errorf("error decoding data64: %w", err)
}
if data.At != nil {
err = filestore.WFS.WriteAt(ctx, data.ZoneId, data.FileName, data.At.Offset, dataBuf)
if err == fs.ErrNotExist {
return fmt.Errorf("NOTFOUND: %w", err)
}
if err != nil {
return fmt.Errorf("error writing to blockfile: %w", err)
}
} else {
err = filestore.WFS.WriteFile(ctx, data.ZoneId, data.FileName, dataBuf)
if err == fs.ErrNotExist {
return fmt.Errorf("NOTFOUND: %w", err)
}
if err != nil {
return fmt.Errorf("error writing to blockfile: %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_Invalidate,
},
})
return nil
}
func (ws *WshServer) FileReadCommand(ctx context.Context, data wshrpc.CommandFileData) (string, error) {
if data.At != nil {
_, dataBuf, err := filestore.WFS.ReadAt(ctx, data.ZoneId, data.FileName, data.At.Offset, data.At.Size)
if err == fs.ErrNotExist {
return "", fmt.Errorf("NOTFOUND: %w", err)
}
if err != nil {
return "", fmt.Errorf("error reading blockfile: %w", err)
}
return base64.StdEncoding.EncodeToString(dataBuf), nil
} else {
_, dataBuf, err := filestore.WFS.ReadFile(ctx, data.ZoneId, data.FileName)
if err == fs.ErrNotExist {
return "", fmt.Errorf("NOTFOUND: %w", err)
}
if err != nil {
return "", fmt.Errorf("error reading blockfile: %w", err)
}
return base64.StdEncoding.EncodeToString(dataBuf), nil
}
}
func (ws *WshServer) FileAppendCommand(ctx context.Context, data wshrpc.CommandFileData) error {
dataBuf, err := base64.StdEncoding.DecodeString(data.Data64)
if err != nil {
return fmt.Errorf("error decoding data64: %w", err)
}
err = filestore.WFS.AppendData(ctx, data.ZoneId, data.FileName, dataBuf)
if err == fs.ErrNotExist {
return fmt.Errorf("NOTFOUND: %w", err)
}
if err != nil {
return fmt.Errorf("error appending to blockfile: %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(dataBuf),
},
})
return nil
}
func (ws *WshServer) FileAppendIJsonCommand(ctx context.Context, data wshrpc.CommandAppendIJsonData) error {
tryCreate := true
if data.FileName == blockcontroller.BlockFile_VDom && tryCreate {
err := filestore.WFS.MakeFile(ctx, data.ZoneId, data.FileName, nil, filestore.FileOptsType{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) DeleteSubBlockCommand(ctx context.Context, data wshrpc.CommandDeleteBlockData) error {
err := wcore.DeleteBlock(ctx, data.BlockId)
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")
}
windowId, err := wstore.DBFindWindowForTabId(ctx, tabId)
if err != nil {
return fmt.Errorf("error finding window for tab: %w", err)
}
if windowId == "" {
return fmt.Errorf("no window found for tab")
}
err = wcore.DeleteBlock(ctx, data.BlockId)
if err != nil {
return fmt.Errorf("error deleting block: %w", err)
}
wlayout.QueueLayoutActionForTab(ctx, tabId, waveobj.LayoutActionData{
ActionType: wlayout.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 wconfig.MetaSettingsType) error {
log.Printf("SETCONFIG: %v\n", data)
return wconfig.SetBaseConfigValue(data.MetaMapType)
}
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 := wsl.GetAllConnStatus()
return rtn, nil
}
func (ws *WshServer) ConnEnsureCommand(ctx context.Context, connName string) error {
if strings.HasPrefix(connName, "wsl://") {
distroName := strings.TrimPrefix(connName, "wsl://")
return wsl.EnsureConnection(ctx, distroName)
}
return conncontroller.EnsureConnection(ctx, connName)
}
func (ws *WshServer) ConnDisconnectCommand(ctx context.Context, connName string) error {
if strings.HasPrefix(connName, "wsl://") {
distroName := strings.TrimPrefix(connName, "wsl://")
conn := wsl.GetWslConn(ctx, distroName, false)
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(ctx, connOpts, false)
if conn == nil {
return fmt.Errorf("connection not found: %s", connName)
}
return conn.Close()
}
func (ws *WshServer) ConnConnectCommand(ctx context.Context, connName string) error {
if strings.HasPrefix(connName, "wsl://") {
distroName := strings.TrimPrefix(connName, "wsl://")
conn := wsl.GetWslConn(ctx, distroName, false)
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(ctx, connOpts, false)
if conn == nil {
return fmt.Errorf("connection not found: %s", connName)
}
return conn.Connect(ctx)
}
func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName string) error {
if strings.HasPrefix(connName, "wsl://") {
distroName := strings.TrimPrefix(connName, "wsl://")
conn := wsl.GetWslConn(ctx, distroName, false)
if conn == nil {
return fmt.Errorf("connection not found: %s", connName)
}
return conn.CheckAndInstallWsh(ctx, connName, &wsl.WshInstallOpts{Force: true, NoUserPrompt: true})
}
connOpts, err := remote.ParseOpts(connName)
if err != nil {
return fmt.Errorf("error parsing connection name: %w", err)
}
conn := conncontroller.GetConn(ctx, connOpts, false)
if conn == nil {
return fmt.Errorf("connection not found: %s", connName)
}
return conn.CheckAndInstallWsh(ctx, connName, &conncontroller.WshInstallOpts{Force: true, NoUserPrompt: true})
}
func (ws *WshServer) ConnListCommand(ctx context.Context) ([]string, error) {
return conncontroller.GetConnectionsList()
}
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
}
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)
}
windowId, err := wstore.DBFindWindowForTabId(ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error finding window for tab: %w", err)
}
return &wshrpc.BlockInfoData{
BlockId: blockId,
TabId: tabId,
WindowId: windowId,
Block: blockData,
}, 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
}
var wshActivityRe = regexp.MustCompile(`^[a-z:#]+$`)
func (ws *WshServer) WshActivityCommand(ctx context.Context, data map[string]int) error {
if len(data) == 0 {
return nil
}
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)
}
}
activityUpdate := telemetry.ActivityUpdate{
WshCmds: data,
}
telemetry.GoUpdateActivityWrap(activityUpdate, "wsh-activity")
return nil
}
func (ws *WshServer) ActivityCommand(ctx context.Context, activity telemetry.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, filestore.FileOptsType{})
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))
}