cookie.go (5929B)
1 package xgb 2 3 import ( 4 "errors" 5 "io" 6 ) 7 8 // Cookie is the internal representation of a cookie, where one is generated 9 // for *every* request sent by XGB. 10 // 'cookie' is most frequently used by embedding it into a more specific 11 // kind of cookie, i.e., 'GetInputFocusCookie'. 12 type Cookie struct { 13 conn *Conn 14 Sequence uint16 15 replyChan chan []byte 16 errorChan chan error 17 pingChan chan bool 18 } 19 20 // NewCookie creates a new cookie with the correct channels initialized 21 // depending upon the values of 'checked' and 'reply'. Together, there are 22 // four different kinds of cookies. (See more detailed comments in the 23 // function for more info on those.) 24 // Note that a sequence number is not set until just before the request 25 // corresponding to this cookie is sent over the wire. 26 // 27 // Unless you're building requests from bytes by hand, this method should 28 // not be used. 29 func (c *Conn) NewCookie(checked, reply bool) *Cookie { 30 cookie := &Cookie{ 31 conn: c, 32 Sequence: 0, // we add the sequence id just before sending a request 33 replyChan: nil, 34 errorChan: nil, 35 pingChan: nil, 36 } 37 38 // There are four different kinds of cookies: 39 // Checked requests with replies get a reply channel and an error channel. 40 // Unchecked requests with replies get a reply channel and a ping channel. 41 // Checked requests w/o replies get a ping channel and an error channel. 42 // Unchecked requests w/o replies get no channels. 43 // The reply channel is used to send reply data. 44 // The error channel is used to send error data. 45 // The ping channel is used when one of the 'reply' or 'error' channels 46 // is missing but the other is present. The ping channel is way to force 47 // the blocking to stop and basically say "the error has been received 48 // in the main event loop" (when the ping channel is coupled with a reply 49 // channel) or "the request you made that has no reply was successful" 50 // (when the ping channel is coupled with an error channel). 51 if checked { 52 cookie.errorChan = make(chan error, 1) 53 if !reply { 54 cookie.pingChan = make(chan bool, 1) 55 } 56 } 57 if reply { 58 cookie.replyChan = make(chan []byte, 1) 59 if !checked { 60 cookie.pingChan = make(chan bool, 1) 61 } 62 } 63 64 return cookie 65 } 66 67 // Reply detects whether this is a checked or unchecked cookie, and calls 68 // 'replyChecked' or 'replyUnchecked' appropriately. 69 // 70 // Unless you're building requests from bytes by hand, this method should 71 // not be used. 72 func (c Cookie) Reply() ([]byte, error) { 73 // checked 74 if c.errorChan != nil { 75 return c.replyChecked() 76 } 77 return c.replyUnchecked() 78 } 79 80 // replyChecked waits for a response on either the replyChan or errorChan 81 // channels. If the former arrives, the bytes are returned with a nil error. 82 // If the latter arrives, no bytes are returned (nil) and the error received 83 // is returned. 84 // Returns (nil, io.EOF) when the connection is closed. 85 // 86 // Unless you're building requests from bytes by hand, this method should 87 // not be used. 88 func (c Cookie) replyChecked() ([]byte, error) { 89 if c.replyChan == nil { 90 return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + 91 "is not expecting a *reply* or an error.") 92 } 93 if c.errorChan == nil { 94 return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + 95 "is not expecting a reply or an *error*.") 96 } 97 98 select { 99 case reply := <-c.replyChan: 100 return reply, nil 101 case err := <-c.errorChan: 102 return nil, err 103 case <-c.conn.doneRead: 104 // c.conn.readResponses is no more, there will be no replys or errors 105 return nil, io.EOF 106 } 107 } 108 109 // replyUnchecked waits for a response on either the replyChan or pingChan 110 // channels. If the former arrives, the bytes are returned with a nil error. 111 // If the latter arrives, no bytes are returned (nil) and a nil error 112 // is returned. (In the latter case, the corresponding error can be retrieved 113 // from (Wait|Poll)ForEvent asynchronously.) 114 // Returns (nil, io.EOF) when the connection is closed. 115 // In all honesty, you *probably* don't want to use this method. 116 // 117 // Unless you're building requests from bytes by hand, this method should 118 // not be used. 119 func (c Cookie) replyUnchecked() ([]byte, error) { 120 if c.replyChan == nil { 121 return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + 122 "that is not expecting a *reply*.") 123 } 124 125 select { 126 case reply := <-c.replyChan: 127 return reply, nil 128 case <-c.pingChan: 129 return nil, nil 130 case <-c.conn.doneRead: 131 // c.conn.readResponses is no more, there will be no replys or pings 132 return nil, io.EOF 133 } 134 } 135 136 // Check is used for checked requests that have no replies. It is a mechanism 137 // by which to report "success" or "error" in a synchronous fashion. (Therefore, 138 // unchecked requests without replies cannot use this method.) 139 // If the request causes an error, it is sent to this cookie's errorChan. 140 // If the request was successful, there is no response from the server. 141 // Thus, pingChan is sent a value when the *next* reply is read. 142 // If no more replies are being processed, we force a round trip request with 143 // GetInputFocus. 144 // Returns io.EOF error when the connection is closed. 145 // 146 // Unless you're building requests from bytes by hand, this method should 147 // not be used. 148 func (c Cookie) Check() error { 149 if c.replyChan != nil { 150 return errors.New("Cannot call 'Check' on a cookie that is " + 151 "expecting a *reply*. Use 'Reply' instead.") 152 } 153 if c.errorChan == nil { 154 return errors.New("Cannot call 'Check' on a cookie that is " + 155 "not expecting a possible *error*.") 156 } 157 158 // First do a quick non-blocking check to see if we've been pinged. 159 select { 160 case err := <-c.errorChan: 161 return err 162 case <-c.pingChan: 163 return nil 164 default: 165 } 166 167 // Now force a round trip and try again, but block this time. 168 c.conn.Sync() 169 select { 170 case err := <-c.errorChan: 171 return err 172 case <-c.pingChan: 173 return nil 174 case <-c.conn.doneRead: 175 // c.conn.readResponses is no more, there will be no errors or pings 176 return io.EOF 177 } 178 }