Refactor io in preparation for using tui

This commit is contained in:
Kovid Goyal 2022-08-25 11:00:30 +05:30
parent 6f8c884bb5
commit 32e59257d2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 76 additions and 118 deletions

View File

@ -22,7 +22,6 @@ import (
"kitty/tools/tty"
"kitty/tools/tui"
"kitty/tools/utils"
"kitty/tools/wcswidth"
)
var ProtocolVersion [3]int = [3]int{0, 20, 0}
@ -75,78 +74,23 @@ type serializer_func func(rc *utils.RemoteControlCmd) ([]byte, error)
var serializer serializer_func = simple_serializer
func create_serializer(password string, encoded_pubkey string, response_timeout float64) (ans serializer_func, timeout float64, err error) {
timeout = response_timeout
func create_serializer(password string, encoded_pubkey string, io_data *rc_io_data) (err error) {
io_data.serializer = simple_serializer
if password != "" {
encryption_version, pubkey, err := get_pubkey(encoded_pubkey)
if err != nil {
return nil, timeout, err
return err
}
ans = func(rc *utils.RemoteControlCmd) (ans []byte, err error) {
io_data.serializer = func(rc *utils.RemoteControlCmd) (ans []byte, err error) {
ec, err := crypto.Encrypt_cmd(rc, global_options.password, pubkey, encryption_version)
ans, err = json.Marshal(ec)
return
}
if timeout < 120 {
timeout = 120
if io_data.timeout < 120*time.Second {
io_data.timeout = 120 * time.Second
}
return ans, timeout, nil
}
return simple_serializer, timeout, nil
}
type IOAbstraction interface {
WriteAllWithTimeout(b []byte, d time.Duration) (n int, err error)
WriteFromReader(r utils.Reader, read_timeout time.Duration, write_timeout time.Duration) (n int, err error)
ReadWithTimeout(b []byte, d time.Duration) (n int, err error)
}
func do_io(device IOAbstraction, input utils.Reader, no_response bool, response_timeout time.Duration) (serialized_response []byte, err error) {
_, err = device.WriteAllWithTimeout([]byte("\x1bP@kitty-cmd"), 2*time.Second)
if err != nil {
return
}
_, err = device.WriteFromReader(input, 2*time.Second, 2*time.Second)
if err != nil {
return
}
_, err = device.WriteAllWithTimeout([]byte("\x1b\\"), 2*time.Second)
if err != nil {
return
}
if no_response {
return
}
response_received := false
cmd_prefix := []byte("@kitty-cmd")
handle_dcs := func(b []byte) error {
if bytes.HasPrefix(b, cmd_prefix) {
response_received = true
}
serialized_response = b[len(cmd_prefix):]
return nil
}
var p = wcswidth.EscapeCodeParser{HandleDCS: handle_dcs}
buf := make([]byte, 0, utils.DEFAULT_IO_BUFFER_SIZE)
for !response_received {
buf = buf[:cap(buf)]
var n int
n, err = device.ReadWithTimeout(buf, response_timeout)
if err != nil {
if err == os.ErrDeadlineExceeded {
err = fmt.Errorf("Timed out while waiting for a response from kitty")
}
return
}
buf = buf[:n]
p.Parse(buf)
}
return
return nil
}
type ResponseData struct {
@ -176,40 +120,24 @@ type Response struct {
Traceback string `json:"tb,omitempty"`
}
func get_response(rc *utils.RemoteControlCmd, timeout float64) (ans *Response, err error) {
serializer, timeout, err = create_serializer(global_options.password, "", timeout)
if err != nil {
return
}
d, err := serializer(rc)
if err != nil {
return
}
var device IOAbstraction
if global_options.to_network == "" {
var term *tty.Term
term, err = tty.OpenControllingTerm(tty.SetRaw)
if err != nil {
return
}
defer term.RestoreAndClose()
device = term
} else {
err = fmt.Errorf("TODO: Implement socket IO")
return
}
r := utils.BytesReader{Data: d}
serialized_response, err := do_io(device, &r, rc.NoResponse, time.Duration(timeout*float64(time.Second)))
type rc_io_data struct {
cmd *cobra.Command
rc *utils.RemoteControlCmd
serializer serializer_func
next_block func(rc *utils.RemoteControlCmd, serializer serializer_func) (b []byte, err error)
send_keypresses bool
string_response_is_err bool
timeout time.Duration
}
func get_response(do_io func(io_data *rc_io_data) ([]byte, error), io_data *rc_io_data) (ans *Response, err error) {
serialized_response, err := do_io(io_data)
if err != nil {
if err == os.ErrDeadlineExceeded {
rc.Payload = nil
rc.CancelAsync = true
rc.NoResponse = true
d, err = serializer(rc)
if err != nil {
return
}
_, err = do_io(device, &r, rc.NoResponse, 0)
io_data.rc.Payload = nil
io_data.rc.CancelAsync = true
io_data.rc.NoResponse = true
_, err = do_io(io_data)
}
return
}
@ -227,12 +155,24 @@ func get_response(rc *utils.RemoteControlCmd, timeout float64) (ans *Response, e
return
}
func send_rc_command(cmd *cobra.Command, rc *utils.RemoteControlCmd, timeout float64, string_response_is_err bool) (err error) {
err = setup_global_options(cmd)
func send_rc_command(io_data *rc_io_data) (err error) {
err = setup_global_options(io_data.cmd)
if err != nil {
return err
}
response, err := get_response(rc, timeout)
err = create_serializer(global_options.password, "", io_data)
if err != nil {
return
}
var response *Response
if global_options.to_network == "" {
response, err = get_response(do_tty_io, io_data)
if err != nil {
return
}
} else {
return fmt.Errorf("TODO: Implement socket IO")
}
if err != nil || response == nil {
return err
}
@ -242,7 +182,7 @@ func send_rc_command(cmd *cobra.Command, rc *utils.RemoteControlCmd, timeout flo
}
return fmt.Errorf("%s", response.Error)
}
if response.Data.is_string && string_response_is_err {
if response.Data.is_string && io_data.string_response_is_err {
return fmt.Errorf("%s", response.Data.as_str)
}
fmt.Println(strings.TrimRight(response.Data.as_str, "\n \t"))

View File

@ -37,7 +37,8 @@ func TestCommandToJSON(t *testing.T) {
}
func TestRCSerialization(t *testing.T) {
serializer, _, err := create_serializer("", "", 0)
io_data := rc_io_data{}
err := create_serializer("", "", &io_data)
if err != nil {
t.Fatal(err)
}
@ -46,7 +47,7 @@ func TestRCSerialization(t *testing.T) {
Cmd: "test", Version: ver,
}
simple := func(expected string) {
actual, err := serializer(&rc)
actual, err := io_data.serializer(&rc)
if err != nil {
t.Fatal(err)
}
@ -64,11 +65,11 @@ func TestRCSerialization(t *testing.T) {
if err != nil {
t.Fatal(err)
}
serializer, _, err = create_serializer("tpw", pubkey, 0)
err = create_serializer("tpw", pubkey, &io_data)
if err != nil {
t.Fatal(err)
}
raw, err := serializer(&rc)
raw, err := io_data.serializer(&rc)
var ec utils.EncryptedRemoteControlCmd
err = json.Unmarshal([]byte(raw), &ec)
if err != nil {

View File

@ -7,6 +7,8 @@
package at
import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -26,21 +28,15 @@ type CMD_NAME_json_type struct {
var CMD_NAME_json CMD_NAME_json_type
func create_payload_CMD_NAME(args []string) (payload map[string]interface{}, err error) {
func create_payload_CMD_NAME(io_data *rc_io_data, args []string) (err error) {
return
}
func create_rc_CMD_NAME(args []string) (*utils.RemoteControlCmd, error) {
var err error
payload, err := create_payload_CMD_NAME(args)
if err != nil {
return nil, err
}
rc := utils.RemoteControlCmd{
Cmd: "CLI_NAME",
Version: ProtocolVersion,
NoResponse: NO_RESPONSE_BASE,
Payload: payload,
}
return &rc, nil
}
@ -61,7 +57,21 @@ func run_CMD_NAME(cmd *cobra.Command, args []string) (err error) {
if err == nil {
timeout = rt
}
err = send_rc_command(cmd, rc, timeout, STRING_RESPONSE_IS_ERROR)
io_data := rc_io_data{
cmd: cmd,
rc: rc,
timeout: time.Duration(timeout * float64(time.Second)),
string_response_is_err: STRING_RESPONSE_IS_ERROR,
next_block: func(rc *utils.RemoteControlCmd, serializer serializer_func) ([]byte, error) {
return make([]byte, 0), nil
},
}
err = create_payload_CMD_NAME(&io_data, args)
if err != nil {
return err
}
err = send_rc_command(&io_data)
return
}

7
tools/cmd/at/tty_io.go Normal file
View File

@ -0,0 +1,7 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package at
func do_tty_io(io_data *rc_io_data) (serialized_response []byte, err error) {
return
}

View File

@ -3,13 +3,13 @@
package utils
type RemoteControlCmd struct {
Cmd string `json:"cmd"`
Version [3]int `json:"version"`
NoResponse bool `json:"no_response,omitempty"`
Payload map[string]interface{} `json:"payload,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Password string `json:"password,omitempty"`
CancelAsync bool `json:"cancel_async,omitempty"`
Cmd string `json:"cmd"`
Version [3]int `json:"version"`
NoResponse bool `json:"no_response,omitempty"`
Payload *interface{} `json:"payload,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Password string `json:"password,omitempty"`
CancelAsync bool `json:"cancel_async,omitempty"`
}
type EncryptedRemoteControlCmd struct {