# HG changeset patch # User Chris Jones # Date 1325290597 25200 # Node ID 571713f494940d47e48296daf56fd206a3d3eba3 # Parent 4e68d8f89dc3c0091a52c961bc829bf76aa49725 Added roster retrieval to StartSession(). diff -r 4e68d8f89dc3 -r 571713f49494 examples/interact.go --- a/examples/interact.go Thu Dec 29 11:25:26 2011 -0700 +++ b/examples/interact.go Fri Dec 30 17:16:37 2011 -0700 @@ -30,10 +30,15 @@ } defer c.Close() - err = c.StartSession(&xmpp.Presence{}) + err = c.StartSession(true, &xmpp.Presence{}) if err != nil { log.Fatalf("StartSession: %v", err) } + roster := c.Roster() + fmt.Printf("%d roster entries:\n", len(roster)) + for jid, entry := range(roster) { + fmt.Printf("%s: %v\n", jid, entry) + } go func(ch <-chan xmpp.Stanza) { for obj := range ch { diff -r 4e68d8f89dc3 -r 571713f49494 stream.go --- a/stream.go Thu Dec 29 11:25:26 2011 -0700 +++ b/stream.go Fri Dec 30 17:16:37 2011 -0700 @@ -551,7 +551,9 @@ // Register a callback to handle the next XMPP stanza (iq, message, or // presence) with a given id. The provided function will not be called // more than once. If it returns false, the stanza will not be made -// available on the normal Client.In channel. +// available on the normal Client.In channel. The stanza handler +// must not read from that channel, as deliveries on it cannot proceed +// until the handler returns true or false. func (cl *Client) HandleStanza(id string, f func(Stanza) bool) { h := &stanzaHandler{id: id, f: f} cl.handlers <- h diff -r 4e68d8f89dc3 -r 571713f49494 structs.go --- a/structs.go Thu Dec 29 11:25:26 2011 -0700 +++ b/structs.go Fri Dec 30 17:16:37 2011 -0700 @@ -138,10 +138,28 @@ Lang string `xml:"attr"` Error *Error Any *Generic + Query *RosterQuery } var _ xml.Marshaler = &Iq{} var _ Stanza = &Iq{} +// Roster query/result +type RosterQuery struct { + // Should always be query in the NsRoster namespace + XMLName xml.Name + Item []RosterItem +} + +// See RFC 3921, Section 7.1. +type RosterItem struct { + // Should always be "item" + XMLName xml.Name + Jid string `xml:"attr"` + Subscription string `xml:"attr"` + Name string `xml:"attr"` + Group []string +} + // Describes an XMPP stanza error. See RFC 3920, Section 9.3. type Error struct { // The error type attribute. @@ -264,6 +282,8 @@ u.XMLName.Local) } +// BUG(cjyar) This is fragile. We should find a way to use go's native +// XML marshaling. func marshalXML(st Stanza) ([]byte, os.Error) { buf := bytes.NewBuffer(nil) buf.WriteString("<") @@ -291,6 +311,9 @@ if st.XChild() != nil { xml.Marshal(buf, st.XChild()) } + if iq, ok := st.(*Iq) ; ok && iq.Query != nil { + xml.Marshal(buf, iq.Query) + } buf.WriteString("") diff -r 4e68d8f89dc3 -r 571713f49494 xmpp.go --- a/xmpp.go Thu Dec 29 11:25:26 2011 -0700 +++ b/xmpp.go Fri Dec 30 17:16:37 2011 -0700 @@ -24,6 +24,7 @@ // Version of RFC 3920 that we implement. Version = "1.0" + // BUG(cjyar) These should be public. // Various XML namespaces. nsStreams = "urn:ietf:params:xml:ns:xmpp-streams" nsStream = "http://etherx.jabber.org/streams" @@ -31,6 +32,7 @@ nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" nsBind = "urn:ietf:params:xml:ns:xmpp-bind" nsSession = "urn:ietf:params:xml:ns:xmpp-session" + NsRoster = "jabber:iq:roster" // DNS SRV names serverSrv = "xmpp-server" @@ -53,8 +55,6 @@ socketSync sync.WaitGroup saslExpected string authDone bool - idMutex sync.Mutex - nextId int64 handlers chan *stanzaHandler inputControl chan int // This channel may be used as a convenient way to generate a @@ -73,6 +73,7 @@ // connection process. It should not be updated once // StartSession() returns. Features *Features + roster map[string] *RosterItem } var _ io.Closer = &Client{} @@ -114,7 +115,7 @@ cl.password = password cl.Jid = *jid cl.socket = tcp - cl.handlers = make(chan *stanzaHandler, 1) + cl.handlers = make(chan *stanzaHandler, 100) cl.inputControl = make(chan int) idCh := make(chan string) cl.Id = idCh @@ -248,12 +249,12 @@ cl.inputControl <- 1 } -// Start an XMPP session. This should typically be done immediately -// after creating the new Client. Once the session has been -// established, pr will be sent as an initial presence; nil means -// don't send initial presence. The initial presence can be a -// newly-initialized Presence struct. See RFC 3921, Section 3. -func (cl *Client) StartSession(pr *Presence) os.Error { +// Start an XMPP session. A typical XMPP client should call this +// immediately after creating the Client in order to start the +// session, retrieve the roster, and broadcast an initial +// presence. The presence can be as simple as a newly-initialized +// Presence struct. See RFC 3921, Section 3. +func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error { id := <- cl.Id iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any: &Generic{XMLName: xml.Name{Space: nsSession, Local: @@ -265,14 +266,75 @@ ch <- st.XError() return false } - if pr != nil { - cl.Out <- pr - } ch <- nil return false } cl.HandleStanza(id, f) cl.Out <- iq + // Now wait until the callback is called. - return <-ch + if err := <- ch ; err != nil { + return err + } + if getRoster { + err := cl.fetchRoster() + if err != nil { + return err + } + } + if pr != nil { + cl.Out <- pr + } + return nil } + +// Synchronously fetch this entity's roster from the server and cache +// that information. +func (cl *Client) fetchRoster() os.Error { + iq := &Iq{From: cl.Jid.String(), Id: <- cl.Id, Type: "get", + Query: &RosterQuery{XMLName: xml.Name{Local: "query", + Space: NsRoster}}} + ch := make(chan os.Error) + f := func(st Stanza) bool { + iq, ok := st.(*Iq) + if !ok { + ch <- os.NewError(fmt.Sprintf( + "Roster query result not iq: %v", st)) + return false + } + if iq.Type == "error" { + ch <- iq.Error + return false + } + q := iq.Query + if q == nil { + ch <- os.NewError(fmt.Sprintf( + "Roster query result nil query: %v", + iq)) + return false + } + cl.roster = make(map[string] *RosterItem, len(q.Item)) + for _, item := range(q.Item) { + cl.roster[item.Jid] = &item + } + ch <- nil + return false + } + cl.HandleStanza(iq.Id, f) + cl.Out <- iq + // Wait for f to complete. + return <- ch +} + +// BUG(cjyar) The roster isn't actually updated when things change. + +// Returns the current roster of other entities which this one has a +// relationship with. Changes to the roster will be signaled by an +// appropriate Iq appearing on Client.In. See RFC 3921, Section 7.4. +func (cl *Client) Roster() map[string] *RosterItem { + r := make(map[string] *RosterItem) + for key, val := range(cl.roster) { + r[key] = val + } + return r +}