From 4f686e891b5caea7b320b347302f3413d02e3111 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 24 Aug 2022 02:14:16 -0700 Subject: [PATCH] big updates to remoteinstance, push changes through stack --- db/migrations/000001_init.up.sql | 7 +- db/schema.sql | 7 +- pkg/cmdrunner/cmdrunner.go | 163 ++++++++++++++++----------- pkg/remote/remote.go | 187 +++++++++++++++++++++++-------- pkg/sstore/dbops.go | 125 ++++++++++++--------- pkg/sstore/sstore.go | 87 +++++++++++--- pkg/sstore/updatebus.go | 65 ++++------- 7 files changed, 411 insertions(+), 230 deletions(-) diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index 630af038..71fc09b9 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -18,7 +18,9 @@ CREATE TABLE session ( CREATE TABLE window ( sessionid varchar(36) NOT NULL, windowid varchar(36) NOT NULL, - curremote varchar(50) NOT NULL, + curremoteowneruserid varchar(36) NOT NULL, + curremoteid varchar(36) NOT NULL, + curremotename varchar(50) NOT NULL, winopts json NOT NULL, owneruserid varchar(36) NOT NULL, sharemode varchar(12) NOT NULL, @@ -52,8 +54,8 @@ CREATE TABLE remote_instance ( name varchar(50) NOT NULL, sessionid varchar(36) NOT NULL, windowid varchar(36) NOT NULL, + remoteowneruserid varchar(36) NOT NULL, remoteid varchar(36) NOT NULL, - sessionscope boolean NOT NULL, state json NOT NULL ); @@ -98,7 +100,6 @@ CREATE TABLE cmd ( donepk json NOT NULL, runout json NOT NULL, usedrows int NOT NULL, - prompt text NOT NULL, PRIMARY KEY (sessionid, cmdid) ); diff --git a/db/schema.sql b/db/schema.sql index 9f4a2d58..94f91b6d 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -18,7 +18,9 @@ CREATE TABLE session ( CREATE TABLE window ( sessionid varchar(36) NOT NULL, windowid varchar(36) NOT NULL, - curremote varchar(50) NOT NULL, + curremoteowneruserid varchar(36) NOT NULL, + curremoteid varchar(36) NOT NULL, + curremotename varchar(50) NOT NULL, winopts json NOT NULL, owneruserid varchar(36) NOT NULL, sharemode varchar(12) NOT NULL, @@ -49,8 +51,8 @@ CREATE TABLE remote_instance ( name varchar(50) NOT NULL, sessionid varchar(36) NOT NULL, windowid varchar(36) NOT NULL, + remoteowneruserid varchar(36) NOT NULL, remoteid varchar(36) NOT NULL, - sessionscope boolean NOT NULL, state json NOT NULL ); CREATE TABLE line ( @@ -92,7 +94,6 @@ CREATE TABLE cmd ( donepk json NOT NULL, runout json NOT NULL, usedrows int NOT NULL, - prompt text NOT NULL, PRIMARY KEY (sessionid, cmdid) ); CREATE TABLE history ( diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index d8801e4f..0914af4a 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -36,12 +36,12 @@ const ( ) type resolvedIds struct { - SessionId string - ScreenId string - WindowId string - RemoteId string - RemoteName string - RemoteState *sstore.RemoteState + SessionId string + ScreenId string + WindowId string + RemotePtr sstore.RemotePtrType + RemoteState *sstore.RemoteState + RemoteDisplayName string } func SubMetaCmd(cmd string) string { @@ -208,22 +208,59 @@ func resolveScreenId(ctx context.Context, pk *scpacket.FeCommandPacketType, sess return resolveSessionScreen(ctx, sessionId, screenArg) } -// returns (remoteName, remoteId, state, err) -func resolveRemote(ctx context.Context, remoteName string, sessionId string, windowId string) (string, string, *sstore.RemoteState, error) { - if remoteName == "" { - return "", "", nil, nil +// returns (remoteuserref, remoteref, name, error) +func parseFullRemoteRef(fullRemoteRef string) (string, string, string, error) { + if strings.HasPrefix(fullRemoteRef, "[") && strings.HasSuffix(fullRemoteRef, "]") { + fullRemoteRef = fullRemoteRef[1 : len(fullRemoteRef)-1] } - remoteId, state, err := sstore.GetRemoteState(ctx, remoteName, sessionId, windowId) + fields := strings.Split(fullRemoteRef, ":") + if len(fields) > 3 { + return "", "", "", fmt.Errorf("invalid remote format '%s'", fullRemoteRef) + } + if len(fields) == 1 { + return "", fields[0], "", nil + } + if len(fields) == 2 { + if strings.HasPrefix(fields[0], "@") { + return fields[0], fields[1], "", nil + } + return "", fields[0], fields[1], nil + } + return fields[0], fields[1], fields[2], nil +} + +// returns (remoteDisplayName, remoteptr, state, err) +func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string, windowId string) (string, *sstore.RemotePtrType, *sstore.RemoteState, error) { + if fullRemoteRef == "" { + return "", nil, nil, nil + } + userRef, remoteRef, remoteName, err := parseFullRemoteRef(fullRemoteRef) if err != nil { - return "", "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteName, err) + return "", nil, nil, err + } + if userRef != "" { + return "", nil, nil, fmt.Errorf("invalid remote '%s', cannot resolve remote userid '%s'", fullRemoteRef, userRef) + } + rstate := remote.ResolveRemoteRef(remoteRef) + if rstate == nil { + return "", nil, nil, fmt.Errorf("cannot resolve remote '%s': not found", fullRemoteRef) + } + rptr := sstore.RemotePtrType{RemoteId: rstate.RemoteId, Name: remoteName} + state, err := sstore.GetRemoteState(ctx, sessionId, windowId, rptr) + if err != nil { + return "", nil, nil, fmt.Errorf("cannot resolve remote state '%s': %w", fullRemoteRef, err) + } + rname := rstate.RemoteCanonicalName + if rstate.RemoteAlias != "" { + rname = rstate.RemoteAlias + } + if rptr.Name != "" { + rname = fmt.Sprintf("%s:%s", rname, rptr.Name) } if state == nil { - state, err = remote.GetDefaultRemoteStateById(remoteId) - if err != nil { - return "", "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteName, err) - } + return rname, &rptr, rstate.DefaultState, nil } - return remoteName, remoteId, state, nil + return rname, &rptr, state, nil } func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) { @@ -261,13 +298,16 @@ func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int } } if (rtype&R_Remote)+(rtype&R_RemoteOpt) > 0 { - rtn.RemoteName, rtn.RemoteId, rtn.RemoteState, err = resolveRemote(ctx, pk.Kwargs["remote"], rtn.SessionId, rtn.WindowId) + rname, rptr, rstate, err := resolveRemote(ctx, pk.Kwargs["remote"], rtn.SessionId, rtn.WindowId) if err != nil { return rtn, err } - if rtn.RemoteId == "" && (rtype&R_Remote) > 0 { + if rptr == nil && (rtype&R_Remote) > 0 { return rtn, fmt.Errorf("no remote") } + rtn.RemoteDisplayName = rname + rtn.RemotePtr = *rptr + rtn.RemoteState = rstate } return rtn, nil } @@ -289,7 +329,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U 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.RemoteId, ids.RemoteState, runPacket) + cmd, err := remote.RunCommand(ctx, cmdId, ids.RemotePtr.RemoteId, ids.RemoteState, runPacket) if err != nil { return nil, err } @@ -297,7 +337,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U if err != nil { return nil, err } - return sstore.LineUpdate{Line: rtnLine, Cmd: cmd}, nil + return sstore.ModelUpdate{Line: rtnLine, Cmd: cmd}, nil } func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, update sstore.UpdatePacket, hadError bool) error { @@ -467,7 +507,7 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore if err != nil { return nil, err } - curRemote := remote.GetRemoteById(ids.RemoteId) + curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) if curRemote == nil { return nil, fmt.Errorf("invalid remote, cannot unset") } @@ -489,18 +529,14 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore } state := *ids.RemoteState state.Env0 = shexec.MakeEnv0(envMap) - remote, err := sstore.UpdateRemoteState(ctx, ids.RemoteName, ids.SessionId, ids.WindowId, ids.RemoteId, state) + remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state) if err != nil { return nil, err } - update := sstore.WindowUpdate{ - Window: sstore.WindowType{ - SessionId: ids.SessionId, - WindowId: ids.WindowId, - Remotes: []*sstore.RemoteInstance{remote}, - }, + update := sstore.ModelUpdate{ + Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteName, makeSetVarsStr(unsetVars)), + InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteDisplayName, makeSetVarsStr(unsetVars)), TimeoutMs: 2000, }, } @@ -513,9 +549,9 @@ func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if err != nil { return nil, err } - curRemote := remote.GetRemoteById(ids.RemoteId) + curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) if curRemote == nil { - return nil, fmt.Errorf("invalid remote [%s] (not found)", ids.RemoteName) + return nil, fmt.Errorf("invalid remote '%s' (not found)", ids.RemoteDisplayName) } state := curRemote.GetRemoteState() var buf bytes.Buffer @@ -530,9 +566,9 @@ func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor buf.WriteString(fmt.Sprintf(" %-15s %s\n", "cwd", ids.RemoteState.Cwd)) } output := buf.String() - return sstore.InfoUpdate{ + return sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteName), + InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteDisplayName), InfoLines: splitLinesForInfo(output), }, }, nil @@ -559,7 +595,7 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if err != nil { return nil, err } - curRemote := remote.GetRemoteById(ids.RemoteId) + curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) if curRemote == nil { return nil, fmt.Errorf("invalid remote, cannot setenv") } @@ -576,9 +612,9 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor line := fmt.Sprintf("%s=%s", varName, shellescape.Quote(varVal)) infoLines = append(infoLines, line) } - update := sstore.InfoUpdate{ + update := sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("environment for [%s] remote", ids.RemoteName), + InfoTitle: fmt.Sprintf("environment for [%s] remote", ids.RemoteDisplayName), InfoLines: infoLines, }, } @@ -597,18 +633,14 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor } state := *ids.RemoteState state.Env0 = shexec.MakeEnv0(envMap) - remote, err := sstore.UpdateRemoteState(ctx, ids.RemoteName, ids.SessionId, ids.WindowId, ids.RemoteId, state) + remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state) if err != nil { return nil, err } - update := sstore.WindowUpdate{ - Window: sstore.WindowType{ - SessionId: ids.SessionId, - WindowId: ids.WindowId, - Remotes: []*sstore.RemoteInstance{remote}, - }, + update := sstore.ModelUpdate{ + Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteName, makeSetVarsStr(setVars)), + InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteDisplayName, makeSetVarsStr(setVars)), TimeoutMs: 2000, }, } @@ -624,23 +656,22 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up if newRemote == "" { return nil, nil } - remoteName, remoteId, _, err := resolveRemote(ctx, newRemote, ids.SessionId, ids.WindowId) - fmt.Printf("found: name[%s] id[%s] err[%v]\n", remoteName, remoteId, err) + remoteName, rptr, _, err := resolveRemote(ctx, newRemote, ids.SessionId, ids.WindowId) if err != nil { return nil, err } - if remoteId == "" { - return nil, fmt.Errorf("/cr error: remote not found") + if rptr == nil { + return nil, fmt.Errorf("/cr error: remote '%s' not found", newRemote) } - err = sstore.UpdateCurRemote(ctx, ids.SessionId, ids.WindowId, remoteName) + err = sstore.UpdateCurRemote(ctx, ids.SessionId, ids.WindowId, *rptr) if err != nil { return nil, fmt.Errorf("/cr error: cannot update curremote: %w", err) } - update := sstore.WindowUpdate{ + update := sstore.ModelUpdate{ Window: sstore.WindowType{ SessionId: ids.SessionId, WindowId: ids.WindowId, - CurRemote: remoteName, + CurRemote: *rptr, }, Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("current remote = %s", remoteName), @@ -656,7 +687,7 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up return nil, fmt.Errorf("/cd error: %w", err) } newDir := firstArg(pk) - curRemote := remote.GetRemoteById(ids.RemoteId) + curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) if curRemote == nil { return nil, fmt.Errorf("invalid remote, cannot change directory") } @@ -667,9 +698,9 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up return nil, fmt.Errorf("remote state is not available") } if newDir == "" { - return sstore.InfoUpdate{ + return sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteName, ids.RemoteState.Cwd), + InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteDisplayName, ids.RemoteState.Cwd), }, }, nil } @@ -699,18 +730,14 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up } state := *ids.RemoteState state.Cwd = newDir - remote, err := sstore.UpdateRemoteState(ctx, ids.RemoteName, ids.SessionId, ids.WindowId, ids.RemoteId, state) + remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state) if err != nil { return nil, err } - update := sstore.WindowUpdate{ - Window: sstore.WindowType{ - SessionId: ids.SessionId, - WindowId: ids.WindowId, - Remotes: []*sstore.RemoteInstance{remote}, - }, + update := sstore.ModelUpdate{ + Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteName, newDir), + InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteDisplayName, newDir), TimeoutMs: 2000, }, } @@ -778,7 +805,7 @@ func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.Upd if len(comps) == 0 { comps = []string{"(no completions)"} } - update := sstore.InfoUpdate{ + update := sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("%s completions", compType), InfoComps: comps, @@ -798,7 +825,7 @@ func makeInsertUpdateFromComps(pos int64, prefix string, comps []string, hasMore } insertChars := lcp[len(prefix):] clu := &sstore.CmdLineType{InsertChars: insertChars, InsertPos: pos} - return sstore.InfoUpdate{CmdLine: clu} + return sstore.ModelUpdate{CmdLine: clu} } func longestPrefix(root string, comps []string) string { @@ -864,7 +891,7 @@ func doCompGen(ctx context.Context, ids resolvedIds, prefix string, compType str return nil, false, fmt.Errorf("/compgen invalid remote state") } cgPacket.Cwd = ids.RemoteState.Cwd - curRemote := remote.GetRemoteById(ids.RemoteId) + curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) if curRemote == nil { return nil, false, fmt.Errorf("invalid remote, cannot execute command") } @@ -938,7 +965,7 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } - return sstore.LineUpdate{Line: rtnLine}, nil + return sstore.ModelUpdate{Line: rtnLine}, nil } func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -961,7 +988,7 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } - return sstore.SessionUpdate{ActiveSessionId: sessionId}, nil + return sstore.ModelUpdate{ActiveSessionId: sessionId}, nil } func splitLinesForInfo(str string) []string { diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index b0308d17..2d8cd6f7 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -1,6 +1,7 @@ package remote import ( + "bytes" "context" "encoding/base64" "errors" @@ -16,6 +17,7 @@ import ( "time" "github.com/creack/pty" + "github.com/google/uuid" "github.com/scripthaus-dev/mshell/pkg/base" "github.com/scripthaus-dev/mshell/pkg/packet" "github.com/scripthaus-dev/mshell/pkg/shexec" @@ -116,6 +118,28 @@ func GetRemoteById(remoteId string) *MShellProc { return GlobalStore.Map[remoteId] } +func ResolveRemoteRef(remoteRef string) *RemoteState { + GlobalStore.Lock.Lock() + defer GlobalStore.Lock.Unlock() + + _, err := uuid.Parse(remoteRef) + if err == nil { + msh := GlobalStore.Map[remoteRef] + if msh != nil { + state := msh.GetRemoteState() + return &state + } + return nil + } + for _, msh := range GlobalStore.Map { + if msh.Remote.RemoteAlias == remoteRef || msh.Remote.RemoteCanonicalName == remoteRef { + state := msh.GetRemoteState() + return &state + } + } + return nil +} + func unquoteDQBashString(str string) (string, bool) { if len(str) < 2 { return str, false @@ -175,19 +199,24 @@ func (proc *MShellProc) GetRemoteState() RemoteState { if proc.Err != nil { state.ErrorStr = proc.Err.Error() } + local := (proc.Remote.SSHOpts == nil || proc.Remote.SSHOpts.Local) vars := make(map[string]string) vars["user"] = proc.Remote.RemoteUser + vars["bestuser"] = vars["user"] vars["host"] = proc.Remote.RemoteHost vars["shorthost"] = makeShortHost(proc.Remote.RemoteHost) - if proc.Remote.RemoteSudo { - vars["sudo"] = "1" - } vars["alias"] = proc.Remote.RemoteAlias vars["cname"] = proc.Remote.RemoteCanonicalName vars["physicalid"] = proc.Remote.PhysicalId vars["remoteid"] = proc.Remote.RemoteId vars["status"] = proc.Status vars["type"] = proc.Remote.RemoteType + if proc.Remote.RemoteSudo { + vars["sudo"] = "1" + } + if local { + vars["local"] = "1" + } if proc.ServerProc != nil && proc.ServerProc.InitPk != nil { state.DefaultState = &sstore.RemoteState{ Cwd: proc.ServerProc.InitPk.Cwd, @@ -195,11 +224,23 @@ func (proc *MShellProc) GetRemoteState() RemoteState { } vars["home"] = proc.ServerProc.InitPk.HomeDir vars["remoteuser"] = proc.ServerProc.InitPk.User + vars["bestuser"] = vars["remoteuser"] vars["remotehost"] = proc.ServerProc.InitPk.HostName vars["remoteshorthost"] = makeShortHost(proc.ServerProc.InitPk.HostName) - if proc.Remote.SSHOpts == nil || proc.Remote.SSHOpts.SSHHost == "" { - vars["local"] = "1" - } + vars["besthost"] = vars["remotehost"] + vars["bestshorthost"] = vars["remoteshorthost"] + } + if local && proc.Remote.RemoteSudo { + vars["bestuser"] = "sudo" + } else if proc.Remote.RemoteSudo { + vars["bestuser"] = "sudo@" + vars["bestuser"] + } + if local { + vars["bestname"] = vars["bestuser"] + "@local" + vars["bestshortname"] = vars["bestuser"] + "@local" + } else { + vars["bestname"] = vars["bestuser"] + "@" + vars["besthost"] + vars["bestshortname"] = vars["bestuser"] + "@" + vars["bestshorthost"] } state.RemoteVars = vars return state @@ -238,8 +279,8 @@ func MakeMShell(r *sstore.RemoteType) *MShellProc { } func convertSSHOpts(opts *sstore.SSHOpts) shexec.SSHOpts { - if opts == nil { - return shexec.SSHOpts{} + if opts == nil || opts.Local { + opts = &sstore.SSHOpts{} } return shexec.SSHOpts{ SSHHost: opts.SSHHost, @@ -347,6 +388,19 @@ func (msh *MShellProc) GetDefaultState() *sstore.RemoteState { return &sstore.RemoteState{Cwd: msh.ServerProc.InitPk.HomeDir, Env0: msh.ServerProc.InitPk.Env0} } +func replaceHomePath(pathStr string, homeDir string) string { + if homeDir == "" { + return pathStr + } + if pathStr == homeDir { + return "~" + } + if strings.HasPrefix(pathStr, homeDir+"/") { + return "~" + pathStr[len(homeDir):] + } + return pathStr +} + func (msh *MShellProc) ExpandHomeDir(pathStr string) (string, error) { if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") { return pathStr, nil @@ -526,7 +580,7 @@ func (msh *MShellProc) notifyHangups_nolock() { if err != nil { continue } - update := sstore.LineUpdate{Cmd: cmd} + update := sstore.ModelUpdate{Cmd: cmd} sstore.MainBus.SendUpdate(ck.GetSessionId(), update) } msh.RunningCmds = nil @@ -602,16 +656,79 @@ func (runner *MShellProc) ProcessPackets() { } } -func EvalPromptEsc(escCode string, vars map[string]string, state sstore.RemoteState) string { - if escCode == "d" { - now := time.Now() - return now.Format("Mon Jan 02") +// returns number of chars (including braces) for brace-expr +func getBracedStr(runeStr []rune) int { + if len(runeStr) < 3 { + return 0 } + if runeStr[0] != '{' { + return 0 + } + for i := 1; i < len(runeStr); i++ { + if runeStr[i] == '}' { + if i == 1 { // cannot have {} + return 0 + } + return i + 1 + } + } + return 0 +} + +func isDigit(r rune) bool { + return r >= '0' && r <= '9' // just check ascii digits (not unicode) +} + +func EvalPrompt(promptFmt string, vars map[string]string, state *sstore.RemoteState) string { + var buf bytes.Buffer + promptRunes := []rune(promptFmt) + for i := 0; i < len(promptRunes); i++ { + ch := promptRunes[i] + if ch == '\\' && i != len(promptRunes)-1 { + nextCh := promptRunes[i+1] + if nextCh == 'x' || nextCh == 'y' { + nr := getBracedStr(promptRunes[i+2:]) + if nr > 0 { + escCode := string(promptRunes[i+1 : i+1+nr+1]) // start at "x" or "y", extend nr+1 runes + escStr := evalPromptEsc(escCode, vars, state) + buf.WriteString(escStr) + i += nr + 1 + continue + } else { + buf.WriteRune(ch) // invalid escape, so just write ch and move on + continue + } + } else if isDigit(nextCh) { + if len(promptRunes) >= i+4 && isDigit(promptRunes[i+2]) && isDigit(promptRunes[i+3]) { + i += 3 + escStr := evalPromptEsc(string(promptRunes[i+1:i+4]), vars, state) + buf.WriteString(escStr) + continue + } else { + buf.WriteRune(ch) // invalid escape, so just write ch and move on + continue + } + } else { + i += 1 + escStr := evalPromptEsc(string(nextCh), vars, state) + buf.WriteString(escStr) + continue + } + } + buf.WriteRune(ch) + } + return buf.String() +} + +func evalPromptEsc(escCode string, vars map[string]string, state *sstore.RemoteState) string { if strings.HasPrefix(escCode, "x{") && strings.HasSuffix(escCode, "}") { varName := escCode[2 : len(escCode)-1] return vars[varName] } if strings.HasPrefix(escCode, "y{") && strings.HasSuffix(escCode, "}") { + if state == nil { + return "" + } varName := escCode[2 : len(escCode)-1] varMap := shexec.ParseEnv0(state.Env0) return varMap[varName] @@ -622,50 +739,26 @@ func EvalPromptEsc(escCode string, vars map[string]string, state sstore.RemoteSt if escCode == "H" { return vars["remotehost"] } - if escCode == "j" { - return "0" - } - if escCode == "l" { - return "(l)" - } if escCode == "s" { return "mshell" } - if escCode == "t" { - now := time.Now() - return now.Format("15:04:05") - } - if escCode == "T" { - now := time.Now() - return now.Format("03:04:05") - } - if escCode == "@" { - now := time.Now() - return now.Format("03:04:05PM") - } if escCode == "u" { return vars["remoteuser"] } - if escCode == "v" { - return "0.1" - } - if escCode == "V" { - return "0" - } if escCode == "w" { - return state.Cwd + if state == nil { + return "?" + } + return replaceHomePath(state.Cwd, vars["home"]) } if escCode == "W" { - return path.Base(state.Cwd) - } - if escCode == "!" { - return "(!)" - } - if escCode == "#" { - return "(#)" + if state == nil { + return "?" + } + return path.Base(replaceHomePath(state.Cwd, vars["home"])) } if escCode == "$" { - if vars["remoteuser"] == "root" { + if vars["remoteuser"] == "root" || vars["sudo"] == "1" { return "#" } else { return "$" @@ -700,5 +793,7 @@ func EvalPromptEsc(escCode string, vars map[string]string, state sstore.RemoteSt if escCode == "]" { return "" } + + // we don't support date/time escapes (d, t, T, @), version escapes (v, V), cmd number (#, !), terminal device (l), jobs (j) return "(" + escCode + ")" } diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 2de2b72d..ac5772c3 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -178,7 +178,7 @@ func GetAllSessions(ctx context.Context) ([]*SessionType, error) { } screen.Windows = append(screen.Windows, sw) } - query = `SELECT * FROM remote_instance WHERE sessionscope` + query = `SELECT * FROM remote_instance` var ris []*RemoteInstance tx.SelectWrap(&ris, query) for _, ri := range ris { @@ -195,22 +195,19 @@ func GetAllSessions(ctx context.Context) ([]*SessionType, error) { func GetWindowById(ctx context.Context, sessionId string, windowId string) (*WindowType, error) { var rtnWindow *WindowType err := WithTx(ctx, func(tx *TxWrap) error { - var window WindowType query := `SELECT * FROM window WHERE sessionid = ? AND windowid = ?` - found := tx.GetWrap(&window, query, sessionId, windowId) - if !found { + m := tx.GetMap(query, sessionId, windowId) + if m == nil { return nil } - rtnWindow = &window + rtnWindow = WindowFromMap(m) query = `SELECT * FROM line WHERE sessionid = ? AND windowid = ?` - tx.SelectWrap(&window.Lines, query, sessionId, windowId) + tx.SelectWrap(&rtnWindow.Lines, query, sessionId, windowId) query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND windowid = ?)` cmdMaps := tx.SelectMaps(query, sessionId, windowId) for _, m := range cmdMaps { - window.Cmds = append(window.Cmds, CmdFromMap(m)) + rtnWindow.Cmds = append(rtnWindow.Cmds, CmdFromMap(m)) } - query = `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND NOT sessionscope` - tx.SelectWrap(&window.Remotes, query, sessionId, windowId) return nil }) return rtnWindow, err @@ -258,7 +255,7 @@ func GetSessionByName(ctx context.Context, name string) (*SessionType, error) { // also creates default window, returns sessionId // if sessionName == "", it will be generated -func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*SessionUpdate, error) { +func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (UpdatePacket, error) { newSessionId := uuid.New().String() txErr := WithTx(ctx, func(tx *TxWrap) error { names := tx.SelectStrings(`SELECT name FROM session`) @@ -279,7 +276,7 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo if err != nil { return nil, err } - update := &SessionUpdate{ + update := ModelUpdate{ Sessions: []*SessionType{session}, } if activate { @@ -328,7 +325,11 @@ func InsertScreen(ctx context.Context, sessionId string, screenName string, acti if !tx.Exists(query, sessionId) { return fmt.Errorf("cannot create screen, no session found") } - newWindowId := txCreateWindow(tx, sessionId) + remoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, LocalRemoteAlias) + if remoteId == "" { + return fmt.Errorf("cannot create screen, no local remote found") + } + newWindowId := txCreateWindow(tx, sessionId, RemotePtrType{RemoteId: remoteId}) maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ?`, sessionId) screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ?`, sessionId) screenName = fmtUniqueName(screenName, "s%d", maxScreenIdx+1, screenNames) @@ -377,11 +378,20 @@ func GetScreenById(ctx context.Context, sessionId string, screenId string) (*Scr return rtnScreen, nil } -func txCreateWindow(tx *TxWrap, sessionId string) string { - windowId := uuid.New().String() - query := `INSERT INTO window (sessionid, windowid, curremote, winopts, shareopts, owneruserid, sharemode) VALUES (?, ?, ?, ?, ?, '', 'local')` - tx.ExecWrap(query, sessionId, windowId, LocalRemoteName, WindowOptsType{}, WindowShareOptsType{}) - return windowId +func txCreateWindow(tx *TxWrap, sessionId string, curRemote RemotePtrType) string { + w := &WindowType{ + SessionId: sessionId, + WindowId: uuid.New().String(), + CurRemote: curRemote, + WinOpts: WindowOptsType{}, + ShareMode: ShareModeLocal, + ShareOpts: WindowShareOptsType{}, + } + wmap := w.ToMap() + query := `INSERT INTO window ( sessionid, windowid, curremoteowneruserid, curremoteid, curremotename, winopts, owneruserid, sharemode, shareopts) + VALUES (:sessionid,:windowid,:curremoteowneruserid,:curremoteid,:curremotename,:winopts,:owneruserid,:sharemode,:shareopts)` + tx.NamedExecWrap(query, wmap) + return w.WindowId } func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error { @@ -404,8 +414,8 @@ func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error { if cmd != nil { cmdMap := cmd.ToMap() query = ` -INSERT INTO cmd ( sessionid, cmdid, remoteid, cmdstr, remotestate, termopts, status, startpk, donepk, runout, usedrows, prompt) - VALUES (:sessionid,:cmdid,:remoteid,:cmdstr,:remotestate,:termopts,:status,:startpk,:donepk,:runout,:usedrows,:prompt) +INSERT INTO cmd ( sessionid, cmdid, remoteid, cmdstr, remotestate, termopts, status, startpk, donepk, runout, usedrows) + VALUES (:sessionid,:cmdid,:remoteid,:cmdstr,:remotestate,:termopts,:status,:startpk,:donepk,:runout,:usedrows) ` tx.NamedExecWrap(query, cmdMap) } @@ -448,7 +458,7 @@ func UpdateCmdDonePk(ctx context.Context, donePk *packet.CmdDonePacketType) (Upd if rtnCmd == nil { return nil, fmt.Errorf("cmd data not found for ck[%s]", donePk.CK) } - return LineUpdate{Cmd: rtnCmd}, nil + return ModelUpdate{Cmd: rtnCmd}, nil } func AppendCmdErrorPk(ctx context.Context, errPk *packet.CmdErrorPacketType) error { @@ -548,67 +558,80 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) (Updat return update, nil } -func GetRemoteState(ctx context.Context, rname string, sessionId string, windowId string) (string, *RemoteState, error) { - var remoteId string +func GetRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*RemoteState, error) { var remoteState *RemoteState txErr := WithTx(ctx, func(tx *TxWrap) error { var ri RemoteInstance - query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND name = ?` - found := tx.GetWrap(&ri, query, sessionId, windowId, rname) + query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteowneruserid = ? AND remoteid = ? AND name = ?` + found := tx.GetWrap(&ri, query, sessionId, windowId, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name) if found { - remoteId = ri.RemoteId remoteState = &ri.State return nil } - query = `SELECT remoteid FROM remote WHERE remotealias = ? OR remotecanonicalname = ?` - remoteId = tx.GetString(query, rname, rname) - if remoteId == "" { - return fmt.Errorf("remote not found", rname) - } return nil }) - return remoteId, remoteState, txErr + return remoteState, txErr } -func UpdateRemoteState(ctx context.Context, rname string, sessionId string, windowId string, remoteId string, state RemoteState) (*RemoteInstance, error) { - var ri RemoteInstance - txErr := WithTx(ctx, func(tx *TxWrap) error { +func validateSessionWindow(tx *TxWrap, sessionId string, windowId string) error { + if windowId == "" { + query := `SELECT sessionid FROM session WHERE sessionid = ?` + if !tx.Exists(query, sessionId) { + return fmt.Errorf("no session found") + } + return nil + } else { query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?` if !tx.Exists(query, sessionId, windowId) { - return fmt.Errorf("cannot update remote instance cwd, no window found") + return fmt.Errorf("no window found") } - query = `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND name = ?` - found := tx.GetWrap(&ri, query, sessionId, windowId, rname) + return nil + } +} + +func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType, state RemoteState) (*RemoteInstance, error) { + if remotePtr.IsSessionScope() { + windowId = "" + } + var ri RemoteInstance + txErr := WithTx(ctx, func(tx *TxWrap) error { + err := validateSessionWindow(tx, sessionId, windowId) + if err != nil { + return fmt.Errorf("cannot update remote instance cwd: %w", err) + } + query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteowneruserid = ? AND remoteid = ? AND name = ?` + found := tx.GetWrap(&ri, query, sessionId, windowId, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name) if !found { ri = RemoteInstance{ - RIId: uuid.New().String(), - Name: rname, - SessionId: sessionId, - WindowId: windowId, - RemoteId: remoteId, - SessionScope: (windowId == ""), - State: state, + RIId: uuid.New().String(), + Name: remotePtr.Name, + SessionId: sessionId, + WindowId: windowId, + RemoteOwnerUserId: remotePtr.OwnerUserId, + RemoteId: remotePtr.RemoteId, + State: state, } - query = `INSERT INTO remote_instance (riid, name, sessionid, windowid, remoteid, sessionscope, state) VALUES (:riid, :name, :sessionid, :windowid, :remoteid, :sessionscope, :state)` + query = `INSERT INTO remote_instance ( riid, name, sessionid, windowid, remoteowneruserid, remoteid, state) + VALUES (:riid,:name,:sessionid,:windowid,:remoteowneruserid,:remoteid,:state)` tx.NamedExecWrap(query, ri) return nil } - query = `UPDATE remote_instance SET state = ? WHERE sessionid = ? AND windowid = ? AND name = ?` + query = `UPDATE remote_instance SET state = ? WHERE sessionid = ? AND windowid = ? AND remoteowneruserid = ? AND remoteid = ? AND name = ?` ri.State = state - tx.ExecWrap(query, ri.State, ri.SessionId, ri.WindowId, ri.Name) + tx.ExecWrap(query, ri.State, ri.SessionId, ri.WindowId, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name) return nil }) return &ri, txErr } -func UpdateCurRemote(ctx context.Context, sessionId string, windowId string, remoteName string) error { +func UpdateCurRemote(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) error { txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?` if !tx.Exists(query, sessionId, windowId) { - return fmt.Errorf("cannot update curremote, no window found") + return fmt.Errorf("cannot update curremote: no window found") } - query = `UPDATE window SET curremote = ? WHERE sessionid = ? AND windowid = ?` - tx.ExecWrap(query, remoteName, sessionId, windowId) + query = `UPDATE window SET curremoteowneruserid = ?, curremoteid = ?, curremotename = ? WHERE sessionid = ? AND windowid = ?` + tx.ExecWrap(query, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name, sessionId, windowId) return nil }) return txErr diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index b07243ac..83b46cca 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -12,6 +12,7 @@ import ( "os" "os/user" "path" + "strings" "sync" "time" @@ -30,7 +31,7 @@ const DBFileName = "sh2.db" const DefaultSessionName = "default" const DefaultWindowName = "default" -const LocalRemoteName = "local" +const LocalRemoteAlias = "local" const DefaultScreenWindowName = "w1" const DefaultCwd = "~" @@ -132,22 +133,78 @@ func (opts WindowShareOptsType) Value() (driver.Value, error) { return quickValueJson(opts) } +type RemotePtrType struct { + OwnerUserId string `json:"owneruserid"` + RemoteId string `json:"remoteid"` + Name string `json:"name"` +} + +func (r RemotePtrType) IsSessionScope() bool { + return strings.HasPrefix(r.Name, "*") +} + +func (r RemotePtrType) MakeFullRemoteRef() string { + if r.RemoteId == "" { + return "" + } + if r.OwnerUserId == "" && r.Name == "" { + return r.RemoteId + } + if r.OwnerUserId != "" && r.Name == "" { + return fmt.Sprintf("@%s:%s", r.OwnerUserId, r.RemoteId) + } + if r.OwnerUserId == "" && r.Name != "" { + return fmt.Sprintf("%s:%s", r.RemoteId, r.Name) + } + return fmt.Sprintf("@%s:%s:%s", r.OwnerUserId, r.RemoteId, r.Name) +} + type WindowType struct { SessionId string `json:"sessionid"` WindowId string `json:"windowid"` - CurRemote string `json:"curremote"` + CurRemote RemotePtrType `json:"curremote"` WinOpts WindowOptsType `json:"winopts"` OwnerUserId string `json:"owneruserid"` ShareMode string `json:"sharemode"` ShareOpts WindowShareOptsType `json:"shareopts"` Lines []*LineType `json:"lines"` Cmds []*CmdType `json:"cmds"` - Remotes []*RemoteInstance `json:"remotes"` // only for updates Remove bool `json:"remove,omitempty"` } +func (w *WindowType) ToMap() map[string]interface{} { + rtn := make(map[string]interface{}) + rtn["sessionid"] = w.SessionId + rtn["windowid"] = w.WindowId + rtn["curremoteowneruserid"] = w.CurRemote.OwnerUserId + rtn["curremoteid"] = w.CurRemote.RemoteId + rtn["curremotename"] = w.CurRemote.Name + rtn["winopts"] = quickJson(w.WinOpts) + rtn["owneruserid"] = w.OwnerUserId + rtn["sharemode"] = w.ShareMode + rtn["shareopts"] = quickJson(w.ShareOpts) + return rtn +} + +func WindowFromMap(m map[string]interface{}) *WindowType { + if len(m) == 0 { + return nil + } + var w WindowType + quickSetStr(&w.SessionId, m, "sessionid") + quickSetStr(&w.WindowId, m, "windowid") + quickSetStr(&w.CurRemote.OwnerUserId, m, "curremoteowneruserid") + quickSetStr(&w.CurRemote.RemoteId, m, "curremoteid") + quickSetStr(&w.CurRemote.Name, m, "curremotename") + quickSetJson(&w.WinOpts, m, "winopts") + quickSetStr(&w.OwnerUserId, m, "owneruserid") + quickSetStr(&w.ShareMode, m, "sharemode") + quickSetJson(&w.ShareOpts, m, "shareopts") + return &w +} + type ScreenOptsType struct { TabColor string `json:"tabcolor"` } @@ -257,13 +314,13 @@ func (opts TermOpts) Value() (driver.Value, error) { } type RemoteInstance struct { - RIId string `json:"riid"` - Name string `json:"name"` - SessionId string `json:"sessionid"` - WindowId string `json:"windowid"` - RemoteId string `json:"remoteid"` - SessionScope bool `json:"sessionscope"` - State RemoteState `json:"state"` + RIId string `json:"riid"` + Name string `json:"name"` + SessionId string `json:"sessionid"` + WindowId string `json:"windowid"` + RemoteOwnerUserId string `json:"remoteowneruserid"` + RemoteId string `json:"remoteid"` + State RemoteState `json:"state"` // only for updates Remove bool `json:"remove,omitempty"` @@ -291,8 +348,7 @@ type SSHOpts struct { } type RemoteOptsType struct { - Color string `json:"color"` - Prompt string `json:"prompt"` + Color string `json:"color"` } func (opts *RemoteOptsType) Scan(val interface{}) error { @@ -341,7 +397,6 @@ type CmdType struct { DonePk *packet.CmdDonePacketType `json:"donepk"` UsedRows int64 `json:"usedrows"` RunOut []packet.PacketType `json:"runout"` - Prompt string `json:"prompt"` Remove bool `json:"remove"` } @@ -397,7 +452,6 @@ func (cmd *CmdType) ToMap() map[string]interface{} { rtn["donepk"] = quickJson(cmd.DonePk) rtn["runout"] = quickJson(cmd.RunOut) rtn["usedrows"] = cmd.UsedRows - rtn["prompt"] = cmd.Prompt return rtn } @@ -417,7 +471,6 @@ func CmdFromMap(m map[string]interface{}) *CmdType { quickSetJson(&cmd.DonePk, m, "donepk") quickSetJson(&cmd.RunOut, m, "runout") quickSetInt64(&cmd.UsedRows, m, "usedrows") - quickSetStr(&cmd.Prompt, m, "prompt") return &cmd } @@ -488,7 +541,7 @@ func EnsureLocalRemote(ctx context.Context) error { RemoteId: uuid.New().String(), PhysicalId: physicalId, RemoteType: RemoteTypeSsh, - RemoteAlias: LocalRemoteName, + RemoteAlias: LocalRemoteAlias, RemoteCanonicalName: fmt.Sprintf("%s@%s", user.Username, hostName), RemoteSudo: false, RemoteUser: user.Username, @@ -521,6 +574,7 @@ func AddTest01Remote(ctx context.Context) error { RemoteUser: "ubuntu", RemoteHost: "test01.ec2", SSHOpts: &SSHOpts{ + Local: false, SSHHost: "test01.ec2", SSHUser: "ubuntu", SSHIdentity: "/Users/mike/aws/mfmt.pem", @@ -552,6 +606,7 @@ func AddTest02Remote(ctx context.Context) error { RemoteUser: "test2", RemoteHost: "test01.ec2", SSHOpts: &SSHOpts{ + Local: false, SSHHost: "test01.ec2", SSHUser: "test2", }, diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index 155c491c..155db012 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -5,12 +5,7 @@ import "sync" var MainBus *UpdateBus = MakeUpdateBus() const PtyDataUpdateStr = "pty" -const SessionUpdateStr = "session" -const WindowUpdateStr = "window" -const CmdUpdateStr = "cmd" -const LineCmdUpdateStr = "line+cmd" -const InfoUpdateStr = "info" -const CompGenUpdateStr = "compgen" +const ModelUpdateStr = "model" type UpdatePacket interface { UpdateType() string @@ -28,56 +23,40 @@ func (PtyDataUpdate) UpdateType() string { return PtyDataUpdateStr } -type WindowUpdate struct { - Window WindowType `json:"window"` - Info *InfoMsgType `json:"info,omitempty"` -} - -func (WindowUpdate) UpdateType() string { - return WindowUpdateStr -} - -type SessionUpdate struct { +type ModelUpdate struct { Sessions []*SessionType `json:"sessions"` ActiveSessionId string `json:"activesessionid,omitempty"` + Window WindowType `json:"window"` + Line *LineType `json:"line"` + Cmd *CmdType `json:"cmd,omitempty"` + CmdLine *CmdLineType `json:"cmdline,omitempty"` Info *InfoMsgType `json:"info,omitempty"` } -func (SessionUpdate) UpdateType() string { - return SessionUpdateStr +func (ModelUpdate) UpdateType() string { + return ModelUpdateStr } -func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) { +func MakeSingleSessionUpdate(sessionId string) (ModelUpdate, *SessionType) { session := &SessionType{ SessionId: sessionId, NotifyNum: -1, } - update := &SessionUpdate{ + update := ModelUpdate{ Sessions: []*SessionType{session}, } return update, session } -type LineUpdate struct { - Line *LineType `json:"line"` - Cmd *CmdType `json:"cmd,omitempty"` - Remove bool `json:"remove,omitempty"` - Info *InfoMsgType `json:"info,omitempty"` -} - -func (LineUpdate) UpdateType() string { - return LineCmdUpdateStr -} - func ReadLineCmdIdFromUpdate(update UpdatePacket) (string, string) { - lineUpdate, ok := update.(LineUpdate) + modelUpdate, ok := update.(ModelUpdate) if !ok { return "", "" } - if lineUpdate.Line == nil { + if modelUpdate.Line == nil { return "", "" } - return lineUpdate.Line.LineId, lineUpdate.Line.CmdId + return modelUpdate.Line.LineId, modelUpdate.Line.CmdId } type InfoMsgType struct { @@ -95,15 +74,6 @@ type CmdLineType struct { InsertPos int64 `json:"insertpos"` } -type InfoUpdate struct { - Info *InfoMsgType `json:"info,omitempty"` - CmdLine *CmdLineType `json:"cmdline,omitempty"` -} - -func (InfoUpdate) UpdateType() string { - return InfoUpdateStr -} - type UpdateChannel struct { SessionId string ClientId string @@ -167,3 +137,12 @@ func (bus *UpdateBus) SendUpdate(sessionId string, update interface{}) { } } } + +func MakeSessionsUpdateForRemote(sessionId string, ri *RemoteInstance) []*SessionType { + return []*SessionType{ + &SessionType{ + SessionId: sessionId, + Remotes: []*RemoteInstance{ri}, + }, + } +}