package nexgb import ( "errors" ) // Cookie is the internal representation of a cookie, where one is generated // for *every* request sent by XGB. // 'cookie' is most frequently used by embedding it into a more specific // kind of cookie, i.e., 'GetInputFocusCookie'. type Cookie struct { conn *Conn Sequence uint16 replyChan chan []byte errorChan chan error pingChan chan bool } // NewCookie creates a new cookie with the correct channels initialized // depending upon the values of 'checked' and 'reply'. Together, there are // four different kinds of cookies. (See more detailed comments in the // function for more info on those.) // Note that a sequence number is not set until just before the request // corresponding to this cookie is sent over the wire. // // Unless you're building requests from bytes by hand, this method should // not be used. func (c *Conn) NewCookie(checked, reply bool) *Cookie { cookie := &Cookie{ conn: c, Sequence: 0, // we add the sequence id just before sending a request replyChan: nil, errorChan: nil, pingChan: nil, } // There are four different kinds of cookies: // Checked requests with replies get a reply channel and an error channel. // Unchecked requests with replies get a reply channel and a ping channel. // Checked requests w/o replies get a ping channel and an error channel. // Unchecked requests w/o replies get no channels. // The reply channel is used to send reply data. // The error channel is used to send error data. // The ping channel is used when one of the 'reply' or 'error' channels // is missing but the other is present. The ping channel is way to force // the blocking to stop and basically say "the error has been received // in the main event loop" (when the ping channel is coupled with a reply // channel) or "the request you made that has no reply was successful" // (when the ping channel is coupled with an error channel). if checked { cookie.errorChan = make(chan error, 1) if !reply { cookie.pingChan = make(chan bool, 1) } } if reply { cookie.replyChan = make(chan []byte, 1) if !checked { cookie.pingChan = make(chan bool, 1) } } return cookie } // Reply detects whether this is a checked or unchecked cookie, and calls // 'replyChecked' or 'replyUnchecked' appropriately. // // Unless you're building requests from bytes by hand, this method should // not be used. func (c Cookie) Reply() ([]byte, error) { // checked if c.errorChan != nil { return c.replyChecked() } return c.replyUnchecked() } // replyChecked waits for a response on either the replyChan or errorChan // channels. If the former arrives, the bytes are returned with a nil error. // If the latter arrives, no bytes are returned (nil) and the error received // is returned. // // Unless you're building requests from bytes by hand, this method should // not be used. func (c Cookie) replyChecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + "is not expecting a *reply* or an error.") } if c.errorChan == nil { return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + "is not expecting a reply or an *error*.") } select { case reply := <-c.replyChan: return reply, nil case err := <-c.errorChan: return nil, err } } // replyUnchecked waits for a response on either the replyChan or pingChan // channels. If the former arrives, the bytes are returned with a nil error. // If the latter arrives, no bytes are returned (nil) and a nil error // is returned. (In the latter case, the corresponding error can be retrieved // from (Wait|Poll)ForEvent asynchronously.) // In all honesty, you *probably* don't want to use this method. // // Unless you're building requests from bytes by hand, this method should // not be used. func (c Cookie) replyUnchecked() ([]byte, error) { if c.replyChan == nil { return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + "that is not expecting a *reply*.") } select { case reply := <-c.replyChan: return reply, nil case <-c.pingChan: return nil, nil } } // Check is used for checked requests that have no replies. It is a mechanism // by which to report "success" or "error" in a synchronous fashion. (Therefore, // unchecked requests without replies cannot use this method.) // If the request causes an error, it is sent to this cookie's errorChan. // If the request was successful, there is no response from the server. // Thus, pingChan is sent a value when the *next* reply is read. // If no more replies are being processed, we force a round trip request with // GetInputFocus. // // Unless you're building requests from bytes by hand, this method should // not be used. func (c Cookie) Check() error { if c.replyChan != nil { return errors.New("Cannot call 'Check' on a cookie that is " + "expecting a *reply*. Use 'Reply' instead.") } if c.errorChan == nil { return errors.New("Cannot call 'Check' on a cookie that is " + "not expecting a possible *error*.") } // First do a quick non-blocking check to see if we've been pinged. select { case err := <-c.errorChan: return err case <-c.pingChan: return nil default: } // Now force a round trip and try again, but block this time. c.conn.Sync() select { case err := <-c.errorChan: return err case <-c.pingChan: return nil } }