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