diff --git a/cmd/main-server.go b/cmd/main-server.go index 924243fe..9f42e1cc 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -270,19 +270,19 @@ func sendCmdInput(pk *packet.InputPacketType) error { return msh.SendInput(pk) } -// params: name +// params: sessionid func HandleGetSession(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() - name := qvals.Get("name") - if name == "" { - WriteJsonError(w, fmt.Errorf("must specify a name")) + sessionId := qvals.Get("sessionid") + if sessionId == "" { + WriteJsonError(w, fmt.Errorf("must specify a sessionid")) return } - session, err := sstore.GetSessionByName(r.Context(), name) + session, err := sstore.GetSessionById(r.Context(), sessionId) if err != nil { WriteJsonError(w, err) return @@ -291,6 +291,69 @@ func HandleGetSession(w http.ResponseWriter, r *http.Request) { return } +func HandleGetAllSessions(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") + list, err := sstore.GetAllSessions(r.Context()) + if err != nil { + WriteJsonError(w, fmt.Errorf("cannot get all sessions: %w", err)) + return + } + WriteJsonSuccess(w, list) + return +} + +// params: name +func HandleCreateSession(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() + name := qvals.Get("name") + sessionId, err := sstore.InsertSessionWithName(r.Context(), name) + if err != nil { + WriteJsonError(w, fmt.Errorf("inserting session: %w", err)) + return + } + session, err := sstore.GetSessionById(r.Context(), sessionId) + if err != nil { + WriteJsonError(w, fmt.Errorf("getting new session: %w", err)) + return + } + WriteJsonSuccess(w, session) + return +} + +// params: sessionid, name +func HandleCreateWindow(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") + windowId, err := sstore.InsertWindow(r.Context(), sessionId, name) + if err != nil { + WriteJsonError(w, fmt.Errorf("inserting new window: %w", err)) + return + } + window, err := sstore.GetWindowById(r.Context(), sessionId, windowId) + if err != nil { + WriteJsonError(w, fmt.Errorf("getting new window: %w", err)) + return + } + WriteJsonSuccess(w, window) + return +} + // params: [none] func HandleGetRemotes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) @@ -303,7 +366,7 @@ func HandleGetRemotes(w http.ResponseWriter, r *http.Request) { } // params: sessionid, windowid -func HandleGetWindowLines(w http.ResponseWriter, r *http.Request) { +func HandleGetWindow(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") @@ -319,12 +382,12 @@ func HandleGetWindowLines(w http.ResponseWriter, r *http.Request) { WriteJsonError(w, fmt.Errorf("invalid windowid: %w", err)) return } - lines, err := sstore.GetWindowLines(r.Context(), sessionId, windowId) + window, err := sstore.GetWindowById(r.Context(), sessionId, windowId) if err != nil { WriteJsonError(w, err) return } - WriteJsonSuccess(w, lines) + WriteJsonSuccess(w, window) return } @@ -623,9 +686,12 @@ func main() { go runWebSocketServer() gr := mux.NewRouter() 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-lines", HandleGetWindowLines) + gr.HandleFunc("/api/get-window", HandleGetWindow) gr.HandleFunc("/api/get-remotes", HandleGetRemotes) + gr.HandleFunc("/api/create-window", HandleCreateWindow) gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS") server := &http.Server{ Addr: MainServerAddr, diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 23d2b5af..17af26f5 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -84,6 +84,20 @@ func InsertRemote(ctx context.Context, remote *RemoteType) error { return nil } +func GetAllSessions(ctx context.Context) ([]*SessionType, error) { + db, err := GetDB() + if err != nil { + return nil, err + } + var rtn []*SessionType + query := `SELECT * FROM session` + err = db.SelectContext(ctx, &rtn, query) + if err != nil { + return nil, err + } + return rtn, nil +} + func GetSessionById(ctx context.Context, id string) (*SessionType, error) { var rtnSession *SessionType err := WithTx(ctx, func(tx *TxWrap) error { @@ -128,35 +142,56 @@ func GetSessionByName(ctx context.Context, name string) (*SessionType, error) { return GetSessionById(ctx, sessionId) } -func GetWindowLines(ctx context.Context, sessionId string, windowId string) ([]*LineType, error) { - var lines []*LineType - db, err := GetDB() - if err != nil { - return nil, err - } - query := `SELECT * FROM line WHERE sessionid = ? AND windowid = ?` - err = db.SelectContext(ctx, &lines, query, sessionId, windowId) - if err != nil { - return nil, err - } - return lines, nil +func GetWindowById(ctx context.Context, sessionId string, windowId string) (*WindowType, error) { + var rtnWindow *WindowType + txErr := WithTx(ctx, func(tx *TxWrap) error { + var window WindowType + query := `SELECT * FROM window WHERE sessionid = ? AND windowid = ?` + found := tx.GetWrap(&window, query, sessionId, windowId) + if !found { + return nil + } + rtnWindow = &window + query = `SELECT * FROM line WHERE sessionid = ? AND windowid = ?` + tx.SelectWrap(&window.Lines, query, sessionId, windowId) + return nil + }) + return rtnWindow, txErr } -// also creates window -func InsertSessionWithName(ctx context.Context, sessionName string) error { - if sessionName == "" { - return fmt.Errorf("invalid session name '%s'", sessionName) - } - session := &SessionType{ - SessionId: uuid.New().String(), - Name: sessionName, - } - return WithTx(ctx, func(tx *TxWrap) error { +// also creates default window, returns sessionId +// if sessionName == "", it will be generated +func InsertSessionWithName(ctx context.Context, sessionName string) (string, error) { + newSessionId := uuid.New().String() + txErr := WithTx(ctx, func(tx *TxWrap) error { + if sessionName == "" { + var names []string + query := `SELECT name FROM session` + tx.GetWrap(&names, query) + snum := len(names) + 1 + for { + sessionName = fmt.Sprintf("session-%d", snum) + if !containsStr(names, sessionName) { + break + } + snum++ + } + } else { + var dupSessionId string + query := `SELECT sessionid FROM session WHERE name = ?` + tx.GetWrap(&dupSessionId, query, sessionName) + if dupSessionId != "" { + return fmt.Errorf("cannot create session with duplicate name") + } + } + newSession := &SessionType{ + SessionId: newSessionId, + Name: sessionName, + } query := `INSERT INTO session (sessionid, name) VALUES (:sessionid, :name)` - tx.NamedExecWrap(query, session) - + tx.NamedExecWrap(query, newSession) window := &WindowType{ - SessionId: session.SessionId, + SessionId: newSessionId, WindowId: uuid.New().String(), Name: DefaultWindowName, CurRemote: LocalRemoteName, @@ -165,6 +200,61 @@ func InsertSessionWithName(ctx context.Context, sessionName string) error { tx.NamedExecWrap(query, window) return nil }) + return newSessionId, txErr +} + +func containsStr(strs []string, testStr string) bool { + for _, s := range strs { + if s == testStr { + return true + } + } + return false +} + +// if windowName == "", it will be generated +// returns (windowid, err) +func InsertWindow(ctx context.Context, sessionId string, windowName string) (string, error) { + var newWindowId string + txErr := WithTx(ctx, func(tx *TxWrap) error { + var testSessionId string + query := `SELECT sesssionid FROM session WHERE sessionid = ?` + sessionExists := tx.GetWrap(&testSessionId, query, sessionId) + if !sessionExists { + return fmt.Errorf("cannot insert window, session does not exist") + } + if windowName == "" { + var names []string + query = `SELECT name FROM window WHERE sessionid = ?` + tx.GetWrap(&names, query, sessionId) + wnum := len(names) + 1 + for { + windowName = fmt.Sprintf("w%d", wnum) + if !containsStr(names, windowName) { + break + } + wnum++ + } + } else { + var testWindowId string + query = `SELECT windowid FROM window WHERE sessionid = ? AND name = ?` + windowExists := tx.GetWrap(&testWindowId, query, sessionId, windowName) + if windowExists { + return fmt.Errorf("cannot insert window, name already exists in session") + } + } + newWindowId = uuid.New().String() + window := &WindowType{ + SessionId: sessionId, + WindowId: newWindowId, + Name: windowName, + CurRemote: LocalRemoteName, + } + query = `INSERT INTO window (sessionid, windowid, name, curremote, version) VALUES (:sessionid, :windowid, :name, :curremote, :version)` + tx.NamedExecWrap(query, window) + return nil + }) + return newWindowId, txErr } func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error { diff --git a/pkg/sstore/fileops.go b/pkg/sstore/fileops.go index 67026ba4..71a0daea 100644 --- a/pkg/sstore/fileops.go +++ b/pkg/sstore/fileops.go @@ -16,6 +16,7 @@ func AppendToCmdPtyBlob(ctx context.Context, sessionId string, cmdId string, dat if err != nil { return err } + defer fd.Close() if len(data) == 0 { return nil } diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 3f50ee08..9e665f5b 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -327,7 +327,7 @@ func EnsureDefaultSession(ctx context.Context) (*SessionType, error) { if session != nil { return session, nil } - err = InsertSessionWithName(ctx, DefaultSessionName) + _, err = InsertSessionWithName(ctx, DefaultSessionName) if err != nil { return nil, err }