Added roster retrieval to StartSession().
--- 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 {
--- 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
--- 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("</")
buf.WriteString(st.XName())
buf.WriteString(">")
--- 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
+}