diff --git a/src/models/model.ts b/src/models/model.ts index 26e7a567..d3f5841d 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -715,7 +715,7 @@ class Model { return this.ws.open.get(); } - runUpdate(genUpdate: UpdateMessage, interactive: boolean) { + runUpdate(genUpdate: UpdatePacket, interactive: boolean) { mobx.action(() => { const oldContext = this.getUIContext(); try { @@ -727,8 +727,9 @@ class Model { const newContext = this.getUIContext(); if (oldContext.sessionid != newContext.sessionid || oldContext.screenid != newContext.screenid) { this.inputModel.resetInput(); - if (!("ptydata64" in genUpdate)) { - const reversedGenUpdate = genUpdate.slice().reverse(); + if (genUpdate.type == "model") { + const modelUpdate = genUpdate as ModelUpdatePacket; + const reversedGenUpdate = modelUpdate.data.slice().reverse(); const lastCmdLine = reversedGenUpdate.find((update) => "cmdline" in update); if (lastCmdLine) { // TODO a bit of a hack since this update gets applied in runUpdate_internal. @@ -768,20 +769,12 @@ class Model { } updateActiveSession(sessionId: string): void { - const [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds(); - if (sessionId != null) { const newSessionId = sessionId; if (this.activeSessionId.get() != newSessionId) { this.activeSessionId.set(newSessionId); } } - const [newActiveSessionId, newActiveScreenId] = this.getActiveIds(); - if (oldActiveSessionId != newActiveSessionId || oldActiveScreenId != newActiveScreenId) { - this.activeMainView.set("session"); - this.deactivateScreenLines(); - this.ws.watchScreen(newActiveSessionId, newActiveScreenId); - } } updateScreenNumRunningCommands(numRunningCommandUpdates: ScreenNumRunningCommandsUpdateType[]) { @@ -796,9 +789,9 @@ class Model { } } - runUpdate_internal(genUpdate: UpdateMessage, uiContext: UIContextType, interactive: boolean) { - if ("ptydata64" in genUpdate) { - const ptyMsg: PtyDataUpdateType = genUpdate; + runUpdate_internal(genUpdate: UpdatePacket, uiContext: UIContextType, interactive: boolean) { + if (genUpdate.type == "pty") { + const ptyMsg = genUpdate.data as PtyDataUpdateType; if (isBlank(ptyMsg.remoteid)) { // regular update this.updatePtyData(ptyMsg); @@ -807,125 +800,138 @@ class Model { const ptyData = base64ToArray(ptyMsg.ptydata64); this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData); } - return; - } - let showedRemotesModal = false; - genUpdate.forEach((update) => { - if (update.connect != null) { - if (update.connect.screens != null) { - this.screenMap.clear(); - this.updateScreens(update.connect.screens); - } - if (update.connect.sessions != null) { - this.sessionList.clear(); - this.updateSessions(update.connect.sessions); - } - if (update.connect.remotes != null) { - this.remotes.clear(); - this.updateRemotes(update.connect.remotes); - } - if (update.connect.activesessionid != null) { - this.updateActiveSession(update.connect.activesessionid); - } - if (update.connect.screennumrunningcommands != null) { - this.updateScreenNumRunningCommands(update.connect.screennumrunningcommands); - } - if (update.connect.screenstatusindicators != null) { - this.updateScreenStatusIndicators(update.connect.screenstatusindicators); - } + } else if (genUpdate.type == "model") { + const modelUpdateItems = genUpdate.data as ModelUpdateItemType[]; - this.sessionListLoaded.set(true); - this.remotesLoaded.set(true); - } else if (update.screen != null) { - this.updateScreens([update.screen]); - } else if (update.session != null) { - this.updateSessions([update.session]); - } else if (update.activesessionid != null) { - this.updateActiveSession(update.activesessionid); - } else if (update.line != null) { - this.addLineCmd(update.line.line, update.line.cmd, interactive); - } else if (update.cmd != null) { - this.updateCmd(update.cmd); - } else if (update.screenlines != null) { - this.updateScreenLines(update.screenlines, false); - } else if (update.remote != null) { - this.updateRemotes([update.remote]); - // This code's purpose is to show view remote connection modal when a new connection is added - if (!showedRemotesModal && this.remotesModel.recentConnAddedState.get()) { - showedRemotesModal = true; - this.remotesModel.openReadModal(update.remote.remoteid); - } - } else if (update.mainview != null) { - switch (update.mainview.mainview) { - case "session": - this.activeMainView.set("session"); - break; - case "history": - if (update.mainview.historyview != null) { - this.historyViewModel.showHistoryView(update.mainview.historyview); - } else { - console.warn("invalid historyview in update:", update.mainview); - } - break; - case "bookmarks": - if (update.mainview.bookmarksview != null) { - this.bookmarksModel.showBookmarksView( - update.mainview.bookmarksview?.bookmarks ?? [], - update.mainview.bookmarksview?.selectedbookmark - ); - } else { - console.warn("invalid bookmarksview in update:", update.mainview); - } - break; - case "plugins": - this.pluginsModel.showPluginsView(); - break; - default: - console.warn("invalid mainview in update:", update.mainview); - } - } else if (update.bookmarks != null) { - if (update.bookmarks.bookmarks != null) { - this.bookmarksModel.mergeBookmarks(update.bookmarks.bookmarks); - } - } else if (update.clientdata != null) { - this.setClientData(update.clientdata); - } else if (update.cmdline != null) { - this.inputModel.updateCmdLine(update.cmdline); - } else if (update.openaicmdinfochat != null) { - this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat); - } else if (update.screenstatusindicator != null) { - this.updateScreenStatusIndicators([update.screenstatusindicator]); - } else if (update.screennumrunningcommands != null) { - this.updateScreenNumRunningCommands([update.screennumrunningcommands]); - } else if (update.userinputrequest != null) { - let userInputRequest: UserInputRequest = update.userinputrequest; - this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest); - } else if (interactive) { - if (update.info != null) { - const info: InfoType = update.info; - this.inputModel.flashInfoMsg(info, info.timeoutms); - } else if (update.remoteview != null) { - const rview: RemoteViewType = update.remoteview; - if (rview.remoteedit != null) { - this.remotesModel.openEditModal({ ...rview.remoteedit }); + let showedRemotesModal = false; + const [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds(); + modelUpdateItems.forEach((update) => { + if (update.connect != null) { + if (update.connect.screens != null) { + this.screenMap.clear(); + this.updateScreens(update.connect.screens); } - } else if (update.alertmessage != null) { - const alertMessage: AlertMessageType = update.alertmessage; - this.showAlert(alertMessage); - } else if (update.history != null) { - if ( - uiContext.sessionid == update.history.sessionid && - uiContext.screenid == update.history.screenid - ) { - this.inputModel.setHistoryInfo(update.history); + if (update.connect.sessions != null) { + this.sessionList.clear(); + this.updateSessions(update.connect.sessions); + } + if (update.connect.remotes != null) { + this.remotes.clear(); + this.updateRemotes(update.connect.remotes); + } + if (update.connect.activesessionid != null) { + this.updateActiveSession(update.connect.activesessionid); + } + if (update.connect.screennumrunningcommands != null) { + this.updateScreenNumRunningCommands(update.connect.screennumrunningcommands); + } + if (update.connect.screenstatusindicators != null) { + this.updateScreenStatusIndicators(update.connect.screenstatusindicators); + } + + this.sessionListLoaded.set(true); + this.remotesLoaded.set(true); + } else if (update.screen != null) { + this.updateScreens([update.screen]); + } else if (update.session != null) { + this.updateSessions([update.session]); + } else if (update.activesessionid != null) { + this.updateActiveSession(update.activesessionid); + } else if (update.line != null) { + this.addLineCmd(update.line.line, update.line.cmd, interactive); + } else if (update.cmd != null) { + this.updateCmd(update.cmd); + } else if (update.screenlines != null) { + this.updateScreenLines(update.screenlines, false); + } else if (update.remote != null) { + this.updateRemotes([update.remote]); + // This code's purpose is to show view remote connection modal when a new connection is added + if (!showedRemotesModal && this.remotesModel.recentConnAddedState.get()) { + showedRemotesModal = true; + this.remotesModel.openReadModal(update.remote.remoteid); + } + } else if (update.mainview != null) { + switch (update.mainview.mainview) { + case "session": + this.activeMainView.set("session"); + break; + case "history": + if (update.mainview.historyview != null) { + this.historyViewModel.showHistoryView(update.mainview.historyview); + } else { + console.warn("invalid historyview in update:", update.mainview); + } + break; + case "bookmarks": + if (update.mainview.bookmarksview != null) { + this.bookmarksModel.showBookmarksView( + update.mainview.bookmarksview?.bookmarks ?? [], + update.mainview.bookmarksview?.selectedbookmark + ); + } else { + console.warn("invalid bookmarksview in update:", update.mainview); + } + break; + case "plugins": + this.pluginsModel.showPluginsView(); + break; + default: + console.warn("invalid mainview in update:", update.mainview); + } + } else if (update.bookmarks != null) { + if (update.bookmarks.bookmarks != null) { + this.bookmarksModel.mergeBookmarks(update.bookmarks.bookmarks); + } + } else if (update.clientdata != null) { + this.setClientData(update.clientdata); + } else if (update.cmdline != null) { + this.inputModel.updateCmdLine(update.cmdline); + } else if (update.openaicmdinfochat != null) { + this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat); + } else if (update.screenstatusindicator != null) { + this.updateScreenStatusIndicators([update.screenstatusindicator]); + } else if (update.screennumrunningcommands != null) { + this.updateScreenNumRunningCommands([update.screennumrunningcommands]); + } else if (update.userinputrequest != null) { + const userInputRequest: UserInputRequest = update.userinputrequest; + this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest); + } else if (interactive) { + if (update.info != null) { + const info: InfoType = update.info; + this.inputModel.flashInfoMsg(info, info.timeoutms); + } else if (update.remoteview != null) { + const rview: RemoteViewType = update.remoteview; + if (rview.remoteedit != null) { + this.remotesModel.openEditModal({ ...rview.remoteedit }); + } + } else if (update.alertmessage != null) { + const alertMessage: AlertMessageType = update.alertmessage; + this.showAlert(alertMessage); + } else if (update.history != null) { + if ( + uiContext.sessionid == update.history.sessionid && + uiContext.screenid == update.history.screenid + ) { + this.inputModel.setHistoryInfo(update.history); + } + } else if (this.isDev) { + console.log("did not match update", update); } } else if (this.isDev) { console.log("did not match update", update); } - } else if (this.isDev) { - console.log("did not match update", update); + }); + + // Check if the active session or screen has changed, and if so, watch the new screen + const [newActiveSessionId, newActiveScreenId] = this.getActiveIds(); + if (oldActiveSessionId != newActiveSessionId || oldActiveScreenId != newActiveScreenId) { + this.activeMainView.set("session"); + this.deactivateScreenLines(); + this.ws.watchScreen(newActiveSessionId, newActiveScreenId); } - }); + } else { + console.warn("unknown update", genUpdate); + } } updateRemotes(remotes: RemoteType[]): void { @@ -1064,11 +1070,13 @@ class Model { this.handleCmdRestart(cmd); } - isInfoUpdate(update: UpdateMessage): boolean { - if (update == null || "ptydata64" in update) { + isInfoUpdate(update: UpdatePacket): boolean { + if (update.type == "model") { + const modelUpdate = update as ModelUpdatePacket; + return modelUpdate.data.some((u) => u.info != null || u.history != null); + } else { return false; } - return update.some((u) => u.info != null || u.history != null); } getClientDataLoop(loopNum: number): void { diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 5d5922a4..500746c2 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -338,6 +338,10 @@ declare global { }; type ModelUpdateType = { + items?: ModelUpdateItemType[]; + }; + + type ModelUpdateItemType = { interactive: boolean; session?: SessionDataType; activesessionid?: string; @@ -440,7 +444,17 @@ declare global { showCut?: boolean; }; - type UpdateMessage = PtyDataUpdateType | ModelUpdateType[]; + type ModelUpdatePacket = { + type: "model"; + data: ModelUpdateItemType[]; + }; + + type PtyDataUpdatePacket = { + type: "pty"; + data: PtyDataUpdateType; + }; + + type UpdatePacket = ModelUpdatePacket | PtyDataUpdatePacket; type RendererContext = { screenId: string; diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 39449e26..6f2da605 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -40,6 +40,7 @@ import ( "github.com/wavetermdev/waveterm/wavesrv/pkg/remote" "github.com/wavetermdev/waveterm/wavesrv/pkg/remote/openai" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" "golang.org/x/mod/semver" @@ -147,7 +148,7 @@ type historyContextType struct { InitialStatus string } -type MetaCmdFnType = func(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) +type MetaCmdFnType = func(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) type MetaCmdEntryType struct { IsAlias bool Fn MetaCmdFnType @@ -299,7 +300,7 @@ func GetCmdStr(pk *scpacket.FeCommandPacketType) string { return pk.MetaCmd + ":" + pk.MetaSubCmd } -func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { metaCmd := SubMetaCmd(pk.MetaCmd) var cmdName string if pk.MetaSubCmd == "" { @@ -483,7 +484,7 @@ func getEvalDepth(ctx context.Context) int { return depthVal.(int) } -func SyncCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SyncCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected) if err != nil { return nil, fmt.Errorf("/run error: %w", err) @@ -516,8 +517,8 @@ func SyncCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. if err != nil { return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) - sstore.MainBus.SendScreenUpdate(ids.ScreenId, update) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) + scbus.MainUpdateBus.DoScreenUpdate(ids.ScreenId, update) return nil, nil } @@ -553,7 +554,7 @@ func getLangArg(pk *scpacket.FeCommandPacketType) (string, error) { return pk.Kwargs[KwArgLang], nil } -func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected) if err != nil { return nil, fmt.Errorf("/run error: %w", err) @@ -624,11 +625,11 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U if err != nil { return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) // this update is sent asynchronously for timing issues. the cmd update comes async as well // so if we return this directly it sometimes gets evaluated first. by pushing it on the MainBus // it ensures it happens after the command creation event. - sstore.MainBus.SendScreenUpdate(ids.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(ids.ScreenId, update) return nil, nil } @@ -682,7 +683,7 @@ func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, history return nil } -func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("usage: /eval [command], no command passed to eval") } @@ -698,7 +699,7 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. } var historyContext historyContextType ctxWithHistory := context.WithValue(ctx, historyContextKey, &historyContext) - var update sstore.UpdatePacket + var update scbus.UpdatePacket newPk, rtnErr := EvalMetaCommand(ctxWithHistory, pk) if rtnErr == nil { update, rtnErr = HandleCommand(ctxWithHistory, newPk) @@ -714,12 +715,12 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. } } var hasModelUpdate bool - var modelUpdate *sstore.ModelUpdate + var modelUpdate *scbus.ModelUpdatePacketType if update == nil { hasModelUpdate = true - modelUpdate = &sstore.ModelUpdate{} + modelUpdate = scbus.MakeUpdatePacket() update = modelUpdate - } else if mu, ok := update.(*sstore.ModelUpdate); ok { + } else if mu, ok := update.(*scbus.ModelUpdatePacketType); ok { hasModelUpdate = true modelUpdate = mu } @@ -739,7 +740,7 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. return update, rtnErr } -func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) // don't force R_Screen if err != nil { return nil, fmt.Errorf("/screen:archive cannot archive screen: %w", err) @@ -759,7 +760,7 @@ func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if len(pk.Args) > 1 { archiveVal = resolveBool(pk.Args[1], true) } - var update sstore.UpdatePacket + var update scbus.UpdatePacket if archiveVal { update, err = sstore.ArchiveScreen(ctx, ids.SessionId, screenId) if err != nil { @@ -776,13 +777,13 @@ func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen) return update, nil } } -func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) // don't force R_Screen if err != nil { return nil, fmt.Errorf("/screen:delete cannot delete screen: %w", err) @@ -805,7 +806,7 @@ func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return update, nil } -func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, fmt.Errorf("/screen:open cannot open screen: %w", err) @@ -825,7 +826,7 @@ func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return update, nil } -func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { // Resolve the UI IDs for the session and screen ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { @@ -853,11 +854,11 @@ func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } // Prepare the update packet to send back to the client - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() for _, screen := range screens { - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) } - sstore.AddUpdate(update, sstore.InfoMsgType{ + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: "screen indices updated successfully", TimeoutMs: 2000, }) @@ -867,7 +868,7 @@ func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) var screenAnchorRe = regexp.MustCompile("^(\\d+)(?::(-?\\d+))?$") -func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -970,16 +971,15 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return nil, nil } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen, sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)), TimeoutMs: 2000, }) return update, nil } -func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, fmt.Errorf("/screen cannot switch to screen: %w", err) @@ -1035,7 +1035,7 @@ func sidebarSetOpen(ctx context.Context, cmdStr string, screenId string, open bo return screen, nil } -func SidebarOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SidebarOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Screen) if err != nil { return nil, err @@ -1044,12 +1044,12 @@ func SidebarOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, err } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen) return update, nil } -func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Screen) if err != nil { return nil, err @@ -1058,12 +1058,12 @@ func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, err } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen) return update, nil } -func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Screen) if err != nil { return nil, err @@ -1088,12 +1088,12 @@ func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s if err != nil { return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen) return update, nil } -func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Screen) if err != nil { return nil, err @@ -1112,22 +1112,22 @@ func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen) return update, nil } -func createRemoteViewRemoteIdUpdate(remoteId string) sstore.UpdatePacket { - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.RemoteViewType{ +func createRemoteViewRemoteIdUpdate(remoteId string) scbus.UpdatePacket { + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.RemoteViewType{ PtyRemoteId: remoteId, }) return update } -func createRemoteViewRemoteEditUpdate(redit *sstore.RemoteEditType) sstore.UpdatePacket { - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.RemoteViewType{ +func createRemoteViewRemoteEditUpdate(redit *sstore.RemoteEditType) scbus.UpdatePacket { + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.RemoteViewType{ RemoteEdit: redit, }) return update @@ -1175,7 +1175,7 @@ func deferWriteCmdStatus(ctx context.Context, cmd *sstore.CmdType, startTime tim log.Printf("error updating cmddoneinfo (in openai): %v\n", err) return } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) } func checkForWriteReady(ctx context.Context, iter *packet.RpcResponseIter) (string, error) { @@ -1523,7 +1523,7 @@ func writeStringToPty(ctx context.Context, cmd *sstore.CmdType, outputString str if err != nil { log.Printf("error writing to pty: %v", err) } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) err = sstore.SetStatusIndicatorLevel(ctx, cmd.ScreenId, sstore.StatusIndicatorLevel_Output, false) if err != nil { // This is not a fatal error, so just log it @@ -1544,7 +1544,7 @@ func parseCopyFileParam(info string) (remote string, path string, err error) { } } -func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("usage: /copyfile [file to copy] local=[path to copy to on local]") } @@ -1647,7 +1647,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) if destRemote != ConnectedRemote && destRemoteId != nil && !destRemoteId.RState.IsConnected() { writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", destRemote), &outputPos) err = destRemoteId.MShell.TryAutoConnect() @@ -1666,8 +1666,8 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst writeStringToPty(ctx, cmd, "Auto connect successful\r\n", &outputPos) } } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) - update = &sstore.ModelUpdate{} + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) + update = scbus.MakeUpdatePacket() if destRemote == LocalRemote && sourceRemote == LocalRemote { go doCopyLocalFileToLocal(context.Background(), cmd, sourceFullPath, destFullPath, outputPos) } else if destRemote == LocalRemote && sourceRemote != LocalRemote { @@ -1680,7 +1680,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst return update, nil } -func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -1690,7 +1690,7 @@ func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } -func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -1700,7 +1700,7 @@ func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacke return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } -func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -1709,7 +1709,7 @@ func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } -func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -1719,7 +1719,7 @@ func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } -func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket { +func makeRemoteEditUpdate_new(err error) scbus.UpdatePacket { redit := &sstore.RemoteEditType{ RemoteEdit: true, } @@ -1729,14 +1729,14 @@ func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket { return createRemoteViewRemoteEditUpdate(redit) } -func makeRemoteEditErrorReturn_new(visual bool, err error) (sstore.UpdatePacket, error) { +func makeRemoteEditErrorReturn_new(visual bool, err error) (scbus.UpdatePacket, error) { if visual { return makeRemoteEditUpdate_new(err), nil } return nil, err } -func makeRemoteEditUpdate_edit(ids resolvedIds, err error) sstore.UpdatePacket { +func makeRemoteEditUpdate_edit(ids resolvedIds, err error) scbus.UpdatePacket { redit := &sstore.RemoteEditType{ RemoteEdit: true, } @@ -1751,7 +1751,7 @@ func makeRemoteEditUpdate_edit(ids resolvedIds, err error) sstore.UpdatePacket { return createRemoteViewRemoteEditUpdate(redit) } -func makeRemoteEditErrorReturn_edit(ids resolvedIds, visual bool, err error) (sstore.UpdatePacket, error) { +func makeRemoteEditErrorReturn_edit(ids resolvedIds, visual bool, err error) (scbus.UpdatePacket, error) { if visual { return makeRemoteEditUpdate_edit(ids, err), nil } @@ -1929,7 +1929,7 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b }, nil } -func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { visualEdit := resolveBool(pk.Kwargs["visual"], false) isSubmitted := resolveBool(pk.Kwargs["submit"], false) if visualEdit && !isSubmitted && len(pk.Args) == 0 { @@ -1963,7 +1963,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return createRemoteViewRemoteIdUpdate(r.RemoteId), nil } -func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -1987,15 +1987,15 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if visualEdit { return createRemoteViewRemoteIdUpdate(ids.Remote.RemoteCopy.RemoteId), nil } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("remote %q updated", ids.Remote.DisplayName), TimeoutMs: 2000, }) return update, nil } -func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -2004,7 +2004,7 @@ func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return createRemoteViewRemoteIdUpdate(state.RemoteId), nil } -func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { stateArr := remote.GetAllRemoteRuntimeState() var buf bytes.Buffer for _, rstate := range stateArr { @@ -2016,8 +2016,8 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } buf.WriteString(fmt.Sprintf("%-12s %-5s %8s %s\n", rstate.Status, rstate.RemoteType, rstate.RemoteId[0:8], name)) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.RemoteViewType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.RemoteViewType{ RemoteShowAll: true, }) return update, nil @@ -2193,7 +2193,7 @@ func NewHostInfo(hostName string) (*HostInfoType, error) { return outHostInfo, nil } -func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { home := base.GetHomeDir() localConfig := filepath.Join(home, ".ssh", "config") systemConfig := filepath.Join("/", "ssh", "config") @@ -2315,23 +2315,23 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT outMsg := createSshImportSummary(remoteChangeList) visualEdit := resolveBool(pk.Kwargs["visual"], false) if visualEdit { - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.AlertMessageType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.AlertMessageType{ Title: "SSH Config Import", Message: outMsg, Markdown: true, }) return update, nil } else { - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: outMsg, }) return update, nil } } -func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) screenArr, err := sstore.GetSessionScreens(ctx, ids.SessionId) if err != nil { @@ -2350,15 +2350,15 @@ func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+archivedStr, screen.ScreenId, screenIdxStr) buf.WriteString(outStr) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("all screens for session"), InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -2389,12 +2389,11 @@ func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) - sstore.AddUpdate(update, sessionUpdate) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive), sessionUpdate) return update, nil } -func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -2414,15 +2413,15 @@ func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("cannot get updated screen: %w", err) } - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) return update, nil } -func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { return nil, fmt.Errorf("/remote requires a subcommand: %s", formatStrs([]string{"show"}, "or", false)) } -func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids resolvedIds) (sstore.UpdatePacket, error) { +func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids resolvedIds) (scbus.UpdatePacket, error) { var buf bytes.Buffer riArr, err := sstore.GetRIsForScreen(ctx, ids.SessionId, ids.ScreenId) if err != nil { @@ -2463,8 +2462,8 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re } buf.WriteString(fmt.Sprintf("%-30s %-50s (default)\n", msh.GetDisplayName(), cwdStr)) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoLines: splitLinesForInfo(buf.String()), }) return update, nil @@ -2502,7 +2501,7 @@ func writeErrorToPty(cmd *sstore.CmdType, errStr string, outputPos int64) { log.Printf("error writing ptyupdate for openai response: %v\n", err) return } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) return } @@ -2516,7 +2515,7 @@ func writePacketToPty(ctx context.Context, cmd *sstore.CmdType, pk packet.Packet return err } *outputPos += int64(len(outBytes)) - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) return nil } @@ -2552,7 +2551,7 @@ func doOpenAICompletion(cmd *sstore.CmdType, opts *sstore.OpenAIOptsType, prompt log.Printf("error updating cmddoneinfo (in openai): %v\n", err) return } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) }() var respPks []*packet.OpenAIPacketType var err error @@ -2573,11 +2572,8 @@ func doOpenAICompletion(cmd *sstore.CmdType, opts *sstore.OpenAIOptsType, prompt } func writePacketToUpdateBus(ctx context.Context, cmd *sstore.CmdType, pk *packet.OpenAICmdInfoChatMessage) { - update, err := sstore.UpdateWithAddNewOpenAICmdInfoPacket(ctx, cmd.ScreenId, pk) - if err != nil { - log.Printf("Open AI Update packet err: %v\n", err) - } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + update := sstore.UpdateWithAddNewOpenAICmdInfoPacket(ctx, cmd.ScreenId, pk) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) } func updateAsstResponseAndWriteToUpdateBus(ctx context.Context, cmd *sstore.CmdType, pk *packet.OpenAICmdInfoChatMessage, messageID int) { @@ -2585,7 +2581,7 @@ func updateAsstResponseAndWriteToUpdateBus(ctx context.Context, cmd *sstore.CmdT if err != nil { log.Printf("Open AI Update packet err: %v\n", err) } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) } func getCmdInfoEngineeredPrompt(userQuery string, curLineStr string) string { @@ -2712,7 +2708,7 @@ func doOpenAIStreamCompletion(cmd *sstore.CmdType, clientId string, opts *sstore log.Printf("error updating cmddoneinfo (in openai): %v\n", err) return } - sstore.MainBus.SendScreenUpdate(cmd.ScreenId, update) + scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update) }() var ch chan *packet.OpenAIPacketType var err error @@ -2782,7 +2778,7 @@ func BuildOpenAIPromptArrayWithContext(messages []*packet.OpenAICmdInfoChatMessa return rtn } -func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, fmt.Errorf("/%s error: %w", GetCmdStr(pk), err) @@ -2820,7 +2816,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if resolveBool(pk.Kwargs["cmdinfo"], false) { if promptStr == "" { // this is requesting an update without wanting an openai query - update, err := sstore.UpdateWithCurrentOpenAICmdInfoChat(cmd.ScreenId, nil) + update := sstore.UpdateWithCurrentOpenAICmdInfoChat(cmd.ScreenId, nil) if err != nil { return nil, fmt.Errorf("error getting update for CmdInfoChat %v", err) } @@ -2833,12 +2829,12 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor writePacketToUpdateBus(ctx, cmd, userQueryPk) prompt := BuildOpenAIPromptArrayWithContext(sstore.ScreenMemGetCmdInfoChat(cmd.ScreenId).Messages) go doOpenAICmdInfoCompletion(cmd, clientData.ClientId, opts, prompt, curLineStr) - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() return update, nil } prompt := []packet.OpenAIPromptMessageType{{Role: sstore.OpenAIRoleUser, Content: promptStr}} if resolveBool(pk.Kwargs["cmdinfoclear"], false) { - update, err := sstore.UpdateWithClearOpenAICmdInfo(cmd.ScreenId) + update := sstore.UpdateWithClearOpenAICmdInfo(cmd.ScreenId) if err != nil { return nil, fmt.Errorf("error clearing CmdInfoChat: %v", err) } @@ -2865,13 +2861,13 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor // ignore error again (nothing to do) log.Printf("openai error updating screen selected line: %v\n", err) } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, line, cmd) - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) return update, nil } -func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, fmt.Errorf("/%s error: %w", GetCmdStr(pk), err) @@ -2900,9 +2896,8 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up if err != nil { return nil, fmt.Errorf("/%s error: cannot resolve screen for update: %w", GetCmdStr(pk), err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) - sstore.AddInteractiveUpdate(update, pk.Interactive) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen, sstore.InteractiveUpdate(pk.Interactive)) return update, nil } outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate)) @@ -2916,7 +2911,7 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) return update, nil } @@ -2976,7 +2971,7 @@ func makeStaticCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr return cmd, nil } -func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids resolvedIds, cmd *sstore.CmdType, renderer string, lineState map[string]any) (*sstore.ModelUpdate, error) { +func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids resolvedIds, cmd *sstore.CmdType, renderer string, lineState map[string]any) (*scbus.ModelUpdatePacketType, error) { rtnLine, err := sstore.AddCmdLine(ctx, ids.ScreenId, DefaultUserId, cmd, renderer, lineState) if err != nil { return nil, err @@ -2998,9 +2993,9 @@ func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids re log.Printf("%s error updating screen selected line: %v\n", metaCmd, err) } } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, rtnLine, cmd) - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) sstore.IncrementNumRunningCmds_Update(update, cmd.ScreenId, 1) updateHistoryContext(ctx, rtnLine, cmd, cmd.FeState) return update, nil @@ -3025,7 +3020,7 @@ func updateHistoryContext(ctx context.Context, line *sstore.LineType, cmd *sstor hctx.FeState = feState } -func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.UpdatePacket { +func makeInfoFromComps(compType string, comps []string, hasMore bool) scbus.UpdatePacket { sort.Slice(comps, func(i int, j int) bool { c1 := comps[i] c2 := comps[j] @@ -3042,8 +3037,8 @@ func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.Upd if len(comps) == 0 { comps = []string{"(no completions)"} } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("%s completions", compType), InfoComps: comps, InfoCompsMore: hasMore, @@ -3138,7 +3133,7 @@ func doCompGen(ctx context.Context, pk *scpacket.FeCommandPacketType, prefix str return comps, hasMore, nil } -func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, 0) // best-effort if err != nil { return nil, fmt.Errorf("/_compgen error: %w", err) @@ -3183,12 +3178,12 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if newSP == nil || cmdSP == *newSP { return nil, nil } - update := &sstore.ModelUpdate{} - sstore.AddCmdLineUpdate(update, utilfn.StrWithPos{Str: newSP.Str, Pos: newSP.Pos}) + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.CmdLineUpdate(utilfn.StrWithPos{Str: newSP.Str, Pos: newSP.Pos})) return update, nil } -func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, fmt.Errorf("/comment error: %w", err) @@ -3210,9 +3205,9 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto // ignore error again (nothing to do) log.Printf("/comment error updating screen selected line: %v\n", err) } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, rtnLine, nil) - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) return update, nil } @@ -3307,13 +3302,13 @@ func validateRemoteColor(color string, typeStr string) error { return fmt.Errorf("invalid %s, valid colors are: %s", typeStr, formatStrs(RemoteColorNames, "or", false)) } -func SessionOpenSharedCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionOpenSharedCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { activity := sstore.ActivityUpdate{ClickShared: 1} sstore.UpdateActivityWrap(ctx, activity, "click-shared") return nil, fmt.Errorf("shared sessions are not available in this version of prompt (stay tuned)") } -func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { activate := resolveBool(pk.Kwargs["activate"], true) newName := pk.Kwargs["name"] if newName != "" { @@ -3333,11 +3328,11 @@ func makeExternLink(urlStr string) string { return fmt.Sprintf(`https://extern?%s`, url.QueryEscape(urlStr)) } -func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { return nil, fmt.Errorf("websharing is no longer available") } -func SessionDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, 0) // don't force R_Session if err != nil { return nil, err @@ -3365,7 +3360,7 @@ func SessionDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return update, nil } -func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, 0) // don't force R_Session if err != nil { return nil, err @@ -3395,7 +3390,7 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("cannot archive session: %v", err) } - sstore.AddUpdate(update, sstore.InfoMsgType{ + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: "session archived", }) return update, nil @@ -3405,14 +3400,14 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("cannot un-archive session: %v", err) } - sstore.AddUpdate(update, sstore.InfoMsgType{ + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: "session un-archived", }) return update, nil } } -func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, err @@ -3448,15 +3443,15 @@ func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( buf.WriteString(fmt.Sprintf(" %-15s %d\n", "cmds", stats.NumCmds)) buf.WriteString(fmt.Sprintf(" %-15s %0.2fM\n", "disksize", float64(stats.DiskStats.TotalSize)/1000000)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "disk-location", stats.DiskStats.Location)) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: "session info", InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { sessions, err := sstore.GetBareSessions(ctx) if err != nil { return nil, fmt.Errorf("error retrieving sessions: %v", err) @@ -3474,15 +3469,15 @@ func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType outStr := fmt.Sprintf("%-30s %s %s\n", session.Name+archivedStr, session.SessionId, sessionIdxStr) buf.WriteString(outStr) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: "all sessions", InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session) if err != nil { return nil, err @@ -3504,16 +3499,15 @@ func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false)) } bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *bareSession) - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(*bareSession, sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("session updated %s", formatStrs(varsUpdated, "and", false)), TimeoutMs: 2000, }) return update, nil } -func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, 0) if err != nil { return nil, err @@ -3530,9 +3524,9 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, (sstore.ActiveSessionIdUpdate)(ritem.Id)) - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.ActiveSessionIdUpdate(ritem.Id)) + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name), TimeoutMs: 2000, }) @@ -3554,7 +3548,7 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto return update, nil } -func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -3590,12 +3584,11 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) - sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(ids.SessionId, remoteInst)) + update.AddUpdate(sstore.MakeSessionUpdateForRemote(ids.SessionId, remoteInst), sstore.InteractiveUpdate(pk.Interactive)) return update, nil } -func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -3605,7 +3598,7 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore if err != nil { return nil, fmt.Errorf("clearing screen (archiving): %v", err) } - sstore.AddUpdate(update, sstore.InfoMsgType{ + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen cleared (all lines archived)"), TimeoutMs: 2000, }) @@ -3615,7 +3608,7 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore if err != nil { return nil, fmt.Errorf("clearing screen: %v", err) } - sstore.AddUpdate(update, sstore.InfoMsgType{ + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen cleared"), TimeoutMs: 2000, }) @@ -3624,7 +3617,7 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore } -func HistoryPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func HistoryPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("/history:purge requires at least one argument (history id)") } @@ -3662,7 +3655,7 @@ func historyCmdFilter(hitem *sstore.HistoryItemType) bool { return true } -func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { _, err := resolveUiIds(ctx, pk, 0) if err != nil { return nil, err @@ -3730,14 +3723,14 @@ func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType } hvdata.Lines = lines hvdata.Cmds = cmds - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, &sstore.MainViewUpdate{MainView: sstore.MainViewHistory, HistoryView: hvdata}) + update := scbus.MakeUpdatePacket() + update.AddUpdate(&sstore.MainViewUpdate{MainView: sstore.MainViewHistory, HistoryView: hvdata}) return update, nil } const DefaultMaxHistoryItems = 10000 -func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -3776,8 +3769,8 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if show { sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{HistoryView: 1}, "history") } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.HistoryInfoType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.HistoryInfoType{ HistoryType: htype, SessionId: ids.SessionId, ScreenId: ids.ScreenId, @@ -3816,7 +3809,7 @@ func resizeRunningCommand(ctx context.Context, cmd *sstore.CmdType, newCols int) return nil } -func ScreenResizeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ScreenResizeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -3856,11 +3849,11 @@ func ScreenResizeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, nil } -func LineCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { return nil, fmt.Errorf("/line requires a subcommand: %s", formatStrs([]string{"show", "star", "hide", "delete", "setheight", "set"}, "or", false)) } -func LineSetHeightCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineSetHeightCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -3888,7 +3881,7 @@ func LineSetHeightCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, nil } -func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected) if err != nil { return nil, err @@ -3970,16 +3963,16 @@ func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, fmt.Errorf("error getting updated line/cmd: %w", err) } cmd.Restarted = true - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, line, cmd) - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) screen, focusErr := focusScreenLine(ctx, ids.ScreenId, line.LineNum) if focusErr != nil { // not a fatal error, so just log log.Printf("error focusing screen line: %v\n", focusErr) } if screen != nil { - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) } return update, nil } @@ -4002,7 +3995,7 @@ func focusScreenLine(ctx context.Context, screenId string, lineNum int64) (*ssto return screen, nil } -func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4058,16 +4051,16 @@ func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, fmt.Errorf("/line:set cannot retrieve updated line: %v", err) } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, updatedLine, nil) - sstore.AddUpdate(update, sstore.InfoMsgType{ + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("line updated %s", formatStrs(varsUpdated, "and", false)), TimeoutMs: 2000, }) return update, nil } -func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) != 3 { return nil, fmt.Errorf("usage /line:view [session] [screen] [line]") } @@ -4109,12 +4102,12 @@ func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if err != nil { return nil, err } - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) } return update, nil } -func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { // no resolve ui ids! var tagName string // defaults to '' if len(pk.Args) > 0 { @@ -4125,16 +4118,16 @@ func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("cannot retrieve bookmarks: %v", err) } sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{BookmarksView: 1}, "bookmarks") - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() - sstore.AddUpdate(update, &sstore.MainViewUpdate{ + update.AddUpdate(&sstore.MainViewUpdate{ MainView: sstore.MainViewBookmarks, BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms}, }) return update, nil } -func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("/bookmark:set requires one argument (bookmark id)") } @@ -4165,13 +4158,13 @@ func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, fmt.Errorf("error retrieving edited bookmark: %v", err) } bms := []*sstore.BookmarkType{bm} - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddBookmarksUpdate(update, bms, nil) - sstore.AddUpdate(update, sstore.InfoMsgUpdate("bookmark edited")) + update.AddUpdate(sstore.InfoMsgUpdate("bookmark edited")) return update, nil } -func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("/bookmark:delete requires one argument (bookmark id)") } @@ -4187,14 +4180,14 @@ func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("error deleting bookmark: %v", err) } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() bms := []*sstore.BookmarkType{{BookmarkId: bookmarkId, Remove: true}} sstore.AddBookmarksUpdate(update, bms, nil) - sstore.AddUpdate(update, sstore.InfoMsgUpdate("bookmark deleted")) + update.AddUpdate(sstore.InfoMsgUpdate("bookmark deleted")) return update, nil } -func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4240,19 +4233,19 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) newBmId = newBm.BookmarkId } bms, err := sstore.GetBookmarks(ctx, "") - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, &sstore.MainViewUpdate{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(&sstore.MainViewUpdate{ MainView: sstore.MainViewBookmarks, BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms, SelectedBookmark: newBmId}, }) return update, nil } -func LinePinCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LinePinCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { return nil, nil } -func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4290,12 +4283,12 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // no line (which is strange given we checked for it above). just return a nop. return nil, nil } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, lineObj, nil) return update, nil } -func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4327,12 +4320,12 @@ func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // no line (which is strange given we checked for it above). just return a nop. return nil, nil } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() sstore.AddLineUpdate(update, lineObj, nil) return update, nil } -func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4355,7 +4348,7 @@ func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s if err != nil { return nil, fmt.Errorf("/line:delete error deleting lines: %v", err) } - update := &sstore.ModelUpdate{} + update := scbus.MakeUpdatePacket() for _, lineId := range lineIds { line := &sstore.LineType{ScreenId: ids.ScreenId, LineId: lineId, Remove: true} sstore.AddLineUpdate(update, line, nil) @@ -4365,12 +4358,12 @@ func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, fmt.Errorf("/line:delete error fixing up screen: %v", err) } if screen != nil { - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) } return update, nil } -func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4449,15 +4442,15 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst stateStr = stateStr[0:77] + "..." } buf.WriteString(fmt.Sprintf(" %-15s %s\n", "state", stateStr)) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("line %d info", line.LineNum), InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func SetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { var setMap map[string]map[string]string setMap = make(map[string]map[string]string) _, err := resolveUiIds(ctx, pk, 0) // best effort @@ -4499,7 +4492,7 @@ func makeStreamFilePk(ids resolvedIds, pk *scpacket.FeCommandPacketType) (*packe return streamPk, nil } -func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("/view:stat requires an argument (file name)") } @@ -4543,15 +4536,15 @@ func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst modeStr = modeStr[len(modeStr)-9:] } buf.WriteString(fmt.Sprintf(" %-15s %s\n", "perms", modeStr)) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("view stat %q", streamPk.Path), InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("/view:test requires an argument (file name)") } @@ -4604,15 +4597,15 @@ func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst buf.Write(dataPk.Data) } buf.WriteString(fmt.Sprintf("\n\ntotal packets: %d\n", numPackets)) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("view file %q", streamPk.Path), InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func CodeEditCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func CodeEditCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk)) } @@ -4651,11 +4644,11 @@ func CodeEditCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) return update, nil } -func CSVViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func CSVViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk)) } @@ -4682,11 +4675,11 @@ func CSVViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) return update, nil } -func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk)) } @@ -4713,11 +4706,11 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) return update, nil } -func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk)) } @@ -4744,11 +4737,11 @@ func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) // TODO tricky error since the command was a success, but we can't show the output return nil, err } - sstore.AddInteractiveUpdate(update, pk.Interactive) + update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive)) return update, nil } -func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { if len(pk.Args) == 0 { return nil, fmt.Errorf("/edit:test requires an argument (file name)") } @@ -4808,14 +4801,14 @@ func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if donePk.Error != "" { return nil, fmt.Errorf("/edit:test %s", donePk.Error) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("edit test, wrote %q", writePk.Path), }) return update, nil } -func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen) if err != nil { return nil, err @@ -4874,12 +4867,12 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if err != nil { return nil, fmt.Errorf("cannot send signal: %v", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgUpdate("sent line %s signal %s", lineArg, sigArg)) + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgUpdate("sent line %s signal %s", lineArg, sigArg)) return update, nil } -func KillServerCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func KillServerCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { go func() { log.Printf("received /killserver, shutting down\n") time.Sleep(1 * time.Second) @@ -4888,7 +4881,7 @@ func KillServerCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, nil } -func DumpStateCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func DumpStateCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Remote) if err != nil { return nil, err @@ -4902,15 +4895,15 @@ func DumpStateCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return sstore.InfoMsgUpdate("current connection state sent to log. festate: %s", dbutil.QuickJson(feState)), nil } -func ClientCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { return nil, fmt.Errorf("/client requires a subcommand: %s", formatStrs([]string{"show", "set"}, "or", false)) } -func ClientNotifyUpdateWriterCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientNotifyUpdateWriterCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { pcloud.ResetUpdateWriterNumFailures() sstore.NotifyUpdateWriter() - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgUpdate("notified update writer")) + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgUpdate("notified update writer")) return update, nil } @@ -4921,7 +4914,7 @@ func boolToStr(v bool, trueStr string, falseStr string) string { return falseStr } -func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -4936,15 +4929,15 @@ func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *clientData) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*clientData) return update, nil } var confirmKeyRe = regexp.MustCompile(`^[a-z][a-z0-9_]*$`) // confirm flags must be all lowercase and only contain letters, numbers, and underscores (and start with letter) -func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { // Check for valid arguments length if len(pk.Args) < 2 { return nil, fmt.Errorf("invalid arguments: expected at least 2, got %d", len(pk.Args)) @@ -4985,13 +4978,13 @@ func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketT return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *clientData) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*clientData) return update, nil } -func ClientSetGlobalShortcut(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientSetGlobalShortcut(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5008,12 +5001,12 @@ func ClientSetGlobalShortcut(ctx context.Context, pk *scpacket.FeCommandPacketTy return nil, fmt.Errorf("error updating client data: %v", err) } clientData.ClientOpts = clientOpts - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *clientData) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*clientData) return update, nil } -func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5062,8 +5055,8 @@ func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *clientData) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*clientData) return update, nil } @@ -5095,7 +5088,7 @@ func validateOpenAIModel(model string) error { return nil } -func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5211,16 +5204,16 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *clientData) - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(*clientData) + update.AddUpdate(sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)), TimeoutMs: 2000, }) return update, nil } -func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5242,8 +5235,8 @@ func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s buf.WriteString(fmt.Sprintf(" %-15s %s\n", "client-version", clientVersion)) buf.WriteString(fmt.Sprintf(" %-15s %s %s\n", "server-version", scbase.WaveVersion, scbase.BuildTime)) buf.WriteString(fmt.Sprintf(" %-15s %s (%s)\n", "arch", scbase.ClientArch(), scbase.UnameKernelRelease())) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("client info"), InfoLines: splitLinesForInfo(buf.String()), }) @@ -5251,7 +5244,7 @@ func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return update, nil } -func TelemetryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func TelemetryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { return nil, fmt.Errorf("/telemetry requires a subcommand: %s", formatStrs([]string{"show", "on", "off", "send"}, "or", false)) } @@ -5275,7 +5268,7 @@ func setNoTelemetry(ctx context.Context, clientData *sstore.ClientData, noTeleme return nil } -func TelemetryOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func TelemetryOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5301,11 +5294,11 @@ func TelemetryOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("telemetry is now on") - sstore.AddUpdate(update, *clientData) + update.AddUpdate(*clientData) return update, nil } -func TelemetryOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func TelemetryOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5322,26 +5315,26 @@ func TelemetryOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("telemetry is now off") - sstore.AddUpdate(update, *clientData) + update.AddUpdate(*clientData) return update, nil } -func TelemetryShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func TelemetryShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) } var buf bytes.Buffer buf.WriteString(fmt.Sprintf(" %-15s %s\n", "telemetry", boolToStr(clientData.ClientOpts.NoTelemetry, "off", "on"))) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.InfoMsgType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("telemetry info"), InfoLines: splitLinesForInfo(buf.String()), }) return update, nil } -func TelemetrySendCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func TelemetrySendCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5382,7 +5375,7 @@ func setNoReleaseCheck(ctx context.Context, clientData *sstore.ClientData, noRel return nil } -func ReleaseCheckOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ReleaseCheckOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5409,11 +5402,11 @@ func ReleaseCheckOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("automatic release checking is now on") - sstore.AddUpdate(update, *clientData) + update.AddUpdate(*clientData) return update, nil } -func ReleaseCheckOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ReleaseCheckOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve client data: %v", err) @@ -5430,11 +5423,11 @@ func ReleaseCheckOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("automatic release checking is now off") - sstore.AddUpdate(update, *clientData) + update.AddUpdate(*clientData) return update, nil } -func ReleaseCheckCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { +func ReleaseCheckCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { err := runReleaseCheck(ctx, true) if err != nil { return nil, err @@ -5453,7 +5446,7 @@ func ReleaseCheckCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } update := sstore.InfoMsgUpdate(rsp) - sstore.AddUpdate(update, *clientData) + update.AddUpdate(*clientData) return update, nil } diff --git a/wavesrv/pkg/releasechecker/releasechecker.go b/wavesrv/pkg/releasechecker/releasechecker.go index 6d13feaa..0e3db34b 100644 --- a/wavesrv/pkg/releasechecker/releasechecker.go +++ b/wavesrv/pkg/releasechecker/releasechecker.go @@ -1,3 +1,6 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package releasechecker import ( @@ -8,6 +11,7 @@ import ( "golang.org/x/mod/semver" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" ) @@ -66,9 +70,9 @@ func CheckNewRelease(ctx context.Context, force bool) (ReleaseCheckResult, error return Failure, fmt.Errorf("error getting updated client data: %w", err) } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *clientData) - sstore.MainBus.SendUpdate(update) + update := scbus.MakeUpdatePacket() + update.AddUpdate(clientData) + scbus.MainUpdateBus.DoUpdate(update) return Success, nil } diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index a1d15dd0..f41c2afb 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -34,8 +34,10 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/statediff" "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" + "golang.org/x/crypto/ssh" "golang.org/x/mod/semver" ) @@ -681,9 +683,9 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState { func (msh *MShellProc) NotifyRemoteUpdate() { rstate := msh.GetRemoteRuntimeState() - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, rstate) - sstore.MainBus.SendUpdate(update) + update := scbus.MakeUpdatePacket() + update.AddUpdate(rstate) + scbus.MainUpdateBus.DoUpdate(update) } func GetAllRemoteRuntimeState() []*RemoteRuntimeState { @@ -943,13 +945,13 @@ func (msh *MShellProc) writeToPtyBuffer_nolock(strFmt string, args ...interface{ func sendRemotePtyUpdate(remoteId string, dataOffset int64, data []byte) { data64 := base64.StdEncoding.EncodeToString(data) - update := &sstore.PtyDataUpdate{ + update := scbus.MakePtyDataUpdate(&scbus.PtyDataUpdate{ RemoteId: remoteId, PtyPos: dataOffset, PtyData64: data64, PtyDataLen: int64(len(data)), - } - sstore.MainBus.SendUpdate(update) + }) + scbus.MainUpdateBus.DoUpdate(update) } func (msh *MShellProc) isWaitingForPassword_nolock() bool { @@ -2016,9 +2018,9 @@ func (msh *MShellProc) notifyHangups_nolock() { if err != nil { continue } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *cmd) - sstore.MainBus.SendScreenUpdate(ck.GetGroupId(), update) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*cmd) + scbus.MainUpdateBus.DoScreenUpdate(ck.GetGroupId(), update) go pushNumRunningCmdsUpdate(&ck, -1) } msh.RunningCmds = make(map[base.CommandKey]RunCmdType) @@ -2047,7 +2049,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } if screen != nil { - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) } rct := msh.GetRunningCmd(donePk.CK) var statePtr *sstore.ShellStatePtr @@ -2059,7 +2061,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } if remoteInst != nil { - sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst)) + update.AddUpdate(sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst)) } statePtr = &sstore.ShellStatePtr{BaseHash: donePk.FinalState.GetHashVal(false)} } else if donePk.FinalStateDiff != nil && rct != nil { @@ -2079,7 +2081,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } if remoteInst != nil { - sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst)) + update.AddUpdate(sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst)) } diffHashArr := append(([]string)(nil), donePk.FinalStateDiff.DiffHashArr...) diffHashArr = append(diffHashArr, donePk.FinalStateDiff.GetHashVal(false)) @@ -2093,7 +2095,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } } - sstore.MainBus.SendUpdate(update) + scbus.MainUpdateBus.DoUpdate(update) return } @@ -2122,13 +2124,13 @@ func (msh *MShellProc) handleCmdFinalPacket(finalPk *packet.CmdFinalPacketType) log.Printf("error getting cmd(2) in handleCmdFinalPacket (not found)\n") return } - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *rtnCmd) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*rtnCmd) if screen != nil { - sstore.AddUpdate(update, *screen) + update.AddUpdate(*screen) } go pushNumRunningCmdsUpdate(&finalPk.CK, -1) - sstore.MainBus.SendUpdate(update) + scbus.MainUpdateBus.DoUpdate(update) } // TODO notify FE about cmd errors @@ -2164,7 +2166,7 @@ func (msh *MShellProc) handleDataPacket(dataPk *packet.DataPacketType, dataPosMa } utilfn.IncSyncMap(dataPosMap, dataPk.CK, int64(len(realData))) if update != nil { - sstore.MainBus.SendScreenUpdate(dataPk.CK.GetGroupId(), update) + scbus.MainUpdateBus.DoScreenUpdate(dataPk.CK.GetGroupId(), update) } } if ack != nil { @@ -2193,9 +2195,9 @@ func (msh *MShellProc) makeHandleCmdFinalPacketClosure(finalPk *packet.CmdFinalP func sendScreenUpdates(screens []*sstore.ScreenType) { for _, screen := range screens { - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, *screen) - sstore.MainBus.SendUpdate(update) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*screen) + scbus.MainUpdateBus.DoUpdate(update) } } diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go index a9becca7..9ec5e977 100644 --- a/wavesrv/pkg/remote/sshclient.go +++ b/wavesrv/pkg/remote/sshclient.go @@ -22,8 +22,9 @@ import ( "github.com/kevinburke/ssh_config" "github.com/wavetermdev/waveterm/waveshell/pkg/base" - "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" + "github.com/wavetermdev/waveterm/wavesrv/pkg/userinput" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" ) @@ -104,14 +105,13 @@ func createPublicKeyCallback(sshKeywords *SshKeywords, passphrase string) func() return createDummySigner() } - request := &sstore.UserInputRequestType{ + request := &userinput.UserInputRequestType{ ResponseType: "text", QueryText: fmt.Sprintf("Enter passphrase for the SSH key: %s", identityFile), Title: "Publickey Auth + Passphrase", } - ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) - defer cancelFn() - response, err := sstore.MainBus.GetUserInput(ctx, request) + ctx, _ := context.WithTimeout(context.Background(), 60*time.Second) + response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) if err != nil { // this is an error where we actually do want to stop // trying keys @@ -141,12 +141,12 @@ func createInteractivePasswordCallbackPrompt() func() (secret string, err error) // in the future ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) defer cancelFn() - request := &sstore.UserInputRequestType{ + request := &userinput.UserInputRequestType{ ResponseType: "text", QueryText: "Password:", Title: "Password Authentication", } - response, err := sstore.MainBus.GetUserInput(ctx, request) + response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) if err != nil { return "", err } @@ -201,12 +201,12 @@ func promptChallengeQuestion(question string, echo bool) (answer string, err err // in the future ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) defer cancelFn() - request := &sstore.UserInputRequestType{ + request := &userinput.UserInputRequestType{ ResponseType: "text", QueryText: question, Title: "Keyboard Interactive Authentication", } - response, err := sstore.MainBus.GetUserInput(ctx, request) + response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) if err != nil { return "", err } @@ -234,10 +234,10 @@ func openKnownHostsForEdit(knownHostsFilename string) (*os.File, error) { return os.OpenFile(knownHostsFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) } -func writeToKnownHosts(knownHostsFile string, newLine string, getUserVerification func() (*scpacket.UserInputResponsePacketType, error)) error { +func writeToKnownHosts(knownHostsFile string, newLine string, getUserVerification func() (*userinput.UserInputResponsePacketType, error)) error { if getUserVerification == nil { - getUserVerification = func() (*scpacket.UserInputResponsePacketType, error) { - return &scpacket.UserInputResponsePacketType{ + getUserVerification = func() (*userinput.UserInputResponsePacketType, error) { + return &userinput.UserInputResponsePacketType{ Type: "confirm", Confirm: true, }, nil @@ -270,7 +270,7 @@ func writeToKnownHosts(knownHostsFile string, newLine string, getUserVerificatio return f.Close() } -func createUnknownKeyVerifier(knownHostsFile string, hostname string, remote string, key ssh.PublicKey) func() (*scpacket.UserInputResponsePacketType, error) { +func createUnknownKeyVerifier(knownHostsFile string, hostname string, remote string, key ssh.PublicKey) func() (*userinput.UserInputResponsePacketType, error) { base64Key := base64.StdEncoding.EncodeToString(key.Marshal()) queryText := fmt.Sprintf( "The authenticity of host '%s (%s)' can't be established "+ @@ -280,20 +280,20 @@ func createUnknownKeyVerifier(knownHostsFile string, hostname string, remote str "**Would you like to continue connecting?** If so, the key will be permanently "+ "added to the file %s "+ "to protect from future man-in-the-middle attacks.", hostname, remote, key.Type(), base64Key, knownHostsFile) - request := &sstore.UserInputRequestType{ + request := &userinput.UserInputRequestType{ ResponseType: "confirm", QueryText: queryText, Markdown: true, Title: "Known Hosts Key Missing", } - return func() (*scpacket.UserInputResponsePacketType, error) { + return func() (*userinput.UserInputResponsePacketType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) defer cancelFn() - return sstore.MainBus.GetUserInput(ctx, request) + return userinput.GetUserInput(ctx, scbus.MainRpcBus, request) } } -func createMissingKnownHostsVerifier(knownHostsFile string, hostname string, remote string, key ssh.PublicKey) func() (*scpacket.UserInputResponsePacketType, error) { +func createMissingKnownHostsVerifier(knownHostsFile string, hostname string, remote string, key ssh.PublicKey) func() (*userinput.UserInputResponsePacketType, error) { base64Key := base64.StdEncoding.EncodeToString(key.Marshal()) queryText := fmt.Sprintf( "The authenticity of host '%s (%s)' can't be established "+ @@ -304,16 +304,16 @@ func createMissingKnownHostsVerifier(knownHostsFile string, hostname string, rem "- %s will be created \n"+ "- the key will be added to %s\n\n"+ "This will protect from future man-in-the-middle attacks.", hostname, remote, key.Type(), base64Key, knownHostsFile, knownHostsFile) - request := &sstore.UserInputRequestType{ + request := &userinput.UserInputRequestType{ ResponseType: "confirm", QueryText: queryText, Markdown: true, Title: "Known Hosts File Missing", } - return func() (*scpacket.UserInputResponsePacketType, error) { + return func() (*userinput.UserInputResponsePacketType, error) { ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) defer cancelFn() - return sstore.MainBus.GetUserInput(ctx, request) + return userinput.GetUserInput(ctx, scbus.MainRpcBus, request) } } @@ -444,13 +444,13 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { "%s\n\n"+ "**Offending Keys** \n"+ "%s", key.Type(), correctKeyFingerprint, strings.Join(bulletListKnownHosts, " \n"), strings.Join(offendingKeysFmt, " \n")) - update := &sstore.ModelUpdate{} - sstore.AddUpdate(update, sstore.AlertMessageType{ + update := scbus.MakeUpdatePacket() + update.AddUpdate(sstore.AlertMessageType{ Markdown: true, Title: "Known Hosts Key Changed", Message: alertText, }) - sstore.MainBus.SendUpdate(update) + scbus.MainUpdateBus.DoUpdate(update) return fmt.Errorf("remote host identification has changed") } diff --git a/wavesrv/pkg/scbus/modelupdate.go b/wavesrv/pkg/scbus/modelupdate.go new file mode 100644 index 00000000..4e6f3605 --- /dev/null +++ b/wavesrv/pkg/scbus/modelupdate.go @@ -0,0 +1,126 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package scbus + +import ( + "encoding/json" + "reflect" + + "github.com/wavetermdev/waveterm/waveshell/pkg/packet" +) + +const ModelUpdateStr = "model" + +// A channel for sending model updates to the client +type ModelUpdateChannel[J any] struct { + ScreenId string + ClientId string + ch chan J +} + +func (uch *ModelUpdateChannel[J]) GetChannel() chan J { + return uch.ch +} + +func (uch *ModelUpdateChannel[J]) SetChannel(ch chan J) { + uch.ch = ch +} + +// Match the screenId to the channel +func (sch *ModelUpdateChannel[J]) Match(screenId string) bool { + if screenId == "" { + return true + } + return screenId == sch.ScreenId +} + +// An interface for all model updates +type ModelUpdateItem interface { + // The key to use when marshalling to JSON and interpreting in the client + GetType() string +} + +// An inner data type for the ModelUpdatePacketType. Stores a collection of model updates to be sent to the client. +type ModelUpdate []ModelUpdateItem + +func (mu *ModelUpdate) IsEmpty() bool { + if mu == nil { + return true + } + muArr := []ModelUpdateItem(*mu) + return len(muArr) == 0 +} + +func (mu *ModelUpdate) MarshalJSON() ([]byte, error) { + rtn := make([]map[string]any, 0) + for _, u := range *mu { + m := make(map[string]any) + m[(u).GetType()] = u + rtn = append(rtn, m) + } + return json.Marshal(rtn) +} + +// An UpdatePacket for sending model updates to the client +type ModelUpdatePacketType struct { + Type string `json:"type"` + Data *ModelUpdate `json:"data"` +} + +func (*ModelUpdatePacketType) GetType() string { + return ModelUpdateStr +} + +func (mu *ModelUpdatePacketType) IsEmpty() bool { + if mu == nil || mu.Data == nil { + return true + } + return mu.Data.IsEmpty() +} + +// Clean the ClientData in an update, if present +func (upk *ModelUpdatePacketType) Clean() { + if upk == nil || upk.Data == nil { + return + } + for _, item := range *(upk.Data) { + if i, ok := (item).(CleanableUpdateItem); ok { + i.Clean() + } + } +} + +// Add a collection of model updates to the update +func (upk *ModelUpdatePacketType) AddUpdate(items ...ModelUpdateItem) { + *(upk.Data) = append(*(upk.Data), items...) +} + +// Create a new model update packet +func MakeUpdatePacket() *ModelUpdatePacketType { + return &ModelUpdatePacketType{ + Type: ModelUpdateStr, + Data: &ModelUpdate{}, + } +} + +// Returns the items in the update that are of type I +func GetUpdateItems[I ModelUpdateItem](upk *ModelUpdatePacketType) []*I { + ret := make([]*I, 0) + for _, item := range *(upk.Data) { + if i, ok := (item).(I); ok { + ret = append(ret, &i) + } + } + return ret +} + +// An interface for model updates that can be cleaned +type CleanableUpdateItem interface { + Clean() +} + +func init() { + // Register the model update packet type + packet.RegisterPacketType(ModelUpdateStr, reflect.TypeOf(ModelUpdatePacketType{})) +} diff --git a/wavesrv/pkg/scbus/ptydataupdate.go b/wavesrv/pkg/scbus/ptydataupdate.go new file mode 100644 index 00000000..38449e9a --- /dev/null +++ b/wavesrv/pkg/scbus/ptydataupdate.go @@ -0,0 +1,50 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package scbus + +import ( + "reflect" + + "github.com/wavetermdev/waveterm/waveshell/pkg/packet" +) + +const PtyDataUpdateStr = "pty" + +// The inner data type for the PtyDataUpdatePacketType. Stores the pty data to be sent to the client. +type PtyDataUpdate struct { + ScreenId string `json:"screenid,omitempty"` + LineId string `json:"lineid,omitempty"` + RemoteId string `json:"remoteid,omitempty"` + PtyPos int64 `json:"ptypos"` + PtyData64 string `json:"ptydata64"` + PtyDataLen int64 `json:"ptydatalen"` +} + +// An UpdatePacket for sending pty data to the client +type PtyDataUpdatePacketType struct { + Type string `json:"type"` + Data *PtyDataUpdate `json:"data"` +} + +func (*PtyDataUpdatePacketType) GetType() string { + return PtyDataUpdateStr +} + +func (pdu *PtyDataUpdatePacketType) Clean() { + // This is a no-op for PtyDataUpdatePacketType, but it is required to satisfy the UpdatePacket interface +} + +func (pdu *PtyDataUpdatePacketType) IsEmpty() bool { + return pdu == nil || pdu.Data == nil || pdu.Data.PtyDataLen == 0 +} + +// Create a new PtyDataUpdatePacketType +func MakePtyDataUpdate(update *PtyDataUpdate) *PtyDataUpdatePacketType { + return &PtyDataUpdatePacketType{Type: PtyDataUpdateStr, Data: update} +} + +func init() { + // Register the PtyDataUpdatePacketType with the packet package + packet.RegisterPacketType(PtyDataUpdateStr, reflect.TypeOf(PtyDataUpdatePacketType{})) +} diff --git a/wavesrv/pkg/scbus/scbus.go b/wavesrv/pkg/scbus/scbus.go new file mode 100644 index 00000000..016c81ce --- /dev/null +++ b/wavesrv/pkg/scbus/scbus.go @@ -0,0 +1,240 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Defines interfaces for creating communciation channels between server and clients +package scbus + +import ( + "context" + "fmt" + "log" + "reflect" + "sync" + "time" + + "github.com/google/uuid" + "github.com/wavetermdev/waveterm/waveshell/pkg/packet" +) + +var MainUpdateBus *UpdateBus = MakeUpdateBus() +var MainRpcBus *RpcBus = MakeRpcBus() + +// The default channel size +const ChSize = 100 + +type Channel[I packet.PacketType] interface { + GetChannel() chan I + SetChannel(chan I) + Match(string) bool +} + +// A concurrent bus for registering and managing channels +type Bus[I packet.PacketType] struct { + Lock *sync.Mutex + Channels map[string]Channel[I] +} + +// Opens new channel and registers it with the bus. If a channel exists, it is closed and replaced. +func (bus *Bus[I]) RegisterChannel(key string, channelEntry Channel[I]) chan I { + bus.Lock.Lock() + defer bus.Lock.Unlock() + uch, found := bus.Channels[key] + ch := make(chan I, ChSize) + log.Printf("registering channel key=%s ch=%v\n", key, ch) + channelEntry.SetChannel(ch) + if found { + close(uch.GetChannel()) + } + bus.Channels[key] = channelEntry + return channelEntry.GetChannel() +} + +// Closes the channel matching the provided key and removes it from the bus +func (bus *Bus[I]) UnregisterChannel(key string) { + bus.Lock.Lock() + defer bus.Lock.Unlock() + uch, found := bus.Channels[key] + if found { + close(uch.GetChannel()) + delete(bus.Channels, key) + } +} + +// An interface for updates to be sent over an UpdateChannel +type UpdatePacket interface { + // The key to use when marshalling to JSON and interpreting in the client + GetType() string + Clean() + IsEmpty() bool +} + +// A channel for sending model updates to the client +type UpdateChannel struct { + ScreenId string + ch chan UpdatePacket +} + +func (uch *UpdateChannel) GetChannel() chan UpdatePacket { + return uch.ch +} + +func (uch *UpdateChannel) SetChannel(ch chan UpdatePacket) { + uch.ch = ch +} + +// Match the screenId to the channel +func (sch *UpdateChannel) Match(screenId string) bool { + if screenId == "" { + return true + } + return screenId == sch.ScreenId +} + +// A collection of channels that can transmit updates +type UpdateBus struct { + Bus[UpdatePacket] +} + +func (bus *UpdateBus) GetLock() *sync.Mutex { + return bus.Lock +} + +// Create a new UpdateBus +func MakeUpdateBus() *UpdateBus { + return &UpdateBus{ + Bus[UpdatePacket]{ + Lock: &sync.Mutex{}, + Channels: make(map[string]Channel[UpdatePacket]), + }, + } +} + +// Send an update to all channels in the collection +func (bus *UpdateBus) DoUpdate(update UpdatePacket) { + if update == nil || update.IsEmpty() { + return + } + update.Clean() + bus.Lock.Lock() + defer bus.Lock.Unlock() + for key, uch := range bus.Channels { + select { + case uch.GetChannel() <- update: + + default: + log.Printf("[error] dropped update on %s updatebus uch key=%s\n", reflect.TypeOf(uch), key) + } + } +} + +// Send a model update to only clients that are subscribed to the given screenId +func (bus *UpdateBus) DoScreenUpdate(screenId string, update UpdatePacket) { + if update == nil { + return + } + update.Clean() + bus.Lock.Lock() + defer bus.Lock.Unlock() + for id, uch := range bus.Channels { + if uch.Match(screenId) { + select { + case uch.GetChannel() <- update: + + default: + log.Printf("[error] dropped update on updatebus uch id=%s\n", id) + } + } + } +} + +// An interface for rpc requests +// This is separate from the RpcPacketType defined in the waveshell/pkg/packet package, as that one is intended for use communicating between wavesrv and waveshell. It is has a different set of required methods. +type RpcPacket interface { + SetReqId(string) + SetTimeoutMs(int) + GetType() string +} + +// An interface for rpc responses +// This is separate from the RpcResponsePacketType defined in the waveshell/pkg/packet package, as that one is intended for use communicating between wavesrv and waveshell. It is has a different set of required methods. +type RpcResponse interface { + SetError(string) + GetError() string + GetType() string +} + +// A collection of channels that can receive rpc responses +type RpcBus struct { + Bus[RpcResponse] +} + +// Create a new RpcBus +func MakeRpcBus() *RpcBus { + return &RpcBus{ + Bus[RpcResponse]{ + Lock: &sync.Mutex{}, + Channels: make(map[string]Channel[RpcResponse]), + }, + } +} + +// Get the user input channel for the given request id +func (bus *RpcBus) GetRpcChannel(id string) (chan RpcResponse, bool) { + bus.Lock.Lock() + defer bus.Lock.Unlock() + + if ch, ok := bus.Channels[id]; ok { + return ch.GetChannel(), ok + } + return nil, false +} + +// Implements the Channel interface to allow receiving rpc responses +type RpcChannel struct { + ch chan RpcResponse +} + +func (ch *RpcChannel) GetChannel() chan RpcResponse { + return ch.ch +} + +func (ch *RpcChannel) SetChannel(newCh chan RpcResponse) { + ch.ch = newCh +} + +// This is a no-op, only used to satisfy the Channel interface +func (ch *RpcChannel) Match(string) bool { + return true +} + +// Send a user input request to the frontend and wait for a response +func (bus *RpcBus) DoRpc(ctx context.Context, pk RpcPacket) (RpcResponse, error) { + id := uuid.New().String() + ch := bus.RegisterChannel(id, &RpcChannel{}) + pk.SetReqId(id) + defer bus.UnregisterChannel(id) + + deadline, _ := ctx.Deadline() + pk.SetTimeoutMs(int(time.Until(deadline).Milliseconds()) - 500) + + // Send the request to the frontend + mu := MakeUpdatePacket() + mu.AddUpdate(pk) + MainUpdateBus.DoUpdate(mu) + + var response RpcResponse + var err error + // prepare to receive response + select { + case resp := <-ch: + response = resp + case <-ctx.Done(): + return nil, fmt.Errorf("timed out waiting for rpc response") + } + + if response.GetError() != "" { + err = fmt.Errorf(response.GetError()) + } + + return response, err +} diff --git a/wavesrv/pkg/scpacket/scpacket.go b/wavesrv/pkg/scpacket/scpacket.go index 05b1c98f..52c8a9ff 100644 --- a/wavesrv/pkg/scpacket/scpacket.go +++ b/wavesrv/pkg/scpacket/scpacket.go @@ -83,7 +83,6 @@ const WatchScreenPacketStr = "watchscreen" const FeInputPacketStr = "feinput" const RemoteInputPacketStr = "remoteinput" const CmdInputTextPacketStr = "cmdinputtext" -const UserInputResponsePacketStr = "userinputresp" type FeCommandPacketType struct { Type string `json:"type"` @@ -156,21 +155,16 @@ type CmdInputTextPacketType struct { Text utilfn.StrWithPos `json:"text"` } -type UserInputResponsePacketType struct { - Type string `json:"type"` - RequestId string `json:"requestid"` - Text string `json:"text,omitempty"` - Confirm bool `json:"confirm,omitempty"` - ErrorMsg string `json:"errormsg,omitempty"` -} - func init() { packet.RegisterPacketType(FeCommandPacketStr, reflect.TypeOf(FeCommandPacketType{})) packet.RegisterPacketType(WatchScreenPacketStr, reflect.TypeOf(WatchScreenPacketType{})) packet.RegisterPacketType(FeInputPacketStr, reflect.TypeOf(FeInputPacketType{})) packet.RegisterPacketType(RemoteInputPacketStr, reflect.TypeOf(RemoteInputPacketType{})) packet.RegisterPacketType(CmdInputTextPacketStr, reflect.TypeOf(CmdInputTextPacketType{})) - packet.RegisterPacketType(UserInputResponsePacketStr, reflect.TypeOf(UserInputResponsePacketType{})) +} + +type PacketType interface { + GetType() string } func (*CmdInputTextPacketType) GetType() string { @@ -212,7 +206,3 @@ func MakeRemoteInputPacket() *RemoteInputPacketType { func (*RemoteInputPacketType) GetType() string { return RemoteInputPacketStr } - -func (*UserInputResponsePacketType) GetType() string { - return UserInputResponsePacketStr -} diff --git a/wavesrv/pkg/scws/scws.go b/wavesrv/pkg/scws/scws.go index f7d3c1b0..e8e1c226 100644 --- a/wavesrv/pkg/scws/scws.go +++ b/wavesrv/pkg/scws/scws.go @@ -15,8 +15,10 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/wavesrv/pkg/mapqueue" "github.com/wavetermdev/waveterm/wavesrv/pkg/remote" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" + "github.com/wavetermdev/waveterm/wavesrv/pkg/userinput" "github.com/wavetermdev/waveterm/wavesrv/pkg/wsshell" ) @@ -35,8 +37,8 @@ type WSState struct { ClientId string ConnectTime time.Time Shell *wsshell.WSShell - UpdateCh chan interface{} - UpdateQueue []interface{} + UpdateCh chan scbus.UpdatePacket + UpdateQueue []any Authenticated bool AuthKey string @@ -71,7 +73,7 @@ func (ws *WSState) GetShell() *wsshell.WSShell { return ws.Shell } -func (ws *WSState) WriteUpdate(update interface{}) error { +func (ws *WSState) WriteUpdate(update any) error { shell := ws.GetShell() if shell == nil { return fmt.Errorf("cannot write update, empty shell") @@ -103,26 +105,21 @@ func (ws *WSState) WatchScreen(sessionId string, screenId string) { } ws.SessionId = sessionId ws.ScreenId = screenId - ws.UpdateCh = sstore.MainBus.RegisterChannel(ws.ClientId, ws.ScreenId) + ws.UpdateCh = scbus.MainUpdateBus.RegisterChannel(ws.ClientId, &scbus.UpdateChannel{ScreenId: ws.ScreenId}) + log.Printf("[ws] watch screen clientid=%s sessionid=%s screenid=%s, updateCh=%v\n", ws.ClientId, sessionId, screenId, ws.UpdateCh) go ws.RunUpdates(ws.UpdateCh) } func (ws *WSState) UnWatchScreen() { ws.Lock.Lock() defer ws.Lock.Unlock() - sstore.MainBus.UnregisterChannel(ws.ClientId) + scbus.MainUpdateBus.UnregisterChannel(ws.ClientId) ws.SessionId = "" ws.ScreenId = "" log.Printf("[ws] unwatch screen clientid=%s\n", ws.ClientId) } -func (ws *WSState) getUpdateCh() chan interface{} { - ws.Lock.Lock() - defer ws.Lock.Unlock() - return ws.UpdateCh -} - -func (ws *WSState) RunUpdates(updateCh chan interface{}) { +func (ws *WSState) RunUpdates(updateCh chan scbus.UpdatePacket) { if updateCh == nil { panic("invalid nil updateCh passed to RunUpdates") } @@ -141,7 +138,6 @@ func writeJsonProtected(shell *wsshell.WSShell, update any) { return } log.Printf("[error] in scws RunUpdates WriteJson: %v\n", r) - return }() shell.WriteJson(update) } @@ -155,7 +151,6 @@ func (ws *WSState) ReplaceShell(shell *wsshell.WSShell) { } ws.Shell.Conn.Close() ws.Shell = shell - return } // returns all state required to display current UI @@ -170,8 +165,8 @@ func (ws *WSState) handleConnection() error { connectUpdate.Remotes = remotes // restore status indicators connectUpdate.ScreenStatusIndicators, connectUpdate.ScreenNumRunningCommands = sstore.GetCurrentIndicatorState() - mu := &sstore.ModelUpdate{} - sstore.AddUpdate(mu, *connectUpdate) + mu := scbus.MakeUpdatePacket() + mu.AddUpdate(*connectUpdate) err = ws.Shell.WriteJson(mu) if err != nil { return err @@ -282,11 +277,11 @@ func (ws *WSState) processMessage(msgBytes []byte) error { sstore.ScreenMemSetCmdInputText(cmdInputPk.ScreenId, cmdInputPk.Text, cmdInputPk.SeqNum) return nil } - if pk.GetType() == scpacket.UserInputResponsePacketStr { - userInputRespPk := pk.(*scpacket.UserInputResponsePacketType) - uich, ok := sstore.MainBus.GetUserInputChannel(userInputRespPk.RequestId) + if pk.GetType() == userinput.UserInputResponsePacketStr { + userInputRespPk := pk.(*userinput.UserInputResponsePacketType) + uich, ok := scbus.MainRpcBus.GetRpcChannel(userInputRespPk.RequestId) if !ok { - return fmt.Errorf("received User Input Response with invalid Id (%s): %v\n", userInputRespPk.RequestId, err) + return fmt.Errorf("received User Input Response with invalid Id (%s): %v", userInputRespPk.RequestId, err) } select { case uich <- userInputRespPk: @@ -302,7 +297,7 @@ func (ws *WSState) RunWSRead() { if shell == nil { return } - shell.WriteJson(map[string]interface{}{"type": "hello"}) // let client know we accepted this connection, ignore error + shell.WriteJson(map[string]any{"type": "hello"}) // let client know we accepted this connection, ignore error for msgBytes := range shell.ReadChan { err := ws.processMessage(msgBytes) if err != nil { diff --git a/wavesrv/pkg/sstore/dbops.go b/wavesrv/pkg/sstore/dbops.go index e8c9d550..8666d5c4 100644 --- a/wavesrv/pkg/sstore/dbops.go +++ b/wavesrv/pkg/sstore/dbops.go @@ -22,6 +22,7 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" "github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" ) const HistoryCols = "h.historyid, h.ts, h.userid, h.sessionid, h.screenid, h.lineid, h.haderror, h.cmdstr, h.remoteownerid, h.remoteid, h.remotename, h.ismetacmd, h.linenum, h.exitcode, h.durationms, h.festate, h.tags, h.status" @@ -563,7 +564,7 @@ func GetSessionByName(ctx context.Context, name string) (*SessionType, error) { // returns sessionId // if sessionName == "", it will be generated -func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*ModelUpdate, error) { +func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*scbus.ModelUpdatePacketType, error) { var newScreen *ScreenType newSessionId := scbase.GenWaveUUID() txErr := WithTx(ctx, func(tx *TxWrap) error { @@ -577,7 +578,7 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo if err != nil { return err } - screenUpdateItems := GetUpdateItems[ScreenType](screenUpdate) + screenUpdateItems := scbus.GetUpdateItems[ScreenType](screenUpdate) if len(screenUpdateItems) < 1 { return fmt.Errorf("no screen update items") } @@ -595,11 +596,11 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo if err != nil { return nil, err } - update := &ModelUpdate{} - AddUpdate(update, *session) - AddUpdate(update, *newScreen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*session) + update.AddUpdate(*newScreen) if activate { - AddUpdate(update, ActiveSessionIdUpdate(newSessionId)) + update.AddUpdate(ActiveSessionIdUpdate(newSessionId)) } return update, nil } @@ -687,7 +688,7 @@ func fmtUniqueName(name string, defaultFmtStr string, startIdx int, strs []strin } } -func InsertScreen(ctx context.Context, sessionId string, origScreenName string, opts ScreenCreateOpts, activate bool) (*ModelUpdate, error) { +func InsertScreen(ctx context.Context, sessionId string, origScreenName string, opts ScreenCreateOpts, activate bool) (*scbus.ModelUpdatePacketType, error) { var newScreenId string txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT sessionid FROM session WHERE sessionid = ? AND NOT archived` @@ -753,14 +754,14 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, if err != nil { return nil, err } - update := &ModelUpdate{} - AddUpdate(update, *newScreen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*newScreen) if activate { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { return nil, txErr } - AddUpdate(update, *bareSession) + update.AddUpdate(*bareSession) UpdateWithCurrentOpenAICmdInfoChat(newScreenId, update) } return update, nil @@ -875,28 +876,30 @@ func GetCmdByScreenId(ctx context.Context, screenId string, lineId string) (*Cmd }) } -func UpdateWithClearOpenAICmdInfo(screenId string) (*ModelUpdate, error) { +func UpdateWithClearOpenAICmdInfo(screenId string) *scbus.ModelUpdatePacketType { ScreenMemClearCmdInfoChat(screenId) return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil) } -func UpdateWithAddNewOpenAICmdInfoPacket(ctx context.Context, screenId string, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) { +func UpdateWithAddNewOpenAICmdInfoPacket(ctx context.Context, screenId string, pk *packet.OpenAICmdInfoChatMessage) *scbus.ModelUpdatePacketType { ScreenMemAddCmdInfoChatMessage(screenId, pk) return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil) } -func UpdateWithCurrentOpenAICmdInfoChat(screenId string, update *ModelUpdate) (*ModelUpdate, error) { - ret := &ModelUpdate{} - AddOpenAICmdInfoChatUpdate(ret, ScreenMemGetCmdInfoChat(screenId).Messages) - return ret, nil +func UpdateWithCurrentOpenAICmdInfoChat(screenId string, update *scbus.ModelUpdatePacketType) *scbus.ModelUpdatePacketType { + if update == nil { + update = scbus.MakeUpdatePacket() + } + update.AddUpdate(OpenAICmdInfoChatUpdate(ScreenMemGetCmdInfoChat(screenId).Messages)) + return update } -func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, messageID int, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) { +func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, messageID int, pk *packet.OpenAICmdInfoChatMessage) (*scbus.ModelUpdatePacketType, error) { err := ScreenMemUpdateCmdInfoChatMessage(screenId, messageID, pk) if err != nil { return nil, err } - return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil) + return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil), nil } func UpdateCmdForRestart(ctx context.Context, ck base.CommandKey, ts int64, cmdPid int, remotePid int, termOpts *TermOpts) error { @@ -913,7 +916,7 @@ func UpdateCmdForRestart(ctx context.Context, ck base.CommandKey, ts int64, cmdP }) } -func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, donePk *packet.CmdDonePacketType, status string) (*ModelUpdate, error) { +func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, donePk *packet.CmdDonePacketType, status string) (*scbus.ModelUpdatePacketType, error) { if donePk == nil { return nil, fmt.Errorf("invalid cmddone packet") } @@ -947,8 +950,8 @@ func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, donePk *packet.C return nil, fmt.Errorf("cmd data not found for ck[%s]", ck) } - update := &ModelUpdate{} - AddUpdate(update, *rtnCmd) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*rtnCmd) // Update in-memory screen indicator status var indicator StatusIndicatorLevel @@ -1096,7 +1099,7 @@ func getNextId(ids []string, delId string) string { return ids[0] } -func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (*ModelUpdate, error) { +func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (*scbus.ModelUpdatePacketType, error) { SetActiveSessionId(ctx, sessionId) txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?` @@ -1114,12 +1117,12 @@ func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (* if err != nil { return nil, err } - update := &ModelUpdate{} - AddUpdate(update, (ActiveSessionIdUpdate)(sessionId)) - AddUpdate(update, *bareSession) + update := scbus.MakeUpdatePacket() + update.AddUpdate(ActiveSessionIdUpdate(sessionId)) + update.AddUpdate(*bareSession) memState := GetScreenMemState(screenId) if memState != nil { - AddCmdLineUpdate(update, memState.CmdInputText) + update.AddUpdate(CmdLineUpdate(memState.CmdInputText)) UpdateWithCurrentOpenAICmdInfoChat(screenId, update) // Clear any previous status indicator for this screen @@ -1151,7 +1154,7 @@ func cleanScreenCmds(ctx context.Context, screenId string) error { return nil } -func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (UpdatePacket, error) { +func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (scbus.UpdatePacket, error) { var isActive bool txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT screenid FROM screen WHERE sessionid = ? AND screenid = ?` @@ -1188,14 +1191,14 @@ func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (Upda if err != nil { return nil, fmt.Errorf("cannot retrive archived screen: %w", err) } - update := &ModelUpdate{} - AddUpdate(update, *newScreen) + update := scbus.MakeUpdatePacket() + update.AddUpdate(*newScreen) if isActive { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { return nil, err } - AddUpdate(update, *bareSession) + update.AddUpdate(*bareSession) } return update, nil } @@ -1215,7 +1218,7 @@ func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) err } // if sessionDel is passed, we do *not* delete the screen directory (session delete will handle that) -func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update *ModelUpdate) (*ModelUpdate, error) { +func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update *scbus.ModelUpdatePacketType) (*scbus.ModelUpdatePacketType, error) { var sessionId string var isActive bool var screenTombstone *ScreenTombstoneType @@ -1276,16 +1279,16 @@ func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update GoDeleteScreenDirs(screenId) } if update == nil { - update = &ModelUpdate{} + update = scbus.MakeUpdatePacket() } - AddUpdate(update, *screenTombstone) - AddUpdate(update, ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}) + update.AddUpdate(*screenTombstone) + update.AddUpdate(ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}) if isActive { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { return nil, err } - AddUpdate(update, *bareSession) + update.AddUpdate(*bareSession) } return update, nil } @@ -1516,7 +1519,7 @@ func SetScreenName(ctx context.Context, sessionId string, screenId string, name return txErr } -func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { +func ArchiveScreenLines(ctx context.Context, screenId string) (*scbus.ModelUpdatePacketType, error) { txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT screenid FROM screen WHERE screenid = ?` if !tx.Exists(query, screenId) { @@ -1535,12 +1538,12 @@ func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, err if err != nil { return nil, err } - ret := &ModelUpdate{} - AddUpdate(ret, *screenLines) + ret := scbus.MakeUpdatePacket() + ret.AddUpdate(*screenLines) return ret, nil } -func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { +func DeleteScreenLines(ctx context.Context, screenId string) (*scbus.ModelUpdatePacketType, error) { var lineIds []string txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT lineid FROM line @@ -1579,9 +1582,9 @@ func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, erro } screenLines.Lines = append(screenLines.Lines, line) } - ret := &ModelUpdate{} - AddUpdate(ret, *screen) - AddUpdate(ret, *screenLines) + ret := scbus.MakeUpdatePacket() + ret.AddUpdate(*screen) + ret.AddUpdate(*screenLines) return ret, nil } @@ -1629,11 +1632,11 @@ func ScreenReset(ctx context.Context, screenId string) ([]*RemoteInstance, error }) } -func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) { +func DeleteSession(ctx context.Context, sessionId string) (scbus.UpdatePacket, error) { var newActiveSessionId string var screenIds []string var sessionTombstone *SessionTombstoneType - update := &ModelUpdate{} + update := scbus.MakeUpdatePacket() txErr := WithTx(ctx, func(tx *TxWrap) error { bareSession, err := GetBareSessionById(tx.Context(), sessionId) if err != nil { @@ -1668,11 +1671,11 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) } GoDeleteScreenDirs(screenIds...) if newActiveSessionId != "" { - AddUpdate(update, (ActiveSessionIdUpdate)(newActiveSessionId)) + update.AddUpdate(ActiveSessionIdUpdate(newActiveSessionId)) } - AddUpdate(update, SessionType{SessionId: sessionId, Remove: true}) + update.AddUpdate(SessionType{SessionId: sessionId, Remove: true}) if sessionTombstone != nil { - AddUpdate(update, *sessionTombstone) + update.AddUpdate(*sessionTombstone) } return update, nil } @@ -1699,7 +1702,7 @@ func fixActiveSessionId(ctx context.Context) (string, error) { return newActiveSessionId, nil } -func ArchiveSession(ctx context.Context, sessionId string) (*ModelUpdate, error) { +func ArchiveSession(ctx context.Context, sessionId string) (*scbus.ModelUpdatePacketType, error) { if sessionId == "" { return nil, fmt.Errorf("invalid blank sessionid") } @@ -1723,17 +1726,17 @@ func ArchiveSession(ctx context.Context, sessionId string) (*ModelUpdate, error) return nil, txErr } bareSession, _ := GetBareSessionById(ctx, sessionId) - update := &ModelUpdate{} + update := scbus.MakeUpdatePacket() if bareSession != nil { - AddUpdate(update, *bareSession) + update.AddUpdate(*bareSession) } if newActiveSessionId != "" { - AddUpdate(update, (ActiveSessionIdUpdate)(newActiveSessionId)) + update.AddUpdate(ActiveSessionIdUpdate(newActiveSessionId)) } return update, nil } -func UnArchiveSession(ctx context.Context, sessionId string, activate bool) (*ModelUpdate, error) { +func UnArchiveSession(ctx context.Context, sessionId string, activate bool) (*scbus.ModelUpdatePacketType, error) { if sessionId == "" { return nil, fmt.Errorf("invalid blank sessionid") } @@ -1759,13 +1762,13 @@ func UnArchiveSession(ctx context.Context, sessionId string, activate bool) (*Mo return nil, txErr } bareSession, _ := GetBareSessionById(ctx, sessionId) - update := &ModelUpdate{} + update := scbus.MakeUpdatePacket() if bareSession != nil { - AddUpdate(update, *bareSession) + update.AddUpdate(*bareSession) } if activate { - AddUpdate(update, (ActiveSessionIdUpdate)(sessionId)) + update.AddUpdate(ActiveSessionIdUpdate(sessionId)) } return update, nil } diff --git a/wavesrv/pkg/sstore/fileops.go b/wavesrv/pkg/sstore/fileops.go index 7b1b9ab1..bd63c5aa 100644 --- a/wavesrv/pkg/sstore/fileops.go +++ b/wavesrv/pkg/sstore/fileops.go @@ -18,6 +18,7 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/cirfile" "github.com/wavetermdev/waveterm/waveshell/pkg/shexec" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" ) func CreateCmdPtyFile(ctx context.Context, screenId string, lineId string, maxSize int64) error { @@ -61,7 +62,7 @@ func ClearCmdPtyFile(ctx context.Context, screenId string, lineId string) error return nil } -func AppendToCmdPtyBlob(ctx context.Context, screenId string, lineId string, data []byte, pos int64) (*PtyDataUpdate, error) { +func AppendToCmdPtyBlob(ctx context.Context, screenId string, lineId string, data []byte, pos int64) (*scbus.PtyDataUpdatePacketType, error) { if screenId == "" { return nil, fmt.Errorf("cannot append to PtyBlob, screenid is not set") } @@ -82,13 +83,13 @@ func AppendToCmdPtyBlob(ctx context.Context, screenId string, lineId string, dat return nil, err } data64 := base64.StdEncoding.EncodeToString(data) - update := &PtyDataUpdate{ + update := scbus.MakePtyDataUpdate(&scbus.PtyDataUpdate{ ScreenId: screenId, LineId: lineId, PtyPos: pos, PtyData64: data64, PtyDataLen: int64(len(data)), - } + }) err = MaybeInsertPtyPosUpdate(ctx, screenId, lineId) if err != nil { // just log diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index 386be8ab..04292453 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -27,6 +27,7 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/shellenv" "github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" _ "github.com/mattn/go-sqlite3" @@ -338,7 +339,7 @@ func (cdata *ClientData) Clean() *ClientData { return &rtn } -func (ClientData) UpdateType() string { +func (ClientData) GetType() string { return "clientdata" } @@ -357,7 +358,7 @@ type SessionType struct { Remove bool `json:"remove,omitempty"` } -func (SessionType) UpdateType() string { +func (SessionType) GetType() string { return "session" } @@ -376,7 +377,7 @@ type SessionTombstoneType struct { func (SessionTombstoneType) UseDBMap() {} -func (SessionTombstoneType) UpdateType() string { +func (SessionTombstoneType) GetType() string { return "sessiontombstone" } @@ -449,7 +450,7 @@ type ScreenLinesType struct { func (ScreenLinesType) UseDBMap() {} -func (ScreenLinesType) UpdateType() string { +func (ScreenLinesType) GetType() string { return "screenlines" } @@ -548,22 +549,22 @@ func (s *ScreenType) FromMap(m map[string]interface{}) bool { return true } -func (ScreenType) UpdateType() string { +func (ScreenType) GetType() string { return "screen" } -func AddScreenUpdate(update *ModelUpdate, newScreen *ScreenType) { +func AddScreenUpdate(update *scbus.ModelUpdatePacketType, newScreen *ScreenType) { if newScreen == nil { return } - screenUpdates := GetUpdateItems[ScreenType](update) + screenUpdates := scbus.GetUpdateItems[ScreenType](update) for _, screenUpdate := range screenUpdates { if screenUpdate.ScreenId == newScreen.ScreenId { screenUpdate = newScreen return } } - AddUpdate(update, newScreen) + update.AddUpdate(newScreen) } type ScreenTombstoneType struct { @@ -576,7 +577,7 @@ type ScreenTombstoneType struct { func (ScreenTombstoneType) UseDBMap() {} -func (ScreenTombstoneType) UpdateType() string { +func (ScreenTombstoneType) GetType() string { return "screentombstone" } @@ -1060,7 +1061,7 @@ func (state RemoteRuntimeState) ExpandHomeDir(pathStr string) (string, error) { return path.Join(homeDir, pathStr[2:]), nil } -func (RemoteRuntimeState) UpdateType() string { +func (RemoteRuntimeState) GetType() string { return "remote" } @@ -1128,7 +1129,7 @@ type CmdType struct { Restarted bool `json:"restarted,omitempty"` // not persisted to DB } -func (CmdType) UpdateType() string { +func (CmdType) GetType() string { return "cmd" } @@ -1479,7 +1480,7 @@ func SetReleaseInfo(ctx context.Context, releaseInfo ReleaseInfoType) error { } // Sets the in-memory status indicator for the given screenId to the given value and adds it to the ModelUpdate. By default, the active screen will be ignored when updating status. To force a status update for the active screen, set force=true. -func SetStatusIndicatorLevel_Update(ctx context.Context, update *ModelUpdate, screenId string, level StatusIndicatorLevel, force bool) error { +func SetStatusIndicatorLevel_Update(ctx context.Context, update *scbus.ModelUpdatePacketType, screenId string, level StatusIndicatorLevel, force bool) error { var newStatus StatusIndicatorLevel if force { // Force the update and set the new status to the given level, regardless of the current status or the active screen @@ -1509,7 +1510,7 @@ func SetStatusIndicatorLevel_Update(ctx context.Context, update *ModelUpdate, sc } } - AddUpdate(update, ScreenStatusIndicatorType{ + update.AddUpdate(ScreenStatusIndicatorType{ ScreenId: screenId, Status: newStatus, }) @@ -1518,17 +1519,17 @@ func SetStatusIndicatorLevel_Update(ctx context.Context, update *ModelUpdate, sc // Sets the in-memory status indicator for the given screenId to the given value and pushes the new value to the FE func SetStatusIndicatorLevel(ctx context.Context, screenId string, level StatusIndicatorLevel, force bool) error { - update := &ModelUpdate{} + update := scbus.MakeUpdatePacket() err := SetStatusIndicatorLevel_Update(ctx, update, screenId, level, false) if err != nil { return err } - MainBus.SendUpdate(update) + scbus.MainUpdateBus.DoUpdate(update) return nil } // Resets the in-memory status indicator for the given screenId to StatusIndicatorLevel_None and adds it to the ModelUpdate -func ResetStatusIndicator_Update(update *ModelUpdate, screenId string) error { +func ResetStatusIndicator_Update(update *scbus.ModelUpdatePacketType, screenId string) error { // We do not need to set context when resetting the status indicator because we will not need to call the DB return SetStatusIndicatorLevel_Update(context.TODO(), update, screenId, StatusIndicatorLevel_None, true) } @@ -1539,9 +1540,9 @@ func ResetStatusIndicator(screenId string) error { return SetStatusIndicatorLevel(context.TODO(), screenId, StatusIndicatorLevel_None, true) } -func IncrementNumRunningCmds_Update(update *ModelUpdate, screenId string, delta int) { +func IncrementNumRunningCmds_Update(update *scbus.ModelUpdatePacketType, screenId string, delta int) { newNum := ScreenMemIncrementNumRunningCommands(screenId, delta) - AddUpdate(update, ScreenNumRunningCommandsType{ + update.AddUpdate(ScreenNumRunningCommandsType{ ScreenId: screenId, Num: newNum, }) @@ -1549,7 +1550,7 @@ func IncrementNumRunningCmds_Update(update *ModelUpdate, screenId string, delta } func IncrementNumRunningCmds(screenId string, delta int) { - update := &ModelUpdate{} + update := scbus.MakeUpdatePacket() IncrementNumRunningCmds_Update(update, screenId, delta) - MainBus.SendUpdate(update) + scbus.MainUpdateBus.DoUpdate(update) } diff --git a/wavesrv/pkg/sstore/updatebus.go b/wavesrv/pkg/sstore/updatebus.go deleted file mode 100644 index b8789804..00000000 --- a/wavesrv/pkg/sstore/updatebus.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2023, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -package sstore - -import ( - "context" - "encoding/json" - "fmt" - "log" - "sync" - "time" - - "github.com/google/uuid" - "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" -) - -var MainBus *UpdateBus = MakeUpdateBus() - -const PtyDataUpdateStr = "pty" -const ModelUpdateStr = "model" -const UpdateChSize = 100 - -type UpdatePacket interface { - // The key to use when marshalling to JSON and interpreting in the client - UpdateType() string - Clean() -} - -type PtyDataUpdate struct { - ScreenId string `json:"screenid,omitempty"` - LineId string `json:"lineid,omitempty"` - RemoteId string `json:"remoteid,omitempty"` - PtyPos int64 `json:"ptypos"` - PtyData64 string `json:"ptydata64"` - PtyDataLen int64 `json:"ptydatalen"` -} - -func (*PtyDataUpdate) UpdateType() string { - return PtyDataUpdateStr -} - -func (pdu *PtyDataUpdate) Clean() {} - -// A collection of independent model updates to be sent to the client. Will be evaluated in order on the client. -type ModelUpdate []*ModelUpdateItem - -func (*ModelUpdate) UpdateType() string { - return ModelUpdateStr -} - -func (mu *ModelUpdate) MarshalJSON() ([]byte, error) { - rtn := make([]map[string]any, 0) - for _, u := range *mu { - m := make(map[string]any) - m[(*u).UpdateType()] = u - rtn = append(rtn, m) - } - return json.Marshal(rtn) -} - -// An interface for all model updates -type ModelUpdateItem interface { - // The key to use when marshalling to JSON and interpreting in the client - UpdateType() string -} - -// Clean the ClientData in an update, if present -func (update *ModelUpdate) Clean() { - if update == nil { - return - } - clientDataUpdates := GetUpdateItems[ClientData](update) - if len(clientDataUpdates) > 0 { - lastUpdate := clientDataUpdates[len(clientDataUpdates)-1] - lastUpdate.Clean() - } -} - -func (update *ModelUpdate) append(item *ModelUpdateItem) { - *update = append(*update, item) -} - -// Add a collection of model updates to the update -func AddUpdate(update *ModelUpdate, item ...ModelUpdateItem) { - for _, i := range item { - update.append(&i) - } -} - -// Returns the items in the update that are of type I -func GetUpdateItems[I ModelUpdateItem](update *ModelUpdate) []*I { - ret := make([]*I, 0) - for _, item := range *update { - if i, ok := (*item).(I); ok { - ret = append(ret, &i) - } - } - return ret -} - -type UpdateChannel struct { - ScreenId string - ClientId string - Ch chan interface{} -} - -func (uch UpdateChannel) Match(screenId string) bool { - if screenId == "" { - return true - } - return screenId == uch.ScreenId -} - -type UpdateBus struct { - Lock *sync.Mutex - Channels map[string]UpdateChannel - UserInputCh map[string](chan *scpacket.UserInputResponsePacketType) -} - -func MakeUpdateBus() *UpdateBus { - return &UpdateBus{ - Lock: &sync.Mutex{}, - Channels: make(map[string]UpdateChannel), - UserInputCh: make(map[string](chan *scpacket.UserInputResponsePacketType)), - } -} - -// always returns a new channel -func (bus *UpdateBus) RegisterChannel(clientId string, screenId string) chan interface{} { - bus.Lock.Lock() - defer bus.Lock.Unlock() - uch, found := bus.Channels[clientId] - if found { - close(uch.Ch) - uch.ScreenId = screenId - uch.Ch = make(chan interface{}, UpdateChSize) - } else { - uch = UpdateChannel{ - ClientId: clientId, - ScreenId: screenId, - Ch: make(chan interface{}, UpdateChSize), - } - } - bus.Channels[clientId] = uch - return uch.Ch -} - -func (bus *UpdateBus) UnregisterChannel(clientId string) { - bus.Lock.Lock() - defer bus.Lock.Unlock() - uch, found := bus.Channels[clientId] - if found { - close(uch.Ch) - delete(bus.Channels, clientId) - } -} - -func (bus *UpdateBus) SendUpdate(update UpdatePacket) { - if update == nil { - return - } - update.Clean() - bus.Lock.Lock() - defer bus.Lock.Unlock() - for _, uch := range bus.Channels { - select { - case uch.Ch <- update: - - default: - log.Printf("[error] dropped update on updatebus uch clientid=%s\n", uch.ClientId) - } - } -} - -func (bus *UpdateBus) SendScreenUpdate(screenId string, update UpdatePacket) { - if update == nil { - return - } - update.Clean() - bus.Lock.Lock() - defer bus.Lock.Unlock() - for _, uch := range bus.Channels { - if uch.Match(screenId) { - select { - case uch.Ch <- update: - - default: - log.Printf("[error] dropped update on updatebus uch clientid=%s\n", uch.ClientId) - } - } - } -} - -func (bus *UpdateBus) registerUserInputChannel() (string, chan *scpacket.UserInputResponsePacketType) { - bus.Lock.Lock() - defer bus.Lock.Unlock() - - id := uuid.New().String() - uich := make(chan *scpacket.UserInputResponsePacketType, 1) - - bus.UserInputCh[id] = uich - return id, uich -} - -func (bus *UpdateBus) unregisterUserInputChannel(id string) { - bus.Lock.Lock() - defer bus.Lock.Unlock() - - delete(bus.UserInputCh, id) -} - -func (bus *UpdateBus) GetUserInputChannel(id string) (chan *scpacket.UserInputResponsePacketType, bool) { - bus.Lock.Lock() - defer bus.Lock.Unlock() - - uich, ok := bus.UserInputCh[id] - return uich, ok -} - -func (bus *UpdateBus) GetUserInput(ctx context.Context, userInputRequest *UserInputRequestType) (*scpacket.UserInputResponsePacketType, error) { - id, uich := bus.registerUserInputChannel() - defer bus.unregisterUserInputChannel(id) - - userInputRequest.RequestId = id - deadline, _ := ctx.Deadline() - userInputRequest.TimeoutMs = int(time.Until(deadline).Milliseconds()) - 500 - update := &ModelUpdate{} - AddUpdate(update, *userInputRequest) - bus.SendUpdate(update) - - var response *scpacket.UserInputResponsePacketType - var err error - // prepare to receive response - select { - case resp := <-uich: - response = resp - case <-ctx.Done(): - return nil, fmt.Errorf("Timed out waiting for user input") - } - - if response.ErrorMsg != "" { - err = fmt.Errorf(response.ErrorMsg) - } - - return response, err -} diff --git a/wavesrv/pkg/sstore/updatetypes.go b/wavesrv/pkg/sstore/updatetypes.go index 0a8eb03d..6b9eb060 100644 --- a/wavesrv/pkg/sstore/updatetypes.go +++ b/wavesrv/pkg/sstore/updatetypes.go @@ -1,3 +1,6 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( @@ -5,11 +8,12 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" ) type ActiveSessionIdUpdate string -func (ActiveSessionIdUpdate) UpdateType() string { +func (ActiveSessionIdUpdate) GetType() string { return "activesessionid" } @@ -18,11 +22,11 @@ type LineUpdate struct { Cmd CmdType `json:"cmd,omitempty"` } -func (LineUpdate) UpdateType() string { +func (LineUpdate) GetType() string { return "line" } -func AddLineUpdate(update *ModelUpdate, newLine *LineType, newCmd *CmdType) { +func AddLineUpdate(update *scbus.ModelUpdatePacketType, newLine *LineType, newCmd *CmdType) { if newLine == nil { return } @@ -32,19 +36,15 @@ func AddLineUpdate(update *ModelUpdate, newLine *LineType, newCmd *CmdType) { if newCmd != nil { newLineUpdate.Cmd = *newCmd } - AddUpdate(update, newLineUpdate) + update.AddUpdate(newLineUpdate) } type CmdLineUpdate utilfn.StrWithPos -func (CmdLineUpdate) UpdateType() string { +func (CmdLineUpdate) GetType() string { return "cmdline" } -func AddCmdLineUpdate(update *ModelUpdate, cmdLine utilfn.StrWithPos) { - AddUpdate(update, CmdLineUpdate(cmdLine)) -} - type InfoMsgType struct { InfoTitle string `json:"infotitle"` InfoError string `json:"infoerror,omitempty"` @@ -57,21 +57,21 @@ type InfoMsgType struct { TimeoutMs int64 `json:"timeoutms,omitempty"` } -func (InfoMsgType) UpdateType() string { +func (InfoMsgType) GetType() string { return "info" } -func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate { +func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *scbus.ModelUpdatePacketType { msg := fmt.Sprintf(infoMsgFmt, args...) - ret := &ModelUpdate{} + ret := scbus.MakeUpdatePacket() newInfoUpdate := InfoMsgType{InfoMsg: msg} - AddUpdate(ret, newInfoUpdate) + ret.AddUpdate(newInfoUpdate) return ret } // only sets InfoError if InfoError is not already set -func AddInfoMsgUpdateError(update *ModelUpdate, errStr string) { - infoUpdates := GetUpdateItems[InfoMsgType](update) +func AddInfoMsgUpdateError(update *scbus.ModelUpdatePacketType, errStr string) { + infoUpdates := scbus.GetUpdateItems[InfoMsgType](update) if len(infoUpdates) > 0 { lastUpdate := infoUpdates[len(infoUpdates)-1] @@ -80,13 +80,13 @@ func AddInfoMsgUpdateError(update *ModelUpdate, errStr string) { return } } else { - AddUpdate(update, InfoMsgType{InfoError: errStr}) + update.AddUpdate(InfoMsgType{InfoError: errStr}) } } type ClearInfoUpdate bool -func (ClearInfoUpdate) UpdateType() string { +func (ClearInfoUpdate) GetType() string { return "clearinfo" } @@ -98,20 +98,16 @@ type HistoryInfoType struct { Show bool `json:"show"` } -func (HistoryInfoType) UpdateType() string { +func (HistoryInfoType) GetType() string { return "history" } type InteractiveUpdate bool -func (InteractiveUpdate) UpdateType() string { +func (InteractiveUpdate) GetType() string { return "interactive" } -func AddInteractiveUpdate(update *ModelUpdate, interactive bool) { - AddUpdate(update, InteractiveUpdate(interactive)) -} - type ConnectUpdate struct { Sessions []*SessionType `json:"sessions,omitempty"` Screens []*ScreenType `json:"screens,omitempty"` @@ -121,7 +117,7 @@ type ConnectUpdate struct { ActiveSessionId string `json:"activesessionid,omitempty"` } -func (ConnectUpdate) UpdateType() string { +func (ConnectUpdate) GetType() string { return "connect" } @@ -131,7 +127,7 @@ type MainViewUpdate struct { BookmarksView *BookmarksUpdate `json:"bookmarksview,omitempty"` } -func (MainViewUpdate) UpdateType() string { +func (MainViewUpdate) GetType() string { return "mainview" } @@ -140,15 +136,15 @@ type BookmarksUpdate struct { SelectedBookmark string `json:"selectedbookmark,omitempty"` } -func (BookmarksUpdate) UpdateType() string { +func (BookmarksUpdate) GetType() string { return "bookmarks" } -func AddBookmarksUpdate(update *ModelUpdate, bookmarks []*BookmarkType, selectedBookmark *string) { +func AddBookmarksUpdate(update *scbus.ModelUpdatePacketType, bookmarks []*BookmarkType, selectedBookmark *string) { if selectedBookmark == nil { - AddUpdate(update, BookmarksUpdate{Bookmarks: bookmarks}) + update.AddUpdate(BookmarksUpdate{Bookmarks: bookmarks}) } else { - AddUpdate(update, BookmarksUpdate{Bookmarks: bookmarks, SelectedBookmark: *selectedBookmark}) + update.AddUpdate(BookmarksUpdate{Bookmarks: bookmarks, SelectedBookmark: *selectedBookmark}) } } @@ -177,20 +173,16 @@ type RemoteViewType struct { RemoteEdit *RemoteEditType `json:"remoteedit,omitempty"` } -func (RemoteViewType) UpdateType() string { +func (RemoteViewType) GetType() string { return "remoteview" } type OpenAICmdInfoChatUpdate []*packet.OpenAICmdInfoChatMessage -func (OpenAICmdInfoChatUpdate) UpdateType() string { +func (OpenAICmdInfoChatUpdate) GetType() string { return "openaicmdinfochat" } -func AddOpenAICmdInfoChatUpdate(update *ModelUpdate, chatMessages []*packet.OpenAICmdInfoChatMessage) { - AddUpdate(update, OpenAICmdInfoChatUpdate(chatMessages)) -} - type AlertMessageType struct { Title string `json:"title,omitempty"` Message string `json:"message"` @@ -198,7 +190,7 @@ type AlertMessageType struct { Markdown bool `json:"markdown,omitempty"` } -func (AlertMessageType) UpdateType() string { +func (AlertMessageType) GetType() string { return "alertmessage" } @@ -207,7 +199,7 @@ type ScreenStatusIndicatorType struct { Status StatusIndicatorLevel `json:"status"` } -func (ScreenStatusIndicatorType) UpdateType() string { +func (ScreenStatusIndicatorType) GetType() string { return "screenstatusindicator" } @@ -216,19 +208,6 @@ type ScreenNumRunningCommandsType struct { Num int `json:"num"` } -func (ScreenNumRunningCommandsType) UpdateType() string { +func (ScreenNumRunningCommandsType) GetType() string { return "screennumrunningcommands" } - -type UserInputRequestType struct { - RequestId string `json:"requestid"` - QueryText string `json:"querytext"` - ResponseType string `json:"responsetype"` - Title string `json:"title"` - Markdown bool `json:"markdown"` - TimeoutMs int `json:"timeoutms"` -} - -func (UserInputRequestType) UpdateType() string { - return "userinputrequest" -} diff --git a/wavesrv/pkg/userinput/userinput.go b/wavesrv/pkg/userinput/userinput.go new file mode 100644 index 00000000..0ab0fe41 --- /dev/null +++ b/wavesrv/pkg/userinput/userinput.go @@ -0,0 +1,77 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Provides a mechanism for the backend to request user input from the frontend. +package userinput + +import ( + "context" + "fmt" + "reflect" + + "github.com/wavetermdev/waveterm/waveshell/pkg/packet" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" +) + +// An RpcPacket for requesting user input from the client +type UserInputRequestType struct { + RequestId string `json:"requestid"` + QueryText string `json:"querytext"` + ResponseType string `json:"responsetype"` + Title string `json:"title"` + Markdown bool `json:"markdown"` + TimeoutMs int `json:"timeoutms"` +} + +func (*UserInputRequestType) GetType() string { + return "userinputrequest" +} + +func (req *UserInputRequestType) SetReqId(reqId string) { + req.RequestId = reqId +} + +func (req *UserInputRequestType) SetTimeoutMs(timeoutMs int) { + req.TimeoutMs = timeoutMs +} + +const UserInputResponsePacketStr = "userinputresp" + +// An RpcResponse for user input requests +type UserInputResponsePacketType struct { + Type string `json:"type"` + RequestId string `json:"requestid"` + Text string `json:"text,omitempty"` + Confirm bool `json:"confirm,omitempty"` + ErrorMsg string `json:"errormsg,omitempty"` +} + +func (*UserInputResponsePacketType) GetType() string { + return UserInputResponsePacketStr +} + +func (pk *UserInputResponsePacketType) GetError() string { + return pk.ErrorMsg +} + +func (pk *UserInputResponsePacketType) SetError(err string) { + pk.ErrorMsg = err +} + +// Send a user input request to the frontend and wait for a response +func GetUserInput(ctx context.Context, bus *scbus.RpcBus, userInputRequest *UserInputRequestType) (*UserInputResponsePacketType, error) { + resp, err := scbus.MainRpcBus.DoRpc(ctx, userInputRequest) + if err != nil { + return nil, err + } + if ret, ok := resp.(*UserInputResponsePacketType); !ok { + return nil, fmt.Errorf("unexpected response type: %v", reflect.TypeOf(resp)) + } else { + return ret, nil + } +} + +func init() { + // Register the user input request packet type + packet.RegisterPacketType(UserInputResponsePacketStr, reflect.TypeOf(UserInputResponsePacketType{})) +}