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#include "talk/xmpp/hangoutpubsubclient.h"
29
30#include "talk/base/logging.h"
31#include "talk/xmpp/constants.h"
32#include "talk/xmpp/jid.h"
33#include "talk/xmllite/qname.h"
34#include "talk/xmllite/xmlelement.h"
35
36
37// Gives a high-level API for MUC call PubSub needs such as
38// presenter state, recording state, mute state, and remote mute.
39
40namespace buzz {
41
42namespace {
43const char kPresenting[] = "s";
44const char kNotPresenting[] = "o";
45
46}  // namespace
47
48// A simple serialiazer where presence of item => true, lack of item
49// => false.
50class BoolStateSerializer : public PubSubStateSerializer<bool> {
51  virtual XmlElement* Write(const QName& state_name, const bool& state) {
52    if (!state) {
53      return NULL;
54    }
55
56    return new XmlElement(state_name, true);
57  }
58
59  virtual void Parse(const XmlElement* state_elem, bool *state_out) {
60    *state_out = state_elem != NULL;
61  }
62};
63
64class PresenterStateClient : public PubSubStateClient<bool> {
65 public:
66  PresenterStateClient(const std::string& publisher_nick,
67                       PubSubClient* client,
68                       const QName& state_name,
69                       bool default_state)
70      : PubSubStateClient<bool>(
71          publisher_nick, client, state_name, default_state,
72          new PublishedNickKeySerializer(), NULL) {
73  }
74
75  virtual void Publish(const std::string& published_nick,
76                       const bool& state,
77                       std::string* task_id_out) {
78    XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
79    presenter_elem->AddAttr(QN_NICK, published_nick);
80
81    XmlElement* presentation_item_elem =
82        new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
83    const std::string& presentation_type = state ? kPresenting : kNotPresenting;
84    presentation_item_elem->AddAttr(
85        QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
86
87    // The Presenter state is kind of dumb in that it doesn't always use
88    // retracts.  It relies on setting the "type" to a special value.
89    std::string itemid = published_nick;
90    std::vector<XmlElement*> children;
91    children.push_back(presenter_elem);
92    children.push_back(presentation_item_elem);
93    client()->PublishItem(itemid, children, task_id_out);
94  }
95
96 protected:
97  virtual bool ParseStateItem(const PubSubItem& item,
98                              StateItemInfo* info_out,
99                              bool* state_out) {
100    const XmlElement* presenter_elem =
101        item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
102    const XmlElement* presentation_item_elem =
103        item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
104    if (presentation_item_elem == NULL || presenter_elem == NULL) {
105      return false;
106    }
107
108    info_out->publisher_nick =
109        client()->GetPublisherNickFromPubSubItem(item.elem);
110    info_out->published_nick = presenter_elem->Attr(QN_NICK);
111    *state_out = (presentation_item_elem->Attr(
112        QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
113    return true;
114  }
115
116  virtual bool StatesEqual(const bool& state1, const bool& state2) {
117    // Make every item trigger an event, even if state doesn't change.
118    return false;
119  }
120};
121
122HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
123                                         const Jid& mucjid,
124                                         const std::string& nick)
125    : mucjid_(mucjid),
126      nick_(nick) {
127  presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
128  presenter_client_->SignalRequestError.connect(
129      this, &HangoutPubSubClient::OnPresenterRequestError);
130
131  media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
132  media_client_->SignalRequestError.connect(
133      this, &HangoutPubSubClient::OnMediaRequestError);
134
135  presenter_state_client_.reset(new PresenterStateClient(
136      nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
137  presenter_state_client_->SignalStateChange.connect(
138      this, &HangoutPubSubClient::OnPresenterStateChange);
139  presenter_state_client_->SignalPublishResult.connect(
140      this, &HangoutPubSubClient::OnPresenterPublishResult);
141  presenter_state_client_->SignalPublishError.connect(
142      this, &HangoutPubSubClient::OnPresenterPublishError);
143
144  audio_mute_state_client_.reset(new PubSubStateClient<bool>(
145      nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
146      new PublishedNickKeySerializer(), new BoolStateSerializer()));
147  // Can't just repeat because we need to watch for remote mutes.
148  audio_mute_state_client_->SignalStateChange.connect(
149      this, &HangoutPubSubClient::OnAudioMuteStateChange);
150  audio_mute_state_client_->SignalPublishResult.connect(
151      this, &HangoutPubSubClient::OnAudioMutePublishResult);
152  audio_mute_state_client_->SignalPublishError.connect(
153      this, &HangoutPubSubClient::OnAudioMutePublishError);
154
155  video_mute_state_client_.reset(new PubSubStateClient<bool>(
156      nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false,
157      new PublishedNickKeySerializer(), new BoolStateSerializer()));
158  // Can't just repeat because we need to watch for remote mutes.
159  video_mute_state_client_->SignalStateChange.connect(
160      this, &HangoutPubSubClient::OnVideoMuteStateChange);
161  video_mute_state_client_->SignalPublishResult.connect(
162      this, &HangoutPubSubClient::OnVideoMutePublishResult);
163  video_mute_state_client_->SignalPublishError.connect(
164      this, &HangoutPubSubClient::OnVideoMutePublishError);
165
166  video_pause_state_client_.reset(new PubSubStateClient<bool>(
167      nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false,
168      new PublishedNickKeySerializer(), new BoolStateSerializer()));
169  video_pause_state_client_->SignalStateChange.connect(
170      this, &HangoutPubSubClient::OnVideoPauseStateChange);
171  video_pause_state_client_->SignalPublishResult.connect(
172      this, &HangoutPubSubClient::OnVideoPausePublishResult);
173  video_pause_state_client_->SignalPublishError.connect(
174      this, &HangoutPubSubClient::OnVideoPausePublishError);
175
176  recording_state_client_.reset(new PubSubStateClient<bool>(
177      nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
178      new PublishedNickKeySerializer(), new BoolStateSerializer()));
179  recording_state_client_->SignalStateChange.connect(
180      this, &HangoutPubSubClient::OnRecordingStateChange);
181  recording_state_client_->SignalPublishResult.connect(
182      this, &HangoutPubSubClient::OnRecordingPublishResult);
183  recording_state_client_->SignalPublishError.connect(
184      this, &HangoutPubSubClient::OnRecordingPublishError);
185
186  media_block_state_client_.reset(new PubSubStateClient<bool>(
187      nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
188      new PublisherAndPublishedNicksKeySerializer(),
189      new BoolStateSerializer()));
190  media_block_state_client_->SignalStateChange.connect(
191      this, &HangoutPubSubClient::OnMediaBlockStateChange);
192  media_block_state_client_->SignalPublishResult.connect(
193      this, &HangoutPubSubClient::OnMediaBlockPublishResult);
194  media_block_state_client_->SignalPublishError.connect(
195      this, &HangoutPubSubClient::OnMediaBlockPublishError);
196}
197
198HangoutPubSubClient::~HangoutPubSubClient() {
199}
200
201void HangoutPubSubClient::RequestAll() {
202  presenter_client_->RequestItems();
203  media_client_->RequestItems();
204}
205
206void HangoutPubSubClient::OnPresenterRequestError(
207    PubSubClient* client, const XmlElement* stanza) {
208  SignalRequestError(client->node(), stanza);
209}
210
211void HangoutPubSubClient::OnMediaRequestError(
212    PubSubClient* client, const XmlElement* stanza) {
213  SignalRequestError(client->node(), stanza);
214}
215
216void HangoutPubSubClient::PublishPresenterState(
217    bool presenting, std::string* task_id_out) {
218  presenter_state_client_->Publish(nick_, presenting, task_id_out);
219}
220
221void HangoutPubSubClient::PublishAudioMuteState(
222    bool muted, std::string* task_id_out) {
223  audio_mute_state_client_->Publish(nick_, muted, task_id_out);
224}
225
226void HangoutPubSubClient::PublishVideoMuteState(
227    bool muted, std::string* task_id_out) {
228  video_mute_state_client_->Publish(nick_, muted, task_id_out);
229}
230
231void HangoutPubSubClient::PublishVideoPauseState(
232    bool paused, std::string* task_id_out) {
233  video_pause_state_client_->Publish(nick_, paused, task_id_out);
234}
235
236void HangoutPubSubClient::PublishRecordingState(
237    bool recording, std::string* task_id_out) {
238  recording_state_client_->Publish(nick_, recording, task_id_out);
239}
240
241// Remote mute is accomplished by setting another client's mute state.
242void HangoutPubSubClient::RemoteMute(
243    const std::string& mutee_nick, std::string* task_id_out) {
244  audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
245}
246
247// Block media is accomplished by setting another client's block
248// state, kind of like remote mute.
249void HangoutPubSubClient::BlockMedia(
250    const std::string& blockee_nick, std::string* task_id_out) {
251  media_block_state_client_->Publish(blockee_nick, true, task_id_out);
252}
253
254void HangoutPubSubClient::OnPresenterStateChange(
255    const PubSubStateChange<bool>& change) {
256  SignalPresenterStateChange(
257      change.published_nick, change.old_state, change.new_state);
258}
259
260void HangoutPubSubClient::OnPresenterPublishResult(
261    const std::string& task_id, const XmlElement* item) {
262  SignalPublishPresenterResult(task_id);
263}
264
265void HangoutPubSubClient::OnPresenterPublishError(
266    const std::string& task_id, const XmlElement* item,
267    const XmlElement* stanza) {
268  SignalPublishPresenterError(task_id, stanza);
269}
270
271// Since a remote mute is accomplished by another client setting our
272// mute state, if our state changes to muted, we should mute ourselves.
273// Note that remote un-muting is disallowed by the RoomServer.
274void HangoutPubSubClient::OnAudioMuteStateChange(
275    const PubSubStateChange<bool>& change) {
276  bool was_muted = change.old_state;
277  bool is_muted = change.new_state;
278  bool remote_action = (!change.publisher_nick.empty() &&
279                        (change.publisher_nick != change.published_nick));
280
281  if (remote_action) {
282    const std::string& mutee_nick = change.published_nick;
283    const std::string& muter_nick = change.publisher_nick;
284    if (!is_muted) {
285      // The server should prevent remote un-mute.
286      LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick;
287      return;
288    }
289    bool should_mute_locally = (mutee_nick == nick_);
290    SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
291  }
292  SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
293}
294
295const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
296  if (item != NULL) {
297    const XmlElement* audio_mute_state =
298        item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
299    if (audio_mute_state != NULL) {
300      return audio_mute_state->Attr(QN_NICK);
301    }
302  }
303  return std::string();
304}
305
306const std::string GetBlockeeNickFromItem(const XmlElement* item) {
307  if (item != NULL) {
308    const XmlElement* media_block_state =
309        item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
310    if (media_block_state != NULL) {
311      return media_block_state->Attr(QN_NICK);
312    }
313  }
314  return std::string();
315}
316
317void HangoutPubSubClient::OnAudioMutePublishResult(
318    const std::string& task_id, const XmlElement* item) {
319  const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
320  if (mutee_nick != nick_) {
321    SignalRemoteMuteResult(task_id, mutee_nick);
322  } else {
323    SignalPublishAudioMuteResult(task_id);
324  }
325}
326
327void HangoutPubSubClient::OnAudioMutePublishError(
328    const std::string& task_id, const XmlElement* item,
329    const XmlElement* stanza) {
330  const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
331  if (mutee_nick != nick_) {
332    SignalRemoteMuteError(task_id, mutee_nick, stanza);
333  } else {
334    SignalPublishAudioMuteError(task_id, stanza);
335  }
336}
337
338void HangoutPubSubClient::OnVideoMuteStateChange(
339    const PubSubStateChange<bool>& change) {
340  SignalVideoMuteStateChange(
341      change.published_nick, change.old_state, change.new_state);
342}
343
344void HangoutPubSubClient::OnVideoMutePublishResult(
345    const std::string& task_id, const XmlElement* item) {
346  SignalPublishVideoMuteResult(task_id);
347}
348
349void HangoutPubSubClient::OnVideoMutePublishError(
350    const std::string& task_id, const XmlElement* item,
351    const XmlElement* stanza) {
352  SignalPublishVideoMuteError(task_id, stanza);
353}
354
355void HangoutPubSubClient::OnVideoPauseStateChange(
356    const PubSubStateChange<bool>& change) {
357  SignalVideoPauseStateChange(
358      change.published_nick, change.old_state, change.new_state);
359}
360
361void HangoutPubSubClient::OnVideoPausePublishResult(
362    const std::string& task_id, const XmlElement* item) {
363  SignalPublishVideoPauseResult(task_id);
364}
365
366void HangoutPubSubClient::OnVideoPausePublishError(
367    const std::string& task_id, const XmlElement* item,
368    const XmlElement* stanza) {
369  SignalPublishVideoPauseError(task_id, stanza);
370}
371
372void HangoutPubSubClient::OnRecordingStateChange(
373    const PubSubStateChange<bool>& change) {
374  SignalRecordingStateChange(
375      change.published_nick, change.old_state, change.new_state);
376}
377
378void HangoutPubSubClient::OnRecordingPublishResult(
379    const std::string& task_id, const XmlElement* item) {
380  SignalPublishRecordingResult(task_id);
381}
382
383void HangoutPubSubClient::OnRecordingPublishError(
384    const std::string& task_id, const XmlElement* item,
385    const XmlElement* stanza) {
386  SignalPublishRecordingError(task_id, stanza);
387}
388
389void HangoutPubSubClient::OnMediaBlockStateChange(
390    const PubSubStateChange<bool>& change) {
391  const std::string& blockee_nick = change.published_nick;
392  const std::string& blocker_nick = change.publisher_nick;
393
394  bool was_blockee = change.old_state;
395  bool is_blockee = change.new_state;
396  if (!was_blockee && is_blockee) {
397    SignalMediaBlock(blockee_nick, blocker_nick);
398  }
399  // TODO: Should we bother signaling unblock? Currently
400  // it isn't allowed, but it might happen when a participant leaves
401  // the room and the item is retracted.
402}
403
404void HangoutPubSubClient::OnMediaBlockPublishResult(
405    const std::string& task_id, const XmlElement* item) {
406  const std::string& blockee_nick = GetBlockeeNickFromItem(item);
407  SignalMediaBlockResult(task_id, blockee_nick);
408}
409
410void HangoutPubSubClient::OnMediaBlockPublishError(
411    const std::string& task_id, const XmlElement* item,
412    const XmlElement* stanza) {
413  const std::string& blockee_nick = GetBlockeeNickFromItem(item);
414  SignalMediaBlockError(task_id, blockee_nick, stanza);
415}
416
417}  // namespace buzz
418