mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
add quoting/shell-parsing for commands
This commit is contained in:
parent
d0806bbd63
commit
dc8cba79da
1
go.mod
1
go.mod
@ -21,6 +21,7 @@ require (
|
|||||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||||
|
mvdan.cc/sh/v3 v3.5.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/scripthaus-dev/mshell v0.0.0 => /Users/mike/work/gopath/src/github.com/scripthaus-dev/mshell/
|
replace github.com/scripthaus-dev/mshell v0.0.0 => /Users/mike/work/gopath/src/github.com/scripthaus-dev/mshell/
|
||||||
|
2
go.sum
2
go.sum
@ -1798,6 +1798,8 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|||||||
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||||
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||||
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
||||||
|
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
|
||||||
|
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
@ -45,25 +45,6 @@ type resolvedIds struct {
|
|||||||
RState remote.RemoteState
|
RState remote.RemoteState
|
||||||
}
|
}
|
||||||
|
|
||||||
func SubMetaCmd(cmd string) string {
|
|
||||||
switch cmd {
|
|
||||||
case "s":
|
|
||||||
return "screen"
|
|
||||||
case "w":
|
|
||||||
return "window"
|
|
||||||
case "r":
|
|
||||||
return "run"
|
|
||||||
case "c":
|
|
||||||
return "comment"
|
|
||||||
case "e":
|
|
||||||
return "eval"
|
|
||||||
case "export":
|
|
||||||
return "setenv"
|
|
||||||
default:
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ValidCommands = []string{
|
var ValidCommands = []string{
|
||||||
"/run",
|
"/run",
|
||||||
"/eval",
|
"/eval",
|
||||||
@ -377,12 +358,11 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.
|
|||||||
if len(pk.Args) == 0 {
|
if len(pk.Args) == 0 {
|
||||||
return nil, fmt.Errorf("usage: /eval [command], no command passed to eval")
|
return nil, fmt.Errorf("usage: /eval [command], no command passed to eval")
|
||||||
}
|
}
|
||||||
// parse metacmd
|
newPk, err := EvalMetaCommand(ctx, pk)
|
||||||
commandStr := strings.TrimSpace(pk.Args[0])
|
if err != nil {
|
||||||
if commandStr == "" {
|
return nil, err
|
||||||
return nil, fmt.Errorf("/eval, invalid emtpty command")
|
|
||||||
}
|
}
|
||||||
update, err := evalCommandInternal(ctx, pk)
|
update, err := HandleCommand(ctx, newPk)
|
||||||
if !resolveBool(pk.Kwargs["nohist"], false) {
|
if !resolveBool(pk.Kwargs["nohist"], false) {
|
||||||
err := addToHistory(ctx, pk, update, (err != nil))
|
err := addToHistory(ctx, pk, update, (err != nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -393,73 +373,6 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.
|
|||||||
return update, err
|
return update, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalCommandInternal(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
|
||||||
commandStr := strings.TrimSpace(pk.Args[0])
|
|
||||||
metaCmd := ""
|
|
||||||
metaSubCmd := ""
|
|
||||||
if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") {
|
|
||||||
metaCmd = "cd"
|
|
||||||
commandStr = strings.TrimSpace(commandStr[2:])
|
|
||||||
} else if commandStr == "cr" || strings.HasPrefix(commandStr, "cr ") {
|
|
||||||
metaCmd = "cr"
|
|
||||||
commandStr = strings.TrimSpace(commandStr[2:])
|
|
||||||
} else if commandStr == "export" || strings.HasPrefix(commandStr, "export ") {
|
|
||||||
metaCmd = "setenv"
|
|
||||||
commandStr = strings.TrimSpace(commandStr[6:])
|
|
||||||
} else if commandStr == "setenv" || strings.HasPrefix(commandStr, "setenv ") {
|
|
||||||
metaCmd = "setenv"
|
|
||||||
commandStr = strings.TrimSpace(commandStr[6:])
|
|
||||||
} else if commandStr == "unset" || strings.HasPrefix(commandStr, "unset ") {
|
|
||||||
metaCmd = "unset"
|
|
||||||
commandStr = strings.TrimSpace(commandStr[5:])
|
|
||||||
} else if commandStr[0] == '/' {
|
|
||||||
spaceIdx := strings.Index(commandStr, " ")
|
|
||||||
if spaceIdx == -1 {
|
|
||||||
metaCmd = commandStr[1:]
|
|
||||||
commandStr = ""
|
|
||||||
} else {
|
|
||||||
metaCmd = commandStr[1:spaceIdx]
|
|
||||||
commandStr = strings.TrimSpace(commandStr[spaceIdx+1:])
|
|
||||||
}
|
|
||||||
colonIdx := strings.Index(metaCmd, ":")
|
|
||||||
if colonIdx != -1 {
|
|
||||||
metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:]
|
|
||||||
}
|
|
||||||
if metaCmd == "" {
|
|
||||||
return nil, fmt.Errorf("invalid command, got bare '/', with no command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if metaCmd == "" {
|
|
||||||
metaCmd = "run"
|
|
||||||
}
|
|
||||||
metaCmd = SubMetaCmd(metaCmd)
|
|
||||||
newPk := &scpacket.FeCommandPacketType{
|
|
||||||
MetaCmd: metaCmd,
|
|
||||||
MetaSubCmd: metaSubCmd,
|
|
||||||
Kwargs: pk.Kwargs,
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(commandStr, " ?") {
|
|
||||||
newPk.Kwargs["ephemeral"] = "1"
|
|
||||||
commandStr = commandStr[0 : len(commandStr)-2]
|
|
||||||
}
|
|
||||||
if metaCmd == "run" || metaCmd == "comment" {
|
|
||||||
newPk.Args = []string{commandStr}
|
|
||||||
} else if (metaCmd == "setenv" || metaCmd == "unset") && metaSubCmd == "" {
|
|
||||||
newPk.Args = strings.Fields(commandStr)
|
|
||||||
} else {
|
|
||||||
allArgs := strings.Fields(commandStr)
|
|
||||||
for _, arg := range allArgs {
|
|
||||||
if strings.Index(arg, "=") == -1 {
|
|
||||||
newPk.Args = append(newPk.Args, arg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.SplitN(arg, "=", 2)
|
|
||||||
newPk.Kwargs[fields[0]] = fields[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return HandleCommand(ctx, newPk)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
if pk.MetaSubCmd == "close" {
|
if pk.MetaSubCmd == "close" {
|
||||||
ids, err := resolveIds(ctx, pk, R_Session|R_Screen)
|
ids, err := resolveIds(ctx, pk, R_Session|R_Screen)
|
||||||
@ -976,6 +889,27 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
|
|||||||
}
|
}
|
||||||
return update, nil
|
return update, nil
|
||||||
}
|
}
|
||||||
|
if pk.MetaSubCmd == "set" {
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if bareSession == nil {
|
||||||
|
return nil, fmt.Errorf("session '%s' not found", ids.SessionId)
|
||||||
|
}
|
||||||
|
update := sstore.ModelUpdate{
|
||||||
|
Sessions: nil,
|
||||||
|
Info: &sstore.InfoMsgType{
|
||||||
|
InfoMsg: fmt.Sprintf("[%s]: update", bareSession.Name),
|
||||||
|
TimeoutMs: 2000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
if pk.MetaSubCmd != "" {
|
if pk.MetaSubCmd != "" {
|
||||||
return nil, fmt.Errorf("invalid /session subcommand '%s'", pk.MetaSubCmd)
|
return nil, fmt.Errorf("invalid /session subcommand '%s'", pk.MetaSubCmd)
|
||||||
}
|
}
|
||||||
|
210
pkg/cmdrunner/shparse.go
Normal file
210
pkg/cmdrunner/shparse.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package cmdrunner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseEnviron struct {
|
||||||
|
Env map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *parseEnviron) Get(name string) expand.Variable {
|
||||||
|
val, ok := e.Env[name]
|
||||||
|
if !ok {
|
||||||
|
return expand.Variable{}
|
||||||
|
}
|
||||||
|
return expand.Variable{
|
||||||
|
Exported: true,
|
||||||
|
Kind: expand.String,
|
||||||
|
Str: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *parseEnviron) Each(fn func(name string, vr expand.Variable) bool) {
|
||||||
|
for key, _ := range e.Env {
|
||||||
|
rtn := fn(key, e.Get(key))
|
||||||
|
if !rtn {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DumpPacket(pk *scpacket.FeCommandPacketType) {
|
||||||
|
if pk == nil || pk.MetaCmd == "" {
|
||||||
|
fmt.Printf("[no metacmd]\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pk.MetaSubCmd == "" {
|
||||||
|
fmt.Printf("/%s\n", pk.MetaCmd)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("/%s:%s\n", pk.MetaCmd, pk.MetaSubCmd)
|
||||||
|
}
|
||||||
|
for _, arg := range pk.Args {
|
||||||
|
fmt.Printf(" %q\n", arg)
|
||||||
|
}
|
||||||
|
for key, val := range pk.Kwargs {
|
||||||
|
fmt.Printf(" [%s]=%q\n", key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doCmdSubst(commandStr string, w io.Writer, word *syntax.CmdSubst) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doProcSubst(w *syntax.ProcSubst) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuoted(source string, w *syntax.Word) bool {
|
||||||
|
if w == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
offset := w.Pos().Offset()
|
||||||
|
if int(offset) >= len(source) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return source[offset] == '"' || source[offset] == '\''
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSourceStr(source string, w *syntax.Word) string {
|
||||||
|
if w == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
offset := w.Pos().Offset()
|
||||||
|
end := w.End().Offset()
|
||||||
|
return source[offset:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
var ValidMetaCmdRe = regexp.MustCompile("^/([a-z][a-z0-9_-]*)(:[a-z][a-z0-9_-]*)?$")
|
||||||
|
|
||||||
|
type BareMetaCmdDecl struct {
|
||||||
|
CmdStr string
|
||||||
|
MetaCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
var BareMetaCmds = []BareMetaCmdDecl{
|
||||||
|
BareMetaCmdDecl{"cd", "cd"},
|
||||||
|
BareMetaCmdDecl{"cr", "cr"},
|
||||||
|
BareMetaCmdDecl{"setenv", "setenv"},
|
||||||
|
BareMetaCmdDecl{"export", "setenv"},
|
||||||
|
BareMetaCmdDecl{"unset", "unset"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubMetaCmd(cmd string) string {
|
||||||
|
switch cmd {
|
||||||
|
case "s":
|
||||||
|
return "screen"
|
||||||
|
case "w":
|
||||||
|
return "window"
|
||||||
|
case "r":
|
||||||
|
return "run"
|
||||||
|
case "c":
|
||||||
|
return "comment"
|
||||||
|
case "e":
|
||||||
|
return "eval"
|
||||||
|
case "export":
|
||||||
|
return "setenv"
|
||||||
|
default:
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns (metaCmd, metaSubCmd, rest)
|
||||||
|
// if metaCmd is "" then this isn't a valid metacmd string
|
||||||
|
func parseMetaCmd(origCommandStr string) (string, string, string) {
|
||||||
|
commandStr := strings.TrimSpace(origCommandStr)
|
||||||
|
if len(commandStr) < 2 {
|
||||||
|
return "run", "", origCommandStr
|
||||||
|
}
|
||||||
|
fields := strings.SplitN(commandStr, " ", 2)
|
||||||
|
firstArg := fields[0]
|
||||||
|
rest := ""
|
||||||
|
if len(fields) > 1 {
|
||||||
|
rest = strings.TrimSpace(fields[1])
|
||||||
|
}
|
||||||
|
for _, decl := range BareMetaCmds {
|
||||||
|
if firstArg == decl.CmdStr {
|
||||||
|
return decl.MetaCmd, "", rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m := ValidMetaCmdRe.FindStringSubmatch(firstArg)
|
||||||
|
if m == nil {
|
||||||
|
return "run", "", origCommandStr
|
||||||
|
}
|
||||||
|
return SubMetaCmd(m[1]), m[2], rest
|
||||||
|
}
|
||||||
|
|
||||||
|
func onlyPositionalArgs(metaCmd string, metaSubCmd string) bool {
|
||||||
|
return (metaCmd == "setenv" || metaCmd == "unset") && metaSubCmd == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func onlyRawArgs(metaCmd string, metaSubCmd string) bool {
|
||||||
|
return metaCmd == "run" || metaCmd == "comment"
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalMetaCommand(ctx context.Context, origPk *scpacket.FeCommandPacketType) (*scpacket.FeCommandPacketType, error) {
|
||||||
|
if len(origPk.Args) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty command (no fields)")
|
||||||
|
}
|
||||||
|
metaCmd, metaSubCmd, commandArgs := parseMetaCmd(origPk.Args[0])
|
||||||
|
rtnPk := scpacket.MakeFeCommandPacket()
|
||||||
|
rtnPk.MetaCmd = metaCmd
|
||||||
|
rtnPk.MetaSubCmd = metaSubCmd
|
||||||
|
rtnPk.Kwargs = make(map[string]string)
|
||||||
|
for key, val := range origPk.Kwargs {
|
||||||
|
rtnPk.Kwargs[key] = val
|
||||||
|
}
|
||||||
|
if onlyRawArgs(metaCmd, metaSubCmd) {
|
||||||
|
// don't evaluate arguments for /run or /comment
|
||||||
|
rtnPk.Args = []string{commandArgs}
|
||||||
|
return rtnPk, nil
|
||||||
|
}
|
||||||
|
commandReader := strings.NewReader(commandArgs)
|
||||||
|
parser := syntax.NewParser(syntax.Variant(syntax.LangBash))
|
||||||
|
var words []*syntax.Word
|
||||||
|
err := parser.Words(commandReader, func(w *syntax.Word) bool {
|
||||||
|
words = append(words, w)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing metacmd, position %v", err)
|
||||||
|
}
|
||||||
|
envMap := make(map[string]string) // later we can add vars like session, window, screen, remote, and user
|
||||||
|
cfg := &expand.Config{
|
||||||
|
Env: &parseEnviron{Env: envMap},
|
||||||
|
GlobStar: false,
|
||||||
|
NullGlob: false,
|
||||||
|
NoUnset: false,
|
||||||
|
CmdSubst: func(w io.Writer, word *syntax.CmdSubst) error { return doCmdSubst(commandArgs, w, word) },
|
||||||
|
ProcSubst: doProcSubst,
|
||||||
|
ReadDir: nil,
|
||||||
|
}
|
||||||
|
// process arguments
|
||||||
|
for idx, w := range words {
|
||||||
|
literalVal, err := expand.Literal(cfg, w)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error evaluating metacmd argument %d [%s]: %v", idx+1, getSourceStr(commandArgs, w), err)
|
||||||
|
}
|
||||||
|
if isQuoted(commandArgs, w) || onlyPositionalArgs(metaCmd, metaSubCmd) {
|
||||||
|
rtnPk.Args = append(rtnPk.Args, literalVal)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eqIdx := strings.Index(literalVal, "=")
|
||||||
|
if eqIdx != -1 && eqIdx != 0 {
|
||||||
|
varName := literalVal[:eqIdx]
|
||||||
|
varVal := literalVal[eqIdx+1:]
|
||||||
|
rtnPk.Kwargs[varName] = varVal
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rtnPk.Args = append(rtnPk.Args, literalVal)
|
||||||
|
}
|
||||||
|
return rtnPk, nil
|
||||||
|
}
|
@ -250,6 +250,12 @@ func (proc *MShellProc) GetRemoteState() RemoteState {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (msh *MShellProc) NotifyUpdate() {
|
||||||
|
rstate := msh.GetRemoteState()
|
||||||
|
update := &sstore.ModelUpdate{Remote: rstate}
|
||||||
|
sstore.MainBus.SendUpdate("", update)
|
||||||
|
}
|
||||||
|
|
||||||
func GetAllRemoteState() []RemoteState {
|
func GetAllRemoteState() []RemoteState {
|
||||||
GlobalStore.Lock.Lock()
|
GlobalStore.Lock.Lock()
|
||||||
defer GlobalStore.Lock.Unlock()
|
defer GlobalStore.Lock.Unlock()
|
||||||
|
@ -142,6 +142,22 @@ func GetBareSessions(ctx context.Context) ([]*SessionType, error) {
|
|||||||
return rtn, nil
|
return rtn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetBareSessionById(ctx context.Context, sessionId string) (*SessionType, error) {
|
||||||
|
var rtn SessionType
|
||||||
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
|
query := `SELECT * FROM session WHERE sessionid = ?`
|
||||||
|
tx.GetWrap(&rtn, query, sessionId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
if rtn.SessionId == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
|
func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
|
||||||
var rtn []*SessionType
|
var rtn []*SessionType
|
||||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
@ -636,3 +652,54 @@ func UpdateCurRemote(ctx context.Context, sessionId string, windowId string, rem
|
|||||||
})
|
})
|
||||||
return txErr
|
return txErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reorderStrings(strs []string, toMove string, newIndex int) []string {
|
||||||
|
if toMove == "" {
|
||||||
|
return strs
|
||||||
|
}
|
||||||
|
var newStrs []string
|
||||||
|
if newIndex < 0 {
|
||||||
|
newStrs = append(newStrs, toMove)
|
||||||
|
}
|
||||||
|
for _, sval := range strs {
|
||||||
|
if len(newStrs) == newIndex {
|
||||||
|
newStrs = append(newStrs, toMove)
|
||||||
|
}
|
||||||
|
if sval != toMove {
|
||||||
|
newStrs = append(newStrs, sval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newIndex >= len(newStrs) {
|
||||||
|
newStrs = append(newStrs, toMove)
|
||||||
|
}
|
||||||
|
return newStrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReIndexSessions(ctx context.Context, sessionId string, newIndex int) error {
|
||||||
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
|
query := `SELECT sessionid FROM sessions ORDER BY sessionidx, name, sessionid`
|
||||||
|
ids := tx.SelectStrings(query)
|
||||||
|
if sessionId != "" {
|
||||||
|
ids = reorderStrings(ids, sessionId, newIndex)
|
||||||
|
}
|
||||||
|
query = `UPDATE sessions SET sessionid = ? WHERE sessionid = ?`
|
||||||
|
for idx, id := range ids {
|
||||||
|
tx.ExecWrap(query, id, idx+1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetSessionName(ctx context.Context, sessionId string, name string) error {
|
||||||
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
|
query := `SELECT sessionid FROM sessions WHERE sessionid = ?`
|
||||||
|
if !tx.Exists(query, sessionId) {
|
||||||
|
return fmt.Errorf("session does not exist")
|
||||||
|
}
|
||||||
|
query = `UPDATE sessions SET name = ? WHERE sessionid = ?`
|
||||||
|
tx.ExecWrap(query, name, sessionId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
@ -31,6 +31,7 @@ type ModelUpdate struct {
|
|||||||
Cmd *CmdType `json:"cmd,omitempty"`
|
Cmd *CmdType `json:"cmd,omitempty"`
|
||||||
CmdLine *CmdLineType `json:"cmdline,omitempty"`
|
CmdLine *CmdLineType `json:"cmdline,omitempty"`
|
||||||
Info *InfoMsgType `json:"info,omitempty"`
|
Info *InfoMsgType `json:"info,omitempty"`
|
||||||
|
Remote interface{} `json:"remote,omitempty"` // *remote.RemoteState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ModelUpdate) UpdateType() string {
|
func (ModelUpdate) UpdateType() string {
|
||||||
|
Loading…
Reference in New Issue
Block a user