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-09-01 21:47:10 +02:00
"os"
2022-08-09 23:24:57 +02:00
"path"
"path/filepath"
2022-08-11 03:33:32 +02:00
"regexp"
"sort"
2022-07-16 02:37:32 +02:00
"strconv"
"strings"
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-07-16 02:37:32 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/remote"
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
)
2022-08-31 22:28:52 +02:00
const (
HistoryTypeWindow = "window"
HistoryTypeSession = "session"
HistoryTypeGlobal = "global"
)
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-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-08-27 06:44:18 +02:00
2022-09-01 21:47:10 +02:00
var hostNameRe = regexp . MustCompile ( "^[a-z][a-z0-9.-]*$" )
var userHostRe = regexp . MustCompile ( "^(sudo@)?([a-z][a-z0-9-]*)@([a-z][a-z0-9.-]*)$" )
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-08-27 01:21:19 +02:00
var positionRe = regexp . MustCompile ( "^((\\+|-)?[0-9]+|(\\+|-))$" )
2022-08-27 01:24:07 +02:00
var wsRe = regexp . MustCompile ( "\\s+" )
2022-08-27 01:21:19 +02:00
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 ( "cd" , CdCommand )
registerCmdFn ( "cr" , CrCommand )
registerCmdFn ( "compgen" , CompGenCommand )
registerCmdFn ( "setenv" , SetEnvCommand )
registerCmdFn ( "unset" , UnSetCommand )
2022-08-27 07:01:29 +02:00
registerCmdFn ( "clear" , ClearCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "session" , SessionCommand )
registerCmdFn ( "session:open" , SessionOpenCommand )
registerCmdAlias ( "session:new" , SessionOpenCommand )
registerCmdFn ( "session:set" , SessionSetCommand )
registerCmdFn ( "screen" , ScreenCommand )
registerCmdFn ( "screen:close" , ScreenCloseCommand )
registerCmdFn ( "screen:open" , ScreenOpenCommand )
registerCmdAlias ( "screen:new" , ScreenOpenCommand )
2022-08-27 02:51:28 +02:00
registerCmdFn ( "screen:set" , ScreenSetCommand )
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-01 21:47:10 +02:00
registerCmdFn ( "remote:disconnect" , RemoteDisconnectCommand )
registerCmdFn ( "remote:connect" , RemoteConnectCommand )
2022-08-28 23:24:05 +02:00
registerCmdFn ( "history" , HistoryCommand )
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
}
rtn = append ( rtn , key )
}
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-08-28 23:24:05 +02:00
func resolveInt ( arg string , def int ) ( int , error ) {
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
return ival , nil
}
2022-07-16 02:37:32 +02:00
func RunCommand ( 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_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
}
cmdId := uuid . New ( ) . String ( )
cmdStr := firstArg ( pk )
runPacket := packet . MakeRunPacket ( )
runPacket . ReqId = uuid . New ( ) . String ( )
runPacket . CK = base . MakeCommandKey ( ids . SessionId , cmdId )
2022-08-30 01:31:06 +02:00
runPacket . Cwd = ids . Remote . RemoteState . Cwd
runPacket . Env0 = ids . Remote . RemoteState . Env0
2022-08-23 01:26:44 +02:00
runPacket . EnvComplete = true
2022-07-16 02:37:32 +02:00
runPacket . UsePty = true
2022-09-04 08:36:15 +02:00
runPacket . TermOpts = & packet . TermOpts { Rows : shexec . DefaultTermRows , Cols : shexec . DefaultTermCols , Term : remote . DefaultTerm , MaxPtySize : shexec . DefaultMaxPtySize }
if pk . UIContext != nil && pk . UIContext . TermOpts != nil {
pkOpts := pk . UIContext . TermOpts
if pkOpts . Cols > 0 {
runPacket . TermOpts . Cols = base . BoundInt ( pkOpts . Cols , shexec . MinTermCols , shexec . MaxTermCols )
}
if pkOpts . MaxPtySize > 0 {
runPacket . TermOpts . MaxPtySize = base . BoundInt64 ( pkOpts . MaxPtySize , shexec . MinMaxPtySize , shexec . MaxMaxPtySize )
}
}
2022-07-16 02:37:32 +02:00
runPacket . Command = strings . TrimSpace ( cmdStr )
2022-09-05 23:49:23 +02:00
cmd , callback , err := remote . RunCommand ( ctx , cmdId , ids . Remote . RemotePtr , ids . Remote . RemoteState , runPacket )
if callback != nil {
defer callback ( )
}
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
rtnLine , err := sstore . AddCmdLine ( ctx , ids . SessionId , ids . WindowId , DefaultUserId , cmd )
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
return sstore . ModelUpdate { Line : rtnLine , Cmd : cmd } , nil
2022-07-16 02:37:32 +02:00
}
2022-08-28 23:24:05 +02:00
func addToHistory ( ctx context . Context , pk * scpacket . FeCommandPacketType , update sstore . UpdatePacket , 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-08-28 23:24:05 +02:00
lineId , cmdId , rptr := sstore . ReadHistoryDataFromUpdate ( update )
2022-08-12 08:45:15 +02:00
hitem := & sstore . HistoryItemType {
HistoryId : uuid . New ( ) . String ( ) ,
Ts : time . Now ( ) . UnixMilli ( ) ,
UserId : DefaultUserId ,
SessionId : ids . SessionId ,
ScreenId : ids . ScreenId ,
WindowId : ids . WindowId ,
LineId : lineId ,
HadError : hadError ,
CmdId : cmdId ,
CmdStr : cmdStr ,
2022-08-28 23:24:05 +02:00
IsMetaCmd : isMetaCmd ,
}
if ! isMetaCmd && rptr != nil {
2022-08-30 01:31:06 +02:00
hitem . Remote = ids . Remote . 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-08-31 09:01:42 +02:00
var update sstore . UpdatePacket
newPk , rtnErr := EvalMetaCommand ( ctx , pk )
if rtnErr == nil {
update , rtnErr = HandleCommand ( ctx , newPk )
2022-07-16 02:37:32 +02:00
}
2022-08-11 21:07:41 +02:00
if ! resolveBool ( pk . Kwargs [ "nohist" ] , false ) {
2022-08-31 09:01:42 +02:00
err := addToHistory ( ctx , pk , update , ( newPk . MetaCmd != "run" ) , ( rtnErr != nil ) )
2022-08-12 08:45:15 +02:00
if err != nil {
2022-08-23 03:38:52 +02:00
fmt . 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-08-27 02:17:33 +02:00
func ScreenCloseCommand ( 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:17:33 +02:00
if err != nil {
return nil , fmt . Errorf ( "/screen:close cannot close screen: %w" , err )
2022-07-16 02:37:32 +02: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
}
update , session := sstore . MakeSingleSessionUpdate ( ids . SessionId )
session . Screens = append ( session . Screens , screenObj )
update . Info = & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "screen updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
TimeoutMs : 2000 ,
}
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-08-27 02:29:32 +02:00
ritem , err := resolveSessionScreen ( ctx , ids . SessionId , firstArg , pk . Kwargs [ "screen" ] )
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-08-23 01:00:25 +02:00
func UnSetCommand ( 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_RemoteConnected )
2022-08-23 03:38:52 +02:00
if err != nil {
2022-08-30 01:31:06 +02:00
return nil , fmt . Errorf ( "cannot unset: %v" , err )
2022-08-23 03:38:52 +02:00
}
2022-08-30 01:31:06 +02:00
envMap := shexec . ParseEnv0 ( ids . Remote . RemoteState . Env0 )
2022-08-23 03:38:52 +02:00
unsetVars := make ( map [ string ] bool )
for _ , argStr := range pk . Args {
eqIdx := strings . Index ( argStr , "=" )
if eqIdx != - 1 {
return nil , fmt . Errorf ( "invalid argument to setenv, '%s' (cannot contain equal sign)" , argStr )
}
delete ( envMap , argStr )
unsetVars [ argStr ] = true
}
2022-08-30 01:31:06 +02:00
state := * ids . Remote . RemoteState
2022-08-23 03:38:52 +02:00
state . Env0 = shexec . MakeEnv0 ( envMap )
2022-08-30 01:31:06 +02:00
remote , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , state )
2022-08-23 03:38:52 +02:00
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
Sessions : sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remote ) ,
2022-08-23 03:38:52 +02:00
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoMsg : fmt . Sprintf ( "[%s] unset vars: %s" , ids . Remote . DisplayName , formatStrs ( mapToStrs ( unsetVars ) , "and" , false ) ) ,
2022-08-23 03:38:52 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
}
2022-09-01 21:47:10 +02:00
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
}
if ids . Remote . RState . IsConnected ( ) {
return sstore . InfoMsgUpdate ( "remote %q already connected (no action taken)" , ids . Remote . DisplayName ) , nil
}
go ids . Remote . MShell . Launch ( )
return sstore . InfoMsgUpdate ( "remote %q reconnecting" , ids . Remote . DisplayName ) , nil
}
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 )
if ! ids . Remote . RState . IsConnected ( ) && ! force {
return sstore . InfoMsgUpdate ( "remote %q already disconnected (no action taken)" , ids . Remote . DisplayName ) , nil
}
numCommands := ids . Remote . MShell . GetNumRunningCommands ( )
if numCommands > 0 && ! force {
return nil , fmt . Errorf ( "remote not disconnected, %q has %d running commands. use 'force=1' to force disconnection" , ids . Remote . DisplayName )
}
ids . Remote . MShell . Disconnect ( )
return sstore . InfoMsgUpdate ( "remote %q disconnected" , ids . Remote . DisplayName ) , nil
}
2022-09-01 08:12:26 +02:00
func RemoteNewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-01 21:47:10 +02:00
if len ( pk . Args ) == 0 || pk . Args [ 0 ] == "" {
return nil , fmt . Errorf ( "/remote:new requires one positional argument of 'user@host'" )
}
userHost := pk . Args [ 0 ]
m := userHostRe . FindStringSubmatch ( userHost )
if m == nil {
return nil , fmt . Errorf ( "/remote:new invalid format of user@host argument" )
}
sudoStr , remoteUser , remoteHost := m [ 1 ] , m [ 2 ] , m [ 3 ]
alias := pk . Kwargs [ "alias" ]
if alias != "" {
if len ( alias ) > MaxRemoteAliasLen {
return nil , fmt . Errorf ( "alias too long, max length = %d" , MaxRemoteAliasLen )
}
if ! remoteAliasRe . MatchString ( alias ) {
return nil , fmt . Errorf ( "invalid alias format" )
}
}
connectMode := sstore . ConnectModeStartup
if pk . Kwargs [ "connectmode" ] != "" {
connectMode = pk . Kwargs [ "connectmode" ]
}
if ! sstore . IsValidConnectMode ( connectMode ) {
return nil , fmt . Errorf ( "/remote:new invalid connectmode %q: valid modes are %s" , connectMode , formatStrs ( [ ] string { sstore . ConnectModeStartup , sstore . ConnectModeAuto , sstore . ConnectModeManual } , "or" , false ) )
}
var isSudo bool
if sudoStr != "" {
isSudo = true
}
if pk . Kwargs [ "sudo" ] != "" {
sudoArg := resolveBool ( pk . Kwargs [ "sudo" ] , false )
if isSudo && ! sudoArg {
return nil , fmt . Errorf ( "/remote:new invalid 'sudo@' argument, with sudo kw arg set to false" )
}
if ! isSudo && sudoArg {
isSudo = true
userHost = "sudo@" + userHost
}
}
sshOpts := & sstore . SSHOpts {
Local : false ,
SSHHost : remoteHost ,
SSHUser : remoteUser ,
}
if pk . Kwargs [ "key" ] != "" {
keyFile := pk . Kwargs [ "key" ]
fd , err := os . Open ( keyFile )
if fd != nil {
fd . Close ( )
}
if err != nil {
return nil , fmt . Errorf ( "/remote:new invalid key %q (cannot read): %v" , keyFile , err )
}
sshOpts . SSHIdentity = keyFile
}
remoteOpts := & sstore . RemoteOptsType { }
if pk . Kwargs [ "color" ] != "" {
color := pk . Kwargs [ "color" ]
err := validateRemoteColor ( color , "remote color" )
if err != nil {
return nil , err
}
remoteOpts . Color = color
}
r := & sstore . RemoteType {
RemoteId : uuid . New ( ) . String ( ) ,
PhysicalId : "" ,
RemoteType : sstore . RemoteTypeSsh ,
RemoteAlias : alias ,
RemoteCanonicalName : userHost ,
RemoteSudo : isSudo ,
RemoteUser : remoteUser ,
RemoteHost : remoteHost ,
ConnectMode : connectMode ,
SSHOpts : sshOpts ,
RemoteOpts : remoteOpts ,
}
err := sstore . InsertRemote ( ctx , r )
if err != nil {
return nil , fmt . Errorf ( "cannot create remote %q: %v" , r . RemoteCanonicalName , err )
}
update := & sstore . ModelUpdate {
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-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
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "type" , state . RemoteType ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "remoteid" , state . RemoteId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "physicalid" , state . PhysicalId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "alias" , state . RemoteAlias ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "canonicalname" , state . RemoteCanonicalName ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "status" , state . Status ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "connectmode" , state . ConnectMode ) )
2022-08-30 01:31:06 +02:00
if ids . Remote . RemoteState != nil {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "cwd" , ids . Remote . RemoteState . Cwd ) )
2022-08-27 02:17:33 +02:00
}
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoTitle : fmt . Sprintf ( "show remote '%s' info" , ids . Remote . DisplayName ) ,
2022-08-28 23:24:05 +02:00
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
} , 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 {
InfoTitle : fmt . Sprintf ( "show all remote info" ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
2022-08-27 02:17:33 +02:00
} ,
} , nil
2022-08-23 23:01:52 +02:00
}
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-21 21:31:29 +02:00
func SetEnvCommand ( 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_RemoteConnected )
2022-08-23 03:38:52 +02:00
if err != nil {
2022-08-30 01:31:06 +02:00
return nil , fmt . Errorf ( "cannot setenv: %v" , err )
2022-08-23 03:38:52 +02:00
}
2022-08-30 01:31:06 +02:00
envMap := shexec . ParseEnv0 ( ids . Remote . RemoteState . Env0 )
2022-08-23 03:53:38 +02:00
if len ( pk . Args ) == 0 {
var infoLines [ ] string
for varName , varVal := range envMap {
line := fmt . Sprintf ( "%s=%s" , varName , shellescape . Quote ( varVal ) )
infoLines = append ( infoLines , line )
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
2022-08-23 03:53:38 +02:00
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoTitle : fmt . Sprintf ( "environment for [%s] remote" , ids . Remote . DisplayName ) ,
2022-08-23 03:53:38 +02:00
InfoLines : infoLines ,
} ,
}
return update , nil
}
2022-08-23 03:38:52 +02:00
setVars := make ( map [ string ] bool )
for _ , argStr := range pk . Args {
eqIdx := strings . Index ( argStr , "=" )
if eqIdx == - 1 {
return nil , fmt . Errorf ( "invalid argument to setenv, '%s' (no equal sign)" , argStr )
}
envName := argStr [ : eqIdx ]
envVal := argStr [ eqIdx + 1 : ]
envMap [ envName ] = envVal
setVars [ envName ] = true
}
2022-08-30 01:31:06 +02:00
state := * ids . Remote . RemoteState
2022-08-23 03:38:52 +02:00
state . Env0 = shexec . MakeEnv0 ( envMap )
2022-08-30 01:31:06 +02:00
remote , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , state )
2022-08-23 03:38:52 +02:00
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
Sessions : sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remote ) ,
2022-08-23 03:38:52 +02:00
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoMsg : fmt . Sprintf ( "[%s] set vars: %s" , ids . Remote . DisplayName , formatStrs ( mapToStrs ( setVars ) , "and" , false ) ) ,
2022-08-23 03:38:52 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-08-21 21:31:29 +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-08-24 22:21:54 +02:00
remoteName , rptr , _ , _ , 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 {
return nil , fmt . Errorf ( "/cr error: remote '%s' not found" , 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 {
InfoMsg : fmt . Sprintf ( "current remote = %s" , remoteName ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
}
2022-07-16 02:37:32 +02:00
func CdCommand ( 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_RemoteConnected )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cd error: %w" , err )
2022-07-16 02:37:32 +02:00
}
newDir := firstArg ( pk )
2022-08-09 23:24:57 +02:00
if newDir == "" {
2022-08-24 11:14:16 +02:00
return sstore . ModelUpdate {
2022-08-17 21:24:09 +02:00
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoMsg : fmt . Sprintf ( "[%s] current directory = %s" , ids . Remote . DisplayName , ids . Remote . RemoteState . Cwd ) ,
2022-08-17 21:24:09 +02:00
} ,
} , nil
}
2022-08-30 01:31:06 +02:00
newDir , err = ids . Remote . RState . ExpandHomeDir ( newDir )
2022-08-17 21:24:09 +02:00
if err != nil {
return nil , err
2022-08-09 23:24:57 +02:00
}
if ! strings . HasPrefix ( newDir , "/" ) {
2022-08-30 01:31:06 +02:00
if ids . Remote . RemoteState == nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cd error: cannot get current remote directory (can only cd with absolute path)" )
2022-08-09 23:24:57 +02:00
}
2022-08-30 01:31:06 +02:00
newDir = path . Join ( ids . Remote . RemoteState . Cwd , newDir )
2022-08-09 23:24:57 +02:00
newDir , err = filepath . Abs ( newDir )
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cd error: error canonicalizing new directory: %w" , err )
2022-08-09 23:24:57 +02:00
}
}
2022-07-16 02:37:32 +02:00
cdPacket := packet . MakeCdPacket ( )
cdPacket . ReqId = uuid . New ( ) . String ( )
cdPacket . Dir = newDir
2022-08-30 01:31:06 +02:00
resp , err := ids . Remote . MShell . PacketRpc ( ctx , cdPacket )
2022-08-09 23:24:57 +02:00
if err != nil {
return nil , err
}
2022-08-11 03:33:32 +02:00
if err = resp . Err ( ) ; err != nil {
return nil , err
}
2022-08-30 01:31:06 +02:00
state := * ids . Remote . RemoteState
2022-08-23 03:38:52 +02:00
state . Cwd = newDir
2022-08-30 01:31:06 +02:00
remoteInst , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , state )
2022-08-09 23:24:57 +02:00
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
2022-08-30 01:31:06 +02:00
Sessions : sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remoteInst ) ,
2022-08-11 03:33:32 +02:00
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoMsg : fmt . Sprintf ( "[%s] current directory = %s" , ids . Remote . DisplayName , newDir ) ,
2022-08-11 03:33:32 +02:00
TimeoutMs : 2000 ,
} ,
2022-08-09 23:24:57 +02:00
}
return update , nil
}
func getStrArr ( v interface { } , field string ) [ ] string {
if v == nil {
return nil
}
m , ok := v . ( map [ string ] interface { } )
if ! ok {
return nil
}
fieldVal := m [ field ]
if fieldVal == nil {
return nil
}
iarr , ok := fieldVal . ( [ ] interface { } )
if ! ok {
return nil
}
var sarr [ ] string
for _ , iv := range iarr {
if sv , ok := iv . ( string ) ; ok {
sarr = append ( sarr , sv )
}
}
return sarr
}
2022-08-11 03:33:32 +02:00
func getBool ( v interface { } , field string ) bool {
if v == nil {
return false
}
m , ok := v . ( map [ string ] interface { } )
if ! ok {
return false
}
fieldVal := m [ field ]
if fieldVal == nil {
return false
}
bval , ok := fieldVal . ( bool )
if ! ok {
return false
}
return bval
}
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
}
func makeInsertUpdateFromComps ( pos int64 , prefix string , comps [ ] string , hasMore bool ) sstore . UpdatePacket {
if hasMore {
return nil
}
lcp := longestPrefix ( prefix , comps )
if lcp == prefix || len ( lcp ) < len ( prefix ) || ! strings . HasPrefix ( lcp , prefix ) {
return nil
}
insertChars := lcp [ len ( prefix ) : ]
clu := & sstore . CmdLineType { InsertChars : insertChars , InsertPos : pos }
2022-08-24 11:14:16 +02:00
return sstore . ModelUpdate { CmdLine : clu }
2022-08-11 03:33:32 +02:00
}
func longestPrefix ( root string , comps [ ] string ) string {
if len ( comps ) == 0 {
return root
}
if len ( comps ) == 1 {
comp := comps [ 0 ]
if len ( comp ) >= len ( root ) && strings . HasPrefix ( comp , root ) {
2022-08-11 19:21:45 +02:00
if strings . HasSuffix ( comp , "/" ) {
return comps [ 0 ]
}
2022-08-11 03:33:32 +02:00
return comps [ 0 ] + " "
}
}
lcp := comps [ 0 ]
for i := 1 ; i < len ( comps ) ; i ++ {
s := comps [ i ]
for j := 0 ; j < len ( lcp ) ; j ++ {
if j >= len ( s ) || lcp [ j ] != s [ j ] {
lcp = lcp [ 0 : j ]
break
}
}
}
if len ( lcp ) < len ( root ) || ! strings . HasPrefix ( lcp , root ) {
return root
}
return lcp
}
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-08-24 02:26:42 +02: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 {
return nil , false , fmt . Errorf ( "compgen error: %w" , err )
}
2022-08-09 23:24:57 +02:00
cgPacket := packet . MakeCompGenPacket ( )
cgPacket . ReqId = uuid . New ( ) . String ( )
cgPacket . CompType = compType
cgPacket . Prefix = prefix
2022-08-30 01:31:06 +02:00
cgPacket . Cwd = ids . Remote . RemoteState . Cwd
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-08-09 23:24:57 +02:00
comps := getStrArr ( resp . Data , "comps" )
2022-08-11 03:33:32 +02:00
hasMore := getBool ( resp . Data , "hasmore" )
return comps , hasMore , nil
}
func CompGenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
cmdLine := firstArg ( pk )
pos := len ( cmdLine )
if pk . Kwargs [ "comppos" ] != "" {
posArg , err := strconv . Atoi ( pk . Kwargs [ "comppos" ] )
if err != nil {
2022-08-24 02:26:42 +02: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 )
prefix := cmdLine [ : pos ]
parts := strings . Split ( prefix , " " )
compType := "file"
2022-08-24 02:26:42 +02:00
if len ( parts ) > 0 && len ( parts ) < 2 && strings . HasPrefix ( parts [ 0 ] , "/" ) {
2022-08-11 03:33:32 +02:00
compType = "metacommand"
2022-08-24 02:26:42 +02:00
} else if len ( parts ) == 2 && ( parts [ 0 ] == "cd" || parts [ 0 ] == "/cd" ) {
2022-08-11 03:33:32 +02:00
compType = "directory"
} else if len ( parts ) <= 1 {
compType = "command"
}
lastPart := ""
if len ( parts ) > 0 {
lastPart = parts [ len ( parts ) - 1 ]
}
2022-08-30 01:31:06 +02:00
comps , hasMore , err := doCompGen ( ctx , pk , lastPart , compType , showComps )
2022-08-11 03:33:32 +02:00
if err != nil {
return nil , err
}
if showComps {
return makeInfoFromComps ( compType , comps , hasMore ) , nil
}
return makeInsertUpdateFromComps ( int64 ( pos ) , lastPart , comps , hasMore ) , 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-08-24 11:14:16 +02:00
return sstore . ModelUpdate { Line : rtnLine } , 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
}
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-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-08-27 02:17:33 +02:00
bareSessions , err := sstore . GetBareSessions ( ctx )
2022-08-09 01:21:46 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:17:33 +02:00
ritems := sessionsToResolveItems ( bareSessions )
ritem , err := genericResolve ( firstArg , pk . Kwargs [ "session" ] , ritems , "session" )
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-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
}
maxItems , err := resolveInt ( pk . Kwargs [ "maxitems" ] , DefaultMaxHistoryItems )
if err != nil {
return nil , fmt . Errorf ( "invalid maxitems value '%s' (must be a number): %v" , pk . Kwargs [ "maxitems" ] , err )
}
if maxItems < 0 {
return nil , fmt . Errorf ( "invalid maxitems value '%s' (cannot be negative)" , maxItems )
}
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-08-28 23:24:05 +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
}