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  }