diff --git a/cmd/main-server.go b/cmd/main-server.go index 9ff85225..a1f70906 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -183,7 +183,7 @@ func HandleGetHistory(w http.ResponseWriter, r *http.Request) { numItems = parsedNum } } - hitems, err := sstore.GetSessionHistoryItems(r.Context(), sessionId, numItems) + hitems, err := sstore.GetHistoryItems(r.Context(), sessionId, "", sstore.HistoryQueryOpts{MaxItems: numItems}) if err != nil { WriteJsonError(w, err) return diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 71866927..54d6a681 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -927,13 +927,13 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if maxItems == 0 { maxItems = DefaultMaxHistoryItems } - hitems, err := sstore.GetSessionHistoryItems(ctx, ids.SessionId, maxItems) + hitems, err := sstore.GetHistoryItems(ctx, ids.SessionId, ids.WindowId, sstore.HistoryQueryOpts{MaxItems: maxItems}) if err != nil { return nil, err } var filteredItems []*sstore.HistoryItemType for _, hitem := range hitems { - if hitem.ScreenId == ids.ScreenId && hitem.WindowId == ids.WindowId && (hitem.Remote == ids.Remote.RemotePtr || hitem.IsMetaCmd) { + if hitem.Remote == ids.Remote.RemotePtr || hitem.IsMetaCmd { filteredItems = append(filteredItems, hitem) } } @@ -952,10 +952,6 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto } } update := &sstore.ModelUpdate{} - update.Info = &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("history, limited to current session, screen, window, and remote (maxitems=%d)", maxItems), - InfoLines: splitLinesForInfo(buf.String()), - } update.History = &sstore.HistoryInfoType{ Items: filteredItems, } diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index f1a92e4b..8a1c5c79 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -119,7 +119,6 @@ func resolveByPosition(items []ResolveItem, curId string, posStr string) *Resolv } func resolveUiIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) { - fmt.Printf("resolve-ui-ids: %#v\n", pk) rtn := resolvedIds{} uictx := pk.UIContext if uictx != nil { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index a7fbec4a..77673437 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -4,13 +4,15 @@ import ( "context" "database/sql" "fmt" - "strconv" "strings" "github.com/google/uuid" "github.com/scripthaus-dev/mshell/pkg/packet" ) +const HistoryCols = "historyid, ts, userid, sessionid, screenid, windowid, lineid, cmdid, haderror, cmdstr, remoteownerid, remoteid, remotename, ismetacmd" +const DefaultMaxHistoryItems = 1000 + func NumSessions(ctx context.Context) (int, error) { db, err := GetDB(ctx) if err != nil { @@ -117,23 +119,60 @@ func InsertHistoryItem(ctx context.Context, hitem *HistoryItemType) error { return nil } -func GetSessionHistoryItems(ctx context.Context, sessionId string, maxItems int) ([]*HistoryItemType, error) { +func runHistoryQuery(tx *TxWrap, sessionId string, windowId string, opts HistoryQueryOpts) ([]*HistoryItemType, error) { + // check sessionid/windowid format because we are directly inserting them into the SQL + if sessionId != "" { + _, err := uuid.Parse(sessionId) + if err != nil { + return nil, fmt.Errorf("malformed sessionid") + } + } + if windowId != "" { + _, err := uuid.Parse(windowId) + if err != nil { + return nil, fmt.Errorf("malformed windowid") + } + } + hnumStr := "" + whereClause := "" + if sessionId != "" && windowId != "" { + whereClause = fmt.Sprintf("WHERE sessionid = '%s' AND windowid = '%s'", sessionId, windowId) + hnumStr = "w" + } else if sessionId != "" { + whereClause = fmt.Sprintf("WHERE sessionid = '%s'", sessionId) + hnumStr = "s" + } else { + hnumStr = "g" + } + maxItems := opts.MaxItems + if maxItems == 0 { + maxItems = DefaultMaxHistoryItems + } + query := fmt.Sprintf("SELECT %s, '%s' || row_number() OVER win AS historynum FROM history %s WINDOW win AS (ORDER BY ts, historyid) ORDER BY ts DESC, historyid DESC LIMIT %d", HistoryCols, hnumStr, whereClause, maxItems) + if opts.FromTs > 0 { + query = fmt.Sprintf("SELECT * FROM (%s) WHERE ts >= %d", query, opts.FromTs) + } + marr := tx.SelectMaps(query) + rtn := make([]*HistoryItemType, len(marr)) + for idx, m := range marr { + hitem := HistoryItemFromMap(m) + rtn[idx] = hitem + } + return rtn, nil +} + +func GetHistoryItems(ctx context.Context, sessionId string, windowId string, opts HistoryQueryOpts) ([]*HistoryItemType, error) { var rtn []*HistoryItemType - err := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT count(*) FROM history WHERE sessionid = ?` - totalNum := tx.GetInt(query, sessionId) - query = `SELECT * FROM history WHERE sessionid = ? ORDER BY ts DESC, historyid LIMIT ?` - marr := tx.SelectMaps(query, sessionId, maxItems) - for idx, m := range marr { - hnum := totalNum - idx - hitem := HistoryItemFromMap(m) - hitem.HistoryNum = strconv.Itoa(hnum) - rtn = append(rtn, hitem) + txErr := WithTx(ctx, func(tx *TxWrap) error { + var err error + rtn, err = runHistoryQuery(tx, sessionId, windowId, opts) + if err != nil { + return err } return nil }) - if err != nil { - return nil, err + if txErr != nil { + return nil, txErr } return rtn, nil } diff --git a/pkg/sstore/quick.go b/pkg/sstore/quick.go index dcc3e789..df7a26a7 100644 --- a/pkg/sstore/quick.go +++ b/pkg/sstore/quick.go @@ -4,6 +4,7 @@ import ( "database/sql/driver" "encoding/json" "fmt" + "strconv" ) func quickSetStr(strVal *string, m map[string]interface{}, name string) { @@ -11,6 +12,11 @@ func quickSetStr(strVal *string, m map[string]interface{}, name string) { if !ok { return } + ival, ok := v.(int64) + if ok { + *strVal = strconv.FormatInt(ival, 10) + return + } str, ok := v.(string) if !ok { return diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 12606d56..dafdf966 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -244,6 +244,7 @@ func HistoryItemFromMap(m map[string]interface{}) *HistoryItemType { quickSetStr(&h.Remote.RemoteId, m, "remoteid") quickSetStr(&h.Remote.Name, m, "remotename") quickSetBool(&h.IsMetaCmd, m, "ismetacmd") + quickSetStr(&h.HistoryNum, m, "historynum") return &h } @@ -332,6 +333,11 @@ type HistoryItemType struct { HistoryNum string `json:"historynum"` } +type HistoryQueryOpts struct { + MaxItems int + FromTs int64 +} + type RemoteState struct { Cwd string `json:"cwd"` Env0 []byte `json:"env0"` // "env -0" format