From 03dd6b1a7e179e8e565a7b0b0bd3ef4d2756db28 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 29 Aug 2022 16:31:06 -0700 Subject: [PATCH] pass 'uicontext' with fecmd, use that to resolve ids instead of kwargs. other bug fixes --- cmd/main-server.go | 2 +- pkg/cmdrunner/cmdrunner.go | 167 ++++++++++++++++--------------------- pkg/cmdrunner/resolver.go | 131 +++++++++++++++++------------ pkg/cmdrunner/shparse.go | 1 + pkg/remote/remote.go | 46 +++++++--- pkg/scpacket/scpacket.go | 8 ++ pkg/sstore/dbops.go | 13 +++ pkg/sstore/updatebus.go | 21 +++-- 8 files changed, 215 insertions(+), 174 deletions(-) diff --git a/cmd/main-server.go b/cmd/main-server.go index b61627b4..9ff85225 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -158,7 +158,7 @@ func HandleGetRemotes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Vary", "Origin") w.Header().Set("Cache-Control", "no-cache") - remotes := remote.GetAllRemoteState() + remotes := remote.GetAllRemoteRuntimeState() WriteJsonSuccess(w, remotes) return } diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index c6825cb3..71866927 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -141,28 +141,22 @@ func resolveInt(arg string, def int) (int, error) { } func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected) if err != nil { return nil, fmt.Errorf("/run error: %w", err) } - if !ids.RState.IsConnected() { - return nil, fmt.Errorf("cannot run command, remote '%s' not connected", ids.RemoteDisplayName) - } - if ids.RemoteState == nil { - return nil, fmt.Errorf("cannot run command, remote '%s' has no state", ids.RemoteDisplayName) - } cmdId := uuid.New().String() cmdStr := firstArg(pk) runPacket := packet.MakeRunPacket() runPacket.ReqId = uuid.New().String() runPacket.CK = base.MakeCommandKey(ids.SessionId, cmdId) - runPacket.Cwd = ids.RemoteState.Cwd - runPacket.Env0 = ids.RemoteState.Env0 + runPacket.Cwd = ids.Remote.RemoteState.Cwd + runPacket.Env0 = ids.Remote.RemoteState.Env0 runPacket.EnvComplete = true runPacket.UsePty = true runPacket.TermOpts = &packet.TermOpts{Rows: remote.DefaultTermRows, Cols: remote.DefaultTermCols, Term: remote.DefaultTerm} runPacket.Command = strings.TrimSpace(cmdStr) - cmd, err := remote.RunCommand(ctx, cmdId, ids.RemotePtr, ids.RemoteState, runPacket) + cmd, err := remote.RunCommand(ctx, cmdId, ids.Remote.RemotePtr, ids.Remote.RemoteState, runPacket) if err != nil { return nil, err } @@ -175,7 +169,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, update sstore.UpdatePacket, isMetaCmd bool, hadError bool) error { cmdStr := firstArg(pk) - ids, err := resolveIds(ctx, pk, R_Session|R_Screen|R_Window) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) if err != nil { return err } @@ -194,7 +188,7 @@ func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, update IsMetaCmd: isMetaCmd, } if !isMetaCmd && rptr != nil { - hitem.Remote = *rptr + hitem.Remote = ids.Remote.RemotePtr } err = sstore.InsertHistoryItem(ctx, hitem) if err != nil { @@ -223,7 +217,7 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. } func ScreenCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Screen) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, fmt.Errorf("/screen:close cannot close screen: %w", err) } @@ -235,7 +229,7 @@ func ScreenCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( } func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session) + ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, fmt.Errorf("/screen:open cannot open screen: %w", err) } @@ -255,7 +249,7 @@ func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s } func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Screen) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err } @@ -310,7 +304,7 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session) + ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, fmt.Errorf("/screen cannot switch to screen: %w", err) } @@ -330,17 +324,11 @@ func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor } func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected) if err != nil { - return nil, err + return nil, fmt.Errorf("cannot unset: %v", err) } - if !ids.RState.IsConnected() { - return nil, fmt.Errorf("remote '%s' is not connected, cannot unset", ids.RemoteDisplayName) - } - if ids.RemoteState == nil { - return nil, fmt.Errorf("remote '%s' state is not available, cannot unset", ids.RemoteDisplayName) - } - envMap := shexec.ParseEnv0(ids.RemoteState.Env0) + envMap := shexec.ParseEnv0(ids.Remote.RemoteState.Env0) unsetVars := make(map[string]bool) for _, argStr := range pk.Args { eqIdx := strings.Index(argStr, "=") @@ -350,16 +338,16 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore delete(envMap, argStr) unsetVars[argStr] = true } - state := *ids.RemoteState + state := *ids.Remote.RemoteState state.Env0 = shexec.MakeEnv0(envMap) - remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state) + remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.Remote.RemotePtr, state) if err != nil { return nil, err } update := sstore.ModelUpdate{ Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteDisplayName, formatStrs(mapToStrs(unsetVars), "and", false)), + InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.Remote.DisplayName, formatStrs(mapToStrs(unsetVars), "and", false)), TimeoutMs: 2000, }, } @@ -367,15 +355,11 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore } func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) if err != nil { return nil, err } - curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) - if curRemote == nil { - return nil, fmt.Errorf("invalid remote '%s' (not found)", ids.RemoteDisplayName) - } - state := curRemote.GetRemoteState() + state := ids.Remote.RState var buf bytes.Buffer buf.WriteString(fmt.Sprintf(" %-15s %s\n", "type", state.RemoteType)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "remoteid", state.RemoteId)) @@ -384,19 +368,19 @@ func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s buf.WriteString(fmt.Sprintf(" %-15s %s\n", "canonicalname", state.RemoteCanonicalName)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "status", state.Status)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "connectmode", state.ConnectMode)) - if ids.RemoteState != nil { - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "cwd", ids.RemoteState.Cwd)) + if ids.Remote.RemoteState != nil { + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "cwd", ids.Remote.RemoteState.Cwd)) } return sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteDisplayName), + InfoTitle: fmt.Sprintf("show remote '%s' info", ids.Remote.DisplayName), InfoLines: splitLinesForInfo(buf.String()), }, }, nil } func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - stateArr := remote.GetAllRemoteState() + stateArr := remote.GetAllRemoteRuntimeState() var buf bytes.Buffer for _, rstate := range stateArr { var name string @@ -420,17 +404,11 @@ func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor } func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected) if err != nil { - return nil, err + return nil, fmt.Errorf("cannot setenv: %v", err) } - if !ids.RState.IsConnected() { - return nil, fmt.Errorf("remote '%s' is not connected, cannot setenv", ids.RemoteDisplayName) - } - if ids.RemoteState == nil { - return nil, fmt.Errorf("remote '%s' state is not available, cannot setenv", ids.RemoteDisplayName) - } - envMap := shexec.ParseEnv0(ids.RemoteState.Env0) + envMap := shexec.ParseEnv0(ids.Remote.RemoteState.Env0) if len(pk.Args) == 0 { var infoLines []string for varName, varVal := range envMap { @@ -439,7 +417,7 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor } update := sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("environment for [%s] remote", ids.RemoteDisplayName), + InfoTitle: fmt.Sprintf("environment for [%s] remote", ids.Remote.DisplayName), InfoLines: infoLines, }, } @@ -456,16 +434,16 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor envMap[envName] = envVal setVars[envName] = true } - state := *ids.RemoteState + state := *ids.Remote.RemoteState state.Env0 = shexec.MakeEnv0(envMap) - remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state) + remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.Remote.RemotePtr, state) if err != nil { return nil, err } update := sstore.ModelUpdate{ Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteDisplayName, formatStrs(mapToStrs(setVars), "and", false)), + InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.Remote.DisplayName, formatStrs(mapToStrs(setVars), "and", false)), TimeoutMs: 2000, }, } @@ -473,7 +451,7 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor } func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window) if err != nil { return nil, fmt.Errorf("/cr error: %w", err) } @@ -507,37 +485,27 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up } func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected) if err != nil { return nil, fmt.Errorf("/cd error: %w", err) } newDir := firstArg(pk) - curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) - if curRemote == nil { - return nil, fmt.Errorf("remote '%s' not found, cannot change directory", ids.RemoteDisplayName) - } - if !ids.RState.IsConnected() { - return nil, fmt.Errorf("remote '%s' is not connected, cannot change directory", ids.RemoteDisplayName) - } - if ids.RemoteState == nil { - return nil, fmt.Errorf("remote '%s' state is not available, cannot change directory", ids.RemoteDisplayName) - } if newDir == "" { return sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteDisplayName, ids.RemoteState.Cwd), + InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.Remote.DisplayName, ids.Remote.RemoteState.Cwd), }, }, nil } - newDir, err = ids.RState.ExpandHomeDir(newDir) + newDir, err = ids.Remote.RState.ExpandHomeDir(newDir) if err != nil { return nil, err } if !strings.HasPrefix(newDir, "/") { - if ids.RemoteState == nil { + if ids.Remote.RemoteState == nil { return nil, fmt.Errorf("/cd error: cannot get current remote directory (can only cd with absolute path)") } - newDir = path.Join(ids.RemoteState.Cwd, newDir) + newDir = path.Join(ids.Remote.RemoteState.Cwd, newDir) newDir, err = filepath.Abs(newDir) if err != nil { return nil, fmt.Errorf("/cd error: error canonicalizing new directory: %w", err) @@ -546,23 +514,23 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up cdPacket := packet.MakeCdPacket() cdPacket.ReqId = uuid.New().String() cdPacket.Dir = newDir - resp, err := curRemote.PacketRpc(ctx, cdPacket) + resp, err := ids.Remote.MShell.PacketRpc(ctx, cdPacket) if err != nil { return nil, err } if err = resp.Err(); err != nil { return nil, err } - state := *ids.RemoteState + state := *ids.Remote.RemoteState state.Cwd = newDir - remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state) + remoteInst, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.Remote.RemotePtr, state) if err != nil { return nil, err } update := sstore.ModelUpdate{ - Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), + Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remoteInst), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteDisplayName, newDir), + InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.Remote.DisplayName, newDir), TimeoutMs: 2000, }, } @@ -682,10 +650,15 @@ func longestPrefix(root string, comps []string) string { return lcp } -func doMetaCompGen(ctx context.Context, ids resolvedIds, prefix string, forDisplay bool) ([]string, bool, error) { - comps, hasMore, err := doCompGen(ctx, ids, prefix, "file", forDisplay) - if err != nil { - return nil, false, err +func doMetaCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix string, forDisplay bool) ([]string, bool, error) { + ids, err := resolveUiIds(ctx, pk, 0) // best effort + var comps []string + var hasMore bool + if ids.Remote != nil && ids.Remote.RState.IsConnected() { + comps, hasMore, err = doCompGen(ctx, pk, prefix, "file", forDisplay) + if err != nil { + return nil, false, err + } } validCommands := getValidCommands() for _, cmd := range validCommands { @@ -700,26 +673,23 @@ func doMetaCompGen(ctx context.Context, ids resolvedIds, prefix string, forDispl return comps, hasMore, nil } -func doCompGen(ctx context.Context, ids resolvedIds, prefix string, compType string, forDisplay bool) ([]string, bool, error) { +func doCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix string, compType string, forDisplay bool) ([]string, bool, error) { if compType == "metacommand" { - return doMetaCompGen(ctx, ids, prefix, forDisplay) + return doMetaCompGen(ctx, pk, prefix, forDisplay) } if !packet.IsValidCompGenType(compType) { return nil, false, fmt.Errorf("/compgen invalid type '%s'", compType) } + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected) + if err != nil { + return nil, false, fmt.Errorf("compgen error: %w", err) + } cgPacket := packet.MakeCompGenPacket() cgPacket.ReqId = uuid.New().String() cgPacket.CompType = compType cgPacket.Prefix = prefix - if ids.RemoteState == nil { - return nil, false, fmt.Errorf("/compgen invalid remote state") - } - cgPacket.Cwd = ids.RemoteState.Cwd - curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) - if curRemote == nil { - return nil, false, fmt.Errorf("invalid remote, cannot execute command") - } - resp, err := curRemote.PacketRpc(ctx, cgPacket) + cgPacket.Cwd = ids.Remote.RemoteState.Cwd + resp, err := ids.Remote.MShell.PacketRpc(ctx, cgPacket) if err != nil { return nil, false, err } @@ -732,10 +702,6 @@ func doCompGen(ctx context.Context, ids resolvedIds, prefix string, compType str } func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) - if err != nil { - return nil, fmt.Errorf("/compgen error: %w", err) - } cmdLine := firstArg(pk) pos := len(cmdLine) if pk.Kwargs["comppos"] != "" { @@ -766,7 +732,7 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if len(parts) > 0 { lastPart = parts[len(parts)-1] } - comps, hasMore, err := doCompGen(ctx, ids, lastPart, compType, showComps) + comps, hasMore, err := doCompGen(ctx, pk, lastPart, compType, showComps) if err != nil { return nil, err } @@ -777,7 +743,7 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Window) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Window) if err != nil { return nil, fmt.Errorf("/comment error: %w", err) } @@ -866,7 +832,7 @@ func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( } func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session) + ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, err } @@ -914,6 +880,10 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } + err = sstore.SetActiveSessionId(ctx, ritem.Id) + if err != nil { + return nil, err + } update := sstore.ModelUpdate{ ActiveSessionId: ritem.Id, Info: &sstore.InfoMsgType{ @@ -925,7 +895,7 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Screen|R_Window) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) if err != nil { return nil, err } @@ -943,7 +913,7 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore const DefaultMaxHistoryItems = 10000 func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -963,7 +933,7 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } var filteredItems []*sstore.HistoryItemType for _, hitem := range hitems { - if hitem.ScreenId == ids.ScreenId && hitem.WindowId == ids.WindowId && (hitem.Remote == ids.RemotePtr || hitem.IsMetaCmd) { + if hitem.ScreenId == ids.ScreenId && hitem.WindowId == ids.WindowId && (hitem.Remote == ids.Remote.RemotePtr || hitem.IsMetaCmd) { filteredItems = append(filteredItems, hitem) } } @@ -986,6 +956,9 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto InfoMsg: fmt.Sprintf("history, limited to current session, screen, window, and remote (maxitems=%d)", maxItems), InfoLines: splitLinesForInfo(buf.String()), } + update.History = &sstore.HistoryInfoType{ + Items: filteredItems, + } return update, nil } diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index 845140df..f1a92e4b 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -14,24 +14,26 @@ import ( ) const ( - R_Session = 1 - R_Screen = 2 - R_Window = 4 - R_Remote = 8 - R_SessionOpt = 16 - R_ScreenOpt = 32 - R_WindowOpt = 64 - R_RemoteOpt = 128 + R_Session = 1 + R_Screen = 2 + R_Window = 4 + R_Remote = 8 + R_RemoteConnected = 8 + 16 ) type resolvedIds struct { - SessionId string - ScreenId string - WindowId string - RemotePtr sstore.RemotePtrType - RemoteState *sstore.RemoteState - RemoteDisplayName string - RState remote.RemoteState + SessionId string + ScreenId string + WindowId string + Remote *ResolvedRemote +} + +type ResolvedRemote struct { + DisplayName string + RemotePtr sstore.RemotePtrType + RemoteState *sstore.RemoteState + RState remote.RemoteRuntimeState + MShell *remote.MShellProc } type ResolveItem struct { @@ -116,52 +118,45 @@ func resolveByPosition(items []ResolveItem, curId string, posStr string) *Resolv return &items[pos-1] } -func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) { +func resolveUiIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) { + fmt.Printf("resolve-ui-ids: %#v\n", pk) rtn := resolvedIds{} - if rtype == 0 { - return rtn, nil - } - var err error - if (rtype&R_Session)+(rtype&R_SessionOpt) > 0 { - rtn.SessionId, err = resolveSessionId(pk) - if err != nil { - return rtn, err - } - if rtn.SessionId == "" && (rtype&R_Session) > 0 { - return rtn, fmt.Errorf("no session") + uictx := pk.UIContext + if uictx != nil { + rtn.SessionId = uictx.SessionId + rtn.ScreenId = uictx.ScreenId + rtn.WindowId = uictx.WindowId + if uictx.Remote != nil && rtn.SessionId != "" && rtn.WindowId != "" { + rr, err := resolveRemoteFromPtr(ctx, uictx.Remote, rtn.SessionId, rtn.WindowId) + if err != nil { + if rtype&R_Remote > 0 { + return rtn, err + } + // otherwise just don't set uictx.Remote + } else { + rtn.Remote = rr + } } } - if (rtype&R_Window)+(rtype&R_WindowOpt) > 0 { - rtn.WindowId, err = resolveWindowId(pk, rtn.SessionId) - if err != nil { - return rtn, err - } - if rtn.WindowId == "" && (rtype&R_Window) > 0 { - return rtn, fmt.Errorf("no window") - } - + if rtype&R_Session > 0 && rtn.SessionId == "" { + return rtn, fmt.Errorf("no session") } - if (rtype&R_Screen)+(rtype&R_ScreenOpt) > 0 { - rtn.ScreenId, err = resolveScreenId(ctx, pk, rtn.SessionId) - if err != nil { - return rtn, err - } - if rtn.ScreenId == "" && (rtype&R_Screen) > 0 { - return rtn, fmt.Errorf("no screen") - } + if rtype&R_Screen > 0 && rtn.ScreenId == "" { + return rtn, fmt.Errorf("no screen") } - if (rtype&R_Remote)+(rtype&R_RemoteOpt) > 0 { - rname, rptr, state, rstate, err := resolveRemote(ctx, pk.Kwargs["remote"], rtn.SessionId, rtn.WindowId) - if err != nil { - return rtn, err + if rtype&R_Window > 0 && rtn.WindowId == "" { + return rtn, fmt.Errorf("no window") + } + if rtype&R_Remote > 0 && rtn.Remote == nil { + return rtn, fmt.Errorf("no remote") + } + if rtype&R_RemoteConnected > 0 { + if !rtn.Remote.RState.IsConnected() { + return rtn, fmt.Errorf("remote '%s' is not connected", rtn.Remote.DisplayName) } - if rptr == nil && (rtype&R_Remote) > 0 { - return rtn, fmt.Errorf("no remote") + if rtn.Remote.RemoteState == nil { + return rtn, fmt.Errorf("remote '%s' state is not available", rtn.Remote.DisplayName) } - rtn.RemoteDisplayName = rname - rtn.RemotePtr = *rptr - rtn.RemoteState = state - rtn.RState = *rstate } return rtn, nil } @@ -281,8 +276,34 @@ func parseFullRemoteRef(fullRemoteRef string) (string, string, string, error) { return fields[0], fields[1], fields[2], nil } +func resolveRemoteFromPtr(ctx context.Context, rptr *sstore.RemotePtrType, sessionId string, windowId string) (*ResolvedRemote, error) { + if rptr == nil || rptr.RemoteId == "" { + return nil, fmt.Errorf("no remote to resolve") + } + msh := remote.GetRemoteById(rptr.RemoteId) + if msh == nil { + return nil, fmt.Errorf("invalid remote '%s', not found", rptr.RemoteId) + } + rstate := msh.GetRemoteRuntimeState() + displayName := rstate.GetDisplayName(rptr) + state, err := sstore.GetRemoteState(ctx, sessionId, windowId, *rptr) + if err != nil { + return nil, fmt.Errorf("cannot resolve remote state '%s': %w", displayName, err) + } + if state == nil { + state = rstate.DefaultState + } + return &ResolvedRemote{ + DisplayName: displayName, + RemotePtr: *rptr, + RemoteState: state, + RState: rstate, + MShell: msh, + }, nil +} + // returns (remoteDisplayName, remoteptr, state, rstate, err) -func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string, windowId string) (string, *sstore.RemotePtrType, *sstore.RemoteState, *remote.RemoteState, error) { +func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string, windowId string) (string, *sstore.RemotePtrType, *sstore.RemoteState, *remote.RemoteRuntimeState, error) { if fullRemoteRef == "" { return "", nil, nil, nil, nil } diff --git a/pkg/cmdrunner/shparse.go b/pkg/cmdrunner/shparse.go index d0a6386c..aa8592f0 100644 --- a/pkg/cmdrunner/shparse.go +++ b/pkg/cmdrunner/shparse.go @@ -163,6 +163,7 @@ func EvalMetaCommand(ctx context.Context, origPk *scpacket.FeCommandPacketType) rtnPk.MetaCmd = metaCmd rtnPk.MetaSubCmd = metaSubCmd rtnPk.Kwargs = make(map[string]string) + rtnPk.UIContext = origPk.UIContext for key, val := range origPk.Kwargs { rtnPk.Kwargs[key] = val } diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index 0c31ad8d..6cac6dff 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -55,7 +55,7 @@ type Store struct { Map map[string]*MShellProc // key=remoteid } -type RemoteState struct { +type RemoteRuntimeState struct { RemoteType string `json:"remotetype"` RemoteId string `json:"remoteid"` PhysicalId string `json:"physicalremoteid"` @@ -68,10 +68,31 @@ type RemoteState struct { ConnectMode string `json:"connectmode"` } -func (state RemoteState) IsConnected() bool { +func (state RemoteRuntimeState) IsConnected() bool { return state.Status == StatusConnected } +func (state RemoteRuntimeState) GetBaseDisplayName() string { + if state.RemoteAlias != "" { + return state.RemoteAlias + } + return state.RemoteCanonicalName +} + +func (state RemoteRuntimeState) GetDisplayName(rptr *sstore.RemotePtrType) string { + name := state.GetBaseDisplayName() + if rptr == nil { + return name + } + if rptr.Name != "" { + name = name + ":" + rptr.Name + } + if rptr.OwnerId != "" { + name = "@" + rptr.OwnerId + ":" + name + } + return name +} + type MShellProc struct { Lock *sync.Mutex Remote *sstore.RemoteType @@ -122,7 +143,7 @@ func GetRemoteById(remoteId string) *MShellProc { return GlobalStore.Map[remoteId] } -func ResolveRemoteRef(remoteRef string) *RemoteState { +func ResolveRemoteRef(remoteRef string) *RemoteRuntimeState { GlobalStore.Lock.Lock() defer GlobalStore.Lock.Unlock() @@ -130,14 +151,14 @@ func ResolveRemoteRef(remoteRef string) *RemoteState { if err == nil { msh := GlobalStore.Map[remoteRef] if msh != nil { - state := msh.GetRemoteState() + state := msh.GetRemoteRuntimeState() return &state } return nil } for _, msh := range GlobalStore.Map { if msh.Remote.RemoteAlias == remoteRef || msh.Remote.RemoteCanonicalName == remoteRef { - state := msh.GetRemoteState() + state := msh.GetRemoteRuntimeState() return &state } } @@ -188,10 +209,10 @@ func makeShortHost(host string) string { return host[0:dotIdx] } -func (proc *MShellProc) GetRemoteState() RemoteState { +func (proc *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState { proc.Lock.Lock() defer proc.Lock.Unlock() - state := RemoteState{ + state := RemoteRuntimeState{ RemoteType: proc.Remote.RemoteType, RemoteId: proc.Remote.RemoteId, RemoteAlias: proc.Remote.RemoteAlias, @@ -251,18 +272,18 @@ func (proc *MShellProc) GetRemoteState() RemoteState { } func (msh *MShellProc) NotifyUpdate() { - rstate := msh.GetRemoteState() + rstate := msh.GetRemoteRuntimeState() update := &sstore.ModelUpdate{Remote: rstate} sstore.MainBus.SendUpdate("", update) } -func GetAllRemoteState() []RemoteState { +func GetAllRemoteRuntimeState() []RemoteRuntimeState { GlobalStore.Lock.Lock() defer GlobalStore.Lock.Unlock() - var rtn []RemoteState + var rtn []RemoteRuntimeState for _, proc := range GlobalStore.Map { - state := proc.GetRemoteState() + state := proc.GetRemoteRuntimeState() rtn = append(rtn, state) } return rtn @@ -427,7 +448,7 @@ func replaceHomePath(pathStr string, homeDir string) string { return pathStr } -func (state RemoteState) ExpandHomeDir(pathStr string) (string, error) { +func (state RemoteRuntimeState) ExpandHomeDir(pathStr string) (string, error) { if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") { return pathStr, nil } @@ -484,7 +505,6 @@ func RunCommand(ctx context.Context, cmdId string, remotePtr sstore.RemotePtrTyp if remoteState == nil { return nil, fmt.Errorf("no remote state passed to RunCommand") } - fmt.Printf("RUN-CMD> %s reqid=%s (msh=%v)\n", runPacket.CK, runPacket.ReqId, msh.Remote) msh.ServerProc.Output.RegisterRpc(runPacket.ReqId) err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket) if err != nil { diff --git a/pkg/scpacket/scpacket.go b/pkg/scpacket/scpacket.go index 8010873f..f1a932a7 100644 --- a/pkg/scpacket/scpacket.go +++ b/pkg/scpacket/scpacket.go @@ -18,6 +18,14 @@ type FeCommandPacketType struct { MetaSubCmd string `json:"metasubcmd,omitempty"` Args []string `json:"args,omitempty"` Kwargs map[string]string `json:"kwargs,omitempty"` + UIContext *UIContextType `json:"uicontext,omitempty"` +} + +type UIContextType struct { + SessionId string `json:"sessionid"` + ScreenId string `json:"screenid"` + WindowId string `json:"windowid"` + Remote *sstore.RemotePtrType `json:"remote,omitempty"` } type FeInputPacketType struct { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 3c993157..a7fbec4a 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -334,6 +334,19 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo return update, nil } +func SetActiveSessionId(ctx context.Context, sessionId string) error { + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT sessionid FROM session WHERE sessionid = ?` + if !tx.Exists(query, sessionId) { + return fmt.Errorf("cannot switch to session, not found") + } + query = `UPDATE client SET activesessionid = ?` + tx.ExecWrap(query, sessionId) + return nil + }) + return txErr +} + func containsStr(strs []string, testStr string) bool { for _, s := range strs { if s == testStr { diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index f9e6a461..3b687bf7 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -24,14 +24,15 @@ func (PtyDataUpdate) UpdateType() string { } type ModelUpdate struct { - Sessions []*SessionType `json:"sessions,omitempty"` - ActiveSessionId string `json:"activesessionid,omitempty"` - Window *WindowType `json:"window,omitempty"` - Line *LineType `json:"line,omitempty"` - Cmd *CmdType `json:"cmd,omitempty"` - CmdLine *CmdLineType `json:"cmdline,omitempty"` - Info *InfoMsgType `json:"info,omitempty"` - Remote interface{} `json:"remote,omitempty"` // *remote.RemoteState + Sessions []*SessionType `json:"sessions,omitempty"` + ActiveSessionId string `json:"activesessionid,omitempty"` + Window *WindowType `json:"window,omitempty"` + Line *LineType `json:"line,omitempty"` + Cmd *CmdType `json:"cmd,omitempty"` + CmdLine *CmdLineType `json:"cmdline,omitempty"` + Info *InfoMsgType `json:"info,omitempty"` + Remote interface{} `json:"remote,omitempty"` // *remote.RemoteState + History *HistoryInfoType `json:"history,omitempty"` } func (ModelUpdate) UpdateType() string { @@ -74,6 +75,10 @@ type InfoMsgType struct { TimeoutMs int64 `json:"timeoutms,omitempty"` } +type HistoryInfoType struct { + Items []*HistoryItemType `json:"items"` +} + type CmdLineType struct { InsertChars string `json:"insertchars"` InsertPos int64 `json:"insertpos"`