diff --git a/cmd/main-server.go b/cmd/main-server.go index a87f86bb..56e1eba5 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -340,17 +340,7 @@ func HandleCreateWindow(w http.ResponseWriter, r *http.Request) { 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) + fmt.Printf("insert-window %s\n", name) return } diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index 5c3e48e5..594b12f4 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -2,25 +2,25 @@ CREATE TABLE session ( sessionid varchar(36) PRIMARY KEY, name varchar(50) NOT NULL, sessionidx int NOT NULL, + activescreenid varchar(36) NOT NULL, notifynum int NOT NULL ); -CREATE UNIQUE INDEX session_name_unique ON session(name); CREATE TABLE window ( sessionid varchar(36) NOT NULL, windowid varchar(36) NOT NULL, - name varchar(50) NOT NULL, curremote varchar(50) NOT NULL, winopts json NOT NULL, PRIMARY KEY (sessionid, windowid) ); -CREATE UNIQUE INDEX window_name_unique ON window(sessionid, name); CREATE TABLE screen ( sessionid varchar(36) NOT NULL, screenid varchar(36) NOT NULL, name varchar(50) NOT NULL, + activewindowid varchar(36) NOT NULL, screenidx int NOT NULL, + screenopts json NOT NULL, PRIMARY KEY (sessionid, screenid) ); @@ -28,6 +28,7 @@ CREATE TABLE screen_window ( sessionid varchar(36) NOT NULL, screenid varchar(36) NOT NULL, windowid varchar(36) NOT NULL, + name varchar(50) NOT NULL, layout json NOT NULL, PRIMARY KEY (sessionid, screenid, windowid) ); diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index f9dbc38a..5240c3a1 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "strings" "github.com/google/uuid" "github.com/scripthaus-dev/mshell/pkg/packet" @@ -101,6 +102,32 @@ func GetAllSessions(ctx context.Context) ([]*SessionType, error) { for _, session := range rtn { session.Windows = winMap[session.SessionId] } + var screens []*ScreenType + query = `SELECT * FROM screen ORDER BY screenidx` + tx.SelectWrap(&screens, query) + screenMap := make(map[string][]*ScreenType) + for _, screen := range screens { + screenArr := screenMap[screen.SessionId] + screenArr = append(screenArr, screen) + screenMap[screen.SessionId] = screenArr + } + for _, session := range rtn { + session.Screens = screenMap[session.SessionId] + } + var sws []*ScreenWindowType + query = `SELECT * FROM screen_window` + tx.SelectWrap(&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) + } return nil }) return rtn, err @@ -177,43 +204,17 @@ func GetSessionByName(ctx context.Context, name string) (*SessionType, error) { 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") - } + names := tx.SelectStrings(`SELECT name FROM session`) + sessionName = fmtUniqueName(sessionName, "session-%d", len(names)+1, names) + 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, "") + if err != nil { + return err } - var maxSessionIdx int64 - query := `SELECT COALESCE(max(sessionidx), 0) FROM session` - tx.GetWrap(&maxSessionIdx, query) - newSession := &SessionType{ - SessionId: newSessionId, - Name: sessionName, - SessionIdx: maxSessionIdx + 1, - } - query = `INSERT INTO session (sessionid, name, sessionidx, notifynum) VALUES (:sessionid, :name, :sessionidx, :notifynum)` - tx.NamedExecWrap(query, newSession) - window := &WindowType{ - SessionId: newSessionId, - WindowId: uuid.New().String(), - Name: DefaultWindowName, - CurRemote: LocalRemoteName, - } - txInsertWindow(tx, window) + query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?` + tx.ExecWrap(query, screenId, newSessionId) return nil }) return newSessionId, txErr @@ -228,61 +229,57 @@ func containsStr(strs []string, testStr string) bool { return false } +func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []string) string { + var fmtStr string + if name != "" { + if !containsStr(strs, name) { + return name + } + fmtStr = name + "-%d" + startIdx = 2 + } else { + fmtStr = defaultFmtStr + } + if strings.Index(fmtStr, "%d") == -1 { + panic("invalid fmtStr: " + fmtStr) + } + for { + testName := fmt.Sprintf(fmtStr, startIdx) + if containsStr(strs, testName) { + startIdx++ + continue + } + return testName + } +} + func InsertScreen(ctx context.Context, sessionId string, screenName string) (string, error) { var newScreenId string txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT sessionid FROM session WHERE sessionid = ?` + if !tx.Exists(query, sessionId) { + return fmt.Errorf("cannot create screen, no session found") + } + newWindowId := txCreateWindow(tx, sessionId) + maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ?`, sessionId) + screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ?`, sessionId) + screenName = fmtUniqueName(screenName, "s%d", maxScreenIdx+1, screenNames) + newScreenId = uuid.New().String() + query = `INSERT INTO screen (sessionid, screenid, name, activewindowid, screenidx, screenopts) VALUES (?, ?, ?, ?, ?, ?)` + tx.ExecWrap(query, sessionId, newScreenId, screenName, newWindowId, maxScreenIdx+1, ScreenOptsType{}) + layout := LayoutType{Type: LayoutFull} + query = `INSERT INTO screen_window (sessionid, screenid, windowid, name, layout) VALUES (?, ?, ?, ?, ?)` + tx.ExecWrap(query, sessionId, newScreenId, newWindowId, DefaultScreenWindowName, layout) return nil }) return newScreenId, txErr } -// 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, - } - txInsertWindow(tx, window) - return nil - }) - return newWindowId, txErr -} - -func txInsertWindow(tx *TxWrap, window *WindowType) { - query := `INSERT INTO window (sessionid, windowid, name, curremote, winopts) VALUES (:sessionid, :windowid, :name, :curremote, :winopts)` - tx.NamedExecWrap(query, window) +func txCreateWindow(tx *TxWrap, sessionId string) string { + windowId := uuid.New().String() + query := `INSERT INTO window (sessionid, windowid, curremote, winopts) VALUES (?, ?, ?, ?)` + tx.ExecWrap(query, sessionId, windowId, LocalRemoteName, WindowOptsType{}) + return windowId } func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error { diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 817d5664..e72b7895 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -25,6 +25,7 @@ const DBFileName = "sh2.db" const DefaultSessionName = "default" const DefaultWindowName = "default" const LocalRemoteName = "local" +const DefaultScreenWindowName = "w1" const DefaultCwd = "~" @@ -56,13 +57,15 @@ func GetDB(ctx context.Context) (*sqlx.DB, error) { } type SessionType struct { - SessionId string `json:"sessionid"` - Name string `json:"name"` - SessionIdx int64 `json:"sessionidx"` - NotifyNum int64 `json:"notifynum"` - Windows []*WindowType `json:"windows"` - Cmds []*CmdType `json:"cmds"` - Remotes []*RemoteInstance `json:"remotes"` + SessionId string `json:"sessionid"` + Name string `json:"name"` + SessionIdx int64 `json:"sessionidx"` + ActiveScreenId string `json:"activescreenid"` + NotifyNum int64 `json:"notifynum"` + Screens []*ScreenType `json:"screens"` + Windows []*WindowType `json:"windows"` + Cmds []*CmdType `json:"cmds"` + Remotes []*RemoteInstance `json:"remotes"` } type WindowOptsType struct { @@ -79,31 +82,51 @@ func (opts WindowOptsType) Value() (driver.Value, error) { type WindowType struct { SessionId string `json:"sessionid"` WindowId string `json:"windowid"` - Name string `json:"name"` CurRemote string `json:"curremote"` + WinOpts WindowOptsType `json:"winopts"` Lines []*LineType `json:"lines"` Cmds []*CmdType `json:"cmds"` History []*HistoryItemType `json:"history"` Remotes []*RemoteInstance `json:"remotes"` - WinOpts WindowOptsType `json:"winopts"` +} + +type ScreenOptsType struct { + TabColor string `json:"tabcolor"` +} + +func (opts *ScreenOptsType) Scan(val interface{}) error { + return quickScanJson(opts, val) +} + +func (opts ScreenOptsType) Value() (driver.Value, error) { + return quickValueJson(opts) } type ScreenType struct { - SessionId string `json:"sessionid"` - ScreenId string `json:"screenid"` - ScreenIdx int64 `json:"screenidx"` - Name string `json:"name"` + SessionId string `json:"sessionid"` + ScreenId string `json:"screenid"` + ScreenIdx int64 `json:"screenidx"` + Name string `json:"name"` + ActiveWindowId string `json:"activewindowid"` + ScreenOpts ScreenOptsType `json:"screenopts"` + Windows []*ScreenWindowType `json:"windows"` } +const ( + LayoutFull = "full" +) + type LayoutType struct { - ZIndex int64 `json:"zindex"` - Float bool `json:"float"` - Top string `json:"top"` - Bottom string `json:"bottom"` - Left string `json:"left"` - Right string `json:"right"` - Width string `json:"width"` - Height string `json:"height"` + Type string `json:"type"` + Parent string `json:"parent,omitempty"` + ZIndex int64 `json:"zindex,omitempty"` + Float bool `json:"float,omitempty"` + Top string `json:"top,omitempty"` + Bottom string `json:"bottom,omitempty"` + Left string `json:"left,omitempty"` + Right string `json:"right,omitempty"` + Width string `json:"width,omitempty"` + Height string `json:"height,omitempty"` } func (l *LayoutType) Scan(val interface{}) error { @@ -118,6 +141,7 @@ type ScreenWindowType struct { SessionId string `json:"sessionid"` ScreenId string `json:"screenid"` WindowId string `json:"windowid"` + Name string `json:"name"` Layout LayoutType `json:"layout"` } diff --git a/pkg/sstore/txwrap.go b/pkg/sstore/txwrap.go index 49862a00..8033c5f9 100644 --- a/pkg/sstore/txwrap.go +++ b/pkg/sstore/txwrap.go @@ -25,6 +25,9 @@ func WithTx(ctx context.Context, fn func(tx *TxWrap) error) (rtnErr error) { ctxVal := ctx.Value(txWrapKey{}) if ctxVal != nil { txWrap = ctxVal.(*TxWrap) + if txWrap.Err != nil { + return txWrap.Err + } } if txWrap == nil { db, err := GetDB(ctx) @@ -84,6 +87,29 @@ func (tx *TxWrap) ExecWrap(query string, args ...interface{}) sql.Result { return result } +func (tx *TxWrap) Exists(query string, args ...interface{}) bool { + var dest interface{} + return tx.GetWrap(&dest, query, args...) +} + +func (tx *TxWrap) GetString(query string, args ...interface{}) string { + var rtnStr string + tx.GetWrap(&rtnStr, query, args...) + return rtnStr +} + +func (tx *TxWrap) SelectStrings(query string, args ...interface{}) []string { + var rtnArr []string + tx.SelectWrap(&rtnArr, query, args...) + return rtnArr +} + +func (tx *TxWrap) GetInt(query string, args ...interface{}) int { + var rtnInt int + tx.GetWrap(&rtnInt, query, args...) + return rtnInt +} + func (tx *TxWrap) GetWrap(dest interface{}, query string, args ...interface{}) bool { if tx.Err != nil { return false