Home | History | Annotate | Download | only in xmpp
      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:
     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:
     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.
     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