mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 669762a9c9 | |||
| f975ecce48 |
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/workspaces/line"
|
||||
)
|
||||
|
||||
type HistoryItemType struct {
|
||||
@@ -114,7 +115,7 @@ type HistoryViewData struct {
|
||||
RawOffset int `json:"rawoffset"`
|
||||
NextRawOffset int `json:"nextrawoffset"`
|
||||
HasMore bool `json:"hasmore"`
|
||||
Lines []*sstore.LineType `json:"lines"`
|
||||
Lines []*line.LineType `json:"lines"`
|
||||
Cmds []*sstore.CmdType `json:"cmds"`
|
||||
}
|
||||
|
||||
@@ -303,14 +304,14 @@ func getLineIdsFromHistoryItems(historyItems []*HistoryItemType) []string {
|
||||
return rtn
|
||||
}
|
||||
|
||||
func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryItemType) ([]*sstore.LineType, []*sstore.CmdType, error) {
|
||||
func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryItemType) ([]*line.LineType, []*sstore.CmdType, error) {
|
||||
if len(historyItems) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return sstore.WithTxRtn3(ctx, func(tx *sstore.TxWrap) ([]*sstore.LineType, []*sstore.CmdType, error) {
|
||||
return sstore.WithTxRtn3(ctx, func(tx *sstore.TxWrap) ([]*line.LineType, []*sstore.CmdType, error) {
|
||||
lineIdsJsonArr := dbutil.QuickJsonArr(getLineIdsFromHistoryItems(historyItems))
|
||||
query := `SELECT * FROM line WHERE lineid IN (SELECT value FROM json_each(?))`
|
||||
lineArr := dbutil.SelectMappable[*sstore.LineType](tx, query, lineIdsJsonArr)
|
||||
lineArr := dbutil.SelectMappable[*line.LineType](tx, query, lineIdsJsonArr)
|
||||
query = `SELECT * FROM cmd WHERE lineid IN (SELECT value FROM json_each(?))`
|
||||
cmdArr := dbutil.SelectMapsGen[*sstore.CmdType](tx, query, lineIdsJsonArr)
|
||||
return lineArr, cmdArr, nil
|
||||
|
||||
+67
-734
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,8 @@ import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
|
||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
|
||||
"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)
|
||||
}
|
||||
|
||||
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 {
|
||||
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
||||
if err != nil {
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
@@ -35,14 +34,12 @@ import (
|
||||
|
||||
type RemotePtrType = scpacket.RemotePtrType
|
||||
|
||||
const LineNoHeight = -1
|
||||
const DBFileName = "waveterm.db"
|
||||
const DBWALFileName = "waveterm.db-wal"
|
||||
const DBFileNameBackup = "backup.waveterm.db"
|
||||
const DBWALFileNameBackup = "backup.waveterm.db-wal"
|
||||
const MaxWebShareLineCount = 50
|
||||
const MaxWebShareScreenCount = 3
|
||||
const MaxLineStateSize = 4 * 1024 // 4k for now, can raise if needed
|
||||
|
||||
const DefaultSessionName = "default"
|
||||
const LocalRemoteAlias = "local"
|
||||
@@ -54,23 +51,6 @@ const APITokenSentinel = "--apitoken--"
|
||||
// be passed to waveshell (it should always get resolved prior to sending a run packet)
|
||||
const ShellTypePref_Detect = "detect"
|
||||
|
||||
const (
|
||||
LineTypeCmd = "cmd"
|
||||
LineTypeText = "text"
|
||||
LineTypeOpenAI = "openai"
|
||||
)
|
||||
|
||||
const (
|
||||
LineState_Source = "prompt:source"
|
||||
LineState_File = "prompt:file"
|
||||
LineState_FileUrl = "wave:fileurl"
|
||||
LineState_Min = "wave:min"
|
||||
LineState_Template = "template"
|
||||
LineState_Mode = "mode"
|
||||
LineState_Lang = "lang"
|
||||
LineState_Minimap = "minimap"
|
||||
)
|
||||
|
||||
const (
|
||||
MainViewSession = "session"
|
||||
MainViewBookmarks = "bookmarks"
|
||||
@@ -127,11 +107,6 @@ const (
|
||||
RemoteTypeOpenAI = "openai"
|
||||
)
|
||||
|
||||
const (
|
||||
ScreenFocusInput = "input"
|
||||
ScreenFocusCmd = "cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
CmdStoreTypeSession = "session"
|
||||
CmdStoreTypeScreen = "screen"
|
||||
@@ -300,198 +275,6 @@ func (ClientData) GetType() string {
|
||||
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 (
|
||||
LayoutFull = "full"
|
||||
)
|
||||
@@ -517,11 +300,6 @@ func (l LayoutType) Value() (driver.Value, error) {
|
||||
return quickValueJson(l)
|
||||
}
|
||||
|
||||
type ScreenAnchorType struct {
|
||||
AnchorLine int `json:"anchorline,omitempty"`
|
||||
AnchorOffset int `json:"anchoroffset,omitempty"`
|
||||
}
|
||||
|
||||
type TermOpts struct {
|
||||
Rows int64 `json:"rows"`
|
||||
Cols int64 `json:"cols"`
|
||||
@@ -654,37 +432,6 @@ func (ri *RemoteInstance) ToMap() map[string]interface{} {
|
||||
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 {
|
||||
ScreenId string `json:"screenid"`
|
||||
UserId string `json:"userid"`
|
||||
LineId string `json:"lineid"`
|
||||
Ts int64 `json:"ts"`
|
||||
LineNum int64 `json:"linenum"`
|
||||
LineNumTemp bool `json:"linenumtemp,omitempty"`
|
||||
LineLocal bool `json:"linelocal"`
|
||||
LineType string `json:"linetype"`
|
||||
LineState map[string]any `json:"linestate"`
|
||||
Renderer string `json:"renderer,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Ephemeral bool `json:"ephemeral,omitempty"`
|
||||
ContentHeight int64 `json:"contentheight,omitempty"`
|
||||
Star bool `json:"star,omitempty"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
Remove bool `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
func (LineType) UseDBMap() {}
|
||||
|
||||
type OpenAIUsage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
@@ -995,79 +742,6 @@ func (cmd *CmdType) IsRunning() bool {
|
||||
return cmd.Status == CmdStatusRunning || cmd.Status == CmdStatusDetached
|
||||
}
|
||||
|
||||
func makeNewLineCmd(screenId string, userId string, lineId string, renderer string, lineState map[string]any) *LineType {
|
||||
rtn := &LineType{}
|
||||
rtn.ScreenId = screenId
|
||||
rtn.UserId = userId
|
||||
rtn.LineId = lineId
|
||||
rtn.Ts = time.Now().UnixMilli()
|
||||
rtn.LineLocal = true
|
||||
rtn.LineType = LineTypeCmd
|
||||
rtn.LineId = lineId
|
||||
rtn.ContentHeight = LineNoHeight
|
||||
rtn.Renderer = renderer
|
||||
if lineState == nil {
|
||||
lineState = make(map[string]any)
|
||||
}
|
||||
rtn.LineState = lineState
|
||||
return rtn
|
||||
}
|
||||
|
||||
func makeNewLineText(screenId string, userId string, text string) *LineType {
|
||||
rtn := &LineType{}
|
||||
rtn.ScreenId = screenId
|
||||
rtn.UserId = userId
|
||||
rtn.LineId = scbase.GenWaveUUID()
|
||||
rtn.Ts = time.Now().UnixMilli()
|
||||
rtn.LineLocal = true
|
||||
rtn.LineType = LineTypeText
|
||||
rtn.Text = text
|
||||
rtn.ContentHeight = LineNoHeight
|
||||
rtn.LineState = make(map[string]any)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func makeNewLineOpenAI(screenId string, userId string, lineId string) *LineType {
|
||||
rtn := &LineType{}
|
||||
rtn.ScreenId = screenId
|
||||
rtn.UserId = userId
|
||||
rtn.LineId = lineId
|
||||
rtn.Ts = time.Now().UnixMilli()
|
||||
rtn.LineLocal = true
|
||||
rtn.LineType = LineTypeOpenAI
|
||||
rtn.ContentHeight = LineNoHeight
|
||||
rtn.Renderer = CmdRendererOpenAI
|
||||
rtn.LineState = make(map[string]any)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func AddCommentLine(ctx context.Context, screenId string, userId string, commentText string) (*LineType, error) {
|
||||
rtnLine := makeNewLineText(screenId, userId, commentText)
|
||||
err := InsertLine(ctx, rtnLine, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtnLine, nil
|
||||
}
|
||||
|
||||
func AddOpenAILine(ctx context.Context, screenId string, userId string, cmd *CmdType) (*LineType, error) {
|
||||
rtnLine := makeNewLineOpenAI(screenId, userId, cmd.LineId)
|
||||
err := InsertLine(ctx, rtnLine, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtnLine, nil
|
||||
}
|
||||
|
||||
func AddCmdLine(ctx context.Context, screenId string, userId string, cmd *CmdType, renderer string, lineState map[string]any) (*LineType, error) {
|
||||
rtnLine := makeNewLineCmd(screenId, userId, cmd.LineId, renderer, lineState)
|
||||
err := InsertLine(ctx, rtnLine, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtnLine, nil
|
||||
}
|
||||
|
||||
func EnsureLocalRemote(ctx context.Context) error {
|
||||
remote, err := GetLocalRemote(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,12 +11,6 @@ import (
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbus"
|
||||
)
|
||||
|
||||
type ActiveSessionIdUpdate string
|
||||
|
||||
func (ActiveSessionIdUpdate) GetType() string {
|
||||
return "activesessionid"
|
||||
}
|
||||
|
||||
type LineUpdate struct {
|
||||
Line LineType `json:"line"`
|
||||
Cmd CmdType `json:"cmd,omitempty"`
|
||||
@@ -97,19 +91,6 @@ func (InteractiveUpdate) GetType() string {
|
||||
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 {
|
||||
RemoteEdit bool `json:"remoteedit"`
|
||||
RemoteId string `json:"remoteid,omitempty"`
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package line
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||
)
|
||||
|
||||
// special "E" returns last unarchived line, "EA" returns last line (even if archived)
|
||||
func FindLineIdByArg(ctx context.Context, screenId string, lineArg string) (string, error) {
|
||||
return sstore.WithTxRtn(ctx, func(tx *sstore.TxWrap) (string, error) {
|
||||
if lineArg == "E" {
|
||||
query := `SELECT lineid FROM line WHERE screenid = ? AND NOT archived ORDER BY linenum DESC LIMIT 1`
|
||||
lineId := tx.GetString(query, screenId)
|
||||
return lineId, nil
|
||||
}
|
||||
if lineArg == "EA" {
|
||||
query := `SELECT lineid FROM line WHERE screenid = ? ORDER BY linenum DESC LIMIT 1`
|
||||
lineId := tx.GetString(query, screenId)
|
||||
return lineId, nil
|
||||
}
|
||||
lineNum, err := strconv.Atoi(lineArg)
|
||||
if err == nil {
|
||||
// valid linenum
|
||||
query := `SELECT lineid FROM line WHERE screenid = ? AND linenum = ?`
|
||||
lineId := tx.GetString(query, screenId, lineNum)
|
||||
return lineId, nil
|
||||
} else if len(lineArg) == 8 {
|
||||
// prefix id string match
|
||||
query := `SELECT lineid FROM line WHERE screenid = ? AND substr(lineid, 1, 8) = ?`
|
||||
lineId := tx.GetString(query, screenId, lineArg)
|
||||
return lineId, nil
|
||||
} else {
|
||||
// id match
|
||||
query := `SELECT lineid FROM line WHERE screenid = ? AND lineid = ?`
|
||||
lineId := tx.GetString(query, screenId, lineArg)
|
||||
return lineId, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func GetLineCmdByLineId(ctx context.Context, screenId string, lineId string) (*LineType, *sstore.CmdType, error) {
|
||||
return sstore.WithTxRtn3(ctx, func(tx *sstore.TxWrap) (*LineType, *sstore.CmdType, error) {
|
||||
query := `SELECT * FROM line WHERE screenid = ? AND lineid = ?`
|
||||
lineVal := dbutil.GetMappable[*LineType](tx, query, screenId, lineId)
|
||||
if lineVal == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
var cmdRtn *sstore.CmdType
|
||||
query = `SELECT * FROM cmd WHERE screenid = ? AND lineid = ?`
|
||||
cmdRtn = dbutil.GetMapGen[*sstore.CmdType](tx, query, screenId, lineId)
|
||||
return lineVal, cmdRtn, nil
|
||||
})
|
||||
}
|
||||
|
||||
func InsertLine(ctx context.Context, line *LineType, cmd *sstore.CmdType) error {
|
||||
if line == nil {
|
||||
return fmt.Errorf("line cannot be nil")
|
||||
}
|
||||
if line.LineId == "" {
|
||||
return fmt.Errorf("line must have lineid set")
|
||||
}
|
||||
if line.LineNum != 0 {
|
||||
return fmt.Errorf("line should not hage linenum set")
|
||||
}
|
||||
if cmd != nil && cmd.ScreenId == "" {
|
||||
return fmt.Errorf("cmd should have screenid set")
|
||||
}
|
||||
qjs := dbutil.QuickJson(line.LineState)
|
||||
if len(qjs) > MaxLineStateSize {
|
||||
return fmt.Errorf("linestate exceeds maxsize, size[%d] max[%d]", len(qjs), MaxLineStateSize)
|
||||
}
|
||||
return sstore.WithTx(ctx, func(tx *sstore.TxWrap) error {
|
||||
query := `SELECT screenid FROM screen WHERE screenid = ?`
|
||||
if !tx.Exists(query, line.ScreenId) {
|
||||
return fmt.Errorf("screen not found, cannot insert line[%s]", line.ScreenId)
|
||||
}
|
||||
query = `SELECT nextlinenum FROM screen WHERE screenid = ?`
|
||||
nextLineNum := tx.GetInt(query, line.ScreenId)
|
||||
line.LineNum = int64(nextLineNum)
|
||||
query = `INSERT INTO line ( screenid, userid, lineid, ts, linenum, linenumtemp, linelocal, linetype, linestate, text, renderer, ephemeral, contentheight, star, archived)
|
||||
VALUES (:screenid,:userid,:lineid,:ts,:linenum,:linenumtemp,:linelocal,:linetype,:linestate,:text,:renderer,:ephemeral,:contentheight,:star,:archived)`
|
||||
tx.NamedExec(query, dbutil.ToDBMap(line, false))
|
||||
query = `UPDATE screen SET nextlinenum = ? WHERE screenid = ?`
|
||||
tx.Exec(query, nextLineNum+1, line.ScreenId)
|
||||
if cmd != nil {
|
||||
cmd.OrigTermOpts = cmd.TermOpts
|
||||
cmdMap := cmd.ToMap()
|
||||
query = `
|
||||
INSERT INTO cmd ( screenid, lineid, remoteownerid, remoteid, remotename, cmdstr, rawcmdstr, festate, statebasehash, statediffhasharr, termopts, origtermopts, status, cmdpid, remotepid, donets, restartts, exitcode, durationms, rtnstate, runout, rtnbasehash, rtndiffhasharr)
|
||||
VALUES (:screenid,:lineid,:remoteownerid,:remoteid,:remotename,:cmdstr,:rawcmdstr,:festate,:statebasehash,:statediffhasharr,:termopts,:origtermopts,:status,:cmdpid,:remotepid,:donets,:restartts,:exitcode,:durationms,:rtnstate,:runout,:rtnbasehash,:rtndiffhasharr)
|
||||
`
|
||||
tx.NamedExec(query, cmdMap)
|
||||
}
|
||||
if sstore.IsWebShare(tx, line.ScreenId) {
|
||||
sstore.InsertScreenLineUpdate(tx, line.ScreenId, line.LineId, sstore.UpdateType_LineNew)
|
||||
}
|
||||
return 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[*LineType](tx, query, screen.ScreenId)
|
||||
query = `SELECT * FROM cmd WHERE screenid = ?`
|
||||
screen.Cmds = dbutil.SelectMapsGen[*sstore.CmdType](tx, query, screen.ScreenId)
|
||||
return screen, nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package line
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||
)
|
||||
|
||||
const MaxLineStateSize = 4 * 1024 // 4k for now, can raise if needed
|
||||
const LineNoHeight = -1
|
||||
|
||||
const (
|
||||
LineTypeCmd = "cmd"
|
||||
LineTypeText = "text"
|
||||
LineTypeOpenAI = "openai"
|
||||
)
|
||||
|
||||
const (
|
||||
LineState_Source = "prompt:source"
|
||||
LineState_File = "prompt:file"
|
||||
LineState_FileUrl = "wave:fileurl"
|
||||
LineState_Min = "wave:min"
|
||||
LineState_Template = "template"
|
||||
LineState_Mode = "mode"
|
||||
LineState_Lang = "lang"
|
||||
LineState_Minimap = "minimap"
|
||||
)
|
||||
|
||||
type LineType struct {
|
||||
ScreenId string `json:"screenid"`
|
||||
UserId string `json:"userid"`
|
||||
LineId string `json:"lineid"`
|
||||
Ts int64 `json:"ts"`
|
||||
LineNum int64 `json:"linenum"`
|
||||
LineNumTemp bool `json:"linenumtemp,omitempty"`
|
||||
LineLocal bool `json:"linelocal"`
|
||||
LineType string `json:"linetype"`
|
||||
LineState map[string]any `json:"linestate"`
|
||||
Renderer string `json:"renderer,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Ephemeral bool `json:"ephemeral,omitempty"`
|
||||
ContentHeight int64 `json:"contentheight,omitempty"`
|
||||
Star bool `json:"star,omitempty"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
Remove bool `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
func (LineType) UseDBMap() {}
|
||||
|
||||
type ScreenLinesType struct {
|
||||
ScreenId string `json:"screenid"`
|
||||
Lines []*LineType `json:"lines" dbmap:"-"`
|
||||
Cmds []*sstore.CmdType `json:"cmds" dbmap:"-"`
|
||||
}
|
||||
|
||||
func (ScreenLinesType) UseDBMap() {}
|
||||
|
||||
func (ScreenLinesType) GetType() string {
|
||||
return "screenlines"
|
||||
}
|
||||
|
||||
func makeNewLineCmd(screenId string, userId string, lineId string, renderer string, lineState map[string]any) *LineType {
|
||||
rtn := &LineType{}
|
||||
rtn.ScreenId = screenId
|
||||
rtn.UserId = userId
|
||||
rtn.LineId = lineId
|
||||
rtn.Ts = time.Now().UnixMilli()
|
||||
rtn.LineLocal = true
|
||||
rtn.LineType = LineTypeCmd
|
||||
rtn.LineId = lineId
|
||||
rtn.ContentHeight = LineNoHeight
|
||||
rtn.Renderer = renderer
|
||||
if lineState == nil {
|
||||
lineState = make(map[string]any)
|
||||
}
|
||||
rtn.LineState = lineState
|
||||
return rtn
|
||||
}
|
||||
|
||||
func makeNewLineText(screenId string, userId string, text string) *LineType {
|
||||
rtn := &LineType{}
|
||||
rtn.ScreenId = screenId
|
||||
rtn.UserId = userId
|
||||
rtn.LineId = scbase.GenWaveUUID()
|
||||
rtn.Ts = time.Now().UnixMilli()
|
||||
rtn.LineLocal = true
|
||||
rtn.LineType = LineTypeText
|
||||
rtn.Text = text
|
||||
rtn.ContentHeight = LineNoHeight
|
||||
rtn.LineState = make(map[string]any)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func makeNewLineOpenAI(screenId string, userId string, lineId string) *LineType {
|
||||
rtn := &LineType{}
|
||||
rtn.ScreenId = screenId
|
||||
rtn.UserId = userId
|
||||
rtn.LineId = lineId
|
||||
rtn.Ts = time.Now().UnixMilli()
|
||||
rtn.LineLocal = true
|
||||
rtn.LineType = LineTypeOpenAI
|
||||
rtn.ContentHeight = LineNoHeight
|
||||
rtn.Renderer = sstore.CmdRendererOpenAI
|
||||
rtn.LineState = make(map[string]any)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func AddCommentLine(ctx context.Context, screenId string, userId string, commentText string) (*LineType, error) {
|
||||
rtnLine := makeNewLineText(screenId, userId, commentText)
|
||||
err := InsertLine(ctx, rtnLine, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtnLine, nil
|
||||
}
|
||||
|
||||
func AddOpenAILine(ctx context.Context, screenId string, userId string, cmd *sstore.CmdType) (*LineType, error) {
|
||||
rtnLine := makeNewLineOpenAI(screenId, userId, cmd.LineId)
|
||||
err := InsertLine(ctx, rtnLine, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtnLine, nil
|
||||
}
|
||||
|
||||
func AddCmdLine(ctx context.Context, screenId string, userId string, cmd *sstore.CmdType, renderer string, lineState map[string]any) (*LineType, error) {
|
||||
rtnLine := makeNewLineCmd(screenId, userId, cmd.LineId, renderer, lineState)
|
||||
err := InsertLine(ctx, rtnLine, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rtnLine, nil
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package screen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"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
|
||||
})
|
||||
}
|
||||
|
||||
// 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 := sstore.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 {
|
||||
sstore.DeletePtyOutFile(ctx, screenId, lineId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) error {
|
||||
txErr := sstore.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
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
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 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"
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package session
|
||||
|
||||
type ActiveSessionIdUpdate string
|
||||
|
||||
func (ActiveSessionIdUpdate) GetType() string {
|
||||
return "activesessionid"
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
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 {
|
||||
scr, err := screen.GetScreenById(tx.Context(), screenId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get screen to delete: %w", err)
|
||||
}
|
||||
if scr == 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 := sstore.GetNextId(screenIds, screenId)
|
||||
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
|
||||
}
|
||||
}
|
||||
screenTombstone = &screen.ScreenTombstoneType{
|
||||
ScreenId: scr.ScreenId,
|
||||
SessionId: scr.SessionId,
|
||||
Name: scr.Name,
|
||||
DeletedTs: time.Now().UnixMilli(),
|
||||
ScreenOpts: scr.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 {
|
||||
screen.InsertScreenDelUpdate(tx, screenId)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return nil, txErr
|
||||
}
|
||||
if !sessionDel {
|
||||
sstore.GoDeleteScreenDirs(screenId)
|
||||
}
|
||||
if update == nil {
|
||||
update = scbus.MakeUpdatePacket()
|
||||
}
|
||||
update.AddUpdate(*screenTombstone)
|
||||
update.AddUpdate(screen.ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true})
|
||||
if isActive {
|
||||
bareSession, err := session.GetBareSessionById(ctx, sessionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
update.AddUpdate(*bareSession)
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (scbus.UpdatePacket, error) {
|
||||
var isActive bool
|
||||
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 close screen (not found)")
|
||||
}
|
||||
if sstore.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 := sstore.GetNextId(screenIds, screenId)
|
||||
tx.Exec(`UPDATE session SET activescreenid = ? WHERE sessionid = ?`, nextId, sessionId)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return nil, txErr
|
||||
}
|
||||
newScreen, err := screen.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 := session.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(session.ActiveSessionIdUpdate(sessionId))
|
||||
update.AddUpdate(*bareSession)
|
||||
memState := sstore.GetScreenMemState(screenId)
|
||||
if memState != nil {
|
||||
update.AddUpdate(sstore.CmdLineUpdate(memState.CmdInputText))
|
||||
sstore.UpdateWithCurrentOpenAICmdInfoChat(screenId, update)
|
||||
|
||||
// Clear any previous status indicator for this screen
|
||||
err := sstore.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
|
||||
}
|
||||
Reference in New Issue
Block a user