...

Source file src/github.com/jezek/xgb/cookie.go

Documentation: github.com/jezek/xgb

     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  }
   179  

View as plain text