2022-07-16 02:37:32 +02:00
package cmdrunner
import (
2022-08-23 23:01:52 +02:00
"bytes"
2022-07-16 02:37:32 +02:00
"context"
"fmt"
2022-10-31 20:40:45 +01:00
"log"
2022-09-01 21:47:10 +02:00
"os"
2022-08-11 03:33:32 +02:00
"regexp"
"sort"
2022-07-16 02:37:32 +02:00
"strconv"
"strings"
2022-10-30 20:52:40 +01:00
"syscall"
2022-08-12 08:45:15 +02:00
"time"
2022-07-16 02:37:32 +02:00
2022-08-23 03:53:38 +02:00
"github.com/alessio/shellescape"
2022-07-16 02:37:32 +02:00
"github.com/google/uuid"
"github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet"
2022-08-23 03:38:52 +02:00
"github.com/scripthaus-dev/mshell/pkg/shexec"
2022-11-10 22:52:51 +01:00
"github.com/scripthaus-dev/sh2-server/pkg/comp"
2022-07-16 02:37:32 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/remote"
2022-09-21 02:37:49 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
2022-07-16 02:37:32 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
2022-11-10 22:52:51 +01:00
"github.com/scripthaus-dev/sh2-server/pkg/utilfn"
2022-07-16 02:37:32 +02:00
)
2022-08-31 22:28:52 +02:00
const (
HistoryTypeWindow = "window"
HistoryTypeSession = "session"
HistoryTypeGlobal = "global"
)
2022-11-10 22:52:51 +01:00
func init ( ) {
2022-11-11 01:00:51 +01:00
comp . RegisterSimpleCompFn ( comp . CGTypeMeta , simpleCompMeta )
comp . RegisterSimpleCompFn ( comp . CGTypeCommandMeta , simpleCompCommandMeta )
2022-11-10 22:52:51 +01:00
}
2022-07-16 02:37:32 +02:00
const DefaultUserId = "sawka"
2022-08-27 01:21:19 +02:00
const MaxNameLen = 50
2022-09-01 21:47:10 +02:00
const MaxRemoteAliasLen = 50
2022-10-04 04:04:48 +02:00
const PasswordUnchangedSentinel = "--unchanged--"
2022-11-28 09:13:00 +01:00
const DefaultPTERM = "MxM"
2022-12-20 02:36:19 +01:00
const MaxCommandLen = 4096
2022-12-21 06:58:58 +01:00
const MaxSignalLen = 12
const MaxSignalNum = 64
2022-08-27 01:21:19 +02:00
2022-08-27 06:44:18 +02:00
var ColorNames = [ ] string { "black" , "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , "orange" }
2022-09-01 21:47:10 +02:00
var RemoteColorNames = [ ] string { "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , "orange" }
2022-10-04 04:04:48 +02:00
var RemoteSetArgs = [ ] string { "alias" , "connectmode" , "key" , "password" , "autoinstall" , "color" }
2022-08-27 06:44:18 +02:00
2022-12-21 06:58:58 +01:00
var WindowCmds = [ ] string { "run" , "comment" , "cd" , "cr" , "clear" , "sw" , "reset" , "signal" }
2022-12-20 02:36:19 +01:00
var NoHistCmds = [ ] string { "_compgen" , "line" , "_history" , "_killserver" }
var GlobalCmds = [ ] string { "session" , "screen" , "remote" , "set" }
2022-11-28 09:13:00 +01:00
var SetVarNameMap map [ string ] string = map [ string ] string {
"tabcolor" : "screen.tabcolor" ,
"pterm" : "window.pterm" ,
"anchor" : "sw.anchor" ,
"focus" : "sw.focus" ,
"line" : "sw.line" ,
}
var SetVarScopes = [ ] SetVarScope {
SetVarScope { ScopeName : "global" , VarNames : [ ] string { } } ,
SetVarScope { ScopeName : "session" , VarNames : [ ] string { "name" , "pos" } } ,
SetVarScope { ScopeName : "screen" , VarNames : [ ] string { "name" , "tabcolor" , "pos" } } ,
SetVarScope { ScopeName : "window" , VarNames : [ ] string { "pterm" } } ,
SetVarScope { ScopeName : "sw" , VarNames : [ ] string { "anchor" , "focus" , "line" } } ,
SetVarScope { ScopeName : "line" , VarNames : [ ] string { } } ,
// connection = remote, remote = remoteinstance
SetVarScope { ScopeName : "connection" , VarNames : [ ] string { "alias" , "connectmode" , "key" , "password" , "autoinstall" , "color" } } ,
SetVarScope { ScopeName : "remote" , VarNames : [ ] string { } } ,
}
2022-10-22 23:46:39 +02:00
2022-09-01 21:47:10 +02:00
var hostNameRe = regexp . MustCompile ( "^[a-z][a-z0-9.-]*$" )
2022-10-03 21:25:43 +02:00
var userHostRe = regexp . MustCompile ( "^(sudo@)?([a-z][a-z0-9-]*)@([a-z][a-z0-9.-]*)(?::([0-9]+))?$" )
2022-09-01 21:47:10 +02:00
var remoteAliasRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_-]*$" )
2022-08-27 01:24:07 +02:00
var genericNameRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$" )
2022-10-07 10:08:03 +02:00
var positionRe = regexp . MustCompile ( "^((S?\\+|E?-)?[0-9]+|(\\+|-|S|E))$" )
2022-08-27 01:24:07 +02:00
var wsRe = regexp . MustCompile ( "\\s+" )
2022-12-21 06:58:58 +01:00
var sigNameRe = regexp . MustCompile ( "^((SIG[A-Z0-9]+)|(\\d+))$" )
2022-08-27 01:21:19 +02:00
2022-09-13 21:06:12 +02:00
type contextType string
var historyContextKey = contextType ( "history" )
2022-11-28 09:13:00 +01:00
type SetVarScope struct {
ScopeName string
VarNames [ ] string
}
2022-09-13 21:06:12 +02:00
type historyContextType struct {
LineId string
CmdId string
RemotePtr * sstore . RemotePtrType
}
2022-08-27 02:17:33 +02:00
type MetaCmdFnType = func ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error )
type MetaCmdEntryType struct {
IsAlias bool
Fn MetaCmdFnType
}
2022-08-17 21:24:09 +02:00
2022-08-27 02:17:33 +02:00
var MetaCmdFnMap = make ( map [ string ] MetaCmdEntryType )
func init ( ) {
registerCmdFn ( "run" , RunCommand )
registerCmdFn ( "eval" , EvalCommand )
registerCmdFn ( "comment" , CommentCommand )
registerCmdFn ( "cr" , CrCommand )
2022-11-23 20:12:05 +01:00
registerCmdFn ( "_compgen" , CompGenCommand )
2022-08-27 07:01:29 +02:00
registerCmdFn ( "clear" , ClearCommand )
2022-12-20 02:36:19 +01:00
registerCmdFn ( "reset" , RemoteResetCommand )
2022-12-21 06:58:58 +01:00
registerCmdFn ( "signal" , SignalCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "session" , SessionCommand )
registerCmdFn ( "session:open" , SessionOpenCommand )
registerCmdAlias ( "session:new" , SessionOpenCommand )
registerCmdFn ( "session:set" , SessionSetCommand )
2022-09-13 21:06:12 +02:00
registerCmdFn ( "session:delete" , SessionDeleteCommand )
2022-12-25 22:03:11 +01:00
registerCmdFn ( "session:archive" , SessionArchiveCommand )
registerCmdFn ( "session:showall" , SessionShowAllCommand )
2022-12-27 04:06:46 +01:00
registerCmdFn ( "session:show" , SessionShowCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "screen" , ScreenCommand )
2022-12-25 22:21:48 +01:00
registerCmdFn ( "screen:archive" , ScreenArchiveCommand )
2022-12-24 00:56:29 +01:00
registerCmdFn ( "screen:purge" , ScreenPurgeCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "screen:open" , ScreenOpenCommand )
registerCmdAlias ( "screen:new" , ScreenOpenCommand )
2022-08-27 02:51:28 +02:00
registerCmdFn ( "screen:set" , ScreenSetCommand )
2022-12-24 00:56:29 +01:00
registerCmdFn ( "screen:showall" , ScreenShowAllCommand )
2022-08-27 02:17:33 +02:00
registerCmdAlias ( "remote" , RemoteCommand )
registerCmdFn ( "remote:show" , RemoteShowCommand )
2022-08-28 23:24:05 +02:00
registerCmdFn ( "remote:showall" , RemoteShowAllCommand )
2022-09-01 08:12:26 +02:00
registerCmdFn ( "remote:new" , RemoteNewCommand )
2022-09-13 21:06:12 +02:00
registerCmdFn ( "remote:archive" , RemoteArchiveCommand )
registerCmdFn ( "remote:set" , RemoteSetCommand )
2022-09-01 21:47:10 +02:00
registerCmdFn ( "remote:disconnect" , RemoteDisconnectCommand )
registerCmdFn ( "remote:connect" , RemoteConnectCommand )
2022-09-27 06:09:43 +02:00
registerCmdFn ( "remote:install" , RemoteInstallCommand )
2022-09-27 08:23:04 +02:00
registerCmdFn ( "remote:installcancel" , RemoteInstallCancelCommand )
2022-08-28 23:24:05 +02:00
2022-10-07 03:33:54 +02:00
registerCmdFn ( "sw:set" , SwSetCommand )
2022-10-22 23:46:39 +02:00
registerCmdFn ( "sw:resize" , SwResizeCommand )
2022-10-07 03:33:54 +02:00
2022-10-22 23:46:39 +02:00
// sw:resize
registerCmdFn ( "window:resize" , SwResizeCommand )
2022-09-06 05:08:59 +02:00
2022-09-13 21:06:12 +02:00
registerCmdFn ( "line" , LineCommand )
registerCmdFn ( "line:show" , LineShowCommand )
2022-12-06 07:59:00 +01:00
registerCmdFn ( "line:star" , LineStarCommand )
2022-12-28 08:12:27 +01:00
registerCmdFn ( "line:archive" , LineArchiveCommand )
2022-12-22 02:45:40 +01:00
registerCmdFn ( "line:purge" , LinePurgeCommand )
2022-09-13 21:06:12 +02:00
2022-12-20 02:36:19 +01:00
registerCmdFn ( "_history" , HistoryCommand )
2022-10-30 20:52:40 +01:00
2022-12-20 02:36:19 +01:00
registerCmdFn ( "_killserver" , KillServerCommand )
2022-11-28 09:13:00 +01:00
registerCmdFn ( "set" , SetCommand )
2022-08-27 02:17:33 +02:00
}
2022-08-11 03:33:32 +02:00
2022-08-27 02:17:33 +02:00
func getValidCommands ( ) [ ] string {
var rtn [ ] string
for key , val := range MetaCmdFnMap {
if val . IsAlias {
continue
}
2022-09-27 08:23:04 +02:00
rtn = append ( rtn , "/" + key )
2022-08-27 02:17:33 +02:00
}
return rtn
}
2022-08-21 21:31:29 +02:00
2022-08-27 02:17:33 +02:00
func registerCmdFn ( cmdName string , fn MetaCmdFnType ) {
MetaCmdFnMap [ cmdName ] = MetaCmdEntryType { Fn : fn }
}
2022-08-23 01:00:25 +02:00
2022-08-27 02:17:33 +02:00
func registerCmdAlias ( cmdName string , fn MetaCmdFnType ) {
MetaCmdFnMap [ cmdName ] = MetaCmdEntryType { IsAlias : true , Fn : fn }
}
2022-08-23 23:01:52 +02:00
2022-08-27 02:17:33 +02:00
func HandleCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
metaCmd := SubMetaCmd ( pk . MetaCmd )
var cmdName string
if pk . MetaSubCmd == "" {
cmdName = metaCmd
} else {
cmdName = fmt . Sprintf ( "%s:%s" , pk . MetaCmd , pk . MetaSubCmd )
}
entry := MetaCmdFnMap [ cmdName ]
if entry . Fn == nil {
if MetaCmdFnMap [ metaCmd ] . Fn != nil {
return nil , fmt . Errorf ( "invalid /%s subcommand '%s'" , metaCmd , pk . MetaSubCmd )
}
return nil , fmt . Errorf ( "invalid command '/%s', no handler" , cmdName )
2022-08-11 03:33:32 +02:00
}
2022-08-27 02:17:33 +02:00
return entry . Fn ( ctx , pk )
2022-08-11 03:33:32 +02:00
}
2022-07-16 02:37:32 +02:00
func firstArg ( pk * scpacket . FeCommandPacketType ) string {
if len ( pk . Args ) == 0 {
return ""
}
return pk . Args [ 0 ]
}
2022-08-09 23:24:57 +02:00
func argN ( pk * scpacket . FeCommandPacketType , n int ) string {
if len ( pk . Args ) <= n {
return ""
}
return pk . Args [ n ]
}
2022-07-16 02:37:32 +02:00
func resolveBool ( arg string , def bool ) bool {
if arg == "" {
return def
}
if arg == "0" || arg == "false" {
return false
}
return true
}
2022-11-28 09:13:00 +01:00
func defaultStr ( arg string , def string ) string {
if arg == "" {
return def
}
return arg
}
2022-10-01 22:23:36 +02:00
func resolveFile ( arg string ) ( string , error ) {
if arg == "" {
return "" , nil
}
fileName := base . ExpandHomeDir ( arg )
if ! strings . HasPrefix ( fileName , "/" ) {
return "" , fmt . Errorf ( "must be absolute, cannot be a relative path" )
}
fd , err := os . Open ( fileName )
if fd != nil {
fd . Close ( )
}
if err != nil {
return "" , fmt . Errorf ( "cannot open file: %v" , err )
}
return fileName , nil
}
func resolvePosInt ( arg string , def int ) ( int , error ) {
2022-08-28 23:24:05 +02:00
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
2022-10-01 22:23:36 +02:00
if ival <= 0 {
return 0 , fmt . Errorf ( "must be greater than 0" )
}
2022-08-28 23:24:05 +02:00
return ival , nil
}
2022-12-21 06:58:58 +01:00
func isAllDigits ( arg string ) bool {
if len ( arg ) == 0 {
return false
}
for i := 0 ; i < len ( arg ) ; i ++ {
if arg [ i ] >= '0' && arg [ i ] <= '9' {
continue
}
return false
}
return true
}
2022-10-07 03:33:54 +02:00
func resolveNonNegInt ( arg string , def int ) ( int , error ) {
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
if ival < 0 {
return 0 , fmt . Errorf ( "cannot be negative" )
}
return ival , nil
}
2022-07-16 02:37:32 +02:00
func RunCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-10-11 02:30:48 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window | R_RemoteConnected )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/run error: %w" , err )
2022-07-16 02:37:32 +02:00
}
cmdStr := firstArg ( pk )
2022-10-27 09:33:50 +02:00
isRtnStateCmd := IsReturnStateCommand ( cmdStr )
2022-11-28 09:13:00 +01:00
// runPacket.State is set in remote.RunCommand()
2022-07-16 02:37:32 +02:00
runPacket := packet . MakeRunPacket ( )
runPacket . ReqId = uuid . New ( ) . String ( )
2022-12-20 03:52:08 +01:00
runPacket . CK = base . MakeCommandKey ( ids . SessionId , scbase . GenPromptUUID ( ) )
2022-07-16 02:37:32 +02:00
runPacket . UsePty = true
2022-11-28 09:13:00 +01:00
ptermVal := defaultStr ( pk . Kwargs [ "pterm" ] , DefaultPTERM )
runPacket . TermOpts , err = GetUITermOpts ( pk . UIContext . WinSize , ptermVal )
if err != nil {
return nil , fmt . Errorf ( "/run error, invalid 'pterm' value %q: %v" , ptermVal , err )
}
2022-07-16 02:37:32 +02:00
runPacket . Command = strings . TrimSpace ( cmdStr )
2022-10-27 09:33:50 +02:00
runPacket . ReturnState = resolveBool ( pk . Kwargs [ "rtnstate" ] , isRtnStateCmd )
2022-10-28 07:00:10 +02:00
cmd , callback , err := remote . RunCommand ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , runPacket )
2022-09-05 23:49:23 +02:00
if callback != nil {
defer callback ( )
}
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-10-19 03:03:02 +02:00
update , err := addLineForCmd ( ctx , "/run" , true , ids , cmd )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-10-19 03:03:02 +02:00
update . Interactive = pk . Interactive
2022-09-05 23:54:17 +02:00
sstore . MainBus . SendUpdate ( ids . SessionId , update )
return nil , nil
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
func addToHistory ( ctx context . Context , pk * scpacket . FeCommandPacketType , historyContext historyContextType , isMetaCmd bool , hadError bool ) error {
2022-08-12 08:45:15 +02:00
cmdStr := firstArg ( pk )
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
2022-08-12 08:45:15 +02:00
if err != nil {
return err
}
2022-12-20 03:52:08 +01:00
isIncognito , err := sstore . IsIncognitoScreen ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return fmt . Errorf ( "cannot add to history, error looking up incognito status of screen: %v" , err )
}
2022-08-12 08:45:15 +02:00
hitem := & sstore . HistoryItemType {
2022-12-20 03:52:08 +01:00
HistoryId : scbase . GenPromptUUID ( ) ,
2022-08-12 08:45:15 +02:00
Ts : time . Now ( ) . UnixMilli ( ) ,
UserId : DefaultUserId ,
SessionId : ids . SessionId ,
ScreenId : ids . ScreenId ,
WindowId : ids . WindowId ,
2022-09-13 21:06:12 +02:00
LineId : historyContext . LineId ,
2022-08-12 08:45:15 +02:00
HadError : hadError ,
2022-09-13 21:06:12 +02:00
CmdId : historyContext . CmdId ,
2022-08-12 08:45:15 +02:00
CmdStr : cmdStr ,
2022-08-28 23:24:05 +02:00
IsMetaCmd : isMetaCmd ,
2022-12-20 03:52:08 +01:00
Incognito : isIncognito ,
2022-08-28 23:24:05 +02:00
}
2022-09-13 21:06:12 +02:00
if ! isMetaCmd && historyContext . RemotePtr != nil {
hitem . Remote = * historyContext . RemotePtr
2022-08-12 08:45:15 +02:00
}
err = sstore . InsertHistoryItem ( ctx , hitem )
if err != nil {
return err
}
2022-08-11 21:07:41 +02:00
return nil
}
2022-07-16 02:37:32 +02:00
func EvalCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "usage: /eval [command], no command passed to eval" )
2022-07-16 02:37:32 +02:00
}
2022-12-20 02:36:19 +01:00
if len ( pk . Args [ 0 ] ) > MaxCommandLen {
return nil , fmt . Errorf ( "command length too long len:%d, max:%d" , len ( pk . Args [ 0 ] ) , MaxCommandLen )
}
2022-09-13 21:06:12 +02:00
var historyContext historyContextType
ctxWithHistory := context . WithValue ( ctx , historyContextKey , & historyContext )
2022-08-31 09:01:42 +02:00
var update sstore . UpdatePacket
2022-09-13 21:06:12 +02:00
newPk , rtnErr := EvalMetaCommand ( ctxWithHistory , pk )
2022-08-31 09:01:42 +02:00
if rtnErr == nil {
2022-09-13 21:06:12 +02:00
update , rtnErr = HandleCommand ( ctxWithHistory , newPk )
2022-07-16 02:37:32 +02:00
}
2022-08-11 21:07:41 +02:00
if ! resolveBool ( pk . Kwargs [ "nohist" ] , false ) {
2022-09-13 21:06:12 +02:00
err := addToHistory ( ctx , pk , historyContext , ( newPk . MetaCmd != "run" ) , ( rtnErr != nil ) )
2022-08-12 08:45:15 +02:00
if err != nil {
2022-10-31 20:40:45 +01:00
log . Printf ( "[error] adding to history: %v\n" , err )
2022-08-12 08:45:15 +02:00
// continue...
}
2022-08-11 21:07:41 +02:00
}
2022-08-31 09:01:42 +02:00
return update , rtnErr
2022-08-12 08:45:15 +02:00
}
2022-12-25 22:21:48 +01:00
func ScreenArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-12-24 00:56:29 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session ) // don't force R_Screen
2022-08-27 02:17:33 +02:00
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot archive screen: %w" , err )
2022-07-16 02:37:32 +02:00
}
2022-12-24 00:56:29 +01:00
screenId := ids . ScreenId
if len ( pk . Args ) > 0 {
ri , err := resolveSessionScreen ( ctx , ids . SessionId , pk . Args [ 0 ] , ids . ScreenId )
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot resolve screen arg: %v" , err )
2022-12-24 00:56:29 +01:00
}
screenId = ri . Id
}
if screenId == "" {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive no active screen or screen arg passed" )
2022-12-24 00:56:29 +01:00
}
2022-12-25 22:21:48 +01:00
archiveVal := true
2022-12-24 00:56:29 +01:00
if len ( pk . Args ) > 1 {
2022-12-25 22:21:48 +01:00
archiveVal = resolveBool ( pk . Args [ 1 ] , true )
2022-12-24 00:56:29 +01:00
}
var update sstore . UpdatePacket
2022-12-25 22:21:48 +01:00
if archiveVal {
update , err = sstore . ArchiveScreen ( ctx , ids . SessionId , screenId )
2022-12-24 00:56:29 +01:00
if err != nil {
return nil , err
}
return update , nil
} else {
2022-12-25 22:21:48 +01:00
fmt . Printf ( "unarchive screen %s\n" , screenId )
err = sstore . UnArchiveScreen ( ctx , ids . SessionId , screenId )
2022-12-24 00:56:29 +01:00
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot re-open screen: %v" , err )
2022-12-24 00:56:29 +01:00
}
screen , err := sstore . GetScreenById ( ctx , ids . SessionId , screenId )
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:archive cannot get updated screen obj: %v" , err )
2022-12-24 00:56:29 +01:00
}
2022-12-27 01:09:21 +01:00
bareSession , err := sstore . GetBareSessionById ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "/screen:archive cannot retrieve updated session obj: %v" , err )
}
bareSession . Screens = append ( bareSession . Screens , screen )
update := sstore . ModelUpdate {
Sessions : [ ] * sstore . SessionType { bareSession } ,
}
2022-12-24 00:56:29 +01:00
return update , nil
}
}
func ScreenPurgeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
if err != nil {
2022-12-25 22:21:48 +01:00
return nil , fmt . Errorf ( "/screen:purge cannot purge screen: %w" , err )
2022-12-24 00:56:29 +01:00
}
2022-08-27 02:17:33 +02:00
update , err := sstore . DeleteScreen ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return nil , err
}
return update , nil
}
func ScreenOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , fmt . Errorf ( "/screen:open cannot open screen: %w" , err )
}
activate := resolveBool ( pk . Kwargs [ "activate" ] , true )
newName := pk . Kwargs [ "name" ]
if newName != "" {
err := validateName ( newName , "screen" )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
}
2022-08-27 02:17:33 +02:00
update , err := sstore . InsertScreen ( ctx , ids . SessionId , newName , activate )
if err != nil {
return nil , err
2022-07-16 02:37:32 +02:00
}
2022-08-27 02:17:33 +02:00
return update , nil
}
2022-08-27 02:51:28 +02:00
func ScreenSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-27 02:51:28 +02:00
if err != nil {
return nil , err
}
var varsUpdated [ ] string
if pk . Kwargs [ "name" ] != "" {
newName := pk . Kwargs [ "name" ]
err = validateName ( newName , "screen" )
if err != nil {
return nil , err
}
err = sstore . SetScreenName ( ctx , ids . SessionId , ids . ScreenId , newName )
if err != nil {
return nil , fmt . Errorf ( "setting screen name: %v" , err )
}
varsUpdated = append ( varsUpdated , "name" )
}
2022-08-27 06:44:18 +02:00
if pk . Kwargs [ "tabcolor" ] != "" {
color := pk . Kwargs [ "tabcolor" ]
err = validateColor ( color , "screen tabcolor" )
if err != nil {
return nil , err
}
screenObj , err := sstore . GetScreenById ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return nil , err
}
opts := screenObj . ScreenOpts
if opts == nil {
opts = & sstore . ScreenOptsType { }
}
opts . TabColor = color
err = sstore . SetScreenOpts ( ctx , ids . SessionId , ids . ScreenId , opts )
if err != nil {
return nil , fmt . Errorf ( "setting screen opts: %v" , err )
}
varsUpdated = append ( varsUpdated , "tabcolor" )
}
2022-08-27 02:51:28 +02:00
if len ( varsUpdated ) == 0 {
2022-08-27 06:44:18 +02:00
return nil , fmt . Errorf ( "/screen:set no updates, can set %s" , formatStrs ( [ ] string { "name" , "pos" , "tabcolor" } , "or" , false ) )
2022-08-27 02:51:28 +02:00
}
screenObj , err := sstore . GetScreenById ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return nil , err
}
2022-12-27 01:09:21 +01:00
bareSession , err := sstore . GetBareSessionById ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "/screen:set cannot retrieve session: %v" , err )
}
bareSession . Screens = append ( bareSession . Screens , screenObj )
update := sstore . ModelUpdate {
Sessions : [ ] * sstore . SessionType { bareSession } ,
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "screen updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
TimeoutMs : 2000 ,
} ,
2022-08-27 02:51:28 +02:00
}
return update , nil
}
2022-08-27 02:17:33 +02:00
func ScreenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/screen cannot switch to screen: %w" , err )
2022-07-16 02:37:32 +02:00
}
firstArg := firstArg ( pk )
if firstArg == "" {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "usage /screen [screen-name|screen-index|screen-id], no param specified" )
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
ritem , err := resolveSessionScreen ( ctx , ids . SessionId , firstArg , ids . ScreenId )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:29:32 +02:00
update , err := sstore . SwitchScreenById ( ctx , ids . SessionId , ritem . Id )
2022-07-16 02:53:23 +02:00
if err != nil {
return nil , err
}
return update , nil
2022-07-16 02:37:32 +02:00
}
2022-10-11 22:23:38 +02:00
var swAnchorRe = regexp . MustCompile ( "^(\\d+)(?::(-?\\d+))?$" )
2022-10-11 10:11:04 +02:00
2022-10-07 03:33:54 +02:00
func SwSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , fmt . Errorf ( "/sw:set cannot resolve current screen-window: %w" , err )
}
var setNonST bool // scrolltop does not receive an update
updateMap := make ( map [ string ] interface { } )
2022-10-11 10:11:04 +02:00
if pk . Kwargs [ "anchor" ] != "" {
m := swAnchorRe . FindStringSubmatch ( pk . Kwargs [ "anchor" ] )
if m == nil {
return nil , fmt . Errorf ( "/sw:set invalid anchor argument (must be [line] or [line]:[offset])" )
}
anchorLine , _ := strconv . Atoi ( m [ 1 ] )
updateMap [ sstore . SWField_AnchorLine ] = anchorLine
if m [ 2 ] != "" {
anchorOffset , _ := strconv . Atoi ( m [ 2 ] )
updateMap [ sstore . SWField_AnchorOffset ] = anchorOffset
2022-10-07 03:33:54 +02:00
}
2022-10-11 10:11:04 +02:00
}
if pk . Kwargs [ "focus" ] != "" {
focusVal := pk . Kwargs [ "focus" ]
if focusVal != sstore . SWFocusInput && focusVal != sstore . SWFocusCmd && focusVal != sstore . SWFocusCmdFg {
return nil , fmt . Errorf ( "/sw:set invalid focus argument %q, must be %s" , focusVal , formatStrs ( [ ] string { sstore . SWFocusInput , sstore . SWFocusCmd , sstore . SWFocusCmdFg } , "or" , false ) )
}
updateMap [ sstore . SWField_Focus ] = focusVal
setNonST = true
2022-10-07 03:33:54 +02:00
}
2022-10-07 10:08:03 +02:00
if pk . Kwargs [ "line" ] != "" {
sw , err := sstore . GetScreenWindowByIds ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId )
if err != nil {
return nil , fmt . Errorf ( "/sw:set cannot get screen-window: %v" , err )
}
var selectedLineStr string
if sw . SelectedLine > 0 {
selectedLineStr = strconv . Itoa ( sw . SelectedLine )
}
ritem , err := resolveLine ( ctx , ids . SessionId , ids . WindowId , pk . Kwargs [ "line" ] , selectedLineStr )
if err != nil {
return nil , fmt . Errorf ( "/sw:set error resolving line: %v" , err )
}
if ritem == nil {
return nil , fmt . Errorf ( "/sw:set could not resolve line %q" , pk . Kwargs [ "line" ] )
}
setNonST = true
updateMap [ sstore . SWField_SelectedLine ] = ritem . Num
}
2022-10-07 03:33:54 +02:00
if len ( updateMap ) == 0 {
2022-10-11 10:11:04 +02:00
return nil , fmt . Errorf ( "/sw:set no updates, can set %s" , formatStrs ( [ ] string { "line" , "scrolltop" , "focus" } , "or" , false ) )
2022-10-07 03:33:54 +02:00
}
sw , err := sstore . UpdateScreenWindow ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId , updateMap )
if err != nil {
return nil , fmt . Errorf ( "/sw:set failed to update: %v" , err )
}
if ! setNonST {
return nil , nil
}
2022-10-11 10:11:04 +02:00
return sstore . ModelUpdate { ScreenWindows : [ ] * sstore . ScreenWindowType { sw } } , nil
2022-10-07 03:33:54 +02:00
}
2022-09-27 06:09:43 +02:00
func RemoteInstallCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-27 08:23:04 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
mshell := ids . Remote . MShell
go mshell . RunInstall ( )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-27 06:09:43 +02:00
}
2022-09-27 08:23:04 +02:00
func RemoteInstallCancelCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-01 21:47:10 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
2022-09-27 08:23:04 +02:00
mshell := ids . Remote . MShell
go mshell . CancelInstall ( )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
}
func RemoteConnectCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
2022-09-16 21:28:09 +02:00
}
2022-09-01 21:47:10 +02:00
go ids . Remote . MShell . Launch ( )
2022-09-27 08:23:04 +02:00
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-01 21:47:10 +02:00
}
func RemoteDisconnectCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
force := resolveBool ( pk . Kwargs [ "force" ] , false )
2022-09-27 08:23:04 +02:00
go ids . Remote . MShell . Disconnect ( force )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-01 21:47:10 +02:00
}
2022-10-04 04:04:48 +02:00
func makeRemoteEditUpdate_new ( err error ) sstore . UpdatePacket {
redit := & sstore . RemoteEditType {
RemoteEdit : true ,
}
2022-09-30 23:46:51 +02:00
if err != nil {
2022-10-04 04:04:48 +02:00
redit . ErrorStr = err . Error ( )
2022-09-30 23:46:51 +02:00
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-10-04 04:04:48 +02:00
RemoteEdit : redit ,
2022-09-30 23:46:51 +02:00
} ,
}
2022-10-04 04:04:48 +02:00
return update
2022-09-30 23:46:51 +02:00
}
2022-10-04 04:04:48 +02:00
func makeRemoteEditErrorReturn_new ( visual bool , err error ) ( sstore . UpdatePacket , error ) {
2022-09-30 23:46:51 +02:00
if visual {
2022-10-04 04:04:48 +02:00
return makeRemoteEditUpdate_new ( err ) , nil
}
return nil , err
}
func makeRemoteEditUpdate_edit ( ids resolvedIds , err error ) sstore . UpdatePacket {
redit := & sstore . RemoteEditType {
RemoteEdit : true ,
}
redit . RemoteId = ids . Remote . RemotePtr . RemoteId
if ids . Remote . RemoteCopy . SSHOpts != nil {
redit . KeyStr = ids . Remote . RemoteCopy . SSHOpts . SSHIdentity
redit . HasPassword = ( ids . Remote . RemoteCopy . SSHOpts . SSHPassword != "" )
}
if err != nil {
redit . ErrorStr = err . Error ( )
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
RemoteEdit : redit ,
} ,
}
return update
}
func makeRemoteEditErrorReturn_edit ( ids resolvedIds , visual bool , err error ) ( sstore . UpdatePacket , error ) {
if visual {
return makeRemoteEditUpdate_edit ( ids , err ) , nil
2022-09-30 23:46:51 +02:00
}
return nil , err
}
2022-10-01 22:23:36 +02:00
type RemoteEditArgs struct {
2022-10-03 21:25:43 +02:00
CanonicalName string
SSHOpts * sstore . SSHOpts
Sudo bool
ConnectMode string
Alias string
AutoInstall bool
SSHPassword string
SSHKeyFile string
Color string
EditMap map [ string ] interface { }
2022-10-01 22:23:36 +02:00
}
func parseRemoteEditArgs ( isNew bool , pk * scpacket . FeCommandPacketType ) ( * RemoteEditArgs , error ) {
2022-10-03 21:25:43 +02:00
var canonicalName string
2022-10-01 22:23:36 +02:00
var sshOpts * sstore . SSHOpts
var isSudo bool
if isNew {
2022-10-04 04:04:48 +02:00
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/remote:new must specify user@host argument (set visual=1 to edit in UI)" )
}
2022-10-03 21:25:43 +02:00
userHost := pk . Args [ 0 ]
2022-10-01 22:23:36 +02:00
m := userHostRe . FindStringSubmatch ( userHost )
if m == nil {
return nil , fmt . Errorf ( "invalid format of user@host argument" )
}
2022-10-03 21:25:43 +02:00
sudoStr , remoteUser , remoteHost , remotePortStr := m [ 1 ] , m [ 2 ] , m [ 3 ] , m [ 4 ]
var uhPort int
if remotePortStr != "" {
var err error
uhPort , err = strconv . Atoi ( remotePortStr )
if err != nil {
return nil , fmt . Errorf ( "invalid port specified on user@host argument" )
}
}
2022-10-01 22:23:36 +02:00
if sudoStr != "" {
isSudo = true
}
if pk . Kwargs [ "sudo" ] != "" {
sudoArg := resolveBool ( pk . Kwargs [ "sudo" ] , false )
if isSudo && ! sudoArg {
2022-10-03 21:25:43 +02:00
return nil , fmt . Errorf ( "invalid 'sudo' argument, with sudo kw arg set to false" )
2022-10-01 22:23:36 +02:00
}
if ! isSudo && sudoArg {
isSudo = true
}
}
sshOpts = & sstore . SSHOpts {
Local : false ,
SSHHost : remoteHost ,
SSHUser : remoteUser ,
}
portVal , err := resolvePosInt ( pk . Kwargs [ "port" ] , 0 )
if err != nil {
return nil , fmt . Errorf ( "invalid port %q: %v" , pk . Kwargs [ "port" ] , err )
}
2022-10-03 21:25:43 +02:00
if portVal != 0 && uhPort != 0 && portVal != uhPort {
return nil , fmt . Errorf ( "invalid port argument, does not match port specified in 'user@host:port' argument" )
}
if portVal == 0 && uhPort != 0 {
portVal = uhPort
}
2022-10-01 22:23:36 +02:00
sshOpts . SSHPort = portVal
2022-10-03 21:25:43 +02:00
canonicalName = remoteUser + "@" + remoteHost
if isSudo {
canonicalName = "sudo@" + canonicalName
}
2022-10-01 22:23:36 +02:00
} else {
if pk . Kwargs [ "sudo" ] != "" {
return nil , fmt . Errorf ( "cannot update 'sudo' value" )
}
if pk . Kwargs [ "port" ] != "" {
return nil , fmt . Errorf ( "cannot update 'port' value" )
}
2022-09-01 21:47:10 +02:00
}
alias := pk . Kwargs [ "alias" ]
if alias != "" {
if len ( alias ) > MaxRemoteAliasLen {
2022-10-01 22:23:36 +02:00
return nil , fmt . Errorf ( "alias too long, max length = %d" , MaxRemoteAliasLen )
2022-09-01 21:47:10 +02:00
}
if ! remoteAliasRe . MatchString ( alias ) {
2022-10-01 22:23:36 +02:00
return nil , fmt . Errorf ( "invalid alias format" )
2022-09-01 21:47:10 +02:00
}
}
2022-10-03 03:52:55 +02:00
var connectMode string
if isNew {
connectMode = sstore . ConnectModeAuto
}
2022-09-01 21:47:10 +02:00
if pk . Kwargs [ "connectmode" ] != "" {
connectMode = pk . Kwargs [ "connectmode" ]
}
2022-10-03 03:52:55 +02:00
if connectMode != "" && ! sstore . IsValidConnectMode ( connectMode ) {
2022-10-01 22:23:36 +02:00
err := fmt . Errorf ( "invalid connectmode %q: valid modes are %s" , connectMode , formatStrs ( [ ] string { sstore . ConnectModeStartup , sstore . ConnectModeAuto , sstore . ConnectModeManual } , "or" , false ) )
return nil , err
2022-09-01 21:47:10 +02:00
}
2022-09-25 07:42:52 +02:00
autoInstall := resolveBool ( pk . Kwargs [ "autoinstall" ] , true )
2022-10-01 22:23:36 +02:00
keyFile , err := resolveFile ( pk . Kwargs [ "key" ] )
if err != nil {
return nil , fmt . Errorf ( "invalid ssh keyfile %q: %v" , pk . Kwargs [ "key" ] , err )
2022-10-01 01:23:40 +02:00
}
2022-10-01 22:23:36 +02:00
color := pk . Kwargs [ "color" ]
if color != "" {
2022-09-01 21:47:10 +02:00
err := validateRemoteColor ( color , "remote color" )
if err != nil {
2022-10-01 22:23:36 +02:00
return nil , err
2022-09-01 21:47:10 +02:00
}
}
2022-10-01 22:23:36 +02:00
sshPassword := pk . Kwargs [ "password" ]
if sshOpts != nil {
sshOpts . SSHIdentity = keyFile
sshOpts . SSHPassword = sshPassword
}
2022-10-03 03:52:55 +02:00
// set up editmap
editMap := make ( map [ string ] interface { } )
if _ , found := pk . Kwargs [ sstore . RemoteField_Alias ] ; found {
editMap [ sstore . RemoteField_Alias ] = alias
}
if connectMode != "" {
editMap [ sstore . RemoteField_ConnectMode ] = connectMode
}
if _ , found := pk . Kwargs [ sstore . RemoteField_AutoInstall ] ; found {
editMap [ sstore . RemoteField_AutoInstall ] = autoInstall
}
if _ , found := pk . Kwargs [ "key" ] ; found {
editMap [ sstore . RemoteField_SSHKey ] = keyFile
}
if _ , found := pk . Kwargs [ sstore . RemoteField_Color ] ; found {
editMap [ sstore . RemoteField_Color ] = color
}
2022-10-04 04:04:48 +02:00
if _ , found := pk . Kwargs [ "password" ] ; found && pk . Kwargs [ "password" ] != PasswordUnchangedSentinel {
2022-10-03 03:52:55 +02:00
editMap [ sstore . RemoteField_SSHPassword ] = sshPassword
}
2022-10-01 22:23:36 +02:00
return & RemoteEditArgs {
2022-10-03 21:25:43 +02:00
SSHOpts : sshOpts ,
Sudo : isSudo ,
ConnectMode : connectMode ,
Alias : alias ,
AutoInstall : autoInstall ,
CanonicalName : canonicalName ,
SSHKeyFile : keyFile ,
SSHPassword : sshPassword ,
Color : color ,
EditMap : editMap ,
2022-10-01 22:23:36 +02:00
} , nil
}
func RemoteNewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
visualEdit := resolveBool ( pk . Kwargs [ "visual" ] , false )
isSubmitted := resolveBool ( pk . Kwargs [ "submit" ] , false )
2022-10-04 04:04:48 +02:00
if visualEdit && ! isSubmitted && len ( pk . Args ) == 0 {
return makeRemoteEditUpdate_new ( nil ) , nil
2022-10-01 22:23:36 +02:00
}
editArgs , err := parseRemoteEditArgs ( true , pk )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_new ( visualEdit , fmt . Errorf ( "/remote:new %v" , err ) )
2022-10-01 02:22:28 +02:00
}
2022-09-01 21:47:10 +02:00
r := & sstore . RemoteType {
2022-12-20 03:52:08 +01:00
RemoteId : scbase . GenPromptUUID ( ) ,
2022-09-01 21:47:10 +02:00
PhysicalId : "" ,
RemoteType : sstore . RemoteTypeSsh ,
2022-10-01 22:23:36 +02:00
RemoteAlias : editArgs . Alias ,
2022-10-03 21:25:43 +02:00
RemoteCanonicalName : editArgs . CanonicalName ,
2022-10-01 22:23:36 +02:00
RemoteSudo : editArgs . Sudo ,
RemoteUser : editArgs . SSHOpts . SSHUser ,
RemoteHost : editArgs . SSHOpts . SSHHost ,
ConnectMode : editArgs . ConnectMode ,
AutoInstall : editArgs . AutoInstall ,
SSHOpts : editArgs . SSHOpts ,
}
if editArgs . Color != "" {
r . RemoteOpts = & sstore . RemoteOptsType { Color : editArgs . Color }
}
err = remote . AddRemote ( ctx , r )
2022-09-01 21:47:10 +02:00
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_new ( visualEdit , fmt . Errorf ( "cannot create remote %q: %v" , r . RemoteCanonicalName , err ) )
2022-09-01 21:47:10 +02:00
}
2022-09-30 23:46:51 +02:00
// SUCCESS
update := sstore . ModelUpdate {
2022-09-01 21:47:10 +02:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "remote %q created" , r . RemoteCanonicalName ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-09-01 08:12:26 +02:00
}
2022-09-13 21:06:12 +02:00
func RemoteSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-10-03 03:52:55 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , err
}
2022-10-03 03:52:55 +02:00
visualEdit := resolveBool ( pk . Kwargs [ "visual" ] , false )
isSubmitted := resolveBool ( pk . Kwargs [ "submit" ] , false )
editArgs , err := parseRemoteEditArgs ( false , pk )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_edit ( ids , visualEdit , fmt . Errorf ( "/remote:new %v" , err ) )
2022-10-03 03:52:55 +02:00
}
if visualEdit && ! isSubmitted && len ( editArgs . EditMap ) == 0 {
2022-10-04 04:04:48 +02:00
return makeRemoteEditUpdate_edit ( ids , nil ) , nil
}
if ! visualEdit && len ( editArgs . EditMap ) == 0 {
return nil , fmt . Errorf ( "/remote:set no updates, can set %s. (set visual=1 to edit in UI)" , formatStrs ( RemoteSetArgs , "or" , false ) )
2022-10-03 03:52:55 +02:00
}
err = ids . Remote . MShell . UpdateRemote ( ctx , editArgs . EditMap )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_edit ( ids , visualEdit , fmt . Errorf ( "/remote:new error updating remote: %v" , err ) )
2022-10-03 03:52:55 +02:00
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "remote %q updated" , ids . Remote . DisplayName ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-09-13 21:06:12 +02:00
}
2022-08-27 02:17:33 +02:00
func RemoteShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
2022-08-23 23:01:52 +02:00
}
2022-08-30 01:31:06 +02:00
state := ids . Remote . RState
2022-08-27 02:17:33 +02:00
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-09-15 09:17:23 +02:00
PtyRemoteId : state . RemoteId ,
2022-08-28 23:24:05 +02:00
} ,
} , nil
}
func RemoteShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
stateArr := remote . GetAllRemoteRuntimeState ( )
2022-08-28 23:24:05 +02:00
var buf bytes . Buffer
for _ , rstate := range stateArr {
var name string
if rstate . RemoteAlias == "" {
name = rstate . RemoteCanonicalName
} else {
name = fmt . Sprintf ( "%s (%s)" , rstate . RemoteCanonicalName , rstate . RemoteAlias )
}
buf . WriteString ( fmt . Sprintf ( "%-12s %-5s %8s %s\n" , rstate . Status , rstate . RemoteType , rstate . RemoteId [ 0 : 8 ] , name ) )
}
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-09-25 04:54:06 +02:00
RemoteShowAll : true ,
2022-08-27 02:17:33 +02:00
} ,
} , nil
2022-08-23 23:01:52 +02:00
}
2022-12-24 00:56:29 +01:00
func ScreenShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-12-26 21:38:47 +01:00
screenArr , err := sstore . GetBareSessionScreens ( ctx , ids . SessionId )
2022-12-24 00:56:29 +01:00
if err != nil {
return nil , fmt . Errorf ( "/screen:showall error getting screen list: %v" , err )
}
var buf bytes . Buffer
for _ , screen := range screenArr {
2022-12-25 22:21:48 +01:00
var archivedStr string
2022-12-25 22:03:11 +01:00
if screen . Archived {
2022-12-25 22:21:48 +01:00
archivedStr = " (archived)"
2022-12-24 00:56:29 +01:00
}
screenIdxStr := "-"
if screen . ScreenIdx != 0 {
screenIdxStr = strconv . Itoa ( int ( screen . ScreenIdx ) )
}
2022-12-27 01:09:21 +01:00
outStr := fmt . Sprintf ( "%-30s %s %s\n" , screen . Name + archivedStr , screen . ScreenId , screenIdxStr )
2022-12-24 00:56:29 +01:00
buf . WriteString ( outStr )
}
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "all screens for session" ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , nil
}
2022-09-13 21:06:12 +02:00
func RemoteArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
2022-09-14 02:11:36 +02:00
err = remote . ArchiveRemote ( ctx , ids . Remote . RemotePtr . RemoteId )
if err != nil {
return nil , fmt . Errorf ( "archiving remote: %v" , err )
}
2022-09-13 21:06:12 +02:00
update := sstore . InfoMsgUpdate ( "remote [%s] archived" , ids . Remote . DisplayName )
2022-10-04 20:45:24 +02:00
localRemote := remote . GetLocalRemote ( )
if localRemote != nil {
update . Window = & sstore . WindowType {
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
CurRemote : sstore . RemotePtrType { RemoteId : localRemote . GetRemoteId ( ) } ,
}
}
2022-09-13 21:06:12 +02:00
return update , nil
}
2022-08-27 02:17:33 +02:00
func RemoteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
return nil , fmt . Errorf ( "/remote requires a subcommand: %s" , formatStrs ( [ ] string { "show" } , "or" , false ) )
2022-08-23 01:00:25 +02:00
}
2022-08-17 21:24:09 +02:00
func CrCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window )
2022-08-17 21:24:09 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cr error: %w" , err )
2022-08-17 21:24:09 +02:00
}
newRemote := firstArg ( pk )
if newRemote == "" {
return nil , nil
}
2022-11-28 09:13:00 +01:00
remoteName , rptr , rstate , err := resolveRemote ( ctx , newRemote , ids . SessionId , ids . WindowId )
2022-08-17 21:24:09 +02:00
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
if rptr == nil {
2022-10-01 02:22:28 +02:00
return nil , fmt . Errorf ( "/cr error: remote %q not found" , newRemote )
}
if rstate . Archived {
return nil , fmt . Errorf ( "/cr error: remote %q cannot switch to archived remote" , newRemote )
2022-08-17 21:24:09 +02:00
}
2022-08-24 11:14:16 +02:00
err = sstore . UpdateCurRemote ( ctx , ids . SessionId , ids . WindowId , * rptr )
2022-08-17 21:24:09 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cr error: cannot update curremote: %w" , err )
2022-08-17 21:24:09 +02:00
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
2022-08-25 03:56:50 +02:00
Window : & sstore . WindowType {
2022-08-17 21:24:09 +02:00
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
2022-08-24 11:14:16 +02:00
CurRemote : * rptr ,
2022-08-17 21:24:09 +02:00
} ,
Info : & sstore . InfoMsgType {
2022-10-01 02:22:28 +02:00
InfoMsg : fmt . Sprintf ( "current remote = %q" , remoteName ) ,
2022-08-17 21:24:09 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
}
2022-10-19 03:03:02 +02:00
func makeStaticCmd ( ctx context . Context , metaCmd string , ids resolvedIds , cmdStr string , cmdOutput [ ] byte ) ( * sstore . CmdType , error ) {
cmd := & sstore . CmdType {
SessionId : ids . SessionId ,
2022-12-20 03:52:08 +01:00
CmdId : scbase . GenPromptUUID ( ) ,
2022-10-19 03:03:02 +02:00
CmdStr : cmdStr ,
Remote : ids . Remote . RemotePtr ,
TermOpts : sstore . TermOpts { Rows : shexec . DefaultTermRows , Cols : shexec . DefaultTermCols , FlexRows : true , MaxPtySize : remote . DefaultMaxPtySize } ,
Status : sstore . CmdStatusDone ,
StartPk : nil ,
2022-11-29 03:03:02 +01:00
DoneInfo : nil ,
2022-10-19 03:03:02 +02:00
RunOut : nil ,
}
2022-11-29 03:03:02 +01:00
if ids . Remote . StatePtr != nil {
cmd . StatePtr = * ids . Remote . StatePtr
}
if ids . Remote . FeState != nil {
cmd . FeState = * ids . Remote . FeState
2022-10-19 03:03:02 +02:00
}
err := sstore . CreateCmdPtyFile ( ctx , cmd . SessionId , cmd . CmdId , cmd . TermOpts . MaxPtySize )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot create local ptyout file for %s command: %w" , metaCmd , err )
}
// can ignore ptyupdate
_ , err = sstore . AppendToCmdPtyBlob ( ctx , cmd . SessionId , cmd . CmdId , cmdOutput , 0 )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot append to local ptyout file for %s command: %v" , metaCmd , err )
}
return cmd , nil
}
func addLineForCmd ( ctx context . Context , metaCmd string , shouldFocus bool , ids resolvedIds , cmd * sstore . CmdType ) ( * sstore . ModelUpdate , error ) {
rtnLine , err := sstore . AddCmdLine ( ctx , ids . SessionId , ids . WindowId , DefaultUserId , cmd )
if err != nil {
return nil , err
}
sw , err := sstore . GetScreenWindowByIds ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId )
if err != nil {
// ignore error here, because the command has already run (nothing to do)
2022-10-31 20:40:45 +01:00
log . Printf ( "%s error getting screen-window: %v\n" , metaCmd , err )
2022-10-19 03:03:02 +02:00
}
if sw != nil {
updateMap := make ( map [ string ] interface { } )
updateMap [ sstore . SWField_SelectedLine ] = rtnLine . LineNum
if shouldFocus {
updateMap [ sstore . SWField_Focus ] = sstore . SWFocusCmdFg
}
sw , err = sstore . UpdateScreenWindow ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId , updateMap )
if err != nil {
// ignore error again (nothing to do)
2022-10-31 20:40:45 +01:00
log . Printf ( "%s error updating screen-window selected line: %v\n" , metaCmd , err )
2022-10-19 03:03:02 +02:00
}
}
update := & sstore . ModelUpdate {
Line : rtnLine ,
Cmd : cmd ,
ScreenWindows : [ ] * sstore . ScreenWindowType { sw } ,
}
updateHistoryContext ( ctx , rtnLine , cmd )
return update , nil
}
func updateHistoryContext ( ctx context . Context , line * sstore . LineType , cmd * sstore . CmdType ) {
ctxVal := ctx . Value ( historyContextKey )
if ctxVal == nil {
return
}
hctx := ctxVal . ( * historyContextType )
if line != nil {
hctx . LineId = line . LineId
}
if cmd != nil {
hctx . CmdId = cmd . CmdId
hctx . RemotePtr = & cmd . Remote
}
}
2022-08-11 03:33:32 +02:00
func makeInfoFromComps ( compType string , comps [ ] string , hasMore bool ) sstore . UpdatePacket {
2022-08-24 02:26:42 +02:00
sort . Slice ( comps , func ( i int , j int ) bool {
c1 := comps [ i ]
c2 := comps [ j ]
c1mc := strings . HasPrefix ( c1 , "^" )
c2mc := strings . HasPrefix ( c2 , "^" )
if c1mc && ! c2mc {
return true
}
if ! c1mc && c2mc {
return false
}
return c1 < c2
} )
2022-08-23 22:37:08 +02:00
if len ( comps ) == 0 {
comps = [ ] string { "(no completions)" }
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
2022-08-11 03:33:32 +02:00
Info : & sstore . InfoMsgType {
2022-08-11 19:21:45 +02:00
InfoTitle : fmt . Sprintf ( "%s completions" , compType ) ,
InfoComps : comps ,
InfoCompsMore : hasMore ,
2022-08-11 03:33:32 +02:00
} ,
}
return update
}
2022-11-23 20:12:05 +01:00
func simpleCompCommandMeta ( ctx context . Context , prefix string , compCtx comp . CompContext , args [ ] interface { } ) ( * comp . CompReturn , error ) {
2022-11-11 03:51:20 +01:00
if strings . HasPrefix ( prefix , "/" ) {
compsCmd , _ := comp . DoSimpleComp ( ctx , comp . CGTypeCommand , prefix , compCtx , nil )
2022-11-23 20:12:05 +01:00
compsMeta , _ := simpleCompMeta ( ctx , prefix , compCtx , nil )
2022-11-11 03:51:20 +01:00
return comp . CombineCompReturn ( comp . CGTypeCommandMeta , compsCmd , compsMeta ) , nil
} else {
return comp . DoSimpleComp ( ctx , comp . CGTypeCommand , prefix , compCtx , nil )
}
2022-11-10 22:52:51 +01:00
}
2022-11-23 20:12:05 +01:00
func simpleCompMeta ( ctx context . Context , prefix string , compCtx comp . CompContext , args [ ] interface { } ) ( * comp . CompReturn , error ) {
2022-11-10 22:52:51 +01:00
rtn := comp . CompReturn { }
validCommands := getValidCommands ( )
for _ , cmd := range validCommands {
2022-11-23 20:12:05 +01:00
if strings . HasPrefix ( cmd , "/_" ) && ! strings . HasPrefix ( prefix , "/_" ) {
continue
}
2022-11-10 22:52:51 +01:00
if strings . HasPrefix ( cmd , prefix ) {
rtn . Entries = append ( rtn . Entries , comp . CompEntry { Word : cmd , IsMetaCmd : true } )
}
}
return & rtn , nil
}
2022-08-30 01:31:06 +02:00
func doMetaCompGen ( ctx context . Context , pk * scpacket . FeCommandPacketType , prefix string , forDisplay bool ) ( [ ] string , bool , error ) {
ids , err := resolveUiIds ( ctx , pk , 0 ) // best effort
var comps [ ] string
var hasMore bool
if ids . Remote != nil && ids . Remote . RState . IsConnected ( ) {
comps , hasMore , err = doCompGen ( ctx , pk , prefix , "file" , forDisplay )
if err != nil {
return nil , false , err
}
2022-08-23 22:37:08 +02:00
}
2022-08-27 02:17:33 +02:00
validCommands := getValidCommands ( )
for _ , cmd := range validCommands {
2022-08-11 03:33:32 +02:00
if strings . HasPrefix ( cmd , prefix ) {
2022-08-24 02:26:42 +02:00
if forDisplay {
comps = append ( comps , "^" + cmd )
} else {
comps = append ( comps , cmd )
}
2022-08-11 03:33:32 +02:00
}
}
2022-08-23 22:37:08 +02:00
return comps , hasMore , nil
2022-08-11 03:33:32 +02:00
}
2022-08-30 01:31:06 +02:00
func doCompGen ( ctx context . Context , pk * scpacket . FeCommandPacketType , prefix string , compType string , forDisplay bool ) ( [ ] string , bool , error ) {
2022-08-11 03:33:32 +02:00
if compType == "metacommand" {
2022-08-30 01:31:06 +02:00
return doMetaCompGen ( ctx , pk , prefix , forDisplay )
2022-08-09 23:24:57 +02:00
}
if ! packet . IsValidCompGenType ( compType ) {
2022-11-23 20:12:05 +01:00
return nil , false , fmt . Errorf ( "/_compgen invalid type '%s'" , compType )
2022-08-09 23:24:57 +02:00
}
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_RemoteConnected )
if err != nil {
2022-11-23 20:12:05 +01:00
return nil , false , fmt . Errorf ( "/_compgen error: %w" , err )
2022-08-30 01:31:06 +02:00
}
2022-08-09 23:24:57 +02:00
cgPacket := packet . MakeCompGenPacket ( )
cgPacket . ReqId = uuid . New ( ) . String ( )
cgPacket . CompType = compType
cgPacket . Prefix = prefix
2022-11-29 03:03:02 +01:00
cgPacket . Cwd = ids . Remote . FeState . Cwd
2022-08-30 01:31:06 +02:00
resp , err := ids . Remote . MShell . PacketRpc ( ctx , cgPacket )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-11 03:33:32 +02:00
return nil , false , err
}
if err = resp . Err ( ) ; err != nil {
return nil , false , err
2022-07-16 02:37:32 +02:00
}
2022-11-10 22:52:51 +01:00
comps := utilfn . GetStrArr ( resp . Data , "comps" )
hasMore := utilfn . GetBool ( resp . Data , "hasmore" )
2022-08-11 03:33:32 +02:00
return comps , hasMore , nil
}
func CompGenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-11-11 03:51:20 +01:00
ids , err := resolveUiIds ( ctx , pk , 0 ) // best-effort
if err != nil {
2022-11-23 20:12:05 +01:00
return nil , fmt . Errorf ( "/_compgen error: %w" , err )
2022-11-11 03:51:20 +01:00
}
2022-08-11 03:33:32 +02:00
cmdLine := firstArg ( pk )
pos := len ( cmdLine )
if pk . Kwargs [ "comppos" ] != "" {
posArg , err := strconv . Atoi ( pk . Kwargs [ "comppos" ] )
if err != nil {
2022-11-23 20:12:05 +01:00
return nil , fmt . Errorf ( "/_compgen invalid comppos '%s': %w" , pk . Kwargs [ "comppos" ] , err )
2022-08-11 03:33:32 +02:00
}
pos = posArg
}
if pos < 0 {
pos = 0
}
if pos > len ( cmdLine ) {
pos = len ( cmdLine )
}
showComps := resolveBool ( pk . Kwargs [ "compshow" ] , false )
2022-11-22 09:26:41 +01:00
cmdSP := utilfn . StrWithPos { Str : cmdLine , Pos : pos }
2022-11-11 03:51:20 +01:00
compCtx := comp . CompContext { }
if ids . Remote != nil {
rptr := ids . Remote . RemotePtr
compCtx . RemotePtr = & rptr
2022-11-29 03:03:02 +01:00
if ids . Remote . FeState != nil {
compCtx . Cwd = ids . Remote . FeState . Cwd
}
2022-11-11 03:51:20 +01:00
}
compCtx . ForDisplay = showComps
crtn , newSP , err := comp . DoCompGen ( ctx , cmdSP , compCtx )
2022-08-11 03:33:32 +02:00
if err != nil {
return nil , err
}
2022-11-11 03:51:20 +01:00
if crtn == nil {
2022-11-22 09:26:41 +01:00
return nil , nil
2022-08-11 03:33:32 +02:00
}
2022-11-22 09:26:41 +01:00
if showComps {
2022-11-11 03:51:20 +01:00
compStrs := crtn . GetCompDisplayStrs ( )
return makeInfoFromComps ( crtn . CompType , compStrs , crtn . HasMore ) , nil
}
2022-11-22 09:26:41 +01:00
if newSP == nil || cmdSP == * newSP {
return nil , nil
}
2022-11-11 03:51:20 +01:00
update := sstore . ModelUpdate {
CmdLine : & sstore . CmdLineType { CmdLine : newSP . Str , CursorPos : newSP . Pos } ,
}
return update , nil
2022-07-16 02:37:32 +02:00
}
func CommentCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/comment error: %w" , err )
2022-07-16 02:37:32 +02:00
}
text := firstArg ( pk )
if strings . TrimSpace ( text ) == "" {
return nil , fmt . Errorf ( "cannot post empty comment" )
}
rtnLine , err := sstore . AddCommentLine ( ctx , ids . SessionId , ids . WindowId , DefaultUserId , text )
if err != nil {
return nil , err
}
2022-10-11 02:30:48 +02:00
updateMap := make ( map [ string ] interface { } )
updateMap [ sstore . SWField_SelectedLine ] = rtnLine . LineNum
2022-10-11 10:11:04 +02:00
updateMap [ sstore . SWField_Focus ] = sstore . SWFocusInput
2022-10-11 02:30:48 +02:00
sw , err := sstore . UpdateScreenWindow ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId , updateMap )
if err != nil {
// ignore error again (nothing to do)
2022-10-31 20:40:45 +01:00
log . Printf ( "/comment error updating screen-window selected line: %v\n" , err )
2022-10-11 02:30:48 +02:00
}
2022-10-11 10:11:04 +02:00
update := sstore . ModelUpdate { Line : rtnLine , ScreenWindows : [ ] * sstore . ScreenWindowType { sw } }
2022-10-11 02:30:48 +02:00
return update , nil
2022-07-16 02:37:32 +02:00
}
2022-07-16 02:53:23 +02:00
2022-08-27 01:21:19 +02:00
func maybeQuote ( s string , quote bool ) string {
if quote {
return fmt . Sprintf ( "%q" , s )
}
return s
}
2022-08-27 02:17:33 +02:00
func mapToStrs ( m map [ string ] bool ) [ ] string {
var rtn [ ] string
for key , val := range m {
if val {
rtn = append ( rtn , key )
}
}
return rtn
}
2022-08-27 01:21:19 +02:00
func formatStrs ( strs [ ] string , conj string , quote bool ) string {
if len ( strs ) == 0 {
return "(none)"
}
if len ( strs ) == 1 {
return maybeQuote ( strs [ 0 ] , quote )
}
if len ( strs ) == 2 {
return fmt . Sprintf ( "%s %s %s" , maybeQuote ( strs [ 0 ] , quote ) , conj , maybeQuote ( strs [ 1 ] , quote ) )
}
var buf bytes . Buffer
for idx := 0 ; idx < len ( strs ) - 1 ; idx ++ {
buf . WriteString ( maybeQuote ( strs [ idx ] , quote ) )
buf . WriteString ( ", " )
}
buf . WriteString ( conj )
buf . WriteString ( " " )
buf . WriteString ( maybeQuote ( strs [ len ( strs ) - 1 ] , quote ) )
return buf . String ( )
}
func validateName ( name string , typeStr string ) error {
if len ( name ) > MaxNameLen {
return fmt . Errorf ( "%s name too long, max length is %d" , typeStr , MaxNameLen )
}
if ! genericNameRe . MatchString ( name ) {
return fmt . Errorf ( "invalid %s name" , typeStr )
}
return nil
}
2022-08-27 06:44:18 +02:00
func validateColor ( color string , typeStr string ) error {
for _ , c := range ColorNames {
if color == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid colors are: %s" , typeStr , formatStrs ( ColorNames , "or" , false ) )
}
2022-09-01 21:47:10 +02:00
func validateRemoteColor ( color string , typeStr string ) error {
for _ , c := range RemoteColorNames {
if color == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid colors are: %s" , typeStr , formatStrs ( RemoteColorNames , "or" , false ) )
}
2022-08-27 02:17:33 +02:00
func SessionOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
activate := resolveBool ( pk . Kwargs [ "activate" ] , true )
newName := pk . Kwargs [ "name" ]
if newName != "" {
err := validateName ( newName , "session" )
2022-08-09 01:21:46 +02:00
if err != nil {
return nil , err
}
}
2022-08-27 02:17:33 +02:00
update , err := sstore . InsertSessionWithName ( ctx , newName , activate )
if err != nil {
return nil , err
}
return update , nil
}
2022-09-13 21:06:12 +02:00
func SessionDeleteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session )
if err != nil {
return nil , err
}
2022-12-27 01:09:21 +01:00
update , err := sstore . DeleteSession ( ctx , ids . SessionId )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , fmt . Errorf ( "cannot delete session: %v" , err )
}
2022-12-27 01:09:21 +01:00
return update , nil
}
func SessionArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , 0 ) // don't force R_Session
if err != nil {
return nil , err
2022-09-13 21:06:12 +02:00
}
2022-12-27 01:09:21 +01:00
sessionId := ""
if len ( pk . Args ) >= 1 {
ritem , err := resolveSession ( ctx , pk . Args [ 0 ] , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "/session:archive error resolving session %q: %w" , pk . Args [ 0 ] , err )
2022-12-24 00:56:29 +01:00
}
2022-12-27 01:09:21 +01:00
if ritem == nil {
return nil , fmt . Errorf ( "/session:archive session %q not found" , pk . Args [ 0 ] )
}
sessionId = ritem . Id
} else {
sessionId = ids . SessionId
2022-09-13 21:06:12 +02:00
}
2022-12-27 01:09:21 +01:00
if sessionId == "" {
return nil , fmt . Errorf ( "/session:archive no sessionid found" )
}
archiveVal := true
if len ( pk . Args ) >= 2 {
archiveVal = resolveBool ( pk . Args [ 1 ] , true )
}
if archiveVal {
update , err := sstore . ArchiveSession ( ctx , sessionId )
if err != nil {
return nil , fmt . Errorf ( "cannot archive session: %v" , err )
}
2022-12-27 03:42:55 +01:00
update . Info = & sstore . InfoMsgType {
InfoMsg : "session archived" ,
}
2022-12-27 01:09:21 +01:00
return update , nil
} else {
2022-12-27 03:42:55 +01:00
activate := resolveBool ( pk . Kwargs [ "activate" ] , false )
update , err := sstore . UnArchiveSession ( ctx , sessionId , activate )
2022-12-27 01:09:21 +01:00
if err != nil {
return nil , fmt . Errorf ( "cannot un-archive session: %v" , err )
}
2022-12-27 03:42:55 +01:00
update . Info = & sstore . InfoMsgType {
InfoMsg : "session un-archived" ,
}
2022-12-27 01:09:21 +01:00
return update , nil
}
}
2022-12-27 04:06:46 +01:00
func SessionShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session )
if err != nil {
return nil , err
}
session , err := sstore . GetSessionById ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "cannot get session: %w" , err )
}
if session == nil {
return nil , fmt . Errorf ( "session not found" )
}
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "sessionid" , session . SessionId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "name" , session . Name ) )
if session . SessionIdx != 0 {
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "index" , session . SessionIdx ) )
}
if session . Archived {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "archived" , "true" ) )
ts := time . UnixMilli ( session . ArchivedTs )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "archivedts" , ts . Format ( "2006-01-02 15:04:05" ) ) )
}
stats , err := sstore . GetSessionStats ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "error getting session stats: %w" , err )
}
var screenArchiveStr string
if stats . NumArchivedScreens > 0 {
screenArchiveStr = fmt . Sprintf ( " (%d archived)" , stats . NumArchivedScreens )
}
buf . WriteString ( fmt . Sprintf ( " %-15s %d%s\n" , "screens" , stats . NumScreens , screenArchiveStr ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %d\n" , "lines" , stats . NumLines ) )
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 ) )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : "session info" ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , nil
}
2022-12-27 01:09:21 +01:00
func SessionShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
sessions , err := sstore . GetBareSessions ( ctx )
if err != nil {
return nil , fmt . Errorf ( "error retrieving sessions: %v" , err )
}
var buf bytes . Buffer
for _ , session := range sessions {
var archivedStr string
if session . Archived {
archivedStr = " (archived)"
}
sessionIdxStr := "-"
if session . SessionIdx != 0 {
sessionIdxStr = strconv . Itoa ( int ( session . SessionIdx ) )
}
outStr := fmt . Sprintf ( "%-30s %s %s\n" , session . Name + archivedStr , session . SessionId , sessionIdxStr )
buf . WriteString ( outStr )
}
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : "all sessions" ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , nil
2022-09-13 21:06:12 +02:00
}
2022-08-27 02:17:33 +02:00
func SessionSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
}
var varsUpdated [ ] string
if pk . Kwargs [ "name" ] != "" {
newName := pk . Kwargs [ "name" ]
err = validateName ( newName , "session" )
2022-08-26 22:12:17 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:17:33 +02:00
err = sstore . SetSessionName ( ctx , ids . SessionId , newName )
2022-08-26 22:12:17 +02:00
if err != nil {
2022-08-27 02:17:33 +02:00
return nil , fmt . Errorf ( "setting session name: %v" , err )
2022-08-26 22:12:17 +02:00
}
2022-08-27 02:17:33 +02:00
varsUpdated = append ( varsUpdated , "name" )
}
if pk . Kwargs [ "pos" ] != "" {
2022-08-27 01:21:19 +02:00
2022-08-26 22:12:17 +02:00
}
2022-08-27 02:17:33 +02:00
if len ( varsUpdated ) == 0 {
return nil , fmt . Errorf ( "/session:set no updates, can set %s" , formatStrs ( [ ] string { "name" , "pos" } , "or" , false ) )
}
2022-08-27 02:51:28 +02:00
bareSession , err := sstore . GetBareSessionById ( ctx , ids . SessionId )
2022-08-27 02:17:33 +02:00
update := sstore . ModelUpdate {
Sessions : [ ] * sstore . SessionType { bareSession } ,
Info : & sstore . InfoMsgType {
2022-08-27 02:51:28 +02:00
InfoMsg : fmt . Sprintf ( "session updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
2022-08-27 02:17:33 +02:00
TimeoutMs : 2000 ,
} ,
2022-08-09 01:21:46 +02:00
}
2022-08-27 02:17:33 +02:00
return update , nil
}
func SessionCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-13 21:06:12 +02:00
ids , err := resolveUiIds ( ctx , pk , 0 )
if err != nil {
return nil , err
}
2022-08-09 01:21:46 +02:00
firstArg := firstArg ( pk )
if firstArg == "" {
2022-08-27 01:21:19 +02:00
return nil , fmt . Errorf ( "usage /session [name|id|pos], no param specified" )
2022-08-09 01:21:46 +02:00
}
2022-12-27 01:09:21 +01:00
ritem , err := resolveSession ( ctx , firstArg , ids . SessionId )
2022-08-27 01:21:19 +02:00
if err != nil {
2022-08-27 02:17:33 +02:00
return nil , err
2022-08-27 01:21:19 +02:00
}
2022-08-30 01:31:06 +02:00
err = sstore . SetActiveSessionId ( ctx , ritem . Id )
if err != nil {
return nil , err
}
2022-08-27 01:21:19 +02:00
update := sstore . ModelUpdate {
2022-08-27 02:17:33 +02:00
ActiveSessionId : ritem . Id ,
2022-08-27 01:21:19 +02:00
Info : & sstore . InfoMsgType {
2022-08-27 02:17:33 +02:00
InfoMsg : fmt . Sprintf ( "switched to session %q" , ritem . Name ) ,
2022-08-27 01:21:19 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-07-16 02:53:23 +02:00
}
2022-08-24 02:26:42 +02:00
2022-12-20 02:36:19 +01:00
func RemoteResetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-10-28 02:10:36 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
initPk , err := ids . Remote . MShell . ReInit ( ctx )
if err != nil {
return nil , err
}
if initPk == nil || initPk . State == nil {
return nil , fmt . Errorf ( "invalid initpk received from remote (no remote state)" )
}
2022-11-28 09:13:00 +01:00
feState := sstore . FeStateFromShellState ( initPk . State )
remoteInst , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , * feState , initPk . State , nil )
2022-10-28 02:10:36 +02:00
if err != nil {
return nil , err
}
outputStr := "reset remote state"
cmd , err := makeStaticCmd ( ctx , "reset" , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
2022-11-29 03:03:02 +01:00
update , err := addLineForCmd ( ctx , "/reset" , false , ids , cmd )
2022-10-28 02:10:36 +02:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update . Interactive = pk . Interactive
update . Sessions = sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remoteInst )
return update , nil
2022-10-25 06:29:11 +02:00
}
2022-08-27 07:01:29 +02:00
func ClearCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
2022-08-27 07:01:29 +02:00
if err != nil {
return nil , err
}
update , err := sstore . ClearWindow ( ctx , ids . SessionId , ids . WindowId )
if err != nil {
return nil , fmt . Errorf ( "clearing window: %v" , err )
}
update . Info = & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "window cleared" ) ,
TimeoutMs : 2000 ,
}
return update , nil
}
2022-08-28 23:24:05 +02:00
const DefaultMaxHistoryItems = 10000
func HistoryCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window | R_Remote )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , err
}
2022-10-01 22:23:36 +02:00
maxItems , err := resolvePosInt ( pk . Kwargs [ "maxitems" ] , DefaultMaxHistoryItems )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , fmt . Errorf ( "invalid maxitems value '%s' (must be a number): %v" , pk . Kwargs [ "maxitems" ] , err )
}
if maxItems < 0 {
2022-10-21 01:14:14 +02:00
return nil , fmt . Errorf ( "invalid maxitems value '%d' (cannot be negative)" , maxItems )
2022-08-28 23:24:05 +02:00
}
if maxItems == 0 {
maxItems = DefaultMaxHistoryItems
}
2022-08-31 22:28:52 +02:00
htype := HistoryTypeWindow
hSessionId := ids . SessionId
hWindowId := ids . WindowId
if pk . Kwargs [ "type" ] != "" {
htype = pk . Kwargs [ "type" ]
if htype != HistoryTypeWindow && htype != HistoryTypeSession && htype != HistoryTypeGlobal {
return nil , fmt . Errorf ( "invalid history type '%s', valid types: %s" , htype , formatStrs ( [ ] string { HistoryTypeWindow , HistoryTypeSession , HistoryTypeGlobal } , "or" , false ) )
}
}
if htype == HistoryTypeGlobal {
hSessionId = ""
hWindowId = ""
} else if htype == HistoryTypeSession {
hWindowId = ""
}
hitems , err := sstore . GetHistoryItems ( ctx , hSessionId , hWindowId , sstore . HistoryQueryOpts { MaxItems : maxItems } )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , err
}
2022-08-31 08:11:06 +02:00
show := ! resolveBool ( pk . Kwargs [ "noshow" ] , false )
2022-09-30 23:46:51 +02:00
update := sstore . ModelUpdate { }
2022-08-30 01:31:06 +02:00
update . History = & sstore . HistoryInfoType {
2022-08-31 22:28:52 +02:00
HistoryType : htype ,
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
Items : hitems ,
Show : show ,
2022-08-30 01:31:06 +02:00
}
2022-08-28 23:24:05 +02:00
return update , nil
}
2022-08-24 02:26:42 +02:00
func splitLinesForInfo ( str string ) [ ] string {
rtn := strings . Split ( str , "\n" )
if rtn [ len ( rtn ) - 1 ] == "" {
return rtn [ : len ( rtn ) - 1 ]
}
return rtn
}
2022-09-06 05:08:59 +02:00
func resizeRunningCommand ( ctx context . Context , cmd * sstore . CmdType , newCols int ) error {
siPk := packet . MakeSpecialInputPacket ( )
siPk . CK = base . MakeCommandKey ( cmd . SessionId , cmd . CmdId )
siPk . WinSize = & packet . WinSize { Rows : int ( cmd . TermOpts . Rows ) , Cols : newCols }
msh := remote . GetRemoteById ( cmd . Remote . RemoteId )
if msh == nil {
return fmt . Errorf ( "cannot resize, cmd remote not found" )
}
err := msh . SendSpecialInput ( siPk )
if err != nil {
return err
}
newTermOpts := cmd . TermOpts
newTermOpts . Cols = int64 ( newCols )
err = sstore . UpdateCmdTermOpts ( ctx , cmd . SessionId , cmd . CmdId , newTermOpts )
if err != nil {
return err
}
return nil
}
2022-10-22 23:46:39 +02:00
func SwResizeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-06 05:08:59 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
colsStr := pk . Kwargs [ "cols" ]
if colsStr == "" {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize requires a numeric 'cols' argument" )
2022-09-06 05:08:59 +02:00
}
cols , err := strconv . Atoi ( colsStr )
if err != nil {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize requires a numeric 'cols' argument: %v" , err )
2022-09-06 05:08:59 +02:00
}
if cols <= 0 {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize invalid zero/negative 'cols' argument" )
2022-09-06 05:08:59 +02:00
}
cols = base . BoundInt ( cols , shexec . MinTermCols , shexec . MaxTermCols )
runningCmds , err := sstore . GetRunningWindowCmds ( ctx , ids . SessionId , ids . WindowId )
if err != nil {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize cannot get running commands: %v" , err )
2022-09-06 05:08:59 +02:00
}
if len ( runningCmds ) == 0 {
return nil , nil
}
for _ , cmd := range runningCmds {
if int ( cmd . TermOpts . Cols ) != cols {
resizeRunningCommand ( ctx , cmd , cols )
}
}
return nil , nil
}
2022-09-13 21:06:12 +02:00
func LineCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-12-22 02:45:40 +01:00
return nil , fmt . Errorf ( "/line requires a subcommand: %s" , formatStrs ( [ ] string { "show" , "star" , "hide" , "purge" } , "or" , false ) )
2022-09-13 21:06:12 +02:00
}
2022-12-06 07:59:00 +01:00
func LineStarCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:star requires an argument (line number or id)" )
}
if len ( pk . Args ) > 2 {
return nil , fmt . Errorf ( "/line:star only takes up to 2 arguments (line-number and star-value)" )
}
lineArg := pk . Args [ 0 ]
lineId , err := sstore . FindLineIdByArg ( ctx , ids . SessionId , ids . WindowId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
starVal , err := resolveNonNegInt ( pk . Args [ 1 ] , 1 )
if err != nil {
return nil , fmt . Errorf ( "/line:star invalid star-value (not integer): %v" , err )
}
if starVal > 5 {
return nil , fmt . Errorf ( "/line:star invalid star-value must be in the range of 0-5" )
}
err = sstore . UpdateLineStar ( ctx , lineId , starVal )
if err != nil {
return nil , fmt . Errorf ( "/line:star error updating star value: %v" , err )
}
lineObj , err := sstore . GetLineById ( ctx , lineId )
if err != nil {
return nil , fmt . Errorf ( "/line:star error getting line: %v" , err )
}
if lineObj == nil {
// no line (which is strange given we checked for it above). just return a nop.
return nil , nil
}
return sstore . ModelUpdate { Line : lineObj } , nil
}
2022-12-28 08:12:27 +01:00
func LineArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-12-22 02:45:40 +01:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
2022-12-28 08:12:27 +01:00
return nil , fmt . Errorf ( "/line:archive requires an argument (line number or id)" )
2022-12-22 02:45:40 +01:00
}
lineArg := pk . Args [ 0 ]
lineId , err := sstore . FindLineIdByArg ( ctx , ids . SessionId , ids . WindowId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
2022-12-28 08:12:27 +01:00
shouldArchive := true
2022-12-22 02:45:40 +01:00
if len ( pk . Args ) >= 2 {
2022-12-28 08:12:27 +01:00
shouldArchive = resolveBool ( pk . Args [ 1 ] , true )
2022-12-22 02:45:40 +01:00
}
2022-12-28 08:12:27 +01:00
err = sstore . SetLineArchivedById ( ctx , lineId , shouldArchive )
2022-12-22 02:45:40 +01:00
if err != nil {
2022-12-28 08:12:27 +01:00
return nil , fmt . Errorf ( "/line:archive error updating hidden status: %v" , err )
2022-12-22 02:45:40 +01:00
}
lineObj , err := sstore . GetLineById ( ctx , lineId )
if err != nil {
2022-12-28 08:12:27 +01:00
return nil , fmt . Errorf ( "/line:archive error getting line: %v" , err )
2022-12-22 02:45:40 +01:00
}
if lineObj == nil {
// no line (which is strange given we checked for it above). just return a nop.
return nil , nil
}
return sstore . ModelUpdate { Line : lineObj } , nil
}
func LinePurgeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:purge requires an argument (line number or id)" )
}
lineArg := pk . Args [ 0 ]
lineId , err := sstore . FindLineIdByArg ( ctx , ids . SessionId , ids . WindowId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
err = sstore . PurgeLineById ( ctx , ids . SessionId , lineId )
if err != nil {
return nil , fmt . Errorf ( "/line:purge error purging line: %v" , err )
}
lineObj := & sstore . LineType {
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
LineId : lineId ,
Remove : true ,
}
return sstore . ModelUpdate { Line : lineObj } , nil
}
2022-09-13 21:06:12 +02:00
func LineShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
2022-09-21 06:50:36 +02:00
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:show requires an argument (line number or id)" )
}
lineArg := pk . Args [ 0 ]
lineId , err := sstore . FindLineIdByArg ( ctx , ids . SessionId , ids . WindowId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
2022-10-28 07:00:10 +02:00
line , cmd , err := sstore . GetLineCmdByLineId ( ctx , ids . SessionId , ids . WindowId , lineId )
2022-09-21 06:50:36 +02:00
if err != nil {
return nil , fmt . Errorf ( "error getting line: %v" , err )
}
if line == nil {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "lineid" , line . LineId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "type" , line . LineType ) )
lineNumStr := strconv . FormatInt ( line . LineNum , 10 )
if line . LineNumTemp {
lineNumStr = "~" + lineNumStr
}
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "linenum" , lineNumStr ) )
ts := time . UnixMilli ( line . Ts )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "ts" , ts . Format ( "2006-01-02 15:04:05" ) ) )
if line . Ephemeral {
2022-10-21 01:14:14 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %v\n" , "ephemeral" , true ) )
2022-09-21 06:50:36 +02:00
}
if cmd != nil {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "cmdid" , cmd . CmdId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "remote" , cmd . Remote . MakeFullRemoteRef ( ) ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "status" , cmd . Status ) )
2022-11-29 03:03:02 +01:00
if cmd . FeState . Cwd != "" {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "cwd" , cmd . FeState . Cwd ) )
2022-09-21 06:50:36 +02:00
}
2022-09-21 21:39:55 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "termopts" , formatTermOpts ( cmd . TermOpts ) ) )
2022-09-22 07:02:38 +02:00
if cmd . TermOpts != cmd . OrigTermOpts {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "orig-termopts" , formatTermOpts ( cmd . OrigTermOpts ) ) )
}
2022-10-28 02:10:36 +02:00
if cmd . RtnState {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "rtnstate" , "true" ) )
}
2022-09-21 06:50:36 +02:00
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "line %d info" , line . LineNum ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
2022-09-13 21:06:12 +02:00
}
2022-09-21 21:39:55 +02:00
2022-11-28 09:13:00 +01:00
func SetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
var setMap map [ string ] map [ string ] string
setMap = make ( map [ string ] map [ string ] string )
_ , err := resolveUiIds ( ctx , pk , 0 ) // best effort
if err != nil {
return nil , err
}
for argIdx , rawArgVal := range pk . Args {
eqIdx := strings . Index ( rawArgVal , "=" )
if eqIdx == - 1 {
return nil , fmt . Errorf ( "/set invalid argument %d, does not contain an '='" , argIdx )
}
argName := rawArgVal [ : eqIdx ]
argVal := rawArgVal [ eqIdx + 1 : ]
ok , scopeName , varName := resolveSetArg ( argName )
if ! ok {
return nil , fmt . Errorf ( "/set invalid setvar %q" , argName )
}
if _ , ok := setMap [ scopeName ] ; ! ok {
setMap [ scopeName ] = make ( map [ string ] string )
}
setMap [ scopeName ] [ varName ] = argVal
}
fmt . Printf ( "setmap: %#v\n" , setMap )
return nil , nil
}
2022-12-21 06:58:58 +01:00
func SignalCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/signal requires a first argument (line number or id)" )
}
if len ( pk . Args ) == 1 {
return nil , fmt . Errorf ( "/signal requires a second argument (signal name)" )
}
lineArg := pk . Args [ 0 ]
lineId , err := sstore . FindLineIdByArg ( ctx , ids . SessionId , ids . WindowId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
line , cmd , err := sstore . GetLineCmdByLineId ( ctx , ids . SessionId , ids . WindowId , lineId )
if err != nil {
return nil , fmt . Errorf ( "error getting line: %v" , err )
}
if line == nil {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
if cmd == nil {
return nil , fmt . Errorf ( "line %q does not have a command" , lineArg )
}
if cmd . Status != sstore . CmdStatusRunning {
return nil , fmt . Errorf ( "line %q command is not running, cannot send signal" , lineArg )
}
sigArg := pk . Args [ 1 ]
if isAllDigits ( sigArg ) {
val , _ := strconv . Atoi ( sigArg )
if val <= 0 || val > MaxSignalNum {
return nil , fmt . Errorf ( "signal number is out of bounds: %q" , sigArg )
}
} else if ! strings . HasPrefix ( sigArg , "SIG" ) {
sigArg = "SIG" + sigArg
}
sigArg = strings . ToUpper ( sigArg )
if len ( sigArg ) > 12 {
return nil , fmt . Errorf ( "invalid signal (too long): %q" , sigArg )
}
if ! sigNameRe . MatchString ( sigArg ) {
return nil , fmt . Errorf ( "invalid signal name/number: %q" , sigArg )
}
msh := remote . GetRemoteById ( cmd . Remote . RemoteId )
if msh == nil {
return nil , fmt . Errorf ( "cannot send signal, no remote found for command" )
}
if ! msh . IsConnected ( ) {
return nil , fmt . Errorf ( "cannot send signal, remote is not connected" )
}
siPk := packet . MakeSpecialInputPacket ( )
siPk . CK = base . MakeCommandKey ( cmd . SessionId , cmd . CmdId )
siPk . SigName = sigArg
err = msh . SendSpecialInput ( siPk )
if err != nil {
return nil , fmt . Errorf ( "cannot send signal: %v" , err )
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "sent line %s signal %s" , lineArg , sigArg ) ,
} ,
}
return update , nil
}
2022-10-30 20:52:40 +01:00
func KillServerCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
go func ( ) {
2022-10-31 20:40:45 +01:00
log . Printf ( "received /killserver, shutting down\n" )
2022-10-30 20:52:40 +01:00
time . Sleep ( 1 * time . Second )
syscall . Kill ( syscall . Getpid ( ) , syscall . SIGINT )
} ( )
return nil , nil
}
2022-09-21 21:39:55 +02:00
func formatTermOpts ( termOpts sstore . TermOpts ) string {
if termOpts . Cols == 0 {
return "???"
}
rtnStr := fmt . Sprintf ( "%dx%d" , termOpts . Rows , termOpts . Cols )
if termOpts . FlexRows {
rtnStr += " flexrows"
}
if termOpts . MaxPtySize > 0 {
rtnStr += " maxbuf=" + scbase . NumFormatB2 ( termOpts . MaxPtySize )
}
return rtnStr
}
2022-09-25 04:54:06 +02:00
type ColMeta struct {
Title string
MinCols int
MaxCols int
}
func toInterfaceArr ( sarr [ ] string ) [ ] interface { } {
rtn := make ( [ ] interface { } , len ( sarr ) )
for idx , s := range sarr {
rtn [ idx ] = s
}
return rtn
}
func formatTextTable ( totalCols int , data [ ] [ ] string , colMeta [ ] ColMeta ) [ ] string {
numCols := len ( colMeta )
maxColLen := make ( [ ] int , len ( colMeta ) )
for i , cm := range colMeta {
maxColLen [ i ] = cm . MinCols
}
for _ , row := range data {
for i := 0 ; i < numCols && i < len ( row ) ; i ++ {
dlen := len ( row [ i ] )
if dlen > maxColLen [ i ] {
maxColLen [ i ] = dlen
}
}
}
fmtStr := ""
for idx , clen := range maxColLen {
if idx != 0 {
fmtStr += " "
}
fmtStr += fmt . Sprintf ( "%%%ds" , clen )
}
var rtn [ ] string
for _ , row := range data {
sval := fmt . Sprintf ( fmtStr , toInterfaceArr ( row ) ... )
rtn = append ( rtn , sval )
}
return rtn
}
2022-10-21 01:14:14 +02:00
2022-11-29 03:03:02 +01:00
const MaxDiffKeyLen = 40
const MaxDiffValLen = 50
2022-11-03 02:45:13 +01:00
func displayStateUpdateDiff ( buf * bytes . Buffer , oldState packet . ShellState , newState packet . ShellState ) {
2022-10-21 01:14:14 +02:00
if newState . Cwd != oldState . Cwd {
2022-10-27 09:33:50 +02:00
buf . WriteString ( fmt . Sprintf ( "cwd %s\n" , newState . Cwd ) )
2022-10-21 01:14:14 +02:00
}
2022-10-25 21:31:29 +02:00
if ! bytes . Equal ( newState . ShellVars , oldState . ShellVars ) {
newEnvMap := shexec . DeclMapFromState ( & newState )
oldEnvMap := shexec . DeclMapFromState ( & oldState )
2022-10-21 01:14:14 +02:00
for key , newVal := range newEnvMap {
oldVal , found := oldEnvMap [ key ]
2022-11-28 09:13:00 +01:00
if ! found || ! shexec . DeclsEqual ( false , oldVal , newVal ) {
2022-10-27 09:33:50 +02:00
var exportStr string
if newVal . IsExport ( ) {
exportStr = "export "
}
2022-11-29 03:03:02 +01:00
buf . WriteString ( fmt . Sprintf ( "%s%s=%s\n" , exportStr , utilfn . EllipsisStr ( key , MaxDiffKeyLen ) , utilfn . EllipsisStr ( newVal . Value , MaxDiffValLen ) ) )
2022-10-21 01:14:14 +02:00
}
}
for key , _ := range oldEnvMap {
_ , found := newEnvMap [ key ]
if ! found {
2022-11-29 03:03:02 +01:00
buf . WriteString ( fmt . Sprintf ( "unset %s\n" , utilfn . EllipsisStr ( key , MaxDiffKeyLen ) ) )
2022-10-21 01:14:14 +02:00
}
}
}
if newState . Aliases != oldState . Aliases {
newAliasMap , _ := ParseAliases ( newState . Aliases )
oldAliasMap , _ := ParseAliases ( oldState . Aliases )
for aliasName , newAliasVal := range newAliasMap {
oldAliasVal , found := oldAliasMap [ aliasName ]
if ! found || newAliasVal != oldAliasVal {
2022-11-29 03:03:02 +01:00
buf . WriteString ( fmt . Sprintf ( "alias %s\n" , utilfn . EllipsisStr ( shellescape . Quote ( aliasName ) , MaxDiffKeyLen ) ) )
2022-10-21 01:14:14 +02:00
}
}
for aliasName , _ := range oldAliasMap {
_ , found := newAliasMap [ aliasName ]
if ! found {
2022-11-29 03:03:02 +01:00
buf . WriteString ( fmt . Sprintf ( "unalias %s\n" , utilfn . EllipsisStr ( shellescape . Quote ( aliasName ) , MaxDiffKeyLen ) ) )
2022-10-21 01:14:14 +02:00
}
}
}
if newState . Funcs != oldState . Funcs {
newFuncMap , _ := ParseFuncs ( newState . Funcs )
oldFuncMap , _ := ParseFuncs ( oldState . Funcs )
for funcName , newFuncVal := range newFuncMap {
oldFuncVal , found := oldFuncMap [ funcName ]
if ! found || newFuncVal != oldFuncVal {
2022-11-29 03:03:02 +01:00
buf . WriteString ( fmt . Sprintf ( "function %s\n" , utilfn . EllipsisStr ( shellescape . Quote ( funcName ) , MaxDiffKeyLen ) ) )
2022-10-21 01:14:14 +02:00
}
}
for funcName , _ := range oldFuncMap {
_ , found := newFuncMap [ funcName ]
if ! found {
2022-11-29 03:03:02 +01:00
buf . WriteString ( fmt . Sprintf ( "unset -f %s\n" , utilfn . EllipsisStr ( shellescape . Quote ( funcName ) , MaxDiffKeyLen ) ) )
2022-10-21 01:14:14 +02:00
}
}
}
}
2022-10-27 09:33:50 +02:00
func GetRtnStateDiff ( ctx context . Context , sessionId string , cmdId string ) ( [ ] byte , error ) {
cmd , err := sstore . GetCmdById ( ctx , sessionId , cmdId )
if err != nil {
return nil , err
}
if cmd == nil {
return nil , nil
}
if ! cmd . RtnState {
return nil , nil
}
2022-11-29 03:03:02 +01:00
if cmd . RtnStatePtr . IsEmpty ( ) {
2022-10-27 09:33:50 +02:00
return nil , nil
}
var outputBytes bytes . Buffer
2022-11-29 03:03:02 +01:00
initialState , err := sstore . GetFullState ( ctx , cmd . StatePtr )
if err != nil {
return nil , fmt . Errorf ( "getting initial full state: %v" , err )
}
rtnState , err := sstore . GetFullState ( ctx , cmd . RtnStatePtr )
if err != nil {
return nil , fmt . Errorf ( "getting rtn full state: %v" , err )
}
displayStateUpdateDiff ( & outputBytes , * initialState , * rtnState )
2022-10-27 09:33:50 +02:00
return outputBytes . Bytes ( ) , nil
}
2022-11-28 09:13:00 +01:00
func isValidInScope ( scopeName string , varName string ) bool {
for _ , varScope := range SetVarScopes {
if varScope . ScopeName == scopeName {
return utilfn . ContainsStr ( varScope . VarNames , varName )
}
}
return false
}
// returns (is-valid, scope, name)
// TODO write a full resolver to allow for indexed arguments. e.g. session[1].screen[1].window.pterm="25x80"
func resolveSetArg ( argName string ) ( bool , string , string ) {
dotIdx := strings . Index ( argName , "." )
if dotIdx == - 1 {
argName = SetVarNameMap [ argName ]
dotIdx = strings . Index ( argName , "." )
}
if argName == "" {
return false , "" , ""
}
scopeName := argName [ 0 : dotIdx ]
varName := argName [ dotIdx + 1 : ]
if ! isValidInScope ( scopeName , varName ) {
return false , "" , ""
}
return true , scopeName , varName
}