working on webshareupdates

This commit is contained in:
sawka
2023-03-26 13:21:58 -07:00
parent 9ffb672062
commit cf3644f0fd
12 changed files with 580 additions and 276 deletions
+2 -1
View File
@@ -23,6 +23,7 @@ import (
"github.com/scripthaus-dev/sh2-server/pkg/cmdrunner"
"github.com/scripthaus-dev/sh2-server/pkg/pcloud"
"github.com/scripthaus-dev/sh2-server/pkg/remote"
"github.com/scripthaus-dev/sh2-server/pkg/rtnstate"
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
"github.com/scripthaus-dev/sh2-server/pkg/scws"
@@ -246,7 +247,7 @@ func HandleRtnState(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf("invalid cmdid: %v", err)))
return
}
data, err := cmdrunner.GetRtnStateDiff(r.Context(), screenId, cmdId)
data, err := rtnstate.GetRtnStateDiff(r.Context(), screenId, cmdId)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("cannot get rtnstate diff: %v", err)))
+3
View File
@@ -0,0 +1,3 @@
DROP TABLE webptypos;
DROP INDEX idx_screenupdate_ids;
+8
View File
@@ -0,0 +1,8 @@
CREATE TABLE webptypos (
screenid varchar(36) NOT NULL,
lineid varchar(36) NOT NULL,
ptypos bigint NOT NULL,
PRIMARY KEY (screenid, cmdid)
);
CREATE INDEX idx_screenupdate_ids ON screenupdate (screenid, lineid);
-89
View File
@@ -16,7 +16,6 @@ import (
"time"
"unicode"
"github.com/alessio/shellescape"
"github.com/google/uuid"
"github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet"
@@ -2951,94 +2950,6 @@ func formatTextTable(totalCols int, data [][]string, colMeta []ColMeta) []string
return rtn
}
const MaxDiffKeyLen = 40
const MaxDiffValLen = 50
func displayStateUpdateDiff(buf *bytes.Buffer, oldState packet.ShellState, newState packet.ShellState) {
if newState.Cwd != oldState.Cwd {
buf.WriteString(fmt.Sprintf("cwd %s\n", newState.Cwd))
}
if !bytes.Equal(newState.ShellVars, oldState.ShellVars) {
newEnvMap := shexec.DeclMapFromState(&newState)
oldEnvMap := shexec.DeclMapFromState(&oldState)
for key, newVal := range newEnvMap {
oldVal, found := oldEnvMap[key]
if !found || !shexec.DeclsEqual(false, oldVal, newVal) {
var exportStr string
if newVal.IsExport() {
exportStr = "export "
}
buf.WriteString(fmt.Sprintf("%s%s=%s\n", exportStr, utilfn.EllipsisStr(key, MaxDiffKeyLen), utilfn.EllipsisStr(newVal.Value, MaxDiffValLen)))
}
}
for key, _ := range oldEnvMap {
_, found := newEnvMap[key]
if !found {
buf.WriteString(fmt.Sprintf("unset %s\n", utilfn.EllipsisStr(key, MaxDiffKeyLen)))
}
}
}
if newState.Aliases != oldState.Aliases {
newAliasMap, _ := ParseAliases(newState.Aliases)
oldAliasMap, _ := ParseAliases(oldState.Aliases)
for aliasName, newAliasVal := range newAliasMap {
oldAliasVal, found := oldAliasMap[aliasName]
if !found || newAliasVal != oldAliasVal {
buf.WriteString(fmt.Sprintf("alias %s\n", utilfn.EllipsisStr(shellescape.Quote(aliasName), MaxDiffKeyLen)))
}
}
for aliasName, _ := range oldAliasMap {
_, found := newAliasMap[aliasName]
if !found {
buf.WriteString(fmt.Sprintf("unalias %s\n", utilfn.EllipsisStr(shellescape.Quote(aliasName), MaxDiffKeyLen)))
}
}
}
if newState.Funcs != oldState.Funcs {
newFuncMap, _ := ParseFuncs(newState.Funcs)
oldFuncMap, _ := ParseFuncs(oldState.Funcs)
for funcName, newFuncVal := range newFuncMap {
oldFuncVal, found := oldFuncMap[funcName]
if !found || newFuncVal != oldFuncVal {
buf.WriteString(fmt.Sprintf("function %s\n", utilfn.EllipsisStr(shellescape.Quote(funcName), MaxDiffKeyLen)))
}
}
for funcName, _ := range oldFuncMap {
_, found := newFuncMap[funcName]
if !found {
buf.WriteString(fmt.Sprintf("unset -f %s\n", utilfn.EllipsisStr(shellescape.Quote(funcName), MaxDiffKeyLen)))
}
}
}
}
func GetRtnStateDiff(ctx context.Context, screenId string, cmdId string) ([]byte, error) {
cmd, err := sstore.GetCmdByScreenId(ctx, screenId, cmdId)
if err != nil {
return nil, err
}
if cmd == nil {
return nil, nil
}
if !cmd.RtnState {
return nil, nil
}
if cmd.RtnStatePtr.IsEmpty() {
return nil, nil
}
var outputBytes bytes.Buffer
initialState, err := sstore.GetFullState(ctx, cmd.StatePtr)
if err != nil {
return nil, fmt.Errorf("getting initial full state: %v", err)
}
rtnState, err := sstore.GetFullState(ctx, cmd.RtnStatePtr)
if err != nil {
return nil, fmt.Errorf("getting rtn full state: %v", err)
}
displayStateUpdateDiff(&outputBytes, *initialState, *rtnState)
return outputBytes.Bytes(), nil
}
func isValidInScope(scopeName string, varName string) bool {
for _, varScope := range SetVarScopes {
if varScope.ScopeName == scopeName {
-84
View File
@@ -269,87 +269,3 @@ func EvalMetaCommand(ctx context.Context, origPk *scpacket.FeCommandPacketType)
}
return rtnPk, nil
}
func parseAliasStmt(stmt *syntax.Stmt, sourceStr string) (string, string, error) {
cmd := stmt.Cmd
callExpr, ok := cmd.(*syntax.CallExpr)
if !ok {
return "", "", fmt.Errorf("wrong cmd type for alias")
}
if len(callExpr.Args) != 2 {
return "", "", fmt.Errorf("wrong number of words in alias expr wordslen=%d", len(callExpr.Args))
}
firstWord := callExpr.Args[0]
if firstWord.Lit() != "alias" {
return "", "", fmt.Errorf("invalid alias cmd word (not 'alias')")
}
secondWord := callExpr.Args[1]
var ectx simpleexpand.SimpleExpandContext // no homedir, do not want ~ expansion
val, _ := simpleexpand.SimpleExpandWord(ectx, secondWord, sourceStr)
eqIdx := strings.Index(val, "=")
if eqIdx == -1 {
return "", "", fmt.Errorf("no '=' in alias definition")
}
return val[0:eqIdx], val[eqIdx+1:], nil
}
func ParseAliases(aliases string) (map[string]string, error) {
r := strings.NewReader(aliases)
parser := syntax.NewParser(syntax.Variant(syntax.LangBash))
file, err := parser.Parse(r, "aliases")
if err != nil {
return nil, err
}
rtn := make(map[string]string)
for _, stmt := range file.Stmts {
aliasName, aliasVal, err := parseAliasStmt(stmt, aliases)
if err != nil {
// fmt.Printf("stmt-err: %v\n", err)
continue
}
if aliasName != "" {
rtn[aliasName] = aliasVal
}
}
return rtn, nil
}
func parseFuncStmt(stmt *syntax.Stmt, source string) (string, string, error) {
cmd := stmt.Cmd
funcDecl, ok := cmd.(*syntax.FuncDecl)
if !ok {
return "", "", fmt.Errorf("cmd not FuncDecl")
}
name := funcDecl.Name.Value
// fmt.Printf("func: [%s]\n", name)
funcBody := funcDecl.Body
// fmt.Printf(" %d:%d\n", funcBody.Cmd.Pos().Offset(), funcBody.Cmd.End().Offset())
bodyStr := source[funcBody.Cmd.Pos().Offset():funcBody.Cmd.End().Offset()]
// fmt.Printf("<<<\n%s\n>>>\n", bodyStr)
// fmt.Printf("\n")
return name, bodyStr, nil
}
func ParseFuncs(funcs string) (map[string]string, error) {
r := strings.NewReader(funcs)
parser := syntax.NewParser(syntax.Variant(syntax.LangBash))
file, err := parser.Parse(r, "funcs")
if err != nil {
return nil, err
}
rtn := make(map[string]string)
for _, stmt := range file.Stmts {
funcName, funcVal, err := parseFuncStmt(stmt, funcs)
if err != nil {
// TODO where to put parse errors
continue
}
if strings.HasPrefix(funcName, "_mshell_") {
continue
}
if funcName != "" {
rtn[funcName] = funcVal
}
}
return rtn, nil
}
+152 -30
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
@@ -12,6 +13,7 @@ import (
"strconv"
"strings"
"github.com/scripthaus-dev/sh2-server/pkg/rtnstate"
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
)
@@ -19,37 +21,11 @@ import (
const PCloudEndpoint = "https://api.getprompt.dev/central"
const PCloudEndpointVarName = "PCLOUD_ENDPOINT"
const APIVersion = 1
const MaxPtyUpdateSize = (128 * 1024) + 1
const TelemetryUrl = "/telemetry"
const NoTelemetryUrl = "/no-telemetry"
const CreateCloudSessionUrl = "/auth/create-cloud-session"
type NoTelemetryInputType struct {
ClientId string `json:"clientid"`
Value bool `json:"value"`
}
type TelemetryInputType struct {
UserId string `json:"userid"`
ClientId string `json:"clientid"`
CurDay string `json:"curday"`
Activity []*sstore.ActivityType `json:"activity"`
}
type CloudSession struct {
SessionId string `json:"sessionid"`
ViewKey string `json:"viewkey"`
WriteKey string `json:"writekey"`
EncType string `json:"enctype"`
UpdateVTS int64 `json:"updatevts"`
EncSessionData []byte `json:"enc_sessiondata" enc:"*"`
Name string `json:"-" enc:"name"`
}
func (cs *CloudSession) GetOData() string {
return fmt.Sprintf("session:%s", cs.SessionId)
}
const CreateWebScreenUrl = "/auth/create-web-screen"
type AuthInfo struct {
UserId string `json:"userid"`
@@ -194,12 +170,158 @@ func getAuthInfo(ctx context.Context) (AuthInfo, error) {
return AuthInfo{UserId: clientData.UserId, ClientId: clientData.ClientId}, nil
}
func CreateCloudSession(ctx context.Context) error {
func defaultError(err error, estr string) error {
if err != nil {
return err
}
return errors.New(estr)
}
func makeWebScreenUpdate(ctx context.Context, update sstore.ScreenUpdateType) (*WebShareUpdateType, error) {
rtn := &WebShareUpdateType{
ScreenId: update.ScreenId,
LineId: update.LineId,
UpdateType: update.UpdateType,
}
switch update.UpdateType {
case sstore.UpdateType_ScreenNew:
screen, err := sstore.GetScreenById(ctx, update.ScreenId)
if err != nil || screen == nil {
return nil, fmt.Errorf("error getting screen: %v", defaultError(err, "not found"))
}
rtn.Screen, err = webScreenFromScreen(screen)
if err != nil {
return nil, fmt.Errorf("error converting screen to web-screen: %v", err)
}
case sstore.UpdateType_ScreenDel:
break
case sstore.UpdateType_ScreenName:
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)")
}
rtn.SVal = screen.WebShareOpts.ShareName
case sstore.UpdateType_LineNew:
line, cmd, err := sstore.GetLineCmdByLineId(ctx, update.ScreenId, update.LineId)
if err != nil || line == nil {
return nil, fmt.Errorf("error getting line/cmd: %v", defaultError(err, "not found"))
}
rtn.Line, err = webLineFromLine(line)
if err != nil {
return nil, fmt.Errorf("error converting line to web-line: %v", err)
}
if cmd != nil {
rtn.Cmd, err = webCmdFromCmd(cmd)
if err != nil {
return nil, fmt.Errorf("error converting cmd to web-cmd: %v", err)
}
}
case sstore.UpdateType_LineDel:
break
case sstore.UpdateType_LineArchived:
line, err := sstore.GetLineById(ctx, update.ScreenId, update.LineId)
if err != nil || line == nil {
return nil, fmt.Errorf("error getting line: %v", defaultError(err, "not found"))
}
rtn.BVal = line.Archived
case sstore.UpdateType_LineRenderer:
line, err := sstore.GetLineById(ctx, update.ScreenId, update.LineId)
if err != nil || line == nil {
return nil, fmt.Errorf("error getting line: %v", defaultError(err, "not found"))
}
rtn.SVal = line.Renderer
case sstore.UpdateType_CmdStatus:
_, cmd, err := sstore.GetLineCmdByLineId(ctx, update.ScreenId, update.LineId)
if err != nil || cmd == nil {
return nil, fmt.Errorf("error getting cmd: %v", defaultError(err, "not found"))
}
rtn.SVal = cmd.Status
case sstore.UpdateType_CmdDoneInfo:
_, cmd, err := sstore.GetLineCmdByLineId(ctx, update.ScreenId, update.LineId)
if err != nil || cmd == nil {
return nil, fmt.Errorf("error getting cmd: %v", defaultError(err, "not found"))
}
rtn.DoneInfo = cmd.DoneInfo
case sstore.UpdateType_CmdRtnState:
_, cmd, err := sstore.GetLineCmdByLineId(ctx, update.ScreenId, update.LineId)
if err != nil || cmd == nil {
return nil, fmt.Errorf("error getting cmd: %v", defaultError(err, "not found"))
}
data, err := rtnstate.GetRtnStateDiff(ctx, update.ScreenId, cmd.CmdId)
if err != nil {
return nil, fmt.Errorf("cannot compute rtnstate: %v", err)
}
rtn.SVal = string(data)
case sstore.UpdateType_PtyPos:
cmdId, err := sstore.GetCmdIdFromLineId(ctx, update.ScreenId, update.LineId)
if err != nil {
return nil, fmt.Errorf("error getting cmdid: %v", err)
}
ptyPos, err := sstore.GetWebPtyPos(ctx, update.ScreenId, update.LineId)
if err != nil {
return nil, fmt.Errorf("error getting ptypos: %v", err)
}
realOffset, data, err := sstore.ReadPtyOutFile(ctx, update.ScreenId, cmdId, ptyPos, MaxPtyUpdateSize)
if err != nil {
return nil, fmt.Errorf("error getting ptydata: %v", err)
}
rtn.PtyData = &WebSharePtyData{PtyPos: realOffset, Data: data}
default:
return nil, fmt.Errorf("unsupported update type (pcloud/makeWebScreenUpdate): %s\n", update.UpdateType)
}
return rtn, nil
}
func finalizeWebScreenUpdate(ctx context.Context, screenUpdate sstore.ScreenUpdateType, webUpdate *WebShareUpdateType) error {
switch screenUpdate.UpdateType {
case sstore.UpdateType_PtyPos:
dataEof := len(webUpdate.PtyData.Data) < MaxPtyUpdateSize
newPos := webUpdate.PtyData.PtyPos + int64(len(webUpdate.PtyData.Data))
if dataEof {
err := sstore.RemoveScreenUpdate(ctx, screenUpdate.UpdateType)
if err != nil {
return err
}
}
err := sstore.SetWebPtyPos(ctx, screenUpdate.ScreenId, screenUpdate.LineId, newPos)
if err != nil {
return err
}
default:
err := sstore.RemoveScreenUpdate(ctx, screenUpdate.UpdateType)
if err != nil {
// this is not great, this *should* never fail and is not easy to recover from
return err
}
}
return nil
}
func DoWebScreenUpdate(ctx context.Context, update sstore.ScreenUpdateType) error {
return nil
}
func CreateWebScreen(ctx context.Context, screen *WebShareScreenType) error {
authInfo, err := getAuthInfo(ctx)
if err != nil {
return err
}
req, err := makeAuthPostReq(ctx, CreateCloudSessionUrl, authInfo, nil)
req, err := makeAuthPostReq(ctx, CreateWebScreenUrl, authInfo, screen)
if err != nil {
return err
}
+100
View File
@@ -0,0 +1,100 @@
package pcloud
import (
"fmt"
"github.com/scripthaus-dev/mshell/pkg/packet"
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
)
type NoTelemetryInputType struct {
ClientId string `json:"clientid"`
Value bool `json:"value"`
}
type TelemetryInputType struct {
UserId string `json:"userid"`
ClientId string `json:"clientid"`
CurDay string `json:"curday"`
Activity []*sstore.ActivityType `json:"activity"`
}
type WebShareUpdateType struct {
ScreenId string `json:"screenid"`
LineId string `json:"lineid"`
UpdateType string `json:"updatetype"`
Screen *WebShareScreenType `json:"screen,omitempty"`
Line *WebShareLineType `json:"line,omitempty"`
Cmd *WebShareCmdType `json:"cmd,omitempty"`
PtyData *WebSharePtyData `json:"ptydata,omitempty"`
SVal string `json:"sval,omitempty"`
BVal bool `json:"bval,omitempty"`
DoneInfo *sstore.CmdDoneInfo `json:"doneinfo,omitempty"`
}
type WebShareRemotePtr struct {
Alias string `json:"remotealias,omitempty"`
CanonicalName string `json:"remotecanonicalname"`
Name string `json:"name,omitempty"`
}
type WebShareScreenType struct {
ScreenId string `json:"screenid"`
ShareName string `json:"sharename"`
ViewKey string `json:"viewkey"`
}
func webScreenFromScreen(s *sstore.ScreenType) (*WebShareScreenType, error) {
if s == nil || s.ScreenId == "" {
return nil, fmt.Errorf("invalid nil screen")
}
if s.WebShareOpts == nil {
return nil, fmt.Errorf("invalid screen, no WebShareOpts")
}
if s.WebShareOpts.ViewKey == "" {
return nil, fmt.Errorf("invalid screen, no ViewKey")
}
if s.WebShareOpts.ShareName == "" {
return nil, fmt.Errorf("invalid screen, no ShareName")
}
return &WebShareScreenType{ScreenId: s.ScreenId, ShareName: s.WebShareOpts.ShareName, ViewKey: s.WebShareOpts.ViewKey}, nil
}
type WebShareLineType struct {
LineId string `json:"lineid"`
Ts int64 `json:"ts"`
LineNum int64 `json:"linenum"`
LineType string `json:"linetype"`
Renderer string `json:"renderer,omitempty"`
Text string `json:"text,omitempty"`
CmdId string `json:"cmdid,omitempty"`
Archived bool `json:"archived,omitempty"`
}
func webLineFromLine(line *sstore.LineType) (*WebShareLineType, error) {
return nil, nil
}
type WebShareCmdType struct {
LineId string `json:"lineid"`
CmdStr string `json:"cmdstr"`
RawCmdStr string `json:"rawcmdstr"`
Remote WebShareRemotePtr `json:"remote"`
FeState sstore.FeStateType `json:"festate"`
TermOpts sstore.TermOpts `json:"termopts"`
Status string `json:"status"`
StartPk *packet.CmdStartPacketType `json:"startpk,omitempty"`
DoneInfo *sstore.CmdDoneInfo `json:"doneinfo,omitempty"`
RtnState bool `json:"rtnstate,omitempty"`
RtnStateStr string `json:"rtnstatestr,omitempty"`
}
func webCmdFromCmd(cmd *sstore.CmdType) (*WebShareCmdType, error) {
return nil, nil
}
type WebSharePtyData struct {
PtyPos int64 `json:"ptypos,omitempty"`
Data []byte `json:"data,omitempty"`
}
+188
View File
@@ -0,0 +1,188 @@
package rtnstate
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/alessio/shellescape"
"github.com/scripthaus-dev/mshell/pkg/packet"
"github.com/scripthaus-dev/mshell/pkg/shexec"
"github.com/scripthaus-dev/mshell/pkg/simpleexpand"
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
"mvdan.cc/sh/v3/syntax"
)
func parseAliasStmt(stmt *syntax.Stmt, sourceStr string) (string, string, error) {
cmd := stmt.Cmd
callExpr, ok := cmd.(*syntax.CallExpr)
if !ok {
return "", "", fmt.Errorf("wrong cmd type for alias")
}
if len(callExpr.Args) != 2 {
return "", "", fmt.Errorf("wrong number of words in alias expr wordslen=%d", len(callExpr.Args))
}
firstWord := callExpr.Args[0]
if firstWord.Lit() != "alias" {
return "", "", fmt.Errorf("invalid alias cmd word (not 'alias')")
}
secondWord := callExpr.Args[1]
var ectx simpleexpand.SimpleExpandContext // no homedir, do not want ~ expansion
val, _ := simpleexpand.SimpleExpandWord(ectx, secondWord, sourceStr)
eqIdx := strings.Index(val, "=")
if eqIdx == -1 {
return "", "", fmt.Errorf("no '=' in alias definition")
}
return val[0:eqIdx], val[eqIdx+1:], nil
}
func ParseAliases(aliases string) (map[string]string, error) {
r := strings.NewReader(aliases)
parser := syntax.NewParser(syntax.Variant(syntax.LangBash))
file, err := parser.Parse(r, "aliases")
if err != nil {
return nil, err
}
rtn := make(map[string]string)
for _, stmt := range file.Stmts {
aliasName, aliasVal, err := parseAliasStmt(stmt, aliases)
if err != nil {
// fmt.Printf("stmt-err: %v\n", err)
continue
}
if aliasName != "" {
rtn[aliasName] = aliasVal
}
}
return rtn, nil
}
func parseFuncStmt(stmt *syntax.Stmt, source string) (string, string, error) {
cmd := stmt.Cmd
funcDecl, ok := cmd.(*syntax.FuncDecl)
if !ok {
return "", "", fmt.Errorf("cmd not FuncDecl")
}
name := funcDecl.Name.Value
// fmt.Printf("func: [%s]\n", name)
funcBody := funcDecl.Body
// fmt.Printf(" %d:%d\n", funcBody.Cmd.Pos().Offset(), funcBody.Cmd.End().Offset())
bodyStr := source[funcBody.Cmd.Pos().Offset():funcBody.Cmd.End().Offset()]
// fmt.Printf("<<<\n%s\n>>>\n", bodyStr)
// fmt.Printf("\n")
return name, bodyStr, nil
}
func ParseFuncs(funcs string) (map[string]string, error) {
r := strings.NewReader(funcs)
parser := syntax.NewParser(syntax.Variant(syntax.LangBash))
file, err := parser.Parse(r, "funcs")
if err != nil {
return nil, err
}
rtn := make(map[string]string)
for _, stmt := range file.Stmts {
funcName, funcVal, err := parseFuncStmt(stmt, funcs)
if err != nil {
// TODO where to put parse errors
continue
}
if strings.HasPrefix(funcName, "_mshell_") {
continue
}
if funcName != "" {
rtn[funcName] = funcVal
}
}
return rtn, nil
}
const MaxDiffKeyLen = 40
const MaxDiffValLen = 50
func displayStateUpdateDiff(buf *bytes.Buffer, oldState packet.ShellState, newState packet.ShellState) {
if newState.Cwd != oldState.Cwd {
buf.WriteString(fmt.Sprintf("cwd %s\n", newState.Cwd))
}
if !bytes.Equal(newState.ShellVars, oldState.ShellVars) {
newEnvMap := shexec.DeclMapFromState(&newState)
oldEnvMap := shexec.DeclMapFromState(&oldState)
for key, newVal := range newEnvMap {
oldVal, found := oldEnvMap[key]
if !found || !shexec.DeclsEqual(false, oldVal, newVal) {
var exportStr string
if newVal.IsExport() {
exportStr = "export "
}
buf.WriteString(fmt.Sprintf("%s%s=%s\n", exportStr, utilfn.EllipsisStr(key, MaxDiffKeyLen), utilfn.EllipsisStr(newVal.Value, MaxDiffValLen)))
}
}
for key, _ := range oldEnvMap {
_, found := newEnvMap[key]
if !found {
buf.WriteString(fmt.Sprintf("unset %s\n", utilfn.EllipsisStr(key, MaxDiffKeyLen)))
}
}
}
if newState.Aliases != oldState.Aliases {
newAliasMap, _ := ParseAliases(newState.Aliases)
oldAliasMap, _ := ParseAliases(oldState.Aliases)
for aliasName, newAliasVal := range newAliasMap {
oldAliasVal, found := oldAliasMap[aliasName]
if !found || newAliasVal != oldAliasVal {
buf.WriteString(fmt.Sprintf("alias %s\n", utilfn.EllipsisStr(shellescape.Quote(aliasName), MaxDiffKeyLen)))
}
}
for aliasName, _ := range oldAliasMap {
_, found := newAliasMap[aliasName]
if !found {
buf.WriteString(fmt.Sprintf("unalias %s\n", utilfn.EllipsisStr(shellescape.Quote(aliasName), MaxDiffKeyLen)))
}
}
}
if newState.Funcs != oldState.Funcs {
newFuncMap, _ := ParseFuncs(newState.Funcs)
oldFuncMap, _ := ParseFuncs(oldState.Funcs)
for funcName, newFuncVal := range newFuncMap {
oldFuncVal, found := oldFuncMap[funcName]
if !found || newFuncVal != oldFuncVal {
buf.WriteString(fmt.Sprintf("function %s\n", utilfn.EllipsisStr(shellescape.Quote(funcName), MaxDiffKeyLen)))
}
}
for funcName, _ := range oldFuncMap {
_, found := newFuncMap[funcName]
if !found {
buf.WriteString(fmt.Sprintf("unset -f %s\n", utilfn.EllipsisStr(shellescape.Quote(funcName), MaxDiffKeyLen)))
}
}
}
}
func GetRtnStateDiff(ctx context.Context, screenId string, cmdId string) ([]byte, error) {
cmd, err := sstore.GetCmdByScreenId(ctx, screenId, cmdId)
if err != nil {
return nil, err
}
if cmd == nil {
return nil, nil
}
if !cmd.RtnState {
return nil, nil
}
if cmd.RtnStatePtr.IsEmpty() {
return nil, nil
}
var outputBytes bytes.Buffer
initialState, err := sstore.GetFullState(ctx, cmd.StatePtr)
if err != nil {
return nil, fmt.Errorf("getting initial full state: %v", err)
}
rtnState, err := sstore.GetFullState(ctx, cmd.RtnStatePtr)
if err != nil {
return nil, fmt.Errorf("getting rtn full state: %v", err)
}
displayStateUpdateDiff(&outputBytes, *initialState, *rtnState)
return outputBytes.Bytes(), nil
}
+89 -15
View File
@@ -725,6 +725,14 @@ func FindLineIdByArg(ctx context.Context, screenId string, lineArg string) (stri
return lineId, nil
}
func GetCmdIdFromLineId(ctx context.Context, screenId string, lineId string) (string, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
query := `SELECT cmdid FROM line WHERE screenid = ? AND lineid = ?`
cmdId := tx.GetString(query, screenId, lineId)
return cmdId, nil
})
}
func GetLineCmdByLineId(ctx context.Context, screenId string, lineId string) (*LineType, *CmdType, error) {
return WithTxRtn3(ctx, func(tx *TxWrap) (*LineType, *CmdType, error) {
var lineVal LineType
@@ -792,7 +800,7 @@ INSERT INTO cmd ( screenid, cmdid, remoteownerid, remoteid, remotename, cmdstr,
tx.NamedExec(query, cmdMap)
}
if isWebShare(tx, line.ScreenId) {
insertScreenUpdate(tx, line.ScreenId, line.LineId, UpdateType_LineNew)
insertScreenLineUpdate(tx, line.ScreenId, line.LineId, UpdateType_LineNew)
}
return nil
})
@@ -829,8 +837,8 @@ func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, doneInfo *CmdDon
return err
}
if isWebShare(tx, screenId) {
insertScreenUpdateByCmdId(tx, screenId, ck.GetCmdId(), UpdateType_CmdDoneInfo)
insertScreenUpdateByCmdId(tx, screenId, ck.GetCmdId(), UpdateType_CmdStatus)
insertScreenCmdUpdate(tx, screenId, ck.GetCmdId(), UpdateType_CmdDoneInfo)
insertScreenCmdUpdate(tx, screenId, ck.GetCmdId(), UpdateType_CmdStatus)
}
return nil
})
@@ -852,7 +860,7 @@ func UpdateCmdRtnState(ctx context.Context, ck base.CommandKey, statePtr ShellSt
query := `UPDATE cmd SET rtnbasehash = ?, rtndiffhasharr = ? WHERE screenid = ? AND cmdid = ?`
tx.Exec(query, statePtr.BaseHash, quickJsonArr(statePtr.DiffHashArr), screenId, ck.GetCmdId())
if isWebShare(tx, screenId) {
insertScreenUpdateByCmdId(tx, screenId, ck.GetCmdId(), UpdateType_CmdRtnState)
insertScreenCmdUpdate(tx, screenId, ck.GetCmdId(), UpdateType_CmdRtnState)
}
return nil
})
@@ -870,9 +878,6 @@ func AppendCmdErrorPk(ctx context.Context, errPk *packet.CmdErrorPacketType) err
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET runout = json_insert(runout, '$[#]', ?) WHERE screenid = ? AND cmdid = ?`
tx.Exec(query, quickJson(errPk), screenId, errPk.CK.GetCmdId())
if isWebShare(tx, screenId) {
insertScreenUpdateByCmdId(tx, screenId, errPk.CK.GetCmdId(), UpdateType_CmdRunOut)
}
return nil
})
}
@@ -887,16 +892,28 @@ func ReInitFocus(ctx context.Context) error {
func HangupAllRunningCmds(ctx context.Context) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ? WHERE status = ?`
var cmdPtrs []CmdPtr
query := `SELECT screenid, cmdid FROM cmd WHERE status = ?`
tx.Select(&cmdPtrs, query, CmdStatusRunning)
query = `UPDATE cmd SET status = ? WHERE status = ?`
tx.Exec(query, CmdStatusHangup, CmdStatusRunning)
for _, cmdPtr := range cmdPtrs {
insertScreenCmdUpdate(tx, cmdPtr.ScreenId, cmdPtr.CmdId, UpdateType_CmdStatus)
}
return nil
})
}
func HangupRunningCmdsByRemoteId(ctx context.Context, remoteId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ? WHERE status = ? AND remoteid = ?`
var cmdPtrs []CmdPtr
query := `SELECT screenid, cmdid FROM cmd WHERE status = ? AND remoteid = ?`
tx.Select(&cmdPtrs, query, CmdStatusRunning, remoteId)
query = `UPDATE cmd SET status = ? WHERE status = ? AND remoteid = ?`
tx.Exec(query, CmdStatusHangup, CmdStatusRunning, remoteId)
for _, cmdPtr := range cmdPtrs {
insertScreenCmdUpdate(tx, cmdPtr.ScreenId, cmdPtr.CmdId, UpdateType_CmdStatus)
}
return nil
})
}
@@ -905,6 +922,7 @@ func HangupCmd(ctx context.Context, ck base.CommandKey) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE cmd SET status = ? WHERE screenid = ? AND cmdid = ?`
tx.Exec(query, CmdStatusHangup, ck.GetGroupId(), ck.GetCmdId())
insertScreenCmdUpdate(tx, ck.GetGroupId(), ck.GetCmdId(), UpdateType_CmdStatus)
return nil
})
}
@@ -1838,7 +1856,7 @@ func UpdateLineRenderer(ctx context.Context, screenId string, lineId string, ren
query := `UPDATE line SET renderer = ? WHERE lineid = ?`
tx.Exec(query, renderer, lineId)
if isWebShare(tx, screenId) {
insertScreenUpdate(tx, screenId, lineId, UpdateType_LineRenderer)
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineRenderer)
}
return nil
})
@@ -1867,7 +1885,7 @@ func SetLineArchivedById(ctx context.Context, screenId string, lineId string, ar
query := `UPDATE line SET archived = ? WHERE lineid = ?`
tx.Exec(query, archived, lineId)
if isWebShare(tx, screenId) {
insertScreenUpdate(tx, screenId, lineId, UpdateType_LineArchived)
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineArchived)
}
return nil
})
@@ -1900,7 +1918,7 @@ func PurgeLinesByIds(ctx context.Context, screenId string, lineIds []string) err
}
}
if isWS {
insertScreenUpdate(tx, screenId, lineId, UpdateType_LineDel)
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_LineDel)
}
}
return nil
@@ -2360,6 +2378,7 @@ func ScreenWebShareStart(ctx context.Context, screenId string, shareOpts ScreenW
}
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)
@@ -2381,6 +2400,7 @@ func ScreenWebShareStop(ctx context.Context, screenId string) error {
tx.Exec(query, ShareModeLocal, "null", screenId)
query = `DELETE FROM screenupdate WHERE screenid = ?`
tx.Exec(query, screenId)
insertScreenUpdate(tx, screenId, UpdateType_ScreenDel)
return nil
})
}
@@ -2389,7 +2409,16 @@ func isWebShare(tx *TxWrap, screenId string) bool {
return tx.Exists(`SELECT screenid FROM screen WHERE screenid = ? AND sharemode = ?`, screenId, ShareModeWeb)
}
func insertScreenUpdate(tx *TxWrap, screenId string, lineId string, updateType string) {
func insertScreenUpdate(tx *TxWrap, screenId string, updateType string) {
if screenId == "" {
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
return
}
query := `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets) VALUES (?, ?, ?, ?)`
tx.Exec(query, screenId, "", updateType, time.Now().UnixMilli())
}
func insertScreenLineUpdate(tx *TxWrap, screenId string, lineId string, updateType string) {
if screenId == "" {
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
return
@@ -2402,7 +2431,7 @@ func insertScreenUpdate(tx *TxWrap, screenId string, lineId string, updateType s
tx.Exec(query, screenId, lineId, updateType, time.Now().UnixMilli())
}
func insertScreenUpdateByCmdId(tx *TxWrap, screenId string, cmdId string, updateType string) {
func insertScreenCmdUpdate(tx *TxWrap, screenId string, cmdId string, updateType string) {
if screenId == "" {
tx.SetErr(errors.New("invalid screen-update, screenid is empty"))
return
@@ -2414,6 +2443,51 @@ func insertScreenUpdateByCmdId(tx *TxWrap, screenId string, cmdId string, update
query := `SELECT lineid FROM line WHERE screenid = ? AND cmdid = ?`
lineId := tx.GetString(query, screenId, cmdId)
if lineId != "" {
insertScreenUpdate(tx, screenId, lineId, updateType)
insertScreenLineUpdate(tx, screenId, lineId, updateType)
}
}
func RemoveScreenUpdate(ctx context.Context, updateId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `DELETE FROM screenupdate WHERE updateid = ?`
tx.Exec(query, updateId)
return nil
})
}
func InsertPtyPosUpdate(ctx context.Context, screenId string, cmdId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
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)
}
query = `SELECT updateid FROM screenupdate WHERE screenid = ? AND lineid = ? AND updatetype = ?`
if !tx.Exists(query, screenId, lineId, UpdateType_PtyPos) {
insertScreenLineUpdate(tx, screenId, lineId, UpdateType_PtyPos)
}
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 = ?`
ptyPos := tx.GetInt(query, screenId, lineId)
return int64(ptyPos), nil
})
}
func SetWebPtyPos(ctx context.Context, screenId string, lineId string, ptyPos int64) error {
return WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT screenid FROM webptypos WHERE screenid = ? AND lineid = ?`
if tx.Exists(query, screenId, lineId) {
query = `UPDATE webptypos SET ptypos = ? WHERE screenid = ? AND lineid = ?`
tx.Exec(query, ptyPos, screenId, lineId)
} else {
query = `INSERT INTO webptypos (screenid, lineid, ptypos) VALUES (?, ?, ?)`
tx.Exec(query, screenId, lineId, ptyPos)
}
return nil
})
}
+21 -1
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"fmt"
"log"
"os"
"path"
@@ -60,10 +61,15 @@ func AppendToCmdPtyBlob(ctx context.Context, screenId string, cmdId string, data
PtyData64: data64,
PtyDataLen: int64(len(data)),
}
err = InsertPtyPosUpdate(ctx, screenId, cmdId)
if err != nil {
// just log
log.Printf("error inserting ptypos update %s/%s: %v\n", screenId, cmdId, err)
}
return update, nil
}
// returns (offset, data, err)
// returns (real-offset, data, err)
func ReadFullPtyOutFile(ctx context.Context, screenId string, cmdId string) (int64, []byte, error) {
ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId)
if err != nil {
@@ -77,6 +83,20 @@ func ReadFullPtyOutFile(ctx context.Context, screenId string, cmdId string) (int
return f.ReadAll(ctx)
}
// returns (real-offset, data, err)
func ReadPtyOutFile(ctx context.Context, screenId string, cmdId string, offset int64, maxSize int64) (int64, []byte, error) {
ptyOutFileName, err := scbase.PtyOutFile(screenId, cmdId)
if err != nil {
return 0, nil, err
}
f, err := cirfile.OpenCirFile(ptyOutFileName)
if err != nil {
return 0, nil, err
}
defer f.Close()
return f.ReadAtWithMax(ctx, offset, maxSize)
}
type SessionDiskSizeType struct {
NumFiles int
TotalSize int64
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"github.com/golang-migrate/migrate/v4"
)
const MaxMigration = 15
const MaxMigration = 16
const MigratePrimaryScreenVersion = 9
func MakeMigrate() (*migrate.Migrate, error) {
+16 -55
View File
@@ -80,16 +80,17 @@ const (
)
const (
UpdateType_ScreenName = "screen:sharename"
UpdateType_ScreenCurRemote = "screen:curremote"
UpdateType_LineNew = "line:new"
UpdateType_LineDel = "line:del"
UpdateType_LineArchived = "line:archived"
UpdateType_LineRenderer = "line:renderer"
UpdateType_CmdStatus = "cmd:status"
UpdateType_CmdDoneInfo = "cmd:doneinfo"
UpdateType_CmdRunOut = "cmd:runout"
UpdateType_CmdRtnState = "cmd:rtnstate"
UpdateType_ScreenNew = "screen:new"
UpdateType_ScreenDel = "screen:del"
UpdateType_ScreenName = "screen:sharename"
UpdateType_LineNew = "line:new"
UpdateType_LineDel = "line:del"
UpdateType_LineArchived = "line:archived"
UpdateType_LineRenderer = "line:renderer"
UpdateType_CmdStatus = "cmd:status"
UpdateType_CmdDoneInfo = "cmd:doneinfo"
UpdateType_CmdRtnState = "cmd:rtnstate"
UpdateType_PtyPos = "pty:pos"
)
const MaxTzNameLen = 50
@@ -144,6 +145,11 @@ func CloseDB() {
globalDB = nil
}
type CmdPtr struct {
ScreenId string
CmdId string
}
type ClientWinSizeType struct {
Width int `json:"width"`
Height int `json:"height"`
@@ -228,11 +234,6 @@ type ClientData struct {
func (ClientData) UseDBMap() {}
type CloudAclType struct {
UserId string `json:"userid"`
Role string `json:"role"`
}
type SessionType struct {
SessionId string `json:"sessionid"`
Name string `json:"name"`
@@ -249,46 +250,6 @@ type SessionType struct {
Full bool `json:"full,omitempty"`
}
type CloudSessionType struct {
SessionId string
ViewKey string
WriteKey string
EncKey string
EncType string
Vts int64
Acl []*CloudAclType
}
func (cs *CloudSessionType) ToMap() map[string]any {
m := make(map[string]any)
m["sessionid"] = cs.SessionId
m["viewkey"] = cs.ViewKey
m["writekey"] = cs.WriteKey
m["enckey"] = cs.EncKey
m["enctype"] = cs.EncType
m["vts"] = cs.Vts
m["acl"] = quickJsonArr(cs.Acl)
return m
}
func (cs *CloudSessionType) FromMap(m map[string]interface{}) bool {
quickSetStr(&cs.SessionId, m, "sessionid")
quickSetStr(&cs.ViewKey, m, "viewkey")
quickSetStr(&cs.WriteKey, m, "writekey")
quickSetStr(&cs.EncKey, m, "enckey")
quickSetStr(&cs.EncType, m, "enctype")
quickSetInt64(&cs.Vts, m, "vts")
quickSetJsonArr(&cs.Acl, m, "acl")
return true
}
type CloudUpdate struct {
UpdateId string
Ts int64
UpdateType string
UpdateKeys []string
}
type SessionStatsType struct {
SessionId string `json:"sessionid"`
NumScreens int `json:"numscreens"`