--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/roster.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,155 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xmpp
+
+// This file contains support for roster management, RFC 3921, Section 7.
+
+import (
+ "encoding/xml"
+)
+
+// Roster query/result
+type RosterQuery struct {
+ XMLName xml.Name `xml:"jabber:iq:roster query"`
+ Item []RosterItem `xml:"item"`
+}
+
+// See RFC 3921, Section 7.1.
+type RosterItem struct {
+ XMLName xml.Name `xml:"jabber:iq:roster item"`
+ Jid string `xml:"jid,attr"`
+ Subscription string `xml:"subscription,attr"`
+ Name string `xml:"name,attr"`
+ Group []string
+}
+
+type rosterCb struct {
+ id string
+ cb func()
+}
+
+type Roster struct {
+ Extension
+ get chan []RosterItem
+ callbacks chan rosterCb
+ toServer chan Stanza
+}
+
+type rosterClient struct {
+ rosterChan <-chan []RosterItem
+ rosterUpdate chan<- RosterItem
+}
+
+// Implicitly becomes part of NewClient's extStanza arg.
+func newRosterQuery(name *xml.Name) interface{} {
+ return &RosterQuery{}
+}
+
+func (r *Roster) rosterMgr(upd <-chan Stanza) {
+ roster := make(map[string]RosterItem)
+ waits := make(map[string]func())
+ var snapshot []RosterItem
+ for {
+ select {
+ case stan, ok := <- upd:
+ if !ok {
+ return
+ }
+ hdr := stan.GetHeader()
+ if f := waits[hdr.Id] ; f != nil {
+ delete(waits, hdr.Id)
+ f()
+ }
+ iq, ok := stan.(*Iq)
+ if iq.Type != "set" {
+ continue
+ }
+ var rq *RosterQuery
+ for _, ele := range iq.Nested {
+ if q, ok := ele.(*RosterQuery); ok {
+ rq = q
+ break
+ }
+ }
+ if rq == nil {
+ continue
+ }
+ for _, item := range rq.Item {
+ roster[item.Jid] = item
+ }
+ snapshot = []RosterItem{}
+ for _, ri := range roster {
+ snapshot = append(snapshot, ri)
+ }
+ case r.get <- snapshot:
+ case cb := <- r.callbacks:
+ waits[cb.id] = cb.cb
+ }
+ }
+}
+
+func (r *Roster) makeFilters() (Filter, Filter) {
+ rosterUpdate := make(chan Stanza)
+ go r.rosterMgr(rosterUpdate)
+ recv := func(in <-chan Stanza, out chan<- Stanza) {
+ defer close(out)
+ for stan := range in {
+ rosterUpdate <- stan
+ out <- stan
+ }
+ }
+ send := func(in <-chan Stanza, out chan<- Stanza) {
+ defer close(out)
+ for {
+ select {
+ case stan, ok := <- in:
+ if !ok {
+ return
+ }
+ out <- stan
+ case stan := <- r.toServer:
+ out <- stan
+ }
+ }
+ }
+ return recv, send
+}
+
+func newRosterExt() *Roster {
+ r := Roster{}
+ r.StanzaHandlers = make(map[string]func(*xml.Name) interface{})
+ r.StanzaHandlers[NsRoster] = newRosterQuery
+ r.RecvFilter, r.SendFilter = r.makeFilters()
+ r.get = make(chan []RosterItem)
+ r.callbacks = make(chan rosterCb)
+ r.toServer = make(chan Stanza)
+ return &r
+}
+
+// Return the most recent snapshot of the roster status. This is
+// updated automatically as roster updates are received from the
+// server, but especially in response to calls to Update().
+func (r *Roster) Get() []RosterItem {
+ return <-r.get
+}
+
+// Synchronously fetch this entity's roster from the server and cache
+// that information. The client can access the roster by watching for
+// RosterQuery objects or by calling Get().
+func (r *Roster) Update() {
+ iq := &Iq{Header: Header{Type: "get", Id: NextId(),
+ Nested: []interface{}{RosterQuery{}}}}
+ waitchan := make(chan int)
+ done := func() {
+ close(waitchan)
+ }
+ r.waitFor(iq.Id, done)
+ r.toServer <- iq
+ <-waitchan
+}
+
+func (r *Roster) waitFor(id string, cb func()) {
+ r.callbacks <- rosterCb{id: id, cb: cb}
+}