waveterm/pkg/wcore/wcore.go
Evan Simkowitz 936d4bfb30
Migrate websocket eventbus messages to wps (#367)
This migrates all remaining eventbus events sent over the websocket to
use the wps interface. WPS is more flexible for registering events and
callbacks and provides support for more reliable unsubscribes and
resubscribes.
2024-09-11 18:03:55 -07:00

178 lines
4.9 KiB
Go

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
// wave core application coordinator
package wcore
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/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
const DefaultActivateBlockTimeout = 60 * 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)
}
go blockcontroller.StopBlockController(blockId)
sendBlockCloseEvent(tabId, blockId)
return nil
}
func sendBlockCloseEvent(tabId string, blockId string) {
waveEvent := wps.WaveEvent{
Event: wps.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
}
// returns tabid
func CreateTab(ctx context.Context, windowId string, tabName string, activateTab bool) (string, error) {
windowData, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId)
if err != nil {
return "", fmt.Errorf("error getting window: %w", err)
}
tab, err := wstore.CreateTab(ctx, windowData.WorkspaceId, tabName)
if err != nil {
return "", fmt.Errorf("error creating tab: %w", err)
}
if activateTab {
err = wstore.SetActiveTab(ctx, windowId, tab.OID)
if err != nil {
return "", fmt.Errorf("error setting active tab: %w", err)
}
}
return tab.OID, nil
}
func CreateWindow(ctx context.Context, winSize *waveobj.WinSize) (*waveobj.Window, error) {
windowId := uuid.NewString()
workspaceId := uuid.NewString()
if winSize == nil {
winSize = &waveobj.WinSize{
Width: 1200,
Height: 850,
}
}
window := &waveobj.Window{
OID: windowId,
WorkspaceId: workspaceId,
Pos: waveobj.Point{
X: 100,
Y: 100,
},
WinSize: *winSize,
}
err := wstore.DBInsert(ctx, window)
if err != nil {
return nil, fmt.Errorf("error inserting window: %w", err)
}
ws := &waveobj.Workspace{
OID: workspaceId,
Name: "w" + workspaceId[0:8],
}
err = wstore.DBInsert(ctx, ws)
if err != nil {
return nil, fmt.Errorf("error inserting workspace: %w", err)
}
_, err = CreateTab(ctx, windowId, "T1", true)
if err != nil {
return nil, fmt.Errorf("error inserting tab: %w", err)
}
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
if err != nil {
return nil, fmt.Errorf("error getting client: %w", err)
}
client.WindowIds = append(client.WindowIds, windowId)
err = wstore.DBUpdate(ctx, client)
if err != nil {
return nil, fmt.Errorf("error updating client: %w", err)
}
return wstore.DBMustGet[*waveobj.Window](ctx, windowId)
}
func EnsureInitialData() (*waveobj.Window, 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 := wstore.DBGetSingleton[*waveobj.Client](ctx)
if err == wstore.ErrNotFound {
client, err = CreateClient(ctx)
if err != nil {
return nil, fmt.Errorf("error creating client: %w", err)
}
}
if len(client.WindowIds) > 0 {
return nil, nil
}
window, err := CreateWindow(ctx, &waveobj.WinSize{Height: 0, Width: 0})
if err != nil {
return nil, fmt.Errorf("error creating window: %w", err)
}
return window, nil
}
func CreateClient(ctx context.Context) (*waveobj.Client, error) {
client := &waveobj.Client{
OID: uuid.NewString(),
WindowIds: []string{},
}
err := wstore.DBInsert(ctx, client)
if err != nil {
return nil, fmt.Errorf("error inserting client: %w", err)
}
return client, nil
}
func CreateBlock(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)
}
return blockData, nil
}