From ca29e28c85643d7d012ac3f2ac011b538743e944 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 26 Aug 2022 17:17:33 -0700 Subject: [PATCH] register handlers instead of a switch statement. make resolve more generic --- pkg/cmdrunner/cmdrunner.go | 365 +++++++++++++++++++------------------ pkg/cmdrunner/resolver.go | 92 ++++++---- pkg/sstore/dbops.go | 12 +- 3 files changed, 258 insertions(+), 211 deletions(-) diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index b245f7b7..4a499864 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -25,59 +25,77 @@ import ( const DefaultUserId = "sawka" const MaxNameLen = 50 -var ValidCommands = []string{ - "/run", - "/eval", - "/screen", "/screen:open", "/screen:close", - "/session", "/session:open", "/session:close", - "/comment", - "/cd", - "/compgen", - "/setenv", "/unset", - "/remote:show", -} var genericNameRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$") var positionRe = regexp.MustCompile("^((\\+|-)?[0-9]+|(\\+|-))$") var wsRe = regexp.MustCompile("\\s+") -func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - switch SubMetaCmd(pk.MetaCmd) { - case "run": - return RunCommand(ctx, pk) +type MetaCmdFnType = func(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) +type MetaCmdEntryType struct { + IsAlias bool + Fn MetaCmdFnType +} - case "eval": - return EvalCommand(ctx, pk) +var MetaCmdFnMap = make(map[string]MetaCmdEntryType) - case "screen": - return ScreenCommand(ctx, pk) +func init() { + registerCmdFn("run", RunCommand) + registerCmdFn("eval", EvalCommand) + registerCmdFn("comment", CommentCommand) + registerCmdFn("cd", CdCommand) + registerCmdFn("cr", CrCommand) + registerCmdFn("compgen", CompGenCommand) + registerCmdFn("setenv", SetEnvCommand) + registerCmdFn("unset", UnSetCommand) - case "session": - return SessionCommand(ctx, pk) + registerCmdFn("session", SessionCommand) + registerCmdFn("session:open", SessionOpenCommand) + registerCmdAlias("session:new", SessionOpenCommand) + registerCmdFn("session:set", SessionSetCommand) - case "comment": - return CommentCommand(ctx, pk) + registerCmdFn("screen", ScreenCommand) + registerCmdFn("screen:close", ScreenCloseCommand) + registerCmdFn("screen:open", ScreenOpenCommand) + registerCmdAlias("screen:new", ScreenOpenCommand) - case "cd": - return CdCommand(ctx, pk) + registerCmdAlias("remote", RemoteCommand) + registerCmdFn("remote:show", RemoteShowCommand) +} - case "cr": - return CrCommand(ctx, pk) - - case "compgen": - return CompGenCommand(ctx, pk) - - case "setenv": - return SetEnvCommand(ctx, pk) - - case "unset": - return UnSetCommand(ctx, pk) - - case "remote": - return RemoteCommand(ctx, pk) - - default: - return nil, fmt.Errorf("invalid command '/%s', no handler", pk.MetaCmd) +func getValidCommands() []string { + var rtn []string + for key, val := range MetaCmdFnMap { + if val.IsAlias { + continue + } + rtn = append(rtn, key) } + return rtn +} + +func registerCmdFn(cmdName string, fn MetaCmdFnType) { + MetaCmdFnMap[cmdName] = MetaCmdEntryType{Fn: fn} +} + +func registerCmdAlias(cmdName string, fn MetaCmdFnType) { + MetaCmdFnMap[cmdName] = MetaCmdEntryType{IsAlias: true, Fn: fn} +} + +func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + metaCmd := SubMetaCmd(pk.MetaCmd) + var cmdName string + if pk.MetaSubCmd == "" { + cmdName = metaCmd + } else { + cmdName = fmt.Sprintf("%s:%s", pk.MetaCmd, pk.MetaSubCmd) + } + entry := MetaCmdFnMap[cmdName] + if entry.Fn == nil { + if MetaCmdFnMap[metaCmd].Fn != nil { + return nil, fmt.Errorf("invalid /%s subcommand '%s'", metaCmd, pk.MetaSubCmd) + } + return nil, fmt.Errorf("invalid command '/%s', no handler", cmdName) + } + return entry.Fn(ctx, pk) } func firstArg(pk *scpacket.FeCommandPacketType) string { @@ -182,33 +200,39 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. return update, err } +func ScreenCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveIds(ctx, pk, R_Session|R_Screen) + if err != nil { + return nil, fmt.Errorf("/screen:close cannot close screen: %w", err) + } + update, err := sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId) + if err != nil { + return nil, err + } + return update, nil +} + +func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveIds(ctx, pk, R_Session) + if err != nil { + return nil, fmt.Errorf("/screen:open cannot open screen: %w", err) + } + activate := resolveBool(pk.Kwargs["activate"], true) + newName := pk.Kwargs["name"] + if newName != "" { + err := validateName(newName, "screen") + if err != nil { + return nil, err + } + } + update, err := sstore.InsertScreen(ctx, ids.SessionId, newName, activate) + if err != nil { + return nil, err + } + return update, nil +} + func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - if pk.MetaSubCmd == "close" { - ids, err := resolveIds(ctx, pk, R_Session|R_Screen) - if err != nil { - return nil, fmt.Errorf("/screen:close cannot close screen: %w", err) - } - update, err := sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId) - if err != nil { - return nil, err - } - return update, nil - } - if pk.MetaSubCmd == "open" || pk.MetaSubCmd == "new" { - ids, err := resolveIds(ctx, pk, R_Session) - if err != nil { - return nil, fmt.Errorf("/screen:open cannot open screen: %w", err) - } - activate := resolveBool(pk.Kwargs["activate"], true) - update, err := sstore.InsertScreen(ctx, ids.SessionId, pk.Kwargs["name"], activate) - if err != nil { - return nil, err - } - return update, nil - } - if pk.MetaSubCmd != "" { - return nil, fmt.Errorf("invalid /screen subcommand '%s'", pk.MetaSubCmd) - } ids, err := resolveIds(ctx, pk, R_Session) if err != nil { return nil, fmt.Errorf("/screen cannot switch to screen: %w", err) @@ -229,9 +253,6 @@ func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor } func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - if pk.MetaSubCmd != "" { - return nil, fmt.Errorf("invalid /unset subcommand '%s'", pk.MetaSubCmd) - } ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) if err != nil { return nil, err @@ -261,61 +282,48 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore update := sstore.ModelUpdate{ Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteDisplayName, makeSetVarsStr(unsetVars)), + InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteDisplayName, formatStrs(mapToStrs(unsetVars), "and", false)), TimeoutMs: 2000, }, } return update, nil } -func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - if pk.MetaSubCmd == "show" { - ids, err := resolveIds(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() - var buf bytes.Buffer - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "type", state.RemoteType)) - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "remoteid", state.RemoteId)) - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "physicalid", state.PhysicalId)) - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "alias", state.RemoteAlias)) - 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)) - } - output := buf.String() - return sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteDisplayName), - InfoLines: splitLinesForInfo(output), - }, - }, nil +func RemoteShowCommand(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, err } - if pk.MetaSubCmd != "" { - return nil, fmt.Errorf("invalid /remote subcommand: '%s'", pk.MetaSubCmd) + curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId) + if curRemote == nil { + return nil, fmt.Errorf("invalid remote '%s' (not found)", ids.RemoteDisplayName) } - return nil, fmt.Errorf("/remote requires a subcommand: 'show'") + state := curRemote.GetRemoteState() + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "type", state.RemoteType)) + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "remoteid", state.RemoteId)) + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "physicalid", state.PhysicalId)) + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "alias", state.RemoteAlias)) + 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)) + } + output := buf.String() + return sstore.ModelUpdate{ + Info: &sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteDisplayName), + InfoLines: splitLinesForInfo(output), + }, + }, nil } -func makeSetVarsStr(setVars map[string]bool) string { - varArr := make([]string, 0, len(setVars)) - for varName, _ := range setVars { - varArr = append(varArr, varName) - } - return strings.Join(varArr, ", ") +func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + return nil, fmt.Errorf("/remote requires a subcommand: %s", formatStrs([]string{"show"}, "or", false)) } func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - if pk.MetaSubCmd != "" { - return nil, fmt.Errorf("invalid /setenv subcommand '%s'", pk.MetaSubCmd) - } ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) if err != nil { return nil, err @@ -361,7 +369,7 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor update := sstore.ModelUpdate{ Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote), Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteDisplayName, makeSetVarsStr(setVars)), + InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteDisplayName, formatStrs(mapToStrs(setVars), "and", false)), TimeoutMs: 2000, }, } @@ -583,7 +591,8 @@ func doMetaCompGen(ctx context.Context, ids resolvedIds, prefix string, forDispl if err != nil { return nil, false, err } - for _, cmd := range ValidCommands { + validCommands := getValidCommands() + for _, cmd := range validCommands { if strings.HasPrefix(cmd, prefix) { if forDisplay { comps = append(comps, "^"+cmd) @@ -694,6 +703,16 @@ func maybeQuote(s string, quote bool) string { return s } +func mapToStrs(m map[string]bool) []string { + var rtn []string + for key, val := range m { + if val { + rtn = append(rtn, key) + } + } + return rtn +} + func formatStrs(strs []string, conj string, quote bool) string { if len(strs) == 0 { return "(none)" @@ -725,82 +744,82 @@ func validateName(name string, typeStr string) error { return nil } -func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - if pk.MetaSubCmd == "open" || pk.MetaSubCmd == "new" { - activate := resolveBool(pk.Kwargs["activate"], true) - newName := pk.Kwargs["name"] - if newName != "" { - err := validateName(newName, "session") - if err != nil { - return nil, err - } - } - update, err := sstore.InsertSessionWithName(ctx, newName, activate) +func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + activate := resolveBool(pk.Kwargs["activate"], true) + newName := pk.Kwargs["name"] + if newName != "" { + err := validateName(newName, "session") if err != nil { return nil, err } - 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) - } - var varsUpdated []string - if pk.Kwargs["name"] != "" { - newName := pk.Kwargs["name"] - err = validateName(newName, "session") - if err != nil { - return nil, err - } - err = sstore.SetSessionName(ctx, ids.SessionId, newName) - if err != nil { - return nil, fmt.Errorf("setting session name: %v", err) - } - varsUpdated = append(varsUpdated, "name") - } - if pk.Kwargs["pos"] != "" { + update, err := sstore.InsertSessionWithName(ctx, newName, activate) + if err != nil { + return nil, err + } + return update, nil +} - } - if len(varsUpdated) == 0 { - return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false)) - } - bareSession, err = sstore.GetBareSessionById(ctx, ids.SessionId) - update := sstore.ModelUpdate{ - Sessions: []*sstore.SessionType{bareSession}, - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("[%s]: updated %s", bareSession.Name, formatStrs(varsUpdated, "and", false)), - TimeoutMs: 2000, - }, - } - return update, nil +func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveIds(ctx, pk, R_Session) + if err != nil { + return nil, err } - if pk.MetaSubCmd != "" { - return nil, fmt.Errorf("invalid /session subcommand '%s'", pk.MetaSubCmd) + 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) + } + var varsUpdated []string + if pk.Kwargs["name"] != "" { + newName := pk.Kwargs["name"] + err = validateName(newName, "session") + if err != nil { + return nil, err + } + err = sstore.SetSessionName(ctx, ids.SessionId, newName) + if err != nil { + return nil, fmt.Errorf("setting session name: %v", err) + } + varsUpdated = append(varsUpdated, "name") + } + if pk.Kwargs["pos"] != "" { + + } + if len(varsUpdated) == 0 { + return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false)) + } + bareSession, err = sstore.GetBareSessionById(ctx, ids.SessionId) + update := sstore.ModelUpdate{ + Sessions: []*sstore.SessionType{bareSession}, + Info: &sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("[%s]: session updated %s", bareSession.Name, formatStrs(varsUpdated, "and", false)), + TimeoutMs: 2000, + }, + } + return update, nil +} + +func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { firstArg := firstArg(pk) if firstArg == "" { return nil, fmt.Errorf("usage /session [name|id|pos], no param specified") } - sessionId, err := resolveSession(ctx, firstArg, pk.Kwargs["session"], nil) + bareSessions, err := sstore.GetBareSessions(ctx) if err != nil { return nil, err } - bareSession, err := sstore.GetSessionById(ctx, sessionId) + ritems := sessionsToResolveItems(bareSessions) + ritem, err := genericResolve(firstArg, pk.Kwargs["session"], ritems, "session") if err != nil { - return nil, fmt.Errorf("could not find session '%s': %v", sessionId, err) + return nil, err } update := sstore.ModelUpdate{ - ActiveSessionId: sessionId, + ActiveSessionId: ritem.Id, Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("switched to session %q", bareSession.Name), + InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name), TimeoutMs: 2000, }, } diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index 1e85d1be..5aaa8e66 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -34,16 +34,43 @@ type resolvedIds struct { RState remote.RemoteState } -func resolveByPosition(ids []string, curId string, posStr string) string { - if len(ids) == 0 { - return "" +type ResolveItem struct { + Name string + Id string +} + +func itemNames(items []ResolveItem) []string { + if len(items) == 0 { + return nil + } + rtn := make([]string, len(items)) + for idx, item := range items { + rtn[idx] = item.Name + } + return rtn +} + +func sessionsToResolveItems(sessions []*sstore.SessionType) []ResolveItem { + if len(sessions) == 0 { + return nil + } + rtn := make([]ResolveItem, len(sessions)) + for idx, session := range sessions { + rtn[idx] = ResolveItem{Name: session.Name, Id: session.SessionId} + } + return rtn +} + +func resolveByPosition(items []ResolveItem, curId string, posStr string) *ResolveItem { + if len(items) == 0 { + return nil } if !positionRe.MatchString(posStr) { - return "" + return nil } curIdx := 1 // if no match, curIdx will be first item - for idx, id := range ids { - if id == curId { + for idx, item := range items { + if item.Id == curId { curIdx = idx + 1 break } @@ -63,19 +90,19 @@ func resolveByPosition(ids []string, curId string, posStr string) string { } if pos < 1 { if isWrap { - pos = len(ids) + pos = len(items) } else { pos = 1 } } - if pos > len(ids) { + if pos > len(items) { if isWrap { pos = 1 } else { - pos = len(ids) + pos = len(items) } } - return ids[pos-1] + return &items[pos-1] } func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) { @@ -163,42 +190,35 @@ func isPartialUUID(s string) bool { return partialUUIDRe.MatchString(s) } -func resolveSession(ctx context.Context, sessionArg string, curSession string, bareSessions []*sstore.SessionType) (string, error) { - if bareSessions == nil { - var err error - bareSessions, err = sstore.GetBareSessions(ctx) - if err != nil { - return "", fmt.Errorf("could not retrive bare sessions") +func genericResolve(arg string, curArg string, items []ResolveItem, typeStr string) (*ResolveItem, error) { + var curId string + if curArg != "" { + curItem, _ := genericResolve(curArg, "", items, typeStr) + if curItem != nil { + curId = curItem.Id } } - var curSessionId string - if curSession != "" { - curSessionId, _ = resolveSession(ctx, curSession, "", bareSessions) + rtnItem := resolveByPosition(items, curId, arg) + if rtnItem != nil { + return rtnItem, nil } - sids := getSessionIds(bareSessions) - rtnId := resolveByPosition(sids, curSessionId, sessionArg) - if rtnId != "" { - return rtnId, nil - } - tryPuid := isPartialUUID(sessionArg) - var prefixMatches []string - var lastPrefixMatchId string - for _, session := range bareSessions { - if session.SessionId == sessionArg || session.Name == sessionArg || (tryPuid && strings.HasPrefix(session.SessionId, sessionArg)) { - return session.SessionId, nil + tryPuid := isPartialUUID(arg) + var prefixMatches []ResolveItem + for _, item := range items { + if item.Id == arg || item.Name == arg || (tryPuid && strings.HasPrefix(item.Id, arg)) { + return &item, nil } - if strings.HasPrefix(session.Name, sessionArg) { - prefixMatches = append(prefixMatches, session.Name) - lastPrefixMatchId = session.SessionId + if strings.HasPrefix(item.Name, arg) { + prefixMatches = append(prefixMatches, item) } } if len(prefixMatches) == 1 { - return lastPrefixMatchId, nil + return &prefixMatches[0], nil } if len(prefixMatches) > 1 { - return "", fmt.Errorf("could not resolve session '%s', ambiguious prefix matched multiple sessions: %s", sessionArg, formatStrs(prefixMatches, "and", true)) + return nil, fmt.Errorf("could not resolve %s '%s', ambiguious prefix matched multiple %ss: %s", typeStr, arg, typeStr, formatStrs(itemNames(prefixMatches), "and", true)) } - return "", fmt.Errorf("could not resolve sesssion '%s' (name/id/pos not found)", sessionArg) + return nil, fmt.Errorf("could not resolve %s '%s' (name/id/pos not found)", typeStr, arg) } func resolveSessionId(pk *scpacket.FeCommandPacketType) (string, error) { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index a0f30dc6..d8b6aca3 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -358,7 +358,7 @@ func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []strin } } -func InsertScreen(ctx context.Context, sessionId string, screenName string, activate bool) (UpdatePacket, error) { +func InsertScreen(ctx context.Context, sessionId string, origScreenName string, activate bool) (UpdatePacket, error) { var newScreenId string txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT sessionid FROM session WHERE sessionid = ?` @@ -372,7 +372,7 @@ func InsertScreen(ctx context.Context, sessionId string, screenName string, acti 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) + screenName := fmtUniqueName(origScreenName, "s%d", maxScreenIdx+1, screenNames) newScreenId = uuid.New().String() query = `INSERT INTO screen (sessionid, screenid, name, activewindowid, screenidx, screenopts, ownerid, sharemode) VALUES (?, ?, ?, ?, ?, ?, '', 'local')` tx.ExecWrap(query, sessionId, newScreenId, screenName, newWindowId, maxScreenIdx+1, ScreenOptsType{}) @@ -721,6 +721,14 @@ func SetSessionName(ctx context.Context, sessionId string, name string) error { if !tx.Exists(query, sessionId) { return fmt.Errorf("session does not exist") } + query = `SELECT sessionid FROM session WHERE name = ?` + dupSessionId := tx.GetString(query, name) + if dupSessionId == sessionId { + return nil + } + if tx.Exists(query, name) { + return fmt.Errorf("invalid duplicate session name '%s'", name) + } query = `UPDATE session SET name = ? WHERE sessionid = ?` tx.ExecWrap(query, name, sessionId) return nil