From 962261ec35df43a3010cf8a31ef4cff2f5253863 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 23 Dec 2022 15:56:29 -0800 Subject: [PATCH] updates for /screen:close --- pkg/cmdrunner/cmdrunner.go | 86 +++++++++++++++++++++++++++++++-- pkg/cmdrunner/resolver.go | 19 +++++--- pkg/sstore/dbops.go | 98 ++++++++++++++++++++++++++++++++++---- pkg/sstore/sstore.go | 20 ++++---- pkg/sstore/txwrap.go | 6 +++ 5 files changed, 200 insertions(+), 29 deletions(-) diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index bc78f4eb..e76d92ff 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -123,9 +123,11 @@ func init() { registerCmdFn("screen", ScreenCommand) registerCmdFn("screen:close", ScreenCloseCommand) + registerCmdFn("screen:purge", ScreenPurgeCommand) registerCmdFn("screen:open", ScreenOpenCommand) registerCmdAlias("screen:new", ScreenOpenCommand) registerCmdFn("screen:set", ScreenSetCommand) + registerCmdFn("screen:showall", ScreenShowAllCommand) registerCmdAlias("remote", RemoteCommand) registerCmdFn("remote:show", RemoteShowCommand) @@ -378,10 +380,53 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. } func ScreenCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) + ids, err := resolveUiIds(ctx, pk, R_Session) // don't force R_Screen if err != nil { return nil, fmt.Errorf("/screen:close cannot close screen: %w", err) } + screenId := ids.ScreenId + if len(pk.Args) > 0 { + ri, err := resolveSessionScreen(ctx, ids.SessionId, pk.Args[0], ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("/screen:close cannot resolve screen arg: %v", err) + } + screenId = ri.Id + } + if screenId == "" { + return nil, fmt.Errorf("/screen:close no active screen or screen arg passed") + } + closeVal := true + if len(pk.Args) > 1 { + closeVal = resolveBool(pk.Args[1], true) + } + var update sstore.UpdatePacket + if closeVal { + update, err = sstore.CloseScreen(ctx, ids.SessionId, screenId) + if err != nil { + return nil, err + } + return update, nil + } else { + fmt.Printf("unclose screen %s\n", screenId) + err = sstore.UnCloseScreen(ctx, ids.SessionId, screenId) + if err != nil { + return nil, fmt.Errorf("/screen:close cannot re-open screen: %v", err) + } + screen, err := sstore.GetScreenById(ctx, ids.SessionId, screenId) + if err != nil { + return nil, fmt.Errorf("/screen:close cannot get updated screen obj: %v", err) + } + update, session := sstore.MakeSingleSessionUpdate(ids.SessionId) + session.Screens = append(session.Screens, screen) + return update, nil + } +} + +func ScreenPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) + if err != nil { + return nil, fmt.Errorf("/screen:purge cannot close screen: %w", err) + } update, err := sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId) if err != nil { return nil, err @@ -900,6 +945,33 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) }, nil } +func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveUiIds(ctx, pk, R_Session) + screenArr, err := sstore.GetAllSessionScreens(ctx, ids.SessionId) + if err != nil { + return nil, fmt.Errorf("/screen:showall error getting screen list: %v", err) + } + var buf bytes.Buffer + for _, screen := range screenArr { + var closedStr string + if screen.Closed { + closedStr = " (closed)" + } + screenIdxStr := "-" + if screen.ScreenIdx != 0 { + screenIdxStr = strconv.Itoa(int(screen.ScreenIdx)) + } + outStr := fmt.Sprintf("%s %-30s %s\n", screen.ScreenId, screen.Name+closedStr, screenIdxStr) + buf.WriteString(outStr) + } + return sstore.ModelUpdate{ + Info: &sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("all screens for session"), + InfoLines: splitLinesForInfo(buf.String()), + }, + }, nil +} + func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) if err != nil { @@ -1309,13 +1381,19 @@ func SessionDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("cannot delete session: %v", err) } - sessionIds, _ := sstore.GetAllSessionIds(ctx) // ignore error, session is already deleted so that's the main return value delSession := &sstore.SessionType{SessionId: ids.SessionId, Remove: true} update := sstore.ModelUpdate{ Sessions: []*sstore.SessionType{delSession}, } - if len(sessionIds) > 0 { - update.ActiveSessionId = sessionIds[0] + activeSessionId, _ := sstore.GetActiveSessionId(ctx) // ignore error + if activeSessionId == "" { + sessionIds, _ := sstore.GetAllSessionIds(ctx) // ignore error, session is already deleted so that's the main return value + if len(sessionIds) > 0 { + err = sstore.SetActiveSessionId(ctx, sessionIds[0]) + if err != nil { + update.ActiveSessionId = sessionIds[0] + } + } } return update, nil } diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index b2933734..7a5edaf0 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -58,7 +58,7 @@ func sessionsToResolveItems(sessions []*sstore.SessionType) []ResolveItem { } rtn := make([]ResolveItem, len(sessions)) for idx, session := range sessions { - rtn[idx] = ResolveItem{Name: session.Name, Id: session.SessionId} + rtn[idx] = ResolveItem{Name: session.Name, Id: session.SessionId, Hidden: session.Closed} } return rtn } @@ -69,7 +69,7 @@ func screensToResolveItems(screens []*sstore.ScreenType) []ResolveItem { } rtn := make([]ResolveItem, len(screens)) for idx, screen := range screens { - rtn[idx] = ResolveItem{Name: screen.Name, Id: screen.ScreenId} + rtn[idx] = ResolveItem{Name: screen.Name, Id: screen.ScreenId, Hidden: screen.Closed} } return rtn } @@ -133,7 +133,13 @@ func parsePosArg(posStr string) *posArgType { return &posArgType{Pos: pos} } -func resolveByPosition(isNumeric bool, items []ResolveItem, curId string, posStr string) *ResolveItem { +func resolveByPosition(isNumeric bool, allItems []ResolveItem, curId string, posStr string) *ResolveItem { + items := make([]ResolveItem, 0, len(allItems)) + for _, item := range allItems { + if !item.Hidden { + items = append(items, item) + } + } if len(items) == 0 { return nil } @@ -160,7 +166,8 @@ func resolveByPosition(isNumeric bool, items []ResolveItem, curId string, posStr finalPos = curIdx + posArg.Pos } else if isNumeric { // these resolve items have a "Num" set that should be used to look up non-relative positions - for _, item := range items { + // use allItems for numeric resolve + for _, item := range allItems { if item.Num == posArg.Pos { return &item } @@ -342,7 +349,7 @@ func genericResolve(arg string, curArg string, items []ResolveItem, isNumeric bo if (isUuid && item.Id == arg) || (tryPuid && strings.HasPrefix(item.Id, arg)) { return &item, nil } - if item.Name != "" { + if !item.Hidden && item.Name != "" { if item.Name == arg { return &item, nil } @@ -396,7 +403,7 @@ func resolveScreenArg(sessionId string, screenArg string) (string, error) { return "", nil } if _, err := uuid.Parse(screenArg); err != nil { - return "", fmt.Errorf("invalid screen arg specified (must be sessionid) '%s'", screenArg) + return "", fmt.Errorf("invalid screen arg specified (must be screenid) '%s'", screenArg) } return screenArg, nil } diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 9fbd0ae0..fa855d61 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -233,6 +233,7 @@ func GetHistoryItems(ctx context.Context, sessionId string, windowId string, opt return rtn, nil } +// includes closed sessions func GetBareSessions(ctx context.Context) ([]*SessionType, error) { var rtn []*SessionType err := WithTx(ctx, func(tx *TxWrap) error { @@ -249,7 +250,7 @@ func GetBareSessions(ctx context.Context) ([]*SessionType, error) { func GetAllSessionIds(ctx context.Context) ([]string, error) { var rtn []string txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT sessionid from session ORDER by sessionidx` + query := `SELECT sessionid from session WHERE NOT closed ORDER by sessionidx` rtn = tx.SelectStrings(query) return nil }) @@ -287,7 +288,7 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { session.Full = true } var screens []*ScreenType - query = `SELECT * FROM screen ORDER BY screenidx` + query = `SELECT * FROM screen WHERE NOT closed ORDER BY screenidx` tx.SelectWrap(&screens, query) screenMap := make(map[string][]*ScreenType) for _, screen := range screens { @@ -352,6 +353,7 @@ func GetWindowById(ctx context.Context, sessionId string, windowId string) (*Win return rtnWindow, err } +// includes closed screens func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) { var rtn []*ScreenType txErr := WithTx(ctx, func(tx *TxWrap) error { @@ -362,6 +364,16 @@ func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, er return rtn, txErr } +func GetAllSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) { + var rtn []*ScreenType + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY closed, screenidx` + tx.SelectWrap(&rtn, query, sessionId) + return nil + }) + return rtn, txErr +} + func GetSessionById(ctx context.Context, id string) (*SessionType, error) { allSessionsUpdate, err := GetAllSessions(ctx) if err != nil { @@ -436,7 +448,7 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo func SetActiveSessionId(ctx context.Context, sessionId string) error { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT sessionid FROM session WHERE sessionid = ?` + query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT closed` if !tx.Exists(query, sessionId) { return fmt.Errorf("cannot switch to session, not found") } @@ -447,6 +459,16 @@ func SetActiveSessionId(ctx context.Context, sessionId string) error { return txErr } +func GetActiveSessionId(ctx context.Context) (string, error) { + var rtnId string + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT activesessionid FROM client` + rtnId = tx.GetString(query) + return nil + }) + return rtnId, txErr +} + func SetWinSize(ctx context.Context, winSize ClientWinSizeType) error { txErr := WithTx(ctx, func(tx *TxWrap) error { query := `UPDATE client SET winsize = ?` @@ -492,7 +514,7 @@ func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []strin 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 = ?` + query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT closed` if !tx.Exists(query, sessionId) { return fmt.Errorf("cannot create screen, no session found") } @@ -501,8 +523,8 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, 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) + maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT closed`, sessionId) + screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ? AND NOT closed`, sessionId) screenName := fmtUniqueName(origScreenName, "s%d", maxScreenIdx+1, screenNames) newScreenId = scbase.GenPromptUUID() query = `INSERT INTO screen (sessionid, screenid, name, activewindowid, screenidx, screenopts, ownerid, sharemode, incognito, closed) VALUES (?, ?, ?, ?, ?, ?, '', 'local', 0, 0)` @@ -810,7 +832,7 @@ func getNextId(ids []string, delId string) string { func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?` + query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ? AND NOT closed` if !tx.Exists(query, sessionId, screenId) { return fmt.Errorf("cannot switch to screen, screen=%s does not exist in session=%s", screenId, sessionId) } @@ -826,12 +848,66 @@ func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (U func CleanWindows() { } +func CloseScreen(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) { + var newActiveScreenId string + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?` + if !tx.Exists(query, sessionId, screenId) { + return fmt.Errorf("cannot close screen (not found)") + } + query = `SELECT closed FROM screen WHERE sessionid = ? AND screenid = ?` + closeVal := tx.GetBool(query, sessionId, screenId) + if closeVal { + return nil + } + query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT closed` + numScreens := tx.GetInt(query, sessionId) + if numScreens <= 1 { + return fmt.Errorf("cannot close the last screen in a session") + } + query = `UPDATE screen SET closed = 1, screenidx = 0 WHERE sessionid = ? AND screenid = ?` + tx.ExecWrap(query, sessionId, screenId) + isActive := tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, sessionId, screenId) + if isActive { + screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT closed ORDER BY screenidx`, sessionId) + nextId := getNextId(screenIds, screenId) + tx.ExecWrap(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId) + newActiveScreenId = nextId + } + return nil + }) + if txErr != nil { + return nil, txErr + } + update, session := MakeSingleSessionUpdate(sessionId) + session.ActiveScreenId = newActiveScreenId + session.Screens = append(session.Screens, &ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}) + return update, nil +} + +func UnCloseScreen(ctx context.Context, sessionId string, screenId string) error { + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ? AND closed` + if !tx.Exists(query, sessionId, screenId) { + return fmt.Errorf("cannot re-open screen (not found or not closed)") + } + origScreenName := tx.GetString(`SELECT name FROM screen WHERE sessionid = ? AND screenid = ?`, sessionId, screenId) + maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT closed`, sessionId) + screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ? AND NOT closed`, sessionId) + newScreenName := fmtUniqueName(origScreenName, "s-%d", 2, screenNames) + query = `UPDATE screen SET closed = 0, screenidx = ?, name = ? WHERE sessionid = ? AND screenid = ?` + tx.ExecWrap(query, maxScreenIdx+1, newScreenName, sessionId, screenId) + return nil + }) + return txErr +} + func DeleteScreen(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) { var newActiveScreenId string txErr := WithTx(ctx, func(tx *TxWrap) error { isActive := tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, sessionId, screenId) if isActive { - screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? ORDER BY screenidx`, sessionId) + screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT closed ORDER BY screenidx`, sessionId) nextId := getNextId(screenIds, screenId) tx.ExecWrap(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId) newActiveScreenId = nextId @@ -1068,7 +1144,7 @@ func SetScreenName(ctx context.Context, sessionId string, screenId string, name if !tx.Exists(query, sessionId, screenId) { return fmt.Errorf("screen does not exist") } - query = `SELECT screenid FROM screen WHERE sessionid = ? AND name = ?` + query = `SELECT screenid FROM screen WHERE sessionid = ? AND name = ? AND NOT closed` dupScreenId := tx.GetString(query, sessionId, name) if dupScreenId == screenId { return nil @@ -1169,8 +1245,10 @@ func GetSessionStats(ctx context.Context, sessionId string) (*SessionStatsType, if !tx.Exists(query, sessionId) { return fmt.Errorf("not found") } - query = `SELECT count(*) FROM screen WHERE sessionid = ?` + query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT closed` rtn.NumScreens = tx.GetInt(query, sessionId) + query = `SELECT count(*) FROM screen WHERE sessionid = ? AND closed` + rtn.NumClosedScreens = tx.GetInt(query, sessionId) query = `SELECT count(*) FROM window WHERE sessionid = ?` rtn.NumWindows = tx.GetInt(query, sessionId) query = `SELECT count(*) FROM line WHERE sessionid = ?` diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index ef40f2a3..eb6e3357 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -162,12 +162,13 @@ type SessionType struct { } type SessionStatsType struct { - SessionId string `json:"sessionid"` - NumScreens int `json:"numscreens"` - NumWindows int `json:"numwindows"` - NumLines int `json:"numlines"` - NumCmds int `json:"numcmds"` - DiskStats SessionDiskSizeType `json:"diskstats"` + SessionId string `json:"sessionid"` + NumScreens int `json:"numscreens"` + NumClosedScreens int `json:"numclosedscreens"` + NumWindows int `json:"numwindows"` + NumLines int `json:"numlines"` + NumCmds int `json:"numcmds"` + DiskStats SessionDiskSizeType `json:"diskstats"` } type WindowOptsType struct { @@ -592,9 +593,10 @@ type LineType struct { } type ResolveItem struct { - Name string - Num int - Id string + Name string + Num int + Id string + Hidden bool } type SSHOpts struct { diff --git a/pkg/sstore/txwrap.go b/pkg/sstore/txwrap.go index 02e5698a..67761950 100644 --- a/pkg/sstore/txwrap.go +++ b/pkg/sstore/txwrap.go @@ -105,6 +105,12 @@ func (tx *TxWrap) GetString(query string, args ...interface{}) string { return rtnStr } +func (tx *TxWrap) GetBool(query string, args ...interface{}) bool { + var rtnBool bool + tx.GetWrap(&rtnBool, query, args...) + return rtnBool +} + func (tx *TxWrap) SelectStrings(query string, args ...interface{}) []string { var rtnArr []string tx.SelectWrap(&rtnArr, query, args...)