81 // with the given password. This function will return as soon as a TCP |
81 // with the given password. This function will return as soon as a TCP |
82 // connection has been established, but before XMPP stream negotiation |
82 // connection has been established, but before XMPP stream negotiation |
83 // has completed. The negotiation will occur asynchronously, and any |
83 // has completed. The negotiation will occur asynchronously, and any |
84 // send operation to Client.Out will block until negotiation (resource |
84 // send operation to Client.Out will block until negotiation (resource |
85 // binding) is complete. |
85 // binding) is complete. |
86 func NewClient(jid *JID, password string) (*Client, os.Error) { |
86 func NewClient(jid *JID, password string, |
|
87 extStanza map[string] func(*xml.Name) ExtendedStanza) (*Client, os.Error) { |
87 // Resolve the domain in the JID. |
88 // Resolve the domain in the JID. |
88 _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain) |
89 _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain) |
89 if err != nil { |
90 if err != nil { |
90 return nil, os.NewError("LookupSrv " + jid.Domain + |
91 return nil, os.NewError("LookupSrv " + jid.Domain + |
91 ": " + err.String()) |
92 ": " + err.String()) |
118 cl.handlers = make(chan *stanzaHandler, 100) |
119 cl.handlers = make(chan *stanzaHandler, 100) |
119 cl.inputControl = make(chan int) |
120 cl.inputControl = make(chan int) |
120 idCh := make(chan string) |
121 idCh := make(chan string) |
121 cl.Id = idCh |
122 cl.Id = idCh |
122 |
123 |
|
124 if extStanza == nil { |
|
125 extStanza = make(map[string] func(*xml.Name) ExtendedStanza) |
|
126 } |
|
127 extStanza[NsRoster] = rosterStanza |
|
128 |
123 // Start the unique id generator. |
129 // Start the unique id generator. |
124 go makeIds(idCh) |
130 go makeIds(idCh) |
125 |
131 |
126 // Start the transport handler, initially unencrypted. |
132 // Start the transport handler, initially unencrypted. |
127 tlsr, tlsw := cl.startTransport() |
133 tlsr, tlsw := cl.startTransport() |
128 |
134 |
129 // Start the reader and writers that convert to and from XML. |
135 // Start the reader and writers that convert to and from XML. |
130 xmlIn := startXmlReader(tlsr) |
136 xmlIn := startXmlReader(tlsr, extStanza) |
131 cl.xmlOut = startXmlWriter(tlsw) |
137 cl.xmlOut = startXmlWriter(tlsw) |
132 |
138 |
133 // Start the XMPP stream handler which filters stream-level |
139 // Start the XMPP stream handler which filters stream-level |
134 // events and responds to them. |
140 // events and responds to them. |
135 clIn := cl.startStreamReader(xmlIn, cl.xmlOut) |
141 clIn := cl.startStreamReader(xmlIn, cl.xmlOut) |
156 go cl.readTransport(inw) |
162 go cl.readTransport(inw) |
157 go cl.writeTransport(outr) |
163 go cl.writeTransport(outr) |
158 return inr, outw |
164 return inr, outw |
159 } |
165 } |
160 |
166 |
161 func startXmlReader(r io.Reader) <-chan interface{} { |
167 func startXmlReader(r io.Reader, |
|
168 extStanza map[string] func(*xml.Name) ExtendedStanza) <-chan interface{} { |
162 ch := make(chan interface{}) |
169 ch := make(chan interface{}) |
163 go readXml(r, ch) |
170 go readXml(r, ch, extStanza) |
164 return ch |
171 return ch |
165 } |
172 } |
166 |
173 |
167 func startXmlWriter(w io.Writer) chan<- interface{} { |
174 func startXmlWriter(w io.Writer) chan<- interface{} { |
168 ch := make(chan interface{}) |
175 ch := make(chan interface{}) |
285 if pr != nil { |
292 if pr != nil { |
286 cl.Out <- pr |
293 cl.Out <- pr |
287 } |
294 } |
288 return nil |
295 return nil |
289 } |
296 } |
290 |
|
291 // Synchronously fetch this entity's roster from the server and cache |
|
292 // that information. |
|
293 func (cl *Client) fetchRoster() os.Error { |
|
294 iq := &Iq{From: cl.Jid.String(), Id: <- cl.Id, Type: "get", |
|
295 Query: &RosterQuery{XMLName: xml.Name{Local: "query", |
|
296 Space: NsRoster}}} |
|
297 ch := make(chan os.Error) |
|
298 f := func(st Stanza) bool { |
|
299 iq, ok := st.(*Iq) |
|
300 if !ok { |
|
301 ch <- os.NewError(fmt.Sprintf( |
|
302 "Roster query result not iq: %v", st)) |
|
303 return false |
|
304 } |
|
305 if iq.Type == "error" { |
|
306 ch <- iq.Error |
|
307 return false |
|
308 } |
|
309 q := iq.Query |
|
310 if q == nil { |
|
311 ch <- os.NewError(fmt.Sprintf( |
|
312 "Roster query result nil query: %v", |
|
313 iq)) |
|
314 return false |
|
315 } |
|
316 cl.roster = make(map[string] *RosterItem, len(q.Item)) |
|
317 for _, item := range(q.Item) { |
|
318 cl.roster[item.Jid] = &item |
|
319 } |
|
320 ch <- nil |
|
321 return false |
|
322 } |
|
323 cl.HandleStanza(iq.Id, f) |
|
324 cl.Out <- iq |
|
325 // Wait for f to complete. |
|
326 return <- ch |
|
327 } |
|
328 |
|
329 // BUG(cjyar) The roster isn't actually updated when things change. |
|
330 |
|
331 // Returns the current roster of other entities which this one has a |
|
332 // relationship with. Changes to the roster will be signaled by an |
|
333 // appropriate Iq appearing on Client.In. See RFC 3921, Section 7.4. |
|
334 func (cl *Client) Roster() map[string] *RosterItem { |
|
335 r := make(map[string] *RosterItem) |
|
336 for key, val := range(cl.roster) { |
|
337 r[key] = val |
|
338 } |
|
339 return r |
|
340 } |
|