 fa4535b270
			
		
	
	fa4535b270
	
	
	
		
			
			git-svn-id: http://webrtc.googlecode.com/svn/trunk@7045 4adac7df-926f-26a2-2b94-8c16560cd09d
		
			
				
	
	
		
			288 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * libjingle
 | |
|  * Copyright 2011, Google Inc.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions are met:
 | |
|  *
 | |
|  *  1. Redistributions of source code must retain the above copyright notice,
 | |
|  *     this list of conditions and the following disclaimer.
 | |
|  *  2. Redistributions in binary form must reproduce the above copyright notice,
 | |
|  *     this list of conditions and the following disclaimer in the documentation
 | |
|  *     and/or other materials provided with the distribution.
 | |
|  *  3. The name of the author may not be used to endorse or promote products
 | |
|  *     derived from this software without specific prior written permission.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 | |
|  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | |
|  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 | |
|  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | |
|  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | |
|  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 | |
|  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 | |
|  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 | |
|  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 | |
|  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
|  */
 | |
| 
 | |
| #ifndef TALK_XMPP_PUBSUBSTATECLIENT_H_
 | |
| #define TALK_XMPP_PUBSUBSTATECLIENT_H_
 | |
| 
 | |
| #include <map>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| 
 | |
| #include "webrtc/libjingle/xmllite/qname.h"
 | |
| #include "webrtc/libjingle/xmllite/xmlelement.h"
 | |
| #include "talk/xmpp/constants.h"
 | |
| #include "talk/xmpp/jid.h"
 | |
| #include "talk/xmpp/pubsubclient.h"
 | |
| #include "webrtc/base/scoped_ptr.h"
 | |
| #include "webrtc/base/sigslot.h"
 | |
| #include "webrtc/base/sigslotrepeater.h"
 | |
| 
 | |
