diff --git a/src/app/appconst.ts b/src/app/appconst.ts
index aedb4a539..0acecdfca 100644
--- a/src/app/appconst.ts
+++ b/src/app/appconst.ts
@@ -61,3 +61,6 @@ export enum StatusIndicatorLevel {
Success = 2,
Error = 3,
}
+
+// matches packet.go
+export const ErrorCode_InvalidCwd = "ERRCWD";
diff --git a/src/app/workspace/cmdinput/cmdinput.less b/src/app/workspace/cmdinput/cmdinput.less
index 21a6590f6..0f0778c7b 100644
--- a/src/app/workspace/cmdinput/cmdinput.less
+++ b/src/app/workspace/cmdinput/cmdinput.less
@@ -407,7 +407,7 @@
}
.info-msg {
- color: var(--cmdinput-history-title-color);
+ color: var(--term-blue);
padding-bottom: 2px;
a {
diff --git a/src/app/workspace/cmdinput/infomsg.tsx b/src/app/workspace/cmdinput/infomsg.tsx
index 0f1568358..95b217a71 100644
--- a/src/app/workspace/cmdinput/infomsg.tsx
+++ b/src/app/workspace/cmdinput/infomsg.tsx
@@ -8,7 +8,7 @@ import cn from "classnames";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel } from "@/models";
-import { makeExternLink } from "@/util/util";
+import * as appconst from "@/app/appconst";
dayjs.extend(localizedFormat);
@@ -104,6 +104,9 @@ class InfoMsg extends React.Component<{}, {}> {
[error] {infoMsg.infoerror}
+
+ to reset, run: /reset:cwd
+
);
diff --git a/src/models/model.ts b/src/models/model.ts
index 8f23d56e1..8f54990e1 100644
--- a/src/models/model.ts
+++ b/src/models/model.ts
@@ -1444,7 +1444,11 @@ class Model {
if (err?.message) {
errMsg = err.message;
}
- this.inputModel.flashInfoMsg({ infoerror: errMsg }, null);
+ let info: InfoType = { infoerror: errMsg };
+ if (err?.errorcode) {
+ info.infoerrorcode = err.errorcode;
+ }
+ this.inputModel.flashInfoMsg(info, null);
}
}
diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts
index c7f561b5f..e9b81c03a 100644
--- a/src/types/custom.d.ts
+++ b/src/types/custom.d.ts
@@ -419,6 +419,7 @@ declare global {
infomsghtml?: boolean;
websharelink?: boolean;
infoerror?: string;
+ infoerrorcode?: string;
infolines?: string[];
infocomps?: string[];
infocompsmore?: boolean;
diff --git a/src/util/util.ts b/src/util/util.ts
index f148d242f..a5a9bef6b 100644
--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -50,7 +50,11 @@ function fetchJsonData(resp: any, ctErr: boolean): Promise {
throw rtnErr;
}
if (rtnData?.error) {
- throw new Error(rtnData.error);
+ let err = new Error(rtnData.error);
+ if (rtnData.errorcode) {
+ err["errorcode"] = rtnData.errorcode;
+ }
+ throw err;
}
return rtnData;
});
diff --git a/waveshell/pkg/base/errors.go b/waveshell/pkg/base/errors.go
new file mode 100644
index 000000000..23f573ed4
--- /dev/null
+++ b/waveshell/pkg/base/errors.go
@@ -0,0 +1,37 @@
+package base
+
+import "fmt"
+
+type CodedError struct {
+ ErrorCode string
+ Err error
+}
+
+func (c *CodedError) Error() string {
+ return fmt.Sprintf("%s %s", c.ErrorCode, c.Err.Error())
+}
+
+func (c *CodedError) Unwrap() error {
+ return c.Err
+}
+
+func MakeCodedError(code string, err error) *CodedError {
+ return &CodedError{
+ ErrorCode: code,
+ Err: err,
+ }
+}
+
+func CodedErrorf(code string, format string, args ...interface{}) *CodedError {
+ return &CodedError{
+ ErrorCode: code,
+ Err: fmt.Errorf(format, args...),
+ }
+}
+
+func GetErrorCode(err error) string {
+ if codedErr, ok := err.(*CodedError); ok {
+ return codedErr.ErrorCode
+ }
+ return ""
+}
diff --git a/waveshell/pkg/packet/packet.go b/waveshell/pkg/packet/packet.go
index 72b5231d5..077e9e884 100644
--- a/waveshell/pkg/packet/packet.go
+++ b/waveshell/pkg/packet/packet.go
@@ -72,6 +72,10 @@ const (
ShellType_zsh = "zsh"
)
+const (
+ EC_InvalidCwd = "ERRCWD"
+)
+
const PacketSenderQueueSize = 20
const PacketEOFStr = "EOF"
@@ -491,11 +495,12 @@ func MakeCompGenPacket() *CompGenPacketType {
}
type ResponsePacketType struct {
- Type string `json:"type"`
- RespId string `json:"respid"`
- Success bool `json:"success"`
- Error string `json:"error,omitempty"`
- Data interface{} `json:"data,omitempty"`
+ Type string `json:"type"`
+ RespId string `json:"respid"`
+ Success bool `json:"success"`
+ Error string `json:"error,omitempty"`
+ ErrorCode string `json:"errorcode,omitempty"` // can be used for structured errors
+ Data interface{} `json:"data,omitempty"`
}
func (*ResponsePacketType) GetType() string {
@@ -516,6 +521,9 @@ func (p *ResponsePacketType) Err() error {
}
if !p.Success {
if p.Error != "" {
+ if p.ErrorCode != "" {
+ return &base.CodedError{ErrorCode: p.ErrorCode, Err: errors.New(p.Error)}
+ }
return errors.New(p.Error)
}
return fmt.Errorf("rpc failed")
@@ -531,6 +539,9 @@ func (p *ResponsePacketType) String() string {
}
func MakeErrorResponsePacket(reqId string, err error) *ResponsePacketType {
+ if codedErr, ok := err.(*base.CodedError); ok {
+ return &ResponsePacketType{Type: ResponsePacketStr, RespId: reqId, Error: codedErr.Err.Error(), ErrorCode: codedErr.ErrorCode}
+ }
return &ResponsePacketType{Type: ResponsePacketStr, RespId: reqId, Error: err.Error()}
}
diff --git a/waveshell/pkg/packet/shellstate.go b/waveshell/pkg/packet/shellstate.go
index 31f99ad53..ff3ef76fe 100644
--- a/waveshell/pkg/packet/shellstate.go
+++ b/waveshell/pkg/packet/shellstate.go
@@ -231,10 +231,25 @@ func (sdiff *ShellStateDiff) GetHashVal(force bool) string {
return sdiff.HashVal
}
+func (state ShellState) Dump() {
+ fmt.Printf("ShellState:\n")
+ fmt.Printf(" version: %s\n", state.Version)
+ fmt.Printf(" shelltype: %s\n", state.GetShellType())
+ fmt.Printf(" hashval: %s\n", state.GetHashVal(false))
+ fmt.Printf(" cwd: %s\n", state.Cwd)
+ fmt.Printf(" vars: %d, aliases: %d, funcs: %d\n", len(state.ShellVars), len(state.Aliases), len(state.Funcs))
+ if state.Error != "" {
+ fmt.Printf(" error: %s\n", state.Error)
+ }
+}
+
func (sdiff ShellStateDiff) Dump(vars bool, aliases bool, funcs bool) {
fmt.Printf("ShellStateDiff:\n")
fmt.Printf(" version: %s\n", sdiff.Version)
fmt.Printf(" base: %s\n", sdiff.BaseHash)
+ fmt.Printf(" diffhash: %s\n", sdiff.GetHashVal(false))
+ fmt.Printf(" diffhasharr: %v\n", sdiff.DiffHashArr)
+ fmt.Printf(" cwd: %s\n", sdiff.Cwd)
fmt.Printf(" vars: %d, aliases: %d, funcs: %d\n", len(sdiff.VarsDiff), len(sdiff.AliasesDiff), len(sdiff.FuncsDiff))
if sdiff.Error != "" {
fmt.Printf(" error: %s\n", sdiff.Error)
diff --git a/waveshell/pkg/shellapi/bashapi.go b/waveshell/pkg/shellapi/bashapi.go
index 1dd630c7e..0281284bd 100644
--- a/waveshell/pkg/shellapi/bashapi.go
+++ b/waveshell/pkg/shellapi/bashapi.go
@@ -79,8 +79,22 @@ func (b bashShellApi) MakeShExecCommand(cmdStr string, rcFileName string, usePty
return MakeBashShExecCommand(cmdStr, rcFileName, usePty)
}
-func (b bashShellApi) GetShellState() (*packet.ShellState, error) {
- return GetBashShellState()
+func (b bashShellApi) GetShellState() chan ShellStateOutput {
+ ch := make(chan ShellStateOutput, 1)
+ defer close(ch)
+ ssPk, err := GetBashShellState()
+ if err != nil {
+ ch <- ShellStateOutput{
+ Status: ShellStateOutputStatus_Done,
+ Error: err.Error(),
+ }
+ return ch
+ }
+ ch <- ShellStateOutput{
+ Status: ShellStateOutputStatus_Done,
+ ShellState: ssPk,
+ }
+ return ch
}
func (b bashShellApi) GetBaseShellOpts() string {
diff --git a/waveshell/pkg/shellapi/shellapi.go b/waveshell/pkg/shellapi/shellapi.go
index 5c0565840..8976ba81d 100644
--- a/waveshell/pkg/shellapi/shellapi.go
+++ b/waveshell/pkg/shellapi/shellapi.go
@@ -48,6 +48,17 @@ type RunCommandOpts struct {
CommandStdinFdNum int // needed for SudoWithPass
}
+const (
+ ShellStateOutputStatus_Done = "done"
+)
+
+type ShellStateOutput struct {
+ Status string
+ StderrOutput []byte
+ ShellState *packet.ShellState
+ Error string
+}
+
type ShellApi interface {
GetShellType() string
MakeExitTrap(fdNum int) string
@@ -56,7 +67,7 @@ type ShellApi interface {
GetRemoteShellPath() string
MakeRunCommand(cmdStr string, opts RunCommandOpts) string
MakeShExecCommand(cmdStr string, rcFileName string, usePty bool) *exec.Cmd
- GetShellState() (*packet.ShellState, error)
+ GetShellState() chan ShellStateOutput
GetBaseShellOpts() string
ParseShellStateOutput(output []byte) (*packet.ShellState, error)
MakeRcFileStr(pk *packet.RunPacketType) string
@@ -64,6 +75,9 @@ type ShellApi interface {
ApplyShellStateDiff(oldState *packet.ShellState, diff *packet.ShellStateDiff) (*packet.ShellState, error)
}
+var _ ShellApi = &bashShellApi{}
+var _ ShellApi = &zshShellApi{}
+
func DetectLocalShellType() string {
shellPath := GetMacUserShell()
if shellPath == "" {
diff --git a/waveshell/pkg/shellapi/zshapi.go b/waveshell/pkg/shellapi/zshapi.go
index 4b53dc8c6..9735c6007 100644
--- a/waveshell/pkg/shellapi/zshapi.go
+++ b/waveshell/pkg/shellapi/zshapi.go
@@ -203,20 +203,25 @@ func (z zshShellApi) MakeShExecCommand(cmdStr string, rcFileName string, usePty
return exec.Command(GetLocalZshPath(), "-l", "-i", "-c", cmdStr)
}
-func (z zshShellApi) GetShellState() (*packet.ShellState, error) {
+func (z zshShellApi) GetShellState() chan ShellStateOutput {
ctx, cancelFn := context.WithTimeout(context.Background(), GetStateTimeout)
defer cancelFn()
+ rtnCh := make(chan ShellStateOutput, 1)
+ defer close(rtnCh)
cmdStr := BaseZshOpts + "; " + GetZshShellStateCmd(StateOutputFdNum)
ecmd := exec.CommandContext(ctx, GetLocalZshPath(), "-l", "-i", "-c", cmdStr)
_, outputBytes, err := RunCommandWithExtraFd(ecmd, StateOutputFdNum)
if err != nil {
- return nil, err
+ rtnCh <- ShellStateOutput{Status: ShellStateOutputStatus_Done, Error: err.Error()}
+ return rtnCh
}
rtn, err := z.ParseShellStateOutput(outputBytes)
if err != nil {
- return nil, err
+ rtnCh <- ShellStateOutput{Status: ShellStateOutputStatus_Done, Error: err.Error()}
+ return rtnCh
}
- return rtn, nil
+ rtnCh <- ShellStateOutput{Status: ShellStateOutputStatus_Done, ShellState: rtn}
+ return rtnCh
}
func (z zshShellApi) GetBaseShellOpts() string {
diff --git a/waveshell/pkg/shexec/shexec.go b/waveshell/pkg/shexec/shexec.go
index abb0effad..93446f5d5 100644
--- a/waveshell/pkg/shexec/shexec.go
+++ b/waveshell/pkg/shexec/shexec.go
@@ -7,6 +7,7 @@ import (
"bytes"
"context"
"encoding/base64"
+ "errors"
"fmt"
"io"
"os"
@@ -368,14 +369,18 @@ func ValidateRunPacket(pk *packet.RunPacketType) error {
return fmt.Errorf("cannot detach command, constant rundata input too large len=%d, max=%d", totalRunData, mpio.MaxTotalRunDataSize)
}
}
- if pk.State != nil && pk.State.Cwd != "" {
- realCwd := base.ExpandHomeDir(pk.State.Cwd)
+ if pk.State != nil {
+ pkCwd := pk.State.Cwd
+ if pkCwd == "" {
+ pkCwd = "~"
+ }
+ realCwd := base.ExpandHomeDir(pkCwd)
dirInfo, err := os.Stat(realCwd)
if err != nil {
- return fmt.Errorf("invalid cwd '%s' for command: %v", realCwd, err)
+ return base.CodedErrorf(packet.EC_InvalidCwd, "invalid cwd '%s' for command: %v", realCwd, err)
}
if !dirInfo.IsDir() {
- return fmt.Errorf("invalid cwd '%s' for command, not a directory", realCwd)
+ return base.CodedErrorf(packet.EC_InvalidCwd, "invalid cwd '%s' for command, not a directory", realCwd)
}
}
for _, runData := range pk.RunData {
@@ -896,7 +901,9 @@ func RunCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender, fro
if sapi.GetShellType() == packet.ShellType_zsh {
shellutil.UpdateCmdEnv(cmd.Cmd, map[string]string{"ZDOTDIR": zdotdir})
}
- if state.Cwd != "" {
+ if state.Cwd == "" {
+ cmd.Cmd.Dir = base.ExpandHomeDir("~")
+ } else if state.Cwd != "" {
cmd.Cmd.Dir = base.ExpandHomeDir(state.Cwd)
}
err = ValidateRemoteFds(pk.Fds)
@@ -1237,12 +1244,13 @@ func MakeShellStatePacket(shellType string) (*packet.ShellStatePacketType, error
if err != nil {
return nil, err
}
- shellState, err := sapi.GetShellState()
- if err != nil {
- return nil, err
+ rtnCh := sapi.GetShellState()
+ ssOutput := <-rtnCh
+ if ssOutput.Error != "" {
+ return nil, errors.New(ssOutput.Error)
}
rtn := packet.MakeShellStatePacket()
- rtn.State = shellState
+ rtn.State = ssOutput.ShellState
return rtn, nil
}
diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go
index db9b38b9c..224b4a4e1 100644
--- a/wavesrv/cmd/main-server.go
+++ b/wavesrv/cmd/main-server.go
@@ -623,6 +623,10 @@ func WriteJsonError(w http.ResponseWriter, errVal error) {
w.WriteHeader(200)
errMap := make(map[string]interface{})
errMap["error"] = errVal.Error()
+ errorCode := base.GetErrorCode(errVal)
+ if errorCode != "" {
+ errMap["errorcode"] = errorCode
+ }
barr, _ := json.Marshal(errMap)
w.Write(barr)
}
diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go
index 6351b358c..1c44c74fd 100644
--- a/wavesrv/pkg/cmdrunner/cmdrunner.go
+++ b/wavesrv/pkg/cmdrunner/cmdrunner.go
@@ -167,6 +167,7 @@ func init() {
registerCmdFn("_compgen", CompGenCommand)
registerCmdFn("clear", ClearCommand)
registerCmdFn("reset", RemoteResetCommand)
+ registerCmdFn("reset:cwd", ResetCwdCommand)
registerCmdFn("signal", SignalCommand)
registerCmdFn("sync", SyncCommand)
@@ -190,6 +191,7 @@ func init() {
registerCmdFn("screen:reset", ScreenResetCommand)
registerCmdFn("screen:webshare", ScreenWebShareCommand)
registerCmdFn("screen:reorder", ScreenReorderCommand)
+ registerCmdFn("screen:show", ScreenShowCommand)
registerCmdAlias("remote", RemoteCommand)
registerCmdFn("remote:show", RemoteShowCommand)
@@ -707,7 +709,7 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.U
if rtnErr == nil {
update, rtnErr = HandleCommand(ctxWithHistory, newPk)
} else {
- return nil, fmt.Errorf("error in Eval Meta Command: %v", rtnErr)
+ return nil, fmt.Errorf("error in Eval Meta Command: %w", rtnErr)
}
if !resolveBool(pk.Kwargs["nohist"], false) {
// TODO should this be "pk" or "newPk" (2nd arg)
@@ -3419,6 +3421,40 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
}
}
+func ScreenShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
+ ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
+ if err != nil {
+ return nil, err
+ }
+ screen, err := sstore.GetScreenById(ctx, ids.ScreenId)
+ if err != nil {
+ return nil, fmt.Errorf("cannot get screen: %v", err)
+ }
+ if screen == nil {
+ return nil, fmt.Errorf("screen not found")
+ }
+ statePtr, err := remote.ResolveCurrentScreenStatePtr(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr)
+ if err != nil {
+ return nil, fmt.Errorf("cannot resolve current screen stateptr: %v", err)
+ }
+ var buf bytes.Buffer
+ buf.WriteString(fmt.Sprintf(" %-15s %s\n", "screenid", screen.ScreenId))
+ buf.WriteString(fmt.Sprintf(" %-15s %s\n", "name", screen.Name))
+ buf.WriteString(fmt.Sprintf(" %-15s %d\n", "screenidx", screen.ScreenIdx))
+ buf.WriteString(fmt.Sprintf(" %-15s %s\n", "tabcolor", screen.ScreenOpts.TabColor))
+ buf.WriteString(fmt.Sprintf(" %-15s %s\n", "tabicon", screen.ScreenOpts.TabIcon))
+ buf.WriteString(fmt.Sprintf(" %-15s %d\n", "selectedline", screen.SelectedLine))
+ buf.WriteString(fmt.Sprintf(" %-15s %s\n", "curremote", GetFullRemoteDisplayName(&screen.CurRemote, &ids.Remote.RState)))
+ buf.WriteString(fmt.Sprintf(" %-15s %s\n", "stateptr-base", statePtr.BaseHash))
+ buf.WriteString(fmt.Sprintf(" %-15s %v\n", "stateptr-diff", statePtr.DiffHashArr))
+ update := scbus.MakeUpdatePacket()
+ update.AddUpdate(sstore.InfoMsgType{
+ InfoTitle: "screen info",
+ InfoLines: splitLinesForInfo(buf.String()),
+ })
+ return update, nil
+}
+
func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session)
if err != nil {
@@ -3600,6 +3636,33 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
return update, nil
}
+func ResetCwdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
+ ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote)
+ if err != nil {
+ return nil, err
+ }
+ statePtr, err := remote.ResolveCurrentScreenStatePtr(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr)
+ if err != nil {
+ return nil, err
+ }
+ stateDiff, err := sstore.GetCurStateDiffFromPtr(ctx, statePtr)
+ if err != nil {
+ return nil, err
+ }
+ feState := ids.Remote.FeState
+ feState["cwd"] = "~"
+ stateDiff.Cwd = "~"
+ stateDiff.GetHashVal(true)
+ remoteInst, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr, feState, nil, stateDiff)
+ if err != nil {
+ return nil, fmt.Errorf("could not update remote state: %v", err)
+ }
+ update := scbus.MakeUpdatePacket()
+ update.AddUpdate(sstore.MakeSessionUpdateForRemote(ids.SessionId, remoteInst), sstore.InteractiveUpdate(pk.Interactive))
+ update.AddUpdate(sstore.InfoMsgType{InfoMsg: "reset cwd to ~"})
+ return update, nil
+}
+
func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
if err != nil {
@@ -3958,13 +4021,13 @@ func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
NoCreateCmdPtyFile: true,
}
cmd, callback, err := remote.RunCommand(ctx, rcOpts, runPacket)
- sstore.IncrementNumRunningCmds(cmd.ScreenId, 1)
if callback != nil {
defer callback()
}
if err != nil {
return nil, err
}
+ sstore.IncrementNumRunningCmds(cmd.ScreenId, 1)
newTs := time.Now().UnixMilli()
err = sstore.UpdateCmdForRestart(ctx, runPacket.CK, newTs, cmd.CmdPid, cmd.RemotePid, convertTermOpts(runPacket.TermOpts))
if err != nil {
diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go
index 2ca02e7dc..9d51f9d73 100644
--- a/wavesrv/pkg/remote/remote.go
+++ b/wavesrv/pkg/remote/remote.go
@@ -1919,6 +1919,25 @@ func (msh *MShellProc) removePendingStateCmd(screenId string, rptr sstore.Remote
}
}
+func ResolveCurrentScreenStatePtr(ctx context.Context, sessionId string, screenId string, remotePtr sstore.RemotePtrType) (*sstore.ShellStatePtr, error) {
+ statePtr, err := sstore.GetRemoteStatePtr(ctx, sessionId, screenId, remotePtr)
+ if err != nil {
+ return nil, fmt.Errorf("cannot get current connection stateptr: %w", err)
+ }
+ if statePtr == nil {
+ msh := GetRemoteById(remotePtr.RemoteId)
+ err := msh.EnsureShellType(ctx, msh.GetShellPref()) // make sure shellType is initialized
+ if err != nil {
+ return nil, err
+ }
+ statePtr = msh.GetDefaultStatePtr(msh.GetShellPref())
+ if statePtr == nil {
+ return nil, fmt.Errorf("no valid default connection stateptr")
+ }
+ }
+ return statePtr, nil
+}
+
type RunCommandOpts struct {
SessionId string
ScreenId string
@@ -1993,19 +2012,9 @@ func RunCommand(ctx context.Context, rcOpts RunCommandOpts, runPacket *packet.Ru
statePtr = rcOpts.StatePtr
} else {
var err error
- statePtr, err = sstore.GetRemoteStatePtr(ctx, sessionId, screenId, remotePtr)
+ statePtr, err = ResolveCurrentScreenStatePtr(ctx, sessionId, screenId, remotePtr)
if err != nil {
- return nil, nil, fmt.Errorf("cannot get current connection stateptr: %w", err)
- }
- }
- if statePtr == nil { // can be null if there is no remote-instance (screen has unchanged state from default)
- err := msh.EnsureShellType(ctx, msh.GetShellPref()) // make sure shellType is initialized
- if err != nil {
- return nil, nil, err
- }
- statePtr = msh.GetDefaultStatePtr(msh.GetShellPref())
- if statePtr == nil {
- return nil, nil, fmt.Errorf("cannot run command, no valid connection stateptr")
+ return nil, nil, fmt.Errorf("cannot run command: %w", err)
}
}
currentState, err := sstore.GetFullState(ctx, *statePtr)
@@ -2049,7 +2058,7 @@ func RunCommand(ctx context.Context, rcOpts RunCommandOpts, runPacket *packet.Ru
return nil, nil, fmt.Errorf("invalid response received from server for run packet: %s", packet.AsString(rtnPk))
}
if respPk.Error != "" {
- return nil, nil, errors.New(respPk.Error)
+ return nil, nil, respPk.Err()
}
return nil, nil, fmt.Errorf("invalid response received from server for run packet: %s", packet.AsString(rtnPk))
}
diff --git a/wavesrv/pkg/sstore/dbops.go b/wavesrv/pkg/sstore/dbops.go
index 6b562a558..e4d6685dd 100644
--- a/wavesrv/pkg/sstore/dbops.go
+++ b/wavesrv/pkg/sstore/dbops.go
@@ -2023,6 +2023,71 @@ func StoreStateDiff(ctx context.Context, diff *packet.ShellStateDiff) error {
return nil
}
+func GetStateBaseVersion(ctx context.Context, baseHash string) (string, error) {
+ return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
+ query := `SELECT version FROM state_base WHERE basehash = ?`
+ rtn := tx.GetString(query, baseHash)
+ return rtn, nil
+ })
+}
+
+func GetCurStateDiffFromPtr(ctx context.Context, ssPtr *ShellStatePtr) (*packet.ShellStateDiff, error) {
+ if ssPtr == nil {
+ return nil, fmt.Errorf("cannot resolve state, empty stateptr")
+ }
+ if len(ssPtr.DiffHashArr) == 0 {
+ baseVersion, err := GetStateBaseVersion(ctx, ssPtr.BaseHash)
+ if err != nil {
+ return nil, fmt.Errorf("cannot get base version: %v", err)
+ }
+ // return an empty diff
+ return &packet.ShellStateDiff{Version: baseVersion, BaseHash: ssPtr.BaseHash}, nil
+ }
+ lastDiffHash := ssPtr.DiffHashArr[len(ssPtr.DiffHashArr)-1]
+ return GetStateDiff(ctx, lastDiffHash)
+}
+
+func GetStateBase(ctx context.Context, baseHash string) (*packet.ShellState, error) {
+ stateBase, txErr := WithTxRtn(ctx, func(tx *TxWrap) (*StateBase, error) {
+ var stateBase StateBase
+ query := `SELECT * FROM state_base WHERE basehash = ?`
+ found := tx.Get(&stateBase, query, baseHash)
+ if !found {
+ return nil, fmt.Errorf("StateBase %s not found", baseHash)
+ }
+ return &stateBase, nil
+ })
+ if txErr != nil {
+ return nil, txErr
+ }
+ state := &packet.ShellState{}
+ err := state.DecodeShellState(stateBase.Data)
+ if err != nil {
+ return nil, err
+ }
+ return state, nil
+}
+
+func GetStateDiff(ctx context.Context, diffHash string) (*packet.ShellStateDiff, error) {
+ stateDiff, txErr := WithTxRtn(ctx, func(tx *TxWrap) (*StateDiff, error) {
+ query := `SELECT * FROM state_diff WHERE diffhash = ?`
+ stateDiff := dbutil.GetMapGen[*StateDiff](tx, query, diffHash)
+ if stateDiff == nil {
+ return nil, fmt.Errorf("StateDiff %s not found", diffHash)
+ }
+ return stateDiff, nil
+ })
+ if txErr != nil {
+ return nil, txErr
+ }
+ state := &packet.ShellStateDiff{}
+ err := state.DecodeShellStateDiff(stateDiff.Data)
+ if err != nil {
+ return nil, err
+ }
+ return state, nil
+}
+
// returns error when not found
func GetFullState(ctx context.Context, ssPtr ShellStatePtr) (*packet.ShellState, error) {
var state *packet.ShellState
diff --git a/wavesrv/pkg/sstore/updatetypes.go b/wavesrv/pkg/sstore/updatetypes.go
index 6b9eb0606..a4cea78a5 100644
--- a/wavesrv/pkg/sstore/updatetypes.go
+++ b/wavesrv/pkg/sstore/updatetypes.go
@@ -48,6 +48,7 @@ func (CmdLineUpdate) GetType() string {
type InfoMsgType struct {
InfoTitle string `json:"infotitle"`
InfoError string `json:"infoerror,omitempty"`
+ InfoErrorCode string `json:"infoerrorcode,omitempty"`
InfoMsg string `json:"infomsg,omitempty"`
InfoMsgHtml bool `json:"infomsghtml,omitempty"`
WebShareLink bool `json:"websharelink,omitempty"`