1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cloud_print/gcp20/prototype/cloud_print_xmpp_listener.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "base/message_loop/message_loop.h"
10#include "base/rand_util.h"
11#include "base/single_thread_task_runner.h"
12#include "cloud_print/gcp20/prototype/cloud_print_url_request_context_getter.h"
13#include "jingle/notifier/base/notifier_options.h"
14#include "jingle/notifier/listener/push_client.h"
15
16namespace {
17
18const int kUrgentPingInterval = 60;  // in seconds
19const int kPingTimeout = 30;  // in seconds
20const double kRandomDelayPercentage = 0.2;
21
22const char kCloudPrintPushNotificationsSource[] = "cloudprint.google.com";
23
24// Splits message into two parts (e.g. '<device_id>/delete' will be splitted
25// into '<device_id>' and '/delete').
26void TokenizeXmppMessage(const std::string& message, std::string* device_id,
27                         std::string* path) {
28  std::string::size_type pos = message.find('/');
29  if (pos != std::string::npos) {
30    *device_id = message.substr(0, pos);
31    *path = message.substr(pos);
32  } else {
33    *device_id = message;
34    path->clear();
35  }
36}
37
38}  // namespace
39
40CloudPrintXmppListener::CloudPrintXmppListener(
41    const std::string& robot_email,
42    int standard_ping_interval,
43    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
44    Delegate* delegate)
45    : robot_email_(robot_email),
46      standard_ping_interval_(standard_ping_interval),
47      ping_timeouts_posted_(0),
48      ping_responses_pending_(0),
49      ping_scheduled_(false),
50      context_getter_(new CloudPrintURLRequestContextGetter(task_runner)),
51      delegate_(delegate) {
52}
53
54CloudPrintXmppListener::~CloudPrintXmppListener() {
55  if (push_client_) {
56    push_client_->RemoveObserver(this);
57    push_client_.reset();
58  }
59}
60
61void CloudPrintXmppListener::Connect(const std::string& access_token) {
62  access_token_ = access_token;
63  ping_responses_pending_ = 0;
64  ping_scheduled_ = false;
65
66  notifier::NotifierOptions options;
67  options.request_context_getter = context_getter_;
68  options.auth_mechanism = "X-OAUTH2";
69  options.try_ssltcp_first = true;
70  push_client_ = notifier::PushClient::CreateDefault(options);
71
72  notifier::Subscription subscription;
73  subscription.channel = kCloudPrintPushNotificationsSource;
74  subscription.from = kCloudPrintPushNotificationsSource;
75
76  notifier::SubscriptionList list;
77  list.push_back(subscription);
78
79  push_client_->UpdateSubscriptions(list);
80  push_client_->AddObserver(this);
81  push_client_->UpdateCredentials(robot_email_, access_token_);
82}
83
84void CloudPrintXmppListener::set_ping_interval(int interval) {
85  standard_ping_interval_ = interval;
86}
87
88void CloudPrintXmppListener::OnNotificationsEnabled() {
89  delegate_->OnXmppConnected();
90  SchedulePing();
91}
92
93void CloudPrintXmppListener::OnNotificationsDisabled(
94    notifier::NotificationsDisabledReason reason) {
95  switch (reason) {
96    case notifier::NOTIFICATION_CREDENTIALS_REJECTED:
97      delegate_->OnXmppAuthError();
98      break;
99    case notifier::TRANSIENT_NOTIFICATION_ERROR:
100      Disconnect();
101      break;
102    default:
103      NOTREACHED() << "XMPP failed with unexpected reason code: " << reason;
104  }
105}
106
107void CloudPrintXmppListener::OnIncomingNotification(
108    const notifier::Notification& notification) {
109  std::string device_id;
110  std::string path;
111  TokenizeXmppMessage(notification.data, &device_id, &path);
112
113  if (path.empty() || path == "/") {
114    delegate_->OnXmppNewPrintJob(device_id);
115  } else if (path == "/update_settings") {
116    delegate_->OnXmppNewLocalSettings(device_id);
117  } else if (path == "/delete") {
118    delegate_->OnXmppDeleteNotification(device_id);
119  } else {
120    LOG(ERROR) << "Cannot parse XMPP notification: " << notification.data;
121  }
122}
123
124void CloudPrintXmppListener::OnPingResponse() {
125  ping_responses_pending_ = 0;
126  SchedulePing();
127}
128
129void CloudPrintXmppListener::Disconnect() {
130  push_client_.reset();
131  delegate_->OnXmppNetworkError();
132}
133
134void CloudPrintXmppListener::SchedulePing() {
135  if (ping_scheduled_)
136    return;
137
138  DCHECK_LE(kPingTimeout, kUrgentPingInterval);
139  int delay = (ping_responses_pending_ > 0)
140      ? kUrgentPingInterval - kPingTimeout
141      : standard_ping_interval_;
142
143  delay += base::RandInt(0, delay*kRandomDelayPercentage);
144
145  base::MessageLoop::current()->PostDelayedTask(
146      FROM_HERE,
147      base::Bind(&CloudPrintXmppListener::SendPing, AsWeakPtr()),
148      base::TimeDelta::FromSeconds(delay));
149
150  ping_scheduled_ = true;
151}
152
153void CloudPrintXmppListener::SendPing() {
154  ping_scheduled_ = false;
155
156  DCHECK(push_client_);
157  push_client_->SendPing();
158  ++ping_responses_pending_;
159
160  base::MessageLoop::current()->PostDelayedTask(
161        FROM_HERE,
162        base::Bind(&CloudPrintXmppListener::OnPingTimeoutReached, AsWeakPtr()),
163        base::TimeDelta::FromSeconds(kPingTimeout));
164  ++ping_timeouts_posted_;
165}
166
167void CloudPrintXmppListener::OnPingTimeoutReached() {
168  DCHECK_GT(ping_timeouts_posted_, 0);
169  --ping_timeouts_posted_;
170  if (ping_timeouts_posted_ > 0)
171    return;  // Fake (old) timeout.
172
173  switch (ping_responses_pending_) {
174    case 0:
175      break;
176    case 1:
177      SchedulePing();
178      break;
179    case 2:
180      Disconnect();
181      break;
182    default:
183      NOTREACHED();
184  }
185}
186
187