diff --git a/cmd/main-server.go b/cmd/main-server.go index 795d178b8..9092626c2 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -193,25 +193,20 @@ func HandleLogActiveState(w http.ResponseWriter, r *http.Request) { return } -// params: sessionid, windowid -func HandleGetWindow(w http.ResponseWriter, r *http.Request) { +// params: screenid +func HandleGetFullScreen(w http.ResponseWriter, r *http.Request) { qvals := r.URL.Query() - sessionId := qvals.Get("sessionid") - windowId := qvals.Get("windowid") - if _, err := uuid.Parse(sessionId); err != nil { - WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err)) + screenId := qvals.Get("screenid") + if _, err := uuid.Parse(screenId); err != nil { + WriteJsonError(w, fmt.Errorf("invalid screenid: %w", err)) return } - if _, err := uuid.Parse(windowId); err != nil { - WriteJsonError(w, fmt.Errorf("invalid windowid: %w", err)) - return - } - window, err := sstore.GetWindowById(r.Context(), sessionId, windowId) + screen, err := sstore.GetFullScreenById(r.Context(), screenId) if err != nil { WriteJsonError(w, err) return } - WriteJsonSuccess(w, window) + WriteJsonSuccess(w, screen) return } @@ -567,7 +562,7 @@ func main() { gr.HandleFunc("/api/ptyout", AuthKeyWrap(HandleGetPtyOut)) gr.HandleFunc("/api/remote-pty", AuthKeyWrap(HandleRemotePty)) gr.HandleFunc("/api/rtnstate", AuthKeyWrap(HandleRtnState)) - gr.HandleFunc("/api/get-window", AuthKeyWrap(HandleGetWindow)) + gr.HandleFunc("/api/get-full-screen", AuthKeyWrap(HandleGetFullScreen)) gr.HandleFunc("/api/run-command", AuthKeyWrap(HandleRunCommand)).Methods("POST") gr.HandleFunc("/api/get-client-data", AuthKeyWrap(HandleGetClientData)) gr.HandleFunc("/api/set-winsize", AuthKeyWrap(HandleSetWinSize)) diff --git a/db/migrations/000009_screenprimary.down.sql b/db/migrations/000009_screenprimary.down.sql new file mode 100644 index 000000000..1c2aaf962 --- /dev/null +++ b/db/migrations/000009_screenprimary.down.sql @@ -0,0 +1,3 @@ +-- invalid, will throw an error, cannot migrate down past 9 +SELECT x; + diff --git a/db/migrations/000009_screenprimary.up.sql b/db/migrations/000009_screenprimary.up.sql new file mode 100644 index 000000000..48efa4f84 --- /dev/null +++ b/db/migrations/000009_screenprimary.up.sql @@ -0,0 +1,56 @@ +CREATE TABLE new_screen ( + sessionid varchar(36) NOT NULL, + screenid varchar(36) NOT NULL, + windowid 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 (sessionid, screenid) +); + +INSERT INTO new_screen +SELECT + s.sessionid, + s.screenid, + w.windowid, + s.name, + s.screenidx, + json_patch(s.screenopts, w.winopts), + s.ownerid, + s.sharemode, + w.curremoteownerid, + w.curremoteid, + w.curremotename, + w.nextlinenum, + sw.selectedline, + sw.anchor, + sw.focustype, + s.archived, + s.archivedts +FROM + screen s, + screen_window sw, + window w +WHERE + s.screenid = sw.screenid + AND sw.windowid = w.windowid +; + +DROP TABLE screen; +DROP TABLE screen_window; +DROP TABLE window; + +ALTER TABLE new_screen RENAME TO screen; + + diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 68e4485cf..3e7470b5a 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -151,7 +151,6 @@ func init() { registerCmdFn("remote:installcancel", RemoteInstallCancelCommand) registerCmdFn("remote:reset", RemoteResetCommand) - registerCmdFn("sw:set", SwSetCommand) registerCmdFn("sw:resize", SwResizeCommand) // sw:resize @@ -458,7 +457,7 @@ func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/screen:archive cannot re-open screen: %v", err) } - screen, err := sstore.GetScreenById(ctx, ids.SessionId, screenId) + screen, err := sstore.GetScreenById(ctx, screenId) if err != nil { return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err) } @@ -512,17 +511,17 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return nil, err } var varsUpdated []string + var setNonAnchor bool // anchor does not receive an update + updateMap := make(map[string]interface{}) if pk.Kwargs["name"] != "" { newName := pk.Kwargs["name"] err = validateName(newName, "screen") if err != nil { return nil, err } - err = sstore.SetScreenName(ctx, ids.SessionId, ids.ScreenId, newName) - if err != nil { - return nil, fmt.Errorf("setting screen name: %v", err) - } + updateMap[sstore.ScreenField_Name] = newName varsUpdated = append(varsUpdated, "name") + setNonAnchor = true } if pk.Kwargs["tabcolor"] != "" { color := pk.Kwargs["tabcolor"] @@ -530,39 +529,67 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if err != nil { return nil, err } - screenObj, err := sstore.GetScreenById(ctx, ids.SessionId, ids.ScreenId) - if err != nil { - return nil, err - } - opts := screenObj.ScreenOpts - if opts == nil { - opts = &sstore.ScreenOptsType{} - } - opts.TabColor = color - err = sstore.SetScreenOpts(ctx, ids.SessionId, ids.ScreenId, opts) - if err != nil { - return nil, fmt.Errorf("setting screen opts: %v", err) - } + updateMap[sstore.ScreenField_TabColor] = color varsUpdated = append(varsUpdated, "tabcolor") + setNonAnchor = true } if pk.Kwargs["pos"] != "" { - varsUpdated = append(varsUpdated, "pos") + setNonAnchor = true + } + if pk.Kwargs["focus"] != "" { + focusVal := pk.Kwargs["focus"] + if focusVal != sstore.ScreenFocusInput && focusVal != sstore.ScreenFocusCmd && focusVal != sstore.ScreenFocusCmdFg { + return nil, fmt.Errorf("/screen:set invalid focus argument %q, must be %s", focusVal, formatStrs([]string{sstore.ScreenFocusInput, sstore.ScreenFocusCmd, sstore.ScreenFocusCmdFg}, "or", false)) + } + updateMap[sstore.ScreenField_Focus] = focusVal + setNonAnchor = true + } + if pk.Kwargs["line"] != "" { + screen, err := sstore.GetScreenById(ctx, ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("/screen:set cannot get screen: %v", err) + } + var selectedLineStr string + if screen.SelectedLine > 0 { + selectedLineStr = strconv.Itoa(int(screen.SelectedLine)) + } + ritem, err := resolveLine(ctx, screen.SessionId, screen.WindowId, pk.Kwargs["line"], selectedLineStr) + if err != nil { + return nil, fmt.Errorf("/screen:set error resolving line: %v", err) + } + if ritem == nil { + return nil, fmt.Errorf("/screen:set could not resolve line %q", pk.Kwargs["line"]) + } + setNonAnchor = true + updateMap[sstore.ScreenField_SelectedLine] = ritem.Num + } + if pk.Kwargs["anchor"] != "" { + m := swAnchorRe.FindStringSubmatch(pk.Kwargs["anchor"]) + if m == nil { + return nil, fmt.Errorf("/screen:set invalid anchor argument (must be [line] or [line]:[offset])") + } + anchorLine, _ := strconv.Atoi(m[1]) + updateMap[sstore.ScreenField_AnchorLine] = anchorLine + if m[2] != "" { + anchorOffset, _ := strconv.Atoi(m[2]) + updateMap[sstore.ScreenField_AnchorOffset] = anchorOffset + } else { + updateMap[sstore.ScreenField_AnchorOffset] = 0 + } } if len(varsUpdated) == 0 { - return nil, fmt.Errorf("/screen:set no updates, can set %s", formatStrs([]string{"name", "pos", "tabcolor"}, "or", false)) + return nil, fmt.Errorf("/screen:set no updates, can set %s", formatStrs([]string{"name", "pos", "tabcolor", "focus", "anchor", "line"}, "or", false)) } - screenObj, err := sstore.GetScreenById(ctx, ids.SessionId, ids.ScreenId) + screen, err := sstore.UpdateScreen(ctx, ids.ScreenId, updateMap) if err != nil { - return nil, err + return nil, fmt.Errorf("error updating screen: %v", err) } - bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId) - if err != nil { - return nil, fmt.Errorf("/screen:set cannot retrieve session: %v", err) + if !setNonAnchor { + return nil, nil } - bareSession.Screens = append(bareSession.Screens, screenObj) update := sstore.ModelUpdate{ - Sessions: []*sstore.SessionType{bareSession}, + Screens: []*sstore.ScreenType{screen}, Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)), TimeoutMs: 2000, @@ -593,69 +620,8 @@ func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor var swAnchorRe = regexp.MustCompile("^(\\d+)(?::(-?\\d+))?$") -func SwSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) - if err != nil { - return nil, fmt.Errorf("/sw:set cannot resolve current screen-window: %w", err) - } - var setNonST bool // scrolltop does not receive an update - updateMap := make(map[string]interface{}) - if pk.Kwargs["anchor"] != "" { - m := swAnchorRe.FindStringSubmatch(pk.Kwargs["anchor"]) - if m == nil { - return nil, fmt.Errorf("/sw:set invalid anchor argument (must be [line] or [line]:[offset])") - } - anchorLine, _ := strconv.Atoi(m[1]) - updateMap[sstore.SWField_AnchorLine] = anchorLine - if m[2] != "" { - anchorOffset, _ := strconv.Atoi(m[2]) - updateMap[sstore.SWField_AnchorOffset] = anchorOffset - } else { - updateMap[sstore.SWField_AnchorOffset] = 0 - } - } - if pk.Kwargs["focus"] != "" { - focusVal := pk.Kwargs["focus"] - if focusVal != sstore.SWFocusInput && focusVal != sstore.SWFocusCmd && focusVal != sstore.SWFocusCmdFg { - return nil, fmt.Errorf("/sw:set invalid focus argument %q, must be %s", focusVal, formatStrs([]string{sstore.SWFocusInput, sstore.SWFocusCmd, sstore.SWFocusCmdFg}, "or", false)) - } - updateMap[sstore.SWField_Focus] = focusVal - setNonST = true - } - if pk.Kwargs["line"] != "" { - sw, err := sstore.GetScreenWindowByIds(ctx, ids.SessionId, ids.ScreenId, ids.WindowId) - if err != nil { - return nil, fmt.Errorf("/sw:set cannot get screen-window: %v", err) - } - var selectedLineStr string - if sw.SelectedLine > 0 { - selectedLineStr = strconv.Itoa(sw.SelectedLine) - } - ritem, err := resolveLine(ctx, ids.SessionId, ids.WindowId, pk.Kwargs["line"], selectedLineStr) - if err != nil { - return nil, fmt.Errorf("/sw:set error resolving line: %v", err) - } - if ritem == nil { - return nil, fmt.Errorf("/sw:set could not resolve line %q", pk.Kwargs["line"]) - } - setNonST = true - updateMap[sstore.SWField_SelectedLine] = ritem.Num - } - if len(updateMap) == 0 { - return nil, fmt.Errorf("/sw:set no updates, can set %s", formatStrs([]string{"line", "scrolltop", "focus"}, "or", false)) - } - sw, err := sstore.UpdateScreenWindow(ctx, ids.SessionId, ids.ScreenId, ids.WindowId, updateMap) - if err != nil { - return nil, fmt.Errorf("/sw:set failed to update: %v", err) - } - if !setNonST { - return nil, nil - } - return sstore.ModelUpdate{ScreenWindows: []*sstore.ScreenWindowType{sw}}, nil -} - func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -669,7 +635,7 @@ func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -683,7 +649,7 @@ func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacke } func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -696,7 +662,7 @@ func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -946,7 +912,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -976,7 +942,7 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -1039,29 +1005,20 @@ func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, err } - screen, err := sstore.GetScreenById(ctx, ids.SessionId, ids.ScreenId) - if err != nil { - return nil, fmt.Errorf("error retrieving screen: %v", err) - } localRemote := remote.GetLocalRemote() if localRemote == nil { return nil, fmt.Errorf("error getting local remote (not found)") } rptr := sstore.RemotePtrType{RemoteId: localRemote.RemoteId} - var windows []*sstore.WindowType sessionUpdate := &sstore.SessionType{SessionId: ids.SessionId} - for _, sw := range screen.Windows { - ris, err := sstore.WindowReset(ctx, ids.SessionId, sw.WindowId) - if err != nil { - return nil, fmt.Errorf("error resetting screen window: %v", err) - } - sessionUpdate.Remotes = append(sessionUpdate.Remotes, ris...) - err = sstore.UpdateCurRemote(ctx, ids.SessionId, sw.WindowId, rptr) - if err != nil { - return nil, fmt.Errorf("cannot reset window remote back to local: %w", err) - } - winUpdate := &sstore.WindowType{SessionId: ids.SessionId, WindowId: sw.WindowId, CurRemote: rptr} - windows = append(windows, winUpdate) + ris, err := sstore.ScreenReset(ctx, ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("error resetting screen window: %v", err) + } + sessionUpdate.Remotes = append(sessionUpdate.Remotes, ris...) + err = sstore.UpdateCurRemote(ctx, ids.ScreenId, rptr) + if err != nil { + return nil, fmt.Errorf("cannot reset screen remote back to local: %w", err) } outputStr := "reset screen state (all remote state reset)" cmd, err := makeStaticCmd(ctx, "screen:reset", ids, pk.GetRawStr(), []byte(outputStr)) @@ -1075,13 +1032,12 @@ func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, err } update.Interactive = pk.Interactive - update.Windows = windows update.Sessions = []*sstore.SessionType{sessionUpdate} return update, nil } func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_Remote) if err != nil { return nil, err } @@ -1091,13 +1047,16 @@ func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } update := sstore.InfoMsgUpdate("remote [%s] archived", ids.Remote.DisplayName) localRemote := remote.GetLocalRemote() - if localRemote != nil { - update.Windows = []*sstore.WindowType{&sstore.WindowType{ - SessionId: ids.SessionId, - WindowId: ids.WindowId, - CurRemote: sstore.RemotePtrType{RemoteId: localRemote.GetRemoteId()}, - }} + rptr := sstore.RemotePtrType{RemoteId: localRemote.GetRemoteId()} + err = sstore.UpdateCurRemote(ctx, ids.ScreenId, rptr) + if err != nil { + return nil, fmt.Errorf("cannot switch remote back to local: %w", err) } + screen, err := sstore.GetScreenById(ctx, ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("cannot get updated screen: %w", err) + } + update.Screens = []*sstore.ScreenType{screen} return update, nil } @@ -1173,7 +1132,7 @@ func GetFullRemoteDisplayName(rptr *sstore.RemotePtrType, rstate *remote.RemoteR } func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) if err != nil { return nil, fmt.Errorf("/%s error: %w", GetCmdStr(pk), err) } @@ -1191,7 +1150,7 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up if rstate.Archived { return nil, fmt.Errorf("/%s error: remote %q cannot switch to archived remote", GetCmdStr(pk), newRemote) } - err = sstore.UpdateCurRemote(ctx, ids.SessionId, ids.WindowId, *rptr) + err = sstore.UpdateCurRemote(ctx, ids.ScreenId, *rptr) if err != nil { return nil, fmt.Errorf("/%s error: cannot update curremote: %w", GetCmdStr(pk), err) } @@ -1206,11 +1165,6 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Windows = []*sstore.WindowType{&sstore.WindowType{ - SessionId: ids.SessionId, - WindowId: ids.WindowId, - CurRemote: *rptr, - }} update.Interactive = pk.Interactive return update, nil } @@ -1252,27 +1206,27 @@ func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids re if err != nil { return nil, err } - sw, err := sstore.GetScreenWindowByIds(ctx, ids.SessionId, ids.ScreenId, ids.WindowId) + screen, err := sstore.GetScreenById(ctx, ids.ScreenId) if err != nil { // ignore error here, because the command has already run (nothing to do) - log.Printf("%s error getting screen-window: %v\n", metaCmd, err) + log.Printf("%s error getting screen: %v\n", metaCmd, err) } - if sw != nil { + if screen != nil { updateMap := make(map[string]interface{}) - updateMap[sstore.SWField_SelectedLine] = rtnLine.LineNum + updateMap[sstore.ScreenField_SelectedLine] = rtnLine.LineNum if shouldFocus { - updateMap[sstore.SWField_Focus] = sstore.SWFocusCmdFg + updateMap[sstore.ScreenField_Focus] = sstore.ScreenFocusCmdFg } - sw, err = sstore.UpdateScreenWindow(ctx, ids.SessionId, ids.ScreenId, ids.WindowId, updateMap) + screen, err = sstore.UpdateScreen(ctx, ids.ScreenId, updateMap) if err != nil { // ignore error again (nothing to do) - log.Printf("%s error updating screen-window selected line: %v\n", metaCmd, err) + log.Printf("%s error updating screen selected line: %v\n", metaCmd, err) } } update := &sstore.ModelUpdate{ - Line: rtnLine, - Cmd: cmd, - ScreenWindows: []*sstore.ScreenWindowType{sw}, + Line: rtnLine, + Cmd: cmd, + Screens: []*sstore.ScreenType{screen}, } updateHistoryContext(ctx, rtnLine, cmd) return update, nil @@ -1374,7 +1328,7 @@ func doCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix str if !packet.IsValidCompGenType(compType) { return nil, false, fmt.Errorf("/_compgen invalid type '%s'", compType) } - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_RemoteConnected) if err != nil { return nil, false, fmt.Errorf("/_compgen error: %w", err) } @@ -1447,7 +1401,7 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - ids, err := resolveUiIds(ctx, pk, R_Session|R_Window) + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) if err != nil { return nil, fmt.Errorf("/comment error: %w", err) } @@ -1461,14 +1415,14 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } updateHistoryContext(ctx, rtnLine, nil) updateMap := make(map[string]interface{}) - updateMap[sstore.SWField_SelectedLine] = rtnLine.LineNum - updateMap[sstore.SWField_Focus] = sstore.SWFocusInput - sw, err := sstore.UpdateScreenWindow(ctx, ids.SessionId, ids.ScreenId, ids.WindowId, updateMap) + updateMap[sstore.ScreenField_SelectedLine] = rtnLine.LineNum + updateMap[sstore.ScreenField_Focus] = sstore.ScreenFocusInput + screen, err := sstore.UpdateScreen(ctx, ids.ScreenId, updateMap) if err != nil { // ignore error again (nothing to do) log.Printf("/comment error updating screen-window selected line: %v\n", err) } - update := sstore.ModelUpdate{Line: rtnLine, ScreenWindows: []*sstore.ScreenWindowType{sw}} + update := sstore.ModelUpdate{Line: rtnLine, Screens: []*sstore.ScreenType{screen}} return update, nil } @@ -1809,22 +1763,22 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore return nil, err } if resolveBool(pk.Kwargs["purge"], false) { - update, err := sstore.PurgeWindowLines(ctx, ids.SessionId, ids.WindowId) + update, err := sstore.PurgeScreenLines(ctx, ids.ScreenId) if err != nil { - return nil, fmt.Errorf("clearing window: %v", err) + return nil, fmt.Errorf("clearing screen: %v", err) } update.Info = &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("window cleared (all lines purged)"), + InfoMsg: fmt.Sprintf("screen cleared (all lines purged)"), TimeoutMs: 2000, } return update, nil } else { - update, err := sstore.ArchiveWindowLines(ctx, ids.SessionId, ids.WindowId) + update, err := sstore.ArchiveScreenLines(ctx, ids.ScreenId) if err != nil { - return nil, fmt.Errorf("clearing window: %v", err) + return nil, fmt.Errorf("clearing screen: %v", err) } update.Info = &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("window cleared"), + InfoMsg: fmt.Sprintf("screen cleared"), TimeoutMs: 2000, } return update, nil @@ -2121,11 +2075,11 @@ func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if err != nil { return nil, fmt.Errorf("/line:view invalid screen arg: %v", err) } - screen, err := sstore.GetScreenById(ctx, sessionId, screenRItem.Id) + screen, err := sstore.GetScreenById(ctx, screenRItem.Id) if err != nil { return nil, fmt.Errorf("/line:view could not get screen: %v", err) } - lineRItem, err := resolveLine(ctx, sessionId, screen.ActiveWindowId, lineArg, "") + lineRItem, err := resolveLine(ctx, sessionId, screen.WindowId, lineArg, "") if err != nil { return nil, fmt.Errorf("/line:view invalid line arg: %v", err) } @@ -2134,14 +2088,14 @@ func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst return nil, err } updateMap := make(map[string]interface{}) - updateMap[sstore.SWField_SelectedLine] = lineRItem.Num - updateMap[sstore.SWField_AnchorLine] = lineRItem.Num - updateMap[sstore.SWField_AnchorOffset] = 0 - sw, err := sstore.UpdateScreenWindow(ctx, sessionId, screenRItem.Id, screen.ActiveWindowId, updateMap) + updateMap[sstore.ScreenField_SelectedLine] = lineRItem.Num + updateMap[sstore.ScreenField_AnchorLine] = lineRItem.Num + updateMap[sstore.ScreenField_AnchorOffset] = 0 + screen, err = sstore.UpdateScreen(ctx, screenRItem.Id, updateMap) if err != nil { return nil, err } - update.ScreenWindows = []*sstore.ScreenWindowType{sw} + update.Screens = []*sstore.ScreenType{screen} return update, nil } diff --git a/pkg/mapqueue/mapqueue.go b/pkg/mapqueue/mapqueue.go new file mode 100644 index 000000000..aa5d4d9ac --- /dev/null +++ b/pkg/mapqueue/mapqueue.go @@ -0,0 +1,99 @@ +package mapqueue + +import ( + "fmt" + "log" + "runtime/debug" + "sync" +) + +type MQEntry struct { + Lock *sync.Mutex + Running bool + Queue chan func() +} + +type MapQueue struct { + Lock *sync.Mutex + M map[string]*MQEntry + QueueSize int +} + +func MakeMapQueue(queueSize int) *MapQueue { + rtn := &MapQueue{ + Lock: &sync.Mutex{}, + M: make(map[string]*MQEntry), + QueueSize: queueSize, + } + return rtn +} + +func (mq *MapQueue) getEntry(id string) *MQEntry { + mq.Lock.Lock() + defer mq.Lock.Unlock() + entry := mq.M[id] + if entry == nil { + entry = &MQEntry{ + Lock: &sync.Mutex{}, + Running: false, + Queue: make(chan func(), mq.QueueSize), + } + mq.M[id] = entry + } + return entry +} + +func (entry *MQEntry) add(fn func()) error { + select { + case entry.Queue <- fn: + break + default: + return fmt.Errorf("input queue full") + } + entry.tryRun() + return nil +} + +func runFn(fn func()) { + defer func() { + r := recover() + if r == nil { + return + } + log.Printf("[error] panic in MQEntry runFn: %v\n", r) + debug.PrintStack() + return + }() + fn() +} + +func (entry *MQEntry) tryRun() { + entry.Lock.Lock() + defer entry.Lock.Unlock() + if entry.Running { + return + } + if len(entry.Queue) > 0 { + entry.Running = true + go entry.run() + } +} + +func (entry *MQEntry) run() { + for fn := range entry.Queue { + runFn(fn) + } + entry.Lock.Lock() + entry.Running = false + entry.Lock.Unlock() + entry.tryRun() +} + +func (mq *MapQueue) Enqueue(id string, fn func()) error { + entry := mq.getEntry(id) + err := entry.add(fn) + if err != nil { + return fmt.Errorf("cannot enqueue: %v", err) + } + return nil +} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index 45477f8f0..b0ce19bf0 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -1499,12 +1499,12 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { msh.WriteToPtyBuffer("*error updating cmddone: %v\n", err) return } - sws, err := sstore.UpdateSWsWithCmdFg(context.Background(), donePk.CK.GetSessionId(), donePk.CK.GetCmdId()) + screens, err := sstore.UpdateScreensWithCmdFg(context.Background(), donePk.CK.GetSessionId(), donePk.CK.GetCmdId()) if err != nil { msh.WriteToPtyBuffer("*error trying to update cmd-fg screen windows: %v\n", err) // fall-through (nothing to do) } - update.ScreenWindows = sws + update.Screens = screens rct := msh.GetRunningCmd(donePk.CK) var statePtr *sstore.ShellStatePtr if donePk.FinalState != nil && rct != nil { diff --git a/pkg/scws/scws.go b/pkg/scws/scws.go index b84fb4ba6..50bb7388b 100644 --- a/pkg/scws/scws.go +++ b/pkg/scws/scws.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/scripthaus-dev/mshell/pkg/packet" + "github.com/scripthaus-dev/sh2-server/pkg/mapqueue" "github.com/scripthaus-dev/sh2-server/pkg/remote" "github.com/scripthaus-dev/sh2-server/pkg/scpacket" "github.com/scripthaus-dev/sh2-server/pkg/sstore" @@ -17,6 +18,13 @@ import ( const WSStatePacketChSize = 20 const MaxInputDataSize = 1000 +const RemoteInputQueueSize = 100 + +var RemoteInputMapQueue *mapqueue.MapQueue + +func init() { + RemoteInputMapQueue = mapqueue.MakeMapQueue(RemoteInputQueueSize) +} type WSState struct { Lock *sync.Mutex @@ -227,13 +235,16 @@ func (ws *WSState) RunWSRead() { log.Printf("[error] invalid input packet, remoteid is not set\n") continue } - go func() { - // TODO enforce a strong ordering (channel with list) + err := RemoteInputMapQueue.Enqueue(feInputPk.Remote.RemoteId, func() { err = sendCmdInput(feInputPk) if err != nil { log.Printf("[error] sending command input: %v\n", err) } - }() + }) + if err != nil { + log.Printf("[error] could not queue sendCmdInput: %v\n", err) + continue + } continue } if pk.GetType() == scpacket.RemoteInputPacketStr { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 2135b7f24..e9dbb5680 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -405,9 +405,8 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { sessionMap[session.SessionId] = session session.Full = true } - var screens []*ScreenType query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts` - tx.Select(&screens, query) + screens := SelectMapsGen[*ScreenType](tx, query) screenMap := make(map[string][]*ScreenType) for _, screen := range screens { screenArr := screenMap[screen.SessionId] @@ -417,20 +416,6 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { for _, session := range rtn { session.Screens = screenMap[session.SessionId] } - var sws []*ScreenWindowType - query = `SELECT * FROM screen_window` - tx.Select(&sws, query) - screenIdMap := make(map[string]*ScreenType) - for _, screen := range screens { - screenIdMap[screen.SessionId+screen.ScreenId] = screen - } - for _, sw := range sws { - screen := screenIdMap[sw.SessionId+sw.ScreenId] - if screen == nil { - continue - } - screen.Windows = append(screen.Windows, sw) - } query = `SELECT * FROM remote_instance` riArr := SelectMapsGen[*RemoteInstance](tx, query) for _, ri := range riArr { @@ -449,22 +434,20 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { return &ModelUpdate{Sessions: rtn, ActiveSessionId: activeSessionId}, nil } -func GetWindowById(ctx context.Context, sessionId string, windowId string) (*WindowType, error) { - var rtnWindow *WindowType - err := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT * FROM window WHERE sessionid = ? AND windowid = ?` - m := tx.GetMap(query, sessionId, windowId) - if m == nil { - return nil +func GetFullScreenById(ctx context.Context, screenId string) (*ScreenType, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) { + query := `SELECT * FROM screen WHERE screenid = ?` + screen := GetMapGen[*ScreenType](tx, query, screenId) + if screen == nil { + return nil, nil } - rtnWindow = FromMap[*WindowType](m) query = `SELECT * FROM line WHERE sessionid = ? AND windowid = ? ORDER BY linenum` - tx.Select(&rtnWindow.Lines, query, sessionId, windowId) + tx.Select(&screen.Lines, query, screen.SessionId, screen.WindowId) query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND windowid = ?)` - rtnWindow.Cmds = SelectMapsGen[*CmdType](tx, query, sessionId, windowId) - return nil + screen.Cmds = SelectMapsGen[*CmdType](tx, query, screen.SessionId, screen.WindowId) + screen.Full = true + return screen, nil }) - return rtnWindow, err } // includes archived screens (does not include screen windows) @@ -649,11 +632,11 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, if !tx.Exists(query, sessionId) { return fmt.Errorf("cannot create screen, no session found (or session archived)") } - remoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, LocalRemoteAlias) - if remoteId == "" { + localRemoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, LocalRemoteAlias) + if localRemoteId == "" { return fmt.Errorf("cannot create screen, no local remote found") } - newWindowId := txCreateWindow(tx, sessionId, RemotePtrType{RemoteId: remoteId}) + newWindowId := scbase.GenPromptUUID() maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId) var screenName string if origScreenName == "" { @@ -663,11 +646,25 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, screenName = origScreenName } newScreenId = scbase.GenPromptUUID() - query = `INSERT INTO screen (sessionid, screenid, name, activewindowid, screenidx, screenopts, ownerid, sharemode, incognito, archived, archivedts) VALUES (?, ?, ?, ?, ?, ?, '', 'local', 0, 0, 0)` - tx.Exec(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 (?, ?, ?, ?, ?, ?, ?, ?)` - tx.Exec(query, sessionId, newScreenId, newWindowId, DefaultScreenWindowName, layout, 0, SWAnchorType{}, "input") + screen := &ScreenType{ + SessionId: sessionId, + ScreenId: newScreenId, + WindowId: newWindowId, + Name: screenName, + ScreenIdx: int64(maxScreenIdx) + 1, + ScreenOpts: ScreenOptsType{}, + OwnerId: "", + ShareMode: ShareModeLocal, + CurRemote: RemotePtrType{RemoteId: localRemoteId}, + NextLineNum: 1, + SelectedLine: 0, + Anchor: ScreenAnchorType{}, + FocusType: ScreenFocusInput, + Archived: false, + ArchivedTs: 0, + } + query = `INSERT INTO screen (sessionid, screenid, windowid, name, screenidx, screenopts, ownerid, sharemode, curremoteownerid, curremoteid, curremotename, nextlinenum, selectedline, anchor, focustype, archived, archivedts) VALUES (:sessionid,:screenid,:windowid,:name,:screenidx,:screenopts,:ownerid,:sharemode,:curremoteownerid,:curremoteid,:curremotename,:nextlinenum,:selectedline,:anchor,:focustype,:archived,:archivedts)` + tx.NamedExec(query, screen.ToMap()) if activate { query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?` tx.Exec(query, newScreenId, sessionId) @@ -677,7 +674,7 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, if txErr != nil { return nil, txErr } - newScreen, err := GetScreenById(ctx, sessionId, newScreenId) + newScreen, err := GetScreenById(ctx, newScreenId) if err != nil { return nil, err } @@ -689,42 +686,12 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, return ModelUpdate{Sessions: []*SessionType{bareSession}}, nil } -func GetScreenById(ctx context.Context, sessionId string, screenId string) (*ScreenType, error) { - var rtnScreen *ScreenType - txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT * FROM screen WHERE sessionid = ? AND screenid = ?` - var screen ScreenType - found := tx.Get(&screen, query, sessionId, screenId) - if !found { - return nil - } - rtnScreen = &screen - query = `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ?` - tx.Select(&screen.Windows, query, sessionId, screenId) - screen.Full = true - return nil +func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) { + query := `SELECT * FROM screen WHERE screenid = ?` + screen := GetMapGen[*ScreenType](tx, query, screenId) + return screen, nil }) - if txErr != nil { - return nil, txErr - } - return rtnScreen, nil -} - -func txCreateWindow(tx *TxWrap, sessionId string, curRemote RemotePtrType) string { - w := &WindowType{ - SessionId: sessionId, - WindowId: scbase.GenPromptUUID(), - CurRemote: curRemote, - NextLineNum: 1, - WinOpts: WindowOptsType{}, - ShareMode: ShareModeLocal, - ShareOpts: WindowShareOptsType{}, - } - wmap := w.ToMap() - query := `INSERT INTO window ( sessionid, windowid, curremoteownerid, curremoteid, curremotename, nextlinenum, winopts, ownerid, sharemode, shareopts) - VALUES (:sessionid,:windowid,:curremoteownerid,:curremoteid,:curremotename,:nextlinenum,:winopts,:ownerid,:sharemode,:shareopts)` - tx.NamedExec(query, wmap) - return w.WindowId } func FindLineIdByArg(ctx context.Context, sessionId string, windowId string, lineArg string) (string, error) { @@ -1071,7 +1038,7 @@ func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (Upda return nil, txErr } update := ModelUpdate{Sessions: []*SessionType{bareSession}} - newScreen, _ := GetScreenById(ctx, sessionId, screenId) + newScreen, _ := GetScreenById(ctx, screenId) if newScreen != nil { bareSession.Screens = append(bareSession.Screens, newScreen) } @@ -1264,17 +1231,16 @@ func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, r return ri, txErr } -func UpdateCurRemote(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) error { - txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?` - if !tx.Exists(query, sessionId, windowId) { - return fmt.Errorf("cannot update curremote: no window found") +func UpdateCurRemote(ctx context.Context, screenId string, remotePtr RemotePtrType) error { + return WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT screenid FROM screen WHERE screenid = ?` + if !tx.Exists(query, screenId) { + return fmt.Errorf("cannot update curremote: no screen found") } - query = `UPDATE window SET curremoteownerid = ?, curremoteid = ?, curremotename = ? WHERE sessionid = ? AND windowid = ?` - tx.Exec(query, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name, sessionId, windowId) + query = `UPDATE screen SET curremoteownerid = ?, curremoteid = ?, curremotename = ? WHERE screenid = ?` + tx.Exec(query, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name, screenId) return nil }) - return txErr } func reorderStrings(strs []string, toMove string, newIndex int) []string { @@ -1353,77 +1319,65 @@ func SetScreenName(ctx context.Context, sessionId string, screenId string, name return txErr } -func SetScreenOpts(ctx context.Context, sessionId string, screenId string, opts *ScreenOptsType) error { - if opts == nil { - return fmt.Errorf("invalid screen opts cannot be nil") - } +func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?` - if !tx.Exists(query, sessionId, screenId) { - return fmt.Errorf("screen does not exist") - } - query = `UPDATE screen SET screenopts = ? WHERE sessionid = ? AND screenid = ?` - tx.Exec(query, opts, sessionId, screenId) - return nil - }) - return txErr -} - -func ArchiveWindowLines(ctx context.Context, sessionId string, windowId string) (*ModelUpdate, error) { - txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?` - if !tx.Exists(query, sessionId, windowId) { - return fmt.Errorf("window does not exist") + query := `SELECT sessionid, windowid FROM screen WHERE screenid = ?` + var swkeys SWKeys + tx.Get(&swkeys, query, screenId) + if swkeys.SessionId == "" || swkeys.WindowId == "" { + return fmt.Errorf("screen windowid does not exist") } query = `UPDATE line SET archived = 1 WHERE sessionid = ? AND windowid = ?` - tx.Exec(query, sessionId, windowId) + tx.Exec(query, swkeys.SessionId, swkeys.WindowId) return nil }) if txErr != nil { return nil, txErr } - win, err := GetWindowById(ctx, sessionId, windowId) + screen, err := GetFullScreenById(ctx, screenId) if err != nil { return nil, err } - return &ModelUpdate{Windows: []*WindowType{win}}, nil + return &ModelUpdate{Screens: []*ScreenType{screen}}, nil } -func PurgeWindowLines(ctx context.Context, sessionId string, windowId string) (*ModelUpdate, error) { +func PurgeScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { var lineIds []string + var swkeys SWKeys txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?` - if !tx.Exists(query, sessionId, windowId) { - return fmt.Errorf("window does not exist") + query := `SELECT sessionid, windowid FROM screen WHERE screenid = ?` + tx.Get(&swkeys, query, screenId) + if swkeys.SessionId == "" || swkeys.WindowId == "" { + return fmt.Errorf("screen windowid does not exist") } query = `SELECT lineid FROM line WHERE sessionid = ? AND windowid = ?` - lineIds = tx.SelectStrings(query, sessionId, windowId) + lineIds = tx.SelectStrings(query, swkeys.SessionId, swkeys.WindowId) query = `DELETE FROM line WHERE sessionid = ? AND windowid = ?` - tx.Exec(query, sessionId, windowId) + tx.Exec(query, swkeys.SessionId, swkeys.WindowId) query = `DELETE FROM history WHERE sessionid = ? AND windowid = ?` - tx.Exec(query, sessionId, windowId) - query = `UPDATE window SET nextlinenum = 1 WHERE sessionid = ? AND windowid = ?` - tx.Exec(query, sessionId, windowId) + tx.Exec(query, swkeys.SessionId, swkeys.WindowId) + query = `UPDATE screen SET nextlinenum = 1 WHERE screenid = ?` + tx.Exec(query, screenId) return nil }) if txErr != nil { return nil, txErr } - go cleanSessionCmds(context.Background(), sessionId) - win, err := GetWindowById(ctx, sessionId, windowId) + go cleanSessionCmds(context.Background(), swkeys.SessionId) + screen, err := GetFullScreenById(ctx, screenId) if err != nil { return nil, err } for _, lineId := range lineIds { line := &LineType{ - SessionId: sessionId, - WindowId: windowId, + SessionId: swkeys.SessionId, + WindowId: swkeys.WindowId, LineId: lineId, Remove: true, } - win.Lines = append(win.Lines, line) + screen.Lines = append(screen.Lines, line) } - return &ModelUpdate{Windows: []*WindowType{win}}, nil + return &ModelUpdate{Screens: []*ScreenType{screen}}, nil } func GetRunningWindowCmds(ctx context.Context, sessionId string, windowId string) ([]*CmdType, error) { @@ -1449,24 +1403,25 @@ func UpdateCmdTermOpts(ctx context.Context, sessionId string, cmdId string, term } // returns riids of deleted RIs -func WindowReset(ctx context.Context, sessionId string, windowId string) ([]*RemoteInstance, error) { - var delRis []*RemoteInstance - txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?` - if !tx.Exists(query, sessionId, windowId) { - return fmt.Errorf("window does not exist") +func ScreenReset(ctx context.Context, screenId string) ([]*RemoteInstance, error) { + return WithTxRtn(ctx, func(tx *TxWrap) ([]*RemoteInstance, error) { + var swkeys SWKeys + query := `SELECT sessionid, windowid FROM screen WHERE screenid = ?` + tx.Get(&swkeys, query, screenId) + if swkeys.SessionId == "" || swkeys.WindowId == "" { + return nil, fmt.Errorf("screen does not exist") } query = `SELECT riid FROM remote_instance WHERE sessionid = ? AND windowid = ?` - riids := tx.SelectStrings(query, sessionId, windowId) + riids := tx.SelectStrings(query, swkeys.SessionId, swkeys.WindowId) + var delRis []*RemoteInstance for _, riid := range riids { - ri := &RemoteInstance{SessionId: sessionId, WindowId: windowId, RIId: riid, Remove: true} + ri := &RemoteInstance{SessionId: swkeys.SessionId, WindowId: swkeys.WindowId, RIId: riid, Remove: true} delRis = append(delRis, ri) } query = `DELETE FROM remote_instance WHERE sessionid = ? AND windowid = ?` - tx.Exec(query, sessionId, windowId) - return nil + tx.Exec(query, swkeys.SessionId, swkeys.WindowId) + return delRis, nil }) - return delRis, txErr } func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) { @@ -1480,10 +1435,6 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) tx.Exec(query, sessionId) query = `DELETE FROM screen WHERE sessionid = ?` tx.Exec(query, sessionId) - query = `DELETE FROM screen_window WHERE sessionid = ?` - tx.Exec(query, sessionId) - query = `DELETE FROM window WHERE sessionid = ?` - tx.Exec(query, sessionId) query = `DELETE FROM history WHERE sessionid = ?` tx.Exec(query, sessionId) query = `DELETE FROM line WHERE sessionid = ?` @@ -1693,64 +1644,55 @@ func UpdateRemote(ctx context.Context, remoteId string, editMap map[string]inter } const ( - SWField_AnchorLine = "anchorline" // int - SWField_AnchorOffset = "anchoroffset" // int - SWField_SelectedLine = "selectedline" // int - SWField_Focus = "focustype" // string + ScreenField_AnchorLine = "anchorline" // int + ScreenField_AnchorOffset = "anchoroffset" // int + ScreenField_SelectedLine = "selectedline" // int + ScreenField_Focus = "focustype" // string + ScreenField_TabColor = "tabcolor" // string + ScreenField_PTerm = "pterm" // string + ScreenField_Name = "name" // string ) -func UpdateScreenWindow(ctx context.Context, sessionId string, screenId string, windowId string, editMap map[string]interface{}) (*ScreenWindowType, error) { - var rtn *ScreenWindowType +func UpdateScreen(ctx context.Context, screenId string, editMap map[string]interface{}) (*ScreenType, error) { txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT sessionid FROM screen_window WHERE sessionid = ? AND screenid = ? AND windowid = ?` - if !tx.Exists(query, sessionId, screenId, windowId) { - return fmt.Errorf("screen-window not found") + query := `SELECT screenid FROM screen WHERE screenid = ?` + if !tx.Exists(query, screenId) { + return fmt.Errorf("screen not found") } - if anchorLine, found := editMap[SWField_AnchorLine]; found { - query = `UPDATE screen_window SET anchor = json_set(anchor, '$.anchorline', ?) WHERE sessionid = ? AND screenid = ? AND windowid = ?` - tx.Exec(query, anchorLine, sessionId, screenId, windowId) + if anchorLine, found := editMap[ScreenField_AnchorLine]; found { + query = `UPDATE screen SET anchor = json_set(anchor, '$.anchorline', ?) WHERE screenid = ?` + tx.Exec(query, anchorLine, screenId) } - if anchorOffset, found := editMap[SWField_AnchorOffset]; found { - query = `UPDATE screen_window SET anchor = json_set(anchor, '$.anchoroffset', ?) WHERE sessionid = ? AND screenid = ? AND windowid = ?` - tx.Exec(query, anchorOffset, sessionId, screenId, windowId) + if anchorOffset, found := editMap[ScreenField_AnchorOffset]; found { + query = `UPDATE screen SET anchor = json_set(anchor, '$.anchoroffset', ?) WHERE screenid = ?` + tx.Exec(query, anchorOffset, screenId) } - if sline, found := editMap[SWField_SelectedLine]; found { - query = `UPDATE screen_window SET selectedline = ? WHERE sessionid = ? AND screenid = ? AND windowid = ?` - tx.Exec(query, sline, sessionId, screenId, windowId) + if sline, found := editMap[ScreenField_SelectedLine]; found { + query = `UPDATE screen SET selectedline = ? WHERE screenid = ?` + tx.Exec(query, sline, screenId) } - if focusType, found := editMap[SWField_Focus]; found { - query = `UPDATE screen_window SET focustype = ? WHERE sessionid = ? AND screenid = ? AND windowid = ?` - tx.Exec(query, focusType, sessionId, screenId, windowId) + if focusType, found := editMap[ScreenField_Focus]; found { + query = `UPDATE screen SET focustype = ? WHERE screenid = ?` + tx.Exec(query, focusType, screenId) } - var sw ScreenWindowType - query = `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ? AND windowid = ?` - found := tx.Get(&sw, query, sessionId, screenId, windowId) - if found { - rtn = &sw + if tabColor, found := editMap[ScreenField_TabColor]; found { + query = `UPDATE screen SET screenopts = json_set(screenopts, '$.tabcolor', ?) WHERE screenid = ?` + tx.Exec(query, tabColor, screenId) + } + if pterm, found := editMap[ScreenField_PTerm]; found { + query = `UPDATE screen SET screenopts = json_set(screenopts, '$.pterm', ?) WHERE screenid = ?` + tx.Exec(query, pterm, screenId) + } + if name, found := editMap[ScreenField_Name]; found { + query = `UPDATE screen SET name = ? WHERE screenid = ?` + tx.Exec(query, name, screenId) } return nil }) if txErr != nil { return nil, txErr } - return rtn, nil -} - -func GetScreenWindowByIds(ctx context.Context, sessionId string, screenId string, windowId string) (*ScreenWindowType, error) { - var rtn *ScreenWindowType - txErr := WithTx(ctx, func(tx *TxWrap) error { - var sw ScreenWindowType - query := `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ? AND windowid = ?` - found := tx.Get(&sw, query, sessionId, screenId, windowId) - if found { - rtn = &sw - } - return nil - }) - if txErr != nil { - return nil, txErr - } - return rtn, nil + return GetScreenById(ctx, screenId) } func GetLineResolveItems(ctx context.Context, sessionId string, windowId string) ([]ResolveItem, error) { @@ -1766,33 +1708,32 @@ func GetLineResolveItems(ctx context.Context, sessionId string, windowId string) return rtn, nil } -func UpdateSWsWithCmdFg(ctx context.Context, sessionId string, cmdId string) ([]*ScreenWindowType, error) { - var rtn []*ScreenWindowType +func UpdateScreensWithCmdFg(ctx context.Context, sessionId string, cmdId string) ([]*ScreenType, error) { + var rtn []*ScreenType txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT sessionid, screenid, windowid - FROM screen_window sw - WHERE - sessionid = ? - AND focustype = 'cmd-fg' - AND selectedline IN (SELECT linenum - FROM line l - WHERE l.sessionid = sw.sessionid - AND l.windowid = sw.windowid - AND l.cmdid = ? - )` - var swKeys []SWKey - tx.Select(&swKeys, query, sessionId, cmdId) - if len(swKeys) == 0 { + 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.windowid = s.windowid + AND l.cmdid = ? + )` + screenIds := tx.SelectStrings(query, sessionId, cmdId) + if len(screenIds) == 0 { return nil } - for _, key := range swKeys { + for _, screenId := range screenIds { editMap := make(map[string]interface{}) - editMap[SWField_Focus] = SWFocusInput - sw, err := UpdateScreenWindow(tx.Context(), key.SessionId, key.ScreenId, key.WindowId, editMap) + editMap[ScreenField_Focus] = ScreenFocusInput + screen, err := UpdateScreen(tx.Context(), screenId, editMap) if err != nil { return err } - rtn = append(rtn, sw) + rtn = append(rtn, screen) } return nil }) diff --git a/pkg/sstore/migrate.go b/pkg/sstore/migrate.go index 1326b90f9..c49f46bf8 100644 --- a/pkg/sstore/migrate.go +++ b/pkg/sstore/migrate.go @@ -17,7 +17,7 @@ import ( "github.com/golang-migrate/migrate/v4" ) -const MaxMigration = 8 +const MaxMigration = 9 const MigratePrimaryScreenVersion = 9 func MakeMigrate() (*migrate.Migrate, error) { diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 9593b2351..344bf0258 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -73,9 +73,9 @@ const ( ) const ( - SWFocusInput = "input" - SWFocusCmd = "cmd" - SWFocusCmdFg = "cmd-fg" + ScreenFocusInput = "input" + ScreenFocusCmd = "cmd" + ScreenFocusCmdFg = "cmd-fg" ) const MaxTzNameLen = 50 @@ -276,29 +276,6 @@ type SessionStatsType struct { DiskStats SessionDiskSizeType `json:"diskstats"` } -type WindowOptsType struct { - PTerm string `json:"pterm,omitempty"` -} - -func (opts *WindowOptsType) Scan(val interface{}) error { - return quickScanJson(opts, val) -} - -func (opts WindowOptsType) Value() (driver.Value, error) { - return quickValueJson(opts) -} - -type WindowShareOptsType struct { -} - -func (opts *WindowShareOptsType) Scan(val interface{}) error { - return quickScanJson(opts, val) -} - -func (opts WindowShareOptsType) Value() (driver.Value, error) { - return quickValueJson(opts) -} - var RemoteNameRe = regexp.MustCompile("^\\*?[a-zA-Z0-9_-]+$") type RemotePtrType struct { @@ -361,51 +338,6 @@ func (r RemotePtrType) MakeFullRemoteRef() string { return fmt.Sprintf("@%s:%s:%s", r.OwnerId, r.RemoteId, r.Name) } -type WindowType struct { - SessionId string `json:"sessionid"` - WindowId string `json:"windowid"` - CurRemote RemotePtrType `json:"curremote"` - WinOpts WindowOptsType `json:"winopts"` - OwnerId string `json:"ownerid"` - NextLineNum int64 `json:"nextlinenum"` - ShareMode string `json:"sharemode"` - ShareOpts WindowShareOptsType `json:"shareopts"` - Lines []*LineType `json:"lines"` - Cmds []*CmdType `json:"cmds"` - - // only for updates - Remove bool `json:"remove,omitempty"` -} - -func (w *WindowType) ToMap() map[string]interface{} { - rtn := make(map[string]interface{}) - rtn["sessionid"] = w.SessionId - rtn["windowid"] = w.WindowId - rtn["curremoteownerid"] = w.CurRemote.OwnerId - rtn["curremoteid"] = w.CurRemote.RemoteId - rtn["curremotename"] = w.CurRemote.Name - rtn["nextlinenum"] = w.NextLineNum - rtn["winopts"] = quickJson(w.WinOpts) - rtn["ownerid"] = w.OwnerId - rtn["sharemode"] = w.ShareMode - rtn["shareopts"] = quickJson(w.ShareOpts) - return rtn -} - -func (w *WindowType) FromMap(m map[string]interface{}) bool { - quickSetStr(&w.SessionId, m, "sessionid") - quickSetStr(&w.WindowId, m, "windowid") - quickSetStr(&w.CurRemote.OwnerId, m, "curremoteownerid") - quickSetStr(&w.CurRemote.RemoteId, m, "curremoteid") - quickSetStr(&w.CurRemote.Name, m, "curremotename") - quickSetInt64(&w.NextLineNum, m, "nextlinenum") - quickSetJson(&w.WinOpts, m, "winopts") - quickSetStr(&w.OwnerId, m, "ownerid") - quickSetStr(&w.ShareMode, m, "sharemode") - quickSetJson(&w.ShareOpts, m, "shareopts") - return true -} - func (h *HistoryItemType) ToMap() map[string]interface{} { rtn := make(map[string]interface{}) rtn["historyid"] = h.HistoryId @@ -448,35 +380,83 @@ func (h *HistoryItemType) FromMap(m map[string]interface{}) bool { type ScreenOptsType struct { TabColor string `json:"tabcolor,omitempty"` + PTerm string `json:"pterm,omitempty"` } -func (opts *ScreenOptsType) Scan(val interface{}) error { - return quickScanJson(opts, val) -} - -func (opts ScreenOptsType) Value() (driver.Value, error) { - return quickValueJson(opts) +type SWKeys struct { + SessionId string + WindowId string } type ScreenType struct { - SessionId string `json:"sessionid"` - ScreenId string `json:"screenid"` - ScreenIdx int64 `json:"screenidx"` - Name string `json:"name"` - ActiveWindowId string `json:"activewindowid"` - ScreenOpts *ScreenOptsType `json:"screenopts"` - OwnerId string `json:"ownerid"` - ShareMode string `json:"sharemode"` - Incognito bool `json:"incognito,omitempty"` - Archived bool `json:"archived,omitempty"` - ArchivedTs int64 `json:"archivedts,omitempty"` - Windows []*ScreenWindowType `json:"windows"` + SessionId string `json:"sessionid"` + ScreenId string `json:"screenid"` + WindowId string `json:"windowid"` + Name string `json:"name"` + ScreenIdx int64 `json:"screenidx"` + ScreenOpts ScreenOptsType `json:"screenopts"` + OwnerId string `json:"ownerid"` + ShareMode string `json:"sharemode"` + CurRemote RemotePtrType `json:"curremote"` + NextLineNum int64 `json:"nextlinenum"` + SelectedLine int64 `json:"selectedline"` + Anchor ScreenAnchorType `json:"anchor"` + FocusType string `json:"focustype"` + Archived bool `json:"archived,omitempty"` + ArchivedTs int64 `json:"archivedts,omitempty"` + + // only for "full" + Lines []*LineType `json:"lines"` + Cmds []*CmdType `json:"cmds"` // only for updates Remove bool `json:"remove,omitempty"` Full bool `json:"full,omitempty"` } +func (s *ScreenType) ToMap() map[string]interface{} { + rtn := make(map[string]interface{}) + rtn["sessionid"] = s.SessionId + rtn["screenid"] = s.ScreenId + rtn["windowid"] = s.WindowId + rtn["name"] = s.Name + rtn["screenidx"] = s.ScreenIdx + rtn["screenopts"] = quickJson(s.ScreenOpts) + rtn["ownerid"] = s.OwnerId + rtn["sharemode"] = s.ShareMode + rtn["curremoteownerid"] = s.CurRemote.OwnerId + rtn["curremoteid"] = s.CurRemote.RemoteId + rtn["curremotename"] = s.CurRemote.Name + rtn["nextlinenum"] = s.NextLineNum + rtn["selectedline"] = s.SelectedLine + rtn["anchor"] = quickJson(s.Anchor) + rtn["focustype"] = s.FocusType + rtn["archived"] = s.Archived + rtn["archivedts"] = s.ArchivedTs + return rtn +} + +func (s *ScreenType) FromMap(m map[string]interface{}) bool { + quickSetStr(&s.SessionId, m, "sessionid") + quickSetStr(&s.ScreenId, m, "screenid") + quickSetStr(&s.WindowId, m, "windowid") + quickSetStr(&s.Name, m, "name") + quickSetInt64(&s.ScreenIdx, m, "screenidx") + quickSetJson(&s.ScreenOpts, m, "screenopts") + quickSetStr(&s.OwnerId, m, "ownerid") + quickSetStr(&s.ShareMode, m, "sharemode") + quickSetStr(&s.CurRemote.OwnerId, m, "curremoteownerid") + quickSetStr(&s.CurRemote.RemoteId, m, "curremoteid") + quickSetStr(&s.CurRemote.Name, m, "curremotename") + quickSetInt64(&s.NextLineNum, m, "nextlinenum") + quickSetInt64(&s.SelectedLine, m, "selectedline") + quickSetJson(&s.Anchor, m, "anchor") + quickSetStr(&s.FocusType, m, "focustype") + quickSetBool(&s.Archived, m, "archived") + quickSetInt64(&s.ArchivedTs, m, "archivedts") + return true +} + const ( LayoutFull = "full" ) @@ -502,39 +482,11 @@ func (l LayoutType) Value() (driver.Value, error) { return quickValueJson(l) } -type SWAnchorType struct { +type ScreenAnchorType struct { AnchorLine int `json:"anchorline,omitempty"` AnchorOffset int `json:"anchoroffset,omitempty"` } -func (a *SWAnchorType) Scan(val interface{}) error { - return quickScanJson(a, val) -} - -func (a SWAnchorType) Value() (driver.Value, error) { - return quickValueJson(a) -} - -type SWKey struct { - SessionId string - ScreenId string - WindowId string -} - -type ScreenWindowType struct { - SessionId string `json:"sessionid"` - ScreenId string `json:"screenid"` - WindowId string `json:"windowid"` - Name string `json:"name"` - Layout LayoutType `json:"layout"` - SelectedLine int `json:"selectedline"` - Anchor SWAnchorType `json:"anchor"` - FocusType string `json:"focustype"` - - // only for updates - Remove bool `json:"remove,omitempty"` -} - type HistoryItemType struct { HistoryId string `json:"historyid"` Ts int64 `json:"ts"` diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index 36608d041..fa7e69741 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -30,24 +30,23 @@ func (PtyDataUpdate) UpdateType() string { } type ModelUpdate struct { - Sessions []*SessionType `json:"sessions,omitempty"` - ActiveSessionId string `json:"activesessionid,omitempty"` - Windows []*WindowType `json:"windows,omitempty"` - ScreenWindows []*ScreenWindowType `json:"screenwindows,omitempty"` - Line *LineType `json:"line,omitempty"` - Lines []*LineType `json:"lines,omitempty"` - Cmd *CmdType `json:"cmd,omitempty"` - CmdLine *CmdLineType `json:"cmdline,omitempty"` - Info *InfoMsgType `json:"info,omitempty"` - ClearInfo bool `json:"clearinfo,omitempty"` - Remotes []interface{} `json:"remotes,omitempty"` // []*remote.RemoteState - History *HistoryInfoType `json:"history,omitempty"` - Interactive bool `json:"interactive"` - Connect bool `json:"connect,omitempty"` - MainView string `json:"mainview,omitempty"` - Bookmarks []*BookmarkType `json:"bookmarks,omitempty"` - HistoryViewData *HistoryViewData `json:"historyviewdata,omitempty"` - ClientData *ClientData `json:"clientdata,omitempty"` + Sessions []*SessionType `json:"sessions,omitempty"` + ActiveSessionId string `json:"activesessionid,omitempty"` + Screens []*ScreenType `json:"screens,omitempty"` + Line *LineType `json:"line,omitempty"` + Lines []*LineType `json:"lines,omitempty"` + Cmd *CmdType `json:"cmd,omitempty"` + CmdLine *CmdLineType `json:"cmdline,omitempty"` + Info *InfoMsgType `json:"info,omitempty"` + ClearInfo bool `json:"clearinfo,omitempty"` + Remotes []interface{} `json:"remotes,omitempty"` // []*remote.RemoteState + History *HistoryInfoType `json:"history,omitempty"` + Interactive bool `json:"interactive"` + Connect bool `json:"connect,omitempty"` + MainView string `json:"mainview,omitempty"` + Bookmarks []*BookmarkType `json:"bookmarks,omitempty"` + HistoryViewData *HistoryViewData `json:"historyviewdata,omitempty"` + ClientData *ClientData `json:"clientdata,omitempty"` } func (ModelUpdate) UpdateType() string {