From 275e177218014ed01a03f5dc9e22cd24b71b9398 Mon Sep 17 00:00:00 2001 From: sawka Date: Thu, 14 Jul 2022 18:39:40 -0700 Subject: [PATCH] checkpoint --- cmd/main-server.go | 118 ++++++++++++++++++++++++++++++++------- pkg/scpacket/scpacket.go | 1 + pkg/sstore/dbops.go | 88 +++++++++++++++++++++-------- pkg/sstore/sstore.go | 16 +++++- pkg/sstore/updatebus.go | 8 +-- 5 files changed, 179 insertions(+), 52 deletions(-) diff --git a/cmd/main-server.go b/cmd/main-server.go index 0c7e61e94..3a9a8dfca 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -9,6 +9,7 @@ import ( "io/fs" "net/http" "os" + "strconv" "strings" "sync" "time" @@ -190,6 +191,30 @@ func HandleCreateWindow(w http.ResponseWriter, r *http.Request) { return } +// params: sessionid, name, activate +func HandleCreateScreen(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Vary", "Origin") + w.Header().Set("Cache-Control", "no-cache") + qvals := r.URL.Query() + sessionId := qvals.Get("sessionid") + if _, err := uuid.Parse(sessionId); err != nil { + WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err)) + return + } + name := qvals.Get("name") + activateStr := qvals.Get("activate") + activate := activateStr != "" + screenId, err := sstore.InsertScreen(r.Context(), sessionId, name, activate) + if err != nil { + WriteJsonError(w, fmt.Errorf("inserting screen: %w", err)) + return + } + WriteJsonSuccess(w, screenId) + return +} + // params: [none] func HandleGetRemotes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) @@ -339,20 +364,45 @@ func HandleRunCommand(w http.ResponseWriter, r *http.Request) { } func ProcessFeCommandPacket(ctx context.Context, pk *scpacket.FeCommandPacketType) (*runCommandResponse, error) { + // parse metacmd commandStr := strings.TrimSpace(pk.CmdStr) if commandStr == "" { return nil, fmt.Errorf("invalid emtpty command") } - if strings.HasPrefix(commandStr, "/comment ") { - text := strings.TrimSpace(commandStr[9:]) + metaCmd := "" + metaSubCmd := "" + if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") { + metaCmd = "cd" + commandStr = strings.TrimSpace(commandStr[2:]) + } else if commandStr[0] == '/' { + spaceIdx := strings.Index(commandStr, " ") + if spaceIdx == -1 { + metaCmd = commandStr[1:] + commandStr = "" + } else { + metaCmd = commandStr[1:spaceIdx] + commandStr = strings.TrimSpace(commandStr[spaceIdx+1:]) + } + colonIdx := strings.Index(metaCmd, ":") + if colonIdx != -1 { + metaCmd = metaCmd[0:colonIdx] + metaSubCmd = metaCmd[colonIdx+1:] + } + if metaCmd == "" { + return nil, fmt.Errorf("invalid command, got bare '/', with no command") + } + } + + // execute metacmd + if metaCmd == "comment" { + text := commandStr rtnLine, err := sstore.AddCommentLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, text) if err != nil { return nil, err } return &runCommandResponse{Line: rtnLine}, nil - } - if strings.HasPrefix(commandStr, "cd ") { - newDir := strings.TrimSpace(commandStr[3:]) + } else if metaCmd == "cd" { + newDir := commandStr cdPacket := packet.MakeCdPacket() cdPacket.ReqId = uuid.New().String() cdPacket.Dir = newDir @@ -366,17 +416,53 @@ func ProcessFeCommandPacket(ctx context.Context, pk *scpacket.FeCommandPacketTyp } fmt.Printf("GOT cd RESP: %v\n", resp) return nil, nil + } else if metaCmd == "s" || metaCmd == "screen" { + err := RunScreenCmd(ctx, pk.SessionId, pk.ScreenId, metaSubCmd, commandStr) + if err != nil { + return nil, err + } + return nil, nil + } else if metaCmd == "" { + cmdId := uuid.New().String() + cmd, err := remote.RunCommand(ctx, pk, cmdId) + if err != nil { + return nil, err + } + rtnLine, err := sstore.AddCmdLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, cmd) + if err != nil { + return nil, err + } + return &runCommandResponse{Line: rtnLine, Cmd: cmd}, nil + } else { + fmt.Printf("INVALID metacmd '%s'\n", metaCmd) + return nil, fmt.Errorf("Invalid command type /%s", metaCmd) } - cmdId := uuid.New().String() - cmd, err := remote.RunCommand(ctx, pk, cmdId) +} + +func RunScreenCmd(ctx context.Context, sessionId string, screenId string, subCmd string, commandStr string) error { + if subCmd != "" { + return fmt.Errorf("invalid /screen subcommand '%s'", subCmd) + } + if commandStr == "" { + return fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified") + } + screens, err := sstore.GetSessionScreens(ctx, sessionId) if err != nil { - return nil, err + return fmt.Errorf("could not retreive screens for session=%s", sessionId) } - rtnLine, err := sstore.AddCmdLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, cmd) - if err != nil { - return nil, err + screenNum, err := strconv.Atoi(commandStr) + if err == nil { + if screenNum < 1 || screenNum > len(screens) { + return fmt.Errorf("could not switch to screen #%d (out of range), valid screens 1-%d", screenNum, len(screens)) + } + return sstore.SwitchScreenById(ctx, sessionId, screens[screenNum-1].ScreenId) } - return &runCommandResponse{Line: rtnLine, Cmd: cmd}, nil + for _, screen := range screens { + if screen.Name == commandStr { + return sstore.SwitchScreenById(ctx, sessionId, screen.ScreenId) + } + } + return fmt.Errorf("could not switch to screen '%s' (name not found)", commandStr) } // /api/start-session @@ -384,12 +470,6 @@ func ProcessFeCommandPacket(ctx context.Context, pk *scpacket.FeCommandPacketTyp // * userid // * sessionid // -// /api/get-session -// params: -// * name -// returns: -// * session -// // /api/ptyout (pos=[position]) - returns contents of ptyout file // params: // * sessionid @@ -525,10 +605,10 @@ func main() { gr.HandleFunc("/api/ptyout", HandleGetPtyOut) gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions) gr.HandleFunc("/api/create-session", HandleCreateSession) - gr.HandleFunc("/api/get-session", HandleGetSession) gr.HandleFunc("/api/get-window", HandleGetWindow) gr.HandleFunc("/api/get-remotes", HandleGetRemotes) gr.HandleFunc("/api/create-window", HandleCreateWindow) + gr.HandleFunc("/api/create-screen", HandleCreateScreen) gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS") server := &http.Server{ Addr: MainServerAddr, diff --git a/pkg/scpacket/scpacket.go b/pkg/scpacket/scpacket.go index 8718e5bc9..0c0b34599 100644 --- a/pkg/scpacket/scpacket.go +++ b/pkg/scpacket/scpacket.go @@ -18,6 +18,7 @@ type RemoteState struct { type FeCommandPacketType struct { Type string `json:"type"` SessionId string `json:"sessionid"` + ScreenId string `json:"screenid"` WindowId string `json:"windowid"` UserId string `json:"userid"` CmdStr string `json:"cmdstr"` diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 5240c3a1c..a9e173f91 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -90,18 +90,6 @@ func GetAllSessions(ctx context.Context) ([]*SessionType, error) { err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM session` tx.SelectWrap(&rtn, query) - var windows []*WindowType - query = `SELECT * FROM window` - tx.SelectWrap(&windows, query) - winMap := make(map[string][]*WindowType) - for _, win := range windows { - winArr := winMap[win.SessionId] - winArr = append(winArr, win) - winMap[win.SessionId] = winArr - } - for _, session := range rtn { - session.Windows = winMap[session.SessionId] - } var screens []*ScreenType query = `SELECT * FROM screen ORDER BY screenidx` tx.SelectWrap(&screens, query) @@ -155,6 +143,16 @@ func GetWindowById(ctx context.Context, sessionId string, windowId string) (*Win return rtnWindow, err } +func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) { + var rtn []*ScreenType + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY screenidx` + tx.SelectWrap(&rtn, query, sessionId) + return nil + }) + return rtn, txErr +} + func GetSessionById(ctx context.Context, id string) (*SessionType, error) { var rtnSession *SessionType err := WithTx(ctx, func(tx *TxWrap) error { @@ -165,15 +163,10 @@ func GetSessionById(ctx context.Context, id string) (*SessionType, error) { return nil } rtnSession = &session - query = `SELECT * FROM window WHERE sessionid = ?` - tx.SelectWrap(&session.Windows, query, session.SessionId) + query = `SELECT * FROM screen WHERE sessionid = ? ORDER BY screenidx` + tx.SelectWrap(&session.Screens, query, session.SessionId) query = `SELECT * FROM remote_instance WHERE sessionid = ?` tx.SelectWrap(&session.Remotes, query, session.SessionId) - query = `SELECT * FROM cmd WHERE sessionid = ?` - marr := tx.SelectMaps(query, session.SessionId) - for _, m := range marr { - session.Cmds = append(session.Cmds, CmdFromMap(m)) - } return nil }) if err != nil { @@ -209,15 +202,16 @@ func InsertSessionWithName(ctx context.Context, sessionName string) (string, err maxSessionIdx := tx.GetInt(`SELECT COALESCE(max(sessionidx), 0) FROM session`) query := `INSERT INTO session (sessionid, name, activescreenid, sessionidx, notifynum) VALUES (?, ?, '', ?, ?)` tx.ExecWrap(query, newSessionId, sessionName, maxSessionIdx+1, 0) - screenId, err := InsertScreen(tx.Context(), newSessionId, "") + _, err := InsertScreen(tx.Context(), newSessionId, "", true) if err != nil { return err } - query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?` - tx.ExecWrap(query, screenId, newSessionId) return nil }) - return newSessionId, txErr + if txErr != nil { + return "", txErr + } + return newSessionId, nil } func containsStr(strs []string, testStr string) bool { @@ -253,7 +247,7 @@ func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []strin } } -func InsertScreen(ctx context.Context, sessionId string, screenName string) (string, error) { +func InsertScreen(ctx context.Context, sessionId string, screenName string, activate bool) (string, error) { var newScreenId string txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT sessionid FROM session WHERE sessionid = ?` @@ -270,11 +264,35 @@ func InsertScreen(ctx context.Context, sessionId string, screenName string) (str layout := LayoutType{Type: LayoutFull} query = `INSERT INTO screen_window (sessionid, screenid, windowid, name, layout) VALUES (?, ?, ?, ?, ?)` tx.ExecWrap(query, sessionId, newScreenId, newWindowId, DefaultScreenWindowName, layout) + if activate { + query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?` + tx.ExecWrap(query, newScreenId, sessionId) + } return nil }) return newScreenId, txErr } +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.GetWrap(&screen, query, sessionId, screenId) + if !found { + return nil + } + rtnScreen = &screen + query = `SELECT * FROM screen_window WHERE sessionid = ? AND screenid = ?` + tx.SelectWrap(&screen.Windows, query, sessionId, screenId) + return nil + }) + if txErr != nil { + return nil, txErr + } + return rtnScreen, nil +} + func txCreateWindow(tx *TxWrap, sessionId string) string { windowId := uuid.New().String() query := `INSERT INTO window (sessionid, windowid, curremote, winopts) VALUES (?, ?, ?, ?)` @@ -366,3 +384,25 @@ func HangupRunningCmdsByRemoteId(ctx context.Context, remoteId string) error { return nil }) } + +func SwitchScreenById(ctx context.Context, sessionId string, screenId string) 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("cannot switch to screen, screen=%s does not exist in session=%s", screenId, sessionId) + } + query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?` + tx.ExecWrap(query, screenId, sessionId) + return nil + }) + sessionUpdate := SessionType{ + SessionId: sessionId, + ActiveScreenId: screenId, + NotifyNum: -1, + } + update := &SessionUpdate{ + Sessions: []SessionType{sessionUpdate}, + } + MainBus.SendUpdate("", update) + return txErr +} diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index a604d0555..d35148643 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -63,9 +63,11 @@ type SessionType struct { ActiveScreenId string `json:"activescreenid"` NotifyNum int64 `json:"notifynum"` Screens []*ScreenType `json:"screens"` - Windows []*WindowType `json:"windows"` - Cmds []*CmdType `json:"cmds"` Remotes []*RemoteInstance `json:"remotes"` + + // only for updates + Remove bool `json:"remove,omitempty"` + Full bool `json:"full,omitempty"` } type WindowOptsType struct { @@ -88,6 +90,9 @@ type WindowType struct { Cmds []*CmdType `json:"cmds"` History []*HistoryItemType `json:"history"` Remotes []*RemoteInstance `json:"remotes"` + + // only for updates + Remove bool `json:"remove,omitempty"` } type ScreenOptsType struct { @@ -110,6 +115,10 @@ type ScreenType struct { ActiveWindowId string `json:"activewindowid"` ScreenOpts ScreenOptsType `json:"screenopts"` Windows []*ScreenWindowType `json:"windows"` + + // only for updates + Remove bool `json:"remove,omitempty"` + Full bool `json:"full,omitempty"` } const ( @@ -143,6 +152,9 @@ type ScreenWindowType struct { WindowId string `json:"windowid"` Name string `json:"name"` Layout LayoutType `json:"layout"` + + // only for updates + Remove bool `json:"remove,omitempty"` } type HistoryItemType struct { diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index 22a754817..1540149d3 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -23,8 +23,7 @@ type WindowUpdate struct { } type SessionUpdate struct { - Session SessionType `json:"session"` - Remove bool `json:"remove,omitempty"` + Sessions []SessionType `json:"sessions"` } type CmdUpdate struct { @@ -32,11 +31,6 @@ type CmdUpdate struct { Remove bool `json:"remove,omitempty"` } -type ScreenUpdate struct { - Screen CmdType `json:"screen"` - Remove bool `json:"remove,omitempty"` -} - type UpdateChannel struct { SessionId string ClientId string