diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index 25bc0f07..4c1e0272 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -14,6 +14,7 @@ CREATE TABLE session ( activescreenid varchar(36) NOT NULL, notifynum int NOT NULL, archived boolean NOT NULL, + archivedts bigint NOT NULL, ownerid varchar(36) NOT NULL, sharemode varchar(12) NOT NULL, accesskey varchar(36) NOT NULL @@ -44,6 +45,7 @@ CREATE TABLE screen ( sharemode varchar(12) NOT NULL, incognito boolean NOT NULL, archived boolean NOT NULL, + archivedts bigint NOT NULL, PRIMARY KEY (sessionid, screenid) ); diff --git a/db/schema.sql b/db/schema.sql index bb477b14..3e3edf92 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -15,6 +15,7 @@ CREATE TABLE session ( activescreenid varchar(36) NOT NULL, notifynum int NOT NULL, archived boolean NOT NULL, + archivedts bigint NOT NULL, ownerid varchar(36) NOT NULL, sharemode varchar(12) NOT NULL, accesskey varchar(36) NOT NULL @@ -43,6 +44,7 @@ CREATE TABLE screen ( sharemode varchar(12) NOT NULL, incognito boolean NOT NULL, archived boolean NOT NULL, + archivedts bigint NOT NULL, PRIMARY KEY (sessionid, screenid) ); CREATE TABLE screen_window ( diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 59161907..d9710ae9 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -124,7 +124,7 @@ func init() { registerCmdFn("session:showall", SessionShowAllCommand) registerCmdFn("screen", ScreenCommand) - registerCmdFn("screen:close", ScreenCloseCommand) + registerCmdFn("screen:archive", ScreenArchiveCommand) registerCmdFn("screen:purge", ScreenPurgeCommand) registerCmdFn("screen:open", ScreenOpenCommand) registerCmdAlias("screen:new", ScreenOpenCommand) @@ -381,42 +381,42 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. return update, rtnErr } -func ScreenCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { 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) + return nil, fmt.Errorf("/screen:archive cannot archive 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) + return nil, fmt.Errorf("/screen:archive cannot resolve screen arg: %v", err) } screenId = ri.Id } if screenId == "" { - return nil, fmt.Errorf("/screen:close no active screen or screen arg passed") + return nil, fmt.Errorf("/screen:archive no active screen or screen arg passed") } - closeVal := true + archiveVal := true if len(pk.Args) > 1 { - closeVal = resolveBool(pk.Args[1], true) + archiveVal = resolveBool(pk.Args[1], true) } var update sstore.UpdatePacket - if closeVal { - update, err = sstore.CloseScreen(ctx, ids.SessionId, screenId) + if archiveVal { + update, err = sstore.ArchiveScreen(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) + fmt.Printf("unarchive screen %s\n", screenId) + err = sstore.UnArchiveScreen(ctx, ids.SessionId, screenId) if err != nil { - return nil, fmt.Errorf("/screen:close cannot re-open screen: %v", err) + return nil, fmt.Errorf("/screen:archive 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) + return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err) } update, session := sstore.MakeSingleSessionUpdate(ids.SessionId) session.Screens = append(session.Screens, screen) @@ -427,7 +427,7 @@ func ScreenCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( 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) + return nil, fmt.Errorf("/screen:purge cannot purge screen: %w", err) } update, err := sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId) if err != nil { @@ -949,21 +949,21 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) 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) + screenArr, err := sstore.GetSessionScreens(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 + var archivedStr string if screen.Archived { - closedStr = " (closed)" + archivedStr = " (archived)" } screenIdxStr := "-" if screen.ScreenIdx != 0 { screenIdxStr = strconv.Itoa(int(screen.ScreenIdx)) } - outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+closedStr, screen.ScreenId, screenIdxStr) + outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+archivedStr, screen.ScreenId, screenIdxStr) buf.WriteString(outStr) } return sstore.ModelUpdate{ diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 1ce6b659..32e86a05 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -289,7 +289,7 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { session.Full = true } var screens []*ScreenType - query = `SELECT * FROM screen WHERE NOT archived ORDER BY screenidx` + query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts` tx.SelectWrap(&screens, query) screenMap := make(map[string][]*ScreenType) for _, screen := range screens { @@ -358,17 +358,7 @@ func GetWindowById(ctx context.Context, sessionId string, windowId string) (*Win func GetSessionScreens(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 screenidx` - tx.SelectWrap(&rtn, query, sessionId) - return nil - }) - 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 archived, screenidx` + query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts` tx.SelectWrap(&rtn, query, sessionId) return nil }) @@ -418,8 +408,8 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo names := tx.SelectStrings(`SELECT name FROM session`) sessionName = fmtUniqueName(sessionName, "session-%d", len(names)+1, names) maxSessionIdx := tx.GetInt(`SELECT COALESCE(max(sessionidx), 0) FROM session`) - query := `INSERT INTO session (sessionid, name, activescreenid, sessionidx, notifynum, archived, ownerid, sharemode, accesskey) - VALUES (?, ?, '', ?, ?, 0, '', 'local', '')` + query := `INSERT INTO session (sessionid, name, activescreenid, sessionidx, notifynum, archived, archivedts, ownerid, sharemode, accesskey) + VALUES (?, ?, '', ?, ?, 0, 0, '', 'local', '')` tx.ExecWrap(query, newSessionId, sessionName, maxSessionIdx+1, 0) _, err := InsertScreen(tx.Context(), newSessionId, "", true) if err != nil { @@ -528,7 +518,7 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ? AND NOT archived`, 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, archived) VALUES (?, ?, ?, ?, ?, ?, '', 'local', 0, 0)` + query = `INSERT INTO screen (sessionid, screenid, name, activewindowid, screenidx, screenopts, ownerid, sharemode, incognito, archived, archivedts) VALUES (?, ?, ?, ?, ?, ?, '', 'local', 0, 0, 0)` tx.ExecWrap(query, sessionId, newScreenId, screenName, newWindowId, maxScreenIdx+1, ScreenOptsType{}) layout := LayoutType{Type: LayoutFull} query = `INSERT INTO screen_window (sessionid, screenid, windowid, name, layout, selectedline, anchor, focustype) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` @@ -858,14 +848,20 @@ func CleanWindows(sessionId string) { tx.ExecWrap(query, sessionId, windowId) query = `DELETE FROM history WHERE sessionid = ? AND windowid = ?` tx.ExecWrap(query, sessionId, windowId) - query = `DELETE FROM line WHERE sessinid = ? AND windowid = ?` + query = `DELETE FROM line WHERE sessionid = ? AND windowid = ?` tx.ExecWrap(query, sessionId, windowId) } query = `SELECT cmdid FROM cmd WHERE sessionid = ? AND cmdid NOT IN (SELECT cmdid FROM line WHERE sessionid = ?)` removedCmds := tx.SelectStrings(query, sessionId, sessionId) query = `DELETE FROM cmd WHERE sessionid = ? AND cmdid NOT IN (SELECT cmdid FROM line WHERE sessionid = ?)` tx.ExecWrap(query, sessionId, sessionId) + if tx.Err != nil { + return nil + } fmt.Printf("removed cmds: %v\n", removedCmds) + for _, cmdId := range removedCmds { + DeletePtyOutFile(tx.Context(), sessionId, cmdId) + } return nil }) if txErr != nil { @@ -873,7 +869,7 @@ func CleanWindows(sessionId string) { } } -func CloseScreen(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) { +func ArchiveScreen(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 = ?` @@ -890,8 +886,8 @@ func CloseScreen(ctx context.Context, sessionId string, screenId string) (Update if numScreens <= 1 { return fmt.Errorf("cannot close the last screen in a session") } - query = `UPDATE screen SET archived = 1, screenidx = 0 WHERE sessionid = ? AND screenid = ?` - tx.ExecWrap(query, sessionId, screenId) + query = `UPDATE screen SET archived = 1, archivedts = ?, screenidx = 0 WHERE sessionid = ? AND screenid = ?` + tx.ExecWrap(query, time.Now().UnixMilli(), 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 archived ORDER BY screenidx`, sessionId) @@ -910,7 +906,7 @@ func CloseScreen(ctx context.Context, sessionId string, screenId string) (Update return update, nil } -func UnCloseScreen(ctx context.Context, sessionId string, screenId string) error { +func UnArchiveScreen(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 archived` if !tx.Exists(query, sessionId, screenId) { diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 88962683..a34bb166 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -153,6 +153,7 @@ type SessionType struct { AccessKey string `json:"-"` NotifyNum int64 `json:"notifynum"` Archived bool `json:"archived,omitempty"` + ArchivedTs int64 `json:"archivedts,omitempty"` Screens []*ScreenType `json:"screens"` Remotes []*RemoteInstance `json:"remotes"` @@ -358,6 +359,7 @@ type ScreenType struct { ShareMode string `json:"sharemode"` Incognito bool `json:"incognito,omitempty"` Archived bool `json:"archived,omitempty"` + ArchivedTs int64 `json:"archivedts,omitempty"` Windows []*ScreenWindowType `json:"windows"` // only for updates