From 02ae7ea972eab235e64c5815be3543cc4c85ee6b Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 20 Mar 2023 19:20:57 -0700 Subject: [PATCH] checkpoint on big cmd screen migration --- .gitignore | 3 + cmd/main-server.go | 31 +- db/migrations/000013_cmdmigration.down.sql | 2 + db/migrations/000013_cmdmigration.up.sql | 123 +++++++ pkg/cmdrunner/cmdrunner.go | 81 +++-- pkg/cmdrunner/resolver.go | 2 +- pkg/remote/remote.go | 36 +- pkg/scbase/scbase.go | 59 ++-- pkg/scws/scws.go | 2 +- pkg/sstore/dbops.go | 370 +++++++++------------ pkg/sstore/fileops.go | 30 +- pkg/sstore/migrate.go | 4 +- pkg/sstore/sstore.go | 175 ++++++++-- pkg/sstore/updatebus.go | 40 ++- 14 files changed, 607 insertions(+), 351 deletions(-) create mode 100644 .gitignore create mode 100644 db/migrations/000013_cmdmigration.down.sql create mode 100644 db/migrations/000013_cmdmigration.up.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..a89c46b43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +bin/ +*.out diff --git a/cmd/main-server.go b/cmd/main-server.go index 57f3a618d..5b782edf1 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -143,6 +143,12 @@ func HandleGetClientData(w http.ResponseWriter, r *http.Request) { WriteJsonError(w, err) return } + mdata, err := sstore.GetCmdMigrationInfo(r.Context()) + if err != nil { + WriteJsonError(w, err) + return + } + cdata.Migration = mdata WriteJsonSuccess(w, cdata) return } @@ -223,16 +229,16 @@ func HandleRtnState(w http.ResponseWriter, r *http.Request) { return }() qvals := r.URL.Query() - sessionId := qvals.Get("sessionid") + screenId := qvals.Get("screenid") cmdId := qvals.Get("cmdid") - if sessionId == "" || cmdId == "" { + if screenId == "" || cmdId == "" { w.WriteHeader(500) - w.Write([]byte(fmt.Sprintf("must specify sessionid and cmdid"))) + w.Write([]byte(fmt.Sprintf("must specify screenid and cmdid"))) return } - if _, err := uuid.Parse(sessionId); err != nil { + if _, err := uuid.Parse(screenId); err != nil { w.WriteHeader(500) - w.Write([]byte(fmt.Sprintf("invalid sessionid: %v", err))) + w.Write([]byte(fmt.Sprintf("invalid screenid: %v", err))) return } if _, err := uuid.Parse(cmdId); err != nil { @@ -240,7 +246,7 @@ func HandleRtnState(w http.ResponseWriter, r *http.Request) { w.Write([]byte(fmt.Sprintf("invalid cmdid: %v", err))) return } - data, err := cmdrunner.GetRtnStateDiff(r.Context(), sessionId, cmdId) + data, err := cmdrunner.GetRtnStateDiff(r.Context(), screenId, cmdId) if err != nil { w.WriteHeader(500) w.Write([]byte(fmt.Sprintf("cannot get rtnstate diff: %v", err))) @@ -278,16 +284,16 @@ func HandleRemotePty(w http.ResponseWriter, r *http.Request) { func HandleGetPtyOut(w http.ResponseWriter, r *http.Request) { qvals := r.URL.Query() - sessionId := qvals.Get("sessionid") + screenId := qvals.Get("screenid") cmdId := qvals.Get("cmdid") - if sessionId == "" || cmdId == "" { + if screenId == "" || cmdId == "" { w.WriteHeader(500) - w.Write([]byte(fmt.Sprintf("must specify sessionid and cmdid"))) + w.Write([]byte(fmt.Sprintf("must specify screenid and cmdid"))) return } - if _, err := uuid.Parse(sessionId); err != nil { + if _, err := uuid.Parse(screenId); err != nil { w.WriteHeader(500) - w.Write([]byte(fmt.Sprintf("invalid sessionid: %v", err))) + w.Write([]byte(fmt.Sprintf("invalid screenid: %v", err))) return } if _, err := uuid.Parse(cmdId); err != nil { @@ -295,7 +301,7 @@ func HandleGetPtyOut(w http.ResponseWriter, r *http.Request) { w.Write([]byte(fmt.Sprintf("invalid cmdid: %v", err))) return } - realOffset, data, err := sstore.ReadFullPtyOutFile(r.Context(), sessionId, cmdId) + realOffset, data, err := sstore.ReadFullPtyOutFile(r.Context(), screenId, cmdId) if err != nil { if errors.Is(err, fs.ErrNotExist) { w.WriteHeader(http.StatusOK) @@ -558,6 +564,7 @@ func main() { go telemetryLoop() go stdinReadWatch() go runWebSocketServer() + go sstore.RunCmdScreenMigration() gr := mux.NewRouter() gr.HandleFunc("/api/ptyout", AuthKeyWrap(HandleGetPtyOut)) gr.HandleFunc("/api/remote-pty", AuthKeyWrap(HandleRemotePty)) diff --git a/db/migrations/000013_cmdmigration.down.sql b/db/migrations/000013_cmdmigration.down.sql new file mode 100644 index 000000000..6332dc5ba --- /dev/null +++ b/db/migrations/000013_cmdmigration.down.sql @@ -0,0 +1,2 @@ +-- invalid, will throw an error, cannot migrate down +SELECT x; diff --git a/db/migrations/000013_cmdmigration.up.sql b/db/migrations/000013_cmdmigration.up.sql new file mode 100644 index 000000000..eed358ee0 --- /dev/null +++ b/db/migrations/000013_cmdmigration.up.sql @@ -0,0 +1,123 @@ +DELETE FROM cmd +WHERE screenid = ''; + +DELETE FROM line +WHERE screenid = ''; + +DELETE FROM cmd +WHERE cmdid NOT IN (SELECT cmdid FROM line); + +DELETE FROM line +WHERE cmdid <> '' AND cmdid NOT IN (SELECT cmdid FROM cmd); + +CREATE TABLE new_bookmark_cmd ( + bookmarkid varchar(36) NOT NULL, + screenid varchar(36) NOT NULL, + cmdid varchar(36) NOT NULL, + PRIMARY KEY (bookmarkid, screenid, cmdid) +); +INSERT INTO new_bookmark_cmd +SELECT + b.bookmarkid, + c.screenid, + c.cmdid +FROM bookmark_cmd b, cmd c +WHERE b.cmdid = c.cmdid; +DROP TABLE bookmark_cmd; +ALTER TABLE new_bookmark_cmd RENAME TO bookmark_cmd; + +ALTER TABLE client ADD COLUMN cmdstoretype varchar(20) DEFAULT 'session'; + +CREATE TABLE cmd_migrate ( + sessionid varchar(36) NOT NULL, + screenid varchar(36) NOT NULL, + cmdid varchar(36) NOT NULL +); +INSERT INTO cmd_migrate +SELECT sessionid, screenid, cmdid +FROM cmd; + +-- update primary key for screen +CREATE TABLE new_screen ( + screenid varchar(36) NOT NULL, + sessionid varchar(36) NOT NULL, + name varchar(50) NOT NULL, + screenidx int NOT NULL, + screenopts json NOT NULL, + ownerid varchar(36) NOT NULL, + sharemode varchar(12) NOT NULL, + curremoteownerid varchar(36) NOT NULL, + curremoteid varchar(36) NOT NULL, + curremotename varchar(50) NOT NULL, + nextlinenum int NOT NULL, + selectedline int NOT NULL, + anchor json NOT NULL, + focustype varchar(12) NOT NULL, + archived boolean NOT NULL, + archivedts bigint NOT NULL, + PRIMARY KEY (screenid) +); +INSERT INTO new_screen +SELECT screenid, sessionid, name, screenidx, screenopts, ownerid, sharemode, + curremoteownerid, curremoteid, curremotename, nextlinenum, selectedline, + anchor, focustype, archived, archivedts +FROM screen; +DROP TABLE screen; +ALTER TABLE new_screen RENAME TO screen; + +-- drop sessionid from line +CREATE TABLE new_line ( + screenid varchar(36) NOT NULL, + userid varchar(36) NOT NULL, + lineid varchar(36) NOT NULL, + ts bigint NOT NULL, + linenum int NOT NULL, + linenumtemp boolean NOT NULL, + linetype varchar(10) NOT NULL, + linelocal boolean NOT NULL, + text text NOT NULL, + cmdid varchar(36) NOT NULL, + ephemeral boolean NOT NULL, + contentheight int NOT NULL, + star int NOT NULL, + archived boolean NOT NULL, + renderer varchar(50) NOT NULL, + bookmarked boolean NOT NULL, + PRIMARY KEY (screenid, lineid) +); +INSERT INTO new_line +SELECT screenid, userid, lineid, ts, linenum, linenumtemp, linetype, linelocal, + text, cmdid, ephemeral, contentheight, star, archived, renderer, bookmarked +FROM line; +DROP TABLE line; +ALTER TABLE new_line RENAME TO line; + +-- drop sessionid from cmd +CREATE TABLE new_cmd ( + screenid varchar(36) NOT NULL, + cmdid varchar(36) NOT NULL, + remoteownerid varchar(36) NOT NULL, + remoteid varchar(36) NOT NULL, + remotename varchar(50) NOT NULL, + cmdstr text NOT NULL, + rawcmdstr text NOT NULL, + festate json NOT NULL, + statebasehash varchar(36) NOT NULL, + statediffhasharr json NOT NULL, + termopts json NOT NULL, + origtermopts json NOT NULL, + status varchar(10) NOT NULL, + startpk json NOT NULL, + doneinfo json NOT NULL, + runout json NOT NULL, + rtnstate boolean NOT NULL, + rtnbasehash varchar(36) NOT NULL, + rtndiffhasharr json NOT NULL, + PRIMARY KEY (screenid, cmdid) +); +INSERT INTO new_cmd +SELECT screenid, cmdid, remoteownerid, remoteid, remotename, cmdstr, cmdstr, + festate, statebasehash, statediffhasharr, termopts, origtermopts, status, startpk, doneinfo, runout, rtnstate, rtnbasehash, rtndiffhasharr +FROM cmd; +DROP TABLE cmd; +ALTER TABLE new_cmd RENAME TO cmd; diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index e976be136..cb22c3fd6 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -417,7 +417,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U // runPacket.State is set in remote.RunCommand() runPacket := packet.MakeRunPacket() runPacket.ReqId = uuid.New().String() - runPacket.CK = base.MakeCommandKey(ids.SessionId, scbase.GenPromptUUID()) + runPacket.CK = base.MakeCommandKey(ids.ScreenId, scbase.GenPromptUUID()) runPacket.UsePty = true ptermVal := defaultStr(pk.Kwargs["pterm"], DefaultPTERM) runPacket.TermOpts, err = GetUITermOpts(pk.UIContext.WinSize, ptermVal) @@ -433,12 +433,13 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U if err != nil { return nil, err } + cmd.RawCmdStr = pk.GetRawStr() update, err := addLineForCmd(ctx, "/run", true, ids, cmd, renderer) if err != nil { return nil, err } update.Interactive = pk.Interactive - sstore.MainBus.SendUpdate(ids.SessionId, update) + sstore.MainBus.SendScreenUpdate(ids.ScreenId, update) return nil, nil } @@ -559,7 +560,7 @@ func ScreenPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, fmt.Errorf("/screen:purge cannot purge screen: %w", err) } - update, err := sstore.DeleteScreen(ctx, ids.ScreenId) + update, err := sstore.PurgeScreen(ctx, ids.ScreenId, false) if err != nil { return nil, err } @@ -1255,10 +1256,10 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up func makeStaticCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr string, cmdOutput []byte) (*sstore.CmdType, error) { cmd := &sstore.CmdType{ - SessionId: ids.SessionId, ScreenId: ids.ScreenId, CmdId: scbase.GenPromptUUID(), CmdStr: cmdStr, + RawCmdStr: cmdStr, Remote: ids.Remote.RemotePtr, TermOpts: sstore.TermOpts{Rows: shexec.DefaultTermRows, Cols: shexec.DefaultTermCols, FlexRows: true, MaxPtySize: remote.DefaultMaxPtySize}, Status: sstore.CmdStatusDone, @@ -1272,13 +1273,13 @@ func makeStaticCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr if ids.Remote.FeState != nil { cmd.FeState = *ids.Remote.FeState } - err := sstore.CreateCmdPtyFile(ctx, cmd.SessionId, cmd.CmdId, cmd.TermOpts.MaxPtySize) + err := sstore.CreateCmdPtyFile(ctx, cmd.ScreenId, cmd.CmdId, cmd.TermOpts.MaxPtySize) if err != nil { // TODO tricky error since the command was a success, but we can't show the output return nil, fmt.Errorf("cannot create local ptyout file for %s command: %w", metaCmd, err) } // can ignore ptyupdate - _, err = sstore.AppendToCmdPtyBlob(ctx, cmd.SessionId, ids.ScreenId, cmd.CmdId, cmdOutput, 0) + _, err = sstore.AppendToCmdPtyBlob(ctx, ids.ScreenId, cmd.CmdId, cmdOutput, 0) if err != nil { // TODO tricky error since the command was a success, but we can't show the output return nil, fmt.Errorf("cannot append to local ptyout file for %s command: %v", metaCmd, err) @@ -1287,7 +1288,7 @@ func makeStaticCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr } func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids resolvedIds, cmd *sstore.CmdType, renderer string) (*sstore.ModelUpdate, error) { - rtnLine, err := sstore.AddCmdLine(ctx, ids.SessionId, ids.ScreenId, DefaultUserId, cmd, renderer) + rtnLine, err := sstore.AddCmdLine(ctx, ids.ScreenId, DefaultUserId, cmd, renderer) if err != nil { return nil, err } @@ -1495,7 +1496,7 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if strings.TrimSpace(text) == "" { return nil, fmt.Errorf("cannot post empty comment") } - rtnLine, err := sstore.AddCommentLine(ctx, ids.SessionId, ids.ScreenId, DefaultUserId, text) + rtnLine, err := sstore.AddCommentLine(ctx, ids.ScreenId, DefaultUserId, text) if err != nil { return nil, err } @@ -1637,7 +1638,7 @@ func SessionDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, err } - update, err := sstore.DeleteSession(ctx, ids.SessionId) + update, err := sstore.PurgeSession(ctx, ids.SessionId) if err != nil { return nil, fmt.Errorf("cannot delete session: %v", err) } @@ -1907,10 +1908,9 @@ func HistoryPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) continue } lineObj := &sstore.LineType{ - SessionId: historyItem.SessionId, - ScreenId: historyItem.ScreenId, - LineId: historyItem.LineId, - Remove: true, + ScreenId: historyItem.ScreenId, + LineId: historyItem.LineId, + Remove: true, } update.Lines = append(update.Lines, lineObj) } @@ -2076,7 +2076,7 @@ func splitLinesForInfo(str string) []string { func resizeRunningCommand(ctx context.Context, cmd *sstore.CmdType, newCols int) error { siPk := packet.MakeSpecialInputPacket() - siPk.CK = base.MakeCommandKey(cmd.SessionId, cmd.CmdId) + siPk.CK = base.MakeCommandKey(cmd.ScreenId, cmd.CmdId) siPk.WinSize = &packet.WinSize{Rows: int(cmd.TermOpts.Rows), Cols: newCols} msh := remote.GetRemoteById(cmd.Remote.RemoteId) if msh == nil { @@ -2088,7 +2088,7 @@ func resizeRunningCommand(ctx context.Context, cmd *sstore.CmdType, newCols int) } newTermOpts := cmd.TermOpts newTermOpts.Cols = int64(newCols) - err = sstore.UpdateCmdTermOpts(ctx, cmd.SessionId, cmd.CmdId, newTermOpts) + err = sstore.UpdateCmdTermOpts(ctx, cmd.ScreenId, cmd.CmdId, newTermOpts) if err != nil { return err } @@ -2112,7 +2112,7 @@ func ScreenResizeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("/screen:resize invalid zero/negative 'cols' argument") } cols = base.BoundInt(cols, shexec.MinTermCols, shexec.MaxTermCols) - runningCmds, err := sstore.GetRunningScreenCmds(ctx, ids.SessionId, ids.ScreenId) + runningCmds, err := sstore.GetRunningScreenCmds(ctx, ids.ScreenId) if err != nil { return nil, fmt.Errorf("/screen:resize cannot get running commands: %v", err) } @@ -2140,7 +2140,7 @@ func LineSetHeightCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("/line:setheight requires 2 arguments (linearg and height)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } @@ -2168,7 +2168,7 @@ func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto return nil, fmt.Errorf("/line:set requires 1 argument (linearg)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } @@ -2186,7 +2186,7 @@ func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if len(varsUpdated) == 0 { return nil, fmt.Errorf("/line:set requires a value to set: %s", formatStrs([]string{"renderer"}, "or", false)) } - updatedLine, err := sstore.GetLineById(ctx, ids.SessionId, ids.ScreenId, lineId) + updatedLine, err := sstore.GetLineById(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("/line:set cannot retrieve updated line: %v", err) } @@ -2332,21 +2332,21 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("/line:bookmark requires an argument (line number or id)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } if lineId == "" { return nil, fmt.Errorf("line %q not found", lineArg) } - lineObj, cmdObj, err := sstore.GetLineCmdByLineId(ctx, ids.SessionId, ids.ScreenId, lineId) + lineObj, cmdObj, err := sstore.GetLineCmdByLineId(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("/line:bookmark error getting line: %v", err) } if cmdObj == nil { return nil, fmt.Errorf("cannot bookmark non-cmd line") } - ck := base.MakeCommandKey(lineObj.SessionId, cmdObj.CmdId) + ck := base.MakeCommandKey(lineObj.ScreenId, cmdObj.CmdId) bm := &sstore.BookmarkType{ BookmarkId: uuid.New().String(), CreatedTs: time.Now().UnixMilli(), @@ -2360,7 +2360,7 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("cannot insert bookmark: %v", err) } - newLineObj, err := sstore.GetLineById(ctx, ids.SessionId, ids.ScreenId, lineId) + newLineObj, err := sstore.GetLineById(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("/line:bookmark error getting line: %v", err) } @@ -2387,7 +2387,7 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst return nil, fmt.Errorf("/line:star only takes up to 2 arguments (line-number and star-value)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } @@ -2405,7 +2405,7 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if err != nil { return nil, fmt.Errorf("/line:star error updating star value: %v", err) } - lineObj, err := sstore.GetLineById(ctx, ids.SessionId, ids.ScreenId, lineId) + lineObj, err := sstore.GetLineById(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("/line:star error getting line: %v", err) } @@ -2425,7 +2425,7 @@ func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, fmt.Errorf("/line:archive requires an argument (line number or id)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } @@ -2440,7 +2440,7 @@ func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, fmt.Errorf("/line:archive error updating hidden status: %v", err) } - lineObj, err := sstore.GetLineById(ctx, ids.SessionId, ids.ScreenId, lineId) + lineObj, err := sstore.GetLineById(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("/line:archive error getting line: %v", err) } @@ -2461,7 +2461,7 @@ func LinePurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } var lineIds []string for _, lineArg := range pk.Args { - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } @@ -2470,17 +2470,16 @@ func LinePurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } lineIds = append(lineIds, lineId) } - err = sstore.PurgeLinesByIds(ctx, ids.SessionId, lineIds) + err = sstore.PurgeLinesByIds(ctx, ids.ScreenId, lineIds) if err != nil { return nil, fmt.Errorf("/line:purge error purging lines: %v", err) } update := sstore.ModelUpdate{} for _, lineId := range lineIds { lineObj := &sstore.LineType{ - SessionId: ids.SessionId, - ScreenId: ids.ScreenId, - LineId: lineId, - Remove: true, + ScreenId: ids.ScreenId, + LineId: lineId, + Remove: true, } update.Lines = append(update.Lines, lineObj) } @@ -2496,14 +2495,14 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst return nil, fmt.Errorf("/line:show requires an argument (line number or id)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } if lineId == "" { return nil, fmt.Errorf("line %q not found", lineArg) } - line, cmd, err := sstore.GetLineCmdByLineId(ctx, ids.SessionId, ids.ScreenId, lineId) + line, cmd, err := sstore.GetLineCmdByLineId(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("error getting line: %v", err) } @@ -2542,7 +2541,7 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if cmd.RtnState { buf.WriteString(fmt.Sprintf(" %-15s %s\n", "rtnstate", "true")) } - stat, _ := sstore.StatCmdPtyFile(ctx, cmd.SessionId, cmd.CmdId) + stat, _ := sstore.StatCmdPtyFile(ctx, cmd.ScreenId, cmd.CmdId) if stat == nil { buf.WriteString(fmt.Sprintf(" %-15s %s\n", "file", "-")) } else { @@ -2599,11 +2598,11 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor return nil, fmt.Errorf("/signal requires a second argument (signal name)") } lineArg := pk.Args[0] - lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.ScreenId, lineArg) + lineId, err := sstore.FindLineIdByArg(ctx, ids.ScreenId, lineArg) if err != nil { return nil, fmt.Errorf("error looking up lineid: %v", err) } - line, cmd, err := sstore.GetLineCmdByLineId(ctx, ids.SessionId, ids.ScreenId, lineId) + line, cmd, err := sstore.GetLineCmdByLineId(ctx, ids.ScreenId, lineId) if err != nil { return nil, fmt.Errorf("error getting line: %v", err) } @@ -2640,7 +2639,7 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor return nil, fmt.Errorf("cannot send signal, remote is not connected") } siPk := packet.MakeSpecialInputPacket() - siPk.CK = base.MakeCommandKey(cmd.SessionId, cmd.CmdId) + siPk.CK = base.MakeCommandKey(cmd.ScreenId, cmd.CmdId) siPk.SigName = sigArg err = msh.SendSpecialInput(siPk) if err != nil { @@ -2950,8 +2949,8 @@ func displayStateUpdateDiff(buf *bytes.Buffer, oldState packet.ShellState, newSt } } -func GetRtnStateDiff(ctx context.Context, sessionId string, cmdId string) ([]byte, error) { - cmd, err := sstore.GetCmdById(ctx, sessionId, cmdId) +func GetRtnStateDiff(ctx context.Context, screenId string, cmdId string) ([]byte, error) { + cmd, err := sstore.GetCmdByScreenId(ctx, screenId, cmdId) if err != nil { return nil, err } diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index 67f3ed6c0..1b5aaa6cc 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -298,7 +298,7 @@ func resolveSession(ctx context.Context, sessionArg string, curSessionArg string } func resolveLine(ctx context.Context, sessionId string, screenId string, lineArg string, curLineArg string) (*ResolveItem, error) { - lines, err := sstore.GetLineResolveItems(ctx, sessionId, screenId) + lines, err := sstore.GetLineResolveItems(ctx, screenId) if err != nil { return nil, fmt.Errorf("could not get lines: %v", err) } diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index c173345a8..a2352497c 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -578,7 +578,7 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState { func (msh *MShellProc) NotifyRemoteUpdate() { rstate := msh.GetRemoteRuntimeState() update := &sstore.ModelUpdate{Remotes: []interface{}{rstate}} - sstore.MainBus.SendUpdate("", update) + sstore.MainBus.SendUpdate(update) } func GetAllRemoteRuntimeState() []RemoteRuntimeState { @@ -811,7 +811,7 @@ func sendRemotePtyUpdate(remoteId string, dataOffset int64, data []byte) { PtyData64: data64, PtyDataLen: int64(len(data)), } - sstore.MainBus.SendUpdate("", update) + sstore.MainBus.SendUpdate(update) } func (msh *MShellProc) isWaitingForPassword_nolock() bool { @@ -1265,8 +1265,8 @@ func RunCommand(ctx context.Context, sessionId string, screenId string, remotePt if remotePtr.OwnerId != "" { return nil, nil, fmt.Errorf("cannot run command against another user's remote '%s'", remotePtr.MakeFullRemoteRef()) } - if sessionId != runPacket.CK.GetSessionId() { - return nil, nil, fmt.Errorf("run commands sessionids do not match") + if screenId != runPacket.CK.GetGroupId() { + return nil, nil, fmt.Errorf("run commands screenids do not match") } msh := GetRemoteById(remotePtr.RemoteId) if msh == nil { @@ -1284,7 +1284,7 @@ func RunCommand(ctx context.Context, sessionId string, screenId string, remotePt } ok, existingPSC := msh.testAndSetPendingStateCmd(remotePtr.Name, newPSC) if !ok { - line, _, err := sstore.GetLineCmdByCmdId(ctx, sessionId, screenId, existingPSC.GetCmdId()) + line, _, err := sstore.GetLineCmdByCmdId(ctx, screenId, existingPSC.GetCmdId()) if err != nil { return nil, nil, fmt.Errorf("cannot run command while a stateful command is still running: %v", err) } @@ -1344,10 +1344,10 @@ func RunCommand(ctx context.Context, sessionId string, screenId string, remotePt status = sstore.CmdStatusDetached } cmd := &sstore.CmdType{ - SessionId: runPacket.CK.GetSessionId(), - ScreenId: screenId, + ScreenId: runPacket.CK.GetGroupId(), CmdId: runPacket.CK.GetCmdId(), CmdStr: runPacket.Command, + RawCmdStr: runPacket.Command, Remote: remotePtr, FeState: *sstore.FeStateFromShellState(currentState), StatePtr: *statePtr, @@ -1358,7 +1358,7 @@ func RunCommand(ctx context.Context, sessionId string, screenId string, remotePt RunOut: nil, RtnState: runPacket.ReturnState, } - err = sstore.CreateCmdPtyFile(ctx, cmd.SessionId, cmd.CmdId, cmd.TermOpts.MaxPtySize) + err = sstore.CreateCmdPtyFile(ctx, cmd.ScreenId, cmd.CmdId, cmd.TermOpts.MaxPtySize) if err != nil { // TODO the cmd is running, so this is a tricky error to handle return nil, nil, fmt.Errorf("cannot create local ptyout file for running command: %v", err) @@ -1476,12 +1476,12 @@ func makeDataAckPacket(ck base.CommandKey, fdNum int, ackLen int, err error) *pa func (msh *MShellProc) notifyHangups_nolock() { for ck, _ := range msh.RunningCmds { - cmd, err := sstore.GetCmdById(context.Background(), ck.GetSessionId(), ck.GetCmdId()) + cmd, err := sstore.GetCmdByScreenId(context.Background(), ck.GetGroupId(), ck.GetCmdId()) if err != nil { continue } update := sstore.ModelUpdate{Cmd: cmd} - sstore.MainBus.SendUpdate(ck.GetSessionId(), update) + sstore.MainBus.SendScreenUpdate(ck.GetGroupId(), update) } msh.RunningCmds = make(map[base.CommandKey]RunCmdType) } @@ -1505,12 +1505,12 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { msh.WriteToPtyBuffer("*error updating cmddone: %v\n", err) return } - screens, err := sstore.UpdateScreensWithCmdFg(context.Background(), donePk.CK.GetSessionId(), donePk.CK.GetCmdId()) + screen, err := sstore.UpdateScreenWithCmdFg(context.Background(), donePk.CK.GetGroupId(), donePk.CK.GetCmdId()) if err != nil { msh.WriteToPtyBuffer("*error trying to update cmd-fg screens: %v\n", err) // fall-through (nothing to do) } - update.Screens = screens + update.Screens = []*sstore.ScreenType{screen} rct := msh.GetRunningCmd(donePk.CK) var statePtr *sstore.ShellStatePtr if donePk.FinalState != nil && rct != nil { @@ -1550,13 +1550,13 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } } - sstore.MainBus.SendUpdate(donePk.CK.GetSessionId(), update) + sstore.MainBus.SendScreenUpdate(donePk.CK.GetGroupId(), update) return } func (msh *MShellProc) handleCmdFinalPacket(finalPk *packet.CmdFinalPacketType) { defer msh.RemoveRunningCmd(finalPk.CK) - rtnCmd, err := sstore.GetCmdById(context.Background(), finalPk.CK.GetSessionId(), finalPk.CK.GetCmdId()) + rtnCmd, err := sstore.GetCmdByScreenId(context.Background(), finalPk.CK.GetGroupId(), finalPk.CK.GetCmdId()) if err != nil { log.Printf("error calling GetCmdById in handleCmdFinalPacket: %v\n", err) return @@ -1566,7 +1566,7 @@ func (msh *MShellProc) handleCmdFinalPacket(finalPk *packet.CmdFinalPacketType) } log.Printf("finalpk %s (hangup): %s\n", finalPk.CK, finalPk.Error) sstore.HangupCmd(context.Background(), finalPk.CK) - rtnCmd, err = sstore.GetCmdById(context.Background(), finalPk.CK.GetSessionId(), finalPk.CK.GetCmdId()) + rtnCmd, err = sstore.GetCmdByScreenId(context.Background(), finalPk.CK.GetGroupId(), finalPk.CK.GetCmdId()) if err != nil { log.Printf("error getting cmd(2) in handleCmdFinalPacket: %v\n", err) return @@ -1576,7 +1576,7 @@ func (msh *MShellProc) handleCmdFinalPacket(finalPk *packet.CmdFinalPacketType) return } update := &sstore.ModelUpdate{Cmd: rtnCmd} - sstore.MainBus.SendUpdate(finalPk.CK.GetSessionId(), update) + sstore.MainBus.SendScreenUpdate(finalPk.CK.GetGroupId(), update) } // TODO notify FE about cmd errors @@ -1600,7 +1600,7 @@ func (msh *MShellProc) handleDataPacket(dataPk *packet.DataPacketType, dataPosMa if len(realData) > 0 { dataPos := dataPosMap[dataPk.CK] rcmd := msh.GetRunningCmd(dataPk.CK) - update, err := sstore.AppendToCmdPtyBlob(context.Background(), dataPk.CK.GetSessionId(), rcmd.ScreenId, dataPk.CK.GetCmdId(), realData, dataPos) + update, err := sstore.AppendToCmdPtyBlob(context.Background(), rcmd.ScreenId, dataPk.CK.GetCmdId(), realData, dataPos) if err != nil { ack = makeDataAckPacket(dataPk.CK, dataPk.FdNum, 0, err) } else { @@ -1608,7 +1608,7 @@ func (msh *MShellProc) handleDataPacket(dataPk *packet.DataPacketType, dataPosMa } dataPosMap[dataPk.CK] += int64(len(realData)) if update != nil { - sstore.MainBus.SendUpdate(dataPk.CK.GetSessionId(), update) + sstore.MainBus.SendScreenUpdate(dataPk.CK.GetGroupId(), update) } } if ack != nil { diff --git a/pkg/scbase/scbase.go b/pkg/scbase/scbase.go index 2f0cee3ee..e4e2dc13a 100644 --- a/pkg/scbase/scbase.go +++ b/pkg/scbase/scbase.go @@ -27,6 +27,7 @@ const HomeVarName = "HOME" const PromptHomeVarName = "PROMPT_HOME" const PromptDevVarName = "PROMPT_DEV" const SessionsDirBaseName = "sessions" +const ScreensDirBaseName = "screens" const PromptLockFile = "prompt.lock" const PromptDirName = "prompt" const PromptDevDirName = "prompt-dev" @@ -36,6 +37,7 @@ const PromptAuthKeyFileName = "prompt.authkey" const MShellVersion = "v0.2.0" var SessionDirCache = make(map[string]string) +var ScreenDirCache = make(map[string]string) var BaseLock = &sync.Mutex{} var BuildTime = "-" @@ -164,6 +166,7 @@ func AcquirePromptLock() (*os.File, error) { return fd, nil } +// deprecated (v0.1.8) func EnsureSessionDir(sessionId string) (string, error) { if sessionId == "" { return "", fmt.Errorf("cannot get session dir for blank sessionid") @@ -186,12 +189,41 @@ func EnsureSessionDir(sessionId string) (string, error) { return sdir, nil } +// deprecated (v0.1.8) func GetSessionsDir() string { promptHome := GetPromptHomeDir() sdir := path.Join(promptHome, SessionsDirBaseName) return sdir } +func EnsureScreenDir(screenId string) (string, error) { + if screenId == "" { + return "", fmt.Errorf("cannot get screen dir for blank sessionid") + } + BaseLock.Lock() + sdir, ok := ScreenDirCache[screenId] + BaseLock.Unlock() + if ok { + return sdir, nil + } + scHome := GetPromptHomeDir() + sdir = path.Join(scHome, ScreensDirBaseName, screenId) + err := ensureDir(sdir) + if err != nil { + return "", err + } + BaseLock.Lock() + ScreenDirCache[screenId] = sdir + BaseLock.Unlock() + return sdir, nil +} + +func GetScreensDir() string { + promptHome := GetPromptHomeDir() + sdir := path.Join(promptHome, ScreensDirBaseName) + return sdir +} + func ensureDir(dirName string) error { info, err := os.Stat(dirName) if errors.Is(err, fs.ErrNotExist) { @@ -211,7 +243,8 @@ func ensureDir(dirName string) error { return nil } -func PtyOutFile(sessionId string, cmdId string) (string, error) { +// deprecated (v0.1.8) +func PtyOutFile_Sessions(sessionId string, cmdId string) (string, error) { sdir, err := EnsureSessionDir(sessionId) if err != nil { return "", err @@ -225,30 +258,18 @@ func PtyOutFile(sessionId string, cmdId string) (string, error) { return fmt.Sprintf("%s/%s.ptyout.cf", sdir, cmdId), nil } -func RunOutFile(sessionId string, cmdId string) (string, error) { - sdir, err := EnsureSessionDir(sessionId) +func PtyOutFile(screenId string, cmdId string) (string, error) { + sdir, err := EnsureScreenDir(screenId) if err != nil { return "", err } - if sessionId == "" { - return "", fmt.Errorf("cannot get runout file for blank sessionid") + if screenId == "" { + return "", fmt.Errorf("cannot get ptyout file for blank screenid") } if cmdId == "" { - return "", fmt.Errorf("cannot get runout file for blank cmdid") + return "", fmt.Errorf("cannot get ptyout file for blank cmdid") } - return fmt.Sprintf("%s/%s.runout", sdir, cmdId), nil -} - -type PromptFileNameGenerator struct { - PromptHome string -} - -func (g PromptFileNameGenerator) PtyOutFile(ck base.CommandKey) string { - return path.Join(g.PromptHome, SessionsDirBaseName, ck.GetSessionId(), ck.GetCmdId()+".ptyout") -} - -func (g PromptFileNameGenerator) RunOutFile(ck base.CommandKey) string { - return path.Join(g.PromptHome, SessionsDirBaseName, ck.GetSessionId(), ck.GetCmdId()+".runout") + return fmt.Sprintf("%s/%s.ptyout.cf", sdir, cmdId), nil } func GenPromptUUID() string { diff --git a/pkg/scws/scws.go b/pkg/scws/scws.go index 50bb7388b..97694ddc9 100644 --- a/pkg/scws/scws.go +++ b/pkg/scws/scws.go @@ -99,7 +99,7 @@ func (ws *WSState) WatchScreen(sessionId string, screenId string) { } ws.SessionId = sessionId ws.ScreenId = screenId - ws.UpdateCh = sstore.MainBus.RegisterChannel(ws.ClientId, ws.SessionId) + ws.UpdateCh = sstore.MainBus.RegisterChannel(ws.ClientId, ws.ScreenId) go ws.RunUpdates(ws.UpdateCh) } diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 3af2c051c..64f300686 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -440,12 +440,10 @@ func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, if screen == nil { return nil, nil } - query = `SELECT sessionid FROM screen WHERE screenid = ?` - sessionId := tx.GetString(query, screenId) - query = `SELECT * FROM line WHERE sessionid = ? AND screenid = ? ORDER BY linenum` - tx.Select(&screen.Lines, query, sessionId, screen.ScreenId) - query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND screenid = ?)` - screen.Cmds = SelectMapsGen[*CmdType](tx, query, sessionId, screen.ScreenId) + query = `SELECT * FROM line WHERE screenid = ? ORDER BY linenum` + tx.Select(&screen.Lines, query, screen.ScreenId) + query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE screenid = ?)` + screen.Cmds = SelectMapsGen[*CmdType](tx, query, screen.ScreenId) return screen, nil }) } @@ -700,22 +698,22 @@ func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) { }) } -func FindLineIdByArg(ctx context.Context, sessionId string, screenId string, lineArg string) (string, error) { +func FindLineIdByArg(ctx context.Context, screenId string, lineArg string) (string, error) { var lineId string txErr := WithTx(ctx, func(tx *TxWrap) error { lineNum, err := strconv.Atoi(lineArg) if err == nil { // valid linenum - query := `SELECT lineid FROM line WHERE sessionid = ? AND screenid = ? AND linenum = ?` - lineId = tx.GetString(query, sessionId, screenId, lineNum) + query := `SELECT lineid FROM line WHERE screenid = ? AND linenum = ?` + lineId = tx.GetString(query, screenId, lineNum) } else if len(lineArg) == 8 { // prefix id string match - query := `SELECT lineid FROM line WHERE sessionid = ? AND screenid = ? AND substr(lineid, 1, 8) = ?` - lineId = tx.GetString(query, sessionId, screenId, lineArg) + query := `SELECT lineid FROM line WHERE screenid = ? AND substr(lineid, 1, 8) = ?` + lineId = tx.GetString(query, screenId, lineArg) } else { // id match - query := `SELECT lineid FROM line WHERE sessionid = ? AND screenid = ? AND lineid = ?` - lineId = tx.GetString(query, sessionId, screenId, lineArg) + query := `SELECT lineid FROM line WHERE screenid = ? AND lineid = ?` + lineId = tx.GetString(query, screenId, lineArg) } return nil }) @@ -725,33 +723,33 @@ func FindLineIdByArg(ctx context.Context, sessionId string, screenId string, lin return lineId, nil } -func GetLineCmdByLineId(ctx context.Context, sessionId string, screenId string, lineId string) (*LineType, *CmdType, error) { +func GetLineCmdByLineId(ctx context.Context, screenId string, lineId string) (*LineType, *CmdType, error) { return WithTxRtn3(ctx, func(tx *TxWrap) (*LineType, *CmdType, error) { var lineVal LineType - query := `SELECT * FROM line WHERE sessionid = ? AND screenid = ? AND lineid = ?` - found := tx.Get(&lineVal, query, sessionId, screenId, lineId) + query := `SELECT * FROM line WHERE screenid = ? AND lineid = ?` + found := tx.Get(&lineVal, query, screenId, lineId) if !found { return nil, nil, nil } var cmdRtn *CmdType if lineVal.CmdId != "" { - query = `SELECT * FROM cmd WHERE sessionid = ? AND cmdid = ?` - cmdRtn = GetMapGen[*CmdType](tx, query, sessionId, lineVal.CmdId) + query = `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?` + cmdRtn = GetMapGen[*CmdType](tx, query, screenId, lineVal.CmdId) } return &lineVal, cmdRtn, nil }) } -func GetLineCmdByCmdId(ctx context.Context, sessionId string, screenId string, cmdId string) (*LineType, *CmdType, error) { +func GetLineCmdByCmdId(ctx context.Context, screenId string, cmdId string) (*LineType, *CmdType, error) { return WithTxRtn3(ctx, func(tx *TxWrap) (*LineType, *CmdType, error) { var lineVal LineType - query := `SELECT * FROM line WHERE sessionid = ? AND screenid = ? AND cmdid = ?` - found := tx.Get(&lineVal, query, sessionId, screenId, cmdId) + query := `SELECT * FROM line WHERE screenid = ? AND cmdid = ?` + found := tx.Get(&lineVal, query, screenId, cmdId) if !found { return nil, nil, nil } - query = `SELECT * FROM cmd WHERE sessionid = ? AND cmdid = ?` - cmdRtn := GetMapGen[*CmdType](tx, query, sessionId, cmdId) + query = `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?` + cmdRtn := GetMapGen[*CmdType](tx, query, screenId, cmdId) return &lineVal, cmdRtn, nil }) } @@ -770,24 +768,24 @@ func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error { return fmt.Errorf("cmd should have screenid set") } return WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?` - if !tx.Exists(query, line.SessionId, line.ScreenId) { - return fmt.Errorf("screen not found, cannot insert line[%s/%s]", line.SessionId, line.ScreenId) + query := `SELECT screenid FROM screen WHERE screenid = ?` + if !tx.Exists(query, line.ScreenId) { + return fmt.Errorf("screen not found, cannot insert line[%s]", line.ScreenId) } - query = `SELECT nextlinenum FROM screen WHERE sessionid = ? AND screenid = ?` - nextLineNum := tx.GetInt(query, line.SessionId, line.ScreenId) + query = `SELECT nextlinenum FROM screen WHERE screenid = ?` + nextLineNum := tx.GetInt(query, line.ScreenId) line.LineNum = int64(nextLineNum) - query = `INSERT INTO line ( sessionid, screenid, userid, lineid, ts, linenum, linenumtemp, linelocal, linetype, text, cmdid, renderer, ephemeral, contentheight, star, archived, bookmarked, pinned) - VALUES (:sessionid,:screenid,:userid,:lineid,:ts,:linenum,:linenumtemp,:linelocal,:linetype,:text,:cmdid,:renderer,:ephemeral,:contentheight,:star,:archived,:bookmarked,:pinned)` + query = `INSERT INTO line ( screenid, userid, lineid, ts, linenum, linenumtemp, linelocal, linetype, text, cmdid, renderer, ephemeral, contentheight, star, archived, bookmarked) + VALUES (:screenid,:userid,:lineid,:ts,:linenum,:linenumtemp,:linelocal,:linetype,:text,:cmdid,:renderer,:ephemeral,:contentheight,:star,:archived,:bookmarked)` tx.NamedExec(query, line) - query = `UPDATE screen SET nextlinenum = ? WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, nextLineNum+1, line.SessionId, line.ScreenId) + query = `UPDATE screen SET nextlinenum = ? WHERE screenid = ?` + tx.Exec(query, nextLineNum+1, line.ScreenId) if cmd != nil { cmd.OrigTermOpts = cmd.TermOpts cmdMap := cmd.ToMap() query = ` -INSERT INTO cmd ( sessionid, screenid, cmdid, remoteownerid, remoteid, remotename, cmdstr, festate, statebasehash, statediffhasharr, termopts, origtermopts, status, startpk, doneinfo, rtnstate, runout, rtnbasehash, rtndiffhasharr) - VALUES (:sessionid,:screenid,:cmdid,:remoteownerid,:remoteid,:remotename,:cmdstr,:festate,:statebasehash,:statediffhasharr,:termopts,:origtermopts,:status,:startpk,:doneinfo,:rtnstate,:runout,:rtnbasehash,:rtndiffhasharr) +INSERT INTO cmd ( screenid, cmdid, remoteownerid, remoteid, remotename, cmdstr, rawcmdstr, festate, statebasehash, statediffhasharr, termopts, origtermopts, status, startpk, doneinfo, rtnstate, runout, rtnbasehash, rtndiffhasharr) + VALUES (:screenid,:cmdid,:remoteownerid,:remoteid,:remotename,:cmdstr,:rawcmdstr,:festate,:statebasehash,:statediffhasharr,:termopts,:origtermopts,:status,:startpk,:doneinfo,:rtnstate,:runout,:rtnbasehash,:rtndiffhasharr) ` tx.NamedExec(query, cmdMap) } @@ -795,11 +793,11 @@ INSERT INTO cmd ( sessionid, screenid, cmdid, remoteownerid, remoteid, remotena }) } -func GetCmdById(ctx context.Context, sessionId string, cmdId string) (*CmdType, error) { +func GetCmdByScreenId(ctx context.Context, screenId string, cmdId string) (*CmdType, error) { var cmd *CmdType err := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT * FROM cmd WHERE sessionid = ? AND cmdid = ?` - cmd = GetMapGen[*CmdType](tx, query, sessionId, cmdId) + query := `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?` + cmd = GetMapGen[*CmdType](tx, query, screenId, cmdId) return nil }) if err != nil { @@ -808,18 +806,6 @@ func GetCmdById(ctx context.Context, sessionId string, cmdId string) (*CmdType, return cmd, nil } -func HasDoneInfo(ctx context.Context, ck base.CommandKey) (bool, error) { - var found bool - txErr := WithTx(ctx, func(tx *TxWrap) error { - found = tx.Exists(`SELECT sessionid FROM cmd WHERE sessionid = ? AND cmdid = ? AND doneinfo is NOT NULL`, ck.GetSessionId(), ck.GetCmdId()) - return nil - }) - if txErr != nil { - return false, txErr - } - return found, nil -} - func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, doneInfo *CmdDoneInfo) (*ModelUpdate, error) { if doneInfo == nil { return nil, fmt.Errorf("invalid cmddone packet") @@ -829,10 +815,10 @@ func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, doneInfo *CmdDon } var rtnCmd *CmdType txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `UPDATE cmd SET status = ?, doneinfo = ? WHERE sessionid = ? AND cmdid = ?` - tx.Exec(query, CmdStatusDone, quickJson(doneInfo), ck.GetSessionId(), ck.GetCmdId()) + query := `UPDATE cmd SET status = ?, doneinfo = ? WHERE screenid = ? AND cmdid = ?` + tx.Exec(query, CmdStatusDone, quickJson(doneInfo), ck.GetGroupId(), ck.GetCmdId()) var err error - rtnCmd, err = GetCmdById(tx.Context(), ck.GetSessionId(), ck.GetCmdId()) + rtnCmd, err = GetCmdByScreenId(tx.Context(), ck.GetGroupId(), ck.GetCmdId()) if err != nil { return err } @@ -852,8 +838,8 @@ func UpdateCmdRtnState(ctx context.Context, ck base.CommandKey, statePtr ShellSt return fmt.Errorf("cannot update cmdrtnstate, empty ck") } txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `UPDATE cmd SET rtnbasehash = ?, rtndiffhasharr = ? WHERE sessionid = ? AND cmdid = ?` - tx.Exec(query, statePtr.BaseHash, quickJsonArr(statePtr.DiffHashArr), ck.GetSessionId(), ck.GetCmdId()) + query := `UPDATE cmd SET rtnbasehash = ?, rtndiffhasharr = ? WHERE screenid = ? AND cmdid = ?` + tx.Exec(query, statePtr.BaseHash, quickJsonArr(statePtr.DiffHashArr), ck.GetGroupId(), ck.GetCmdId()) return nil }) if txErr != nil { @@ -867,8 +853,8 @@ func AppendCmdErrorPk(ctx context.Context, errPk *packet.CmdErrorPacketType) err return fmt.Errorf("invalid cmderror packet (no ck)") } return WithTx(ctx, func(tx *TxWrap) error { - query := `UPDATE cmd SET runout = json_insert(runout, '$[#]', ?) WHERE sessionid = ? AND cmdid = ?` - tx.Exec(query, quickJson(errPk), errPk.CK.GetSessionId(), errPk.CK.GetCmdId()) + query := `UPDATE cmd SET runout = json_insert(runout, '$[#]', ?) WHERE screenid = ? AND cmdid = ?` + tx.Exec(query, quickJson(errPk), errPk.CK.GetGroupId(), errPk.CK.GetCmdId()) return nil }) } @@ -899,8 +885,8 @@ func HangupRunningCmdsByRemoteId(ctx context.Context, remoteId string) error { func HangupCmd(ctx context.Context, ck base.CommandKey) error { return WithTx(ctx, func(tx *TxWrap) error { - query := `UPDATE cmd SET status = ? WHERE sessionid = ? AND cmdid = ?` - tx.Exec(query, CmdStatusHangup, ck.GetSessionId(), ck.GetCmdId()) + query := `UPDATE cmd SET status = ? WHERE screenid = ? AND cmdid = ?` + tx.Exec(query, CmdStatusHangup, ck.GetGroupId(), ck.GetCmdId()) return nil }) } @@ -949,40 +935,27 @@ func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (* return &ModelUpdate{ActiveSessionId: sessionId, Sessions: []*SessionType{bareSession}}, nil } -func cleanSessionCmds(ctx context.Context, sessionId string) error { +// screen may not exist at this point (so don't query screen table) +func cleanScreenCmds(ctx context.Context, screenId string) error { var removedCmds []string txErr := WithTx(ctx, func(tx *TxWrap) error { - 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.Exec(query, sessionId, sessionId) - query = `DELETE FROM bookmark_cmd WHERE sessionid = ? AND cmdid NOT IN (SELECT cmdid FROM cmd WHERE sessionid = ?)` - tx.Exec(query, sessionId, sessionId) + query := `SELECT cmdid FROM cmd WHERE screenid = ? AND cmdid NOT IN (SELECT cmdid FROM line WHERE screenid = ?)` + removedCmds = tx.SelectStrings(query, screenId, screenId) + query = `DELETE FROM cmd WHERE screenid = ? AND cmdid NOT IN (SELECT cmdid FROM line WHERE screenid = ?)` + tx.Exec(query, screenId, screenId) + query = `DELETE FROM bookmark_cmd WHERE screenid = ? AND cmdid NOT IN (SELECT cmdid FROM cmd WHERE screenid = ?)` + tx.Exec(query, screenId, screenId) return nil }) if txErr != nil { return txErr } for _, cmdId := range removedCmds { - DeletePtyOutFile(ctx, sessionId, cmdId) + DeletePtyOutFile(ctx, screenId, cmdId) } return nil } -func CleanScreen(sessionId string, screenId string) { - // NOTE: context.Background() here! (this could take a long time, and is async) - txErr := WithTx(context.Background(), func(tx *TxWrap) error { - query := `DELETE FROM history WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, sessionId, screenId) - query = `DELETE FROM line WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, sessionId, screenId) - return cleanSessionCmds(tx.Context(), sessionId) - }) - if txErr != nil { - fmt.Printf("ERROR cleaning session:%s screen:%s : %v\n", sessionId, screenId, txErr) - } -} - func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) { var isActive bool txErr := WithTx(ctx, func(tx *TxWrap) error { @@ -1042,7 +1015,7 @@ func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) err return txErr } -func DeleteScreen(ctx context.Context, screenId string) (UpdatePacket, error) { +func PurgeScreen(ctx context.Context, screenId string, sessionDel bool) (UpdatePacket, error) { var sessionId string var isActive bool txErr := WithTx(ctx, func(tx *TxWrap) error { @@ -1050,30 +1023,39 @@ func DeleteScreen(ctx context.Context, screenId string) (UpdatePacket, error) { if !tx.Exists(query, screenId) { return fmt.Errorf("cannot purge screen (not found)") } - query = `SELECT sessionid FROM screen WHERE screenid = ?` - sessionId = tx.GetString(query, screenId) - if sessionId == "" { - return fmt.Errorf("cannot purge screen (no sessionid)") - } - query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived` - numScreens := tx.GetInt(query, sessionId) - if numScreens <= 1 { - return fmt.Errorf("cannot purge the last screen in a session") - } - 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) - nextId := getNextId(screenIds, screenId) - tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId) + if !sessionDel { + query = `SELECT sessionid FROM screen WHERE screenid = ?` + sessionId = tx.GetString(query, screenId) + if sessionId == "" { + return fmt.Errorf("cannot purge screen (no sessionid)") + } + query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived` + numScreens := tx.GetInt(query, sessionId) + if numScreens <= 1 { + return fmt.Errorf("cannot purge the last screen in a session") + } + 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) + nextId := getNextId(screenIds, screenId) + tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId) + } } query = `DELETE FROM screen WHERE screenid = ?` tx.Exec(query, screenId) + query = `DELETE FROM history WHERE screenid = ?` + tx.Exec(query, screenId) + query = `DELETE FROM line WHERE screenid = ?` + tx.Exec(query, screenId) return nil }) if txErr != nil { return nil, txErr } - go CleanScreen(sessionId, screenId) + go cleanScreenCmds(context.Background(), screenId) + if sessionDel { + return nil, nil + } update := ModelUpdate{} update.Screens = []*ScreenType{&ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}} if isActive { @@ -1312,13 +1294,12 @@ func SetScreenName(ctx context.Context, sessionId string, screenId string, name func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT sessionid FROM screen WHERE screenid = ?` - sessionId := tx.GetString(query, screenId) - if sessionId == "" { - return fmt.Errorf("screen sessionid does not exist") + query := `SELECT screenid FROM screen WHERE screenid = ?` + if !tx.Exists(query, screenId) { + return fmt.Errorf("screen does not exist") } - query = `UPDATE line SET archived = 1 WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, sessionId, screenId) + query = `UPDATE line SET archived = 1 WHERE screenid = ?` + tx.Exec(query, screenId) return nil }) if txErr != nil { @@ -1333,19 +1314,13 @@ func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, err func PurgeScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { var lineIds []string - var sessionId string txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT sessionid FROM screen WHERE screenid = ?` - sessionId = tx.GetString(query, screenId) - if sessionId == "" { - return fmt.Errorf("screen sessionid does not exist") - } - query = `SELECT lineid FROM line WHERE sessionid = ? AND screenid = ?` - lineIds = tx.SelectStrings(query, sessionId, screenId) - query = `DELETE FROM line WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, sessionId, screenId) - query = `DELETE FROM history WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, sessionId, screenId) + query := `SELECT lineid FROM line WHERE screenid = ?` + lineIds = tx.SelectStrings(query, screenId) + query = `DELETE FROM line WHERE screenid = ?` + tx.Exec(query, screenId) + query = `DELETE FROM history WHERE screenid = ?` + tx.Exec(query, screenId) query = `UPDATE screen SET nextlinenum = 1 WHERE screenid = ?` tx.Exec(query, screenId) return nil @@ -1353,7 +1328,7 @@ func PurgeScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error if txErr != nil { return nil, txErr } - go cleanSessionCmds(context.Background(), sessionId) + go cleanScreenCmds(context.Background(), screenId) screen, err := GetScreenById(ctx, screenId) if err != nil { return nil, err @@ -1364,21 +1339,20 @@ func PurgeScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error } for _, lineId := range lineIds { line := &LineType{ - SessionId: sessionId, - ScreenId: screenId, - LineId: lineId, - Remove: true, + ScreenId: screenId, + LineId: lineId, + Remove: true, } screenLines.Lines = append(screenLines.Lines, line) } return &ModelUpdate{Screens: []*ScreenType{screen}, ScreenLines: screenLines}, nil } -func GetRunningScreenCmds(ctx context.Context, sessionId string, screenId string) ([]*CmdType, error) { +func GetRunningScreenCmds(ctx context.Context, screenId string) ([]*CmdType, error) { var rtn []*CmdType txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT * from cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND screenid = ?) AND status = ?` - rtn = SelectMapsGen[*CmdType](tx, query, sessionId, screenId, CmdStatusRunning) + query := `SELECT * from cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE screenid = ?) AND status = ?` + rtn = SelectMapsGen[*CmdType](tx, query, screenId, CmdStatusRunning) return nil }) if txErr != nil { @@ -1387,10 +1361,10 @@ func GetRunningScreenCmds(ctx context.Context, sessionId string, screenId string return rtn, nil } -func UpdateCmdTermOpts(ctx context.Context, sessionId string, cmdId string, termOpts TermOpts) error { +func UpdateCmdTermOpts(ctx context.Context, screenId string, cmdId string, termOpts TermOpts) error { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `UPDATE cmd SET termopts = ? WHERE sessionid = ? AND cmdid = ?` - tx.Exec(query, termOpts, sessionId, cmdId) + query := `UPDATE cmd SET termopts = ? WHERE screenid = ? AND cmdid = ?` + tx.Exec(query, termOpts, screenId, cmdId) return nil }) return txErr @@ -1417,25 +1391,24 @@ func ScreenReset(ctx context.Context, screenId string) ([]*RemoteInstance, error }) } -func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) { +func PurgeSession(ctx context.Context, sessionId string) (UpdatePacket, error) { var newActiveSessionId string + var screenIds []string txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT sessionid FROM session WHERE sessionid = ?` if !tx.Exists(query, sessionId) { return fmt.Errorf("session does not exist") } + query = `SELECT screenid FROM screen WHERE sessionid = ?` + screenIds = tx.SelectStrings(query, sessionId) + for _, screenId := range screenIds { + _, err := PurgeScreen(ctx, screenId, true) + if err != nil { + return fmt.Errorf("error purging screen[%s]: %v", screenId, err) + } + } query = `DELETE FROM session WHERE sessionid = ?` tx.Exec(query, sessionId) - query = `DELETE FROM screen WHERE sessionid = ?` - tx.Exec(query, sessionId) - query = `DELETE FROM history WHERE sessionid = ?` - tx.Exec(query, sessionId) - query = `DELETE FROM line WHERE sessionid = ?` - tx.Exec(query, sessionId) - query = `DELETE FROM cmd WHERE sessionid = ?` - tx.Exec(query, sessionId) - query = `DELETE FROM bookmark_cmd WHERE sessionid = ?` - tx.Exec(query, sessionId) newActiveSessionId, _ = fixActiveSessionId(tx.Context()) return nil }) @@ -1453,6 +1426,9 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) } } update.Sessions = append(update.Sessions, &SessionType{SessionId: sessionId, Remove: true}) + for _, screenId := range screenIds { + update.Screens = append(update.Screens, &ScreenType{ScreenId: screenId, Remove: true}) + } return update, nil } @@ -1561,7 +1537,7 @@ func GetSessionStats(ctx context.Context, sessionId string) (*SessionStatsType, rtn.NumArchivedScreens = tx.GetInt(query, sessionId) query = `SELECT count(*) FROM line WHERE sessionid = ?` rtn.NumLines = tx.GetInt(query, sessionId) - query = `SELECT count(*) FROM cmd WHERE sessionid = ?` + query = `SELECT count(*) FROM cmd WHERE screenid IN (select screenid FROM screen WHERE sessionid = ?)` rtn.NumCmds = tx.GetInt(query, sessionId) return nil }) @@ -1686,11 +1662,11 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter return GetScreenById(ctx, screenId) } -func GetLineResolveItems(ctx context.Context, sessionId string, screenId string) ([]ResolveItem, error) { +func GetLineResolveItems(ctx context.Context, screenId string) ([]ResolveItem, error) { var rtn []ResolveItem txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT lineid as id, linenum as num FROM line WHERE sessionid = ? AND screenid = ? ORDER BY linenum` - tx.Select(&rtn, query, sessionId, screenId) + query := `SELECT lineid as id, linenum as num FROM line WHERE screenid = ? ORDER BY linenum` + tx.Select(&rtn, query, screenId) return nil }) if txErr != nil { @@ -1699,39 +1675,24 @@ func GetLineResolveItems(ctx context.Context, sessionId string, screenId string) return rtn, nil } -func UpdateScreensWithCmdFg(ctx context.Context, sessionId string, cmdId string) ([]*ScreenType, error) { - var rtn []*ScreenType - txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT screenid - FROM screen s - WHERE - s.sessionid = ? - AND s.focustype = 'cmd-fg' - AND s.selectedline IN (SELECT linenum - FROM line l - WHERE l.sessionid = s.sessionid - AND l.screenid = s.screenid - AND l.cmdid = ? - )` - screenIds := tx.SelectStrings(query, sessionId, cmdId) - if len(screenIds) == 0 { - return nil +func UpdateScreenWithCmdFg(ctx context.Context, screenId string, cmdId string) (*ScreenType, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) { + query := `SELECT screenid + FROM screen s + WHERE s.screenid = ? AND s.focustype = 'cmd-fg' + AND s.selectedline IN (SELECT linenum FROM line l WHERE l.screenid = s.screenid AND l.cmdid = ?) + ` + if !tx.Exists(query, screenId, cmdId) { + return nil, nil } - for _, screenId := range screenIds { - editMap := make(map[string]interface{}) - editMap[ScreenField_Focus] = ScreenFocusInput - screen, err := UpdateScreen(tx.Context(), screenId, editMap) - if err != nil { - return err - } - rtn = append(rtn, screen) + editMap := make(map[string]interface{}) + editMap[ScreenField_Focus] = ScreenFocusInput + screen, err := UpdateScreen(tx.Context(), screenId, editMap) + if err != nil { + return nil, err } - return nil + return screen, nil }) - if txErr != nil { - return nil, txErr - } - return rtn, nil } func StoreStateBase(ctx context.Context, state *packet.ShellState) error { @@ -1865,12 +1826,12 @@ func UpdateLineRenderer(ctx context.Context, lineId string, renderer string) err } // can return nil, nil if line is not found -func GetLineById(ctx context.Context, sessionId string, screenId string, lineId string) (*LineType, error) { +func GetLineById(ctx context.Context, screenId string, lineId string) (*LineType, error) { var rtn *LineType txErr := WithTx(ctx, func(tx *TxWrap) error { var line LineType - query := `SELECT * FROM line WHERE sessionid = ? AND screenid = ? AND lineid = ?` - found := tx.Get(&line, query, sessionId, screenId, lineId) + query := `SELECT * FROM line WHERE screenid = ? AND lineid = ?` + found := tx.Get(&line, query, screenId, lineId) if found { rtn = &line } @@ -1891,32 +1852,28 @@ func SetLineArchivedById(ctx context.Context, lineId string, archived bool) erro return txErr } -func purgeCmdById(ctx context.Context, sessionId string, cmdId string) error { +func purgeCmdByScreenId(ctx context.Context, screenId string, cmdId string) error { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `DELETE FROM cmd WHERE sessionid = ? AND cmdid = ?` - tx.Exec(query, sessionId, cmdId) - return DeletePtyOutFile(tx.Context(), sessionId, cmdId) + query := `DELETE FROM cmd WHERE screenid = ? AND cmdid = ?` + tx.Exec(query, screenId, cmdId) + return DeletePtyOutFile(tx.Context(), screenId, cmdId) }) return txErr } -func PurgeLinesByIds(ctx context.Context, sessionId string, lineIds []string) error { +func PurgeLinesByIds(ctx context.Context, screenId string, lineIds []string) error { txErr := WithTx(ctx, func(tx *TxWrap) error { for _, lineId := range lineIds { - query := `SELECT cmdid FROM line WHERE sessionid = ? AND lineid = ?` - cmdId := tx.GetString(query, sessionId, lineId) - query = `DELETE FROM line WHERE sessionid = ? AND lineid = ?` - tx.Exec(query, sessionId, lineId) - query = `DELETE FROM history WHERE sessionid = ? AND lineid = ?` - tx.Exec(query, sessionId, lineId) + query := `SELECT cmdid FROM line WHERE screenid = ? AND lineid = ?` + cmdId := tx.GetString(query, screenId, lineId) + query = `DELETE FROM line WHERE screenid = ? AND lineid = ?` + tx.Exec(query, screenId, lineId) + query = `DELETE FROM history WHERE screenid = ? AND lineid = ?` + tx.Exec(query, screenId, lineId) if cmdId != "" { - query = `SELECT count(*) FROM line WHERE sessionid = ? AND cmdid = ?` - cmdRefCount := tx.GetInt(query, sessionId, cmdId) - if cmdRefCount == 0 { - err := purgeCmdById(tx.Context(), sessionId, cmdId) - if err != nil { - return err - } + err := purgeCmdByScreenId(tx.Context(), screenId, cmdId) + if err != nil { + return err } } } @@ -2084,7 +2041,7 @@ type bookmarkOrderType struct { type bookmarkCmdType struct { BookmarkId string - SessionId string + ScreenId string CmdId string } @@ -2110,12 +2067,12 @@ func GetBookmarks(ctx context.Context, tag string) ([]*BookmarkType, error) { } } var cmds []bookmarkCmdType - query = `SELECT bookmarkid, sessionid, cmdid FROM bookmark_cmd` + query = `SELECT bookmarkid, screenid, cmdid FROM bookmark_cmd` tx.Select(&cmds, query) for _, cmd := range cmds { bm := bmMap[cmd.BookmarkId] if bm != nil { - bm.Cmds = append(bm.Cmds, base.MakeCommandKey(cmd.SessionId, cmd.CmdId)) + bm.Cmds = append(bm.Cmds, base.MakeCommandKey(cmd.ScreenId, cmd.CmdId)) } } return nil @@ -2137,11 +2094,11 @@ func GetBookmarkById(ctx context.Context, bookmarkId string, tag string) (*Bookm query = `SELECT orderidx FROM bookmark_order WHERE bookmarkid = ? AND tag = ?` orderIdx := tx.GetInt(query, bookmarkId, tag) rtn.OrderIdx = int64(orderIdx) - query = `SELECT bookmarkid, sessionid, cmdid FROM bookmark_cmd WHERE bookmarkid = ?` + query = `SELECT bookmarkid, screenid, cmdid FROM bookmark_cmd WHERE bookmarkid = ?` var cmds []bookmarkCmdType tx.Select(&cmds, query, bookmarkId) for _, cmd := range cmds { - rtn.Cmds = append(rtn.Cmds, base.MakeCommandKey(cmd.SessionId, cmd.CmdId)) + rtn.Cmds = append(rtn.Cmds, base.MakeCommandKey(cmd.ScreenId, cmd.CmdId)) } return nil }) @@ -2188,13 +2145,13 @@ func InsertBookmark(ctx context.Context, bm *BookmarkType) error { query = `INSERT INTO bookmark_order (tag, bookmarkid, orderidx) VALUES (?, ?, ?)` tx.Exec(query, tag, bm.BookmarkId, maxOrder+1) } - query = `INSERT INTO bookmark_cmd (bookmarkid, sessionid, cmdid) VALUES (?, ?, ?)` + query = `INSERT INTO bookmark_cmd (bookmarkid, screenid, cmdid) VALUES (?, ?, ?)` for _, ck := range bm.Cmds { - tx.Exec(query, bm.BookmarkId, ck.GetSessionId(), ck.GetCmdId()) + tx.Exec(query, bm.BookmarkId, ck.GetGroupId(), ck.GetCmdId()) } - query = `UPDATE line SET bookmarked = 1 WHERE sessionid = ? AND cmdid = ?` + query = `UPDATE line SET bookmarked = 1 WHERE screenid = ? AND cmdid = ?` for _, ck := range bm.Cmds { - tx.Exec(query, ck.GetSessionId(), ck.GetCmdId()) + tx.Exec(query, ck.GetGroupId(), ck.GetCmdId()) } return nil }) @@ -2248,7 +2205,7 @@ func DeleteBookmark(ctx context.Context, bookmarkId string) error { tx.Exec(query, bookmarkId) query = `DELETE FROM bookmark_order WHERE bookmarkid = ?` tx.Exec(query, bookmarkId) - query = `UPDATE line SET bookmarked = 0 WHERE bookmarked AND cmdid <> '' AND (sessionid||cmdid) IN (SELECT sessionid||cmdid FROM bookmark_cmd WHERE bookmarkid = ?) ` + query = `UPDATE line SET bookmarked = 0 WHERE bookmarked AND cmdid <> '' AND (screenid||cmdid) IN (SELECT screenid||cmdid FROM bookmark_cmd WHERE bookmarkid = ?) ` tx.Exec(query, bookmarkId) query = `DELETE FROM bookmark_cmd WHERE bookmarkid = ?` tx.Exec(query, bookmarkId) @@ -2377,7 +2334,7 @@ func PurgeHistoryByIds(ctx context.Context, historyIds []string) ([]*HistoryItem tx.Exec(query, quickJsonArr(historyIds)) for _, hitem := range rtn { if hitem.LineId != "" { - err := PurgeLinesByIds(tx.Context(), hitem.SessionId, []string{hitem.LineId}) + err := PurgeLinesByIds(tx.Context(), hitem.ScreenId, []string{hitem.LineId}) if err != nil { return nil, err } @@ -2386,10 +2343,3 @@ func PurgeHistoryByIds(ctx context.Context, historyIds []string) ([]*HistoryItem return rtn, nil }) } - -func GetScreenIdFromCmd(ctx context.Context, sessionId string, cmdId string) (string, error) { - return WithTxRtn(ctx, func(tx *TxWrap) (string, error) { - query := `SELECT screenid FROM cmd WHERE sessionid = ? AND cmdid = ?` - return tx.GetString(query, sessionId, cmdId), nil - }) -} diff --git a/pkg/sstore/fileops.go b/pkg/sstore/fileops.go index c27971a27..2585c50df 100644 --- a/pkg/sstore/fileops.go +++ b/pkg/sstore/fileops.go @@ -12,8 +12,8 @@ import ( "github.com/scripthaus-dev/sh2-server/pkg/scbase" ) -func CreateCmdPtyFile(ctx context.Context, sessionId string, cmdId string, maxSize int64) error { - ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId) +func CreateCmdPtyFile(ctx context.Context, screenId string, cmdId string, maxSize int64) error { + ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId) if err != nil { return err } @@ -24,22 +24,22 @@ func CreateCmdPtyFile(ctx context.Context, sessionId string, cmdId string, maxSi return f.Close() } -func StatCmdPtyFile(ctx context.Context, sessionId string, cmdId string) (*cirfile.Stat, error) { - ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId) +func StatCmdPtyFile(ctx context.Context, screenId string, cmdId string) (*cirfile.Stat, error) { + ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId) if err != nil { return nil, err } return cirfile.StatCirFile(ctx, ptyOutFileName) } -func AppendToCmdPtyBlob(ctx context.Context, sessionId string, screenId string, cmdId string, data []byte, pos int64) (*PtyDataUpdate, error) { +func AppendToCmdPtyBlob(ctx context.Context, screenId string, cmdId string, data []byte, pos int64) (*PtyDataUpdate, error) { if screenId == "" { return nil, fmt.Errorf("cannot append to PtyBlob, screenid is not set") } if pos < 0 { return nil, fmt.Errorf("invalid seek pos '%d' in AppendToCmdPtyBlob", pos) } - ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId) + ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId) if err != nil { return nil, err } @@ -54,7 +54,6 @@ func AppendToCmdPtyBlob(ctx context.Context, sessionId string, screenId string, } data64 := base64.StdEncoding.EncodeToString(data) update := &PtyDataUpdate{ - SessionId: sessionId, ScreenId: screenId, CmdId: cmdId, PtyPos: pos, @@ -65,8 +64,8 @@ func AppendToCmdPtyBlob(ctx context.Context, sessionId string, screenId string, } // returns (offset, data, err) -func ReadFullPtyOutFile(ctx context.Context, sessionId string, cmdId string) (int64, []byte, error) { - ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId) +func ReadFullPtyOutFile(ctx context.Context, screenId string, cmdId string) (int64, []byte, error) { + ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId) if err != nil { return 0, nil, err } @@ -141,8 +140,8 @@ func FullSessionDiskSize() (map[string]SessionDiskSizeType, error) { return rtn, nil } -func DeletePtyOutFile(ctx context.Context, sessionId string, cmdId string) error { - ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId) +func DeletePtyOutFile(ctx context.Context, screenId string, cmdId string) error { + ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId) if err != nil { return err } @@ -157,3 +156,12 @@ func DeleteSessionDir(ctx context.Context, sessionId string) error { fmt.Printf("remove-all %s\n", sessionDir) return os.RemoveAll(sessionDir) } + +func DeleteScreenDir(ctx context.Context, screenId string) error { + screenDir, err := scbase.EnsureScreenDir(screenId) + if err != nil { + return fmt.Errorf("error getting screendir: %w", err) + } + fmt.Printf("remove-all %s\n", screenDir) + return os.RemoveAll(screenDir) +} diff --git a/pkg/sstore/migrate.go b/pkg/sstore/migrate.go index 894e4769e..613006922 100644 --- a/pkg/sstore/migrate.go +++ b/pkg/sstore/migrate.go @@ -17,7 +17,7 @@ import ( "github.com/golang-migrate/migrate/v4" ) -const MaxMigration = 12 +const MaxMigration = 13 const MigratePrimaryScreenVersion = 9 func MakeMigrate() (*migrate.Migrate, error) { @@ -81,7 +81,9 @@ func MigrateUp() error { if err != nil { return fmt.Errorf("error creating database backup: %v", err) } + startTime := time.Now() err = m.Migrate(MaxMigration) + log.Printf("[db] migration took %v\n", time.Since(startTime)) if err != nil { return err } diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index b85b83eae..6d52f8150 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -76,6 +76,11 @@ const ( ScreenFocusCmdFg = "cmd-fg" ) +const ( + CmdStoreTypeSession = "session" + CmdStoreTypeScreen = "screen" +) + const MaxTzNameLen = 50 var globalDBLock = &sync.Mutex{} @@ -187,17 +192,26 @@ type FeOptsType struct { TermFontSize int `json:"termfontsize,omitempty"` } +type ClientMigrationData struct { + MigrationType string `json:"migrationtype"` + MigrationPos int `json:"migrationpos"` + MigrationTotal int `json:"migrationtotal"` + MigrationDone bool `json:"migrationdone"` +} + type ClientData struct { - ClientId string `json:"clientid"` - UserId string `json:"userid"` - UserPrivateKeyBytes []byte `json:"-"` - UserPublicKeyBytes []byte `json:"-"` - UserPrivateKey *ecdsa.PrivateKey `json:"-" dbmap:"-"` - UserPublicKey *ecdsa.PublicKey `json:"-" dbmap:"-"` - ActiveSessionId string `json:"activesessionid"` - WinSize ClientWinSizeType `json:"winsize"` - ClientOpts ClientOptsType `json:"clientopts"` - FeOpts FeOptsType `json:"feopts"` + ClientId string `json:"clientid"` + UserId string `json:"userid"` + UserPrivateKeyBytes []byte `json:"-"` + UserPublicKeyBytes []byte `json:"-"` + UserPrivateKey *ecdsa.PrivateKey `json:"-" dbmap:"-"` + UserPublicKey *ecdsa.PublicKey `json:"-" dbmap:"-"` + ActiveSessionId string `json:"activesessionid"` + WinSize ClientWinSizeType `json:"winsize"` + ClientOpts ClientOptsType `json:"clientopts"` + FeOpts FeOptsType `json:"feopts"` + CmdStoreType string `json:"cmdstoretype"` + Migration *ClientMigrationData `json:"migration,omitempty" dbmap:"-"` } func (ClientData) UseDBMap() {} @@ -641,7 +655,6 @@ func (ri *RemoteInstance) ToMap() map[string]interface{} { } type LineType struct { - SessionId string `json:"sessionid"` ScreenId string `json:"screenid"` UserId string `json:"userid"` LineId string `json:"lineid"` @@ -657,7 +670,6 @@ type LineType struct { ContentHeight int64 `json:"contentheight,omitempty"` Star bool `json:"star,omitempty"` Bookmarked bool `json:"bookmarked,omitempty"` - Pinned bool `json:"pinned,omitempty"` Archived bool `json:"archived,omitempty"` Remove bool `json:"remove,omitempty"` } @@ -832,12 +844,18 @@ type CmdDoneInfo struct { DurationMs int64 `json:"durationms"` } +type CmdMapType struct { + SessionId string `json:"sessionid"` + ScreenId string `json:"screenid"` + CmdId string `json:"cmdid"` +} + type CmdType struct { - SessionId string `json:"sessionid"` ScreenId string `json:"screenid"` CmdId string `json:"cmdid"` Remote RemotePtrType `json:"remote"` CmdStr string `json:"cmdstr"` + RawCmdStr string `json:"rawcmdstr"` FeState FeStateType `json:"festate"` StatePtr ShellStatePtr `json:"state"` TermOpts TermOpts `json:"termopts"` @@ -894,13 +912,13 @@ func (r *RemoteType) FromMap(m map[string]interface{}) bool { func (cmd *CmdType) ToMap() map[string]interface{} { rtn := make(map[string]interface{}) - rtn["sessionid"] = cmd.SessionId rtn["screenid"] = cmd.ScreenId rtn["cmdid"] = cmd.CmdId rtn["remoteownerid"] = cmd.Remote.OwnerId rtn["remoteid"] = cmd.Remote.RemoteId rtn["remotename"] = cmd.Remote.Name rtn["cmdstr"] = cmd.CmdStr + rtn["rawcmdstr"] = cmd.RawCmdStr rtn["festate"] = quickJson(cmd.FeState) rtn["statebasehash"] = cmd.StatePtr.BaseHash rtn["statediffhasharr"] = quickJsonArr(cmd.StatePtr.DiffHashArr) @@ -917,13 +935,13 @@ func (cmd *CmdType) ToMap() map[string]interface{} { } func (cmd *CmdType) FromMap(m map[string]interface{}) bool { - quickSetStr(&cmd.SessionId, m, "sessionid") quickSetStr(&cmd.ScreenId, m, "screenid") quickSetStr(&cmd.CmdId, m, "cmdid") quickSetStr(&cmd.Remote.OwnerId, m, "remoteownerid") quickSetStr(&cmd.Remote.RemoteId, m, "remoteid") quickSetStr(&cmd.Remote.Name, m, "remotename") quickSetStr(&cmd.CmdStr, m, "cmdstr") + quickSetStr(&cmd.RawCmdStr, m, "rawcmdstr") quickSetJson(&cmd.FeState, m, "festate") quickSetStr(&cmd.StatePtr.BaseHash, m, "statebasehash") quickSetJsonArr(&cmd.StatePtr.DiffHashArr, m, "statediffhasharr") @@ -939,9 +957,8 @@ func (cmd *CmdType) FromMap(m map[string]interface{}) bool { return true } -func makeNewLineCmd(sessionId string, screenId string, userId string, cmdId string, renderer string) *LineType { +func makeNewLineCmd(screenId string, userId string, cmdId string, renderer string) *LineType { rtn := &LineType{} - rtn.SessionId = sessionId rtn.ScreenId = screenId rtn.UserId = userId rtn.LineId = scbase.GenPromptUUID() @@ -954,9 +971,8 @@ func makeNewLineCmd(sessionId string, screenId string, userId string, cmdId stri return rtn } -func makeNewLineText(sessionId string, screenId string, userId string, text string) *LineType { +func makeNewLineText(screenId string, userId string, text string) *LineType { rtn := &LineType{} - rtn.SessionId = sessionId rtn.ScreenId = screenId rtn.UserId = userId rtn.LineId = scbase.GenPromptUUID() @@ -968,8 +984,8 @@ func makeNewLineText(sessionId string, screenId string, userId string, text stri return rtn } -func AddCommentLine(ctx context.Context, sessionId string, screenId string, userId string, commentText string) (*LineType, error) { - rtnLine := makeNewLineText(sessionId, screenId, userId, commentText) +func AddCommentLine(ctx context.Context, screenId string, userId string, commentText string) (*LineType, error) { + rtnLine := makeNewLineText(screenId, userId, commentText) err := InsertLine(ctx, rtnLine, nil) if err != nil { return nil, err @@ -977,8 +993,8 @@ func AddCommentLine(ctx context.Context, sessionId string, screenId string, user return rtnLine, nil } -func AddCmdLine(ctx context.Context, sessionId string, screenId string, userId string, cmd *CmdType, renderer string) (*LineType, error) { - rtnLine := makeNewLineCmd(sessionId, screenId, userId, cmd.CmdId, renderer) +func AddCmdLine(ctx context.Context, screenId string, userId string, cmd *CmdType, renderer string) (*LineType, error) { + rtnLine := makeNewLineCmd(screenId, userId, cmd.CmdId, renderer) err := InsertLine(ctx, rtnLine, cmd) if err != nil { return nil, err @@ -1085,6 +1101,7 @@ func createClientData(tx *TxWrap) error { UserPublicKeyBytes: pubBytes, ActiveSessionId: "", WinSize: ClientWinSizeType{}, + CmdStoreType: CmdStoreTypeScreen, } query := `INSERT INTO client ( clientid, userid, activesessionid, userpublickeybytes, userprivatekeybytes, winsize) VALUES (:clientid,:userid,:activesessionid,:userpublickeybytes,:userprivatekeybytes,:winsize)` @@ -1137,6 +1154,28 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) { return rtn, nil } +func GetCmdMigrationInfo(ctx context.Context) (*ClientMigrationData, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*ClientMigrationData, error) { + cdata := GetMappable[*ClientData](tx, `SELECT * FROM client`) + if cdata == nil { + return nil, fmt.Errorf("no client data found") + } + if cdata.CmdStoreType == "session" { + total := tx.GetInt(`SELECT count(*) FROM cmd`) + posInv := tx.GetInt(`SELECT count(*) FROM cmd_migrate`) + mdata := &ClientMigrationData{ + MigrationType: "cmdscreen", + MigrationPos: total - posInv, + MigrationTotal: total, + MigrationDone: false, + } + return mdata, nil + } + // no migration info + return nil, nil + }) +} + func SetClientOpts(ctx context.Context, clientOpts ClientOptsType) error { txErr := WithTx(ctx, func(tx *TxWrap) error { query := `UPDATE client SET clientopts = ?` @@ -1145,3 +1184,93 @@ func SetClientOpts(ctx context.Context, clientOpts ClientOptsType) error { }) return txErr } + +type cmdMigrationType struct { + SessionId string + ScreenId string + CmdId string +} + +func getSliceChunk[T any](slice []T, chunkSize int) ([]T, []T) { + if chunkSize >= len(slice) { + return slice, nil + } + return slice[0:chunkSize], slice[chunkSize:] +} + +func processChunk(ctx context.Context, mchunk []cmdMigrationType) error { + for _, mig := range mchunk { + newFile, err := scbase.PtyOutFile(mig.ScreenId, mig.CmdId) + if err != nil { + log.Printf("ptyoutfile error: %v\n", err) + continue + } + oldFile, err := scbase.PtyOutFile_Sessions(mig.SessionId, mig.CmdId) + if err != nil { + log.Printf("ptyoutfile_sessions error: %v\n", err) + continue + } + err = os.Rename(oldFile, newFile) + if err != nil { + log.Printf("error renaming %s => %s: %v\n", oldFile, newFile, err) + continue + } + } + txErr := WithTx(ctx, func(tx *TxWrap) error { + for _, mig := range mchunk { + query := `DELETE FROM cmd_migrate WHERE cmdid = ?` + tx.Exec(query, mig.CmdId) + } + return nil + }) + if txErr != nil { + return txErr + } + return nil +} + +func RunCmdScreenMigration() { + ctx := context.Background() + startTime := time.Now() + mdata, err := GetCmdMigrationInfo(ctx) + if err != nil { + log.Printf("[prompt] error trying to run cmd migration: %v\n", err) + return + } + if mdata == nil || mdata.MigrationType != "cmdscreen" { + return + } + var migrations []cmdMigrationType + txErr := WithTx(ctx, func(tx *TxWrap) error { + tx.Select(&migrations, `SELECT * FROM cmd_migrate`) + return nil + }) + if txErr != nil { + log.Printf("[prompt] error trying to get cmd migrations: %v\n", txErr) + return + } + log.Printf("[db] got %d cmd migrations\n", len(migrations)) + for len(migrations) > 0 { + var mchunk []cmdMigrationType + mchunk, migrations = getSliceChunk(migrations, 5) + err = processChunk(ctx, mchunk) + if err != nil { + log.Printf("[prompt] cmd migration failed on chunk: %v\n%#v\n", err, mchunk) + return + } + } + err = os.RemoveAll(scbase.GetSessionsDir()) + if err != nil { + log.Printf("[db] cannot remove old sessions dir %s: %v\n", scbase.GetSessionsDir(), err) + } + txErr = WithTx(ctx, func(tx *TxWrap) error { + query := `UPDATE client SET cmdstoretype = 'screen'` + tx.Exec(query) + return nil + }) + if txErr != nil { + log.Printf("[db] cannot change client cmdstoretype: %v\n", err) + } + log.Printf("[db] cmd screen migration done: %v\n", time.Since(startTime)) + return +} diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index 416b6920c..657c87509 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -17,7 +17,6 @@ type UpdatePacket interface { } type PtyDataUpdate struct { - SessionId string `json:"sessionid,omitempty"` ScreenId string `json:"screenid,omitempty"` CmdId string `json:"cmdid,omitempty"` RemoteId string `json:"remoteid,omitempty"` @@ -123,16 +122,16 @@ type CmdLineType struct { } type UpdateChannel struct { - SessionId string - ClientId string - Ch chan interface{} + ScreenId string + ClientId string + Ch chan interface{} } -func (uch UpdateChannel) Match(sessionId string) bool { - if sessionId == "" { +func (uch UpdateChannel) Match(screenId string) bool { + if screenId == "" { return true } - return sessionId == uch.SessionId + return screenId == uch.ScreenId } type UpdateBus struct { @@ -148,19 +147,19 @@ func MakeUpdateBus() *UpdateBus { } // always returns a new channel -func (bus *UpdateBus) RegisterChannel(clientId string, sessionId string) chan interface{} { +func (bus *UpdateBus) RegisterChannel(clientId string, screenId string) chan interface{} { bus.Lock.Lock() defer bus.Lock.Unlock() uch, found := bus.Channels[clientId] if found { close(uch.Ch) - uch.SessionId = sessionId + uch.ScreenId = screenId uch.Ch = make(chan interface{}, UpdateChSize) } else { uch = UpdateChannel{ - ClientId: clientId, - SessionId: sessionId, - Ch: make(chan interface{}, UpdateChSize), + ClientId: clientId, + ScreenId: screenId, + Ch: make(chan interface{}, UpdateChSize), } } bus.Channels[clientId] = uch @@ -177,11 +176,24 @@ func (bus *UpdateBus) UnregisterChannel(clientId string) { } } -func (bus *UpdateBus) SendUpdate(sessionId string, update interface{}) { +func (bus *UpdateBus) SendUpdate(update interface{}) { bus.Lock.Lock() defer bus.Lock.Unlock() for _, uch := range bus.Channels { - if uch.Match(sessionId) { + select { + case uch.Ch <- update: + + default: + log.Printf("[error] dropped update on updatebus uch clientid=%s\n", uch.ClientId) + } + } +} + +func (bus *UpdateBus) SendScreenUpdate(screenId string, update interface{}) { + bus.Lock.Lock() + defer bus.Lock.Unlock() + for _, uch := range bus.Channels { + if uch.Match(screenId) { select { case uch.Ch <- update: