1 /* 2 * libjingle 3 * Copyright 2011, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #ifndef TALK_XMPP_PUBSUBSTATECLIENT_H_ 29 #define TALK_XMPP_PUBSUBSTATECLIENT_H_ 30 31 #include <map> 32 #include <string> 33 #include <vector> 34 35 #include "webrtc/libjingle/xmllite/qname.h" 36 #include "webrtc/libjingle/xmllite/xmlelement.h" 37 #include "talk/xmpp/constants.h" 38 #include "talk/xmpp/jid.h" 39 #include "talk/xmpp/pubsubclient.h" 40 #include "webrtc/base/scoped_ptr.h" 41 #include "webrtc/base/sigslot.h" 42 #include "webrtc/base/sigslotrepeater.h" 43 44 namespace buzz { 45 46 // To handle retracts correctly, we need to remember certain details 47 // about an item. We could just cache the entire XML element, but 48 // that would take more memory and require re-parsing. 49 struct StateItemInfo { 50 std::string published_nick; 51 std::string publisher_nick; 52 }; 53 54 // Represents a PubSub state change. Usually, the key is the nick, 55 // but not always. It's a per-state-type thing. Look below on how keys are 56 // computed. 57 template <typename C> 58 struct PubSubStateChange { 59 // The nick of the user changing the state. 60 std::string publisher_nick; 61 // The nick of the user whose state is changing. 62 std::string published_nick; 63 C old_state; 64 C new_state; 65 }; 66 67 // Knows how to handle specific states and XML. 68 template <typename C> 69 class PubSubStateSerializer { 70 public: 71 virtual ~PubSubStateSerializer() {} 72 virtual XmlElement* Write(const QName& state_name, const C& state) = 0; 73 virtual void Parse(const XmlElement* state_elem, C* state_out) = 0; 74 }; 75 76 // Knows how to create "keys" for states, which determines their 77 // uniqueness. Most states are per-nick, but block is 78 // per-blocker-and-blockee. This is independent of itemid, especially 79 // in the case of presenter state. 80 class PubSubStateKeySerializer { 81 public: 82 virtual ~PubSubStateKeySerializer() {} 83 virtual std::string GetKey(const std::string& publisher_nick, 84 const std::string& published_nick) = 0; 85 }; 86 87 class PublishedNickKeySerializer : public PubSubStateKeySerializer { 88 public: 89 virtual std::string GetKey(const std::string& publisher_nick, 90 const std::string& published_nick); 91 }; 92 93 class PublisherAndPublishedNicksKeySerializer 94 : public PubSubStateKeySerializer { 95 public: 96 virtual std::string GetKey(const std::string& publisher_nick, 97 const std::string& published_nick); 98 }; 99 100 // Adapts PubSubClient to be specifically suited for pub sub call 101 // states. Signals state changes and keeps track of keys, which are 102 // normally nicks. 103 template <typename C> 104 class PubSubStateClient : public sigslot::has_slots<> { 105 public: 106 // Gets ownership of the serializers, but not the client. 107 PubSubStateClient(const std::string& publisher_nick, 108 PubSubClient* client, 109 const QName& state_name, 110 C default_state, 111 PubSubStateKeySerializer* key_serializer, 112 PubSubStateSerializer<C>* state_serializer) 113 : publisher_nick_(publisher_nick), 114 client_(client), 115 state_name_(state_name), 116 default_state_(default_state) { 117 key_serializer_.reset(key_serializer); 118 state_serializer_.reset(state_serializer); 119 client_->SignalItems.connect( 120 this, &PubSubStateClient<C>::OnItems); 121 client_->SignalPublishResult.connect( 122 this, &PubSubStateClient<C>::OnPublishResult); 123 client_->SignalPublishError.connect( 124 this, &PubSubStateClient<C>::OnPublishError); 125 client_->SignalRetractResult.connect( 126 this, &PubSubStateClient<C>::OnRetractResult); 127 client_->SignalRetractError.connect( 128 this, &PubSubStateClient<C>::OnRetractError); 129 } 130 131 virtual ~PubSubStateClient() {} 132 133 virtual void Publish(const std::string& published_nick, 134 const C& state, 135 std::string* task_id_out) { 136 std::string key = key_serializer_->GetKey(publisher_nick_, published_nick); 137 std::string itemid = state_name_.LocalPart() + ":" + key; 138 if (StatesEqual(state, default_state_)) { 139 client_->RetractItem(itemid, task_id_out); 140 } else { 141 XmlElement* state_elem = state_serializer_->Write(state_name_, state); 142 state_elem->AddAttr(QN_NICK, published_nick); 143 client_->PublishItem(itemid, state_elem, task_id_out); 144 } 145 } 146 147 sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange; 148 // Signal (task_id, item). item is NULL for retract. 149 sigslot::signal2<const std::string&, 150 const XmlElement*> SignalPublishResult; 151 // Signal (task_id, item, error stanza). item is NULL for retract. 152 sigslot::signal3<const std::string&, 153 const XmlElement*, 154 const XmlElement*> SignalPublishError; 155 156 protected: 157 // return false if retracted item (no info or state given) 158 virtual bool ParseStateItem(const PubSubItem& item, 159 StateItemInfo* info_out, 160 C* state_out) { 161 const XmlElement* state_elem = item.elem->FirstNamed(state_name_); 162 if (state_elem == NULL) { 163 return false; 164 } 165 166 info_out->publisher_nick = 167 client_->GetPublisherNickFromPubSubItem(item.elem); 168 info_out->published_nick = state_elem->Attr(QN_NICK); 169 state_serializer_->Parse(state_elem, state_out); 170 return true; 171 } 172 173 virtual bool StatesEqual(const C& state1, const C& state2) { 174 return state1 == state2; 175 } 176 177 PubSubClient* client() { return client_; } 178 const QName& state_name() { return state_name_; } 179 180 private: 181 void OnItems(PubSubClient* pub_sub_client, 182 const std::vector<PubSubItem>& items) { 183 for (std::vector<PubSubItem>::const_iterator item = items.begin(); 184 item != items.end(); ++item) { 185 OnItem(*item); 186 } 187 } 188 189 void OnItem(const PubSubItem& item) { 190 const std::string& itemid = item.itemid; 191 StateItemInfo info; 192 C new_state; 193 194 bool retracted = !ParseStateItem(item, &info, &new_state); 195 if (retracted) { 196 bool known_itemid = 197 (info_by_itemid_.find(itemid) != info_by_itemid_.end()); 198 if (!known_itemid) { 199 // Nothing to retract, and nothing to publish. 200 // Probably a different state type. 201 return; 202 } else { 203 info = info_by_itemid_[itemid]; 204 info_by_itemid_.erase(itemid); 205 new_state = default_state_; 206 } 207 } else { 208 // TODO: Assert new key matches the known key. It 209 // shouldn't change! 210 info_by_itemid_[itemid] = info; 211 } 212 213 std::string key = key_serializer_->GetKey( 214 info.publisher_nick, info.published_nick); 215 bool has_old_state = (state_by_key_.find(key) != state_by_key_.end()); 216 C old_state = has_old_state ? state_by_key_[key] : default_state_; 217 if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) { 218 // Nothing change, so don't bother signalling. 219 return; 220 } 221 222 if (retracted || StatesEqual(new_state, default_state_)) { 223 // We treat a default state similar to a retract. 224 state_by_key_.erase(key); 225 } else { 226 state_by_key_[key] = new_state; 227 } 228 229 PubSubStateChange<C> change; 230 if (!retracted) { 231 // Retracts do not have publisher information. 232 change.publisher_nick = info.publisher_nick; 233 } 234 change.published_nick = info.published_nick; 235 change.old_state = old_state; 236 change.new_state = new_state; 237 SignalStateChange(change); 238 } 239 240 void OnPublishResult(PubSubClient* pub_sub_client, 241 const std::string& task_id, 242 const XmlElement* item) { 243 SignalPublishResult(task_id, item); 244 } 245 246 void OnPublishError(PubSubClient* pub_sub_client, 247 const std::string& task_id, 248 const buzz::XmlElement* item, 249 const buzz::XmlElement* stanza) { 250 SignalPublishError(task_id, item, stanza); 251 } 252 253 void OnRetractResult(PubSubClient* pub_sub_client, 254 const std::string& task_id) { 255 // There's no point in differentiating between publish and retract 256 // errors, so we simplify by making them both signal a publish 257 // result. 258 const XmlElement* item = NULL; 259 SignalPublishResult(task_id, item); 260 } 261 262 void OnRetractError(PubSubClient* pub_sub_client, 263 const std::string& task_id, 264 const buzz::XmlElement* stanza) { 265 // There's no point in differentiating between publish and retract 266 // errors, so we simplify by making them both signal a publish 267 // error. 268 const XmlElement* item = NULL; 269 SignalPublishError(task_id, item, stanza); 270 } 271 272 std::string publisher_nick_; 273 PubSubClient* client_; 274 const QName state_name_; 275 C default_state_; 276 rtc::scoped_ptr<PubSubStateKeySerializer> key_serializer_; 277 rtc::scoped_ptr<PubSubStateSerializer<C> > state_serializer_; 278 // key => state 279 std::map<std::string, C> state_by_key_; 280 // itemid => StateItemInfo 281 std::map<std::string, StateItemInfo> info_by_itemid_; 282 283 DISALLOW_COPY_AND_ASSIGN(PubSubStateClient); 284 }; 285 } // namespace buzz 286 287 #endif // TALK_XMPP_PUBSUBSTATECLIENT_H_ 288