mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Moving screen and session into workspaces package
This commit is contained in:
parent
a121bd4bb5
commit
f975ecce48
@ -231,227 +231,6 @@ func UpdateRemoteStateVars(ctx context.Context, remoteId string, stateVars map[s
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// includes archived sessions
|
|
||||||
func GetBareSessions(ctx context.Context) ([]*SessionType, error) {
|
|
||||||
var rtn []*SessionType
|
|
||||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
|
|
||||||
tx.Select(&rtn, query)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
query := `SELECT sessionid from session WHERE NOT archived ORDER by sessionidx`
|
|
||||||
rtn = tx.SelectStrings(query)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return "", txErr
|
|
||||||
}
|
|
||||||
if len(rtn) == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return rtn[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
if rtn.SessionId == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return &rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAllSessionsQuery = `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
|
|
||||||
|
|
||||||
// Gets all sessions, including archived
|
|
||||||
func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) ([]*SessionType, error) {
|
|
||||||
rtn := []*SessionType{}
|
|
||||||
tx.Select(&rtn, getAllSessionsQuery)
|
|
||||||
return rtn, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all sessions and screens, including remotes
|
|
||||||
func GetConnectUpdate(ctx context.Context) (*ConnectUpdate, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (*ConnectUpdate, error) {
|
|
||||||
update := &ConnectUpdate{}
|
|
||||||
sessions := []*SessionType{}
|
|
||||||
tx.Select(&sessions, getAllSessionsQuery)
|
|
||||||
sessionMap := make(map[string]*SessionType)
|
|
||||||
for _, session := range sessions {
|
|
||||||
sessionMap[session.SessionId] = session
|
|
||||||
update.Sessions = append(update.Sessions, session)
|
|
||||||
}
|
|
||||||
query := `SELECT * FROM screen ORDER BY archived, screenidx, archivedts`
|
|
||||||
screens := dbutil.SelectMapsGen[*ScreenType](tx, query)
|
|
||||||
for _, screen := range screens {
|
|
||||||
update.Screens = append(update.Screens, screen)
|
|
||||||
}
|
|
||||||
query = `SELECT * FROM remote_instance`
|
|
||||||
riArr := dbutil.SelectMapsGen[*RemoteInstance](tx, query)
|
|
||||||
for _, ri := range riArr {
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenLinesType, error) {
|
|
||||||
query := `SELECT screenid FROM screen WHERE screenid = ?`
|
|
||||||
screen := dbutil.GetMappable[*ScreenLinesType](tx, query, screenId)
|
|
||||||
if screen == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
query = `SELECT * FROM line WHERE screenid = ? ORDER BY linenum`
|
|
||||||
screen.Lines = dbutil.SelectMappable[*LineType](tx, query, screen.ScreenId)
|
|
||||||
query = `SELECT * FROM cmd WHERE screenid = ?`
|
|
||||||
screen.Cmds = dbutil.SelectMapsGen[*CmdType](tx, query, screen.ScreenId)
|
|
||||||
return screen, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// includes archived screens
|
|
||||||
func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) {
|
|
||||||
query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts`
|
|
||||||
rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId)
|
|
||||||
return rtn, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSessionById(ctx context.Context, id string) (*SessionType, error) {
|
|
||||||
allSessions, err := GetAllSessions(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, session := range allSessions {
|
|
||||||
if session.SessionId == id {
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// counts non-archived sessions
|
|
||||||
func GetSessionCount(ctx context.Context) (int, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (int, error) {
|
|
||||||
query := `SELECT COALESCE(count(*), 0) FROM session WHERE NOT archived`
|
|
||||||
numSessions := tx.GetInt(query)
|
|
||||||
return numSessions, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
session, err = GetSessionById(tx.Context(), sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns sessionId
|
|
||||||
// if sessionName == "", it will be generated
|
|
||||||
func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*scbus.ModelUpdatePacketType, error) {
|
|
||||||
var newScreen *ScreenType
|
|
||||||
newSessionId := scbase.GenWaveUUID()
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
names := tx.SelectStrings(`SELECT name FROM session`)
|
|
||||||
sessionName = fmtUniqueName(sessionName, "workspace-%d", len(names)+1, names)
|
|
||||||
maxSessionIdx := tx.GetInt(`SELECT COALESCE(max(sessionidx), 0) FROM session`)
|
|
||||||
query := `INSERT INTO session (sessionid, name, activescreenid, sessionidx, notifynum, archived, archivedts, sharemode)
|
|
||||||
VALUES (?, ?, '', ?, 0, 0, 0, ?)`
|
|
||||||
tx.Exec(query, newSessionId, sessionName, maxSessionIdx+1, ShareModeLocal)
|
|
||||||
screenUpdate, err := InsertScreen(tx.Context(), newSessionId, "", ScreenCreateOpts{}, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
screenUpdateItems := scbus.GetUpdateItems[ScreenType](screenUpdate)
|
|
||||||
if len(screenUpdateItems) < 1 {
|
|
||||||
return fmt.Errorf("no screen update items")
|
|
||||||
}
|
|
||||||
newScreen = screenUpdateItems[0]
|
|
||||||
if activate {
|
|
||||||
query = `UPDATE client SET activesessionid = ?`
|
|
||||||
tx.Exec(query, newSessionId)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
session, err := GetSessionById(ctx, newSessionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
update := scbus.MakeUpdatePacket()
|
|
||||||
update.AddUpdate(*session)
|
|
||||||
update.AddUpdate(*newScreen)
|
|
||||||
if activate {
|
|
||||||
update.AddUpdate(ActiveSessionIdUpdate(newSessionId))
|
|
||||||
}
|
|
||||||
return update, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetActiveSessionId(ctx context.Context, sessionId string) error {
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetWinSize(ctx context.Context, winSize ClientWinSizeType) error {
|
func SetWinSize(ctx context.Context, winSize ClientWinSizeType) error {
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE client SET winsize = ?`
|
query := `UPDATE client SET winsize = ?`
|
||||||
@ -488,7 +267,7 @@ func containsStr(strs []string, testStr string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []string) string {
|
func FmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []string) string {
|
||||||
var fmtStr string
|
var fmtStr string
|
||||||
if name != "" {
|
if name != "" {
|
||||||
if !containsStr(strs, name) {
|
if !containsStr(strs, name) {
|
||||||
@ -512,93 +291,6 @@ func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InsertScreen(ctx context.Context, sessionId string, origScreenName string, opts ScreenCreateOpts, activate bool) (*scbus.ModelUpdatePacketType, error) {
|
|
||||||
var newScreenId string
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT archived`
|
|
||||||
if !tx.Exists(query, sessionId) {
|
|
||||||
return fmt.Errorf("cannot create screen, no session found (or session archived)")
|
|
||||||
}
|
|
||||||
localRemoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, LocalRemoteAlias)
|
|
||||||
if localRemoteId == "" {
|
|
||||||
return fmt.Errorf("cannot create screen, no local remote found")
|
|
||||||
}
|
|
||||||
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
var baseScreen *ScreenType
|
|
||||||
if opts.HasCopy() {
|
|
||||||
if opts.BaseScreenId == "" {
|
|
||||||
return fmt.Errorf("invalid screen create opts, copy option with no base screen specified")
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
baseScreen, err = GetScreenById(tx.Context(), opts.BaseScreenId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if baseScreen == nil {
|
|
||||||
return fmt.Errorf("cannot create screen, base screen not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newScreenId = scbase.GenWaveUUID()
|
|
||||||
screen := &ScreenType{
|
|
||||||
SessionId: sessionId,
|
|
||||||
ScreenId: newScreenId,
|
|
||||||
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, name, screenidx, screenopts, screenviewopts, ownerid, sharemode, webshareopts, curremoteownerid, curremoteid, curremotename, nextlinenum, selectedline, anchor, focustype, archived, archivedts)
|
|
||||||
VALUES (:sessionid,:screenid,:name,:screenidx,:screenopts,:screenviewopts,:ownerid,:sharemode,:webshareopts,:curremoteownerid,:curremoteid,:curremotename,:nextlinenum,:selectedline,:anchor,:focustype,:archived,:archivedts)`
|
|
||||||
tx.NamedExec(query, screen.ToMap())
|
|
||||||
if activate {
|
|
||||||
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
|
|
||||||
tx.Exec(query, newScreenId, sessionId)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
newScreen, err := GetScreenById(ctx, newScreenId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
update := scbus.MakeUpdatePacket()
|
|
||||||
update.AddUpdate(*newScreen)
|
|
||||||
if activate {
|
|
||||||
bareSession, err := GetBareSessionById(ctx, sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
update.AddUpdate(*bareSession)
|
|
||||||
UpdateWithCurrentOpenAICmdInfoChat(newScreenId, update)
|
|
||||||
}
|
|
||||||
return update, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) {
|
|
||||||
query := `SELECT * FROM screen WHERE screenid = ?`
|
|
||||||
screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId)
|
|
||||||
return screen, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// special "E" returns last unarchived line, "EA" returns last line (even if archived)
|
// special "E" returns last unarchived line, "EA" returns last line (even if archived)
|
||||||
func FindLineIdByArg(ctx context.Context, screenId string, lineArg string) (string, error) {
|
func FindLineIdByArg(ctx context.Context, screenId string, lineArg string) (string, error) {
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
|
return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
|
||||||
@ -685,8 +377,8 @@ INSERT INTO cmd ( screenid, lineid, remoteownerid, remoteid, remotename, cmdstr
|
|||||||
`
|
`
|
||||||
tx.NamedExec(query, cmdMap)
|
tx.NamedExec(query, cmdMap)
|
||||||
}
|
}
|
||||||
if isWebShare(tx, line.ScreenId) {
|
if IsWebShare(tx, line.ScreenId) {
|
||||||
insertScreenLineUpdate(tx, line.ScreenId, line.LineId, UpdateType_LineNew)
|
InsertScreenLineUpdate(tx, line.ScreenId, line.LineId, UpdateType_LineNew)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -760,10 +452,10 @@ func UpdateCmdDoneInfo(ctx context.Context, update *scbus.ModelUpdatePacketType,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdExitCode)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdExitCode)
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdDurationMs)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdDurationMs)
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdStatus)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdStatus)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -799,8 +491,8 @@ func UpdateCmdRtnState(ctx context.Context, ck base.CommandKey, statePtr ShellSt
|
|||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE cmd SET rtnbasehash = ?, rtndiffhasharr = ? WHERE screenid = ? AND lineid = ?`
|
query := `UPDATE cmd SET rtnbasehash = ?, rtndiffhasharr = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, statePtr.BaseHash, quickJsonArr(statePtr.DiffHashArr), screenId, lineId)
|
tx.Exec(query, statePtr.BaseHash, quickJsonArr(statePtr.DiffHashArr), screenId, lineId)
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdRtnState)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdRtnState)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -826,8 +518,8 @@ func HangupAllRunningCmds(ctx context.Context) error {
|
|||||||
query = `UPDATE cmd SET status = ? WHERE status = ?`
|
query = `UPDATE cmd SET status = ? WHERE status = ?`
|
||||||
tx.Exec(query, CmdStatusHangup, CmdStatusRunning)
|
tx.Exec(query, CmdStatusHangup, CmdStatusRunning)
|
||||||
for _, cmdPtr := range cmdPtrs {
|
for _, cmdPtr := range cmdPtrs {
|
||||||
if isWebShare(tx, cmdPtr.ScreenId) {
|
if IsWebShare(tx, cmdPtr.ScreenId) {
|
||||||
insertScreenLineUpdate(tx, cmdPtr.ScreenId, cmdPtr.LineId, UpdateType_CmdStatus)
|
InsertScreenLineUpdate(tx, cmdPtr.ScreenId, cmdPtr.LineId, UpdateType_CmdStatus)
|
||||||
}
|
}
|
||||||
query = `UPDATE history SET status = ? WHERE screenid = ? AND lineid = ?`
|
query = `UPDATE history SET status = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, CmdStatusHangup, cmdPtr.ScreenId, cmdPtr.LineId)
|
tx.Exec(query, CmdStatusHangup, cmdPtr.ScreenId, cmdPtr.LineId)
|
||||||
@ -846,8 +538,8 @@ func HangupRunningCmdsByRemoteId(ctx context.Context, remoteId string) ([]*Scree
|
|||||||
tx.Exec(query, CmdStatusHangup, CmdStatusRunning, remoteId)
|
tx.Exec(query, CmdStatusHangup, CmdStatusRunning, remoteId)
|
||||||
var rtn []*ScreenType
|
var rtn []*ScreenType
|
||||||
for _, cmdPtr := range cmdPtrs {
|
for _, cmdPtr := range cmdPtrs {
|
||||||
if isWebShare(tx, cmdPtr.ScreenId) {
|
if IsWebShare(tx, cmdPtr.ScreenId) {
|
||||||
insertScreenLineUpdate(tx, cmdPtr.ScreenId, cmdPtr.LineId, UpdateType_CmdStatus)
|
InsertScreenLineUpdate(tx, cmdPtr.ScreenId, cmdPtr.LineId, UpdateType_CmdStatus)
|
||||||
}
|
}
|
||||||
query = `UPDATE history SET status = ? WHERE screenid = ? AND lineid = ?`
|
query = `UPDATE history SET status = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, CmdStatusHangup, cmdPtr.ScreenId, cmdPtr.LineId)
|
tx.Exec(query, CmdStatusHangup, cmdPtr.ScreenId, cmdPtr.LineId)
|
||||||
@ -871,8 +563,8 @@ func HangupCmd(ctx context.Context, ck base.CommandKey) (*ScreenType, error) {
|
|||||||
tx.Exec(query, CmdStatusHangup, ck.GetGroupId(), lineIdFromCK(ck))
|
tx.Exec(query, CmdStatusHangup, ck.GetGroupId(), lineIdFromCK(ck))
|
||||||
query = `UPDATE history SET status = ? WHERE screenid = ? AND lineid = ?`
|
query = `UPDATE history SET status = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, CmdStatusHangup, ck.GetGroupId(), lineIdFromCK(ck))
|
tx.Exec(query, CmdStatusHangup, ck.GetGroupId(), lineIdFromCK(ck))
|
||||||
if isWebShare(tx, ck.GetGroupId()) {
|
if IsWebShare(tx, ck.GetGroupId()) {
|
||||||
insertScreenLineUpdate(tx, ck.GetGroupId(), lineIdFromCK(ck), UpdateType_CmdStatus)
|
InsertScreenLineUpdate(tx, ck.GetGroupId(), lineIdFromCK(ck), UpdateType_CmdStatus)
|
||||||
}
|
}
|
||||||
screen, err := UpdateScreenFocusForDoneCmd(tx.Context(), ck.GetGroupId(), lineIdFromCK(ck))
|
screen, err := UpdateScreenFocusForDoneCmd(tx.Context(), ck.GetGroupId(), lineIdFromCK(ck))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -906,200 +598,6 @@ func getNextId(ids []string, delId string) string {
|
|||||||
return ids[0]
|
return ids[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (*scbus.ModelUpdatePacketType, error) {
|
|
||||||
SetActiveSessionId(ctx, sessionId)
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?`
|
|
||||||
if !tx.Exists(query, sessionId, screenId) {
|
|
||||||
return fmt.Errorf("cannot switch to screen, screen=%s does not exist in session=%s", screenId, sessionId)
|
|
||||||
}
|
|
||||||
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
|
|
||||||
tx.Exec(query, screenId, sessionId)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
bareSession, err := GetBareSessionById(ctx, sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
update := scbus.MakeUpdatePacket()
|
|
||||||
update.AddUpdate(ActiveSessionIdUpdate(sessionId))
|
|
||||||
update.AddUpdate(*bareSession)
|
|
||||||
memState := GetScreenMemState(screenId)
|
|
||||||
if memState != nil {
|
|
||||||
update.AddUpdate(CmdLineUpdate(memState.CmdInputText))
|
|
||||||
UpdateWithCurrentOpenAICmdInfoChat(screenId, update)
|
|
||||||
|
|
||||||
// Clear any previous status indicator for this screen
|
|
||||||
err := ResetStatusIndicator_Update(update, screenId)
|
|
||||||
if err != nil {
|
|
||||||
// This is not a fatal error, so just log it
|
|
||||||
log.Printf("error resetting status indicator when switching screens: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return update, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// screen may not exist at this point (so don't query screen table)
|
|
||||||
func cleanScreenCmds(ctx context.Context, screenId string) error {
|
|
||||||
var removedCmds []string
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT lineid FROM cmd WHERE screenid = ? AND lineid NOT IN (SELECT lineid FROM line WHERE screenid = ?)`
|
|
||||||
removedCmds = tx.SelectStrings(query, screenId, screenId)
|
|
||||||
query = `DELETE FROM cmd WHERE screenid = ? AND lineid NOT IN (SELECT lineid FROM line WHERE screenid = ?)`
|
|
||||||
tx.Exec(query, screenId, screenId)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
for _, lineId := range removedCmds {
|
|
||||||
DeletePtyOutFile(ctx, screenId, lineId)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (scbus.UpdatePacket, error) {
|
|
||||||
var isActive bool
|
|
||||||
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)")
|
|
||||||
}
|
|
||||||
if isWebShare(tx, screenId) {
|
|
||||||
return fmt.Errorf("cannot archive screen while web-sharing. stop web-sharing before trying to archive.")
|
|
||||||
}
|
|
||||||
query = `SELECT archived FROM screen WHERE sessionid = ? AND screenid = ?`
|
|
||||||
closeVal := tx.GetBool(query, sessionId, screenId)
|
|
||||||
if closeVal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
|
|
||||||
numScreens := tx.GetInt(query, sessionId)
|
|
||||||
if numScreens <= 1 {
|
|
||||||
return fmt.Errorf("cannot archive the last screen in a session")
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if isActive {
|
|
||||||
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, sessionId)
|
|
||||||
nextId := getNextId(screenIds, screenId)
|
|
||||||
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
newScreen, err := GetScreenById(ctx, screenId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot retrive archived screen: %w", err)
|
|
||||||
}
|
|
||||||
update := scbus.MakeUpdatePacket()
|
|
||||||
update.AddUpdate(*newScreen)
|
|
||||||
if isActive {
|
|
||||||
bareSession, err := GetBareSessionById(ctx, sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
update.AddUpdate(*bareSession)
|
|
||||||
}
|
|
||||||
return update, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) error {
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ? AND archived`
|
|
||||||
if !tx.Exists(query, sessionId, screenId) {
|
|
||||||
return fmt.Errorf("cannot re-open screen (not found or not archived)")
|
|
||||||
}
|
|
||||||
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
|
|
||||||
query = `UPDATE screen SET archived = 0, screenidx = ? WHERE sessionid = ? AND screenid = ?`
|
|
||||||
tx.Exec(query, maxScreenIdx+1, sessionId, screenId)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return txErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// if sessionDel is passed, we do *not* delete the screen directory (session delete will handle that)
|
|
||||||
func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update *scbus.ModelUpdatePacketType) (*scbus.ModelUpdatePacketType, error) {
|
|
||||||
var sessionId string
|
|
||||||
var isActive bool
|
|
||||||
var screenTombstone *ScreenTombstoneType
|
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
screen, err := GetScreenById(tx.Context(), screenId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot get screen to delete: %w", err)
|
|
||||||
}
|
|
||||||
if screen == nil {
|
|
||||||
return fmt.Errorf("cannot delete screen (not found)")
|
|
||||||
}
|
|
||||||
webSharing := isWebShare(tx, screenId)
|
|
||||||
if !sessionDel {
|
|
||||||
query := `SELECT sessionid FROM screen WHERE screenid = ?`
|
|
||||||
sessionId = tx.GetString(query, screenId)
|
|
||||||
if sessionId == "" {
|
|
||||||
return fmt.Errorf("cannot delete screen (no sessionid)")
|
|
||||||
}
|
|
||||||
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
|
|
||||||
numScreens := tx.GetInt(query, sessionId)
|
|
||||||
if numScreens <= 1 {
|
|
||||||
return fmt.Errorf("cannot delete the last screen in a session")
|
|
||||||
}
|
|
||||||
isActive = tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, sessionId, screenId)
|
|
||||||
if isActive {
|
|
||||||
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, sessionId)
|
|
||||||
nextId := getNextId(screenIds, screenId)
|
|
||||||
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screenTombstone = &ScreenTombstoneType{
|
|
||||||
ScreenId: screen.ScreenId,
|
|
||||||
SessionId: screen.SessionId,
|
|
||||||
Name: screen.Name,
|
|
||||||
DeletedTs: time.Now().UnixMilli(),
|
|
||||||
ScreenOpts: screen.ScreenOpts,
|
|
||||||
}
|
|
||||||
query := `INSERT INTO screen_tombstone ( screenid, sessionid, name, deletedts, screenopts)
|
|
||||||
VALUES (:screenid,:sessionid,:name,:deletedts,:screenopts)`
|
|
||||||
tx.NamedExec(query, dbutil.ToDBMap(screenTombstone, false))
|
|
||||||
query = `DELETE FROM screen WHERE screenid = ?`
|
|
||||||
tx.Exec(query, screenId)
|
|
||||||
query = `DELETE FROM line WHERE screenid = ?`
|
|
||||||
tx.Exec(query, screenId)
|
|
||||||
query = `DELETE FROM cmd WHERE screenid = ?`
|
|
||||||
tx.Exec(query, screenId)
|
|
||||||
query = `UPDATE history SET lineid = '', linenum = 0 WHERE screenid = ?`
|
|
||||||
tx.Exec(query, screenId)
|
|
||||||
if webSharing {
|
|
||||||
insertScreenDelUpdate(tx, screenId)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
return nil, txErr
|
|
||||||
}
|
|
||||||
if !sessionDel {
|
|
||||||
GoDeleteScreenDirs(screenId)
|
|
||||||
}
|
|
||||||
if update == nil {
|
|
||||||
update = scbus.MakeUpdatePacket()
|
|
||||||
}
|
|
||||||
update.AddUpdate(*screenTombstone)
|
|
||||||
update.AddUpdate(ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true})
|
|
||||||
if isActive {
|
|
||||||
bareSession, err := GetBareSessionById(ctx, sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
update.AddUpdate(*bareSession)
|
|
||||||
}
|
|
||||||
return update, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRemoteState(ctx context.Context, sessionId string, screenId string, remotePtr RemotePtrType) (*packet.ShellState, *ShellStatePtr, error) {
|
func GetRemoteState(ctx context.Context, sessionId string, screenId string, remotePtr RemotePtrType) (*packet.ShellState, *ShellStatePtr, error) {
|
||||||
ssptr, err := GetRemoteStatePtr(ctx, sessionId, screenId, remotePtr)
|
ssptr, err := GetRemoteStatePtr(ctx, sessionId, screenId, remotePtr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1412,7 +910,7 @@ func UpdateCmdTermOpts(ctx context.Context, screenId string, lineId string, term
|
|||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE cmd SET termopts = ? WHERE screenid = ? AND lineid = ?`
|
query := `UPDATE cmd SET termopts = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, termOpts, screenId, lineId)
|
tx.Exec(query, termOpts, screenId, lineId)
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdTermOpts)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_CmdTermOpts)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return txErr
|
return txErr
|
||||||
@ -1696,7 +1194,7 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter
|
|||||||
if sline, found := editMap[ScreenField_SelectedLine]; found {
|
if sline, found := editMap[ScreenField_SelectedLine]; found {
|
||||||
query = `UPDATE screen SET selectedline = ? WHERE screenid = ?`
|
query = `UPDATE screen SET selectedline = ? WHERE screenid = ?`
|
||||||
tx.Exec(query, sline, screenId)
|
tx.Exec(query, sline, screenId)
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
insertScreenUpdate(tx, screenId, UpdateType_ScreenSelectedLine)
|
insertScreenUpdate(tx, screenId, UpdateType_ScreenSelectedLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1721,7 +1219,7 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter
|
|||||||
tx.Exec(query, name, screenId)
|
tx.Exec(query, name, screenId)
|
||||||
}
|
}
|
||||||
if shareName, found := editMap[ScreenField_ShareName]; found {
|
if shareName, found := editMap[ScreenField_ShareName]; found {
|
||||||
if !isWebShare(tx, screenId) {
|
if !IsWebShare(tx, screenId) {
|
||||||
return fmt.Errorf("cannot set sharename, screen is not web-shared")
|
return fmt.Errorf("cannot set sharename, screen is not web-shared")
|
||||||
}
|
}
|
||||||
query = `UPDATE screen SET webshareopts = json_set(webshareopts, '$.sharename', ?) WHERE screenid = ?`
|
query = `UPDATE screen SET webshareopts = json_set(webshareopts, '$.sharename', ?) WHERE screenid = ?`
|
||||||
@ -1961,8 +1459,8 @@ func UpdateLineHeight(ctx context.Context, screenId string, lineId string, heigh
|
|||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE line SET contentheight = ? WHERE screenid = ? AND lineid = ?`
|
query := `UPDATE line SET contentheight = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, heightVal, screenId, lineId)
|
tx.Exec(query, heightVal, screenId, lineId)
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineContentHeight)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineContentHeight)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -1976,8 +1474,8 @@ func UpdateLineRenderer(ctx context.Context, screenId string, lineId string, ren
|
|||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
return WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE line SET renderer = ? WHERE screenid = ? AND lineid = ?`
|
query := `UPDATE line SET renderer = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, renderer, screenId, lineId)
|
tx.Exec(query, renderer, screenId, lineId)
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineRenderer)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineRenderer)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -1991,8 +1489,8 @@ func UpdateLineState(ctx context.Context, screenId string, lineId string, lineSt
|
|||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
return WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE line SET linestate = ? WHERE screenid = ? AND lineid = ?`
|
query := `UPDATE line SET linestate = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, qjs, screenId, lineId)
|
tx.Exec(query, qjs, screenId, lineId)
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineState)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineState)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -2011,11 +1509,11 @@ func SetLineArchivedById(ctx context.Context, screenId string, lineId string, ar
|
|||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `UPDATE line SET archived = ? WHERE screenid = ? AND lineid = ?`
|
query := `UPDATE line SET archived = ? WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, archived, screenId, lineId)
|
tx.Exec(query, archived, screenId, lineId)
|
||||||
if isWebShare(tx, screenId) {
|
if IsWebShare(tx, screenId) {
|
||||||
if archived {
|
if archived {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineDel)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineDel)
|
||||||
} else {
|
} else {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineNew)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineNew)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -2061,7 +1559,7 @@ func FixupScreenSelectedLine(ctx context.Context, screenId string) (*ScreenType,
|
|||||||
|
|
||||||
func DeleteLinesByIds(ctx context.Context, screenId string, lineIds []string) error {
|
func DeleteLinesByIds(ctx context.Context, screenId string, lineIds []string) error {
|
||||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
isWS := isWebShare(tx, screenId)
|
isWS := IsWebShare(tx, screenId)
|
||||||
for _, lineId := range lineIds {
|
for _, lineId := range lineIds {
|
||||||
query := `SELECT status FROM cmd WHERE screenid = ? AND lineid = ?`
|
query := `SELECT status FROM cmd WHERE screenid = ? AND lineid = ?`
|
||||||
cmdStatus := tx.GetString(query, screenId, lineId)
|
cmdStatus := tx.GetString(query, screenId, lineId)
|
||||||
@ -2076,7 +1574,7 @@ func DeleteLinesByIds(ctx context.Context, screenId string, lineIds []string) er
|
|||||||
query = `UPDATE history SET lineid = '', linenum = 0 WHERE screenid = ? AND lineid = ?`
|
query = `UPDATE history SET lineid = '', linenum = 0 WHERE screenid = ? AND lineid = ?`
|
||||||
tx.Exec(query, screenId, lineId)
|
tx.Exec(query, screenId, lineId)
|
||||||
if isWS {
|
if isWS {
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineDel)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineDel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -2210,84 +1708,48 @@ func CountScreenLines(ctx context.Context, screenId string) (int, error) {
|
|||||||
// return nil
|
// return nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenWebShareOpts) error {
|
// func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenWebShareOpts) error {
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
// return WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `SELECT screenid FROM screen WHERE screenid = ?`
|
// query := `SELECT screenid FROM screen WHERE screenid = ?`
|
||||||
if !tx.Exists(query, screenId) {
|
// if !tx.Exists(query, screenId) {
|
||||||
return fmt.Errorf("screen does not exist")
|
// return fmt.Errorf("screen does not exist")
|
||||||
}
|
// }
|
||||||
shareMode := tx.GetString(`SELECT sharemode FROM screen WHERE screenid = ?`, screenId)
|
// shareMode := tx.GetString(`SELECT sharemode FROM screen WHERE screenid = ?`, screenId)
|
||||||
if shareMode == ShareModeWeb {
|
// if shareMode == ShareModeWeb {
|
||||||
return fmt.Errorf("screen is already shared to web")
|
// return fmt.Errorf("screen is already shared to web")
|
||||||
}
|
// }
|
||||||
if shareMode != ShareModeLocal {
|
// if shareMode != ShareModeLocal {
|
||||||
return fmt.Errorf("screen cannot be shared, invalid current share mode %q (must be local)", shareMode)
|
// return fmt.Errorf("screen cannot be shared, invalid current share mode %q (must be local)", shareMode)
|
||||||
}
|
// }
|
||||||
query = `UPDATE screen SET sharemode = ?, webshareopts = ? WHERE screenid = ?`
|
// query = `UPDATE screen SET sharemode = ?, webshareopts = ? WHERE screenid = ?`
|
||||||
tx.Exec(query, ShareModeWeb, quickJson(shareOpts), screenId)
|
// tx.Exec(query, ShareModeWeb, quickJson(shareOpts), screenId)
|
||||||
insertScreenNewUpdate(tx, screenId)
|
// insertScreenNewUpdate(tx, screenId)
|
||||||
return nil
|
// return nil
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
func ScreenWebShareStop(ctx context.Context, screenId string) error {
|
// func ScreenWebShareStop(ctx context.Context, screenId string) error {
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
// return WithTx(ctx, func(tx *TxWrap) error {
|
||||||
query := `SELECT screenid FROM screen WHERE screenid = ?`
|
// query := `SELECT screenid FROM screen WHERE screenid = ?`
|
||||||
if !tx.Exists(query, screenId) {
|
// if !tx.Exists(query, screenId) {
|
||||||
return fmt.Errorf("screen does not exist")
|
// return fmt.Errorf("screen does not exist")
|
||||||
}
|
// }
|
||||||
shareMode := tx.GetString(`SELECT sharemode FROM screen WHERE screenid = ?`, screenId)
|
// shareMode := tx.GetString(`SELECT sharemode FROM screen WHERE screenid = ?`, screenId)
|
||||||
if shareMode != ShareModeWeb {
|
// if shareMode != ShareModeWeb {
|
||||||
return fmt.Errorf("screen is not currently shared to the web")
|
// return fmt.Errorf("screen is not currently shared to the web")
|
||||||
}
|
// }
|
||||||
query = `UPDATE screen SET sharemode = ?, webshareopts = ? WHERE screenid = ?`
|
// query = `UPDATE screen SET sharemode = ?, webshareopts = ? WHERE screenid = ?`
|
||||||
tx.Exec(query, ShareModeLocal, "null", screenId)
|
// tx.Exec(query, ShareModeLocal, "null", screenId)
|
||||||
handleScreenDelUpdate(tx, screenId)
|
// handleScreenDelUpdate(tx, screenId)
|
||||||
return nil
|
// return nil
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
func isWebShare(tx *TxWrap, screenId string) bool {
|
func IsWebShare(tx *TxWrap, screenId string) bool {
|
||||||
return tx.Exists(`SELECT screenid FROM screen WHERE screenid = ? AND sharemode = ?`, screenId, ShareModeWeb)
|
return tx.Exists(`SELECT screenid FROM screen WHERE screenid = ? AND sharemode = ?`, screenId, ShareModeWeb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertScreenUpdate(tx *TxWrap, screenId string, updateType string) {
|
func InsertScreenLineUpdate(tx *TxWrap, screenId string, lineId string, updateType string) {
|
||||||
if screenId == "" {
|
|
||||||
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nowTs := time.Now().UnixMilli()
|
|
||||||
query := `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets) VALUES (?, ?, ?, ?)`
|
|
||||||
tx.Exec(query, screenId, "", updateType, nowTs)
|
|
||||||
NotifyUpdateWriter()
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertScreenNewUpdate(tx *TxWrap, screenId string) {
|
|
||||||
nowTs := time.Now().UnixMilli()
|
|
||||||
query := `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets)
|
|
||||||
SELECT screenid, lineid, ?, ? FROM line WHERE screenid = ? AND NOT archived ORDER BY linenum DESC`
|
|
||||||
tx.Exec(query, UpdateType_LineNew, nowTs, screenId)
|
|
||||||
query = `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets)
|
|
||||||
SELECT c.screenid, c.lineid, ?, ? FROM cmd c, line l WHERE c.screenid = ? AND l.lineid = c.lineid AND NOT l.archived ORDER BY l.linenum DESC`
|
|
||||||
tx.Exec(query, UpdateType_PtyPos, nowTs, screenId)
|
|
||||||
NotifyUpdateWriter()
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleScreenDelUpdate(tx *TxWrap, screenId string) {
|
|
||||||
query := `DELETE FROM screenupdate WHERE screenid = ?`
|
|
||||||
tx.Exec(query, screenId)
|
|
||||||
query = `DELETE FROM webptypos WHERE screenid = ?`
|
|
||||||
tx.Exec(query, screenId)
|
|
||||||
// don't insert UpdateType_ScreenDel (we already processed it in cmdrunner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertScreenDelUpdate(tx *TxWrap, screenId string) {
|
|
||||||
handleScreenDelUpdate(tx, screenId)
|
|
||||||
insertScreenUpdate(tx, screenId, UpdateType_ScreenDel)
|
|
||||||
// don't insert UpdateType_ScreenDel (we already processed it in cmdrunner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertScreenLineUpdate(tx *TxWrap, screenId string, lineId string, updateType string) {
|
|
||||||
if screenId == "" {
|
if screenId == "" {
|
||||||
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
|
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
|
||||||
return
|
return
|
||||||
@ -2308,47 +1770,12 @@ func insertScreenLineUpdate(tx *TxWrap, screenId string, lineId string, updateTy
|
|||||||
NotifyUpdateWriter()
|
NotifyUpdateWriter()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetScreenUpdates(ctx context.Context, maxNum int) ([]*ScreenUpdateType, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenUpdateType, error) {
|
|
||||||
var updates []*ScreenUpdateType
|
|
||||||
query := `SELECT * FROM screenupdate ORDER BY updateid LIMIT ?`
|
|
||||||
tx.Select(&updates, query, maxNum)
|
|
||||||
return updates, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveScreenUpdate(ctx context.Context, updateId int64) error {
|
|
||||||
if updateId < 0 {
|
|
||||||
return nil // in-memory updates (not from DB)
|
|
||||||
}
|
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `DELETE FROM screenupdate WHERE updateid = ?`
|
|
||||||
tx.Exec(query, updateId)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func CountScreenUpdates(ctx context.Context) (int, error) {
|
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (int, error) {
|
|
||||||
query := `SELECT count(*) FROM screenupdate`
|
|
||||||
return tx.GetInt(query), nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveScreenUpdates(ctx context.Context, updateIds []int64) error {
|
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
|
||||||
query := `DELETE FROM screenupdate WHERE updateid IN (SELECT value FROM json_each(?))`
|
|
||||||
tx.Exec(query, quickJsonArr(updateIds))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func MaybeInsertPtyPosUpdate(ctx context.Context, screenId string, lineId string) error {
|
func MaybeInsertPtyPosUpdate(ctx context.Context, screenId string, lineId string) error {
|
||||||
return WithTx(ctx, func(tx *TxWrap) error {
|
return WithTx(ctx, func(tx *TxWrap) error {
|
||||||
if !isWebShare(tx, screenId) {
|
if !IsWebShare(tx, screenId) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_PtyPos)
|
InsertScreenLineUpdate(tx, screenId, lineId, UpdateType_PtyPos)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,8 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||||
@ -126,69 +124,6 @@ func ReadPtyOutFile(ctx context.Context, screenId string, lineId string, offset
|
|||||||
return f.ReadAtWithMax(ctx, offset, maxSize)
|
return f.ReadAtWithMax(ctx, offset, maxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionDiskSizeType struct {
|
|
||||||
NumFiles int
|
|
||||||
TotalSize int64
|
|
||||||
ErrorCount int
|
|
||||||
Location string
|
|
||||||
}
|
|
||||||
|
|
||||||
func directorySize(dirName string) (SessionDiskSizeType, error) {
|
|
||||||
var rtn SessionDiskSizeType
|
|
||||||
rtn.Location = dirName
|
|
||||||
entries, err := os.ReadDir(dirName)
|
|
||||||
if err != nil {
|
|
||||||
return rtn, err
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
rtn.ErrorCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
finfo, err := entry.Info()
|
|
||||||
if err != nil {
|
|
||||||
rtn.ErrorCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rtn.NumFiles++
|
|
||||||
rtn.TotalSize += finfo.Size()
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SessionDiskSize(sessionId string) (SessionDiskSizeType, error) {
|
|
||||||
sessionDir, err := scbase.EnsureSessionDir(sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return SessionDiskSizeType{}, err
|
|
||||||
}
|
|
||||||
return directorySize(sessionDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FullSessionDiskSize() (map[string]SessionDiskSizeType, error) {
|
|
||||||
sdir := scbase.GetSessionsDir()
|
|
||||||
entries, err := os.ReadDir(sdir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rtn := make(map[string]SessionDiskSizeType)
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := entry.Name()
|
|
||||||
_, err = uuid.Parse(name)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
diskSize, err := directorySize(path.Join(sdir, name))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rtn[name] = diskSize
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeletePtyOutFile(ctx context.Context, screenId string, lineId string) error {
|
func DeletePtyOutFile(ctx context.Context, screenId string, lineId string) error {
|
||||||
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -127,11 +127,6 @@ const (
|
|||||||
RemoteTypeOpenAI = "openai"
|
RemoteTypeOpenAI = "openai"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
ScreenFocusInput = "input"
|
|
||||||
ScreenFocusCmd = "cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CmdStoreTypeSession = "session"
|
CmdStoreTypeSession = "session"
|
||||||
CmdStoreTypeScreen = "screen"
|
CmdStoreTypeScreen = "screen"
|
||||||
@ -300,198 +295,6 @@ func (ClientData) GetType() string {
|
|||||||
return "clientdata"
|
return "clientdata"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionType struct {
|
|
||||||
SessionId string `json:"sessionid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
SessionIdx int64 `json:"sessionidx"`
|
|
||||||
ActiveScreenId string `json:"activescreenid"`
|
|
||||||
ShareMode string `json:"sharemode"`
|
|
||||||
NotifyNum int64 `json:"notifynum"`
|
|
||||||
Archived bool `json:"archived,omitempty"`
|
|
||||||
ArchivedTs int64 `json:"archivedts,omitempty"`
|
|
||||||
Remotes []*RemoteInstance `json:"remotes"`
|
|
||||||
|
|
||||||
// only for updates
|
|
||||||
Remove bool `json:"remove,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (SessionType) GetType() string {
|
|
||||||
return "session"
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeSessionUpdateForRemote(sessionId string, ri *RemoteInstance) SessionType {
|
|
||||||
return SessionType{
|
|
||||||
SessionId: sessionId,
|
|
||||||
Remotes: []*RemoteInstance{ri},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SessionTombstoneType struct {
|
|
||||||
SessionId string `json:"sessionid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
DeletedTs int64 `json:"deletedts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (SessionTombstoneType) UseDBMap() {}
|
|
||||||
|
|
||||||
func (SessionTombstoneType) GetType() string {
|
|
||||||
return "sessiontombstone"
|
|
||||||
}
|
|
||||||
|
|
||||||
type SessionStatsType struct {
|
|
||||||
SessionId string `json:"sessionid"`
|
|
||||||
NumScreens int `json:"numscreens"`
|
|
||||||
NumArchivedScreens int `json:"numarchivedscreens"`
|
|
||||||
NumLines int `json:"numlines"`
|
|
||||||
NumCmds int `json:"numcmds"`
|
|
||||||
DiskStats SessionDiskSizeType `json:"diskstats"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenOptsType struct {
|
|
||||||
TabColor string `json:"tabcolor,omitempty"`
|
|
||||||
TabIcon string `json:"tabicon,omitempty"`
|
|
||||||
PTerm string `json:"pterm,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenLinesType struct {
|
|
||||||
ScreenId string `json:"screenid"`
|
|
||||||
Lines []*LineType `json:"lines" dbmap:"-"`
|
|
||||||
Cmds []*CmdType `json:"cmds" dbmap:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ScreenLinesType) UseDBMap() {}
|
|
||||||
|
|
||||||
func (ScreenLinesType) GetType() string {
|
|
||||||
return "screenlines"
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenWebShareOpts struct {
|
|
||||||
ShareName string `json:"sharename"`
|
|
||||||
ViewKey string `json:"viewkey"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenCreateOpts struct {
|
|
||||||
BaseScreenId string
|
|
||||||
CopyRemote bool
|
|
||||||
CopyCwd bool
|
|
||||||
CopyEnv bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sco ScreenCreateOpts) HasCopy() bool {
|
|
||||||
return sco.CopyRemote || sco.CopyCwd || sco.CopyEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenSidebarOptsType struct {
|
|
||||||
Open bool `json:"open,omitempty"`
|
|
||||||
Width string `json:"width,omitempty"`
|
|
||||||
|
|
||||||
// this used to be more complicated (sections with types). simplified for this release
|
|
||||||
SidebarLineId string `json:"sidebarlineid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenViewOptsType struct {
|
|
||||||
Sidebar *ScreenSidebarOptsType `json:"sidebar,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenType struct {
|
|
||||||
SessionId string `json:"sessionid"`
|
|
||||||
ScreenId string `json:"screenid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
ScreenIdx int64 `json:"screenidx"`
|
|
||||||
ScreenOpts ScreenOptsType `json:"screenopts"`
|
|
||||||
ScreenViewOpts ScreenViewOptsType `json:"screenviewopts"`
|
|
||||||
OwnerId string `json:"ownerid"`
|
|
||||||
ShareMode string `json:"sharemode"`
|
|
||||||
WebShareOpts *ScreenWebShareOpts `json:"webshareopts,omitempty"`
|
|
||||||
CurRemote RemotePtrType `json:"curremote"`
|
|
||||||
NextLineNum int64 `json:"nextlinenum"`
|
|
||||||
SelectedLine int64 `json:"selectedline"`
|
|
||||||
Anchor ScreenAnchorType `json:"anchor"`
|
|
||||||
FocusType string `json:"focustype"`
|
|
||||||
Archived bool `json:"archived,omitempty"`
|
|
||||||
ArchivedTs int64 `json:"archivedts,omitempty"`
|
|
||||||
|
|
||||||
// only for updates
|
|
||||||
Remove bool `json:"remove,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScreenType) ToMap() map[string]interface{} {
|
|
||||||
rtn := make(map[string]interface{})
|
|
||||||
rtn["sessionid"] = s.SessionId
|
|
||||||
rtn["screenid"] = s.ScreenId
|
|
||||||
rtn["name"] = s.Name
|
|
||||||
rtn["screenidx"] = s.ScreenIdx
|
|
||||||
rtn["screenopts"] = quickJson(s.ScreenOpts)
|
|
||||||
rtn["screenviewopts"] = quickJson(s.ScreenViewOpts)
|
|
||||||
rtn["ownerid"] = s.OwnerId
|
|
||||||
rtn["sharemode"] = s.ShareMode
|
|
||||||
rtn["webshareopts"] = quickNullableJson(s.WebShareOpts)
|
|
||||||
rtn["curremoteownerid"] = s.CurRemote.OwnerId
|
|
||||||
rtn["curremoteid"] = s.CurRemote.RemoteId
|
|
||||||
rtn["curremotename"] = s.CurRemote.Name
|
|
||||||
rtn["nextlinenum"] = s.NextLineNum
|
|
||||||
rtn["selectedline"] = s.SelectedLine
|
|
||||||
rtn["anchor"] = quickJson(s.Anchor)
|
|
||||||
rtn["focustype"] = s.FocusType
|
|
||||||
rtn["archived"] = s.Archived
|
|
||||||
rtn["archivedts"] = s.ArchivedTs
|
|
||||||
return rtn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScreenType) FromMap(m map[string]interface{}) bool {
|
|
||||||
quickSetStr(&s.SessionId, m, "sessionid")
|
|
||||||
quickSetStr(&s.ScreenId, m, "screenid")
|
|
||||||
quickSetStr(&s.Name, m, "name")
|
|
||||||
quickSetInt64(&s.ScreenIdx, m, "screenidx")
|
|
||||||
quickSetJson(&s.ScreenOpts, m, "screenopts")
|
|
||||||
quickSetJson(&s.ScreenViewOpts, m, "screenviewopts")
|
|
||||||
quickSetStr(&s.OwnerId, m, "ownerid")
|
|
||||||
quickSetStr(&s.ShareMode, m, "sharemode")
|
|
||||||
quickSetNullableJson(&s.WebShareOpts, m, "webshareopts")
|
|
||||||
quickSetStr(&s.CurRemote.OwnerId, m, "curremoteownerid")
|
|
||||||
quickSetStr(&s.CurRemote.RemoteId, m, "curremoteid")
|
|
||||||
quickSetStr(&s.CurRemote.Name, m, "curremotename")
|
|
||||||
quickSetInt64(&s.NextLineNum, m, "nextlinenum")
|
|
||||||
quickSetInt64(&s.SelectedLine, m, "selectedline")
|
|
||||||
quickSetJson(&s.Anchor, m, "anchor")
|
|
||||||
quickSetStr(&s.FocusType, m, "focustype")
|
|
||||||
quickSetBool(&s.Archived, m, "archived")
|
|
||||||
quickSetInt64(&s.ArchivedTs, m, "archivedts")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ScreenType) GetType() string {
|
|
||||||
return "screen"
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddScreenUpdate(update *scbus.ModelUpdatePacketType, newScreen *ScreenType) {
|
|
||||||
if newScreen == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
screenUpdates := scbus.GetUpdateItems[ScreenType](update)
|
|
||||||
for _, screenUpdate := range screenUpdates {
|
|
||||||
if screenUpdate.ScreenId == newScreen.ScreenId {
|
|
||||||
screenUpdate = newScreen
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update.AddUpdate(newScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenTombstoneType struct {
|
|
||||||
ScreenId string `json:"screenid"`
|
|
||||||
SessionId string `json:"sessionid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
DeletedTs int64 `json:"deletedts"`
|
|
||||||
ScreenOpts ScreenOptsType `json:"screenopts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ScreenTombstoneType) UseDBMap() {}
|
|
||||||
|
|
||||||
func (ScreenTombstoneType) GetType() string {
|
|
||||||
return "screentombstone"
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LayoutFull = "full"
|
LayoutFull = "full"
|
||||||
)
|
)
|
||||||
@ -517,11 +320,6 @@ func (l LayoutType) Value() (driver.Value, error) {
|
|||||||
return quickValueJson(l)
|
return quickValueJson(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScreenAnchorType struct {
|
|
||||||
AnchorLine int `json:"anchorline,omitempty"`
|
|
||||||
AnchorOffset int `json:"anchoroffset,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TermOpts struct {
|
type TermOpts struct {
|
||||||
Rows int64 `json:"rows"`
|
Rows int64 `json:"rows"`
|
||||||
Cols int64 `json:"cols"`
|
Cols int64 `json:"cols"`
|
||||||
@ -654,16 +452,6 @@ func (ri *RemoteInstance) ToMap() map[string]interface{} {
|
|||||||
return rtn
|
return rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScreenUpdateType struct {
|
|
||||||
UpdateId int64 `json:"updateid"`
|
|
||||||
ScreenId string `json:"screenid"`
|
|
||||||
LineId string `json:"lineid"`
|
|
||||||
UpdateType string `json:"updatetype"`
|
|
||||||
UpdateTs int64 `json:"updatets"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ScreenUpdateType) UseDBMap() {}
|
|
||||||
|
|
||||||
type LineType struct {
|
type LineType struct {
|
||||||
ScreenId string `json:"screenid"`
|
ScreenId string `json:"screenid"`
|
||||||
UserId string `json:"userid"`
|
UserId string `json:"userid"`
|
||||||
|
@ -11,12 +11,6 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActiveSessionIdUpdate string
|
|
||||||
|
|
||||||
func (ActiveSessionIdUpdate) GetType() string {
|
|
||||||
return "activesessionid"
|
|
||||||
}
|
|
||||||
|
|
||||||
type LineUpdate struct {
|
type LineUpdate struct {
|
||||||
Line LineType `json:"line"`
|
Line LineType `json:"line"`
|
||||||
Cmd CmdType `json:"cmd,omitempty"`
|
Cmd CmdType `json:"cmd,omitempty"`
|
||||||
@ -97,19 +91,6 @@ func (InteractiveUpdate) GetType() string {
|
|||||||
return "interactive"
|
return "interactive"
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectUpdate struct {
|
|
||||||
Sessions []*SessionType `json:"sessions,omitempty"`
|
|
||||||
Screens []*ScreenType `json:"screens,omitempty"`
|
|
||||||
Remotes []*RemoteRuntimeState `json:"remotes,omitempty"`
|
|
||||||
ScreenStatusIndicators []*ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"`
|
|
||||||
ScreenNumRunningCommands []*ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"`
|
|
||||||
ActiveSessionId string `json:"activesessionid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ConnectUpdate) GetType() string {
|
|
||||||
return "connect"
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteEditType struct {
|
type RemoteEditType struct {
|
||||||
RemoteEdit bool `json:"remoteedit"`
|
RemoteEdit bool `json:"remoteedit"`
|
||||||
RemoteId string `json:"remoteid,omitempty"`
|
RemoteId string `json:"remoteid,omitempty"`
|
||||||
|
197
wavesrv/pkg/workspaces/screen/dbops.go
Normal file
197
wavesrv/pkg/workspaces/screen/dbops.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package screen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (*ScreenType, error) {
|
||||||
|
query := `SELECT * FROM screen WHERE screenid = ?`
|
||||||
|
screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId)
|
||||||
|
return screen, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (*ScreenLinesType, error) {
|
||||||
|
query := `SELECT screenid FROM screen WHERE screenid = ?`
|
||||||
|
screen := dbutil.GetMappable[*ScreenLinesType](tx, query, screenId)
|
||||||
|
if screen == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
query = `SELECT * FROM line WHERE screenid = ? ORDER BY linenum`
|
||||||
|
screen.Lines = dbutil.SelectMappable[*sstore.LineType](tx, query, screen.ScreenId)
|
||||||
|
query = `SELECT * FROM cmd WHERE screenid = ?`
|
||||||
|
screen.Cmds = dbutil.SelectMapsGen[*sstore.CmdType](tx, query, screen.ScreenId)
|
||||||
|
return screen, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// includes archived screens
|
||||||
|
func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) ([]*ScreenType, error) {
|
||||||
|
query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts`
|
||||||
|
rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId)
|
||||||
|
return rtn, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// screen may not exist at this point (so don't query screen table)
|
||||||
|
func cleanScreenCmds(ctx context.Context, screenId string) error {
|
||||||
|
var removedCmds []string
|
||||||
|
txErr := WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT lineid FROM cmd WHERE screenid = ? AND lineid NOT IN (SELECT lineid FROM line WHERE screenid = ?)`
|
||||||
|
removedCmds = tx.SelectStrings(query, screenId, screenId)
|
||||||
|
query = `DELETE FROM cmd WHERE screenid = ? AND lineid NOT IN (SELECT lineid FROM line WHERE screenid = ?)`
|
||||||
|
tx.Exec(query, screenId, screenId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
for _, lineId := range removedCmds {
|
||||||
|
DeletePtyOutFile(ctx, screenId, lineId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (scbus.UpdatePacket, error) {
|
||||||
|
var isActive bool
|
||||||
|
txErr := WithTx(ctx, func(tx *sstore.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)")
|
||||||
|
}
|
||||||
|
if isWebShare(tx, screenId) {
|
||||||
|
return fmt.Errorf("cannot archive screen while web-sharing. stop web-sharing before trying to archive.")
|
||||||
|
}
|
||||||
|
query = `SELECT archived FROM screen WHERE sessionid = ? AND screenid = ?`
|
||||||
|
closeVal := tx.GetBool(query, sessionId, screenId)
|
||||||
|
if closeVal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
|
||||||
|
numScreens := tx.GetInt(query, sessionId)
|
||||||
|
if numScreens <= 1 {
|
||||||
|
return fmt.Errorf("cannot archive the last screen in a session")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if isActive {
|
||||||
|
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, sessionId)
|
||||||
|
nextId := getNextId(screenIds, screenId)
|
||||||
|
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
newScreen, err := GetScreenById(ctx, screenId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot retrive archived screen: %w", err)
|
||||||
|
}
|
||||||
|
update := scbus.MakeUpdatePacket()
|
||||||
|
update.AddUpdate(*newScreen)
|
||||||
|
if isActive {
|
||||||
|
bareSession, err := GetBareSessionById(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update.AddUpdate(*bareSession)
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) error {
|
||||||
|
txErr := WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ? AND archived`
|
||||||
|
if !tx.Exists(query, sessionId, screenId) {
|
||||||
|
return fmt.Errorf("cannot re-open screen (not found or not archived)")
|
||||||
|
}
|
||||||
|
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
|
||||||
|
query = `UPDATE screen SET archived = 0, screenidx = ? WHERE sessionid = ? AND screenid = ?`
|
||||||
|
tx.Exec(query, maxScreenIdx+1, sessionId, screenId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertScreenUpdate(tx *sstore.TxWrap, screenId string, updateType string) {
|
||||||
|
if screenId == "" {
|
||||||
|
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nowTs := time.Now().UnixMilli()
|
||||||
|
query := `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets) VALUES (?, ?, ?, ?)`
|
||||||
|
tx.Exec(query, screenId, "", updateType, nowTs)
|
||||||
|
sstore.NotifyUpdateWriter()
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertScreenNewUpdate(tx *sstore.TxWrap, screenId string) {
|
||||||
|
nowTs := time.Now().UnixMilli()
|
||||||
|
query := `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets)
|
||||||
|
SELECT screenid, lineid, ?, ? FROM line WHERE screenid = ? AND NOT archived ORDER BY linenum DESC`
|
||||||
|
tx.Exec(query, sstore.UpdateType_LineNew, nowTs, screenId)
|
||||||
|
query = `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets)
|
||||||
|
SELECT c.screenid, c.lineid, ?, ? FROM cmd c, line l WHERE c.screenid = ? AND l.lineid = c.lineid AND NOT l.archived ORDER BY l.linenum DESC`
|
||||||
|
tx.Exec(query, sstore.UpdateType_PtyPos, nowTs, screenId)
|
||||||
|
sstore.NotifyUpdateWriter()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleScreenDelUpdate(tx *sstore.TxWrap, screenId string) {
|
||||||
|
query := `DELETE FROM screenupdate WHERE screenid = ?`
|
||||||
|
tx.Exec(query, screenId)
|
||||||
|
query = `DELETE FROM webptypos WHERE screenid = ?`
|
||||||
|
tx.Exec(query, screenId)
|
||||||
|
// don't insert UpdateType_ScreenDel (we already processed it in cmdrunner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertScreenDelUpdate(tx *sstore.TxWrap, screenId string) {
|
||||||
|
handleScreenDelUpdate(tx, screenId)
|
||||||
|
insertScreenUpdate(tx, screenId, sstore.UpdateType_ScreenDel)
|
||||||
|
// don't insert UpdateType_ScreenDel (we already processed it in cmdrunner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetScreenUpdates(ctx context.Context, maxNum int) ([]*ScreenUpdateType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) ([]*ScreenUpdateType, error) {
|
||||||
|
var updates []*ScreenUpdateType
|
||||||
|
query := `SELECT * FROM screenupdate ORDER BY updateid LIMIT ?`
|
||||||
|
tx.Select(&updates, query, maxNum)
|
||||||
|
return updates, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveScreenUpdate(ctx context.Context, updateId int64) error {
|
||||||
|
if updateId < 0 {
|
||||||
|
return nil // in-memory updates (not from DB)
|
||||||
|
}
|
||||||
|
return sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `DELETE FROM screenupdate WHERE updateid = ?`
|
||||||
|
tx.Exec(query, updateId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CountScreenUpdates(ctx context.Context) (int, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (int, error) {
|
||||||
|
query := `SELECT count(*) FROM screenupdate`
|
||||||
|
return tx.GetInt(query), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveScreenUpdates(ctx context.Context, updateIds []int64) error {
|
||||||
|
return sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `DELETE FROM screenupdate WHERE updateid IN (SELECT value FROM json_each(?))`
|
||||||
|
tx.Exec(query, dbutil.QuickJsonArr(updateIds))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
162
wavesrv/pkg/workspaces/screen/screen.go
Normal file
162
wavesrv/pkg/workspaces/screen/screen.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package screen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScreenFocusInput = "input"
|
||||||
|
ScreenFocusCmd = "cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScreenOptsType struct {
|
||||||
|
TabColor string `json:"tabcolor,omitempty"`
|
||||||
|
TabIcon string `json:"tabicon,omitempty"`
|
||||||
|
PTerm string `json:"pterm,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenLinesType struct {
|
||||||
|
ScreenId string `json:"screenid"`
|
||||||
|
Lines []*sstore.LineType `json:"lines" dbmap:"-"`
|
||||||
|
Cmds []*sstore.CmdType `json:"cmds" dbmap:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ScreenLinesType) UseDBMap() {}
|
||||||
|
|
||||||
|
func (ScreenLinesType) GetType() string {
|
||||||
|
return "screenlines"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenWebShareOpts struct {
|
||||||
|
ShareName string `json:"sharename"`
|
||||||
|
ViewKey string `json:"viewkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenCreateOpts struct {
|
||||||
|
BaseScreenId string
|
||||||
|
CopyRemote bool
|
||||||
|
CopyCwd bool
|
||||||
|
CopyEnv bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sco ScreenCreateOpts) HasCopy() bool {
|
||||||
|
return sco.CopyRemote || sco.CopyCwd || sco.CopyEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenSidebarOptsType struct {
|
||||||
|
Open bool `json:"open,omitempty"`
|
||||||
|
Width string `json:"width,omitempty"`
|
||||||
|
|
||||||
|
// this used to be more complicated (sections with types). simplified for this release
|
||||||
|
SidebarLineId string `json:"sidebarlineid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenViewOptsType struct {
|
||||||
|
Sidebar *ScreenSidebarOptsType `json:"sidebar,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenAnchorType struct {
|
||||||
|
AnchorLine int `json:"anchorline,omitempty"`
|
||||||
|
AnchorOffset int `json:"anchoroffset,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenType struct {
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
ScreenId string `json:"screenid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ScreenIdx int64 `json:"screenidx"`
|
||||||
|
ScreenOpts ScreenOptsType `json:"screenopts"`
|
||||||
|
ScreenViewOpts ScreenViewOptsType `json:"screenviewopts"`
|
||||||
|
OwnerId string `json:"ownerid"`
|
||||||
|
ShareMode string `json:"sharemode"`
|
||||||
|
WebShareOpts *ScreenWebShareOpts `json:"webshareopts,omitempty"`
|
||||||
|
CurRemote sstore.RemotePtrType `json:"curremote"`
|
||||||
|
NextLineNum int64 `json:"nextlinenum"`
|
||||||
|
SelectedLine int64 `json:"selectedline"`
|
||||||
|
Anchor ScreenAnchorType `json:"anchor"`
|
||||||
|
FocusType string `json:"focustype"`
|
||||||
|
Archived bool `json:"archived,omitempty"`
|
||||||
|
ArchivedTs int64 `json:"archivedts,omitempty"`
|
||||||
|
|
||||||
|
// only for updates
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenType) ToMap() map[string]interface{} {
|
||||||
|
rtn := make(map[string]interface{})
|
||||||
|
rtn["sessionid"] = s.SessionId
|
||||||
|
rtn["screenid"] = s.ScreenId
|
||||||
|
rtn["name"] = s.Name
|
||||||
|
rtn["screenidx"] = s.ScreenIdx
|
||||||
|
rtn["screenopts"] = dbutil.QuickJson(s.ScreenOpts)
|
||||||
|
rtn["screenviewopts"] = dbutil.QuickJson(s.ScreenViewOpts)
|
||||||
|
rtn["ownerid"] = s.OwnerId
|
||||||
|
rtn["sharemode"] = s.ShareMode
|
||||||
|
rtn["webshareopts"] = dbutil.QuickNullableJson(s.WebShareOpts)
|
||||||
|
rtn["curremoteownerid"] = s.CurRemote.OwnerId
|
||||||
|
rtn["curremoteid"] = s.CurRemote.RemoteId
|
||||||
|
rtn["curremotename"] = s.CurRemote.Name
|
||||||
|
rtn["nextlinenum"] = s.NextLineNum
|
||||||
|
rtn["selectedline"] = s.SelectedLine
|
||||||
|
rtn["anchor"] = dbutil.QuickJson(s.Anchor)
|
||||||
|
rtn["focustype"] = s.FocusType
|
||||||
|
rtn["archived"] = s.Archived
|
||||||
|
rtn["archivedts"] = s.ArchivedTs
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScreenType) FromMap(m map[string]interface{}) bool {
|
||||||
|
dbutil.QuickSetStr(&s.SessionId, m, "sessionid")
|
||||||
|
dbutil.QuickSetStr(&s.ScreenId, m, "screenid")
|
||||||
|
dbutil.QuickSetStr(&s.Name, m, "name")
|
||||||
|
dbutil.QuickSetInt64(&s.ScreenIdx, m, "screenidx")
|
||||||
|
dbutil.QuickSetJson(&s.ScreenOpts, m, "screenopts")
|
||||||
|
dbutil.QuickSetJson(&s.ScreenViewOpts, m, "screenviewopts")
|
||||||
|
dbutil.QuickSetStr(&s.OwnerId, m, "ownerid")
|
||||||
|
dbutil.QuickSetStr(&s.ShareMode, m, "sharemode")
|
||||||
|
dbutil.QuickSetNullableJson(&s.WebShareOpts, m, "webshareopts")
|
||||||
|
dbutil.QuickSetStr(&s.CurRemote.OwnerId, m, "curremoteownerid")
|
||||||
|
dbutil.QuickSetStr(&s.CurRemote.RemoteId, m, "curremoteid")
|
||||||
|
dbutil.QuickSetStr(&s.CurRemote.Name, m, "curremotename")
|
||||||
|
dbutil.QuickSetInt64(&s.NextLineNum, m, "nextlinenum")
|
||||||
|
dbutil.QuickSetInt64(&s.SelectedLine, m, "selectedline")
|
||||||
|
dbutil.QuickSetJson(&s.Anchor, m, "anchor")
|
||||||
|
dbutil.QuickSetStr(&s.FocusType, m, "focustype")
|
||||||
|
dbutil.QuickSetBool(&s.Archived, m, "archived")
|
||||||
|
dbutil.QuickSetInt64(&s.ArchivedTs, m, "archivedts")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ScreenType) GetType() string {
|
||||||
|
return "screen"
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddScreenUpdate(update *scbus.ModelUpdatePacketType, newScreen *ScreenType) {
|
||||||
|
if newScreen == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
screenUpdates := scbus.GetUpdateItems[ScreenType](update)
|
||||||
|
for _, screenUpdate := range screenUpdates {
|
||||||
|
if screenUpdate.ScreenId == newScreen.ScreenId {
|
||||||
|
screenUpdate = newScreen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update.AddUpdate(newScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenTombstoneType struct {
|
||||||
|
ScreenId string `json:"screenid"`
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DeletedTs int64 `json:"deletedts"`
|
||||||
|
ScreenOpts ScreenOptsType `json:"screenopts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ScreenTombstoneType) UseDBMap() {}
|
||||||
|
|
||||||
|
func (ScreenTombstoneType) GetType() string {
|
||||||
|
return "screentombstone"
|
||||||
|
}
|
11
wavesrv/pkg/workspaces/screen/updatetypes.go
Normal file
11
wavesrv/pkg/workspaces/screen/updatetypes.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package screen
|
||||||
|
|
||||||
|
type ScreenUpdateType struct {
|
||||||
|
UpdateId int64 `json:"updateid"`
|
||||||
|
ScreenId string `json:"screenid"`
|
||||||
|
LineId string `json:"lineid"`
|
||||||
|
UpdateType string `json:"updatetype"`
|
||||||
|
UpdateTs int64 `json:"updatets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ScreenUpdateType) UseDBMap() {}
|
132
wavesrv/pkg/workspaces/session/dbops.go
Normal file
132
wavesrv/pkg/workspaces/session/dbops.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// includes archived sessions
|
||||||
|
func GetBareSessions(ctx context.Context) ([]*SessionType, error) {
|
||||||
|
var rtn []*SessionType
|
||||||
|
err := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
|
||||||
|
tx.Select(&rtn, query)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not include archived, finds lowest sessionidx (for resetting active session)
|
||||||
|
func GetFirstSessionId(ctx context.Context) (string, error) {
|
||||||
|
var rtn []string
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT sessionid from session WHERE NOT archived ORDER by sessionidx`
|
||||||
|
rtn = tx.SelectStrings(query)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return "", txErr
|
||||||
|
}
|
||||||
|
if len(rtn) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return rtn[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBareSessionById(ctx context.Context, sessionId string) (*SessionType, error) {
|
||||||
|
var rtn SessionType
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT * FROM session WHERE sessionid = ?`
|
||||||
|
tx.Get(&rtn, query, sessionId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
if rtn.SessionId == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetAllSessionsQuery = `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
|
||||||
|
|
||||||
|
// Gets all sessions, including archived
|
||||||
|
func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) ([]*SessionType, error) {
|
||||||
|
rtn := []*SessionType{}
|
||||||
|
tx.Select(&rtn, GetAllSessionsQuery)
|
||||||
|
return rtn, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSessionById(ctx context.Context, id string) (*SessionType, error) {
|
||||||
|
allSessions, err := GetAllSessions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, session := range allSessions {
|
||||||
|
if session.SessionId == id {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// counts non-archived sessions
|
||||||
|
func GetSessionCount(ctx context.Context) (int, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (int, error) {
|
||||||
|
query := `SELECT COALESCE(count(*), 0) FROM session WHERE NOT archived`
|
||||||
|
numSessions := tx.GetInt(query)
|
||||||
|
return numSessions, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSessionByName(ctx context.Context, name string) (*SessionType, error) {
|
||||||
|
var session *SessionType
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT sessionid FROM session WHERE name = ?`
|
||||||
|
sessionId := tx.GetString(query, name)
|
||||||
|
if sessionId == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
session, err = GetSessionById(tx.Context(), sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetActiveSessionId(ctx context.Context, sessionId string) error {
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActiveSessionId(ctx context.Context) (string, error) {
|
||||||
|
var rtnId string
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT activesessionid FROM client`
|
||||||
|
rtnId = tx.GetString(query)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return rtnId, txErr
|
||||||
|
}
|
72
wavesrv/pkg/workspaces/session/fileops.go
Normal file
72
wavesrv/pkg/workspaces/session/fileops.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionDiskSizeType struct {
|
||||||
|
NumFiles int
|
||||||
|
TotalSize int64
|
||||||
|
ErrorCount int
|
||||||
|
Location string
|
||||||
|
}
|
||||||
|
|
||||||
|
func directorySize(dirName string) (SessionDiskSizeType, error) {
|
||||||
|
var rtn SessionDiskSizeType
|
||||||
|
rtn.Location = dirName
|
||||||
|
entries, err := os.ReadDir(dirName)
|
||||||
|
if err != nil {
|
||||||
|
return rtn, err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
rtn.ErrorCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
finfo, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
rtn.ErrorCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rtn.NumFiles++
|
||||||
|
rtn.TotalSize += finfo.Size()
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionDiskSize(sessionId string) (SessionDiskSizeType, error) {
|
||||||
|
sessionDir, err := scbase.EnsureSessionDir(sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return SessionDiskSizeType{}, err
|
||||||
|
}
|
||||||
|
return directorySize(sessionDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FullSessionDiskSize() (map[string]SessionDiskSizeType, error) {
|
||||||
|
sdir := scbase.GetSessionsDir()
|
||||||
|
entries, err := os.ReadDir(sdir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rtn := make(map[string]SessionDiskSizeType)
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := entry.Name()
|
||||||
|
_, err = uuid.Parse(name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
diskSize, err := directorySize(path.Join(sdir, name))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rtn[name] = diskSize
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
50
wavesrv/pkg/workspaces/session/session.go
Normal file
50
wavesrv/pkg/workspaces/session/session.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
|
||||||
|
type SessionType struct {
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SessionIdx int64 `json:"sessionidx"`
|
||||||
|
ActiveScreenId string `json:"activescreenid"`
|
||||||
|
ShareMode string `json:"sharemode"`
|
||||||
|
NotifyNum int64 `json:"notifynum"`
|
||||||
|
Archived bool `json:"archived,omitempty"`
|
||||||
|
ArchivedTs int64 `json:"archivedts,omitempty"`
|
||||||
|
Remotes []*sstore.RemoteInstance `json:"remotes"`
|
||||||
|
|
||||||
|
// only for updates
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SessionType) GetType() string {
|
||||||
|
return "session"
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeSessionUpdateForRemote(sessionId string, ri *sstore.RemoteInstance) SessionType {
|
||||||
|
return SessionType{
|
||||||
|
SessionId: sessionId,
|
||||||
|
Remotes: []*sstore.RemoteInstance{ri},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionTombstoneType struct {
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DeletedTs int64 `json:"deletedts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SessionTombstoneType) UseDBMap() {}
|
||||||
|
|
||||||
|
func (SessionTombstoneType) GetType() string {
|
||||||
|
return "sessiontombstone"
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionStatsType struct {
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
NumScreens int `json:"numscreens"`
|
||||||
|
NumArchivedScreens int `json:"numarchivedscreens"`
|
||||||
|
NumLines int `json:"numlines"`
|
||||||
|
NumCmds int `json:"numcmds"`
|
||||||
|
DiskStats SessionDiskSizeType `json:"diskstats"`
|
||||||
|
}
|
7
wavesrv/pkg/workspaces/session/updatetypes.go
Normal file
7
wavesrv/pkg/workspaces/session/updatetypes.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
type ActiveSessionIdUpdate string
|
||||||
|
|
||||||
|
func (ActiveSessionIdUpdate) GetType() string {
|
||||||
|
return "activesessionid"
|
||||||
|
}
|
292
wavesrv/pkg/workspaces/workspaces.go
Normal file
292
wavesrv/pkg/workspaces/workspaces.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package workspaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/workspaces/screen"
|
||||||
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/workspaces/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectUpdate struct {
|
||||||
|
Sessions []*session.SessionType `json:"sessions,omitempty"`
|
||||||
|
Screens []*screen.ScreenType `json:"screens,omitempty"`
|
||||||
|
Remotes []*sstore.RemoteRuntimeState `json:"remotes,omitempty"`
|
||||||
|
ScreenStatusIndicators []*sstore.ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"`
|
||||||
|
ScreenNumRunningCommands []*sstore.ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"`
|
||||||
|
ActiveSessionId string `json:"activesessionid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ConnectUpdate) GetType() string {
|
||||||
|
return "connect"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all sessions and screens, including remotes
|
||||||
|
func GetConnectUpdate(ctx context.Context) (*ConnectUpdate, error) {
|
||||||
|
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (*ConnectUpdate, error) {
|
||||||
|
update := &ConnectUpdate{}
|
||||||
|
sessions := []*session.SessionType{}
|
||||||
|
tx.Select(&sessions, session.GetAllSessionsQuery)
|
||||||
|
sessionMap := make(map[string]*session.SessionType)
|
||||||
|
for _, session := range sessions {
|
||||||
|
sessionMap[session.SessionId] = session
|
||||||
|
update.Sessions = append(update.Sessions, session)
|
||||||
|
}
|
||||||
|
query := `SELECT * FROM screen ORDER BY archived, screenidx, archivedts`
|
||||||
|
screens := dbutil.SelectMapsGen[*screen.ScreenType](tx, query)
|
||||||
|
for _, screen := range screens {
|
||||||
|
update.Screens = append(update.Screens, screen)
|
||||||
|
}
|
||||||
|
query = `SELECT * FROM remote_instance`
|
||||||
|
riArr := dbutil.SelectMapsGen[*sstore.RemoteInstance](tx, query)
|
||||||
|
for _, ri := range riArr {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertScreen(ctx context.Context, sessionId string, origScreenName string, opts screen.ScreenCreateOpts, activate bool) (*scbus.ModelUpdatePacketType, error) {
|
||||||
|
var newScreenId string
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT archived`
|
||||||
|
if !tx.Exists(query, sessionId) {
|
||||||
|
return fmt.Errorf("cannot create screen, no session found (or session archived)")
|
||||||
|
}
|
||||||
|
localRemoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, sstore.LocalRemoteAlias)
|
||||||
|
if localRemoteId == "" {
|
||||||
|
return fmt.Errorf("cannot create screen, no local remote found")
|
||||||
|
}
|
||||||
|
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
|
||||||
|
var screenName string
|
||||||
|
if origScreenName == "" {
|
||||||
|
screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ? AND NOT archived`, sessionId)
|
||||||
|
screenName = sstore.FmtUniqueName("", "s%d", maxScreenIdx+1, screenNames)
|
||||||
|
} else {
|
||||||
|
screenName = origScreenName
|
||||||
|
}
|
||||||
|
var baseScreen *screen.ScreenType
|
||||||
|
if opts.HasCopy() {
|
||||||
|
if opts.BaseScreenId == "" {
|
||||||
|
return fmt.Errorf("invalid screen create opts, copy option with no base screen specified")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
baseScreen, err = screen.GetScreenById(tx.Context(), opts.BaseScreenId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if baseScreen == nil {
|
||||||
|
return fmt.Errorf("cannot create screen, base screen not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newScreenId = scbase.GenWaveUUID()
|
||||||
|
screen := &screen.ScreenType{
|
||||||
|
SessionId: sessionId,
|
||||||
|
ScreenId: newScreenId,
|
||||||
|
Name: screenName,
|
||||||
|
ScreenIdx: int64(maxScreenIdx) + 1,
|
||||||
|
ScreenOpts: screen.ScreenOptsType{},
|
||||||
|
OwnerId: "",
|
||||||
|
ShareMode: sstore.ShareModeLocal,
|
||||||
|
CurRemote: sstore.RemotePtrType{RemoteId: localRemoteId},
|
||||||
|
NextLineNum: 1,
|
||||||
|
SelectedLine: 0,
|
||||||
|
Anchor: screen.ScreenAnchorType{},
|
||||||
|
FocusType: screen.ScreenFocusInput,
|
||||||
|
Archived: false,
|
||||||
|
ArchivedTs: 0,
|
||||||
|
}
|
||||||
|
query = `INSERT INTO screen ( sessionid, screenid, name, screenidx, screenopts, screenviewopts, ownerid, sharemode, webshareopts, curremoteownerid, curremoteid, curremotename, nextlinenum, selectedline, anchor, focustype, archived, archivedts)
|
||||||
|
VALUES (:sessionid,:screenid,:name,:screenidx,:screenopts,:screenviewopts,:ownerid,:sharemode,:webshareopts,:curremoteownerid,:curremoteid,:curremotename,:nextlinenum,:selectedline,:anchor,:focustype,:archived,:archivedts)`
|
||||||
|
tx.NamedExec(query, screen.ToMap())
|
||||||
|
if activate {
|
||||||
|
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
|
||||||
|
tx.Exec(query, newScreenId, sessionId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
newScreen, err := screen.GetScreenById(ctx, newScreenId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update := scbus.MakeUpdatePacket()
|
||||||
|
update.AddUpdate(*newScreen)
|
||||||
|
if activate {
|
||||||
|
bareSession, err := session.GetBareSessionById(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
update.AddUpdate(*bareSession)
|
||||||
|
sstore.UpdateWithCurrentOpenAICmdInfoChat(newScreenId, update)
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if sessionDel is passed, we do *not* delete the screen directory (session delete will handle that)
|
||||||
|
func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update *scbus.ModelUpdatePacketType) (*scbus.ModelUpdatePacketType, error) {
|
||||||
|
var sessionId string
|
||||||
|
var isActive bool
|
||||||
|
var screenTombstone *screen.ScreenTombstoneType
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
screen, err := screen.GetScreenById(tx.Context(), screenId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get screen to delete: %w", err)
|
||||||
|
}
|
||||||
|
if screen == nil {
|
||||||
|
return fmt.Errorf("cannot delete screen (not found)")
|
||||||
|
}
|
||||||
|
webSharing := sstore.IsWebShare(tx, screenId)
|
||||||
|
if !sessionDel {
|
||||||
|
query := `SELECT sessionid FROM screen WHERE screenid = ?`
|
||||||
|
sessionId = tx.GetString(query, screenId)
|
||||||
|
if sessionId == "" {
|
||||||
|
return fmt.Errorf("cannot delete screen (no sessionid)")
|
||||||
|
}
|
||||||
|
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
|
||||||
|
numScreens := tx.GetInt(query, sessionId)
|
||||||
|
if numScreens <= 1 {
|
||||||
|
return fmt.Errorf("cannot delete the last screen in a session")
|
||||||
|
}
|
||||||
|
isActive = tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, sessionId, screenId)
|
||||||
|
if isActive {
|
||||||
|
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, sessionId)
|
||||||
|
nextId := getNextId(screenIds, screenId)
|
||||||
|
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screenTombstone = &ScreenTombstoneType{
|
||||||
|
ScreenId: screen.ScreenId,
|
||||||
|
SessionId: screen.SessionId,
|
||||||
|
Name: screen.Name,
|
||||||
|
DeletedTs: time.Now().UnixMilli(),
|
||||||
|
ScreenOpts: screen.ScreenOpts,
|
||||||
|
}
|
||||||
|
query := `INSERT INTO screen_tombstone ( screenid, sessionid, name, deletedts, screenopts)
|
||||||
|
VALUES (:screenid,:sessionid,:name,:deletedts,:screenopts)`
|
||||||
|
tx.NamedExec(query, dbutil.ToDBMap(screenTombstone, false))
|
||||||
|
query = `DELETE FROM screen WHERE screenid = ?`
|
||||||
|
tx.Exec(query, screenId)
|
||||||
|
query = `DELETE FROM line WHERE screenid = ?`
|
||||||
|
tx.Exec(query, screenId)
|
||||||
|
query = `DELETE FROM cmd WHERE screenid = ?`
|
||||||
|
tx.Exec(query, screenId)
|
||||||
|
query = `UPDATE history SET lineid = '', linenum = 0 WHERE screenid = ?`
|
||||||
|
tx.Exec(query, screenId)
|
||||||
|
if webSharing {
|
||||||
|
insertScreenDelUpdate(tx, screenId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
if !sessionDel {
|
||||||
|
GoDeleteScreenDirs(screenId)
|
||||||
|
}
|
||||||
|
if update == nil {
|
||||||
|
update = scbus.MakeUpdatePacket()
|
||||||
|
}
|
||||||
|
update.AddUpdate(*screenTombstone)
|
||||||
|
update.AddUpdate(ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true})
|
||||||
|
if isActive {
|
||||||
|
bareSession, err := GetBareSessionById(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update.AddUpdate(*bareSession)
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns sessionId
|
||||||
|
// if sessionName == "", it will be generated
|
||||||
|
func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*scbus.ModelUpdatePacketType, error) {
|
||||||
|
var newScreen *screen.ScreenType
|
||||||
|
newSessionId := scbase.GenWaveUUID()
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
names := tx.SelectStrings(`SELECT name FROM session`)
|
||||||
|
sessionName = sstore.FmtUniqueName(sessionName, "workspace-%d", len(names)+1, names)
|
||||||
|
maxSessionIdx := tx.GetInt(`SELECT COALESCE(max(sessionidx), 0) FROM session`)
|
||||||
|
query := `INSERT INTO session (sessionid, name, activescreenid, sessionidx, notifynum, archived, archivedts, sharemode)
|
||||||
|
VALUES (?, ?, '', ?, 0, 0, 0, ?)`
|
||||||
|
tx.Exec(query, newSessionId, sessionName, maxSessionIdx+1, sstore.ShareModeLocal)
|
||||||
|
screenUpdate, err := InsertScreen(tx.Context(), newSessionId, "", screen.ScreenCreateOpts{}, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
screenUpdateItems := scbus.GetUpdateItems[screen.ScreenType](screenUpdate)
|
||||||
|
if len(screenUpdateItems) < 1 {
|
||||||
|
return fmt.Errorf("no screen update items")
|
||||||
|
}
|
||||||
|
newScreen = screenUpdateItems[0]
|
||||||
|
if activate {
|
||||||
|
query = `UPDATE client SET activesessionid = ?`
|
||||||
|
tx.Exec(query, newSessionId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
sess, err := session.GetSessionById(ctx, newSessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update := scbus.MakeUpdatePacket()
|
||||||
|
update.AddUpdate(*sess)
|
||||||
|
update.AddUpdate(*newScreen)
|
||||||
|
if activate {
|
||||||
|
update.AddUpdate(session.ActiveSessionIdUpdate(newSessionId))
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (*scbus.ModelUpdatePacketType, error) {
|
||||||
|
session.SetActiveSessionId(ctx, sessionId)
|
||||||
|
txErr := sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||||
|
query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?`
|
||||||
|
if !tx.Exists(query, sessionId, screenId) {
|
||||||
|
return fmt.Errorf("cannot switch to screen, screen=%s does not exist in session=%s", screenId, sessionId)
|
||||||
|
}
|
||||||
|
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
|
||||||
|
tx.Exec(query, screenId, sessionId)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if txErr != nil {
|
||||||
|
return nil, txErr
|
||||||
|
}
|
||||||
|
bareSession, err := session.GetBareSessionById(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
update := scbus.MakeUpdatePacket()
|
||||||
|
update.AddUpdate(ActiveSessionIdUpdate(sessionId))
|
||||||
|
update.AddUpdate(*bareSession)
|
||||||
|
memState := GetScreenMemState(screenId)
|
||||||
|
if memState != nil {
|
||||||
|
update.AddUpdate(CmdLineUpdate(memState.CmdInputText))
|
||||||
|
UpdateWithCurrentOpenAICmdInfoChat(screenId, update)
|
||||||
|
|
||||||
|
// Clear any previous status indicator for this screen
|
||||||
|
err := ResetStatusIndicator_Update(update, screenId)
|
||||||
|
if err != nil {
|
||||||
|
// This is not a fatal error, so just log it
|
||||||
|
log.Printf("error resetting status indicator when switching screens: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return update, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user