diff --git a/pkg/pcloud/pcloud.go b/pkg/pcloud/pcloud.go index 1026eab1..9c26a18d 100644 --- a/pkg/pcloud/pcloud.go +++ b/pkg/pcloud/pcloud.go @@ -24,7 +24,7 @@ import ( const PCloudEndpoint = "https://api.getprompt.dev/central" const PCloudEndpointVarName = "PCLOUD_ENDPOINT" const APIVersion = 1 -const MaxPtyUpdateSize = (128 * 1024) + 1 +const MaxPtyUpdateSize = (128 * 1024) const MaxUpdatesPerReq = 10 const MaxUpdateWriterErrors = 3 const PCloudDefaultTimeout = 5 * time.Second @@ -291,11 +291,19 @@ func makeWebShareUpdate(ctx context.Context, update *sstore.ScreenUpdateType) (* if err != nil { return nil, fmt.Errorf("error getting ptypos: %v", err) } - realOffset, data, err := sstore.ReadPtyOutFile(ctx, update.ScreenId, cmdId, ptyPos, MaxPtyUpdateSize) + sstore.SetWebScreenPtyPosDelIntent(update.ScreenId, update.LineId) + realOffset, data, err := sstore.ReadPtyOutFile(ctx, update.ScreenId, cmdId, ptyPos, MaxPtyUpdateSize+1) if err != nil { return nil, fmt.Errorf("error getting ptydata: %v", err) } - rtn.PtyData = &WebSharePtyData{PtyPos: realOffset, Data: data} + if len(data) == 0 { + return nil, nil + } + if len(data) > MaxPtyUpdateSize { + rtn.PtyData = &WebSharePtyData{PtyPos: realOffset, Data: data[0:MaxPtyUpdateSize], Eof: false} + } else { + rtn.PtyData = &WebSharePtyData{PtyPos: realOffset, Data: data, Eof: true} + } default: return nil, fmt.Errorf("unsupported update type (pcloud/makeWebScreenUpdate): %s\n", update.UpdateType) @@ -306,7 +314,7 @@ func makeWebShareUpdate(ctx context.Context, update *sstore.ScreenUpdateType) (* func finalizeWebScreenUpdate(ctx context.Context, webUpdate *WebShareUpdateType) error { switch webUpdate.UpdateType { case sstore.UpdateType_PtyPos: - dataEof := len(webUpdate.PtyData.Data) < MaxPtyUpdateSize + dataEof := webUpdate.PtyData.Eof newPos := webUpdate.PtyData.PtyPos + int64(len(webUpdate.PtyData.Data)) if dataEof { err := sstore.RemoveScreenUpdate(ctx, webUpdate.UpdateId) @@ -318,6 +326,10 @@ func finalizeWebScreenUpdate(ctx context.Context, webUpdate *WebShareUpdateType) if err != nil { return err } + err = sstore.MaybeRemovePtyPosUpdate(ctx, webUpdate.ScreenId, webUpdate.LineId, webUpdate.UpdateId) + if err != nil { + return err + } default: err := sstore.RemoveScreenUpdate(ctx, webUpdate.UpdateId) @@ -338,21 +350,27 @@ func DoWebScreenUpdates(authInfo AuthInfo, updateArr []*sstore.ScreenUpdateType) var webUpdates []*WebShareUpdateType for _, update := range updateArr { webUpdate, err := makeWebShareUpdate(context.Background(), update) - if err != nil { - // log error, remove update, and continue - log.Printf("[pcloud] error create web-share update updateid:%d: %v", update.UpdateId, err) - err = sstore.RemoveScreenUpdate(context.Background(), update.UpdateId) + if err != nil || webUpdate == nil { + // log error (if there is one), remove update, and continue + if err != nil { + log.Printf("[pcloud] error create web-share update updateid:%d: %v", update.UpdateId, err) + } + if update.UpdateType == sstore.UpdateType_PtyPos { + err = sstore.MaybeRemovePtyPosUpdate(context.Background(), update.ScreenId, update.LineId, update.UpdateId) + } else { + err = sstore.RemoveScreenUpdate(context.Background(), update.UpdateId) + } if err != nil { // ignore this error too (although this is really problematic, there is nothing to do) log.Printf("[pcloud] error removing screen update updateid:%d: %v", update.UpdateId, err) } continue } - if webUpdate == nil { - continue - } webUpdates = append(webUpdates, webUpdate) } + if len(webUpdates) == 0 { + return nil + } ctx, cancelFn := context.WithTimeout(context.Background(), PCloudDefaultTimeout) defer cancelFn() req, err := makeAuthPostReq(ctx, WebShareUpdateUrl, authInfo, webUpdates) diff --git a/pkg/pcloud/pclouddata.go b/pkg/pcloud/pclouddata.go index 7f56edee..102fe76d 100644 --- a/pkg/pcloud/pclouddata.go +++ b/pkg/pcloud/pclouddata.go @@ -156,4 +156,5 @@ func webCmdFromCmd(lineId string, cmd *sstore.CmdType) (*WebShareCmdType, error) type WebSharePtyData struct { PtyPos int64 `json:"ptypos,omitempty"` Data []byte `json:"data,omitempty"` + Eof bool `json:"-"` // internal use } diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 647dc282..f6fd32a5 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -24,6 +24,8 @@ const DefaultMaxHistoryItems = 1000 var updateWriterCVar = sync.NewCond(&sync.Mutex{}) var updateWriterMoreData = false +var WebScreenPtyPosLock = &sync.Mutex{} +var WebScreenPtyPosDelIntent = make(map[string]bool) // map[screenid + ":" + lineid] -> bool type SingleConnDBGetter struct { SingleConnLock *sync.Mutex @@ -2400,12 +2402,16 @@ func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenW if shareMode != ShareModeLocal { return fmt.Errorf("screen cannot be shared, invalid current share mode %q (must be local)", shareMode) } + nowTs := time.Now().UnixMilli() query = `UPDATE screen SET sharemode = ?, webshareopts = ? WHERE screenid = ?` tx.Exec(query, ShareModeWeb, quickJson(shareOpts), screenId) insertScreenUpdate(tx, screenId, UpdateType_ScreenNew) query = `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets) SELECT screenid, lineid, ?, ? FROM line WHERE screenid = ? ORDER BY linenum` - tx.Exec(query, UpdateType_LineNew, time.Now().UnixMilli(), screenId) + tx.Exec(query, UpdateType_LineNew, nowTs, screenId) + query = `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets) + SELECT c.screenid, l.lineid, ?, ? FROM cmd c, line l WHERE c.screenid = ? AND l.cmdid = c.cmdid` + tx.Exec(query, UpdateType_PtyPos, nowTs, screenId) NotifyUpdateWriter() return nil }) @@ -2425,6 +2431,8 @@ func ScreenWebShareStop(ctx context.Context, screenId string) error { tx.Exec(query, ShareModeLocal, "null", screenId) query = `DELETE FROM screenupdate WHERE screenid = ?` tx.Exec(query, screenId) + query = `DELETE FROM webptypos WHERE screenid = ?` + tx.Exec(query, screenId) insertScreenUpdate(tx, screenId, UpdateType_ScreenDel) return nil }) @@ -2494,13 +2502,31 @@ func RemoveScreenUpdate(ctx context.Context, updateId int64) error { }) } -func InsertPtyPosUpdate(ctx context.Context, screenId string, cmdId string) error { +func SetWebScreenPtyPosDelIntent(screenId string, lineId string) { + WebScreenPtyPosLock.Lock() + defer WebScreenPtyPosLock.Unlock() + WebScreenPtyPosDelIntent[screenId+":"+lineId] = true +} + +func ClearWebScreenPtyPosDelIntent(screenId string, lineId string) bool { + WebScreenPtyPosLock.Lock() + defer WebScreenPtyPosLock.Unlock() + rtn := WebScreenPtyPosDelIntent[screenId+":"+lineId] + delete(WebScreenPtyPosDelIntent, screenId+":"+lineId) + return rtn +} + +func MaybeInsertPtyPosUpdate(ctx context.Context, screenId string, cmdId string) error { return WithTx(ctx, func(tx *TxWrap) error { + if !isWebShare(tx, screenId) { + return nil + } query := `SELECT lineid FROM line WHERE screenid = ? AND cmdid = ?` lineId := tx.GetString(query, screenId, cmdId) if lineId == "" { return fmt.Errorf("invalid ptypos update, no lineid found for %s/%s", screenId, cmdId) } + ClearWebScreenPtyPosDelIntent(screenId, lineId) // clear delete intention because we have a new update query = `SELECT updateid FROM screenupdate WHERE screenid = ? AND lineid = ? AND updatetype = ?` if !tx.Exists(query, screenId, lineId, UpdateType_PtyPos) { query := `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets) VALUES (?, ?, ?, ?)` @@ -2511,6 +2537,18 @@ func InsertPtyPosUpdate(ctx context.Context, screenId string, cmdId string) erro }) } +func MaybeRemovePtyPosUpdate(ctx context.Context, screenId string, lineId string, updateId int64) error { + return WithTx(ctx, func(tx *TxWrap) error { + intent := ClearWebScreenPtyPosDelIntent(screenId, lineId) // check for intention before deleting + if !intent { + return nil + } + query := `DELETE FROM screenupdate WHERE updateid = ?` + tx.Exec(query, updateId) + return nil + }) +} + func GetWebPtyPos(ctx context.Context, screenId string, lineId string) (int64, error) { return WithTxRtn(ctx, func(tx *TxWrap) (int64, error) { query := `SELECT ptypos FROM webptypos WHERE screenid = ? AND lineid = ?` diff --git a/pkg/sstore/fileops.go b/pkg/sstore/fileops.go index 9361f445..c2f8e050 100644 --- a/pkg/sstore/fileops.go +++ b/pkg/sstore/fileops.go @@ -61,7 +61,7 @@ func AppendToCmdPtyBlob(ctx context.Context, screenId string, cmdId string, data PtyData64: data64, PtyDataLen: int64(len(data)), } - err = InsertPtyPosUpdate(ctx, screenId, cmdId) + err = MaybeInsertPtyPosUpdate(ctx, screenId, cmdId) if err != nil { // just log log.Printf("error inserting ptypos update %s/%s: %v\n", screenId, cmdId, err)