diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index fbc68246..e6adcc74 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "fmt" "log" + "net/url" "os" "regexp" "sort" @@ -1641,6 +1642,10 @@ func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return update, nil } +func makeExternLink(urlStr string) string { + return fmt.Sprintf(`https://extern?%s`, url.QueryEscape(urlStr)) +} + func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Screen) if err != nil { @@ -1655,6 +1660,7 @@ func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType return nil, err } var infoMsg string + var infoWebShareLink bool if shouldShare { viewKeyBytes := make([]byte, 9) _, err = rand.Read(viewKeyBytes) @@ -1663,11 +1669,24 @@ func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType } viewKey := base64.RawURLEncoding.EncodeToString(viewKeyBytes) webShareOpts := sstore.ScreenWebShareOpts{ShareName: shareName, ViewKey: viewKey} + screen, err := sstore.GetScreenById(ctx, ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("cannot get screen: %v", err) + } + err = sstore.CanScreenWebShare(screen) + if err != nil { + return nil, err + } + webUpdate := pcloud.MakeScreenNewUpdate(screen, webShareOpts) + err = pcloud.DoSyncWebUpdate(webUpdate) + if err != nil { + return nil, fmt.Errorf("error starting webshare, error contacting prompt cloud server: %v", err) + } err = sstore.ScreenWebShareStart(ctx, ids.ScreenId, webShareOpts) if err != nil { - return nil, fmt.Errorf("cannot web-share screen: %v", err) + return nil, fmt.Errorf("cannot webshare screen, error updating: %v", err) } - infoMsg = fmt.Sprintf("screen is now shared to the web at %s", GetWebShareUrl(ids.ScreenId, viewKey)) + infoWebShareLink = true } else { err = sstore.ScreenWebShareStop(ctx, ids.ScreenId) if err != nil { @@ -1682,7 +1701,8 @@ func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType update := sstore.ModelUpdate{ Screens: []*sstore.ScreenType{screen}, Info: &sstore.InfoMsgType{ - InfoMsg: infoMsg, + InfoMsg: infoMsg, + WebShareLink: infoWebShareLink, }, } return update, nil diff --git a/pkg/pcloud/pcloud.go b/pkg/pcloud/pcloud.go index 9f3465ff..eb07022e 100644 --- a/pkg/pcloud/pcloud.go +++ b/pkg/pcloud/pcloud.go @@ -186,6 +186,22 @@ func defaultError(err error, estr string) error { return errors.New(estr) } +func MakeScreenNewUpdate(screen *sstore.ScreenType, webShareOpts sstore.ScreenWebShareOpts) *WebShareUpdateType { + rtn := &WebShareUpdateType{ + ScreenId: screen.ScreenId, + UpdateId: -1, + UpdateType: sstore.UpdateType_ScreenNew, + UpdateTs: time.Now().UnixMilli(), + } + rtn.Screen = &WebShareScreenType{ + ScreenId: screen.ScreenId, + SelectedLine: int(screen.SelectedLine), + ShareName: webShareOpts.ShareName, + ViewKey: webShareOpts.ViewKey, + } + return rtn +} + func makeWebShareUpdate(ctx context.Context, update *sstore.ScreenUpdateType) (*WebShareUpdateType, error) { rtn := &WebShareUpdateType{ ScreenId: update.ScreenId, @@ -208,15 +224,19 @@ func makeWebShareUpdate(ctx context.Context, update *sstore.ScreenUpdateType) (* case sstore.UpdateType_ScreenDel: break - case sstore.UpdateType_ScreenName: + case sstore.UpdateType_ScreenName, sstore.UpdateType_ScreenSelectedLine: screen, err := sstore.GetScreenById(ctx, update.ScreenId) if err != nil { return nil, fmt.Errorf("error getting screen: %v", err) } - if screen == nil || screen.WebShareOpts == nil || screen.WebShareOpts.ShareName == "" { - return nil, fmt.Errorf("invalid screen sharename (makeWebScreenUpdate)") + if screen == nil || screen.WebShareOpts == nil { + return nil, fmt.Errorf("invalid screen, not webshared (makeWebScreenUpdate)") + } + if update.UpdateType == sstore.UpdateType_ScreenName { + rtn.SVal = screen.WebShareOpts.ShareName + } else if update.UpdateType == sstore.UpdateType_ScreenSelectedLine { + rtn.IVal = int64(screen.SelectedLine) } - rtn.SVal = screen.WebShareOpts.ShareName case sstore.UpdateType_LineNew: line, cmd, err := sstore.GetLineCmdByLineId(ctx, update.ScreenId, update.LineId) @@ -332,7 +352,7 @@ type webShareResponseType struct { Data []*WebShareUpdateResponseType `json:"data"` } -func DoWebScreenUpdates(authInfo AuthInfo, updateArr []*sstore.ScreenUpdateType) error { +func convertUpdates(updateArr []*sstore.ScreenUpdateType) []*WebShareUpdateType { var webUpdates []*WebShareUpdateType for _, update := range updateArr { webUpdate, err := makeWebShareUpdate(context.Background(), update) @@ -352,9 +372,43 @@ func DoWebScreenUpdates(authInfo AuthInfo, updateArr []*sstore.ScreenUpdateType) } webUpdates = append(webUpdates, webUpdate) } + return webUpdates +} + +func DoSyncWebUpdate(webUpdate *WebShareUpdateType) error { + authInfo, err := getAuthInfo(context.Background()) + if err != nil { + return fmt.Errorf("could not get authinfo for request: %v", err) + } + ctx, cancelFn := context.WithTimeout(context.Background(), PCloudDefaultTimeout) + defer cancelFn() + req, err := makeAuthPostReq(ctx, WebShareUpdateUrl, authInfo, []*WebShareUpdateType{webUpdate}) + if err != nil { + return fmt.Errorf("cannot create auth-post-req for %s: %v", WebShareUpdateUrl, err) + } + var resp webShareResponseType + _, err = doRequest(req, &resp) + if err != nil { + return err + } + if len(resp.Data) == 0 { + return fmt.Errorf("invalid response received from server") + } + urt := resp.Data[0] + if urt.Error != "" { + return errors.New(urt.Error) + } + return nil +} + +func DoWebUpdates(webUpdates []*WebShareUpdateType) error { if len(webUpdates) == 0 { return nil } + authInfo, err := getAuthInfo(context.Background()) + if err != nil { + return fmt.Errorf("could not get authinfo for request: %v", err) + } ctx, cancelFn := context.WithTimeout(context.Background(), PCloudDefaultTimeout) defer cancelFn() req, err := makeAuthPostReq(ctx, WebShareUpdateUrl, authInfo, webUpdates) @@ -377,7 +431,9 @@ func DoWebScreenUpdates(authInfo AuthInfo, updateArr []*sstore.ScreenUpdateType) if resp == nil { resp = &WebShareUpdateResponseType{Success: false, Error: "resp not found"} } - log.Printf("[pcloud] updateid:%d, type:%s %s/%s success:%v err:%v\n", update.UpdateId, update.UpdateType, update.ScreenId, update.LineId, resp.Success, resp.Error) + if resp.Error != "" { + log.Printf("[pcloud] error updateid:%d, type:%s %s/%s err:%v\n", update.UpdateId, update.UpdateType, update.ScreenId, update.LineId, resp.Error) + } } return nil } @@ -447,8 +503,8 @@ func runWebShareUpdateWriter() { continue } numErrors = 0 - authInfo, err := getAuthInfo(context.Background()) - err = DoWebScreenUpdates(authInfo, updateArr) + webUpdates := convertUpdates(updateArr) + err = DoWebUpdates(webUpdates) if err != nil { numSendErrors++ backoffTime := computeBackoff(numSendErrors) diff --git a/pkg/pcloud/pclouddata.go b/pkg/pcloud/pclouddata.go index 685ef7d1..456a1469 100644 --- a/pkg/pcloud/pclouddata.go +++ b/pkg/pcloud/pclouddata.go @@ -34,6 +34,7 @@ type WebShareUpdateType struct { Cmd *WebShareCmdType `json:"cmd,omitempty"` PtyData *WebSharePtyData `json:"ptydata,omitempty"` SVal string `json:"sval,omitempty"` + IVal int64 `json:"ival,omitempty"` BVal bool `json:"bval,omitempty"` DoneInfo *sstore.CmdDoneInfo `json:"doneinfo,omitempty"` TermOpts *sstore.TermOpts `json:"termopts,omitempty"` @@ -59,9 +60,10 @@ type WebShareRemote struct { } type WebShareScreenType struct { - ScreenId string `json:"screenid"` - ShareName string `json:"sharename"` - ViewKey string `json:"viewkey"` + ScreenId string `json:"screenid"` + ShareName string `json:"sharename"` + ViewKey string `json:"viewkey"` + SelectedLine int `json:"selectedline"` } func webRemoteFromRemote(rptr sstore.RemotePtrType, r *sstore.RemoteType) *WebShareRemote { @@ -91,7 +93,7 @@ func webScreenFromScreen(s *sstore.ScreenType) (*WebShareScreenType, error) { } else { shareName = s.Name } - return &WebShareScreenType{ScreenId: s.ScreenId, ShareName: shareName, ViewKey: s.WebShareOpts.ViewKey}, nil + return &WebShareScreenType{ScreenId: s.ScreenId, ShareName: shareName, ViewKey: s.WebShareOpts.ViewKey, SelectedLine: int(s.SelectedLine)}, nil } type WebShareLineType struct { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 2c1f2fd4..761ba31e 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -1114,6 +1114,7 @@ func PurgeScreen(ctx context.Context, screenId string, sessionDel bool) (UpdateP tx.Exec(query, screenId) query = `DELETE FROM line WHERE screenid = ?` tx.Exec(query, screenId) + insertScreenDelUpdate(tx, screenId) return nil }) if txErr != nil { @@ -1715,6 +1716,9 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter if sline, found := editMap[ScreenField_SelectedLine]; found { query = `UPDATE screen SET selectedline = ? WHERE screenid = ?` tx.Exec(query, sline, screenId) + if isWebShare(tx, screenId) { + insertScreenUpdate(tx, screenId, UpdateType_ScreenSelectedLine) + } } if focusType, found := editMap[ScreenField_Focus]; found { query = `UPDATE screen SET focustype = ? WHERE screenid = ?` @@ -1743,7 +1747,7 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter func GetLineResolveItems(ctx context.Context, screenId string) ([]ResolveItem, error) { var rtn []ResolveItem txErr := WithTx(ctx, func(tx *TxWrap) error { - query := `SELECT lineid as id, linenum as num FROM line WHERE screenid = ? ORDER BY linenum` + query := `SELECT lineid as id, linenum as num, archived as hidden FROM line WHERE screenid = ? ORDER BY linenum` tx.Select(&rtn, query, screenId) return nil }) @@ -2411,6 +2415,19 @@ func PurgeHistoryByIds(ctx context.Context, historyIds []string) ([]*HistoryItem }) } +func CanScreenWebShare(screen *ScreenType) error { + if screen == nil { + return fmt.Errorf("cannot share screen, not found") + } + if screen.ShareMode == ShareModeWeb { + return fmt.Errorf("screen is already shared to web") + } + if screen.ShareMode != ShareModeLocal { + return fmt.Errorf("screen cannot be shared, invalid current share mode %q (must be local)", screen.ShareMode) + } + return nil +} + func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenWebShareOpts) error { return WithTx(ctx, func(tx *TxWrap) error { query := `SELECT screenid FROM screen WHERE screenid = ?` @@ -2424,17 +2441,9 @@ 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 = ? AND NOT archived ORDER BY linenum` - 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 AND NOT l.archived` - tx.Exec(query, UpdateType_PtyPos, nowTs, screenId) - NotifyUpdateWriter() + insertScreenNewUpdate(tx, screenId) return nil }) } @@ -2451,11 +2460,7 @@ func ScreenWebShareStop(ctx context.Context, screenId string) error { } query = `UPDATE screen SET sharemode = ?, webshareopts = ? WHERE screenid = ?` 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) + insertScreenDelUpdate(tx, screenId) return nil }) } @@ -2469,11 +2474,31 @@ func insertScreenUpdate(tx *TxWrap, screenId string, updateType string) { 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, time.Now().UnixMilli()) + 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` + 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 AND NOT l.archived` + tx.Exec(query, UpdateType_PtyPos, nowTs, screenId) + NotifyUpdateWriter() +} + +func insertScreenDelUpdate(tx *TxWrap, screenId string) { + 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) +} + func insertScreenLineUpdate(tx *TxWrap, screenId string, lineId string, updateType string) { if screenId == "" { tx.SetErr(errors.New("invalid screen-update, screenid is empty")) @@ -2521,6 +2546,9 @@ func GetScreenUpdates(ctx context.Context, maxNum int) ([]*ScreenUpdateType, err } 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) diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 072dd8af..ae48f9ff 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -81,17 +81,18 @@ const ( ) const ( - UpdateType_ScreenNew = "screen:new" - UpdateType_ScreenDel = "screen:del" - UpdateType_ScreenName = "screen:sharename" - UpdateType_LineNew = "line:new" - UpdateType_LineDel = "line:del" - UpdateType_LineRenderer = "line:renderer" - UpdateType_CmdStatus = "cmd:status" - UpdateType_CmdTermOpts = "cmd:termopts" - UpdateType_CmdDoneInfo = "cmd:doneinfo" - UpdateType_CmdRtnState = "cmd:rtnstate" - UpdateType_PtyPos = "pty:pos" + UpdateType_ScreenNew = "screen:new" + UpdateType_ScreenDel = "screen:del" + UpdateType_ScreenSelectedLine = "screen:selectedline" + UpdateType_ScreenName = "screen:sharename" + UpdateType_LineNew = "line:new" + UpdateType_LineDel = "line:del" + UpdateType_LineRenderer = "line:renderer" + UpdateType_CmdStatus = "cmd:status" + UpdateType_CmdTermOpts = "cmd:termopts" + UpdateType_CmdDoneInfo = "cmd:doneinfo" + UpdateType_CmdRtnState = "cmd:rtnstate" + UpdateType_PtyPos = "pty:pos" ) const MaxTzNameLen = 50 diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index add29e74..5cdd22e6 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -100,6 +100,8 @@ type InfoMsgType struct { InfoTitle string `json:"infotitle"` InfoError string `json:"infoerror,omitempty"` InfoMsg string `json:"infomsg,omitempty"` + InfoMsgHtml bool `json:"infomsghtml,omitempty"` + WebShareLink bool `json:"websharelink,omitempty"` InfoComps []string `json:"infocomps,omitempty"` InfoCompsMore bool `json:"infocompssmore,omitempty"` InfoLines []string `json:"infolines,omitempty"`