zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

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 }