Home | History | Annotate | Download | only in prototype
      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 
     16 namespace {
     17 
     18 const int kUrgentPingInterval = 60;  // in seconds
     19 const int kPingTimeout = 30;  // in seconds
     20 const double kRandomDelayPercentage = 0.2;
     21 
     22 const 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').
     26 void 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 
     40 CloudPrintXmppListener::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 
     54 CloudPrintXmppListener::~CloudPrintXmppListener() {
     55   if (push_client_) {
     56     push_client_->RemoveObserver(this);
     57     push_client_.reset();
     58   }
     59 }
     60 
     61 void 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 
     84 void CloudPrintXmppListener::set_ping_interval(int interval) {
     85   standard_ping_interval_ = interval;
     86 }
     87 
     88 void CloudPrintXmppListener::OnNotificationsEnabled() {
     89   delegate_->OnXmppConnected();
     90   SchedulePing();
     91 }
     92 
     93 void 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 
    107 void 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 
    124 void CloudPrintXmppListener::OnPingResponse() {
    125   ping_responses_pending_ = 0;
    126   SchedulePing();
    127 }
    128 
    129 void CloudPrintXmppListener::Disconnect() {
    130   push_client_.reset();
    131   delegate_->OnXmppNetworkError();
    132 }
    133 
    134 void 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 
    153 void 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 
    167 void 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