1/*
2 *  Copyright 2011 The WebRTC Project Authors. All rights reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_
12#define WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_
13
14#include <map>
15#include <string>
16#include <vector>
17
18#include "webrtc/libjingle/xmllite/qname.h"
19#include "webrtc/libjingle/xmllite/xmlelement.h"
20#include "webrtc/libjingle/xmpp/constants.h"
21#include "webrtc/libjingle/xmpp/jid.h"
22#include "webrtc/libjingle/xmpp/pubsubclient.h"
23#include "webrtc/base/scoped_ptr.h"
24#include "webrtc/base/sigslot.h"
25#include "webrtc/base/sigslotrepeater.h"
26
27namespace buzz {
28
29// To handle retracts correctly, we need to remember certain details
30// about an item.  We could just cache the entire XML element, but
31// that would take more memory and require re-parsing.
32struct StateItemInfo {
33  std::string published_nick;
34  std::string publisher_nick;
35};
36
37// Represents a PubSub state change.  Usually, the key is the nick,
38// but not always.  It's a per-state-type thing.  Look below on how keys are
39// computed.
40template <typename C>
41struct PubSubStateChange {
42  // The nick of the user changing the state.
43  std::string publisher_nick;
44  // The nick of the user whose state is changing.
45  std::string published_nick;
46  C old_state;
47  C new_state;
48};
49
50// Knows how to handle specific states and XML.
51template <typename C>
52class PubSubStateSerializer {
53 public:
54  virtual ~PubSubStateSerializer() {}
55  virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
56  virtual void Parse(const XmlElement* state_elem, C* state_out) = 0;
57};
58
59// Knows how to create "keys" for states, which determines their
60// uniqueness.  Most states are per-nick, but block is
61// per-blocker-and-blockee.  This is independent of itemid, especially
62// in the case of presenter state.
63class PubSubStateKeySerializer {
64 public:
65  virtual ~PubSubStateKeySerializer() {}
66  virtual std::string GetKey(const std::string& publisher_nick,
67                             const std::string& published_nick) = 0;
68};
69
70class PublishedNickKeySerializer : public PubSubStateKeySerializer {
71 public:
72  virtual std::string GetKey(const std::string& publisher_nick,
73                             const std::string& published_nick);
74};
75
76class PublisherAndPublishedNicksKeySerializer
77  : public PubSubStateKeySerializer {
78 public:
79  virtual std::string GetKey(const std::string& publisher_nick,
80                             const std::string& published_nick);
81};
82
83// Adapts PubSubClient to be specifically suited for pub sub call
84// states.  Signals state changes and keeps track of keys, which are
85// normally nicks.
86template <typename C>
87class PubSubStateClient : public sigslot::has_slots<> {
88 public:
89  // Gets ownership of the serializers, but not the client.
90  PubSubStateClient(const std::string& publisher_nick,
91                    PubSubClient* client,
92                    const QName& state_name,
93                    C default_state,
94                    PubSubStateKeySerializer* key_serializer,
95                    PubSubStateSerializer<C>* state_serializer)
96      : publisher_nick_(publisher_nick),
97        client_(client),
98        state_name_(state_name),
99        default_state_(default_state) {
100    key_serializer_.reset(key_serializer);
101    state_serializer_.reset(state_serializer);
102    client_->SignalItems.connect(
103        this, &PubSubStateClient<C>::OnItems);
104    client_->SignalPublishResult.connect(
105        this, &PubSubStateClient<C>::OnPublishResult);
106    client_->SignalPublishError.connect(
107        this, &PubSubStateClient<C>::OnPublishError);
108    client_->SignalRetractResult.connect(
109        this, &PubSubStateClient<C>::OnRetractResult);
110    client_->SignalRetractError.connect(
111        this, &PubSubStateClient<C>::OnRetractError);
112  }
113
114  virtual ~PubSubStateClient() {}
115
116  virtual void Publish(const std::string& published_nick,
117                       const C& state,
118                       std::string* task_id_out) {
119    std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
120    std::string itemid = state_name_.LocalPart() + ":" + key;
121    if (StatesEqual(state, default_state_)) {
122      client_->RetractItem(itemid, task_id_out);
123    } else {
124      XmlElement* state_elem = state_serializer_->Write(state_name_, state);
125      state_elem->AddAttr(QN_NICK, published_nick);
126      client_->PublishItem(itemid, state_elem, task_id_out);
127    }
128  }
129
130  sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
131  // Signal (task_id, item).  item is NULL for retract.
132  sigslot::signal2<const std::string&,
133  const XmlElement*> SignalPublishResult;
134  // Signal (task_id, item, error stanza).  item is NULL for retract.
135  sigslot::signal3<const std::string&,
136  const XmlElement*,
137  const XmlElement*> SignalPublishError;
138
139 protected:
140  // return false if retracted item (no info or state given)
141  virtual bool ParseStateItem(const PubSubItem& item,
142                              StateItemInfo* info_out,
143                              C* state_out) {
144    const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
145    if (state_elem == NULL) {
146      return false;
147    }
148
149    info_out->publisher_nick =
150        client_->GetPublisherNickFromPubSubItem(item.elem);
151    info_out->published_nick = state_elem->Attr(QN_NICK);
152    state_serializer_->Parse(state_elem, state_out);
153    return true;
154  }
155
156  virtual bool StatesEqual(const C& state1, const C& state2) {
157    return state1 == state2;
158  }
159
160  PubSubClient* client() { return client_; }
161  const QName& state_name() { return state_name_; }
162
163 private:
164  void OnItems(PubSubClient* pub_sub_client,
165               const std::vector<PubSubItem>& items) {
166    for (std::vector<PubSubItem>::const_iterator item = items.begin();
167         item != items.end(); ++item) {
168      OnItem(*item);
169    }
170  }
171
172  void OnItem(const PubSubItem& item) {
173    const std::string& itemid = item.itemid;
174    StateItemInfo info;
175    C new_state;
176
177    bool retracted = !ParseStateItem(item, &info, &new_state);
178    if (retracted) {
179      bool known_itemid =
180          (info_by_itemid_.find(itemid) != info_by_itemid_.end());
181      if (!known_itemid) {
182        // Nothing to retract, and nothing to publish.
183        // Probably a different state type.
184        return;
185      } else {
186        info = info_by_itemid_[itemid];
187        info_by_itemid_.erase(itemid);
188        new_state = default_state_;
189      }
190    } else {
191      // TODO: Assert new key matches the known key. It
192      // shouldn't change!
193      info_by_itemid_[itemid] = info;
194    }
195
196    std::string key = key_serializer_->GetKey(
197        info.publisher_nick, info.published_nick);
198    bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
199    C old_state = has_old_state ? state_by_key_[key] : default_state_;
200    if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
201      // Nothing change, so don't bother signalling.
202      return;
203    }
204
205    if (retracted || StatesEqual(new_state, default_state_)) {
206      // We treat a default state similar to a retract.
207      state_by_key_.erase(key);
208    } else {
209      state_by_key_[key] = new_state;
210    }
211
212    PubSubStateChange<C> change;
213    if (!retracted) {
214      // Retracts do not have publisher information.
215      change.publisher_nick = info.publisher_nick;
216    }
217    change.published_nick = info.published_nick;
218    change.old_state = old_state;
219    change.new_state = new_state;
220    SignalStateChange(change);
221  }
222
223  void OnPublishResult(PubSubClient* pub_sub_client,
224                       const std::string& task_id,
225                       const XmlElement* item) {
226    SignalPublishResult(task_id, item);
227  }
228
229  void OnPublishError(PubSubClient* pub_sub_client,
230                      const std::string& task_id,
231                      const buzz::XmlElement* item,
232                      const buzz::XmlElement* stanza) {
233    SignalPublishError(task_id, item, stanza);
234  }
235
236  void OnRetractResult(PubSubClient* pub_sub_client,
237                       const std::string& task_id) {
238    // There's no point in differentiating between publish and retract
239    // errors, so we simplify by making them both signal a publish
240    // result.
241    const XmlElement* item = NULL;
242    SignalPublishResult(task_id, item);
243  }
244
245  void OnRetractError(PubSubClient* pub_sub_client,
246                      const std::string& task_id,
247                      const buzz::XmlElement* stanza) {
248    // There's no point in differentiating between publish and retract
249    // errors, so we simplify by making them both signal a publish
250    // error.
251    const XmlElement* item = NULL;
252    SignalPublishError(task_id, item, stanza);
253  }
254
255  std::string publisher_nick_;
256  PubSubClient* client_;
257  const QName state_name_;
258  C default_state_;
259  rtc::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
260  rtc::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
261  // key => state
262  std::map<std::string, C> state_by_key_;
263  // itemid => StateItemInfo
264  std::map<std::string, StateItemInfo> info_by_itemid_;
265
266  RTC_DISALLOW_COPY_AND_ASSIGN(PubSubStateClient);
267};
268}  // namespace buzz
269
270#endif  // WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_
271