|
1 // Copyright 2011 The Go Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style |
|
3 // license that can be found in the LICENSE file. |
|
4 |
|
5 package xmpp |
|
6 |
|
7 // This file contains support for roster management, RFC 3921, Section 7. |
|
8 |
|
9 import ( |
|
10 "encoding/xml" |
|
11 ) |
|
12 |
|
13 // Roster query/result |
|
14 type RosterQuery struct { |
|
15 XMLName xml.Name `xml:"jabber:iq:roster query"` |
|
16 Item []RosterItem `xml:"item"` |
|
17 } |
|
18 |
|
19 // See RFC 3921, Section 7.1. |
|
20 type RosterItem struct { |
|
21 XMLName xml.Name `xml:"jabber:iq:roster item"` |
|
22 Jid string `xml:"jid,attr"` |
|
23 Subscription string `xml:"subscription,attr"` |
|
24 Name string `xml:"name,attr"` |
|
25 Group []string |
|
26 } |
|
27 |
|
28 type rosterCb struct { |
|
29 id string |
|
30 cb func() |
|
31 } |
|
32 |
|
33 type Roster struct { |
|
34 Extension |
|
35 get chan []RosterItem |
|
36 callbacks chan rosterCb |
|
37 toServer chan Stanza |
|
38 } |
|
39 |
|
40 type rosterClient struct { |
|
41 rosterChan <-chan []RosterItem |
|
42 rosterUpdate chan<- RosterItem |
|
43 } |
|
44 |
|
45 // Implicitly becomes part of NewClient's extStanza arg. |
|
46 func newRosterQuery(name *xml.Name) interface{} { |
|
47 return &RosterQuery{} |
|
48 } |
|
49 |
|
50 func (r *Roster) rosterMgr(upd <-chan Stanza) { |
|
51 roster := make(map[string]RosterItem) |
|
52 waits := make(map[string]func()) |
|
53 var snapshot []RosterItem |
|
54 for { |
|
55 select { |
|
56 case stan, ok := <- upd: |
|
57 if !ok { |
|
58 return |
|
59 } |
|
60 hdr := stan.GetHeader() |
|
61 if f := waits[hdr.Id] ; f != nil { |
|
62 delete(waits, hdr.Id) |
|
63 f() |
|
64 } |
|
65 iq, ok := stan.(*Iq) |
|
66 if iq.Type != "set" { |
|
67 continue |
|
68 } |
|
69 var rq *RosterQuery |
|
70 for _, ele := range iq.Nested { |
|
71 if q, ok := ele.(*RosterQuery); ok { |
|
72 rq = q |
|
73 break |
|
74 } |
|
75 } |
|
76 if rq == nil { |
|
77 continue |
|
78 } |
|
79 for _, item := range rq.Item { |
|
80 roster[item.Jid] = item |
|
81 } |
|
82 snapshot = []RosterItem{} |
|
83 for _, ri := range roster { |
|
84 snapshot = append(snapshot, ri) |
|
85 } |
|
86 case r.get <- snapshot: |
|
87 case cb := <- r.callbacks: |
|
88 waits[cb.id] = cb.cb |
|
89 } |
|
90 } |
|
91 } |
|
92 |
|
93 func (r *Roster) makeFilters() (Filter, Filter) { |
|
94 rosterUpdate := make(chan Stanza) |
|
95 go r.rosterMgr(rosterUpdate) |
|
96 recv := func(in <-chan Stanza, out chan<- Stanza) { |
|
97 defer close(out) |
|
98 for stan := range in { |
|
99 rosterUpdate <- stan |
|
100 out <- stan |
|
101 } |
|
102 } |
|
103 send := func(in <-chan Stanza, out chan<- Stanza) { |
|
104 defer close(out) |
|
105 for { |
|
106 select { |
|
107 case stan, ok := <- in: |
|
108 if !ok { |
|
109 return |
|
110 } |
|
111 out <- stan |
|
112 case stan := <- r.toServer: |
|
113 out <- stan |
|
114 } |
|
115 } |
|
116 } |
|
117 return recv, send |
|
118 } |
|
119 |
|
120 func newRosterExt() *Roster { |
|
121 r := Roster{} |
|
122 r.StanzaHandlers = make(map[string]func(*xml.Name) interface{}) |
|
123 r.StanzaHandlers[NsRoster] = newRosterQuery |
|
124 r.RecvFilter, r.SendFilter = r.makeFilters() |
|
125 r.get = make(chan []RosterItem) |
|
126 r.callbacks = make(chan rosterCb) |
|
127 r.toServer = make(chan Stanza) |
|
128 return &r |
|
129 } |
|
130 |
|
131 // Return the most recent snapshot of the roster status. This is |
|
132 // updated automatically as roster updates are received from the |
|
133 // server, but especially in response to calls to Update(). |
|
134 func (r *Roster) Get() []RosterItem { |
|
135 return <-r.get |
|
136 } |
|
137 |
|
138 // Synchronously fetch this entity's roster from the server and cache |
|
139 // that information. The client can access the roster by watching for |
|
140 // RosterQuery objects or by calling Get(). |
|
141 func (r *Roster) Update() { |
|
142 iq := &Iq{Header: Header{Type: "get", Id: NextId(), |
|
143 Nested: []interface{}{RosterQuery{}}}} |
|
144 waitchan := make(chan int) |
|
145 done := func() { |
|
146 close(waitchan) |
|
147 } |
|
148 r.waitFor(iq.Id, done) |
|
149 r.toServer <- iq |
|
150 <-waitchan |
|
151 } |
|
152 |
|
153 func (r *Roster) waitFor(id string, cb func()) { |
|
154 r.callbacks <- rosterCb{id: id, cb: cb} |
|
155 } |