| namespace buzz {
 | |
| 
 | |
| // To handle retracts correctly, we need to remember certain details
 | |
| // about an item.  We could just cache the entire XML element, but
 | |
| // that would take more memory and require re-parsing.
 | |
| struct StateItemInfo {
 | |
|   std::string published_nick;
 | |
|   std::string publisher_nick;
 | |
| };
 | |
| 
 | |
| // Represents a PubSub state change.  Usually, the key is the nick,
 | |
| // but not always.  It's a per-state-type thing.  Look below on how keys are
 | |
| // computed.
 | |
| template <typename C>
 | |
| struct PubSubStateChange {
 | |
|   // The nick of the user changing the state.
 | |
|   std::string publisher_nick;
 | |
|   // The nick of the user whose state is changing.
 | |
|   std::string published_nick;
 | |
|   C old_state;
 | |
|   C new_state;
 | |
| };
 | |
| 
 | |
| // Knows how to handle specific states and XML.
 | |
| template <typename C>
 | |
| class PubSubStateSerializer {
 | |
|  public:
 | |
|   virtual ~PubSubStateSerializer() {}
 | |
|   virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
 | |
|   virtual void Parse(const XmlElement* state_elem, C* state_out) = 0;
 | |
| };
 | |
| 
 | |
| // Knows how to create "keys" for states, which determines their
 | |
| // uniqueness.  Most states are per-nick, but block is
 | |
| // per-blocker-and-blockee.  This is independent of itemid, especially
 | |
| // in the case of presenter state.
 | |
| class PubSubStateKeySerializer {
 | |
|  public:
 | |
|   virtual ~PubSubStateKeySerializer() {}
 | |
|   virtual std::string GetKey(const std::string& publisher_nick,
 | |
|                              const std::string& published_nick) = 0;
 | |
| };
 | |
| 
 | |
| class PublishedNickKeySerializer : public PubSubStateKeySerializer {
 | |
|  public:
 | |
|   virtual std::string GetKey(const std::string& publisher_nick,
 | |
|                              const std::string& published_nick);
 | |
| };
 | |
| 
 | |
| class PublisherAndPublishedNicksKeySerializer
 | |
|   : public PubSubStateKeySerializer {
 | |
|  public:
 | |
|   virtual std::string GetKey(const std::string& publisher_nick,
 | |
|                              const std::string& published_nick);
 | |
| };
 | |
| 
 | |
| // Adapts PubSubClient to be specifically suited for pub sub call
 | |
| // states.  Signals state changes and keeps track of keys, which are
 | |
| // normally nicks.
 | |
| template <typename C>
 | |
| class PubSubStateClient : public sigslot::has_slots<> {
 | |
|  public:
 | |
|   // 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)
 | |
|       : publisher_nick_(publisher_nick),
 | |
|         client_(client),
 | |
|         state_name_(state_name),
 | |
|         default_state_(default_state) {
 | |
|     key_serializer_.reset(key_serializer);
 | |
|     state_serializer_.reset(state_serializer);
 | |
|     client_->SignalItems.connect(
 | |
|         this, &PubSubStateClient<C>::OnItems);
 | |
|     client_->SignalPublishResult.connect(
 | |
|         this, &PubSubStateClient<C>::OnPublishResult);
 | |
|     client_->SignalPublishError.connect(
 | |
|         this, &PubSubStateClient<C>::OnPublishError);
 | |
|     client_->SignalRetractResult.connect(
 | |
|         this, &PubSubStateClient<C>::OnRetractResult);
 | |
|     client_->SignalRetractError.connect(
 | |
|         this, &PubSubStateClient<C>::OnRetractError);
 | |
|   }
 | |
| 
 | |
|   virtual ~PubSubStateClient() {}
 | |
| 
 | |
|   virtual void Publish(const std::string& published_nick,
 | |
|                        const C& state,
 | |
|                        std::string* task_id_out) {
 | |
|     std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
 | |
|     std::string itemid = state_name_.LocalPart() + ":" + key;
 | |
|     if (StatesEqual(state, default_state_)) {
 | |
|       client_->RetractItem(itemid, task_id_out);
 | |
|     } else {
 | |
|       XmlElement* state_elem = state_serializer_->Write(state_name_, state);
 | |
|       state_elem->AddAttr(QN_NICK, published_nick);
 | |
|       client_->PublishItem(itemid, state_elem, task_id_out);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
 | |
|   // Signal (task_id, item).  item is NULL for retract.
 | |
|   sigslot::signal2<const std::string&,
 | |
|   const XmlElement*> SignalPublishResult;
 | |
|   // Signal (task_id, item, error stanza).  item is NULL for retract.
 | |
|   sigslot::signal3<const std::string&,
 | |
|   const XmlElement*,
 | |
|   const XmlElement*> SignalPublishError;
 | |
| 
 | |
|  protected:
 | |
|   // return false if retracted item (no info or state given)
 | |
|   virtual bool ParseStateItem(const PubSubItem& item,
 | |
|                               StateItemInfo* info_out,
 | |
|                               C* state_out) {
 | |
|     const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
 | |
|     if (state_elem == NULL) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     info_out->publisher_nick =
 | |
|         client_->GetPublisherNickFromPubSubItem(item.elem);
 | |
|     info_out->published_nick = state_elem->Attr(QN_NICK);
 | |
|     state_serializer_->Parse(state_elem, state_out);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   virtual bool StatesEqual(const C& state1, const C& state2) {
 | |
|     return state1 == state2;
 | |
|   }
 | |
| 
 | |
|   PubSubClient* client() { return client_; }
 | |
|   const QName& state_name() { return state_name_; }
 | |
| 
 | |
|  private:
 | |
|   void OnItems(PubSubClient* pub_sub_client,
 | |
|                const std::vector<PubSubItem>& items) {
 | |
|     for (std::vector<PubSubItem>::const_iterator item = items.begin();
 | |
|          item != items.end(); ++item) {
 | |
|       OnItem(*item);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void OnItem(const PubSubItem& item) {
 | |
|     const std::string& itemid = item.itemid;
 | |
|     StateItemInfo info;
 | |
|     C new_state;
 | |
| 
 | |
|     bool retracted = !ParseStateItem(item, &info, &new_state);
 | |
|     if (retracted) {
 | |
|       bool known_itemid =
 | |
|           (info_by_itemid_.find(itemid) != info_by_itemid_.end());
 | |
|       if (!known_itemid) {
 | |
|         // Nothing to retract, and nothing to publish.
 | |
|         // Probably a different state type.
 | |
|         return;
 | |
|       } else {
 | |
|         info = info_by_itemid_[itemid];
 | |
|         info_by_itemid_.erase(itemid);
 | |
|         new_state = default_state_;
 | |
|       }
 | |
|     } else {
 | |
|       // TODO: Assert new key matches the known key. It
 | |
|       // shouldn't change!
 | |
|       info_by_itemid_[itemid] = info;
 | |
|     }
 | |
| 
 | |
|     std::string key = key_serializer_->GetKey(
 | |
|         info.publisher_nick, info.published_nick);
 | |
|     bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
 | |
|     C old_state = has_old_state ? state_by_key_[key] : default_state_;
 | |
|     if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
 | |
|       // Nothing change, so don't bother signalling.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (retracted || StatesEqual(new_state, default_state_)) {
 | |
|       // We treat a default state similar to a retract.
 | |
|       state_by_key_.erase(key);
 | |
|     } else {
 | |
|       state_by_key_[key] = new_state;
 | |
|     }
 | |
| 
 | |
|     PubSubStateChange<C> change;
 | |
|     if (!retracted) {
 | |
|       // Retracts do not have publisher information.
 | |
|       change.publisher_nick = info.publisher_nick;
 | |
|     }
 | |
|     change.published_nick = info.published_nick;
 | |
|     change.old_state = old_state;
 | |
|     change.new_state = new_state;
 | |
|     SignalStateChange(change);
 | |
|   }
 | |
| 
 | |
|   void OnPublishResult(PubSubClient* pub_sub_client,
 | |
|                        const std::string& task_id,
 | |
|                        const XmlElement* item) {
 | |
|     SignalPublishResult(task_id, item);
 | |
|   }
 | |
| 
 | |
|   void OnPublishError(PubSubClient* pub_sub_client,
 | |
|                       const std::string& task_id,
 | |
|                       const buzz::XmlElement* item,
 | |
|                       const buzz::XmlElement* stanza) {
 | |
|     SignalPublishError(task_id, item, stanza);
 | |
|   }
 | |
| 
 | |
|   void OnRetractResult(PubSubClient* pub_sub_client,
 | |
|                        const std::string& task_id) {
 | |
|     // There's no point in differentiating between publish and retract
 | |
|     // errors, so we simplify by making them both signal a publish
 | |
|     // result.
 | |
|     const XmlElement* item = NULL;
 | |
|     SignalPublishResult(task_id, item);
 | |
|   }
 | |
| 
 | |
|   void OnRetractError(PubSubClient* pub_sub_client,
 | |
|                       const std::string& task_id,
 | |
|                       const buzz::XmlElement* stanza) {
 | |
|     // There's no point in differentiating between publish and retract
 | |
|     // errors, so we simplify by making them both signal a publish
 | |
|     // error.
 | |
|     const XmlElement* item = NULL;
 | |
|     SignalPublishError(task_id, item, stanza);
 | |
|   }
 | |
| 
 | |
|   std::string publisher_nick_;
 | |
|   PubSubClient* client_;
 | |
|   const QName state_name_;
 | |
|   C default_state_;
 | |
|   rtc::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
 | |
|   rtc::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
 | |
|   // key => state
 | |
|   std::map<std::string, C> state_by_key_;
 | |
|   // itemid => StateItemInfo
 | |
|   std::map<std::string, StateItemInfo> info_by_itemid_;
 | |
| 
 | |
|   DISALLOW_COPY_AND_ASSIGN(PubSubStateClient);
 | |
| };
 | |
| }  // namespace buzz
 | |
| 
 | |
| #endif  // TALK_XMPP_PUBSUBSTATECLIENT_H_
 |