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 27 namespace 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. 32 struct 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. 40 template <typename C> 41 struct 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. 51 template <typename C> 52 class PubSubStateSerializer { 53 public: ~PubSubStateSerializer()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. 63 class PubSubStateKeySerializer { 64 public: ~PubSubStateKeySerializer()65 virtual ~PubSubStateKeySerializer() {} 66 virtual std::string GetKey(const std::string& publisher_nick, 67 const std::string& published_nick) = 0; 68 }; 69 70 class PublishedNickKeySerializer : public PubSubStateKeySerializer { 71 public: 72 virtual std::string GetKey(const std::string& publisher_nick, 73 const std::string& published_nick); 74 }; 75 76 class 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. 86 template <typename C> 87 class PubSubStateClient : public sigslot::has_slots<> { 88 public: 89 // Gets ownership of the serializers, but not the client. PubSubStateClient(const std::string & publisher_nick,PubSubClient * client,const QName & state_name,C default_state,PubSubStateKeySerializer * key_serializer,PubSubStateSerializer<C> * state_serializer)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 ~PubSubStateClient()114 virtual ~PubSubStateClient() {} 115 Publish(const std::string & published_nick,const C & state,std::string * task_id_out)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) ParseStateItem(const PubSubItem & item,StateItemInfo * info_out,C * state_out)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 StatesEqual(const C & state1,const C & state2)156 virtual bool StatesEqual(const C& state1, const C& state2) { 157 return state1 == state2; 158 } 159 client()160 PubSubClient* client() { return client_; } state_name()161 const QName& state_name() { return state_name_; } 162 163 private: OnItems(PubSubClient * pub_sub_client,const std::vector<PubSubItem> & items)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 OnItem(const PubSubItem & item)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 OnPublishResult(PubSubClient * pub_sub_client,const std::string & task_id,const XmlElement * item)223 void OnPublishResult(PubSubClient* pub_sub_client, 224 const std::string& task_id, 225 const XmlElement* item) { 226 SignalPublishResult(task_id, item); 227 } 228 OnPublishError(PubSubClient * pub_sub_client,const std::string & task_id,const buzz::XmlElement * item,const buzz::XmlElement * stanza)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 OnRetractResult(PubSubClient * pub_sub_client,const std::string & task_id)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 OnRetractError(PubSubClient * pub_sub_client,const std::string & task_id,const buzz::XmlElement * stanza)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