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