go-tg-screenshot-bot
178 строк · 5.8 Кб
1package xgb2
3import (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'.
12type Cookie struct {13conn *Conn14Sequence uint1615replyChan chan []byte16errorChan chan error17pingChan chan bool18}
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.
29func (c *Conn) NewCookie(checked, reply bool) *Cookie {30cookie := &Cookie{31conn: c,32Sequence: 0, // we add the sequence id just before sending a request33replyChan: nil,34errorChan: nil,35pingChan: 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' channels46// is missing but the other is present. The ping channel is way to force47// the blocking to stop and basically say "the error has been received48// in the main event loop" (when the ping channel is coupled with a reply49// channel) or "the request you made that has no reply was successful"50// (when the ping channel is coupled with an error channel).51if checked {52cookie.errorChan = make(chan error, 1)53if !reply {54cookie.pingChan = make(chan bool, 1)55}56}57if reply {58cookie.replyChan = make(chan []byte, 1)59if !checked {60cookie.pingChan = make(chan bool, 1)61}62}63
64return cookie65}
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.
72func (c Cookie) Reply() ([]byte, error) {73// checked74if c.errorChan != nil {75return c.replyChecked()76}77return 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.
88func (c Cookie) replyChecked() ([]byte, error) {89if c.replyChan == nil {90return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +91"is not expecting a *reply* or an error.")92}93if c.errorChan == nil {94return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +95"is not expecting a reply or an *error*.")96}97
98select {99case reply := <-c.replyChan:100return reply, nil101case err := <-c.errorChan:102return nil, err103case <-c.conn.doneRead:104// c.conn.readResponses is no more, there will be no replys or errors105return nil, io.EOF106}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.
119func (c Cookie) replyUnchecked() ([]byte, error) {120if c.replyChan == nil {121return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " +122"that is not expecting a *reply*.")123}124
125select {126case reply := <-c.replyChan:127return reply, nil128case <-c.pingChan:129return nil, nil130case <-c.conn.doneRead:131// c.conn.readResponses is no more, there will be no replys or pings132return nil, io.EOF133}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.
148func (c Cookie) Check() error {149if c.replyChan != nil {150return errors.New("Cannot call 'Check' on a cookie that is " +151"expecting a *reply*. Use 'Reply' instead.")152}153if c.errorChan == nil {154return 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.159select {160case err := <-c.errorChan:161return err162case <-c.pingChan:163return nil164default:165}166
167// Now force a round trip and try again, but block this time.168c.conn.Sync()169select {170case err := <-c.errorChan:171return err172case <-c.pingChan:173return nil174case <-c.conn.doneRead:175// c.conn.readResponses is no more, there will be no errors or pings176return io.EOF177}178}
179