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"`