Merge pull request #4 from leif/RawLines

make ReadResponse public, and add RawLines
This commit is contained in:
Yawning Angel 2016-08-21 22:24:25 +00:00 committed by GitHub
commit 2d65b370c8
2 changed files with 27 additions and 7 deletions

14
conn.go
View File

@ -66,7 +66,7 @@ func (c *Conn) isAsyncReaderRunning() bool {
func (c *Conn) asyncReader() { func (c *Conn) asyncReader() {
for { for {
resp, err := c.readResponse() resp, err := c.ReadResponse()
if err != nil { if err != nil {
c.setRdErr(err, false) c.setRdErr(err, false)
break break
@ -107,7 +107,7 @@ func (c *Conn) Close() error {
// StartAsyncReader starts the asynchronous reader go routine that allows // StartAsyncReader starts the asynchronous reader go routine that allows
// asynchronous events to be handled. It must not be called simultaniously // asynchronous events to be handled. It must not be called simultaniously
// with Request or undefined behavior will occur. // with Read, Request, or ReadResponse or undefined behavior will occur.
func (c *Conn) StartAsyncReader() { func (c *Conn) StartAsyncReader() {
c.asyncReaderLock.Lock() c.asyncReaderLock.Lock()
defer c.asyncReaderLock.Unlock() defer c.asyncReaderLock.Unlock()
@ -145,8 +145,9 @@ func (c *Conn) NextEvent() (*Response, error) {
// Request sends a raw control port request and returns the response. // Request sends a raw control port request and returns the response.
// If the async. reader is not currently running, events received while waiting // If the async. reader is not currently running, events received while waiting
// for the response will be silently dropped. Calling StartAsyncReader // for the response will be silently dropped. Calling Request simultaniously
// simultaniously with Request will lead to undefined behavior. // with StartAsyncReader, Read, Write, or ReadResponse will lead to undefined
// behavior.
func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) { func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) {
if err := c.getRdErr(); err != nil { if err := c.getRdErr(); err != nil {
return nil, err return nil, err
@ -175,7 +176,7 @@ func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) {
// Event handing requires the asyncReader() goroutine, try to get a // Event handing requires the asyncReader() goroutine, try to get a
// response, while silently swallowing events. // response, while silently swallowing events.
for resp == nil || resp.IsAsync() { for resp == nil || resp.IsAsync() {
resp, err = c.readResponse() resp, err = c.ReadResponse()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -191,7 +192,8 @@ func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) {
} }
// Read reads directly from the control port connection. Mixing this call // Read reads directly from the control port connection. Mixing this call
// with Request, or asynchronous events will lead to undefined behavior. // with Request, ReadResponse, or asynchronous events will lead to undefined
// behavior.
func (c *Conn) Read(p []byte) (int, error) { func (c *Conn) Read(p []byte) (int, error) {
return c.conn.R.Read(p) return c.conn.R.Read(p)
} }

View File

@ -11,6 +11,7 @@ import (
"log" "log"
"net/textproto" "net/textproto"
"strconv" "strconv"
"strings"
) )
// Response is a response to a control port command, or an asyncrhonous event. // Response is a response to a control port command, or an asyncrhonous event.
@ -27,6 +28,9 @@ type Response struct {
// data is "decoded" and presented as a single string (terminal ".CRLF" // data is "decoded" and presented as a single string (terminal ".CRLF"
// removed, all intervening CRs stripped). // removed, all intervening CRs stripped).
Data []string Data []string
// RawLines is all of the lines of a response, without CRLFs.
RawLines []string
} }
// IsOk returns true if the response status code indicates success or // IsOk returns true if the response status code indicates success or
@ -45,7 +49,10 @@ func (r *Response) IsAsync() bool {
return r.Err.Code == StatusAsyncEvent return r.Err.Code == StatusAsyncEvent
} }
func (c *Conn) readResponse() (*Response, error) { // ReadResponse returns the next response object. Calling this
// simultaniously with Read, Request, or StartAsyncReader will lead to
// undefined behavior
func (c *Conn) ReadResponse() (*Response, error) {
var resp *Response var resp *Response
var statusCode int var statusCode int
for { for {
@ -74,11 +81,15 @@ func (c *Conn) readResponse() (*Response, error) {
// lines. // lines.
return nil, newProtocolError("status code changed: %03d != %03d", code, statusCode) return nil, newProtocolError("status code changed: %03d != %03d", code, statusCode)
} }
if resp.RawLines == nil {
resp.RawLines = make([]string, 0, 1)
}
if line[3] == ' ' { if line[3] == ' ' {
// Final line in the response. // Final line in the response.
resp.Reply = line[4:] resp.Reply = line[4:]
resp.Err = statusCodeToError(statusCode, resp.Reply) resp.Err = statusCodeToError(statusCode, resp.Reply)
resp.RawLines = append(resp.RawLines, line)
return resp, nil return resp, nil
} }
@ -89,9 +100,11 @@ func (c *Conn) readResponse() (*Response, error) {
case '-': case '-':
// Continuation, keep reading. // Continuation, keep reading.
resp.Data = append(resp.Data, line[4:]) resp.Data = append(resp.Data, line[4:])
resp.RawLines = append(resp.RawLines, line)
case '+': case '+':
// A "dot-encoded" payload follows. // A "dot-encoded" payload follows.
resp.Data = append(resp.Data, line[4:]) resp.Data = append(resp.Data, line[4:])
resp.RawLines = append(resp.RawLines, line)
dotBody, err := c.conn.ReadDotBytes() dotBody, err := c.conn.ReadDotBytes()
if err != nil { if err != nil {
return nil, err return nil, err
@ -100,6 +113,11 @@ func (c *Conn) readResponse() (*Response, error) {
log.Printf("S: [dot encoded data]") log.Printf("S: [dot encoded data]")
} }
resp.Data = append(resp.Data, string(dotBody)) resp.Data = append(resp.Data, string(dotBody))
dotLines := strings.Split(string(dotBody), "\n")
for _, dotLine := range dotLines[:len(dotLines)-1] {
resp.RawLines = append(resp.RawLines, dotLine)
}
resp.RawLines = append(resp.RawLines, ".")
default: default:
return nil, newProtocolError("invalid separator: '%c'", line[3]) return nil, newProtocolError("invalid separator: '%c'", line[3])
} }