checkpoint, backend changes for consolidating window and screen_window to screen

This commit is contained in:
sawka 2023-03-13 01:52:30 -07:00
parent ee2fd8e98f
commit e154aacaad
11 changed files with 519 additions and 509 deletions

View File

@ -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))

View File

@ -0,0 +1,3 @@
-- invalid, will throw an error, cannot migrate down past 9
SELECT x;

View File

@ -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;

View File

@ -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
}

99
pkg/mapqueue/mapqueue.go Normal file
View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
})

View File

@ -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) {

View File

@ -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"`

View File

@ -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 {