From 037497e7f141e397f9e7310ead1b55e10883695a Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Tue, 20 Aug 2024 14:56:48 -0700 Subject: [PATCH] wsh edit working (#252) --- cmd/server/main-server.go | 5 +- cmd/wsh/cmd/wshcmd-edit.go | 69 ++++++ cmd/wsh/cmd/wshcmd-root.go | 8 + cmd/wsh/cmd/wshcmd-term.go | 13 +- cmd/wsh/cmd/wshcmd-view.go | 20 +- frontend/app/store/wshserver.ts | 2 +- frontend/types/gotypes.d.ts | 41 ++-- pkg/blockcontroller/blockcontroller.go | 58 ++--- pkg/remote/conncontroller/conncontroller.go | 3 +- pkg/service/blockservice/blockservice.go | 7 +- pkg/service/clientservice/clientservice.go | 81 +++---- pkg/service/objectservice/objectservice.go | 64 +++--- pkg/service/service.go | 25 +-- pkg/service/windowservice/windowservice.go | 46 ++-- pkg/shellexec/shellexec.go | 8 +- pkg/tsgen/tsgen.go | 13 +- pkg/util/shellutil/shellutil.go | 6 +- pkg/waveobj/ctxupdate.go | 137 +++++++++++ .../wstore_types.go => waveobj/wtype.go} | 30 +-- .../wstore_meta.go => waveobj/wtypemeta.go} | 6 +- pkg/wconfig/settingsconfig.go | 77 ++++--- pkg/wcore/wcore.go | 70 ++++++ pkg/web/web.go | 3 +- pkg/web/webcmd/webcmd.go | 8 +- pkg/wshrpc/wshclient/wshclient.go | 6 +- pkg/wshrpc/wshrpctypes.go | 59 +++-- pkg/wshrpc/wshserver/wshserver.go | 50 ++--- pkg/wshutil/wshevent.go | 67 ++++++ pkg/wshutil/wshproxy.go | 27 ++- pkg/wshutil/wshrouter.go | 9 +- pkg/wshutil/wshrpc.go | 48 ++-- pkg/wshutil/wshutil.go | 49 +++- pkg/wstore/wstore.go | 212 ++++-------------- pkg/wstore/wstore_dbops.go | 8 +- pkg/wstore/wstore_dbsetup.go | 13 +- 35 files changed, 828 insertions(+), 520 deletions(-) create mode 100644 cmd/wsh/cmd/wshcmd-edit.go create mode 100644 pkg/waveobj/ctxupdate.go rename pkg/{wstore/wstore_types.go => waveobj/wtype.go} (90%) rename pkg/{wstore/wstore_meta.go => waveobj/wtypemeta.go} (98%) create mode 100644 pkg/wcore/wcore.go create mode 100644 pkg/wshutil/wshevent.go diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 769a529e..4dae197d 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -22,6 +22,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/telemetry" "github.com/wavetermdev/thenextwave/pkg/util/shellutil" "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wcloud" "github.com/wavetermdev/thenextwave/pkg/wconfig" "github.com/wavetermdev/thenextwave/pkg/web" @@ -116,7 +117,7 @@ func sendTelemetryWrapper() { }() ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - client, err := wstore.DBGetSingleton[*wstore.Client](ctx) + client, err := wstore.DBGetSingleton[*waveobj.Client](ctx) if err != nil { log.Printf("[error] getting client data for telemetry: %v\n", err) return @@ -133,7 +134,7 @@ func startupActivityUpdate() { activity := telemetry.ActivityUpdate{ Startup: 1, } - activity.NumTabs, _ = wstore.DBGetCount[*wstore.Tab](ctx) + activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx) err := telemetry.UpdateActivity(ctx, activity) // set at least one record into activity (don't use go routine wrap here) if err != nil { log.Printf("error updating startup activity: %v\n", err) diff --git a/cmd/wsh/cmd/wshcmd-edit.go b/cmd/wsh/cmd/wshcmd-edit.go new file mode 100644 index 00000000..4640cf9f --- /dev/null +++ b/cmd/wsh/cmd/wshcmd-edit.go @@ -0,0 +1,69 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "io/fs" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/wavetermdev/thenextwave/pkg/waveobj" + "github.com/wavetermdev/thenextwave/pkg/wshrpc" + "github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient" +) + +var editCmd = &cobra.Command{ + Use: "edit", + Short: "edit a file", + Args: cobra.ExactArgs(1), + Run: editRun, + PreRunE: preRunSetupRpcClient, +} + +func init() { + rootCmd.AddCommand(editCmd) +} + +func editRun(cmd *cobra.Command, args []string) { + fileArg := args[0] + wshCmd := wshrpc.CommandCreateBlockData{ + BlockDef: &waveobj.BlockDef{ + Meta: map[string]any{ + waveobj.MetaKey_View: "preview", + waveobj.MetaKey_File: fileArg, + }, + }, + } + if RpcContext.Conn != "" { + wshCmd.BlockDef.Meta[waveobj.MetaKey_Connection] = RpcContext.Conn + } + absFile, err := filepath.Abs(fileArg) + if err != nil { + WriteStderr("[error] getting absolute path: %v\n", err) + return + } + _, err = os.Stat(absFile) + if err == fs.ErrNotExist { + WriteStderr("[error] file does not exist: %q\n", absFile) + return + } + if err != nil { + WriteStderr("[error] getting file info: %v\n", err) + return + } + blockRef, err := wshclient.CreateBlockCommand(RpcClient, wshCmd, &wshrpc.RpcOpts{Timeout: 2000}) + if err != nil { + WriteStderr("[error] running view command: %v\r\n", err) + return + } + doneCh := make(chan bool) + RpcClient.EventListener.On("blockclose", func(event *wshrpc.WaveEvent) { + if event.HasScope(blockRef.String()) { + close(doneCh) + } + }) + wshclient.EventSubCommand(RpcClient, wshrpc.SubscriptionRequest{Event: "blockclose", Scopes: []string{blockRef.String()}}, nil) + <-doneCh +} diff --git a/cmd/wsh/cmd/wshcmd-root.go b/cmd/wsh/cmd/wshcmd-root.go index 7e465122..62214c32 100644 --- a/cmd/wsh/cmd/wshcmd-root.go +++ b/cmd/wsh/cmd/wshcmd-root.go @@ -9,6 +9,7 @@ import ( "log" "os" "regexp" + "runtime/debug" "strconv" "strings" "time" @@ -33,6 +34,7 @@ var ( var usingHtmlMode bool var WrappedStdin io.Reader = os.Stdin var RpcClient *wshutil.WshRpc +var RpcContext wshrpc.RpcContext var UsingTermWshMode bool func extraShutdownFn() { @@ -78,6 +80,11 @@ func setupRpcClient(serverImpl wshutil.ServerImpl) error { RpcClient, WrappedStdin = wshutil.SetupTerminalRpcClient(serverImpl) return nil } + rpcCtx, err := wshutil.ExtractUnverifiedRpcContext(jwtToken) + if err != nil { + return fmt.Errorf("error extracting rpc context from %s: %v", wshutil.WaveJwtTokenVarName, err) + } + RpcContext = *rpcCtx sockName, err := wshutil.ExtractUnverifiedSocketName(jwtToken) if err != nil { return fmt.Errorf("error extracting socket name from %s: %v", wshutil.WaveJwtTokenVarName, err) @@ -162,6 +169,7 @@ func Execute() { r := recover() if r != nil { WriteStderr("[panic] %v\n", r) + debug.PrintStack() wshutil.DoShutdown("", 1, true) } else { wshutil.DoShutdown("", 0, false) diff --git a/cmd/wsh/cmd/wshcmd-term.go b/cmd/wsh/cmd/wshcmd-term.go index 46ea6fda..52371167 100644 --- a/cmd/wsh/cmd/wshcmd-term.go +++ b/cmd/wsh/cmd/wshcmd-term.go @@ -9,9 +9,9 @@ import ( "github.com/spf13/cobra" "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wshrpc" "github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) var termCmd = &cobra.Command{ @@ -46,14 +46,17 @@ func termRun(cmd *cobra.Command, args []string) { return } createBlockData := wshrpc.CommandCreateBlockData{ - BlockDef: &wstore.BlockDef{ + BlockDef: &waveobj.BlockDef{ Meta: map[string]interface{}{ - wstore.MetaKey_View: "term", - wstore.MetaKey_CmdCwd: cwd, - wstore.MetaKey_Controller: "shell", + waveobj.MetaKey_View: "term", + waveobj.MetaKey_CmdCwd: cwd, + waveobj.MetaKey_Controller: "shell", }, }, } + if RpcContext.Conn != "" { + createBlockData.BlockDef.Meta[waveobj.MetaKey_Connection] = RpcContext.Conn + } oref, err := wshclient.CreateBlockCommand(RpcClient, createBlockData, nil) if err != nil { WriteStderr("[error] creating new terminal block: %v\n", err) diff --git a/cmd/wsh/cmd/wshcmd-view.go b/cmd/wsh/cmd/wshcmd-view.go index 74668594..2e7192b7 100644 --- a/cmd/wsh/cmd/wshcmd-view.go +++ b/cmd/wsh/cmd/wshcmd-view.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wshrpc" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) var viewNewBlock bool @@ -31,13 +31,14 @@ func init() { func viewRun(cmd *cobra.Command, args []string) { fileArg := args[0] + conn := RpcContext.Conn var wshCmd *wshrpc.CommandCreateBlockData if strings.HasPrefix(fileArg, "http://") || strings.HasPrefix(fileArg, "https://") { wshCmd = &wshrpc.CommandCreateBlockData{ - BlockDef: &wstore.BlockDef{ - Meta: map[string]interface{}{ - wstore.MetaKey_View: "web", - wstore.MetaKey_Url: fileArg, + BlockDef: &waveobj.BlockDef{ + Meta: map[string]any{ + waveobj.MetaKey_View: "web", + waveobj.MetaKey_Url: fileArg, }, }, } @@ -57,13 +58,16 @@ func viewRun(cmd *cobra.Command, args []string) { return } wshCmd = &wshrpc.CommandCreateBlockData{ - BlockDef: &wstore.BlockDef{ + BlockDef: &waveobj.BlockDef{ Meta: map[string]interface{}{ - wstore.MetaKey_View: "preview", - wstore.MetaKey_File: absFile, + waveobj.MetaKey_View: "preview", + waveobj.MetaKey_File: absFile, }, }, } + if conn != "" { + wshCmd.BlockDef.Meta[waveobj.MetaKey_Connection] = conn + } } _, err := RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000}) if err != nil { diff --git a/frontend/app/store/wshserver.ts b/frontend/app/store/wshserver.ts index 8afebe4d..df43efb7 100644 --- a/frontend/app/store/wshserver.ts +++ b/frontend/app/store/wshserver.ts @@ -13,7 +13,7 @@ class WshServerType { } // command "authenticate" [call] - AuthenticateCommand(data: string, opts?: RpcOpts): Promise { + AuthenticateCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("authenticate", data, opts); } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index d98b045c..2fd95895 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -22,7 +22,7 @@ declare global { installonquit: boolean; }; - // wstore.Block + // waveobj.Block type Block = WaveObj & { blockdef: BlockDef; runtimeopts?: RuntimeOpts; @@ -36,7 +36,7 @@ declare global { shellprocstatus?: string; }; - // wstore.BlockDef + // waveobj.BlockDef type BlockDef = { files?: {[key: string]: FileDef}; meta?: MetaType; @@ -54,7 +54,7 @@ declare global { inputdata64: string; }; - // wstore.Client + // waveobj.Client type Client = WaveObj & { windowids: string[]; tosagreed?: number; @@ -67,6 +67,11 @@ declare global { data: {[key: string]: any}; }; + // wshrpc.CommandAuthenticateRtnData + type CommandAuthenticateRtnData = { + routeid: string; + }; + // wshrpc.CommandBlockInputData type CommandBlockInputData = { blockid: string; @@ -158,7 +163,7 @@ declare global { count: number; }; - // wstore.FileDef + // waveobj.FileDef type FileDef = { filetype?: string; path?: string; @@ -194,13 +199,13 @@ declare global { data64: string; }; - // wstore.LayoutState + // waveobj.LayoutState type LayoutState = WaveObj & { rootnode?: any; magnifiednodeid?: string; }; - // wstore.MetaTSType + // waveobj.MetaTSType type MetaType = { view?: string; controller?: string; @@ -297,7 +302,7 @@ declare global { prompt: OpenAIPromptMessageType[]; }; - // wstore.Point + // waveobj.Point type Point = { x: number; y: number; @@ -325,7 +330,7 @@ declare global { route?: string; }; - // wstore.RuntimeOpts + // waveobj.RuntimeOpts type RuntimeOpts = { termsize?: TermSize; winsize?: WinSize; @@ -355,20 +360,20 @@ declare global { presets?: {[key: string]: MetaType}; }; - // wstore.StickerClickOptsType + // waveobj.StickerClickOptsType type StickerClickOptsType = { sendinput?: string; createblock?: BlockDef; }; - // wstore.StickerDisplayOptsType + // waveobj.StickerDisplayOptsType type StickerDisplayOptsType = { icon: string; imgsrc: string; svgblob?: string; }; - // wstore.StickerType + // waveobj.StickerType type StickerType = { stickertype: string; style: {[key: string]: any}; @@ -383,7 +388,7 @@ declare global { allscopes?: boolean; }; - // wstore.Tab + // waveobj.Tab type Tab = WaveObj & { name: string; layoutstate: string; @@ -395,7 +400,7 @@ declare global { enabled: boolean; }; - // wstore.TermSize + // waveobj.TermSize type TermSize = { rows: number; cols: number; @@ -440,7 +445,7 @@ declare global { values: {[key: string]: number}; }; - // wstore.UIContext + // waveobj.UIContext type UIContext = { windowid: string; activetabid: string; @@ -558,7 +563,7 @@ declare global { meta: MetaType; }; - // wstore.WaveObjUpdate + // waveobj.WaveObjUpdate type WaveObjUpdate = { updatetype: string; otype: string; @@ -566,7 +571,7 @@ declare global { obj?: WaveObj; }; - // wstore.Window + // waveobj.Window type WaveWindow = WaveObj & { workspaceid: string; activetabid: string; @@ -607,7 +612,7 @@ declare global { blockdef: BlockDef; }; - // wstore.WinSize + // waveobj.WinSize type WinSize = { width: number; height: number; @@ -622,7 +627,7 @@ declare global { reducedmotion: boolean; }; - // wstore.Workspace + // waveobj.Workspace type Workspace = WaveObj & { name: string; tabids: string[]; diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index 7f08f08b..6e2bc222 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -54,9 +54,9 @@ var globalLock = &sync.Mutex{} var blockControllerMap = make(map[string]*BlockController) type BlockInputUnion struct { - InputData []byte `json:"inputdata,omitempty"` - SigName string `json:"signame,omitempty"` - TermSize *wstore.TermSize `json:"termsize,omitempty"` + InputData []byte `json:"inputdata,omitempty"` + SigName string `json:"signame,omitempty"` + TermSize *waveobj.TermSize `json:"termsize,omitempty"` } type BlockController struct { @@ -64,7 +64,7 @@ type BlockController struct { ControllerType string TabId string BlockId string - BlockDef *wstore.BlockDef + BlockDef *waveobj.BlockDef Status string CreatedHtmlFile bool ShellProc *shellexec.ShellProc @@ -115,7 +115,7 @@ func (bc *BlockController) getShellProc() *shellexec.ShellProc { } type RunShellOpts struct { - TermSize wstore.TermSize `json:"termsize,omitempty"` + TermSize waveobj.TermSize `json:"termsize,omitempty"` } func (bc *BlockController) UpdateControllerAndSendUpdate(updateFn func() bool) { @@ -127,7 +127,7 @@ func (bc *BlockController) UpdateControllerAndSendUpdate(updateFn func() bool) { log.Printf("sending blockcontroller update %#v\n", bc.GetRuntimeStatus()) go eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_BlockControllerStatus, - ORef: waveobj.MakeORef(wstore.OType_Block, bc.BlockId).String(), + ORef: waveobj.MakeORef(waveobj.OType_Block, bc.BlockId).String(), Data: bc.GetRuntimeStatus(), }) } @@ -145,7 +145,7 @@ func HandleTruncateBlockFile(blockId string, blockFile string) error { } eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_BlockFile, - ORef: waveobj.MakeORef(wstore.OType_Block, blockId).String(), + ORef: waveobj.MakeORef(waveobj.OType_Block, blockId).String(), Data: &eventbus.WSFileEventData{ ZoneId: blockId, FileName: blockFile, @@ -165,7 +165,7 @@ func HandleAppendBlockFile(blockId string, blockFile string, data []byte) error } eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_BlockFile, - ORef: waveobj.MakeORef(wstore.OType_Block, blockId).String(), + ORef: waveobj.MakeORef(waveobj.OType_Block, blockId).String(), Data: &eventbus.WSFileEventData{ ZoneId: blockId, FileName: blockFile, @@ -180,9 +180,9 @@ func (bc *BlockController) resetTerminalState() { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() var shouldTruncate bool - blockData, getBlockDataErr := wstore.DBMustGet[*wstore.Block](ctx, bc.BlockId) + blockData, getBlockDataErr := wstore.DBMustGet[*waveobj.Block](ctx, bc.BlockId) if getBlockDataErr == nil { - shouldTruncate = blockData.Meta.GetBool(wstore.MetaKey_CmdClearOnRestart, false) + shouldTruncate = blockData.Meta.GetBool(waveobj.MetaKey_CmdClearOnRestart, false) } if shouldTruncate { err := HandleTruncateBlockFile(bc.BlockId, BlockFile_Term) @@ -231,7 +231,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj if shellProcErr != nil { return shellProcErr } - remoteName := blockMeta.GetString(wstore.MetaKey_Connection, "") + remoteName := blockMeta.GetString(waveobj.MetaKey_Connection, "") var cmdStr string cmdOpts := shellexec.CommandOptsType{ Env: make(map[string]string), @@ -239,22 +239,22 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj if bc.ControllerType == BlockController_Shell { cmdOpts.Interactive = true cmdOpts.Login = true - cmdOpts.Cwd = blockMeta.GetString(wstore.MetaKey_CmdCwd, "") + cmdOpts.Cwd = blockMeta.GetString(waveobj.MetaKey_CmdCwd, "") if cmdOpts.Cwd != "" { cmdOpts.Cwd = wavebase.ExpandHomeDir(cmdOpts.Cwd) } } else if bc.ControllerType == BlockController_Cmd { - cmdStr = blockMeta.GetString(wstore.MetaKey_Cmd, "") + cmdStr = blockMeta.GetString(waveobj.MetaKey_Cmd, "") if cmdStr == "" { return fmt.Errorf("missing cmd in block meta") } - cmdOpts.Cwd = blockMeta.GetString(wstore.MetaKey_CmdCwd, "") + cmdOpts.Cwd = blockMeta.GetString(waveobj.MetaKey_CmdCwd, "") if cmdOpts.Cwd != "" { cmdOpts.Cwd = wavebase.ExpandHomeDir(cmdOpts.Cwd) } - cmdOpts.Interactive = blockMeta.GetBool(wstore.MetaKey_CmdInteractive, false) - cmdOpts.Login = blockMeta.GetBool(wstore.MetaKey_CmdLogin, false) - cmdEnv := blockMeta.GetMap(wstore.MetaKey_CmdEnv) + cmdOpts.Interactive = blockMeta.GetBool(waveobj.MetaKey_CmdInteractive, false) + cmdOpts.Login = blockMeta.GetBool(waveobj.MetaKey_CmdLogin, false) + cmdEnv := blockMeta.GetMap(waveobj.MetaKey_CmdEnv) for k, v := range cmdEnv { if v == nil { continue @@ -282,8 +282,8 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj if err != nil { return err } - if !blockMeta.GetBool(wstore.MetaKey_CmdNoWsh, false) { - jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, conn.SockName) + if !blockMeta.GetBool(waveobj.MetaKey_CmdNoWsh, false) { + jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId, Conn: conn.Opts.String()}, conn.SockName) if err != nil { return fmt.Errorf("error making jwt token: %w", err) } @@ -294,7 +294,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj return err } } else { - if !blockMeta.GetBool(wstore.MetaKey_CmdNoWsh, false) { + if !blockMeta.GetBool(waveobj.MetaKey_CmdNoWsh, false) { jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, wavebase.GetDomainSocketName()) if err != nil { return fmt.Errorf("error making jwt token: %w", err) @@ -404,18 +404,18 @@ func getBoolFromMeta(meta map[string]any, key string, def bool) bool { return def } -func getTermSize(bdata *wstore.Block) wstore.TermSize { +func getTermSize(bdata *waveobj.Block) waveobj.TermSize { if bdata.RuntimeOpts != nil { return bdata.RuntimeOpts.TermSize } else { - return wstore.TermSize{ + return waveobj.TermSize{ Rows: 25, Cols: 80, } } } -func (bc *BlockController) run(bdata *wstore.Block, blockMeta map[string]any) { +func (bc *BlockController) run(bdata *waveobj.Block, blockMeta map[string]any) { defer func() { bc.UpdateControllerAndSendUpdate(func() bool { if bc.Status == Status_Running { @@ -432,18 +432,18 @@ func (bc *BlockController) run(bdata *wstore.Block, blockMeta map[string]any) { bc.Status = Status_Running return true }) - controllerName := bdata.Meta.GetString(wstore.MetaKey_Controller, "") + controllerName := bdata.Meta.GetString(waveobj.MetaKey_Controller, "") if controllerName != BlockController_Shell && controllerName != BlockController_Cmd { log.Printf("unknown controller %q\n", controllerName) return } - if getBoolFromMeta(blockMeta, wstore.MetaKey_CmdClearOnStart, false) { + if getBoolFromMeta(blockMeta, waveobj.MetaKey_CmdClearOnStart, false) { err := HandleTruncateBlockFile(bc.BlockId, BlockFile_Term) if err != nil { log.Printf("error truncating term blockfile: %v\n", err) } } - runOnStart := getBoolFromMeta(blockMeta, wstore.MetaKey_CmdRunOnStart, true) + runOnStart := getBoolFromMeta(blockMeta, waveobj.MetaKey_CmdRunOnStart, true) if runOnStart { go func() { err := bc.DoRunShellCommand(&RunShellOpts{TermSize: getTermSize(bdata)}, bdata.Meta) @@ -466,7 +466,7 @@ func (bc *BlockController) SendInput(inputUnion *BlockInputUnion) error { func (bc *BlockController) RestartController() error { // TODO: if shell command is already running // we probably want to kill it off, wait, and then restart it - bdata, err := wstore.DBMustGet[*wstore.Block](context.Background(), bc.BlockId) + bdata, err := wstore.DBMustGet[*waveobj.Block](context.Background(), bc.BlockId) if err != nil { return fmt.Errorf("error getting block: %w", err) } @@ -479,11 +479,11 @@ func (bc *BlockController) RestartController() error { func StartBlockController(ctx context.Context, tabId string, blockId string) error { log.Printf("start blockcontroller %q\n", blockId) - blockData, err := wstore.DBMustGet[*wstore.Block](ctx, blockId) + blockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) if err != nil { return fmt.Errorf("error getting block: %w", err) } - controllerName := blockData.Meta.GetString(wstore.MetaKey_Controller, "") + controllerName := blockData.Meta.GetString(waveobj.MetaKey_Controller, "") if controllerName == "" { // nothing to start return nil diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go index b8f0169d..90b95b74 100644 --- a/pkg/remote/conncontroller/conncontroller.go +++ b/pkg/remote/conncontroller/conncontroller.go @@ -83,7 +83,8 @@ func (conn *SSHConn) StartConnServer() error { } wshPath := remote.GetWshPath(conn.Client) rpcCtx := wshrpc.RpcContext{ - Conn: conn.Opts.String(), + ClientType: wshrpc.ClientType_ConnServer, + Conn: conn.Opts.String(), } jwtToken, err := wshutil.MakeClientJWTToken(rpcCtx, conn.SockName) if err != nil { diff --git a/pkg/service/blockservice/blockservice.go b/pkg/service/blockservice/blockservice.go index 73b0d896..73acbeea 100644 --- a/pkg/service/blockservice/blockservice.go +++ b/pkg/service/blockservice/blockservice.go @@ -12,6 +12,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/blockcontroller" "github.com/wavetermdev/thenextwave/pkg/filestore" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wshrpc" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -41,7 +42,7 @@ func (bs *BlockService) GetControllerStatus(ctx context.Context, blockId string) } func (bs *BlockService) SaveTerminalState(ctx context.Context, blockId string, state string, stateType string, ptyOffset int64) error { - _, err := wstore.DBMustGet[*wstore.Block](ctx, blockId) + _, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) if err != nil { return err } @@ -62,11 +63,11 @@ func (bs *BlockService) SaveTerminalState(ctx context.Context, blockId string, s } func (bs *BlockService) SaveWaveAiData(ctx context.Context, blockId string, history []wshrpc.OpenAIPromptMessageType) error { - block, err := wstore.DBMustGet[*wstore.Block](ctx, blockId) + block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) if err != nil { return err } - viewName := block.Meta.GetString(wstore.MetaKey_View, "") + viewName := block.Meta.GetString(waveobj.MetaKey_View, "") if viewName != "waveai" { return fmt.Errorf("invalid view type: %s", viewName) } diff --git a/pkg/service/clientservice/clientservice.go b/pkg/service/clientservice/clientservice.go index 92a62b91..ffefb224 100644 --- a/pkg/service/clientservice/clientservice.go +++ b/pkg/service/clientservice/clientservice.go @@ -12,6 +12,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/eventbus" "github.com/wavetermdev/thenextwave/pkg/service/objectservice" "github.com/wavetermdev/thenextwave/pkg/util/utilfn" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -19,47 +20,47 @@ type ClientService struct{} const DefaultTimeout = 2 * time.Second -func (cs *ClientService) GetClientData() (*wstore.Client, error) { +func (cs *ClientService) GetClientData() (*waveobj.Client, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - clientData, err := wstore.DBGetSingleton[*wstore.Client](ctx) + clientData, err := wstore.DBGetSingleton[*waveobj.Client](ctx) if err != nil { return nil, fmt.Errorf("error getting client data: %w", err) } return clientData, nil } -func (cs *ClientService) GetWorkspace(workspaceId string) (*wstore.Workspace, error) { +func (cs *ClientService) GetWorkspace(workspaceId string) (*waveobj.Workspace, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ws, err := wstore.DBGet[*wstore.Workspace](ctx, workspaceId) + ws, err := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId) if err != nil { return nil, fmt.Errorf("error getting workspace: %w", err) } return ws, nil } -func (cs *ClientService) GetTab(tabId string) (*wstore.Tab, error) { +func (cs *ClientService) GetTab(tabId string) (*waveobj.Tab, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - tab, err := wstore.DBGet[*wstore.Tab](ctx, tabId) + tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId) if err != nil { return nil, fmt.Errorf("error getting tab: %w", err) } return tab, nil } -func (cs *ClientService) GetWindow(windowId string) (*wstore.Window, error) { +func (cs *ClientService) GetWindow(windowId string) (*waveobj.Window, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - window, err := wstore.DBGet[*wstore.Window](ctx, windowId) + window, err := wstore.DBGet[*waveobj.Window](ctx, windowId) if err != nil { return nil, fmt.Errorf("error getting window: %w", err) } return window, nil } -func (cs *ClientService) MakeWindow(ctx context.Context) (*wstore.Window, error) { +func (cs *ClientService) MakeWindow(ctx context.Context) (*waveobj.Window, error) { return wstore.CreateWindow(ctx, nil) } @@ -77,9 +78,9 @@ func (cs *ClientService) FocusWindow(ctx context.Context, windowId string) error return wstore.DBUpdate(ctx, client) } -func (cs *ClientService) AgreeTos(ctx context.Context) (wstore.UpdatesRtnType, error) { - ctx = wstore.ContextWithUpdates(ctx) - clientData, err := wstore.DBGetSingleton[*wstore.Client](ctx) +func (cs *ClientService) AgreeTos(ctx context.Context) (waveobj.UpdatesRtnType, error) { + ctx = waveobj.ContextWithUpdates(ctx) + clientData, err := wstore.DBGetSingleton[*waveobj.Client](ctx) if err != nil { return nil, fmt.Errorf("error getting client data: %w", err) } @@ -90,19 +91,19 @@ func (cs *ClientService) AgreeTos(ctx context.Context) (wstore.UpdatesRtnType, e return nil, fmt.Errorf("error updating client data: %w", err) } cs.BootstrapStarterLayout(ctx) - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } type PortableLayout []struct { IndexArr []int Size uint - BlockDef *wstore.BlockDef + BlockDef *waveobj.BlockDef } func (cs *ClientService) BootstrapStarterLayout(ctx context.Context) error { ctx, cancelFn := context.WithTimeout(ctx, 2*time.Second) defer cancelFn() - client, err := wstore.DBGetSingleton[*wstore.Client](ctx) + client, err := wstore.DBGetSingleton[*waveobj.Client](ctx) if err != nil { log.Printf("unable to find client: %v\n", err) return fmt.Errorf("unable to find client: %w", err) @@ -114,7 +115,7 @@ func (cs *ClientService) BootstrapStarterLayout(ctx context.Context) error { windowId := client.WindowIds[0] - window, err := wstore.DBMustGet[*wstore.Window](ctx, windowId) + window, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId) if err != nil { return fmt.Errorf("error getting window: %w", err) } @@ -122,43 +123,43 @@ func (cs *ClientService) BootstrapStarterLayout(ctx context.Context) error { tabId := window.ActiveTabId starterLayout := PortableLayout{ - {IndexArr: []int{0}, BlockDef: &wstore.BlockDef{ - Meta: wstore.MetaMapType{ - wstore.MetaKey_View: "term", - wstore.MetaKey_Controller: "shell", + {IndexArr: []int{0}, BlockDef: &waveobj.BlockDef{ + Meta: waveobj.MetaMapType{ + waveobj.MetaKey_View: "term", + waveobj.MetaKey_Controller: "shell", }, }}, - {IndexArr: []int{1}, BlockDef: &wstore.BlockDef{ - Meta: wstore.MetaMapType{ - wstore.MetaKey_View: "cpuplot", + {IndexArr: []int{1}, BlockDef: &waveobj.BlockDef{ + Meta: waveobj.MetaMapType{ + waveobj.MetaKey_View: "cpuplot", }, }}, - {IndexArr: []int{1, 1}, BlockDef: &wstore.BlockDef{ - Meta: wstore.MetaMapType{ - wstore.MetaKey_View: "web", - wstore.MetaKey_Url: "https://github.com/wavetermdev/waveterm", + {IndexArr: []int{1, 1}, BlockDef: &waveobj.BlockDef{ + Meta: waveobj.MetaMapType{ + waveobj.MetaKey_View: "web", + waveobj.MetaKey_Url: "https://github.com/wavetermdev/waveterm", }, }}, - {IndexArr: []int{1, 2}, BlockDef: &wstore.BlockDef{ - Meta: wstore.MetaMapType{ - wstore.MetaKey_View: "preview", - wstore.MetaKey_File: "~", + {IndexArr: []int{1, 2}, BlockDef: &waveobj.BlockDef{ + Meta: waveobj.MetaMapType{ + waveobj.MetaKey_View: "preview", + waveobj.MetaKey_File: "~", }, }}, - {IndexArr: []int{2}, BlockDef: &wstore.BlockDef{ - Meta: wstore.MetaMapType{ - wstore.MetaKey_View: "help", + {IndexArr: []int{2}, BlockDef: &waveobj.BlockDef{ + Meta: waveobj.MetaMapType{ + waveobj.MetaKey_View: "help", }, }}, - {IndexArr: []int{2, 1}, BlockDef: &wstore.BlockDef{ - Meta: wstore.MetaMapType{ - wstore.MetaKey_View: "waveai", + {IndexArr: []int{2, 1}, BlockDef: &waveobj.BlockDef{ + Meta: waveobj.MetaMapType{ + waveobj.MetaKey_View: "waveai", }, }}, // {IndexArr: []int{2, 2}, BlockDef: &wstore.BlockDef{ // Meta: wstore.MetaMapType{ - // wstore.MetaKey_View: "web", - // wstore.MetaKey_Url: "https://www.youtube.com/embed/cKqsw_sAsU8", + // waveobj.MetaKey_View: "web", + // waveobj.MetaKey_Url: "https://www.youtube.com/embed/cKqsw_sAsU8", // }, // }}, } @@ -168,7 +169,7 @@ func (cs *ClientService) BootstrapStarterLayout(ctx context.Context) error { for i := 0; i < len(starterLayout); i++ { layoutAction := starterLayout[i] - blockData, err := objsvc.CreateBlock_NoUI(ctx, tabId, layoutAction.BlockDef, &wstore.RuntimeOpts{}) + blockData, err := objsvc.CreateBlock_NoUI(ctx, tabId, layoutAction.BlockDef, &waveobj.RuntimeOpts{}) if err != nil { return fmt.Errorf("unable to create block for starter layout: %w", err) diff --git a/pkg/service/objectservice/objectservice.go b/pkg/service/objectservice/objectservice.go index ef55f53e..03e91acf 100644 --- a/pkg/service/objectservice/objectservice.go +++ b/pkg/service/objectservice/objectservice.go @@ -13,6 +13,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/blockcontroller" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/waveobj" + "github.com/wavetermdev/thenextwave/pkg/wcore" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -78,11 +79,11 @@ func (svc *ObjectService) AddTabToWorkspace_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) AddTabToWorkspace(uiContext wstore.UIContext, tabName string, activateTab bool) (string, wstore.UpdatesRtnType, error) { +func (svc *ObjectService) AddTabToWorkspace(uiContext waveobj.UIContext, tabName string, activateTab bool) (string, waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) - windowData, err := wstore.DBMustGet[*wstore.Window](ctx, uiContext.WindowId) + ctx = waveobj.ContextWithUpdates(ctx) + windowData, err := wstore.DBMustGet[*waveobj.Window](ctx, uiContext.WindowId) if err != nil { return "", nil, fmt.Errorf("error getting window: %w", err) } @@ -96,7 +97,7 @@ func (svc *ObjectService) AddTabToWorkspace(uiContext wstore.UIContext, tabName return "", nil, fmt.Errorf("error setting active tab: %w", err) } } - return tab.OID, wstore.ContextGetUpdatesRtn(ctx), nil + return tab.OID, waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *ObjectService) UpdateWorkspaceTabIds_Meta() tsgenmeta.MethodMeta { @@ -105,15 +106,15 @@ func (svc *ObjectService) UpdateWorkspaceTabIds_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) UpdateWorkspaceTabIds(uiContext wstore.UIContext, workspaceId string, tabIds []string) (wstore.UpdatesRtnType, error) { +func (svc *ObjectService) UpdateWorkspaceTabIds(uiContext waveobj.UIContext, workspaceId string, tabIds []string) (waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) err := wstore.UpdateWorkspaceTabIds(ctx, workspaceId, tabIds) if err != nil { return nil, fmt.Errorf("error updating workspace tab ids: %w", err) } - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *ObjectService) SetActiveTab_Meta() tsgenmeta.MethodMeta { @@ -122,16 +123,16 @@ func (svc *ObjectService) SetActiveTab_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string) (wstore.UpdatesRtnType, error) { +func (svc *ObjectService) SetActiveTab(uiContext waveobj.UIContext, tabId string) (waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) err := wstore.SetActiveTab(ctx, uiContext.WindowId, tabId) if err != nil { return nil, fmt.Errorf("error setting active tab: %w", err) } // check all blocks in tab and start controllers (if necessary) - tab, err := wstore.DBMustGet[*wstore.Tab](ctx, tabId) + tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId) if err != nil { return nil, fmt.Errorf("error getting tab: %w", err) } @@ -148,9 +149,9 @@ func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string) if err != nil { return nil, fmt.Errorf("error getting tab blocks: %w", err) } - updates := wstore.ContextGetUpdatesRtn(ctx) - updates = append(updates, wstore.MakeUpdate(tab)) - updates = append(updates, wstore.MakeUpdates(blocks)...) + updates := waveobj.ContextGetUpdatesRtn(ctx) + updates = append(updates, waveobj.MakeUpdate(tab)) + updates = append(updates, waveobj.MakeUpdates(blocks)...) return updates, nil } @@ -160,15 +161,15 @@ func (svc *ObjectService) UpdateTabName_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) UpdateTabName(uiContext wstore.UIContext, tabId, name string) (wstore.UpdatesRtnType, error) { +func (svc *ObjectService) UpdateTabName(uiContext waveobj.UIContext, tabId, name string) (waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) err := wstore.UpdateTabName(ctx, tabId, name) if err != nil { return nil, fmt.Errorf("error updating tab name: %w", err) } - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *ObjectService) CreateBlock_Meta() tsgenmeta.MethodMeta { @@ -178,12 +179,12 @@ func (svc *ObjectService) CreateBlock_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) CreateBlock_NoUI(ctx context.Context, tabId string, blockDef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (*wstore.Block, error) { +func (svc *ObjectService) CreateBlock_NoUI(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { blockData, err := wstore.CreateBlock(ctx, tabId, blockDef, rtOpts) if err != nil { return nil, fmt.Errorf("error creating block: %w", err) } - controllerName := blockData.Meta.GetString(wstore.MetaKey_Controller, "") + controllerName := blockData.Meta.GetString(waveobj.MetaKey_Controller, "") if controllerName != "" { err = blockcontroller.StartBlockController(ctx, tabId, blockData.OID) if err != nil { @@ -194,20 +195,20 @@ func (svc *ObjectService) CreateBlock_NoUI(ctx context.Context, tabId string, bl return blockData, nil } -func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (string, wstore.UpdatesRtnType, error) { +func (svc *ObjectService) CreateBlock(uiContext waveobj.UIContext, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (string, waveobj.UpdatesRtnType, error) { if uiContext.ActiveTabId == "" { return "", nil, fmt.Errorf("no active tab") } ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) blockData, err := svc.CreateBlock_NoUI(ctx, uiContext.ActiveTabId, blockDef, rtOpts) if err != nil { return "", nil, err } - return blockData.OID, wstore.ContextGetUpdatesRtn(ctx), nil + return blockData.OID, waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *ObjectService) DeleteBlock_Meta() tsgenmeta.MethodMeta { @@ -216,16 +217,15 @@ func (svc *ObjectService) DeleteBlock_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string) (wstore.UpdatesRtnType, error) { +func (svc *ObjectService) DeleteBlock(uiContext waveobj.UIContext, blockId string) (waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) - err := wstore.DeleteBlock(ctx, uiContext.ActiveTabId, blockId) + ctx = waveobj.ContextWithUpdates(ctx) + err := wcore.DeleteBlock(ctx, uiContext.ActiveTabId, blockId) if err != nil { return nil, fmt.Errorf("error deleting block: %w", err) } - blockcontroller.StopBlockController(blockId) - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *ObjectService) CloseTab_Meta() tsgenmeta.MethodMeta { @@ -240,10 +240,10 @@ func (svc *ObjectService) UpdateObjectMeta_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr string, meta wstore.MetaMapType) (wstore.UpdatesRtnType, error) { +func (svc *ObjectService) UpdateObjectMeta(uiContext waveobj.UIContext, orefStr string, meta waveobj.MetaMapType) (waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) oref, err := parseORef(orefStr) if err != nil { return nil, fmt.Errorf("error parsing object reference: %w", err) @@ -252,7 +252,7 @@ func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr s if err != nil { return nil, fmt.Errorf("error updateing %q meta: %w", orefStr, err) } - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *ObjectService) UpdateObject_Meta() tsgenmeta.MethodMeta { @@ -261,10 +261,10 @@ func (svc *ObjectService) UpdateObject_Meta() tsgenmeta.MethodMeta { } } -func (svc *ObjectService) UpdateObject(uiContext wstore.UIContext, waveObj waveobj.WaveObj, returnUpdates bool) (wstore.UpdatesRtnType, error) { +func (svc *ObjectService) UpdateObject(uiContext waveobj.UIContext, waveObj waveobj.WaveObj, returnUpdates bool) (waveobj.UpdatesRtnType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) if waveObj == nil { return nil, fmt.Errorf("update wavobj is nil") } @@ -281,7 +281,7 @@ func (svc *ObjectService) UpdateObject(uiContext wstore.UIContext, waveObj waveo return nil, fmt.Errorf("error updating object: %w", err) } if returnUpdates { - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } return nil, nil } diff --git a/pkg/service/service.go b/pkg/service/service.go index 566faa30..d06f83e4 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -19,7 +19,6 @@ import ( "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/web/webcmd" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) var ServiceMap = map[string]any{ @@ -33,28 +32,28 @@ var ServiceMap = map[string]any{ var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem() var errorRType = reflect.TypeOf((*error)(nil)).Elem() -var updatesRType = reflect.TypeOf(([]wstore.WaveObjUpdate{})) +var updatesRType = reflect.TypeOf(([]waveobj.WaveObjUpdate{})) var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() var waveObjSliceRType = reflect.TypeOf([]waveobj.WaveObj{}) var waveObjMapRType = reflect.TypeOf(map[string]waveobj.WaveObj{}) var methodMetaRType = reflect.TypeOf(tsgenmeta.MethodMeta{}) -var waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{}) -var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem() +var waveObjUpdateRType = reflect.TypeOf(waveobj.WaveObjUpdate{}) +var uiContextRType = reflect.TypeOf((*waveobj.UIContext)(nil)).Elem() var wsCommandRType = reflect.TypeOf((*webcmd.WSCommandType)(nil)).Elem() var orefRType = reflect.TypeOf((*waveobj.ORef)(nil)).Elem() type WebCallType struct { - Service string `json:"service"` - Method string `json:"method"` - UIContext *wstore.UIContext `json:"uicontext,omitempty"` - Args []any `json:"args"` + Service string `json:"service"` + Method string `json:"method"` + UIContext *waveobj.UIContext `json:"uicontext,omitempty"` + Args []any `json:"args"` } type WebReturnType struct { - Success bool `json:"success,omitempty"` - Error string `json:"error,omitempty"` - Data any `json:"data,omitempty"` - Updates []wstore.WaveObjUpdate `json:"updates,omitempty"` + Success bool `json:"success,omitempty"` + Error string `json:"error,omitempty"` + Data any `json:"data,omitempty"` + Updates []waveobj.WaveObjUpdate `json:"updates,omitempty"` } func convertNumber(argType reflect.Type, jsonArg float64) (any, error) { @@ -289,7 +288,7 @@ func convertReturnValues(rtnVals []reflect.Value) *WebReturnType { } if valType == updatesRType { // has a special MarshalJSON method - rtn.Updates = val.Interface().([]wstore.WaveObjUpdate) + rtn.Updates = val.Interface().([]waveobj.WaveObjUpdate) continue } if isSpecialWaveArgType(valType) { diff --git a/pkg/service/windowservice/windowservice.go b/pkg/service/windowservice/windowservice.go index b673f71c..ea330e5b 100644 --- a/pkg/service/windowservice/windowservice.go +++ b/pkg/service/windowservice/windowservice.go @@ -13,6 +13,8 @@ import ( "github.com/wavetermdev/thenextwave/pkg/eventbus" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/util/utilfn" + "github.com/wavetermdev/thenextwave/pkg/waveobj" + "github.com/wavetermdev/thenextwave/pkg/wcore" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -20,12 +22,12 @@ const DefaultTimeout = 2 * time.Second type WindowService struct{} -func (ws *WindowService) SetWindowPosAndSize(ctx context.Context, windowId string, pos *wstore.Point, size *wstore.WinSize) (wstore.UpdatesRtnType, error) { +func (ws *WindowService) SetWindowPosAndSize(ctx context.Context, windowId string, pos *waveobj.Point, size *waveobj.WinSize) (waveobj.UpdatesRtnType, error) { if pos == nil && size == nil { return nil, nil } - ctx = wstore.ContextWithUpdates(ctx) - win, err := wstore.DBMustGet[*wstore.Window](ctx, windowId) + ctx = waveobj.ContextWithUpdates(ctx) + win, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId) if err != nil { return nil, err } @@ -39,20 +41,20 @@ func (ws *WindowService) SetWindowPosAndSize(ctx context.Context, windowId strin if err != nil { return nil, err } - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } -func (svc *WindowService) CloseTab(ctx context.Context, uiContext wstore.UIContext, tabId string) (wstore.UpdatesRtnType, error) { - ctx = wstore.ContextWithUpdates(ctx) - window, err := wstore.DBMustGet[*wstore.Window](ctx, uiContext.WindowId) +func (svc *WindowService) CloseTab(ctx context.Context, uiContext waveobj.UIContext, tabId string) (waveobj.UpdatesRtnType, error) { + ctx = waveobj.ContextWithUpdates(ctx) + window, err := wstore.DBMustGet[*waveobj.Window](ctx, uiContext.WindowId) if err != nil { return nil, fmt.Errorf("error getting window: %w", err) } - tab, err := wstore.DBMustGet[*wstore.Tab](ctx, tabId) + tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId) if err != nil { return nil, fmt.Errorf("error getting tab: %w", err) } - ws, err := wstore.DBMustGet[*wstore.Workspace](ctx, window.WorkspaceId) + ws, err := wstore.DBMustGet[*waveobj.Workspace](ctx, window.WorkspaceId) if err != nil { return nil, fmt.Errorf("error getting workspace: %w", err) } @@ -66,7 +68,7 @@ func (svc *WindowService) CloseTab(ctx context.Context, uiContext wstore.UIConte for _, blockId := range tab.BlockIds { blockcontroller.StopBlockController(blockId) } - if err := wstore.DeleteTab(ctx, window.WorkspaceId, tabId); err != nil { + if err := wcore.DeleteTab(ctx, window.WorkspaceId, tabId); err != nil { return nil, fmt.Errorf("error closing tab: %w", err) } if window.ActiveTabId == tabId && tabIndex != -1 { @@ -85,7 +87,7 @@ func (svc *WindowService) CloseTab(ctx context.Context, uiContext wstore.UIConte } } } - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *WindowService) MoveBlockToNewWindow_Meta() tsgenmeta.MethodMeta { @@ -95,14 +97,14 @@ func (svc *WindowService) MoveBlockToNewWindow_Meta() tsgenmeta.MethodMeta { } } -func (svc *WindowService) MoveBlockToNewWindow(ctx context.Context, currentTabId string, blockId string) (wstore.UpdatesRtnType, error) { +func (svc *WindowService) MoveBlockToNewWindow(ctx context.Context, currentTabId string, blockId string) (waveobj.UpdatesRtnType, error) { log.Printf("MoveBlockToNewWindow(%s, %s)", currentTabId, blockId) - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) curWindowId, err := wstore.DBFindWindowForTabId(ctx, currentTabId) if err != nil { return nil, fmt.Errorf("error finding window for current-tab: %w", err) } - tab, err := wstore.DBMustGet[*wstore.Tab](ctx, currentTabId) + tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, currentTabId) if err != nil { return nil, fmt.Errorf("error getting tab: %w", err) } @@ -149,35 +151,35 @@ func (svc *WindowService) MoveBlockToNewWindow(ctx context.Context, currentTabId BlockId: blockId, }, }) - return wstore.ContextGetUpdatesRtn(ctx), nil + return waveobj.ContextGetUpdatesRtn(ctx), nil } func (svc *WindowService) CloseWindow(ctx context.Context, windowId string) error { - ctx = wstore.ContextWithUpdates(ctx) - window, err := wstore.DBMustGet[*wstore.Window](ctx, windowId) + ctx = waveobj.ContextWithUpdates(ctx) + window, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId) if err != nil { return fmt.Errorf("error getting window: %w", err) } - workspace, err := wstore.DBMustGet[*wstore.Workspace](ctx, window.WorkspaceId) + workspace, err := wstore.DBMustGet[*waveobj.Workspace](ctx, window.WorkspaceId) if err != nil { return fmt.Errorf("error getting workspace: %w", err) } for _, tabId := range workspace.TabIds { - uiContext := wstore.UIContext{WindowId: windowId} + uiContext := waveobj.UIContext{WindowId: windowId} _, err := svc.CloseTab(ctx, uiContext, tabId) if err != nil { return fmt.Errorf("error closing tab: %w", err) } } - err = wstore.DBDelete(ctx, wstore.OType_Workspace, window.WorkspaceId) + err = wstore.DBDelete(ctx, waveobj.OType_Workspace, window.WorkspaceId) if err != nil { return fmt.Errorf("error deleting workspace: %w", err) } - err = wstore.DBDelete(ctx, wstore.OType_Window, windowId) + err = wstore.DBDelete(ctx, waveobj.OType_Window, windowId) if err != nil { return fmt.Errorf("error deleting window: %w", err) } - client, err := wstore.DBGetSingleton[*wstore.Client](ctx) + client, err := wstore.DBGetSingleton[*waveobj.Client](ctx) if err != nil { return fmt.Errorf("error getting client: %w", err) } diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go index 701830ec..2936fd91 100644 --- a/pkg/shellexec/shellexec.go +++ b/pkg/shellexec/shellexec.go @@ -22,8 +22,8 @@ import ( "github.com/wavetermdev/thenextwave/pkg/remote" "github.com/wavetermdev/thenextwave/pkg/util/shellutil" "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wshutil" - "github.com/wavetermdev/thenextwave/pkg/wstore" "golang.org/x/crypto/ssh" ) @@ -150,7 +150,7 @@ func (pp *PipePty) WriteString(s string) (n int, err error) { return pp.Write([]byte(s)) } -func StartRemoteShellProc(termSize wstore.TermSize, cmdStr string, cmdOpts CommandOptsType, client *ssh.Client) (*ShellProc, error) { +func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, client *ssh.Client) (*ShellProc, error) { shellPath, err := remote.DetectShell(client) if err != nil { return nil, err @@ -266,7 +266,7 @@ func isBashShell(shellPath string) bool { return strings.Contains(shellBase, "bash") } -func StartShellProc(termSize wstore.TermSize, cmdStr string, cmdOpts CommandOptsType) (*ShellProc, error) { +func StartShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType) (*ShellProc, error) { shellutil.InitCustomShellStartupFiles() var ecmd *exec.Cmd var shellOpts []string @@ -328,7 +328,7 @@ func StartShellProc(termSize wstore.TermSize, cmdStr string, cmdOpts CommandOpts return &ShellProc{Cmd: CmdWrap{ecmd, cmdPty}, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil } -func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize wstore.TermSize) ([]byte, error) { +func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize waveobj.TermSize) ([]byte, error) { ecmd.Env = os.Environ() shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellLocalEnvVars(shellutil.DefaultTermType)) if termSize.Rows == 0 || termSize.Cols == 0 { diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go index 3a0efe88..9f7fa88f 100644 --- a/pkg/tsgen/tsgen.go +++ b/pkg/tsgen/tsgen.go @@ -21,7 +21,6 @@ import ( "github.com/wavetermdev/thenextwave/pkg/web/webcmd" "github.com/wavetermdev/thenextwave/pkg/wshrpc" "github.com/wavetermdev/thenextwave/pkg/wshutil" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) // add extra types to generate here @@ -31,7 +30,7 @@ var ExtraTypes = []any{ map[string]any{}, service.WebCallType{}, service.WebReturnType{}, - wstore.UIContext{}, + waveobj.UIContext{}, eventbus.WSEventType{}, eventbus.WSFileEventData{}, eventbus.WSLayoutActionData{}, @@ -45,7 +44,7 @@ var ExtraTypes = []any{ vdom.Elem{}, vdom.VDomFuncType{}, vdom.VDomRefType{}, - wstore.MetaTSType{}, + waveobj.MetaTSType{}, } // add extra type unions to generate here @@ -56,10 +55,10 @@ var TypeUnions = []tsgenmeta.TypeUnionMeta{ var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem() var errorRType = reflect.TypeOf((*error)(nil)).Elem() var anyRType = reflect.TypeOf((*interface{})(nil)).Elem() -var metaRType = reflect.TypeOf((*wstore.MetaMapType)(nil)).Elem() -var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem() +var metaRType = reflect.TypeOf((*waveobj.MetaMapType)(nil)).Elem() +var uiContextRType = reflect.TypeOf((*waveobj.UIContext)(nil)).Elem() var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() -var updatesRtnRType = reflect.TypeOf(wstore.UpdatesRtnType{}) +var updatesRtnRType = reflect.TypeOf(waveobj.UpdatesRtnType{}) var orefRType = reflect.TypeOf((*waveobj.ORef)(nil)).Elem() var wshRpcInterfaceRType = reflect.TypeOf((*wshrpc.WshRpcInterface)(nil)).Elem() @@ -471,7 +470,7 @@ func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) { for _, extraType := range ExtraTypes { GenerateTSType(reflect.TypeOf(extraType), tsTypesMap) } - for _, rtype := range wstore.AllWaveObjTypes() { + for _, rtype := range waveobj.AllWaveObjTypes() { GenerateTSType(rtype, tsTypesMap) } } diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index bf5a48f1..035483bd 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -19,7 +19,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/wavebase" - "github.com/wavetermdev/thenextwave/pkg/wstore" + "github.com/wavetermdev/thenextwave/pkg/waveobj" ) const DefaultTermType = "xterm-256color" @@ -126,8 +126,8 @@ func internalMacUserShell() string { return m[1] } -func DefaultTermSize() wstore.TermSize { - return wstore.TermSize{Rows: DefaultTermRows, Cols: DefaultTermCols} +func DefaultTermSize() waveobj.TermSize { + return waveobj.TermSize{Rows: DefaultTermRows, Cols: DefaultTermCols} } func WaveshellLocalEnvVars(termType string) map[string]string { diff --git a/pkg/waveobj/ctxupdate.go b/pkg/waveobj/ctxupdate.go new file mode 100644 index 00000000..7bce8102 --- /dev/null +++ b/pkg/waveobj/ctxupdate.go @@ -0,0 +1,137 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package waveobj + +import ( + "bytes" + "context" + "fmt" + "log" +) + +var waveObjUpdateKey = struct{}{} + +type contextUpdatesType struct { + UpdatesStack []map[ORef]WaveObjUpdate +} + +func dumpUpdateStack(updates *contextUpdatesType) { + log.Printf("dumpUpdateStack len:%d\n", len(updates.UpdatesStack)) + for idx, update := range updates.UpdatesStack { + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf(" [%d]:", idx)) + for k := range update { + buf.WriteString(fmt.Sprintf(" %s:%s", k.OType, k.OID)) + } + buf.WriteString("\n") + log.Print(buf.String()) + } +} + +func ContextWithUpdates(ctx context.Context) context.Context { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal != nil { + return ctx + } + return context.WithValue(ctx, waveObjUpdateKey, &contextUpdatesType{ + UpdatesStack: []map[ORef]WaveObjUpdate{make(map[ORef]WaveObjUpdate)}, + }) +} + +func ContextGetUpdates(ctx context.Context) map[ORef]WaveObjUpdate { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal == nil { + return nil + } + updates := updatesVal.(*contextUpdatesType) + if len(updates.UpdatesStack) == 1 { + return updates.UpdatesStack[0] + } + rtn := make(map[ORef]WaveObjUpdate) + for _, update := range updates.UpdatesStack { + for k, v := range update { + rtn[k] = v + } + } + return rtn +} + +func ContextGetUpdate(ctx context.Context, oref ORef) *WaveObjUpdate { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal == nil { + return nil + } + updates := updatesVal.(*contextUpdatesType) + for idx := len(updates.UpdatesStack) - 1; idx >= 0; idx-- { + if obj, ok := updates.UpdatesStack[idx][oref]; ok { + return &obj + } + } + return nil +} + +func ContextAddUpdate(ctx context.Context, update WaveObjUpdate) { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal == nil { + return + } + updates := updatesVal.(*contextUpdatesType) + oref := ORef{ + OType: update.OType, + OID: update.OID, + } + updates.UpdatesStack[len(updates.UpdatesStack)-1][oref] = update +} + +func ContextUpdatesBeginTx(ctx context.Context) context.Context { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal == nil { + return ctx + } + updates := updatesVal.(*contextUpdatesType) + updates.UpdatesStack = append(updates.UpdatesStack, make(map[ORef]WaveObjUpdate)) + return ctx +} + +func ContextUpdatesCommitTx(ctx context.Context) { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal == nil { + return + } + updates := updatesVal.(*contextUpdatesType) + if len(updates.UpdatesStack) <= 1 { + panic(fmt.Errorf("no updates transaction to commit")) + } + // merge the last two updates + curUpdateMap := updates.UpdatesStack[len(updates.UpdatesStack)-1] + prevUpdateMap := updates.UpdatesStack[len(updates.UpdatesStack)-2] + for k, v := range curUpdateMap { + prevUpdateMap[k] = v + } + updates.UpdatesStack = updates.UpdatesStack[:len(updates.UpdatesStack)-1] +} + +func ContextUpdatesRollbackTx(ctx context.Context) { + updatesVal := ctx.Value(waveObjUpdateKey) + if updatesVal == nil { + return + } + updates := updatesVal.(*contextUpdatesType) + if len(updates.UpdatesStack) <= 1 { + panic(fmt.Errorf("no updates transaction to rollback")) + } + updates.UpdatesStack = updates.UpdatesStack[:len(updates.UpdatesStack)-1] +} + +func ContextGetUpdatesRtn(ctx context.Context) UpdatesRtnType { + updatesMap := ContextGetUpdates(ctx) + if updatesMap == nil { + return nil + } + rtn := make(UpdatesRtnType, 0, len(updatesMap)) + for _, v := range updatesMap { + rtn = append(rtn, v) + } + return rtn +} diff --git a/pkg/wstore/wstore_types.go b/pkg/waveobj/wtype.go similarity index 90% rename from pkg/wstore/wstore_types.go rename to pkg/waveobj/wtype.go index 13952ad7..08de4dbd 100644 --- a/pkg/wstore/wstore_types.go +++ b/pkg/waveobj/wtype.go @@ -1,16 +1,16 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -package wstore +package waveobj import ( "encoding/json" "fmt" "reflect" - - "github.com/wavetermdev/thenextwave/pkg/waveobj" ) +type UpdatesRtnType = []WaveObjUpdate + type UIContext struct { WindowId string `json:"windowid"` ActiveTabId string `json:"activetabid"` @@ -31,10 +31,10 @@ const ( ) type WaveObjUpdate struct { - UpdateType string `json:"updatetype"` - OType string `json:"otype"` - OID string `json:"oid"` - Obj waveobj.WaveObj `json:"obj,omitempty"` + UpdateType string `json:"updatetype"` + OType string `json:"otype"` + OID string `json:"oid"` + Obj WaveObj `json:"obj,omitempty"` } func (update WaveObjUpdate) MarshalJSON() ([]byte, error) { @@ -44,7 +44,7 @@ func (update WaveObjUpdate) MarshalJSON() ([]byte, error) { rtn["oid"] = update.OID if update.Obj != nil { var err error - rtn["obj"], err = waveobj.ToJsonMap(update.Obj) + rtn["obj"], err = ToJsonMap(update.Obj) if err != nil { return nil, err } @@ -52,16 +52,16 @@ func (update WaveObjUpdate) MarshalJSON() ([]byte, error) { return json.Marshal(rtn) } -func MakeUpdate(obj waveobj.WaveObj) WaveObjUpdate { +func MakeUpdate(obj WaveObj) WaveObjUpdate { return WaveObjUpdate{ UpdateType: UpdateType_Update, OType: obj.GetOType(), - OID: waveobj.GetOID(obj), + OID: GetOID(obj), Obj: obj, } } -func MakeUpdates(objs []waveobj.WaveObj) []WaveObjUpdate { +func MakeUpdates(objs []WaveObj) []WaveObjUpdate { rtn := make([]WaveObjUpdate, 0, len(objs)) for _, obj := range objs { rtn = append(rtn, MakeUpdate(obj)) @@ -102,7 +102,7 @@ func (update *WaveObjUpdate) UnmarshalJSON(data []byte) error { if !ok { return fmt.Errorf("in WaveObjUpdate bad obj type %T", objMap["obj"]) } - waveObj, err := waveobj.FromJsonMap(objMap) + waveObj, err := FromJsonMap(objMap) if err != nil { return fmt.Errorf("in WaveObjUpdate error decoding obj: %w", err) } @@ -167,10 +167,10 @@ func (*Tab) GetOType() string { return OType_Tab } -func (t *Tab) GetBlockORefs() []waveobj.ORef { - rtn := make([]waveobj.ORef, 0, len(t.BlockIds)) +func (t *Tab) GetBlockORefs() []ORef { + rtn := make([]ORef, 0, len(t.BlockIds)) for _, blockId := range t.BlockIds { - rtn = append(rtn, waveobj.ORef{OType: OType_Block, OID: blockId}) + rtn = append(rtn, ORef{OType: OType_Block, OID: blockId}) } return rtn } diff --git a/pkg/wstore/wstore_meta.go b/pkg/waveobj/wtypemeta.go similarity index 98% rename from pkg/wstore/wstore_meta.go rename to pkg/waveobj/wtypemeta.go index 50418b4f..12c71615 100644 --- a/pkg/wstore/wstore_meta.go +++ b/pkg/waveobj/wtypemeta.go @@ -1,18 +1,14 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -package wstore +package waveobj import ( "strings" - - "github.com/wavetermdev/thenextwave/pkg/waveobj" ) const Entity_Any = "any" -type MetaMapType = waveobj.MetaMapType - // well known meta keys // to add a new key, add it here and add it to MetaTSType (make sure the keys match) // TODO: will code generate one side of this so we don't need to add the keys in two places diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 70c6e257..dbd37221 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -8,7 +8,6 @@ import ( "path/filepath" "github.com/wavetermdev/thenextwave/pkg/waveobj" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) const termThemesDir = "terminal-themes" @@ -17,11 +16,11 @@ const settingsFile = "settings.json" var settingsAbsPath = filepath.Join(configDirAbsPath, settingsFile) type WidgetsConfigType struct { - Icon string `json:"icon"` - Color string `json:"color,omitempty"` - Label string `json:"label,omitempty"` - Description string `json:"description,omitempty"` - BlockDef wstore.BlockDef `json:"blockdef"` + Icon string `json:"icon"` + Color string `json:"color,omitempty"` + Label string `json:"label,omitempty"` + Description string `json:"description,omitempty"` + BlockDef waveobj.BlockDef `json:"blockdef"` } type TerminalConfigType struct { @@ -192,38 +191,38 @@ var CampbellTheme = TermThemeType{ } var BgDefaultPreset = waveobj.MetaMapType{ - wstore.MetaKey_DisplayName: "Default", - wstore.MetaKey_DisplayOrder: -1, - wstore.MetaKey_BgClear: true, + waveobj.MetaKey_DisplayName: "Default", + waveobj.MetaKey_DisplayOrder: -1, + waveobj.MetaKey_BgClear: true, } var BgRainbowPreset = waveobj.MetaMapType{ - wstore.MetaKey_DisplayName: "Rainbow", - wstore.MetaKey_DisplayOrder: 1, - wstore.MetaKey_BgClear: true, - wstore.MetaKey_Bg: "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% );", - wstore.MetaKey_BgOpacity: 0.3, + waveobj.MetaKey_DisplayName: "Rainbow", + waveobj.MetaKey_DisplayOrder: 1, + waveobj.MetaKey_BgClear: true, + waveobj.MetaKey_Bg: "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% );", + waveobj.MetaKey_BgOpacity: 0.3, } var BgGreenPreset = waveobj.MetaMapType{ - wstore.MetaKey_DisplayName: "Green", - wstore.MetaKey_BgClear: true, - wstore.MetaKey_Bg: "green", - wstore.MetaKey_BgOpacity: 0.3, + waveobj.MetaKey_DisplayName: "Green", + waveobj.MetaKey_BgClear: true, + waveobj.MetaKey_Bg: "green", + waveobj.MetaKey_BgOpacity: 0.3, } var BgBluePreset = waveobj.MetaMapType{ - wstore.MetaKey_DisplayName: "Blue", - wstore.MetaKey_BgClear: true, - wstore.MetaKey_Bg: "blue", - wstore.MetaKey_BgOpacity: 0.3, + waveobj.MetaKey_DisplayName: "Blue", + waveobj.MetaKey_BgClear: true, + waveobj.MetaKey_Bg: "blue", + waveobj.MetaKey_BgOpacity: 0.3, } var BgRedPreset = waveobj.MetaMapType{ - wstore.MetaKey_DisplayName: "Red", - wstore.MetaKey_BgClear: true, - wstore.MetaKey_Bg: "red", - wstore.MetaKey_BgOpacity: 0.3, + waveobj.MetaKey_DisplayName: "Red", + waveobj.MetaKey_BgClear: true, + waveobj.MetaKey_Bg: "red", + waveobj.MetaKey_BgOpacity: 0.3, } func applyDefaultSettings(settings *SettingsConfigType) { @@ -282,48 +281,48 @@ func applyDefaultSettings(settings *SettingsConfigType) { { Icon: "square-terminal", Label: "terminal", - BlockDef: wstore.BlockDef{ + BlockDef: waveobj.BlockDef{ Meta: map[string]any{ - wstore.MetaKey_View: "term", - wstore.MetaKey_Controller: "shell", + waveobj.MetaKey_View: "term", + waveobj.MetaKey_Controller: "shell", }, }, }, { Icon: "folder", Label: "files", - BlockDef: wstore.BlockDef{ + BlockDef: waveobj.BlockDef{ Meta: map[string]any{ - wstore.MetaKey_View: "preview", - wstore.MetaKey_File: "~", + waveobj.MetaKey_View: "preview", + waveobj.MetaKey_File: "~", }, }, }, { Icon: "globe", Label: "web", - BlockDef: wstore.BlockDef{ + BlockDef: waveobj.BlockDef{ Meta: map[string]any{ - wstore.MetaKey_View: "web", - wstore.MetaKey_Url: "https://waveterm.dev/", + waveobj.MetaKey_View: "web", + waveobj.MetaKey_Url: "https://waveterm.dev/", }, }, }, { Icon: "sparkles", Label: "waveai", - BlockDef: wstore.BlockDef{ + BlockDef: waveobj.BlockDef{ Meta: map[string]any{ - wstore.MetaKey_View: "waveai", + waveobj.MetaKey_View: "waveai", }, }, }, { Icon: "chart-line", Label: "cpu", - BlockDef: wstore.BlockDef{ + BlockDef: waveobj.BlockDef{ Meta: map[string]any{ - wstore.MetaKey_View: "cpuplot", + waveobj.MetaKey_View: "cpuplot", }, }, }, diff --git a/pkg/wcore/wcore.go b/pkg/wcore/wcore.go new file mode 100644 index 00000000..e3e831e2 --- /dev/null +++ b/pkg/wcore/wcore.go @@ -0,0 +1,70 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// wave core application coordinator +package wcore + +import ( + "context" + "fmt" + "time" + + "github.com/wavetermdev/thenextwave/pkg/blockcontroller" + "github.com/wavetermdev/thenextwave/pkg/waveobj" + "github.com/wavetermdev/thenextwave/pkg/wps" + "github.com/wavetermdev/thenextwave/pkg/wshrpc" + "github.com/wavetermdev/thenextwave/pkg/wstore" +) + +// the wcore package coordinates actions across the storage layer +// orchestrating the wave object store, the wave pubsub system, and the wave rpc system + +// TODO bring Tx infra into wcore + +const DefaultTimeout = 2 * time.Second + +func DeleteBlock(ctx context.Context, tabId string, blockId string) error { + err := wstore.DeleteBlock(ctx, tabId, blockId) + if err != nil { + return fmt.Errorf("error deleting block: %w", err) + } + blockcontroller.StopBlockController(blockId) + sendBlockCloseEvent(tabId, blockId) + return nil +} + +func sendBlockCloseEvent(tabId string, blockId string) { + waveEvent := wshrpc.WaveEvent{ + Event: wshrpc.Event_BlockClose, + Scopes: []string{ + waveobj.MakeORef(waveobj.OType_Tab, tabId).String(), + waveobj.MakeORef(waveobj.OType_Block, blockId).String(), + }, + Data: blockId, + } + wps.Broker.Publish(waveEvent) +} + +func DeleteTab(ctx context.Context, workspaceId string, tabId string) error { + tabData, err := wstore.DBGet[*waveobj.Tab](ctx, tabId) + if err != nil { + return fmt.Errorf("error getting tab: %w", err) + } + if tabData == nil { + return nil + } + // close blocks (sends events + stops block controllers) + for _, blockId := range tabData.BlockIds { + err := DeleteBlock(ctx, tabId, blockId) + if err != nil { + return fmt.Errorf("error deleting block %s: %w", blockId, err) + } + } + // now delete tab (also deletes layout) + err = wstore.DeleteTab(ctx, workspaceId, tabId) + if err != nil { + return fmt.Errorf("error deleting tab: %w", err) + } + + return nil +} diff --git a/pkg/web/web.go b/pkg/web/web.go index ffffdaba..d2335c35 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -25,6 +25,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/service" "github.com/wavetermdev/thenextwave/pkg/telemetry" "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wshrpc" "github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient" "github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver" @@ -363,7 +364,7 @@ func handleLogActiveState(w http.ResponseWriter, r *http.Request) { if activeState.Open { activity.OpenMinutes = 1 } - activity.NumTabs, _ = wstore.DBGetCount[*wstore.Tab](r.Context()) + activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](r.Context()) err = telemetry.UpdateActivity(r.Context(), activity) if err != nil { WriteJsonError(w, fmt.Errorf("error updating activity: %w", err)) diff --git a/pkg/web/webcmd/webcmd.go b/pkg/web/webcmd/webcmd.go index d23d3f12..199c4bb1 100644 --- a/pkg/web/webcmd/webcmd.go +++ b/pkg/web/webcmd/webcmd.go @@ -9,8 +9,8 @@ import ( "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/util/utilfn" + "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wshutil" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) const ( @@ -45,9 +45,9 @@ func (cmd *WSRpcCommand) GetWSCommand() string { } type SetBlockTermSizeWSCommand struct { - WSCommand string `json:"wscommand" tstype:"\"setblocktermsize\""` - BlockId string `json:"blockid"` - TermSize wstore.TermSize `json:"termsize"` + WSCommand string `json:"wscommand" tstype:"\"setblocktermsize\""` + BlockId string `json:"blockid"` + TermSize waveobj.TermSize `json:"termsize"` } func (cmd *SetBlockTermSizeWSCommand) GetWSCommand() string { diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index cd9942de..7eea9d7c 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -18,9 +18,9 @@ func AnnounceCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error } // command "authenticate", wshserver.AuthenticateCommand -func AuthenticateCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error { - _, err := sendRpcRequestCallHelper[any](w, "authenticate", data, opts) - return err +func AuthenticateCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (wshrpc.CommandAuthenticateRtnData, error) { + resp, err := sendRpcRequestCallHelper[wshrpc.CommandAuthenticateRtnData](w, "authenticate", data, opts) + return resp, err } // command "controllerinput", wshserver.ControllerInputCommand diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 0ebd71f1..875fb0e2 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -11,8 +11,8 @@ import ( "reflect" "github.com/wavetermdev/thenextwave/pkg/ijson" + "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/waveobj" - "github.com/wavetermdev/thenextwave/pkg/wstore" ) const LocalConnName = "local" @@ -25,8 +25,12 @@ const ( ) const ( - Command_Authenticate = "authenticate" - Command_Announce = "announce" // special (for routing) + Event_BlockClose = "blockclose" +) + +const ( + Command_Authenticate = "authenticate" // special + Command_Announce = "announce" // special (for routing) Command_Message = "message" Command_GetMeta = "getmeta" Command_SetMeta = "setmeta" @@ -53,7 +57,6 @@ const ( Command_RemoteFileInfo = "remotefileinfo" Command_RemoteWriteFile = "remotewritefile" Command_RemoteFileDelete = "remotefiledelete" - Command_Event = "event" ) type RespOrErrorUnion[T any] struct { @@ -62,11 +65,11 @@ type RespOrErrorUnion[T any] struct { } type WshRpcInterface interface { - AuthenticateCommand(ctx context.Context, data string) error + AuthenticateCommand(ctx context.Context, data string) (CommandAuthenticateRtnData, error) AnnounceCommand(ctx context.Context, data string) error // (special) announces a new route to the main router MessageCommand(ctx context.Context, data CommandMessageData) error - GetMetaCommand(ctx context.Context, data CommandGetMetaData) (wstore.MetaMapType, error) + GetMetaCommand(ctx context.Context, data CommandGetMetaData) (waveobj.MetaMapType, error) SetMetaCommand(ctx context.Context, data CommandSetMetaData) error SetViewCommand(ctx context.Context, data CommandBlockSetViewData) error ControllerInputCommand(ctx context.Context, data CommandBlockInputData) error @@ -79,7 +82,6 @@ type WshRpcInterface interface { FileWriteCommand(ctx context.Context, data CommandFileData) error FileReadCommand(ctx context.Context, data CommandFileData) (string, error) EventPublishCommand(ctx context.Context, data WaveEvent) error - EventRecvCommand(ctx context.Context, data WaveEvent) error EventSubCommand(ctx context.Context, data SubscriptionRequest) error EventUnsubCommand(ctx context.Context, data SubscriptionRequest) error EventUnsubAllCommand(ctx context.Context) error @@ -88,6 +90,9 @@ type WshRpcInterface interface { StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData] TestCommand(ctx context.Context, data string) error + // eventrecv is special, it's handled internally by WshRpc with EventListener + EventRecvCommand(ctx context.Context, data WaveEvent) error + // remotes RemoteStreamFileCommand(ctx context.Context, data CommandRemoteStreamFileData) chan RespOrErrorUnion[CommandRemoteStreamFileRtnData] RemoteFileInfoCommand(ctx context.Context, path string) (*FileInfo, error) @@ -109,10 +114,16 @@ type RpcOpts struct { StreamCancelFn func() `json:"-"` // this is an *output* parameter, set by the handler } +const ( + ClientType_ConnServer = "connserver" + ClientType_BlockController = "blockcontroller" +) + type RpcContext struct { - BlockId string `json:"blockid,omitempty"` - TabId string `json:"tabid,omitempty"` - Conn string `json:"conn,omitempty"` + ClientType string `json:"ctype,omitempty"` + BlockId string `json:"blockid,omitempty"` + TabId string `json:"tabid,omitempty"` + Conn string `json:"conn,omitempty"` } func HackRpcContextIntoData(dataPtr any, rpcContext RpcContext) { @@ -138,7 +149,7 @@ func HackRpcContextIntoData(dataPtr any, rpcContext RpcContext) { field.SetString(rpcContext.TabId) case "BlockORef": if rpcContext.BlockId != "" { - field.Set(reflect.ValueOf(waveobj.MakeORef(wstore.OType_Block, rpcContext.BlockId))) + field.Set(reflect.ValueOf(waveobj.MakeORef(waveobj.OType_Block, rpcContext.BlockId))) } default: log.Printf("invalid wshcontext tag: %q in type(%T)", tag, dataPtr) @@ -146,6 +157,10 @@ func HackRpcContextIntoData(dataPtr any, rpcContext RpcContext) { } } +type CommandAuthenticateRtnData struct { + RouteId string `json:"routeid"` +} + type CommandMessageData struct { ORef waveobj.ORef `json:"oref" wshcontext:"BlockORef"` Message string `json:"message"` @@ -156,8 +171,8 @@ type CommandGetMetaData struct { } type CommandSetMetaData struct { - ORef waveobj.ORef `json:"oref" wshcontext:"BlockORef"` - Meta wstore.MetaMapType `json:"meta"` + ORef waveobj.ORef `json:"oref" wshcontext:"BlockORef"` + Meta waveobj.MetaMapType `json:"meta"` } type CommandResolveIdsData struct { @@ -170,9 +185,9 @@ type CommandResolveIdsRtnData struct { } type CommandCreateBlockData struct { - TabId string `json:"tabid" wshcontext:"TabId"` - BlockDef *wstore.BlockDef `json:"blockdef"` - RtOpts *wstore.RuntimeOpts `json:"rtopts"` + TabId string `json:"tabid" wshcontext:"TabId"` + BlockDef *waveobj.BlockDef `json:"blockdef"` + RtOpts *waveobj.RuntimeOpts `json:"rtopts"` } type CommandBlockSetViewData struct { @@ -185,10 +200,10 @@ type CommandBlockRestartData struct { } type CommandBlockInputData struct { - BlockId string `json:"blockid" wshcontext:"BlockId"` - InputData64 string `json:"inputdata64,omitempty"` - SigName string `json:"signame,omitempty"` - TermSize *wstore.TermSize `json:"termsize,omitempty"` + BlockId string `json:"blockid" wshcontext:"BlockId"` + InputData64 string `json:"inputdata64,omitempty"` + SigName string `json:"signame,omitempty"` + TermSize *waveobj.TermSize `json:"termsize,omitempty"` } type CommandFileData struct { @@ -214,6 +229,10 @@ type WaveEvent struct { Data any `json:"data,omitempty"` } +func (e WaveEvent) HasScope(scope string) bool { + return utilfn.ContainsStr(e.Scopes, scope) +} + type SubscriptionRequest struct { Event string `json:"event"` Scopes []string `json:"scopes,omitempty"` diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index b86af5df..498ea3e1 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -21,6 +21,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/filestore" "github.com/wavetermdev/thenextwave/pkg/waveai" "github.com/wavetermdev/thenextwave/pkg/waveobj" + "github.com/wavetermdev/thenextwave/pkg/wcore" "github.com/wavetermdev/thenextwave/pkg/wps" "github.com/wavetermdev/thenextwave/pkg/wshrpc" "github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient" @@ -127,12 +128,12 @@ func (ws *WshServer) StreamCpuDataCommand(ctx context.Context, request wshrpc.Cp rtn <- wshrpc.RespOrErrorUnion[wshrpc.TimeSeriesData]{Error: err} return } - blockData, getBlockDataErr := wstore.DBMustGet[*wstore.Block](ctx, request.Id) + blockData, getBlockDataErr := wstore.DBMustGet[*waveobj.Block](ctx, request.Id) if getBlockDataErr != nil { rtn <- wshrpc.RespOrErrorUnion[wshrpc.TimeSeriesData]{Error: getBlockDataErr} return } - count := blockData.Meta.GetInt(wstore.MetaKey_Count, 0) + count := blockData.Meta.GetInt(waveobj.MetaKey_Count, 0) if count != request.Count { rtn <- wshrpc.RespOrErrorUnion[wshrpc.TimeSeriesData]{Error: fmt.Errorf("new instance created. canceling old goroutine")} return @@ -145,11 +146,11 @@ func (ws *WshServer) StreamCpuDataCommand(ctx context.Context, request wshrpc.Cp } func MakePlotData(ctx context.Context, blockId string) error { - block, err := wstore.DBMustGet[*wstore.Block](ctx, blockId) + block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) if err != nil { return err } - viewName := block.Meta.GetString(wstore.MetaKey_View, "") + viewName := block.Meta.GetString(waveobj.MetaKey_View, "") if viewName != "cpuplot" { return fmt.Errorf("invalid view type: %s", viewName) } @@ -157,11 +158,11 @@ func MakePlotData(ctx context.Context, blockId string) error { } func SavePlotData(ctx context.Context, blockId string, history string) error { - block, err := wstore.DBMustGet[*wstore.Block](ctx, blockId) + block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) if err != nil { return err } - viewName := block.Meta.GetString(wstore.MetaKey_View, "") + viewName := block.Meta.GetString(waveobj.MetaKey_View, "") if viewName != "cpuplot" { return fmt.Errorf("invalid view type: %s", viewName) } @@ -210,8 +211,8 @@ func sendWaveObjUpdate(oref waveobj.ORef) { eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_WaveObjUpdate, ORef: oref.String(), - Data: wstore.WaveObjUpdate{ - UpdateType: wstore.UpdateType_Update, + Data: waveobj.WaveObjUpdate{ + UpdateType: waveobj.UpdateType_Update, OType: waveObj.GetOType(), OID: waveobj.GetOID(waveObj), Obj: waveObj, @@ -224,7 +225,7 @@ func resolveSimpleId(ctx context.Context, data wshrpc.CommandResolveIdsData, sim if data.BlockId == "" { return nil, fmt.Errorf("no blockid in request") } - return &waveobj.ORef{OType: wstore.OType_Block, OID: data.BlockId}, nil + return &waveobj.ORef{OType: waveobj.OType_Block, OID: data.BlockId}, nil } if strings.Contains(simpleId, ":") { rtn, err := waveobj.ParseORef(simpleId) @@ -249,7 +250,7 @@ func (ws *WshServer) ResolveIdsCommand(ctx context.Context, data wshrpc.CommandR return rtn, nil } -func sendWStoreUpdatesToEventBus(updates wstore.UpdatesRtnType) { +func sendWStoreUpdatesToEventBus(updates waveobj.UpdatesRtnType) { for _, update := range updates { eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_WaveObjUpdate, @@ -260,7 +261,7 @@ func sendWStoreUpdatesToEventBus(updates wstore.UpdatesRtnType) { } func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.CommandCreateBlockData) (*waveobj.ORef, error) { - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) tabId := data.TabId if data.TabId != "" { tabId = data.TabId @@ -269,7 +270,7 @@ func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.Command if err != nil { return nil, fmt.Errorf("error creating block: %w", err) } - controllerName := blockData.Meta.GetString(wstore.MetaKey_Controller, "") + controllerName := blockData.Meta.GetString(waveobj.MetaKey_Controller, "") if controllerName != "" { // TODO err = blockcontroller.StartBlockController(ctx, data.TabId, blockData.OID) @@ -277,7 +278,7 @@ func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.Command return nil, fmt.Errorf("error starting block controller: %w", err) } } - updates := wstore.ContextGetUpdatesRtn(ctx) + updates := waveobj.ContextGetUpdatesRtn(ctx) sendWStoreUpdatesToEventBus(updates) windowId, err := wstore.DBFindWindowForTabId(ctx, tabId) if err != nil { @@ -294,22 +295,22 @@ func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.Command BlockId: blockData.OID, }, }) - return &waveobj.ORef{OType: wstore.OType_Block, OID: blockData.OID}, nil + return &waveobj.ORef{OType: waveobj.OType_Block, OID: blockData.OID}, nil } func (ws *WshServer) SetViewCommand(ctx context.Context, data wshrpc.CommandBlockSetViewData) error { log.Printf("SETVIEW: %s | %q\n", data.BlockId, data.View) - ctx = wstore.ContextWithUpdates(ctx) - block, err := wstore.DBGet[*wstore.Block](ctx, data.BlockId) + ctx = waveobj.ContextWithUpdates(ctx) + block, err := wstore.DBGet[*waveobj.Block](ctx, data.BlockId) if err != nil { return fmt.Errorf("error getting block: %w", err) } - block.Meta[wstore.MetaKey_View] = data.View + block.Meta[waveobj.MetaKey_View] = data.View err = wstore.DBUpdate(ctx, block) if err != nil { return fmt.Errorf("error updating block: %w", err) } - updates := wstore.ContextGetUpdatesRtn(ctx) + updates := waveobj.ContextGetUpdatesRtn(ctx) sendWStoreUpdatesToEventBus(updates) return nil } @@ -353,7 +354,7 @@ func (ws *WshServer) FileWriteCommand(ctx context.Context, data wshrpc.CommandFi } eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_BlockFile, - ORef: waveobj.MakeORef(wstore.OType_Block, data.ZoneId).String(), + ORef: waveobj.MakeORef(waveobj.OType_Block, data.ZoneId).String(), Data: &eventbus.WSFileEventData{ ZoneId: data.ZoneId, FileName: data.FileName, @@ -382,7 +383,7 @@ func (ws *WshServer) FileAppendCommand(ctx context.Context, data wshrpc.CommandF } eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_BlockFile, - ORef: waveobj.MakeORef(wstore.OType_Block, data.ZoneId).String(), + ORef: waveobj.MakeORef(waveobj.OType_Block, data.ZoneId).String(), Data: &eventbus.WSFileEventData{ ZoneId: data.ZoneId, FileName: data.FileName, @@ -407,7 +408,7 @@ func (ws *WshServer) FileAppendIJsonCommand(ctx context.Context, data wshrpc.Com } eventbus.SendEvent(eventbus.WSEventType{ EventType: eventbus.WSEvent_BlockFile, - ORef: waveobj.MakeORef(wstore.OType_Block, data.ZoneId).String(), + ORef: waveobj.MakeORef(waveobj.OType_Block, data.ZoneId).String(), Data: &eventbus.WSFileEventData{ ZoneId: data.ZoneId, FileName: data.FileName, @@ -419,7 +420,7 @@ func (ws *WshServer) FileAppendIJsonCommand(ctx context.Context, data wshrpc.Com } func (ws *WshServer) DeleteBlockCommand(ctx context.Context, data wshrpc.CommandDeleteBlockData) error { - ctx = wstore.ContextWithUpdates(ctx) + ctx = waveobj.ContextWithUpdates(ctx) tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId) if err != nil { return fmt.Errorf("error finding tab for block: %w", err) @@ -434,7 +435,7 @@ func (ws *WshServer) DeleteBlockCommand(ctx context.Context, data wshrpc.Command if windowId == "" { return fmt.Errorf("no window found for tab") } - err = wstore.DeleteBlock(ctx, tabId, data.BlockId) + err = wcore.DeleteBlock(ctx, tabId, data.BlockId) if err != nil { return fmt.Errorf("error deleting block: %w", err) } @@ -446,8 +447,7 @@ func (ws *WshServer) DeleteBlockCommand(ctx context.Context, data wshrpc.Command BlockId: data.BlockId, }, }) - blockcontroller.StopBlockController(data.BlockId) - updates := wstore.ContextGetUpdatesRtn(ctx) + updates := waveobj.ContextGetUpdatesRtn(ctx) sendWStoreUpdatesToEventBus(updates) return nil } diff --git a/pkg/wshutil/wshevent.go b/pkg/wshutil/wshevent.go new file mode 100644 index 00000000..78968ad1 --- /dev/null +++ b/pkg/wshutil/wshevent.go @@ -0,0 +1,67 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package wshutil + +import ( + "sync" + + "github.com/google/uuid" + "github.com/wavetermdev/thenextwave/pkg/wshrpc" +) + +// event inverter. converts WaveEvents to a listener.On() API + +type singleListener struct { + Id string + Fn func(*wshrpc.WaveEvent) +} + +type EventListener struct { + Lock *sync.Mutex + Listeners map[string][]singleListener +} + +func MakeEventListener() *EventListener { + return &EventListener{ + Lock: &sync.Mutex{}, + Listeners: make(map[string][]singleListener), + } +} + +func (el *EventListener) On(eventName string, fn func(*wshrpc.WaveEvent)) string { + id := uuid.New().String() + el.Lock.Lock() + defer el.Lock.Unlock() + larr := el.Listeners[eventName] + larr = append(larr, singleListener{Id: id, Fn: fn}) + el.Listeners[eventName] = larr + return id +} + +func (el *EventListener) Unregister(eventName string, id string) { + el.Lock.Lock() + defer el.Lock.Unlock() + larr := el.Listeners[eventName] + newArr := make([]singleListener, 0) + for _, sl := range larr { + if sl.Id == id { + continue + } + newArr = append(newArr, sl) + } + el.Listeners[eventName] = newArr +} + +func (el *EventListener) getListeners(eventName string) []singleListener { + el.Lock.Lock() + defer el.Lock.Unlock() + return el.Listeners[eventName] +} + +func (el *EventListener) RecvEvent(e *wshrpc.WaveEvent) { + larr := el.getListeners(e.Event) + for _, sl := range larr { + sl.Fn(e) + } +} diff --git a/pkg/wshutil/wshproxy.go b/pkg/wshutil/wshproxy.go index c64d5b78..25102d5f 100644 --- a/pkg/wshutil/wshproxy.go +++ b/pkg/wshutil/wshproxy.go @@ -54,7 +54,7 @@ func (p *WshRpcProxy) sendResponseError(msg RpcMessage, sendErr error) { p.SendRpcMessage(respBytes) } -func (p *WshRpcProxy) sendResponse(msg RpcMessage) { +func (p *WshRpcProxy) sendResponse(msg RpcMessage, routeId string) { if msg.ReqId == "" { // no response needed return @@ -62,35 +62,40 @@ func (p *WshRpcProxy) sendResponse(msg RpcMessage) { resp := RpcMessage{ ResId: msg.ReqId, Route: msg.Source, + Data: wshrpc.CommandAuthenticateRtnData{RouteId: routeId}, } respBytes, _ := json.Marshal(resp) p.SendRpcMessage(respBytes) } -func handleAuthenticationCommand(msg RpcMessage) (*wshrpc.RpcContext, error) { +func handleAuthenticationCommand(msg RpcMessage) (*wshrpc.RpcContext, string, error) { if msg.Data == nil { - return nil, fmt.Errorf("no data in authenticate message") + return nil, "", fmt.Errorf("no data in authenticate message") } strData, ok := msg.Data.(string) if !ok { - return nil, fmt.Errorf("data in authenticate message not a string") + return nil, "", fmt.Errorf("data in authenticate message not a string") } newCtx, err := ValidateAndExtractRpcContextFromToken(strData) if err != nil { - return nil, fmt.Errorf("error validating token: %w", err) + return nil, "", fmt.Errorf("error validating token: %w", err) } if newCtx == nil { - return nil, fmt.Errorf("no context found in jwt token") + return nil, "", fmt.Errorf("no context found in jwt token") } if newCtx.BlockId == "" && newCtx.Conn == "" { - return nil, fmt.Errorf("no blockid or conn found in jwt token") + return nil, "", fmt.Errorf("no blockid or conn found in jwt token") } if newCtx.BlockId != "" { if _, err := uuid.Parse(newCtx.BlockId); err != nil { - return nil, fmt.Errorf("invalid blockId in jwt token") + return nil, "", fmt.Errorf("invalid blockId in jwt token") } } - return newCtx, nil + routeId, err := MakeRouteIdFromCtx(newCtx) + if err != nil { + return nil, "", fmt.Errorf("error making routeId from context: %w", err) + } + return newCtx, routeId, nil } func (p *WshRpcProxy) HandleAuthentication() (*wshrpc.RpcContext, error) { @@ -115,13 +120,13 @@ func (p *WshRpcProxy) HandleAuthentication() (*wshrpc.RpcContext, error) { p.sendResponseError(msg, respErr) continue } - newCtx, err := handleAuthenticationCommand(msg) + newCtx, routeId, err := handleAuthenticationCommand(msg) if err != nil { log.Printf("error handling authentication: %v\n", err) p.sendResponseError(msg, err) continue } - p.sendResponse(msg) + p.sendResponse(msg, routeId) return newCtx, nil } } diff --git a/pkg/wshutil/wshrouter.go b/pkg/wshutil/wshrouter.go index 9d2ad523..cd0ab4ea 100644 --- a/pkg/wshutil/wshrouter.go +++ b/pkg/wshutil/wshrouter.go @@ -50,6 +50,10 @@ func MakeWindowRouteId(windowId string) string { return "window:" + windowId } +func MakeProcRouteId(procId string) string { + return "proc:" + procId +} + var DefaultRouter = NewWshRouter() func NewWshRouter() *WshRouter { @@ -77,7 +81,7 @@ func (router *WshRouter) SendEvent(routeId string, event wshrpc.WaveEvent) { return } msg := RpcMessage{ - Command: wshrpc.Command_Event, + Command: wshrpc.Command_EventRecv, Route: routeId, Data: event, } @@ -238,7 +242,7 @@ func (router *WshRouter) RegisterRoute(routeId string, rpc AbstractRpcClient) { log.Printf("error: WshRouter cannot register sys route\n") return } - log.Printf("registering wsh route %q\n", routeId) + log.Printf("[router] registering wsh route %q\n", routeId) router.Lock.Lock() defer router.Lock.Unlock() router.RouteMap[routeId] = rpc @@ -277,6 +281,7 @@ func (router *WshRouter) RegisterRoute(routeId string, rpc AbstractRpcClient) { } func (router *WshRouter) UnregisterRoute(routeId string) { + log.Printf("[router] unregistering wsh route %q\n", routeId) router.Lock.Lock() defer router.Lock.Unlock() delete(router.RouteMap, routeId) diff --git a/pkg/wshutil/wshrpc.go b/pkg/wshutil/wshrpc.go index fc86e844..43dd1dab 100644 --- a/pkg/wshutil/wshrpc.go +++ b/pkg/wshutil/wshrpc.go @@ -16,6 +16,7 @@ import ( "time" "github.com/google/uuid" + "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/wshrpc" ) @@ -38,14 +39,14 @@ type AbstractRpcClient interface { } type WshRpc struct { - Lock *sync.Mutex - clientId string - InputCh chan []byte - OutputCh chan []byte - RpcContext *atomic.Pointer[wshrpc.RpcContext] - RpcMap map[string]*rpcData - ServerImpl ServerImpl - + Lock *sync.Mutex + clientId string + InputCh chan []byte + OutputCh chan []byte + RpcContext *atomic.Pointer[wshrpc.RpcContext] + RpcMap map[string]*rpcData + ServerImpl ServerImpl + EventListener *EventListener ResponseHandlerMap map[string]*RpcResponseHandler // reqId => handler } @@ -202,6 +203,7 @@ func MakeWshRpc(inputCh chan []byte, outputCh chan []byte, rpcCtx wshrpc.RpcCont OutputCh: outputCh, RpcMap: make(map[string]*rpcData), RpcContext: &atomic.Pointer[wshrpc.RpcContext]{}, + EventListener: MakeEventListener(), ServerImpl: serverImpl, ResponseHandlerMap: make(map[string]*RpcResponseHandler), } @@ -214,20 +216,6 @@ func (w *WshRpc) ClientId() string { return w.clientId } -func (w *WshRpc) SendEvent(event wshrpc.WaveEvent) { - // for wps compatibility - msg := &RpcMessage{ - Command: wshrpc.Command_EventPublish, - Data: event, - } - barr, err := json.Marshal(msg) - if err != nil { - log.Printf("error marshalling event: %v\n", err) - return - } - w.OutputCh <- barr -} - func (w *WshRpc) GetRpcContext() wshrpc.RpcContext { rtnPtr := w.RpcContext.Load() return *rtnPtr @@ -263,6 +251,22 @@ func (w *WshRpc) cancelRequest(reqId string) { } func (w *WshRpc) handleRequest(req *RpcMessage) { + // events first + if req.Command == wshrpc.Command_EventRecv { + if req.Data == nil { + // invalid + return + } + var waveEvent wshrpc.WaveEvent + err := utilfn.ReUnmarshal(&waveEvent, req.Data) + if err != nil { + // invalid + return + } + w.EventListener.RecvEvent(&waveEvent) + return + } + var respHandler *RpcResponseHandler defer func() { if r := recover(); r != nil { diff --git a/pkg/wshutil/wshutil.go b/pkg/wshutil/wshutil.go index cdc0f4d8..a1562e6a 100644 --- a/pkg/wshutil/wshutil.go +++ b/pkg/wshutil/wshutil.go @@ -18,6 +18,7 @@ import ( "time" "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" "github.com/wavetermdev/thenextwave/pkg/wavebase" "github.com/wavetermdev/thenextwave/pkg/wshrpc" "golang.org/x/term" @@ -251,6 +252,9 @@ func MakeClientJWTToken(rpcCtx wshrpc.RpcContext, sockName string) (string, erro if rpcCtx.Conn != "" { claims["conn"] = rpcCtx.Conn } + if rpcCtx.ClientType != "" { + claims["ctype"] = rpcCtx.ClientType + } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenStr, err := token.SignedString([]byte(wavebase.JwtSecret)) if err != nil { @@ -307,6 +311,11 @@ func mapClaimsToRpcContext(claims jwt.MapClaims) *wshrpc.RpcContext { rpcCtx.Conn = conn } } + if claims["ctype"] != nil { + if ctype, ok := claims["ctype"].(string); ok { + rpcCtx.ClientType = ctype + } + } return rpcCtx } @@ -326,7 +335,28 @@ func RunWshRpcOverListener(listener net.Listener) { } } +func MakeRouteIdFromCtx(rpcCtx *wshrpc.RpcContext) (string, error) { + if rpcCtx.ClientType != "" { + if rpcCtx.ClientType == wshrpc.ClientType_ConnServer { + if rpcCtx.Conn != "" { + return MakeConnectionRouteId(rpcCtx.Conn), nil + } + return "", fmt.Errorf("invalid connserver connection, no conn id") + } + if rpcCtx.ClientType == wshrpc.ClientType_BlockController { + if rpcCtx.BlockId != "" { + return MakeControllerRouteId(rpcCtx.BlockId), nil + } + return "", fmt.Errorf("invalid block controller connection, no block id") + } + return "", fmt.Errorf("invalid client type: %q", rpcCtx.ClientType) + } + procId := uuid.New().String() + return MakeProcRouteId(procId), nil +} + func handleDomainSocketClient(conn net.Conn) { + var routeIdContainer atomic.Pointer[string] proxy := MakeRpcProxy() go func() { writeErr := AdaptOutputChToStream(proxy.ToRemoteCh, conn) @@ -336,7 +366,13 @@ func handleDomainSocketClient(conn net.Conn) { }() go func() { // when input is closed, close the connection - defer conn.Close() + defer func() { + conn.Close() + routeIdPtr := routeIdContainer.Load() + if routeIdPtr != nil && *routeIdPtr != "" { + DefaultRouter.UnregisterRoute(*routeIdPtr) + } + }() AdaptStreamToMsgCh(conn, proxy.FromRemoteCh) }() rpcCtx, err := proxy.HandleAuthentication() @@ -348,11 +384,14 @@ func handleDomainSocketClient(conn net.Conn) { // now that we're authenticated, set the ctx and attach to the router log.Printf("domain socket connection authenticated: %#v\n", rpcCtx) proxy.SetRpcContext(rpcCtx) - if rpcCtx.BlockId != "" { - DefaultRouter.RegisterRoute(MakeControllerRouteId(rpcCtx.BlockId), proxy) - } else if rpcCtx.Conn != "" { - DefaultRouter.RegisterRoute(MakeConnectionRouteId(rpcCtx.Conn), proxy) + routeId, err := MakeRouteIdFromCtx(rpcCtx) + if err != nil { + conn.Close() + log.Printf("error making route id: %v\n", err) + return } + routeIdContainer.Store(&routeId) + DefaultRouter.RegisterRoute(routeId, proxy) } // only for use on client diff --git a/pkg/wstore/wstore.go b/pkg/wstore/wstore.go index 9ce6058c..c6b9e0df 100644 --- a/pkg/wstore/wstore.go +++ b/pkg/wstore/wstore.go @@ -4,10 +4,8 @@ package wstore import ( - "bytes" "context" "fmt" - "log" "time" "github.com/google/uuid" @@ -15,154 +13,26 @@ import ( "github.com/wavetermdev/thenextwave/pkg/waveobj" ) -var waveObjUpdateKey = struct{}{} - -type UpdatesRtnType = []WaveObjUpdate - func init() { - for _, rtype := range AllWaveObjTypes() { + for _, rtype := range waveobj.AllWaveObjTypes() { waveobj.RegisterType(rtype) } } -type contextUpdatesType struct { - UpdatesStack []map[waveobj.ORef]WaveObjUpdate -} - -func dumpUpdateStack(updates *contextUpdatesType) { - log.Printf("dumpUpdateStack len:%d\n", len(updates.UpdatesStack)) - for idx, update := range updates.UpdatesStack { - var buf bytes.Buffer - buf.WriteString(fmt.Sprintf(" [%d]:", idx)) - for k := range update { - buf.WriteString(fmt.Sprintf(" %s:%s", k.OType, k.OID)) - } - buf.WriteString("\n") - log.Print(buf.String()) - } -} - -func ContextWithUpdates(ctx context.Context) context.Context { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal != nil { - return ctx - } - return context.WithValue(ctx, waveObjUpdateKey, &contextUpdatesType{ - UpdatesStack: []map[waveobj.ORef]WaveObjUpdate{make(map[waveobj.ORef]WaveObjUpdate)}, - }) -} - -func ContextGetUpdates(ctx context.Context) map[waveobj.ORef]WaveObjUpdate { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal == nil { - return nil - } - updates := updatesVal.(*contextUpdatesType) - if len(updates.UpdatesStack) == 1 { - return updates.UpdatesStack[0] - } - rtn := make(map[waveobj.ORef]WaveObjUpdate) - for _, update := range updates.UpdatesStack { - for k, v := range update { - rtn[k] = v - } - } - return rtn -} - -func ContextGetUpdatesRtn(ctx context.Context) UpdatesRtnType { - updatesMap := ContextGetUpdates(ctx) - if updatesMap == nil { - return nil - } - rtn := make(UpdatesRtnType, 0, len(updatesMap)) - for _, v := range updatesMap { - rtn = append(rtn, v) - } - return rtn -} - -func ContextGetUpdate(ctx context.Context, oref waveobj.ORef) *WaveObjUpdate { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal == nil { - return nil - } - updates := updatesVal.(*contextUpdatesType) - for idx := len(updates.UpdatesStack) - 1; idx >= 0; idx-- { - if obj, ok := updates.UpdatesStack[idx][oref]; ok { - return &obj - } - } - return nil -} - -func ContextAddUpdate(ctx context.Context, update WaveObjUpdate) { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal == nil { - return - } - updates := updatesVal.(*contextUpdatesType) - oref := waveobj.ORef{ - OType: update.OType, - OID: update.OID, - } - updates.UpdatesStack[len(updates.UpdatesStack)-1][oref] = update -} - -func ContextUpdatesBeginTx(ctx context.Context) context.Context { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal == nil { - return ctx - } - updates := updatesVal.(*contextUpdatesType) - updates.UpdatesStack = append(updates.UpdatesStack, make(map[waveobj.ORef]WaveObjUpdate)) - return ctx -} - -func ContextUpdatesCommitTx(ctx context.Context) { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal == nil { - return - } - updates := updatesVal.(*contextUpdatesType) - if len(updates.UpdatesStack) <= 1 { - panic(fmt.Errorf("no updates transaction to commit")) - } - // merge the last two updates - curUpdateMap := updates.UpdatesStack[len(updates.UpdatesStack)-1] - prevUpdateMap := updates.UpdatesStack[len(updates.UpdatesStack)-2] - for k, v := range curUpdateMap { - prevUpdateMap[k] = v - } - updates.UpdatesStack = updates.UpdatesStack[:len(updates.UpdatesStack)-1] -} - -func ContextUpdatesRollbackTx(ctx context.Context) { - updatesVal := ctx.Value(waveObjUpdateKey) - if updatesVal == nil { - return - } - updates := updatesVal.(*contextUpdatesType) - if len(updates.UpdatesStack) <= 1 { - panic(fmt.Errorf("no updates transaction to rollback")) - } - updates.UpdatesStack = updates.UpdatesStack[:len(updates.UpdatesStack)-1] -} - -func CreateTab(ctx context.Context, workspaceId string, name string) (*Tab, error) { - return WithTxRtn(ctx, func(tx *TxWrap) (*Tab, error) { - ws, _ := DBGet[*Workspace](tx.Context(), workspaceId) +func CreateTab(ctx context.Context, workspaceId string, name string) (*waveobj.Tab, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.Tab, error) { + ws, _ := DBGet[*waveobj.Workspace](tx.Context(), workspaceId) if ws == nil { return nil, fmt.Errorf("workspace not found: %q", workspaceId) } layoutStateId := uuid.NewString() - tab := &Tab{ + tab := &waveobj.Tab{ OID: uuid.NewString(), Name: name, BlockIds: []string{}, LayoutState: layoutStateId, } - layoutState := &LayoutState{ + layoutState := &waveobj.LayoutState{ OID: layoutStateId, } ws.TabIds = append(ws.TabIds, tab.OID) @@ -173,8 +43,8 @@ func CreateTab(ctx context.Context, workspaceId string, name string) (*Tab, erro }) } -func CreateWorkspace(ctx context.Context) (*Workspace, error) { - ws := &Workspace{ +func CreateWorkspace(ctx context.Context) (*waveobj.Workspace, error) { + ws := &waveobj.Workspace{ OID: uuid.NewString(), TabIds: []string{}, } @@ -184,7 +54,7 @@ func CreateWorkspace(ctx context.Context) (*Workspace, error) { func UpdateWorkspaceTabIds(ctx context.Context, workspaceId string, tabIds []string) error { return WithTx(ctx, func(tx *TxWrap) error { - ws, _ := DBGet[*Workspace](tx.Context(), workspaceId) + ws, _ := DBGet[*waveobj.Workspace](tx.Context(), workspaceId) if ws == nil { return fmt.Errorf("workspace not found: %q", workspaceId) } @@ -196,12 +66,12 @@ func UpdateWorkspaceTabIds(ctx context.Context, workspaceId string, tabIds []str func SetActiveTab(ctx context.Context, windowId string, tabId string) error { return WithTx(ctx, func(tx *TxWrap) error { - window, _ := DBGet[*Window](tx.Context(), windowId) + window, _ := DBGet[*waveobj.Window](tx.Context(), windowId) if window == nil { return fmt.Errorf("window not found: %q", windowId) } if tabId != "" { - tab, _ := DBGet[*Tab](tx.Context(), tabId) + tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) if tab == nil { return fmt.Errorf("tab not found: %q", tabId) } @@ -214,7 +84,7 @@ func SetActiveTab(ctx context.Context, windowId string, tabId string) error { func UpdateTabName(ctx context.Context, tabId, name string) error { return WithTx(ctx, func(tx *TxWrap) error { - tab, _ := DBGet[*Tab](tx.Context(), tabId) + tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) if tab == nil { return fmt.Errorf("tab not found: %q", tabId) } @@ -226,14 +96,14 @@ func UpdateTabName(ctx context.Context, tabId, name string) error { }) } -func CreateBlock(ctx context.Context, tabId string, blockDef *BlockDef, rtOpts *RuntimeOpts) (*Block, error) { - return WithTxRtn(ctx, func(tx *TxWrap) (*Block, error) { - tab, _ := DBGet[*Tab](tx.Context(), tabId) +func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.Block, error) { + tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) if tab == nil { return nil, fmt.Errorf("tab not found: %q", tabId) } blockId := uuid.NewString() - blockData := &Block{ + blockData := &waveobj.Block{ OID: blockId, BlockDef: blockDef, RuntimeOpts: rtOpts, @@ -257,7 +127,7 @@ func findStringInSlice(slice []string, val string) int { func DeleteBlock(ctx context.Context, tabId string, blockId string) error { return WithTx(ctx, func(tx *TxWrap) error { - tab, _ := DBGet[*Tab](tx.Context(), tabId) + tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) if tab == nil { return fmt.Errorf("tab not found: %q", tabId) } @@ -267,37 +137,39 @@ func DeleteBlock(ctx context.Context, tabId string, blockId string) error { } tab.BlockIds = append(tab.BlockIds[:blockIdx], tab.BlockIds[blockIdx+1:]...) DBUpdate(tx.Context(), tab) - DBDelete(tx.Context(), OType_Block, blockId) + DBDelete(tx.Context(), waveobj.OType_Block, blockId) return nil }) } +// must delete all blocks individually first +// also deletes LayoutState func DeleteTab(ctx context.Context, workspaceId string, tabId string) error { return WithTx(ctx, func(tx *TxWrap) error { - ws, _ := DBGet[*Workspace](tx.Context(), workspaceId) + ws, _ := DBGet[*waveobj.Workspace](tx.Context(), workspaceId) if ws == nil { return fmt.Errorf("workspace not found: %q", workspaceId) } - tab, _ := DBGet[*Tab](tx.Context(), tabId) + tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) if tab == nil { return fmt.Errorf("tab not found: %q", tabId) } + if len(tab.BlockIds) != 0 { + return fmt.Errorf("tab has blocks, must delete blocks first") + } tabIdx := findStringInSlice(ws.TabIds, tabId) if tabIdx == -1 { return nil } ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...) DBUpdate(tx.Context(), ws) - DBDelete(tx.Context(), OType_Tab, tabId) - DBDelete(tx.Context(), OType_LayoutState, tab.LayoutState) - for _, blockId := range tab.BlockIds { - DBDelete(tx.Context(), OType_Block, blockId) - } + DBDelete(tx.Context(), waveobj.OType_Tab, tabId) + DBDelete(tx.Context(), waveobj.OType_LayoutState, tab.LayoutState) return nil }) } -func UpdateObjectMeta(ctx context.Context, oref waveobj.ORef, meta MetaMapType) error { +func UpdateObjectMeta(ctx context.Context, oref waveobj.ORef, meta waveobj.MetaMapType) error { return WithTx(ctx, func(tx *TxWrap) error { if oref.IsEmpty() { return fmt.Errorf("empty object reference") @@ -310,27 +182,27 @@ func UpdateObjectMeta(ctx context.Context, oref waveobj.ORef, meta MetaMapType) if objMeta == nil { objMeta = make(map[string]any) } - newMeta := MergeMeta(objMeta, meta) + newMeta := waveobj.MergeMeta(objMeta, meta) waveobj.SetMeta(obj, newMeta) DBUpdate(tx.Context(), obj) return nil }) } -func CreateWindow(ctx context.Context, winSize *WinSize) (*Window, error) { +func CreateWindow(ctx context.Context, winSize *waveobj.WinSize) (*waveobj.Window, error) { windowId := uuid.NewString() workspaceId := uuid.NewString() if winSize == nil { - winSize = &WinSize{ + winSize = &waveobj.WinSize{ Width: 1200, Height: 800, } } - window := &Window{ + window := &waveobj.Window{ OID: windowId, WorkspaceId: workspaceId, ActiveBlockMap: make(map[string]string), - Pos: Point{ + Pos: waveobj.Point{ X: 100, Y: 100, }, @@ -340,7 +212,7 @@ func CreateWindow(ctx context.Context, winSize *WinSize) (*Window, error) { if err != nil { return nil, fmt.Errorf("error inserting window: %w", err) } - ws := &Workspace{ + ws := &waveobj.Workspace{ OID: workspaceId, Name: "w" + workspaceId[0:8], } @@ -356,7 +228,7 @@ func CreateWindow(ctx context.Context, winSize *WinSize) (*Window, error) { if err != nil { return nil, fmt.Errorf("error setting active tab: %w", err) } - client, err := DBGetSingleton[*Client](ctx) + client, err := DBGetSingleton[*waveobj.Client](ctx) if err != nil { return nil, fmt.Errorf("error getting client: %w", err) } @@ -365,16 +237,16 @@ func CreateWindow(ctx context.Context, winSize *WinSize) (*Window, error) { if err != nil { return nil, fmt.Errorf("error updating client: %w", err) } - return DBMustGet[*Window](ctx, windowId) + return DBMustGet[*waveobj.Window](ctx, windowId) } func MoveBlockToTab(ctx context.Context, currentTabId string, newTabId string, blockId string) error { return WithTx(ctx, func(tx *TxWrap) error { - currentTab, _ := DBGet[*Tab](tx.Context(), currentTabId) + currentTab, _ := DBGet[*waveobj.Tab](tx.Context(), currentTabId) if currentTab == nil { return fmt.Errorf("current tab not found: %q", currentTabId) } - newTab, _ := DBGet[*Tab](tx.Context(), newTabId) + newTab, _ := DBGet[*waveobj.Tab](tx.Context(), newTabId) if newTab == nil { return fmt.Errorf("new tab not found: %q", newTabId) } @@ -390,8 +262,8 @@ func MoveBlockToTab(ctx context.Context, currentTabId string, newTabId string, b }) } -func CreateClient(ctx context.Context) (*Client, error) { - client := &Client{ +func CreateClient(ctx context.Context) (*waveobj.Client, error) { + client := &waveobj.Client{ OID: uuid.NewString(), WindowIds: []string{}, } @@ -406,7 +278,7 @@ func EnsureInitialData() error { // does not need to run in a transaction since it is called on startup ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) defer cancelFn() - client, err := DBGetSingleton[*Client](ctx) + client, err := DBGetSingleton[*waveobj.Client](ctx) if err == ErrNotFound { client, err = CreateClient(ctx) if err != nil { @@ -416,7 +288,7 @@ func EnsureInitialData() error { if len(client.WindowIds) > 0 { return nil } - _, err = CreateWindow(ctx, &WinSize{0, 0}) + _, err = CreateWindow(ctx, &waveobj.WinSize{Height: 0, Width: 0}) if err != nil { return fmt.Errorf("error creating window: %w", err) } diff --git a/pkg/wstore/wstore_dbops.go b/pkg/wstore/wstore_dbops.go index acc96ef1..fb0c8bc5 100644 --- a/pkg/wstore/wstore_dbops.go +++ b/pkg/wstore/wstore_dbops.go @@ -167,7 +167,7 @@ func DBSelectORefs(ctx context.Context, orefs []waveobj.ORef) ([]waveobj.WaveObj func DBResolveEasyOID(ctx context.Context, oid string) (*waveobj.ORef, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.ORef, error) { - for _, rtype := range AllWaveObjTypes() { + for _, rtype := range waveobj.AllWaveObjTypes() { otype := reflect.Zero(rtype).Interface().(waveobj.WaveObj).GetOType() table := tableNameFromOType(otype) var fullOID string @@ -204,7 +204,7 @@ func DBDelete(ctx context.Context, otype string, id string) error { table := tableNameFromOType(otype) query := fmt.Sprintf("DELETE FROM %s WHERE oid = ?", table) tx.Exec(query, id) - ContextAddUpdate(ctx, WaveObjUpdate{UpdateType: UpdateType_Delete, OType: otype, OID: id}) + waveobj.ContextAddUpdate(ctx, waveobj.WaveObjUpdate{UpdateType: waveobj.UpdateType_Delete, OType: otype, OID: id}) return nil }) if err != nil { @@ -237,7 +237,7 @@ func DBUpdate(ctx context.Context, val waveobj.WaveObj) error { query := fmt.Sprintf("UPDATE %s SET data = ?, version = version+1 WHERE oid = ? RETURNING version", table) newVersion := tx.GetInt(query, jsonData, oid) waveobj.SetVersion(val, newVersion) - ContextAddUpdate(ctx, WaveObjUpdate{UpdateType: UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val}) + waveobj.ContextAddUpdate(ctx, waveobj.WaveObjUpdate{UpdateType: waveobj.UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val}) return nil }) } @@ -256,7 +256,7 @@ func DBInsert(ctx context.Context, val waveobj.WaveObj) error { waveobj.SetVersion(val, 1) query := fmt.Sprintf("INSERT INTO %s (oid, version, data) VALUES (?, ?, ?)", table) tx.Exec(query, oid, 1, jsonData) - ContextAddUpdate(ctx, WaveObjUpdate{UpdateType: UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val}) + waveobj.ContextAddUpdate(ctx, waveobj.WaveObjUpdate{UpdateType: waveobj.UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val}) return nil }) } diff --git a/pkg/wstore/wstore_dbsetup.go b/pkg/wstore/wstore_dbsetup.go index fafdade3..b6258da8 100644 --- a/pkg/wstore/wstore_dbsetup.go +++ b/pkg/wstore/wstore_dbsetup.go @@ -14,6 +14,7 @@ import ( "github.com/sawka/txwrap" "github.com/wavetermdev/thenextwave/pkg/util/migrateutil" "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wavetermdev/thenextwave/pkg/waveobj" dbfs "github.com/wavetermdev/thenextwave/db" ) @@ -56,24 +57,24 @@ func MakeDB(ctx context.Context) (*sqlx.DB, error) { } func WithTx(ctx context.Context, fn func(tx *TxWrap) error) (rtnErr error) { - ContextUpdatesBeginTx(ctx) + waveobj.ContextUpdatesBeginTx(ctx) defer func() { if rtnErr != nil { - ContextUpdatesRollbackTx(ctx) + waveobj.ContextUpdatesRollbackTx(ctx) } else { - ContextUpdatesCommitTx(ctx) + waveobj.ContextUpdatesCommitTx(ctx) } }() return txwrap.WithTx(ctx, globalDB, fn) } func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (rtnVal RT, rtnErr error) { - ContextUpdatesBeginTx(ctx) + waveobj.ContextUpdatesBeginTx(ctx) defer func() { if rtnErr != nil { - ContextUpdatesRollbackTx(ctx) + waveobj.ContextUpdatesRollbackTx(ctx) } else { - ContextUpdatesCommitTx(ctx) + waveobj.ContextUpdatesCommitTx(ctx) } }() return txwrap.WithTxRtn(ctx, globalDB, fn)