| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cloud_print/gcp20/prototype/cloud_print_xmpp_listener.h" |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/rand_util.h" |
| #include "base/single_thread_task_runner.h" |
| #include "cloud_print/gcp20/prototype/cloud_print_url_request_context_getter.h" |
| #include "jingle/notifier/base/notifier_options.h" |
| #include "jingle/notifier/listener/push_client.h" |
| |
| namespace { |
| |
| const int kUrgentPingInterval = 60; // in seconds |
| const int kPingTimeout = 30; // in seconds |
| const double kRandomDelayPercentage = 0.2; |
| |
| const char kCloudPrintPushNotificationsSource[] = "cloudprint.google.com"; |
| |
| // Splits message into two parts (e.g. '<device_id>/delete' will be splitted |
| // into '<device_id>' and '/delete'). |
| void TokenizeXmppMessage(const std::string& message, std::string* device_id, |
| std::string* path) { |
| std::string::size_type pos = message.find('/'); |
| if (pos != std::string::npos) { |
| *device_id = message.substr(0, pos); |
| *path = message.substr(pos); |
| } else { |
| *device_id = message; |
| path->clear(); |
| } |
| } |
| |
| } // namespace |
| |
| CloudPrintXmppListener::CloudPrintXmppListener( |
| const std::string& robot_email, |
| int standard_ping_interval, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| Delegate* delegate) |
| : robot_email_(robot_email), |
| standard_ping_interval_(standard_ping_interval), |
| ping_timeouts_posted_(0), |
| ping_responses_pending_(0), |
| ping_scheduled_(false), |
| context_getter_(new CloudPrintURLRequestContextGetter(task_runner)), |
| delegate_(delegate) { |
| } |
| |
| CloudPrintXmppListener::~CloudPrintXmppListener() { |
| if (push_client_) { |
| push_client_->RemoveObserver(this); |
| push_client_.reset(); |
| } |
| } |
| |
| void CloudPrintXmppListener::Connect(const std::string& access_token) { |
| access_token_ = access_token; |
| ping_responses_pending_ = 0; |
| ping_scheduled_ = false; |
| |
| notifier::NotifierOptions options; |
| options.request_context_getter = context_getter_; |
| options.auth_mechanism = "X-OAUTH2"; |
| options.try_ssltcp_first = true; |
| push_client_ = notifier::PushClient::CreateDefault(options); |
| |
| notifier::Subscription subscription; |
| subscription.channel = kCloudPrintPushNotificationsSource; |
| subscription.from = kCloudPrintPushNotificationsSource; |
| |
| notifier::SubscriptionList list; |
| list.push_back(subscription); |
| |
| push_client_->UpdateSubscriptions(list); |
| push_client_->AddObserver(this); |
| push_client_->UpdateCredentials(robot_email_, access_token_); |
| } |
| |
| void CloudPrintXmppListener::set_ping_interval(int interval) { |
| standard_ping_interval_ = interval; |
| } |
| |
| void CloudPrintXmppListener::OnNotificationsEnabled() { |
| delegate_->OnXmppConnected(); |
| SchedulePing(); |
| } |
| |
| void CloudPrintXmppListener::OnNotificationsDisabled( |
| notifier::NotificationsDisabledReason reason) { |
| switch (reason) { |
| case notifier::NOTIFICATION_CREDENTIALS_REJECTED: |
| delegate_->OnXmppAuthError(); |
| break; |
| case notifier::TRANSIENT_NOTIFICATION_ERROR: |
| Disconnect(); |
| break; |
| default: |
| NOTREACHED() << "XMPP failed with unexpected reason code: " << reason; |
| } |
| } |
| |
| void CloudPrintXmppListener::OnIncomingNotification( |
| const notifier::Notification& notification) { |
| std::string device_id; |
| std::string path; |
| TokenizeXmppMessage(notification.data, &device_id, &path); |
| |
| if (path.empty() || path == "/") { |
| delegate_->OnXmppNewPrintJob(device_id); |
| } else if (path == "/update_settings") { |
| delegate_->OnXmppNewLocalSettings(device_id); |
| } else if (path == "/delete") { |
| delegate_->OnXmppDeleteNotification(device_id); |
| } else { |
| LOG(ERROR) << "Cannot parse XMPP notification: " << notification.data; |
| } |
| } |
| |
| void CloudPrintXmppListener::OnPingResponse() { |
| ping_responses_pending_ = 0; |
| SchedulePing(); |
| } |
| |
| void CloudPrintXmppListener::Disconnect() { |
| push_client_.reset(); |
| delegate_->OnXmppNetworkError(); |
| } |
| |
| void CloudPrintXmppListener::SchedulePing() { |
| if (ping_scheduled_) |
| return; |
| |
| DCHECK_LE(kPingTimeout, kUrgentPingInterval); |
| int delay = (ping_responses_pending_ > 0) |
| ? kUrgentPingInterval - kPingTimeout |
| : standard_ping_interval_; |
| |
| delay += base::RandInt(0, delay*kRandomDelayPercentage); |
| |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&CloudPrintXmppListener::SendPing, AsWeakPtr()), |
| base::TimeDelta::FromSeconds(delay)); |
| |
| ping_scheduled_ = true; |
| } |
| |
| void CloudPrintXmppListener::SendPing() { |
| ping_scheduled_ = false; |
| |
| DCHECK(push_client_); |
| push_client_->SendPing(); |
| ++ping_responses_pending_; |
| |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&CloudPrintXmppListener::OnPingTimeoutReached, AsWeakPtr()), |
| base::TimeDelta::FromSeconds(kPingTimeout)); |
| ++ping_timeouts_posted_; |
| } |
| |
| void CloudPrintXmppListener::OnPingTimeoutReached() { |
| DCHECK_GT(ping_timeouts_posted_, 0); |
| --ping_timeouts_posted_; |
| if (ping_timeouts_posted_ > 0) |
| return; // Fake (old) timeout. |
| |
| switch (ping_responses_pending_) { |
| case 0: |
| break; |
| case 1: |
| SchedulePing(); |
| break; |
| case 2: |
| Disconnect(); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |