David Haukeness
2 years ago
6 changed files with 242 additions and 253 deletions
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
package keybase |
||||
|
||||
import ( |
||||
"bufio" |
||||
"context" |
||||
"fmt" |
||||
"sync" |
||||
|
||||
"git.hugfreevikings.wtf/keybase/keybase/pkg/ctxreader" |
||||
) |
||||
|
||||
type ChatAPI struct { |
||||
sync.Mutex |
||||
toStdin chan []byte |
||||
fromStdout chan []byte |
||||
fromListen chan []byte |
||||
errors chan error |
||||
} |
||||
|
||||
func NewChatAPI(ctx context.Context, opts *Options) (api *ChatAPI, err error) { |
||||
// set up the channels
|
||||
api.toStdin = make(chan []byte, opts.ChannelBufferSize) |
||||
api.fromStdout = make(chan []byte, opts.ChannelBufferSize) |
||||
api.fromListen = make(chan []byte, opts.ChannelBufferSize) |
||||
api.errors = make(chan error, opts.ChannelBufferSize) |
||||
|
||||
// get the basics
|
||||
kbCmd := opts.locateKeybase() |
||||
chatApiArgs := opts.buildArgs("chat", "api") |
||||
chatApiListenArgs := opts.buildArgs("chat", "api-listen") |
||||
|
||||
// build the commands
|
||||
chatApi, err := newApiCmd(ctx, kbCmd, chatApiArgs...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
chatListen, err := newApiCmd(ctx, kbCmd, chatApiListenArgs...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// create the goroutines
|
||||
// listen reader
|
||||
go func() { |
||||
cr := ctxreader.NewContextReader(ctx, chatListen.Stdout()) |
||||
scanner := bufio.NewScanner(cr) |
||||
for scanner.Scan() { |
||||
api.fromListen <- scanner.Bytes() |
||||
} |
||||
}() |
||||
|
||||
// writing to stdin
|
||||
go func() { |
||||
for { |
||||
select { |
||||
case msg := <-api.toStdin: |
||||
_, err := chatApi.Stdin().Write(msg) |
||||
if err != nil { |
||||
api.errors <- err |
||||
} |
||||
case <-ctx.Done(): |
||||
return |
||||
} |
||||
} |
||||
}() |
||||
|
||||
// reading from stdout
|
||||
go func() { |
||||
cr := ctxreader.NewContextReader(ctx, chatApi.Stdout()) |
||||
scanner := bufio.NewScanner(cr) |
||||
for scanner.Scan() { |
||||
api.fromStdout <- scanner.Bytes() |
||||
} |
||||
}() |
||||
|
||||
// reading from stderr
|
||||
go func() { |
||||
cr := ctxreader.NewContextReader(ctx, chatApi.Stderr()) |
||||
scanner := bufio.NewScanner(cr) |
||||
for scanner.Scan() { |
||||
api.errors <- fmt.Errorf("%v", scanner.Text()) |
||||
} |
||||
}() |
||||
|
||||
// then start the cmds
|
||||
err = chatApi.Start() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
err = chatListen.Start() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func (c *ChatAPI) Listen() chan []byte { |
||||
return c.fromListen |
||||
} |
||||
|
||||
func (c *ChatAPI) SendRaw(msg []byte) ([]byte, error) { |
||||
c.Lock() |
||||
defer c.Unlock() |
||||
c.toStdin <- msg |
||||
select { |
||||
case resp := <-c.fromStdout: |
||||
return resp, nil |
||||
case err := <-c.errors: |
||||
return nil, err |
||||
} |
||||
} |
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
package keybase |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"io" |
||||
"os/exec" |
||||
) |
||||
|
||||
// apiCmd holds the pipes that connect to an executed command
|
||||
type apiCmd struct { |
||||
Stderr io.ReadCloser |
||||
Stdin io.WriteCloser |
||||
Stdout io.ReadCloser |
||||
startFunc func() error |
||||
} |
||||
|
||||
type cmd interface { |
||||
Start() error |
||||
Stdin() io.WriteCloser |
||||
Stdout() io.ReadCloser |
||||
Stderr() io.ReadCloser |
||||
} |
||||
|
||||
type defaultCmd struct { |
||||
apiCmd *apiCmd |
||||
} |
||||
|
||||
func (c *defaultCmd) Start() error { |
||||
return c.apiCmd.startFunc() |
||||
} |
||||
|
||||
func (c *defaultCmd) Stdin() io.WriteCloser { |
||||
return c.apiCmd.Stdin |
||||
} |
||||
|
||||
func (c *defaultCmd) Stdout() io.ReadCloser { |
||||
return c.apiCmd.Stdout |
||||
} |
||||
|
||||
func (c *defaultCmd) Stderr() io.ReadCloser { |
||||
return c.apiCmd.Stderr |
||||
} |
||||
|
||||
var _ cmd = &defaultCmd{} |
||||
|
||||
func newApiCmd(ctx context.Context, execCmd string, args ...string) (cmd, error) { |
||||
var err error |
||||
|
||||
// create a new apiCmd
|
||||
cmd := &apiCmd{} |
||||
|
||||
// set up the command
|
||||
api := exec.CommandContext(ctx, execCmd, args...) |
||||
|
||||
// an empty start func for failures
|
||||
cmd.startFunc = func() error { return nil } |
||||
|
||||
// hook up the stderr pipe
|
||||
if cmd.Stderr, err = api.StderrPipe(); err != nil { |
||||
return nil, fmt.Errorf("failed to get pipe: %v", err) |
||||
} |
||||
|
||||
// hook up the stdout pipe
|
||||
if cmd.Stdout, err = api.StdoutPipe(); err != nil { |
||||
return nil, fmt.Errorf("failed to get pipe: %v", err) |
||||
} |
||||
|
||||
// hook up the stdin pipe
|
||||
if cmd.Stdin, err = api.StdinPipe(); err != nil { |
||||
return nil, fmt.Errorf("failed to get pipe: %v", err) |
||||
} |
||||
|
||||
// create an anonymous function to start the commands
|
||||
cmd.startFunc = func() error { |
||||
if err := api.Start(); err != nil { |
||||
return fmt.Errorf("failed to start command: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
return &defaultCmd{cmd}, nil |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
package ctxreader |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
) |
||||
|
||||
type ContextReader struct { |
||||
ctx context.Context |
||||
r io.ReadCloser |
||||
} |
||||
|
||||
func (cr *ContextReader) Read(p []byte) (n int, err error) { |
||||
if err := cr.ctx.Err(); err != nil { |
||||
return 0, err |
||||
} |
||||
return cr.r.Read(p) |
||||
} |
||||
|
||||
func (cr *ContextReader) Close() error { |
||||
return cr.r.Close() |
||||
} |
||||
|
||||
func NewContextReader(ctx context.Context, r io.ReadCloser) io.ReadCloser { |
||||
return &ContextReader{ |
||||
ctx: ctx, |
||||
r: r, |
||||
} |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
package keybase |
||||
|
||||
type RateLimit struct { |
||||
Tank string `json:"tank"` |
||||
Capacity int `json:"capacity"` |
||||
Reset int `json:"reset"` |
||||
Gas int `json:"gas"` |
||||
} |
||||
|
||||
type KeybaseApiResponse struct { |
||||
Result struct { |
||||
Message string `json:"message"` |
||||
ID int `json:"id"` |
||||
RateLimits []RateLimit `json:"ratelimits"` |
||||
} `json:"result"` |
||||
} |
Loading…
Reference in new issue