waveterm/pkg/sstore/dbops.go

2367 lines
76 KiB
Go
Raw Normal View History

2022-07-01 23:07:13 +02:00
package sstore
import (
"context"
"fmt"
2022-09-21 02:37:49 +02:00
"strconv"
"strings"
"sync"
2022-11-28 09:13:00 +01:00
"time"
2022-07-01 23:07:13 +02:00
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/sawka/txwrap"
"github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet"
2022-11-28 09:13:00 +01:00
"github.com/scripthaus-dev/mshell/pkg/shexec"
2022-09-21 02:37:49 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
2022-07-01 23:07:13 +02:00
)
const HistoryCols = "historyid, ts, userid, sessionid, screenid, windowid, lineid, cmdid, haderror, cmdstr, remoteownerid, remoteid, remotename, ismetacmd, incognito"
2022-08-30 04:18:02 +02:00
const DefaultMaxHistoryItems = 1000
type SingleConnDBGetter struct {
SingleConnLock *sync.Mutex
}
type TxWrap = txwrap.TxWrap
var dbWrap *SingleConnDBGetter
func init() {
dbWrap = &SingleConnDBGetter{SingleConnLock: &sync.Mutex{}}
}
func (dbg *SingleConnDBGetter) GetDB(ctx context.Context) (*sqlx.DB, error) {
db, err := GetDB(ctx)
if err != nil {
return nil, err
}
dbg.SingleConnLock.Lock()
return db, nil
}
func (dbg *SingleConnDBGetter) ReleaseDB(db *sqlx.DB) {
dbg.SingleConnLock.Unlock()
}
func WithTx(ctx context.Context, fn func(tx *TxWrap) error) error {
return txwrap.DBGWithTx(ctx, dbWrap, fn)
}
2022-07-01 23:07:13 +02:00
func NumSessions(ctx context.Context) (int, error) {
var numSessions int
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := "SELECT count(*) FROM session"
numSessions = tx.GetInt(query)
return nil
})
return numSessions, txErr
2022-07-01 23:07:13 +02:00
}
func GetAllRemotes(ctx context.Context) ([]*RemoteType, error) {
var rtn []*RemoteType
err := WithTx(ctx, func(tx *TxWrap) error {
2022-09-14 21:06:55 +02:00
query := `SELECT * FROM remote ORDER BY remoteidx`
marr := tx.SelectMaps(query)
for _, m := range marr {
rtn = append(rtn, FromMap[*RemoteType](m))
}
return nil
})
if err != nil {
return nil, err
}
return rtn, nil
}
func GetRemoteByAlias(ctx context.Context, alias string) (*RemoteType, error) {
var remote *RemoteType
err := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote WHERE remotealias = ?`
m := tx.GetMap(query, alias)
remote = FromMap[*RemoteType](m)
return nil
})
if err != nil {
return nil, err
}
return remote, nil
}
2022-08-17 00:08:28 +02:00
func GetRemoteById(ctx context.Context, remoteId string) (*RemoteType, error) {
var remote *RemoteType
err := WithTx(ctx, func(tx *TxWrap) error {
2022-08-17 00:08:28 +02:00
query := `SELECT * FROM remote WHERE remoteid = ?`
m := tx.GetMap(query, remoteId)
remote = FromMap[*RemoteType](m)
return nil
})
2022-07-01 23:07:13 +02:00
if err != nil {
return nil, err
}
return remote, nil
2022-07-01 23:07:13 +02:00
}
func GetLocalRemote(ctx context.Context) (*RemoteType, error) {
var remote *RemoteType
err := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote WHERE local`
m := tx.GetMap(query)
remote = FromMap[*RemoteType](m)
return nil
})
if err != nil {
return nil, err
}
return remote, nil
}
2022-09-14 02:11:36 +02:00
func GetRemoteByCanonicalName(ctx context.Context, cname string) (*RemoteType, error) {
var remote *RemoteType
err := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote WHERE remotecanonicalname = ?`
remote = GetMapGen[*RemoteType](tx, query, cname)
2022-09-14 02:11:36 +02:00
return nil
})
if err != nil {
return nil, err
}
return remote, nil
}
2022-08-17 00:08:28 +02:00
func GetRemoteByPhysicalId(ctx context.Context, physicalId string) (*RemoteType, error) {
var remote *RemoteType
err := WithTx(ctx, func(tx *TxWrap) error {
2022-08-17 00:08:28 +02:00
query := `SELECT * FROM remote WHERE physicalid = ?`
remote = GetMapGen[*RemoteType](tx, query, physicalId)
return nil
})
2022-07-01 23:07:13 +02:00
if err != nil {
return nil, err
}
return remote, nil
2022-07-01 23:07:13 +02:00
}
2022-09-14 02:11:36 +02:00
func UpsertRemote(ctx context.Context, r *RemoteType) error {
if r == nil {
2022-07-01 23:07:13 +02:00
return fmt.Errorf("cannot insert nil remote")
}
2022-09-14 02:11:36 +02:00
if r.RemoteId == "" {
return fmt.Errorf("cannot insert remote without id")
2022-07-01 23:07:13 +02:00
}
2022-09-14 02:11:36 +02:00
if r.RemoteCanonicalName == "" {
return fmt.Errorf("cannot insert remote with canonicalname")
}
2022-09-14 02:11:36 +02:00
if r.RemoteType == "" {
return fmt.Errorf("cannot insert remote without type")
2022-07-01 23:07:13 +02:00
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT remoteid FROM remote WHERE remoteid = ?`
2022-09-14 02:11:36 +02:00
if tx.Exists(query, r.RemoteId) {
tx.Exec(`DELETE FROM remote WHERE remoteid = ?`, r.RemoteId)
}
2022-09-14 02:11:36 +02:00
query = `SELECT remoteid FROM remote WHERE remotecanonicalname = ?`
if tx.Exists(query, r.RemoteCanonicalName) {
return fmt.Errorf("remote has duplicate canonicalname '%s', cannot create", r.RemoteCanonicalName)
}
2022-10-01 01:23:40 +02:00
query = `SELECT remoteid FROM remote WHERE remotealias = ?`
if r.RemoteAlias != "" && tx.Exists(query, r.RemoteAlias) {
return fmt.Errorf("remote has duplicate alias '%s', cannot create", r.RemoteAlias)
}
2022-09-20 23:23:53 +02:00
query = `SELECT COALESCE(max(remoteidx), 0) FROM remote`
2022-09-14 21:06:55 +02:00
maxRemoteIdx := tx.GetInt(query)
r.RemoteIdx = int64(maxRemoteIdx + 1)
query = `INSERT INTO remote
2022-11-28 09:13:00 +01:00
( remoteid, physicalid, remotetype, remotealias, remotecanonicalname, remotesudo, remoteuser, remotehost, connectmode, autoinstall, sshopts, remoteopts, lastconnectts, archived, remoteidx, local) VALUES
(:remoteid,:physicalid,:remotetype,:remotealias,:remotecanonicalname,:remotesudo,:remoteuser,:remotehost,:connectmode,:autoinstall,:sshopts,:remoteopts,:lastconnectts,:archived,:remoteidx,:local)`
tx.NamedExec(query, r.ToMap())
2022-09-13 21:06:12 +02:00
return nil
})
return txErr
}
2022-08-11 21:07:41 +02:00
func InsertHistoryItem(ctx context.Context, hitem *HistoryItemType) error {
if hitem == nil {
return fmt.Errorf("cannot insert nil history item")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `INSERT INTO history
( historyid, ts, userid, sessionid, screenid, windowid, lineid, cmdid, haderror, cmdstr, remoteownerid, remoteid, remotename, ismetacmd, incognito) VALUES
(:historyid,:ts,:userid,:sessionid,:screenid,:windowid,:lineid,:cmdid,:haderror,:cmdstr,:remoteownerid,:remoteid,:remotename,:ismetacmd,:incognito)`
tx.NamedExec(query, hitem.ToMap())
return nil
})
return txErr
2022-08-11 21:07:41 +02:00
}
func IsIncognitoScreen(ctx context.Context, sessionId string, screenId string) (bool, error) {
var rtn bool
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT incognito FROM screen WHERE sessionid = ? AND screenid = ?`
tx.Get(&rtn, query, sessionId, screenId)
return nil
})
return rtn, txErr
}
const HistoryQueryChunkSize = 1000
func _getNextHistoryItem(items []*HistoryItemType, index int, filterFn func(*HistoryItemType) bool) (*HistoryItemType, int) {
for ; index < len(items); index++ {
item := items[index]
if filterFn(item) {
return item, index
}
}
return nil, index
}
2023-03-06 22:54:38 +01:00
// returns true if done, false if we still need to process more items
func (result *HistoryQueryResult) processItem(item *HistoryItemType, rawOffset int) bool {
2023-03-07 00:42:50 +01:00
if result.prevItems < result.Offset {
2023-03-06 22:54:38 +01:00
result.prevItems++
return false
}
if len(result.Items) == result.MaxItems {
result.HasMore = true
result.NextRawOffset = rawOffset
2023-03-06 22:54:38 +01:00
return true
}
2023-03-07 00:42:50 +01:00
if len(result.Items) == 0 {
result.RawOffset = rawOffset
}
result.Items = append(result.Items, item)
2023-03-06 22:54:38 +01:00
return false
}
2023-03-06 22:54:38 +01:00
func runHistoryQueryWithFilter(tx *TxWrap, opts HistoryQueryOpts) (*HistoryQueryResult, error) {
if opts.MaxItems == 0 {
return nil, fmt.Errorf("invalid query, maxitems is 0")
}
2023-03-07 00:42:50 +01:00
rtn := &HistoryQueryResult{Offset: opts.Offset, MaxItems: opts.MaxItems}
2023-03-06 22:54:38 +01:00
var rawOffset int
if opts.RawOffset >= opts.Offset {
rtn.prevItems = opts.Offset
rawOffset = opts.RawOffset
} else {
rawOffset = 0
}
for {
resultItems, err := runHistoryQuery(tx, opts, rawOffset, HistoryQueryChunkSize)
if err != nil {
return nil, err
}
isDone := false
for resultIdx := 0; resultIdx < len(resultItems); resultIdx++ {
2023-03-06 22:54:38 +01:00
if opts.FilterFn != nil && !opts.FilterFn(resultItems[resultIdx]) {
continue
}
isDone = rtn.processItem(resultItems[resultIdx], rawOffset+resultIdx)
if isDone {
break
}
}
if isDone {
break
}
if len(resultItems) < HistoryQueryChunkSize {
break
}
2023-03-06 22:54:38 +01:00
rawOffset += HistoryQueryChunkSize
}
return rtn, nil
}
func runHistoryQuery(tx *TxWrap, opts HistoryQueryOpts, realOffset int, itemLimit int) ([]*HistoryItemType, error) {
2022-08-30 04:18:02 +02:00
// check sessionid/windowid format because we are directly inserting them into the SQL
if opts.SessionId != "" {
_, err := uuid.Parse(opts.SessionId)
2022-08-30 04:18:02 +02:00
if err != nil {
return nil, fmt.Errorf("malformed sessionid")
}
}
if opts.WindowId != "" {
_, err := uuid.Parse(opts.WindowId)
2022-08-30 04:18:02 +02:00
if err != nil {
return nil, fmt.Errorf("malformed windowid")
}
}
if opts.RemoteId != "" {
_, err := uuid.Parse(opts.RemoteId)
if err != nil {
return nil, fmt.Errorf("malformed remoteid")
}
}
2022-08-30 04:18:02 +02:00
hnumStr := ""
whereClause := "WHERE 1"
2023-03-02 09:31:19 +01:00
var queryArgs []interface{}
if opts.SessionId != "" && opts.WindowId != "" {
whereClause += fmt.Sprintf(" AND sessionid = '%s' AND windowid = '%s'", opts.SessionId, opts.WindowId)
2022-08-30 04:18:02 +02:00
hnumStr = "w"
} else if opts.SessionId != "" {
whereClause += fmt.Sprintf(" AND sessionid = '%s'", opts.SessionId)
2022-08-30 04:18:02 +02:00
hnumStr = "s"
} else {
hnumStr = "g"
}
2023-03-02 09:31:19 +01:00
if opts.SearchText != "" {
whereClause += " AND cmdstr LIKE ? ESCAPE '\\'"
2023-03-02 09:31:19 +01:00
likeArg := opts.SearchText
likeArg = strings.ReplaceAll(likeArg, "%", "\\%")
likeArg = strings.ReplaceAll(likeArg, "_", "\\_")
queryArgs = append(queryArgs, "%"+likeArg+"%")
}
if opts.FromTs > 0 {
2023-03-05 22:53:24 +01:00
whereClause += fmt.Sprintf(" AND ts <= %d", opts.FromTs)
}
if opts.RemoteId != "" {
whereClause += fmt.Sprintf(" AND remoteid = '%s'", opts.RemoteId)
}
if opts.NoMeta {
whereClause += " AND NOT ismetacmd"
}
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 OFFSET %d", HistoryCols, hnumStr, whereClause, itemLimit, realOffset)
2023-03-02 09:31:19 +01:00
marr := tx.SelectMaps(query, queryArgs...)
2022-08-30 04:18:02 +02:00
rtn := make([]*HistoryItemType, len(marr))
for idx, m := range marr {
hitem := FromMap[*HistoryItemType](m)
2022-08-30 04:18:02 +02:00
rtn[idx] = hitem
}
return rtn, nil
}
func GetHistoryItems(ctx context.Context, opts HistoryQueryOpts) (*HistoryQueryResult, error) {
var rtn *HistoryQueryResult
2022-08-30 04:18:02 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
var err error
2023-03-06 22:54:38 +01:00
rtn, err = runHistoryQueryWithFilter(tx, opts)
2022-08-30 04:18:02 +02:00
if err != nil {
return err
}
2022-08-12 08:45:15 +02:00
return nil
})
2022-08-30 04:18:02 +02:00
if txErr != nil {
return nil, txErr
2022-08-12 08:45:15 +02:00
}
return rtn, nil
}
2022-12-25 22:03:11 +01:00
// includes archived sessions
func GetBareSessions(ctx context.Context) ([]*SessionType, error) {
var rtn []*SessionType
err := WithTx(ctx, func(tx *TxWrap) error {
2022-12-27 01:09:21 +01:00
query := `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
tx.Select(&rtn, query)
return nil
})
if err != nil {
return nil, err
}
return rtn, nil
}
2022-12-27 01:09:21 +01:00
// does not include archived, finds lowest sessionidx (for resetting active session)
func GetFirstSessionId(ctx context.Context) (string, error) {
var rtn []string
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-12-25 22:03:11 +01:00
query := `SELECT sessionid from session WHERE NOT archived ORDER by sessionidx`
rtn = tx.SelectStrings(query)
return nil
})
if txErr != nil {
2022-12-27 01:09:21 +01:00
return "", txErr
}
2022-12-27 01:09:21 +01:00
if len(rtn) == 0 {
return "", nil
}
return rtn[0], nil
}
2022-08-26 22:12:17 +02:00
func GetBareSessionById(ctx context.Context, sessionId string) (*SessionType, error) {
var rtn SessionType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM session WHERE sessionid = ?`
tx.Get(&rtn, query, sessionId)
2022-08-26 22:12:17 +02:00
return nil
})
if txErr != nil {
return nil, txErr
}
if rtn.SessionId == "" {
return nil, nil
}
return &rtn, nil
}
func GetAllSessions(ctx context.Context) (*ModelUpdate, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*ModelUpdate, error) {
update := &ModelUpdate{}
2022-12-27 01:09:21 +01:00
query := `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
tx.Select(&update.Sessions, query)
2022-08-11 03:33:32 +02:00
sessionMap := make(map[string]*SessionType)
for _, session := range update.Sessions {
2022-08-11 03:33:32 +02:00
sessionMap[session.SessionId] = session
session.Full = true
}
2022-12-25 22:21:48 +01:00
query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts`
update.Screens = SelectMapsGen[*ScreenType](tx, query)
2023-03-13 20:10:23 +01:00
for _, screen := range update.Screens {
screen.Full = true
}
query = `SELECT * FROM remote_instance`
riArr := SelectMapsGen[*RemoteInstance](tx, query)
for _, ri := range riArr {
2022-08-11 03:33:32 +02:00
s := sessionMap[ri.SessionId]
if s != nil {
s.Remotes = append(s.Remotes, ri)
}
}
query = `SELECT activesessionid FROM client`
update.ActiveSessionId = tx.GetString(query)
return update, nil
2022-07-12 22:50:44 +02:00
})
}
func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenLinesType, error) {
query := `SELECT sessionid, screenid, windowid FROM screen WHERE screenid = ?`
screen := GetMappable[*ScreenLinesType](tx, query, screenId)
if screen == nil {
return nil, nil
2022-07-12 22:50:44 +02:00
}
query = `SELECT * FROM line WHERE sessionid = ? AND windowid = ? ORDER BY linenum`
tx.Select(&screen.Lines, query, screen.SessionId, screen.WindowId)
2022-07-12 22:50:44 +02:00
query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND windowid = ?)`
screen.Cmds = SelectMapsGen[*CmdType](tx, query, screen.SessionId, screen.WindowId)
return screen, nil
2022-07-12 22:50:44 +02:00
})
2022-07-08 22:23:45 +02:00
}
2022-12-26 21:38:47 +01:00
// includes archived screens (does not include screen windows)
2023-03-13 20:10:23 +01:00
func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) {
return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) {
2022-12-25 22:21:48 +01:00
query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts`
2023-03-13 20:10:23 +01:00
rtn := SelectMapsGen[*ScreenType](tx, query, sessionId)
for _, screen := range rtn {
screen.Full = true
}
return rtn, nil
2022-12-24 00:56:29 +01:00
})
}
2022-07-01 23:07:13 +02:00
func GetSessionById(ctx context.Context, id string) (*SessionType, error) {
allSessionsUpdate, err := GetAllSessions(ctx)
2022-07-01 23:07:13 +02:00
if err != nil {
return nil, err
}
allSessions := allSessionsUpdate.Sessions
for _, session := range allSessions {
if session.SessionId == id {
return session, nil
}
}
return nil, nil
2022-07-01 23:07:13 +02:00
}
2022-07-05 07:18:01 +02:00
func GetSessionByName(ctx context.Context, name string) (*SessionType, error) {
var session *SessionType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT sessionid FROM session WHERE name = ?`
sessionId := tx.GetString(query, name)
if sessionId == "" {
return nil
2022-07-05 07:18:01 +02:00
}
var err error
session, err = GetSessionById(tx.Context(), sessionId)
if err != nil {
return err
}
return nil
})
if txErr != nil {
return nil, txErr
2022-07-05 07:18:01 +02:00
}
return session, nil
2022-07-05 07:18:01 +02:00
}
func InsertCloudSession(ctx context.Context, sessionName string, shareMode string, activate bool) (*ModelUpdate, error) {
var updateRtn *ModelUpdate
txErr := WithTx(ctx, func(tx *TxWrap) error {
var err error
updateRtn, err = InsertSessionWithName(tx.Context(), sessionName, shareMode, activate)
if err != nil {
return err
}
sessionId := updateRtn.Sessions[0].SessionId
fmt.Printf("sessionid: %v\n", sessionId)
return nil
})
if txErr != nil {
return nil, txErr
}
return updateRtn, nil
}
2022-07-08 22:23:45 +02:00
// also creates default window, returns sessionId
// if sessionName == "", it will be generated
func InsertSessionWithName(ctx context.Context, sessionName string, shareMode string, activate bool) (*ModelUpdate, error) {
newSessionId := scbase.GenPromptUUID()
2022-07-08 22:23:45 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
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`)
2023-03-09 02:16:06 +01:00
query := `INSERT INTO session (sessionid, name, activescreenid, sessionidx, notifynum, archived, archivedts, sharemode)
VALUES (?, ?, '', ?, ?, 0, 0, 'local')`
tx.Exec(query, newSessionId, sessionName, maxSessionIdx+1, 0)
2022-07-15 03:39:40 +02:00
_, err := InsertScreen(tx.Context(), newSessionId, "", true)
if err != nil {
return err
2022-07-08 22:23:45 +02:00
}
if activate {
query = `UPDATE client SET activesessionid = ?`
tx.Exec(query, newSessionId)
}
2022-07-01 23:07:13 +02:00
return nil
})
2022-07-15 03:39:40 +02:00
if txErr != nil {
return nil, txErr
}
session, err := GetSessionById(ctx, newSessionId)
if err != nil {
return nil, err
}
update := ModelUpdate{
Sessions: []*SessionType{session},
}
if activate {
update.ActiveSessionId = newSessionId
2022-07-15 03:39:40 +02:00
}
return &update, nil
2022-07-08 22:23:45 +02:00
}
func SetActiveSessionId(ctx context.Context, sessionId string) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-12-27 01:09:21 +01:00
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("cannot switch to session, not found")
}
query = `UPDATE client SET activesessionid = ?`
tx.Exec(query, sessionId)
return nil
})
return txErr
}
2022-12-24 00:56:29 +01:00
func GetActiveSessionId(ctx context.Context) (string, error) {
var rtnId string
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT activesessionid FROM client`
rtnId = tx.GetString(query)
return nil
})
return rtnId, txErr
}
2022-09-25 09:26:33 +02:00
func SetWinSize(ctx context.Context, winSize ClientWinSizeType) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE client SET winsize = ?`
tx.Exec(query, quickJson(winSize))
2022-09-25 09:26:33 +02:00
return nil
})
return txErr
}
func UpdateClientFeOpts(ctx context.Context, feOpts FeOptsType) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE client SET feopts = ?`
tx.Exec(query, quickJson(feOpts))
return nil
})
return txErr
}
2022-07-08 22:23:45 +02:00
func containsStr(strs []string, testStr string) bool {
for _, s := range strs {
if s == testStr {
return true
}
}
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, origScreenName string, activate bool) (UpdatePacket, error) {
var newScreenId string
2022-07-08 22:23:45 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-12-25 22:03:11 +01:00
query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT archived`
if !tx.Exists(query, sessionId) {
2022-12-27 01:09:21 +01:00
return fmt.Errorf("cannot create screen, no session found (or session archived)")
2022-07-08 22:23:45 +02:00
}
localRemoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, LocalRemoteAlias)
if localRemoteId == "" {
return fmt.Errorf("cannot create screen, no local remote found")
}
newWindowId := scbase.GenPromptUUID()
2022-12-25 22:03:11 +01:00
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
2022-12-26 21:18:13 +01:00
var screenName string
if origScreenName == "" {
screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
screenName = fmtUniqueName("", "s%d", maxScreenIdx+1, screenNames)
} else {
screenName = origScreenName
}
newScreenId = scbase.GenPromptUUID()
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())
2022-07-15 03:39:40 +02:00
if activate {
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
tx.Exec(query, newScreenId, sessionId)
2022-07-15 03:39:40 +02:00
}
2022-07-08 22:23:45 +02:00
return nil
})
2022-12-27 01:09:21 +01:00
if txErr != nil {
return nil, txErr
}
newScreen, err := GetScreenById(ctx, newScreenId)
2022-07-15 10:57:45 +02:00
if err != nil {
2022-07-16 02:53:23 +02:00
return nil, err
2022-07-15 10:57:45 +02:00
}
update := ModelUpdate{Screens: []*ScreenType{newScreen}}
if activate {
bareSession, err := GetBareSessionById(ctx, sessionId)
if err != nil {
return nil, txErr
}
update.Sessions = []*SessionType{bareSession}
2022-07-15 10:57:45 +02:00
}
return update, nil
2022-07-01 23:07:13 +02:00
}
2022-07-02 22:31:56 +02:00
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)
2023-03-13 20:10:23 +01:00
screen.Full = true
return screen, nil
2022-07-15 03:39:40 +02:00
})
2022-07-12 22:50:44 +02:00
}
2022-09-21 02:37:49 +02:00
func FindLineIdByArg(ctx context.Context, sessionId string, windowId string, lineArg string) (string, error) {
var lineId string
txErr := WithTx(ctx, func(tx *TxWrap) error {
lineNum, err := strconv.Atoi(lineArg)
if err == nil {
// valid linenum
query := `SELECT lineid FROM line WHERE sessionid = ? AND windowid = ? AND linenum = ?`
lineId = tx.GetString(query, sessionId, windowId, lineNum)
} else if len(lineArg) == 8 {
// prefix id string match
query := `SELECT lineid FROM line WHERE sessionid = ? AND windowid = ? AND substr(lineid, 1, 8) = ?`
lineId = tx.GetString(query, sessionId, windowId, lineArg)
} else {
// id match
query := `SELECT lineid FROM line WHERE sessionid = ? AND windowid = ? AND lineid = ?`
2022-09-21 02:37:49 +02:00
lineId = tx.GetString(query, sessionId, windowId, lineArg)
}
return nil
})
if txErr != nil {
return "", txErr
}
return lineId, nil
}
func GetLineCmdByLineId(ctx context.Context, sessionId string, windowId string, lineId string) (*LineType, *CmdType, error) {
return WithTxRtn3(ctx, func(tx *TxWrap) (*LineType, *CmdType, error) {
2022-09-21 02:37:49 +02:00
var lineVal LineType
query := `SELECT * FROM line WHERE sessionid = ? AND windowid = ? AND lineid = ?`
found := tx.Get(&lineVal, query, sessionId, windowId, lineId)
2022-09-21 02:37:49 +02:00
if !found {
return nil, nil, nil
2022-09-21 02:37:49 +02:00
}
var cmdRtn *CmdType
2022-09-21 02:37:49 +02:00
if lineVal.CmdId != "" {
query = `SELECT * FROM cmd WHERE sessionid = ? AND cmdid = ?`
cmdRtn = GetMapGen[*CmdType](tx, query, sessionId, lineVal.CmdId)
2022-09-21 02:37:49 +02:00
}
return &lineVal, cmdRtn, nil
2022-09-21 02:37:49 +02:00
})
}
func GetLineCmdByCmdId(ctx context.Context, sessionId string, windowId string, cmdId string) (*LineType, *CmdType, error) {
return WithTxRtn3(ctx, func(tx *TxWrap) (*LineType, *CmdType, error) {
var lineVal LineType
query := `SELECT * FROM line WHERE sessionid = ? AND windowid = ? AND cmdid = ?`
found := tx.Get(&lineVal, query, sessionId, windowId, cmdId)
if !found {
return nil, nil, nil
}
query = `SELECT * FROM cmd WHERE sessionid = ? AND cmdid = ?`
cmdRtn := GetMapGen[*CmdType](tx, query, sessionId, cmdId)
return &lineVal, cmdRtn, nil
})
}
func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error {
2022-07-02 22:31:56 +02:00
if line == nil {
return fmt.Errorf("line cannot be nil")
}
if line.LineId == "" {
return fmt.Errorf("line must have lineid set")
2022-07-02 22:31:56 +02:00
}
if line.LineNum != 0 {
return fmt.Errorf("line should not hage linenum set")
}
2022-07-02 22:31:56 +02:00
return WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT screenid FROM screen WHERE sessionid = ? AND windowid = ?`
2022-09-21 02:37:49 +02:00
if !tx.Exists(query, line.SessionId, line.WindowId) {
return fmt.Errorf("screen not found, cannot insert line[%s/%s]", line.SessionId, line.WindowId)
2022-07-02 22:31:56 +02:00
}
query = `SELECT nextlinenum FROM screen WHERE sessionid = ? AND windowid = ?`
nextLineNum := tx.GetInt(query, line.SessionId, line.WindowId)
line.LineNum = int64(nextLineNum)
2023-02-21 06:39:29 +01:00
query = `INSERT INTO line ( sessionid, windowid, userid, lineid, ts, linenum, linenumtemp, linelocal, linetype, text, cmdid, renderer, ephemeral, contentheight, star, archived, bookmarked, pinned)
VALUES (:sessionid,:windowid,:userid,:lineid,:ts,:linenum,:linenumtemp,:linelocal,:linetype,:text,:cmdid,:renderer,:ephemeral,:contentheight,:star,:archived,:bookmarked,:pinned)`
tx.NamedExec(query, line)
query = `UPDATE screen SET nextlinenum = ? WHERE sessionid = ? AND windowid = ?`
tx.Exec(query, nextLineNum+1, line.SessionId, line.WindowId)
if cmd != nil {
cmd.OrigTermOpts = cmd.TermOpts
cmdMap := cmd.ToMap()
query = `
INSERT INTO cmd ( sessionid, cmdid, remoteownerid, remoteid, remotename, cmdstr, festate, statebasehash, statediffhasharr, termopts, origtermopts, status, startpk, doneinfo, rtnstate, runout, rtnbasehash, rtndiffhasharr)
VALUES (:sessionid,:cmdid,:remoteownerid,:remoteid,:remotename,:cmdstr,:festate,:statebasehash,:statediffhasharr,:termopts,:origtermopts,:status,:startpk,:doneinfo,:rtnstate,:runout,:rtnbasehash,:rtndiffhasharr)
2022-07-07 09:10:37 +02:00
`
tx.NamedExec(query, cmdMap)
}
2022-07-07 09:10:37 +02:00
return nil
})
}
func GetCmdById(ctx context.Context, sessionId string, cmdId string) (*CmdType, error) {
var cmd *CmdType
err := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM cmd WHERE sessionid = ? AND cmdid = ?`
cmd = GetMapGen[*CmdType](tx, query, sessionId, cmdId)
return nil
})
2022-07-07 09:10:37 +02:00
if err != nil {
return nil, err
}
return cmd, nil
2022-07-07 09:10:37 +02:00
}
func HasDoneInfo(ctx context.Context, ck base.CommandKey) (bool, error) {
var found bool
txErr := WithTx(ctx, func(tx *TxWrap) error {
found = tx.Exists(`SELECT sessionid FROM cmd WHERE sessionid = ? AND cmdid = ? AND doneinfo is NOT NULL`, ck.GetSessionId(), ck.GetCmdId())
return nil
})
if txErr != nil {
return false, txErr
}
return found, nil
}
func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, doneInfo *CmdDoneInfo) (*ModelUpdate, error) {
if doneInfo == nil {
return nil, fmt.Errorf("invalid cmddone packet")
}
if ck.IsEmpty() {
return nil, fmt.Errorf("cannot update cmddoneinfo, empty ck")
}
2022-08-20 02:14:53 +02:00
var rtnCmd *CmdType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ?, doneinfo = ? WHERE sessionid = ? AND cmdid = ?`
tx.Exec(query, CmdStatusDone, quickJson(doneInfo), ck.GetSessionId(), ck.GetCmdId())
2022-08-20 02:14:53 +02:00
var err error
rtnCmd, err = GetCmdById(tx.Context(), ck.GetSessionId(), ck.GetCmdId())
2022-08-20 02:14:53 +02:00
if err != nil {
return err
}
return nil
})
2022-08-20 02:14:53 +02:00
if txErr != nil {
return nil, txErr
}
if rtnCmd == nil {
return nil, fmt.Errorf("cmd data not found for ck[%s]", ck)
2022-08-20 02:14:53 +02:00
}
return &ModelUpdate{Cmd: rtnCmd}, nil
}
func UpdateCmdRtnState(ctx context.Context, ck base.CommandKey, statePtr ShellStatePtr) error {
if ck.IsEmpty() {
return fmt.Errorf("cannot update cmdrtnstate, empty ck")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET rtnbasehash = ?, rtndiffhasharr = ? WHERE sessionid = ? AND cmdid = ?`
tx.Exec(query, statePtr.BaseHash, quickJsonArr(statePtr.DiffHashArr), ck.GetSessionId(), ck.GetCmdId())
return nil
})
if txErr != nil {
return txErr
}
return nil
}
func AppendCmdErrorPk(ctx context.Context, errPk *packet.CmdErrorPacketType) error {
if errPk == nil || errPk.CK.IsEmpty() {
return fmt.Errorf("invalid cmderror packet (no ck)")
}
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET runout = json_insert(runout, '$[#]', ?) WHERE sessionid = ? AND cmdid = ?`
tx.Exec(query, quickJson(errPk), errPk.CK.GetSessionId(), errPk.CK.GetCmdId())
return nil
})
}
2023-01-12 05:53:46 +01:00
func ReInitFocus(ctx context.Context) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE screen SET focustype = 'input'`
tx.Exec(query)
2023-01-12 05:53:46 +01:00
return nil
})
}
func HangupAllRunningCmds(ctx context.Context) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ? WHERE status = ?`
tx.Exec(query, CmdStatusHangup, CmdStatusRunning)
return nil
})
}
func HangupRunningCmdsByRemoteId(ctx context.Context, remoteId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ? WHERE status = ? AND remoteid = ?`
tx.Exec(query, CmdStatusHangup, CmdStatusRunning, remoteId)
return nil
})
}
2022-07-15 03:39:40 +02:00
func HangupCmd(ctx context.Context, ck base.CommandKey) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ? WHERE sessionid = ? AND cmdid = ?`
tx.Exec(query, CmdStatusHangup, ck.GetSessionId(), ck.GetCmdId())
return nil
})
}
2022-07-15 10:57:45 +02:00
func getNextId(ids []string, delId string) string {
if len(ids) == 0 {
return ""
}
if len(ids) == 1 {
if ids[0] == delId {
return ""
}
return ids[0]
}
for idx := 0; idx < len(ids); idx++ {
if ids[idx] == delId {
var rtnIdx int
if idx == len(ids)-1 {
rtnIdx = idx - 1
} else {
rtnIdx = idx + 1
}
return ids[rtnIdx]
}
}
return ids[0]
}
2023-03-03 19:15:57 +01:00
func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (*ModelUpdate, error) {
2022-07-15 03:39:40 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-12-26 21:18:13 +01:00
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?`
2022-07-15 03:39:40 +02:00
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.Exec(query, screenId, sessionId)
2022-07-15 03:39:40 +02:00
return nil
})
2022-12-27 01:09:21 +01:00
if txErr != nil {
return nil, txErr
}
bareSession, err := GetBareSessionById(ctx, sessionId)
if err != nil {
return nil, err
}
2023-03-03 19:15:57 +01:00
return &ModelUpdate{ActiveSessionId: sessionId, Sessions: []*SessionType{bareSession}}, nil
2022-07-15 03:39:40 +02:00
}
2022-07-15 10:57:45 +02:00
2023-01-02 21:09:01 +01:00
func cleanSessionCmds(ctx context.Context, sessionId string) error {
2023-01-25 23:29:12 +01:00
var removedCmds []string
txErr := WithTx(ctx, func(tx *TxWrap) error {
2023-01-02 21:09:01 +01:00
query := `SELECT cmdid FROM cmd WHERE sessionid = ? AND cmdid NOT IN (SELECT cmdid FROM line WHERE sessionid = ?)`
2023-01-25 23:29:12 +01:00
removedCmds = tx.SelectStrings(query, sessionId, sessionId)
2023-01-02 21:09:01 +01:00
query = `DELETE FROM cmd WHERE sessionid = ? AND cmdid NOT IN (SELECT cmdid FROM line WHERE sessionid = ?)`
tx.Exec(query, sessionId, sessionId)
2023-02-21 06:39:29 +01:00
query = `DELETE FROM bookmark_cmd WHERE sessionid = ? AND cmdid NOT IN (SELECT cmdid FROM cmd WHERE sessionid = ?)`
tx.Exec(query, sessionId, sessionId)
2023-01-02 21:09:01 +01:00
return nil
})
if txErr != nil {
return txErr
}
2023-01-25 23:29:12 +01:00
for _, cmdId := range removedCmds {
DeletePtyOutFile(ctx, sessionId, cmdId)
}
2023-01-02 21:09:01 +01:00
return nil
}
func CleanWindow(sessionId string, windowId string) {
2023-01-25 23:29:12 +01:00
// NOTE: context.Background() here! (this could take a long time, and is async)
2022-12-25 22:03:11 +01:00
txErr := WithTx(context.Background(), func(tx *TxWrap) error {
query := `DELETE FROM history WHERE sessionid = ? AND windowid = ?`
tx.Exec(query, sessionId, windowId)
query = `DELETE FROM line WHERE sessionid = ? AND windowid = ?`
tx.Exec(query, sessionId, windowId)
2023-01-02 21:09:01 +01:00
return cleanSessionCmds(tx.Context(), sessionId)
2022-12-25 22:03:11 +01:00
})
if txErr != nil {
fmt.Printf("ERROR cleaning session:%s window:%s : %v\n", sessionId, windowId, txErr)
2022-12-25 22:03:11 +01:00
}
2022-07-15 10:57:45 +02:00
}
2022-12-25 22:21:48 +01:00
func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) {
var isActive bool
2022-12-24 00:56:29 +01:00
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 close screen (not found)")
}
2022-12-25 22:03:11 +01:00
query = `SELECT archived FROM screen WHERE sessionid = ? AND screenid = ?`
2022-12-24 00:56:29 +01:00
closeVal := tx.GetBool(query, sessionId, screenId)
if closeVal {
return nil
}
2022-12-25 22:03:11 +01:00
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
2022-12-24 00:56:29 +01:00
numScreens := tx.GetInt(query, sessionId)
if numScreens <= 1 {
2022-12-26 21:18:13 +01:00
return fmt.Errorf("cannot archive the last screen in a session")
2022-12-24 00:56:29 +01:00
}
2022-12-25 22:21:48 +01:00
query = `UPDATE screen SET archived = 1, archivedts = ?, screenidx = 0 WHERE sessionid = ? AND screenid = ?`
tx.Exec(query, time.Now().UnixMilli(), sessionId, screenId)
isActive = tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, sessionId, screenId)
2022-12-24 00:56:29 +01:00
if isActive {
2022-12-25 22:03:11 +01:00
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, sessionId)
2022-12-24 00:56:29 +01:00
nextId := getNextId(screenIds, screenId)
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
2022-12-24 00:56:29 +01:00
}
return nil
})
if txErr != nil {
return nil, txErr
}
newScreen, err := GetScreenById(ctx, screenId)
2022-12-27 01:09:21 +01:00
if err != nil {
return nil, fmt.Errorf("cannot retrive archived screen: %w", err)
2022-12-27 01:09:21 +01:00
}
update := ModelUpdate{Screens: []*ScreenType{newScreen}}
if isActive {
bareSession, err := GetBareSessionById(ctx, sessionId)
if err != nil {
return nil, err
}
update.Sessions = []*SessionType{bareSession}
2022-12-26 21:38:47 +01:00
}
2022-12-24 00:56:29 +01:00
return update, nil
}
2022-12-25 22:21:48 +01:00
func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) error {
2022-12-24 00:56:29 +01:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-12-25 22:03:11 +01:00
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ? AND archived`
2022-12-24 00:56:29 +01:00
if !tx.Exists(query, sessionId, screenId) {
2022-12-25 22:03:11 +01:00
return fmt.Errorf("cannot re-open screen (not found or not archived)")
2022-12-24 00:56:29 +01:00
}
2022-12-25 22:03:11 +01:00
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
2022-12-26 21:18:13 +01:00
query = `UPDATE screen SET archived = 0, screenidx = ? WHERE sessionid = ? AND screenid = ?`
tx.Exec(query, maxScreenIdx+1, sessionId, screenId)
2022-12-24 00:56:29 +01:00
return nil
})
return txErr
}
func DeleteScreen(ctx context.Context, screenId string) (UpdatePacket, error) {
var swkeys SWKeys
var isActive bool
2022-07-15 10:57:45 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT screenid FROM screen WHERE screenid = ?`
if !tx.Exists(query, screenId) {
2022-12-25 22:03:11 +01:00
return fmt.Errorf("cannot purge screen (not found)")
}
query = `SELECT sessionid, windowid FROM screen WHERE screenid = ?`
tx.Get(&swkeys, query, screenId)
if swkeys.SessionId == "" || swkeys.WindowId == "" {
return fmt.Errorf("cannot purge screen (no windowid)")
}
2022-12-25 22:03:11 +01:00
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
numScreens := tx.GetInt(query, swkeys.SessionId)
2022-12-25 22:03:11 +01:00
if numScreens <= 1 {
return fmt.Errorf("cannot purge the last screen in a session")
}
isActive = tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, swkeys.SessionId, screenId)
2022-07-15 10:57:45 +02:00
if isActive {
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, swkeys.SessionId)
2022-07-15 10:57:45 +02:00
nextId := getNextId(screenIds, screenId)
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, swkeys.SessionId)
2022-07-15 10:57:45 +02:00
}
query = `DELETE FROM screen WHERE screenid = ?`
tx.Exec(query, screenId)
2022-07-15 10:57:45 +02:00
return nil
})
if txErr != nil {
2022-07-16 02:53:23 +02:00
return nil, txErr
2022-07-15 10:57:45 +02:00
}
go CleanWindow(swkeys.SessionId, swkeys.WindowId)
update := ModelUpdate{}
update.Screens = []*ScreenType{&ScreenType{SessionId: swkeys.SessionId, ScreenId: screenId, Remove: true}}
if isActive {
bareSession, err := GetBareSessionById(ctx, swkeys.SessionId)
if err != nil {
return nil, err
}
update.Sessions = []*SessionType{bareSession}
2022-12-27 01:09:21 +01:00
}
2022-12-28 22:56:19 +01:00
return update, nil
2022-07-15 10:57:45 +02:00
}
func GetRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*packet.ShellState, *ShellStatePtr, error) {
ssptr, err := GetRemoteStatePtr(ctx, sessionId, windowId, remotePtr)
if err != nil {
return nil, nil, err
}
if ssptr == nil {
return nil, nil, nil
}
state, err := GetFullState(ctx, *ssptr)
if err != nil {
return nil, nil, err
}
return state, ssptr, err
}
func GetRemoteStatePtr(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*ShellStatePtr, error) {
var ssptr *ShellStatePtr
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-11-28 09:13:00 +01:00
ri, err := GetRemoteInstance(tx.Context(), sessionId, windowId, remotePtr)
if err != nil {
return err
}
if ri == nil {
return nil
}
ssptr = &ShellStatePtr{ri.StateBaseHash, ri.StateDiffHashArr}
return nil
})
2022-11-28 09:13:00 +01:00
if txErr != nil {
return nil, txErr
}
return ssptr, nil
}
2022-08-09 23:24:57 +02:00
func validateSessionWindow(tx *TxWrap, sessionId string, windowId string) error {
if windowId == "" {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("no session found")
}
return nil
} else {
query := `SELECT windowid FROM screen WHERE sessionid = ? AND windowid = ?`
2022-08-09 23:24:57 +02:00
if !tx.Exists(query, sessionId, windowId) {
return fmt.Errorf("no screen found")
}
return nil
}
}
2022-11-28 09:13:00 +01:00
func GetRemoteInstance(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*RemoteInstance, error) {
if remotePtr.IsSessionScope() {
windowId = ""
}
var ri *RemoteInstance
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
ri = GetMapGen[*RemoteInstance](tx, query, sessionId, windowId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
2022-11-28 09:13:00 +01:00
return nil
})
if txErr != nil {
return nil, txErr
}
return ri, nil
}
// internal function for UpdateRemoteState
func updateRIWithState(ctx context.Context, ri *RemoteInstance, stateBase *packet.ShellState, stateDiff *packet.ShellStateDiff) error {
if stateBase != nil {
ri.StateBaseHash = stateBase.GetHashVal(false)
ri.StateDiffHashArr = nil
2022-11-28 09:13:00 +01:00
err := StoreStateBase(ctx, stateBase)
if err != nil {
return err
}
} else if stateDiff != nil {
ri.StateBaseHash = stateDiff.BaseHash
ri.StateDiffHashArr = append(stateDiff.DiffHashArr, stateDiff.GetHashVal(false))
err := StoreStateDiff(ctx, stateDiff)
if err != nil {
return err
}
}
return nil
}
func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType, feState FeStateType, stateBase *packet.ShellState, stateDiff *packet.ShellStateDiff) (*RemoteInstance, error) {
if stateBase == nil && stateDiff == nil {
return nil, fmt.Errorf("UpdateRemoteState, must set state or diff")
}
if stateBase != nil && stateDiff != nil {
return nil, fmt.Errorf("UpdateRemoteState, cannot set state and diff")
}
if remotePtr.IsSessionScope() {
windowId = ""
}
var ri *RemoteInstance
txErr := WithTx(ctx, func(tx *TxWrap) error {
err := validateSessionWindow(tx, sessionId, windowId)
if err != nil {
2022-11-28 09:13:00 +01:00
return fmt.Errorf("cannot update remote instance state: %w", err)
2022-08-09 23:24:57 +02:00
}
2022-08-24 22:21:54 +02:00
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
ri = GetMapGen[*RemoteInstance](tx, query, sessionId, windowId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
if ri == nil {
ri = &RemoteInstance{
RIId: scbase.GenPromptUUID(),
2022-08-24 22:21:54 +02:00
Name: remotePtr.Name,
SessionId: sessionId,
WindowId: windowId,
RemoteOwnerId: remotePtr.OwnerId,
RemoteId: remotePtr.RemoteId,
2022-11-28 09:13:00 +01:00
FeState: feState,
}
err = updateRIWithState(tx.Context(), ri, stateBase, stateDiff)
if err != nil {
return err
2022-08-09 23:24:57 +02:00
}
2022-11-28 09:13:00 +01:00
query = `INSERT INTO remote_instance ( riid, name, sessionid, windowid, remoteownerid, remoteid, festate, statebasehash, statediffhasharr)
VALUES (:riid,:name,:sessionid,:windowid,:remoteownerid,:remoteid,:festate,:statebasehash,:statediffhasharr)`
tx.NamedExec(query, ri.ToMap())
2022-08-09 23:24:57 +02:00
return nil
2022-11-28 09:13:00 +01:00
} else {
query = `UPDATE remote_instance SET festate = ?, statebasehash = ?, statediffhasharr = ? WHERE riid = ?`
2022-11-28 09:13:00 +01:00
ri.FeState = feState
err = updateRIWithState(tx.Context(), ri, stateBase, stateDiff)
if err != nil {
return err
}
tx.Exec(query, quickJson(ri.FeState), ri.StateBaseHash, quickJsonArr(ri.StateDiffHashArr), ri.RIId)
2022-11-28 09:13:00 +01:00
return nil
2022-08-09 23:24:57 +02:00
}
})
return ri, txErr
2022-08-09 23:24:57 +02:00
}
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 screen SET curremoteownerid = ?, curremoteid = ?, curremotename = ? WHERE screenid = ?`
tx.Exec(query, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name, screenId)
return nil
})
}
2022-08-26 22:12:17 +02:00
func reorderStrings(strs []string, toMove string, newIndex int) []string {
if toMove == "" {
return strs
}
var newStrs []string
if newIndex < 0 {
newStrs = append(newStrs, toMove)
}
for _, sval := range strs {
if len(newStrs) == newIndex {
newStrs = append(newStrs, toMove)
}
if sval != toMove {
newStrs = append(newStrs, sval)
}
}
if newIndex >= len(newStrs) {
newStrs = append(newStrs, toMove)
}
return newStrs
}
func ReIndexSessions(ctx context.Context, sessionId string, newIndex int) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
2022-12-27 01:09:21 +01:00
query := `SELECT sessionid FROM session WHERE NOT archived ORDER BY sessionidx, name, sessionid`
2022-08-26 22:12:17 +02:00
ids := tx.SelectStrings(query)
if sessionId != "" {
ids = reorderStrings(ids, sessionId, newIndex)
}
query = `UPDATE session SET sessionid = ? WHERE sessionid = ?`
2022-08-26 22:12:17 +02:00
for idx, id := range ids {
tx.Exec(query, id, idx+1)
2022-08-26 22:12:17 +02:00
}
return nil
})
return txErr
}
func SetSessionName(ctx context.Context, sessionId string, name string) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
2022-08-26 22:12:17 +02:00
if !tx.Exists(query, sessionId) {
return fmt.Errorf("session does not exist")
}
2022-12-27 01:09:21 +01:00
query = `SELECT archived FROM session WHERE sessionid = ?`
isArchived := tx.GetBool(query, sessionId)
if !isArchived {
query = `SELECT sessionid FROM session WHERE name = ? AND NOT archived`
dupSessionId := tx.GetString(query, name)
if dupSessionId == sessionId {
return nil
}
if dupSessionId != "" {
return fmt.Errorf("invalid duplicate session name '%s'", name)
}
}
query = `UPDATE session SET name = ? WHERE sessionid = ?`
tx.Exec(query, name, sessionId)
2022-08-26 22:12:17 +02:00
return nil
})
return txErr
}
2022-08-27 02:51:28 +02:00
func SetScreenName(ctx context.Context, sessionId string, screenId string, name 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("screen does not exist")
}
query = `UPDATE screen SET name = ? WHERE sessionid = ? AND screenid = ?`
tx.Exec(query, name, sessionId, screenId)
2022-08-27 02:51:28 +02:00
return nil
})
return txErr
}
2022-08-27 06:44:18 +02:00
func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) {
2022-08-27 06:44:18 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
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")
2023-01-02 21:09:01 +01:00
}
query = `UPDATE line SET archived = 1 WHERE sessionid = ? AND windowid = ?`
tx.Exec(query, swkeys.SessionId, swkeys.WindowId)
2023-01-02 21:09:01 +01:00
return nil
})
if txErr != nil {
return nil, txErr
}
screenLines, err := GetScreenLinesById(ctx, screenId)
2023-01-02 21:09:01 +01:00
if err != nil {
return nil, err
}
return &ModelUpdate{ScreenLines: screenLines}, nil
2023-01-02 21:09:01 +01:00
}
func PurgeScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) {
2022-08-27 07:01:29 +02:00
var lineIds []string
var swkeys SWKeys
2022-08-27 07:01:29 +02:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
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")
2022-08-27 07:01:29 +02:00
}
query = `SELECT lineid FROM line WHERE sessionid = ? AND windowid = ?`
lineIds = tx.SelectStrings(query, swkeys.SessionId, swkeys.WindowId)
2022-08-27 07:01:29 +02:00
query = `DELETE FROM line WHERE sessionid = ? AND windowid = ?`
tx.Exec(query, swkeys.SessionId, swkeys.WindowId)
2023-01-02 21:09:01 +01:00
query = `DELETE FROM history WHERE sessionid = ? AND windowid = ?`
tx.Exec(query, swkeys.SessionId, swkeys.WindowId)
query = `UPDATE screen SET nextlinenum = 1 WHERE screenid = ?`
tx.Exec(query, screenId)
2022-08-27 07:01:29 +02:00
return nil
})
if txErr != nil {
return nil, txErr
}
go cleanSessionCmds(context.Background(), swkeys.SessionId)
screen, err := GetScreenById(ctx, screenId)
if err != nil {
return nil, err
}
screenLines, err := GetScreenLinesById(ctx, screenId)
2022-08-27 07:01:29 +02:00
if err != nil {
return nil, err
}
for _, lineId := range lineIds {
line := &LineType{
SessionId: swkeys.SessionId,
WindowId: swkeys.WindowId,
2022-08-27 07:01:29 +02:00
LineId: lineId,
Remove: true,
}
screenLines.Lines = append(screenLines.Lines, line)
2022-08-27 07:01:29 +02:00
}
return &ModelUpdate{Screens: []*ScreenType{screen}, ScreenLines: screenLines}, nil
2022-08-27 07:01:29 +02:00
}
func GetRunningWindowCmds(ctx context.Context, sessionId string, windowId string) ([]*CmdType, error) {
var rtn []*CmdType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * from cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND windowid = ?) AND status = ?`
rtn = SelectMapsGen[*CmdType](tx, query, sessionId, windowId, CmdStatusRunning)
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
func UpdateCmdTermOpts(ctx context.Context, sessionId string, cmdId string, termOpts TermOpts) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET termopts = ? WHERE sessionid = ? AND cmdid = ?`
tx.Exec(query, termOpts, sessionId, cmdId)
return nil
})
return txErr
}
2022-09-13 21:06:12 +02:00
// returns riids of deleted RIs
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, swkeys.SessionId, swkeys.WindowId)
var delRis []*RemoteInstance
for _, riid := range riids {
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, swkeys.SessionId, swkeys.WindowId)
return delRis, nil
})
}
2022-12-27 01:09:21 +01:00
func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) {
var newActiveSessionId string
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("session does not exist")
}
query = `DELETE FROM session WHERE sessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 01:09:21 +01:00
query = `DELETE FROM screen WHERE sessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 01:09:21 +01:00
query = `DELETE FROM history WHERE sessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 01:09:21 +01:00
query = `DELETE FROM line WHERE sessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 01:09:21 +01:00
query = `DELETE FROM cmd WHERE sessionid = ?`
tx.Exec(query, sessionId)
2023-02-21 06:39:29 +01:00
query = `DELETE FROM bookmark_cmd WHERE sessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 01:09:21 +01:00
newActiveSessionId, _ = fixActiveSessionId(tx.Context())
return nil
})
if txErr != nil {
return nil, txErr
}
delErr := DeleteSessionDir(ctx, sessionId)
update := ModelUpdate{}
if newActiveSessionId != "" {
update.ActiveSessionId = newActiveSessionId
}
if delErr != nil {
update.Info = &InfoMsgType{
InfoMsg: fmt.Sprintf("error removing session files: %v", delErr),
}
}
update.Sessions = append(update.Sessions, &SessionType{SessionId: sessionId, Remove: true})
return update, nil
}
func fixActiveSessionId(ctx context.Context) (string, error) {
var newActiveSessionId string
txErr := WithTx(ctx, func(tx *TxWrap) error {
curActiveSessionId := tx.GetString("SELECT activesessionid FROM client")
query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT archived`
if tx.Exists(query, curActiveSessionId) {
return nil
}
var err error
newActiveSessionId, err = GetFirstSessionId(tx.Context())
if err != nil {
return err
}
tx.Exec("UPDATE client SET activesessionid = ?", newActiveSessionId)
2022-12-27 01:09:21 +01:00
return nil
})
if txErr != nil {
return "", txErr
}
return newActiveSessionId, nil
}
2022-12-27 03:42:55 +01:00
func ArchiveSession(ctx context.Context, sessionId string) (*ModelUpdate, error) {
2022-12-27 01:09:21 +01:00
if sessionId == "" {
return nil, fmt.Errorf("invalid blank sessionid")
}
var newActiveSessionId string
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("session does not exist")
}
query = `SELECT archived FROM session WHERE sessionid = ?`
isArchived := tx.GetBool(query, sessionId)
if isArchived {
return nil
}
query = `UPDATE session SET archived = 1, archivedts = ? WHERE sessionid = ?`
tx.Exec(query, time.Now().UnixMilli(), sessionId)
2022-12-27 01:09:21 +01:00
newActiveSessionId, _ = fixActiveSessionId(tx.Context())
return nil
})
if txErr != nil {
return nil, txErr
}
bareSession, _ := GetBareSessionById(ctx, sessionId)
2022-12-27 03:42:55 +01:00
update := &ModelUpdate{}
2022-12-27 01:09:21 +01:00
if bareSession != nil {
update.Sessions = append(update.Sessions, bareSession)
}
if newActiveSessionId != "" {
update.ActiveSessionId = newActiveSessionId
}
return update, nil
}
2022-12-27 03:42:55 +01:00
func UnArchiveSession(ctx context.Context, sessionId string, activate bool) (*ModelUpdate, error) {
if sessionId == "" {
return nil, fmt.Errorf("invalid blank sessionid")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("session does not exist")
}
query = `SELECT archived FROM session WHERE sessionid = ?`
isArchived := tx.GetBool(query, sessionId)
if !isArchived {
return nil
}
query = `UPDATE session SET archived = 0, archivedts = 0 WHERE sessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 03:42:55 +01:00
if activate {
query = `UPDATE client SET activesessionid = ?`
tx.Exec(query, sessionId)
2022-12-27 03:42:55 +01:00
}
return nil
})
if txErr != nil {
return nil, txErr
}
bareSession, _ := GetBareSessionById(ctx, sessionId)
update := &ModelUpdate{}
if bareSession != nil {
update.Sessions = append(update.Sessions, bareSession)
}
if activate {
update.ActiveSessionId = sessionId
}
return update, nil
2022-09-13 21:06:12 +02:00
}
2022-09-20 23:15:20 +02:00
func GetSessionStats(ctx context.Context, sessionId string) (*SessionStatsType, error) {
rtn := &SessionStatsType{SessionId: sessionId}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("not found")
}
2022-12-25 22:03:11 +01:00
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
2022-09-20 23:15:20 +02:00
rtn.NumScreens = tx.GetInt(query, sessionId)
2022-12-25 22:03:11 +01:00
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND archived`
rtn.NumArchivedScreens = tx.GetInt(query, sessionId)
2022-09-20 23:15:20 +02:00
query = `SELECT count(*) FROM line WHERE sessionid = ?`
rtn.NumLines = tx.GetInt(query, sessionId)
query = `SELECT count(*) FROM cmd WHERE sessionid = ?`
rtn.NumCmds = tx.GetInt(query, sessionId)
return nil
})
if txErr != nil {
return nil, txErr
}
diskSize, err := SessionDiskSize(sessionId)
if err != nil {
return nil, err
}
rtn.DiskStats = diskSize
return rtn, nil
}
2022-10-03 03:52:55 +02:00
const (
RemoteField_Alias = "alias" // string
RemoteField_ConnectMode = "connectmode" // string
RemoteField_AutoInstall = "autoinstall" // bool
RemoteField_SSHKey = "sshkey" // string
RemoteField_SSHPassword = "sshpassword" // string
RemoteField_Color = "color" // string
)
// editMap: alias, connectmode, autoinstall, sshkey, color, sshpassword (from constants)
func UpdateRemote(ctx context.Context, remoteId string, editMap map[string]interface{}) (*RemoteType, error) {
var rtn *RemoteType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT remoteid FROM remote WHERE remoteid = ?`
if !tx.Exists(query, remoteId) {
return fmt.Errorf("remote not found")
}
if alias, found := editMap[RemoteField_Alias]; found {
query = `SELECT remoteid FROM remote WHERE remotealias = ? AND remoteid <> ?`
2022-10-04 04:04:48 +02:00
if alias != "" && tx.Exists(query, alias, remoteId) {
2022-10-03 03:52:55 +02:00
return fmt.Errorf("remote has duplicate alias, cannot update")
}
query = `UPDATE remote SET remotealias = ? WHERE remoteid = ?`
tx.Exec(query, alias, remoteId)
2022-10-03 03:52:55 +02:00
}
if mode, found := editMap[RemoteField_ConnectMode]; found {
query = `UPDATE remote SET connectmode = ? WHERE remoteid = ?`
tx.Exec(query, mode, remoteId)
2022-10-03 03:52:55 +02:00
}
if autoInstall, found := editMap[RemoteField_AutoInstall]; found {
query = `UPDATE remote SET autoinstall = ? WHERE remoteid = ?`
tx.Exec(query, autoInstall, remoteId)
2022-10-03 03:52:55 +02:00
}
if sshKey, found := editMap[RemoteField_SSHKey]; found {
query = `UPDATE remote SET sshopts = json_set(sshopts, '$.sshidentity', ?) WHERE remoteid = ?`
tx.Exec(query, sshKey, remoteId)
2022-10-03 03:52:55 +02:00
}
if sshPassword, found := editMap[RemoteField_SSHPassword]; found {
query = `UPDATE remote SET sshopts = json_set(sshopts, '$.sshpassword', ?) WHERE remoteid = ?`
tx.Exec(query, sshPassword, remoteId)
2022-10-03 03:52:55 +02:00
}
if color, found := editMap[RemoteField_Color]; found {
query = `UPDATE remote SET remoteopts = json_set(remoteopts, '$.color', ?) WHERE remoteid = ?`
tx.Exec(query, color, remoteId)
2022-10-03 03:52:55 +02:00
}
var err error
rtn, err = GetRemoteById(tx.Context(), remoteId)
if err != nil {
return err
}
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
const (
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 UpdateScreen(ctx context.Context, screenId string, editMap map[string]interface{}) (*ScreenType, error) {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT screenid FROM screen WHERE screenid = ?`
if !tx.Exists(query, screenId) {
return fmt.Errorf("screen not found")
}
if anchorLine, found := editMap[ScreenField_AnchorLine]; found {
query = `UPDATE screen SET anchor = json_set(anchor, '$.anchorline', ?) WHERE screenid = ?`
tx.Exec(query, anchorLine, screenId)
2022-10-11 10:11:04 +02:00
}
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[ScreenField_SelectedLine]; found {
query = `UPDATE screen SET selectedline = ? WHERE screenid = ?`
tx.Exec(query, sline, screenId)
}
if focusType, found := editMap[ScreenField_Focus]; found {
query = `UPDATE screen SET focustype = ? WHERE screenid = ?`
tx.Exec(query, focusType, screenId)
2022-10-11 10:11:04 +02:00
}
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 GetScreenById(ctx, screenId)
}
func GetLineResolveItems(ctx context.Context, sessionId string, windowId string) ([]ResolveItem, error) {
var rtn []ResolveItem
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT lineid as id, linenum as num FROM line WHERE sessionid = ? AND windowid = ? ORDER BY linenum`
tx.Select(&rtn, query, sessionId, windowId)
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
func UpdateScreensWithCmdFg(ctx context.Context, sessionId string, cmdId string) ([]*ScreenType, error) {
var rtn []*ScreenType
txErr := WithTx(ctx, func(tx *TxWrap) error {
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 _, screenId := range screenIds {
editMap := make(map[string]interface{})
editMap[ScreenField_Focus] = ScreenFocusInput
screen, err := UpdateScreen(tx.Context(), screenId, editMap)
if err != nil {
return err
}
rtn = append(rtn, screen)
}
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
2022-11-28 09:13:00 +01:00
func StoreStateBase(ctx context.Context, state *packet.ShellState) error {
stateBase := &StateBase{
Version: state.Version,
Ts: time.Now().UnixMilli(),
}
stateBase.BaseHash, stateBase.Data = state.EncodeAndHash()
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT basehash FROM state_base WHERE basehash = ?`
if tx.Exists(query, stateBase.BaseHash) {
return nil
}
query = `INSERT INTO state_base (basehash, ts, version, data) VALUES (:basehash,:ts,:version,:data)`
tx.NamedExec(query, stateBase)
2022-11-28 09:13:00 +01:00
return nil
})
if txErr != nil {
return txErr
}
return nil
}
func StoreStateDiff(ctx context.Context, diff *packet.ShellStateDiff) error {
stateDiff := &StateDiff{
BaseHash: diff.BaseHash,
Ts: time.Now().UnixMilli(),
DiffHashArr: diff.DiffHashArr,
}
stateDiff.DiffHash, stateDiff.Data = diff.EncodeAndHash()
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT basehash FROM state_base WHERE basehash = ?`
if stateDiff.BaseHash == "" || !tx.Exists(query, stateDiff.BaseHash) {
return fmt.Errorf("cannot store statediff, basehash:%s does not exist", stateDiff.BaseHash)
}
query = `SELECT diffhash FROM state_diff WHERE diffhash = ?`
for idx, diffHash := range stateDiff.DiffHashArr {
if !tx.Exists(query, diffHash) {
return fmt.Errorf("cannot store statediff, diffhash[%d]:%s does not exist", idx, diffHash)
}
}
if tx.Exists(query, stateDiff.DiffHash) {
return nil
}
query = `INSERT INTO state_diff (diffhash, ts, basehash, diffhasharr, data) VALUES (:diffhash,:ts,:basehash,:diffhasharr,:data)`
tx.NamedExec(query, stateDiff.ToMap())
2022-11-28 09:13:00 +01:00
return nil
})
if txErr != nil {
return txErr
}
return nil
}
// returns error when not found
func GetFullState(ctx context.Context, ssPtr ShellStatePtr) (*packet.ShellState, error) {
2022-11-28 09:13:00 +01:00
var state *packet.ShellState
if ssPtr.BaseHash == "" {
2022-11-28 09:13:00 +01:00
return nil, fmt.Errorf("invalid empty basehash")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
var stateBase StateBase
query := `SELECT * FROM state_base WHERE basehash = ?`
found := tx.Get(&stateBase, query, ssPtr.BaseHash)
2022-11-28 09:13:00 +01:00
if !found {
return fmt.Errorf("ShellState %s not found", ssPtr.BaseHash)
2022-11-28 09:13:00 +01:00
}
state = &packet.ShellState{}
err := state.DecodeShellState(stateBase.Data)
if err != nil {
return err
}
for idx, diffHash := range ssPtr.DiffHashArr {
2022-11-28 09:13:00 +01:00
query = `SELECT * FROM state_diff WHERE diffhash = ?`
stateDiff := GetMapGen[*StateDiff](tx, query, diffHash)
2022-11-28 09:13:00 +01:00
if stateDiff == nil {
return fmt.Errorf("ShellStateDiff %s not found", diffHash)
}
var ssDiff packet.ShellStateDiff
err = ssDiff.DecodeShellStateDiff(stateDiff.Data)
if err != nil {
return err
}
newState, err := shexec.ApplyShellStateDiff(*state, ssDiff)
if err != nil {
return fmt.Errorf("GetFullState, diff[%d]:%s: %v", idx, diffHash, err)
}
state = &newState
}
return nil
})
if txErr != nil {
return nil, txErr
}
if state == nil {
return nil, fmt.Errorf("ShellState not found")
}
return state, nil
}
func UpdateLineStar(ctx context.Context, lineId string, starVal int) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE line SET star = ? WHERE lineid = ?`
tx.Exec(query, starVal, lineId)
return nil
})
if txErr != nil {
return txErr
}
return nil
}
2023-02-01 07:21:19 +01:00
func UpdateLineHeight(ctx context.Context, lineId string, heightVal int) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE line SET contentheight = ? WHERE lineid = ?`
tx.Exec(query, heightVal, lineId)
2023-02-01 07:21:19 +01:00
return nil
})
if txErr != nil {
return txErr
}
return nil
}
// can return nil, nil if line is not found
2023-02-21 06:39:29 +01:00
func GetLineById(ctx context.Context, sessionId string, windowId string, lineId string) (*LineType, error) {
var rtn *LineType
txErr := WithTx(ctx, func(tx *TxWrap) error {
var line LineType
2023-02-21 06:39:29 +01:00
query := `SELECT * FROM line WHERE sessionid = ? AND windowid = ? AND lineid = ?`
found := tx.Get(&line, query, sessionId, windowId, lineId)
if found {
rtn = &line
}
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
func SetLineArchivedById(ctx context.Context, lineId string, archived bool) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE line SET archived = ? WHERE lineid = ?`
tx.Exec(query, archived, lineId)
return nil
})
return txErr
}
func purgeCmdById(ctx context.Context, sessionId string, cmdId string) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `DELETE FROM cmd WHERE sessionid = ? AND cmdid = ?`
tx.Exec(query, sessionId, cmdId)
return DeletePtyOutFile(tx.Context(), sessionId, cmdId)
})
return txErr
}
2023-03-03 22:31:16 +01:00
func PurgeLinesByIds(ctx context.Context, sessionId string, lineIds []string) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
for _, lineId := range lineIds {
query := `SELECT cmdid FROM line WHERE sessionid = ? AND lineid = ?`
cmdId := tx.GetString(query, sessionId, lineId)
query = `DELETE FROM line WHERE sessionid = ? AND lineid = ?`
tx.Exec(query, sessionId, lineId)
query = `DELETE FROM history WHERE sessionid = ? AND lineid = ?`
tx.Exec(query, sessionId, lineId)
if cmdId != "" {
query = `SELECT count(*) FROM line WHERE sessionid = ? AND cmdid = ?`
cmdRefCount := tx.GetInt(query, sessionId, cmdId)
if cmdRefCount == 0 {
err := purgeCmdById(tx.Context(), sessionId, cmdId)
if err != nil {
return err
}
}
}
}
return nil
})
return txErr
}
2022-12-31 02:01:17 +01:00
func GetRIsForWindow(ctx context.Context, sessionId string, windowId string) ([]*RemoteInstance, error) {
var rtn []*RemoteInstance
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND (windowid = '' OR windowid = ?)`
rtn = SelectMapsGen[*RemoteInstance](tx, query, sessionId, windowId)
2022-12-31 02:01:17 +01:00
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
2023-01-17 08:36:52 +01:00
func GetCurDayStr() string {
2023-01-17 08:36:52 +01:00
now := time.Now()
dayStr := now.Format("2006-01-02")
return dayStr
}
func UpdateCurrentActivity(ctx context.Context, update ActivityUpdate) error {
now := time.Now()
dayStr := GetCurDayStr()
2023-01-17 08:36:52 +01:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
var tdata TelemetryData
query := `SELECT tdata FROM activity WHERE day = ?`
found := tx.Get(&tdata, query, dayStr)
if !found {
2023-02-24 00:17:47 +01:00
query = `INSERT INTO activity (day, uploaded, tdata, tzname, tzoffset, clientversion, clientarch, buildtime, osrelease)
VALUES (?, 0, ?, ?, ?, ?, ? , ? , ?)`
2023-01-17 08:36:52 +01:00
tzName, tzOffset := now.Zone()
if len(tzName) > MaxTzNameLen {
tzName = tzName[0:MaxTzNameLen]
}
2023-02-24 00:17:47 +01:00
tx.Exec(query, dayStr, tdata, tzName, tzOffset, scbase.PromptVersion, scbase.ClientArch(), scbase.BuildTime, scbase.MacOSRelease())
2023-01-17 08:36:52 +01:00
}
tdata.NumCommands += update.NumCommands
tdata.FgMinutes += update.FgMinutes
tdata.ActiveMinutes += update.ActiveMinutes
tdata.OpenMinutes += update.OpenMinutes
2023-02-22 07:41:56 +01:00
tdata.ClickShared += update.ClickShared
tdata.HistoryView += update.HistoryView
tdata.BookmarksView += update.BookmarksView
if update.NumConns > 0 {
tdata.NumConns = update.NumConns
}
query = `UPDATE activity
SET tdata = ?,
2023-02-24 00:17:47 +01:00
clientversion = ?,
buildtime = ?
WHERE day = ?`
2023-02-24 00:17:47 +01:00
tx.Exec(query, tdata, scbase.PromptVersion, scbase.BuildTime, dayStr)
2023-01-17 08:36:52 +01:00
return nil
})
if txErr != nil {
return txErr
}
return nil
}
func GetNonUploadedActivity(ctx context.Context) ([]*ActivityType, error) {
var rtn []*ActivityType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM activity WHERE uploaded = 0 ORDER BY day DESC LIMIT 30`
tx.Select(&rtn, query)
2023-01-17 08:36:52 +01:00
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
// note, will not mark the current day as uploaded
2023-01-17 08:36:52 +01:00
func MarkActivityAsUploaded(ctx context.Context, activityArr []*ActivityType) error {
dayStr := GetCurDayStr()
2023-01-17 08:36:52 +01:00
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE activity SET uploaded = 1 WHERE day = ?`
for _, activity := range activityArr {
if activity.Day == dayStr {
continue
}
tx.Exec(query, activity.Day)
2023-01-17 08:36:52 +01:00
}
return nil
})
return txErr
}
2023-02-21 00:41:39 +01:00
func foundInStrArr(strs []string, s string) bool {
for _, sval := range strs {
if s == sval {
return true
}
}
return false
}
// newPos is 0-indexed
func reorderStrs(strs []string, toMove string, newPos int) []string {
if !foundInStrArr(strs, toMove) {
return strs
}
var added bool
rtn := make([]string, 0, len(strs))
for _, s := range strs {
if s == toMove {
continue
}
if len(rtn) == newPos {
added = true
rtn = append(rtn, toMove)
}
rtn = append(rtn, s)
}
if !added {
rtn = append(rtn, toMove)
}
return rtn
}
// newScreenIdx is 1-indexed
func SetScreenIdx(ctx context.Context, sessionId string, screenId string, newScreenIdx int) error {
if newScreenIdx <= 0 {
return fmt.Errorf("invalid screenidx/pos, must be greater than 0")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ? AND NOT archived`
if !tx.Exists(query, sessionId, screenId) {
return fmt.Errorf("invalid screen, not found (or archived)")
}
query = `SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`
screens := tx.SelectStrings(query, sessionId)
newScreens := reorderStrs(screens, screenId, newScreenIdx-1)
query = `UPDATE screen SET screenidx = ? WHERE sessionid = ? AND screenid = ?`
for idx, sid := range newScreens {
tx.Exec(query, idx+1, sessionId, sid)
}
return nil
})
return txErr
}
func GetDBVersion(ctx context.Context) (int, error) {
var version int
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT version FROM schema_migrations`
version = tx.GetInt(query)
return nil
})
return version, txErr
}
2023-02-21 06:39:29 +01:00
2023-02-21 07:00:07 +01:00
type bookmarkOrderType struct {
BookmarkId string
OrderIdx int64
}
type bookmarkCmdType struct {
BookmarkId string
SessionId string
CmdId string
}
func GetBookmarks(ctx context.Context, tag string) ([]*BookmarkType, error) {
var bms []*BookmarkType
txErr := WithTx(ctx, func(tx *TxWrap) error {
var query string
if tag == "" {
query = `SELECT * FROM bookmark`
bms = SelectMapsGen[*BookmarkType](tx, query)
2023-02-21 07:00:07 +01:00
} else {
query = `SELECT * FROM bookmark WHERE EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)`
bms = SelectMapsGen[*BookmarkType](tx, query, tag)
2023-02-21 07:00:07 +01:00
}
bmMap := MakeGenMap(bms)
2023-02-21 07:00:07 +01:00
var orders []bookmarkOrderType
query = `SELECT bookmarkid, orderidx FROM bookmark_order WHERE tag = ?`
tx.Select(&orders, query, tag)
for _, bmOrder := range orders {
bm := bmMap[bmOrder.BookmarkId]
if bm != nil {
bm.OrderIdx = bmOrder.OrderIdx
}
}
var cmds []bookmarkCmdType
query = `SELECT bookmarkid, sessionid, cmdid FROM bookmark_cmd`
tx.Select(&cmds, query)
for _, cmd := range cmds {
bm := bmMap[cmd.BookmarkId]
if bm != nil {
bm.Cmds = append(bm.Cmds, base.MakeCommandKey(cmd.SessionId, cmd.CmdId))
}
}
return nil
})
if txErr != nil {
return nil, txErr
}
return bms, nil
}
2023-02-22 03:03:13 +01:00
func GetBookmarkById(ctx context.Context, bookmarkId string, tag string) (*BookmarkType, error) {
var rtn *BookmarkType
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM bookmark WHERE bookmarkid = ?`
rtn = GetMapGen[*BookmarkType](tx, query, bookmarkId)
2023-02-22 03:03:13 +01:00
if rtn == nil {
return nil
}
query = `SELECT orderidx FROM bookmark_order WHERE bookmarkid = ? AND tag = ?`
orderIdx := tx.GetInt(query, bookmarkId, tag)
rtn.OrderIdx = int64(orderIdx)
query = `SELECT bookmarkid, sessionid, cmdid FROM bookmark_cmd WHERE bookmarkid = ?`
var cmds []bookmarkCmdType
tx.Select(&cmds, query, bookmarkId)
for _, cmd := range cmds {
rtn.Cmds = append(rtn.Cmds, base.MakeCommandKey(cmd.SessionId, cmd.CmdId))
}
return nil
})
if txErr != nil {
return nil, txErr
}
return rtn, nil
}
func GetBookmarkIdByArg(ctx context.Context, bookmarkArg string) (string, error) {
var rtnId string
txErr := WithTx(ctx, func(tx *TxWrap) error {
if len(bookmarkArg) == 8 {
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid LIKE (? || '%')`
rtnId = tx.GetString(query, bookmarkArg)
return nil
}
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
rtnId = tx.GetString(query, bookmarkArg)
return nil
})
if txErr != nil {
return "", txErr
}
return rtnId, nil
}
2023-02-21 07:00:07 +01:00
// ignores OrderIdx field
2023-02-21 06:39:29 +01:00
func InsertBookmark(ctx context.Context, bm *BookmarkType) error {
if bm == nil || bm.BookmarkId == "" {
return fmt.Errorf("invalid empty bookmark id")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
if tx.Exists(query, bm.BookmarkId) {
return fmt.Errorf("bookmarkid already exists")
}
query = `INSERT INTO bookmark ( bookmarkid, createdts, cmdstr, alias, tags, description)
VALUES (:bookmarkid,:createdts,:cmdstr,:alias,:tags,:description)`
tx.NamedExec(query, bm.ToMap())
for _, tag := range append(bm.Tags, "") {
query = `SELECT COALESCE(max(orderidx), 0) FROM bookmark_order WHERE tag = ?`
maxOrder := tx.GetInt(query, tag)
query = `INSERT INTO bookmark_order (tag, bookmarkid, orderidx) VALUES (?, ?, ?)`
tx.Exec(query, tag, bm.BookmarkId, maxOrder+1)
}
query = `INSERT INTO bookmark_cmd (bookmarkid, sessionid, cmdid) VALUES (?, ?, ?)`
2023-02-21 07:00:07 +01:00
for _, ck := range bm.Cmds {
2023-02-21 06:39:29 +01:00
tx.Exec(query, bm.BookmarkId, ck.GetSessionId(), ck.GetCmdId())
}
query = `UPDATE line SET bookmarked = 1 WHERE sessionid = ? AND cmdid = ?`
2023-02-21 07:00:07 +01:00
for _, ck := range bm.Cmds {
2023-02-21 06:39:29 +01:00
tx.Exec(query, ck.GetSessionId(), ck.GetCmdId())
}
return nil
})
return txErr
}
2023-02-22 03:03:13 +01:00
const (
BookmarkField_Desc = "desc"
BookmarkField_CmdStr = "cmdstr"
)
func EditBookmark(ctx context.Context, bookmarkId string, editMap map[string]interface{}) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
if !tx.Exists(query, bookmarkId) {
return fmt.Errorf("bookmark not found")
}
if desc, found := editMap[BookmarkField_Desc]; found {
query = `UPDATE bookmark SET description = ? WHERE bookmarkid = ?`
tx.Exec(query, desc, bookmarkId)
}
if cmdStr, found := editMap[BookmarkField_CmdStr]; found {
query = `UPDATE bookmark SET cmdstr = ? WHERE bookmarkid = ?`
tx.Exec(query, cmdStr, bookmarkId)
}
return nil
})
return txErr
}
2023-02-21 06:39:29 +01:00
func fixupBookmarkOrder(tx *TxWrap) {
query := `
WITH new_order AS (
SELECT tag, bookmarkid, row_number() OVER (PARTITION BY tag ORDER BY orderidx) AS newidx FROM bookmark_order
)
UPDATE bookmark_order
SET orderidx = new_order.newidx
FROM new_order
WHERE bookmark_order.tag = new_order.tag AND bookmark_order.bookmarkid = new_order.bookmarkid
`
tx.Exec(query)
}
func DeleteBookmark(ctx context.Context, bookmarkId string) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT bookmarkid FROM bookmark WHERE bookmarkid = ?`
if !tx.Exists(query, bookmarkId) {
return fmt.Errorf("bookmark not found")
}
query = `DELETE FROM bookmark WHERE bookmarkid = ?`
tx.Exec(query, bookmarkId)
query = `DELETE FROM bookmark_order WHERE bookmarkid = ?`
tx.Exec(query, bookmarkId)
query = `UPDATE line SET bookmarked = 0 WHERE bookmarked AND cmdid <> '' AND (sessionid||cmdid) IN (SELECT sessionid||cmdid FROM bookmark_cmd WHERE bookmarkid = ?) `
tx.Exec(query, bookmarkId)
query = `DELETE FROM bookmark_cmd WHERE bookmarkid = ?`
tx.Exec(query, bookmarkId)
fixupBookmarkOrder(tx)
return nil
})
return txErr
}
2023-03-02 09:31:19 +01:00
func CreatePlaybook(ctx context.Context, name string) (*PlaybookType, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*PlaybookType, error) {
2023-03-02 09:31:19 +01:00
query := `SELECT playbookid FROM playbook WHERE name = ?`
if tx.Exists(query, name) {
return nil, fmt.Errorf("playbook %q already exists", name)
2023-03-02 09:31:19 +01:00
}
rtn := &PlaybookType{}
2023-03-02 09:31:19 +01:00
rtn.PlaybookId = uuid.New().String()
rtn.PlaybookName = name
query = `INSERT INTO playbook ( playbookid, playbookname, description, entryids)
VALUES (:playbookid,:playbookname,:description,:entryids)`
tx.Exec(query, rtn.ToMap())
return rtn, nil
2023-03-02 09:31:19 +01:00
})
}
func selectPlaybook(tx *TxWrap, playbookId string) *PlaybookType {
query := `SELECT * FROM playbook where playbookid = ?`
playbook := GetMapGen[*PlaybookType](tx, query, playbookId)
2023-03-02 09:31:19 +01:00
return playbook
}
func AddPlaybookEntry(ctx context.Context, entry *PlaybookEntry) error {
if entry.EntryId == "" {
return fmt.Errorf("invalid entryid")
}
return WithTx(ctx, func(tx *TxWrap) error {
2023-03-02 09:31:19 +01:00
playbook := selectPlaybook(tx, entry.PlaybookId)
if playbook == nil {
return fmt.Errorf("cannot add entry, playbook does not exist")
}
query := `SELECT entryid FROM playbook_entry WHERE entryid = ?`
if tx.Exists(query, entry.EntryId) {
return fmt.Errorf("cannot add entry, entryid already exists")
}
query = `INSERT INTO playbook_entry ( entryid, playbookid, description, alias, cmdstr, createdts, updatedts)
VALUES (:entryid,:playbookid,:description,:alias,:cmdstr,:createdts,:updatedts)`
tx.Exec(query, entry)
playbook.EntryIds = append(playbook.EntryIds, entry.EntryId)
query = `UPDATE playbook SET entryids = ? WHERE playbookid = ?`
tx.Exec(query, quickJsonArr(playbook.EntryIds), entry.PlaybookId)
return nil
})
}
func RemovePlaybookEntry(ctx context.Context, playbookId string, entryId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
2023-03-02 09:31:19 +01:00
playbook := selectPlaybook(tx, playbookId)
if playbook == nil {
return fmt.Errorf("cannot remove playbook entry, playbook does not exist")
}
query := `SELECT entryid FROM playbook_entry WHERE entryid = ?`
if !tx.Exists(query, entryId) {
return fmt.Errorf("cannot remove playbook entry, entry does not exist")
}
query = `DELETE FROM playbook_entry WHERE entryid = ?`
tx.Exec(query, entryId)
playbook.RemoveEntry(entryId)
query = `UPDATE playbook SET entryids = ? WHERE playbookid = ?`
tx.Exec(query, quickJsonArr(playbook.EntryIds), playbookId)
return nil
})
}
func GetPlaybookById(ctx context.Context, playbookId string) (*PlaybookType, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*PlaybookType, error) {
rtn := selectPlaybook(tx, playbookId)
2023-03-02 09:31:19 +01:00
if rtn == nil {
return nil, nil
2023-03-02 09:31:19 +01:00
}
query := `SELECT * FROM playbook_entry WHERE playbookid = ?`
tx.Select(&rtn.Entries, query, playbookId)
rtn.OrderEntries()
return rtn, nil
2023-03-02 09:31:19 +01:00
})
}
func getLineIdsFromHistoryItems(historyItems []*HistoryItemType) []string {
var rtn []string
for _, hitem := range historyItems {
if hitem.LineId != "" {
rtn = append(rtn, hitem.LineId)
}
}
return rtn
}
func getCmdIdsFromHistoryItems(historyItems []*HistoryItemType) []string {
var rtn []string
for _, hitem := range historyItems {
if hitem.CmdId != "" {
rtn = append(rtn, hitem.CmdId)
}
}
return rtn
}
func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryItemType) ([]*LineType, []*CmdType, error) {
if len(historyItems) == 0 {
return nil, nil, nil
}
return WithTxRtn3(ctx, func(tx *TxWrap) ([]*LineType, []*CmdType, error) {
var lineArr []*LineType
query := `SELECT * FROM line WHERE lineid IN (SELECT value FROM json_each(?))`
tx.Select(&lineArr, query, quickJsonArr(getLineIdsFromHistoryItems(historyItems)))
query = `SELECT * FROM cmd WHERE cmdid IN (SELECT value FROM json_each(?))`
cmdArr := SelectMapsGen[*CmdType](tx, query, quickJsonArr(getCmdIdsFromHistoryItems(historyItems)))
return lineArr, cmdArr, nil
})
}
2023-03-03 22:31:16 +01:00
func PurgeHistoryByIds(ctx context.Context, historyIds []string) ([]*HistoryItemType, error) {
return WithTxRtn(ctx, func(tx *TxWrap) ([]*HistoryItemType, error) {
2023-03-03 22:31:16 +01:00
query := `SELECT * FROM history WHERE historyid IN (SELECT value FROM json_each(?))`
rtn := SelectMapsGen[*HistoryItemType](tx, query, quickJsonArr(historyIds))
2023-03-03 22:31:16 +01:00
query = `DELETE FROM history WHERE historyid IN (SELECT value FROM json_each(?))`
tx.Exec(query, quickJsonArr(historyIds))
for _, hitem := range rtn {
if hitem.LineId != "" {
err := PurgeLinesByIds(tx.Context(), hitem.SessionId, []string{hitem.LineId})
if err != nil {
return nil, err
2023-03-03 22:31:16 +01:00
}
}
}
return rtn, nil
2023-03-03 22:31:16 +01:00
})
}