diff --git a/conn.go b/conn.go index b1d4ba7..a2613a4 100644 --- a/conn.go +++ b/conn.go @@ -66,7 +66,7 @@ func (c *Conn) isAsyncReaderRunning() bool { func (c *Conn) asyncReader() { for { - resp, err := c.readResponse() + resp, err := c.ReadResponse() if err != nil { c.setRdErr(err, false) break @@ -107,7 +107,7 @@ func (c *Conn) Close() error { // StartAsyncReader starts the asynchronous reader go routine that allows // 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() { c.asyncReaderLock.Lock() 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. // If the async. reader is not currently running, events received while waiting -// for the response will be silently dropped. Calling StartAsyncReader -// simultaniously with Request will lead to undefined behavior. +// for the response will be silently dropped. Calling Request simultaniously +// with StartAsyncReader, Read, Write, or ReadResponse will lead to undefined +// behavior. func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) { if err := c.getRdErr(); err != nil { 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 // response, while silently swallowing events. for resp == nil || resp.IsAsync() { - resp, err = c.readResponse() + resp, err = c.ReadResponse() if err != nil { 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 -// 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) { return c.conn.R.Read(p) } diff --git a/response.go b/response.go index fb16d0e..8fb2dc8 100644 --- a/response.go +++ b/response.go @@ -11,6 +11,7 @@ import ( "log" "net/textproto" "strconv" + "strings" ) // 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" // removed, all intervening CRs stripped). 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 @@ -45,7 +49,10 @@ func (r *Response) IsAsync() bool { 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 statusCode int for { @@ -74,11 +81,15 @@ func (c *Conn) readResponse() (*Response, error) { // lines. return nil, newProtocolError("status code changed: %03d != %03d", code, statusCode) } + if resp.RawLines == nil { + resp.RawLines = make([]string, 0, 1) + } if line[3] == ' ' { // Final line in the response. resp.Reply = line[4:] resp.Err = statusCodeToError(statusCode, resp.Reply) + resp.RawLines = append(resp.RawLines, line) return resp, nil } @@ -89,9 +100,11 @@ func (c *Conn) readResponse() (*Response, error) { case '-': // Continuation, keep reading. resp.Data = append(resp.Data, line[4:]) + resp.RawLines = append(resp.RawLines, line) case '+': // A "dot-encoded" payload follows. resp.Data = append(resp.Data, line[4:]) + resp.RawLines = append(resp.RawLines, line) dotBody, err := c.conn.ReadDotBytes() if err != nil { return nil, err @@ -100,6 +113,11 @@ func (c *Conn) readResponse() (*Response, error) { log.Printf("S: [dot encoded data]") } 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: return nil, newProtocolError("invalid separator: '%c'", line[3]) }