cloud_print_proxy_backend.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
1// Copyright (c) 2012 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 "chrome/service/cloud_print/cloud_print_proxy_backend.h" 6 7#include <map> 8#include <vector> 9 10#include "base/bind.h" 11#include "base/command_line.h" 12#include "base/compiler_specific.h" 13#include "base/rand_util.h" 14#include "base/values.h" 15#include "chrome/common/chrome_switches.h" 16#include "chrome/common/cloud_print/cloud_print_constants.h" 17#include "chrome/service/cloud_print/cloud_print_auth.h" 18#include "chrome/service/cloud_print/cloud_print_connector.h" 19#include "chrome/service/cloud_print/cloud_print_helpers.h" 20#include "chrome/service/cloud_print/cloud_print_token_store.h" 21#include "chrome/service/cloud_print/connector_settings.h" 22#include "chrome/service/net/service_url_request_context.h" 23#include "chrome/service/service_process.h" 24#include "google_apis/gaia/gaia_oauth_client.h" 25#include "google_apis/gaia/gaia_urls.h" 26#include "grit/generated_resources.h" 27#include "jingle/notifier/base/notifier_options.h" 28#include "jingle/notifier/listener/push_client.h" 29#include "jingle/notifier/listener/push_client_observer.h" 30#include "ui/base/l10n/l10n_util.h" 31#include "url/gurl.h" 32 33namespace cloud_print { 34 35// The real guts of CloudPrintProxyBackend, to keep the public client API clean. 36class CloudPrintProxyBackend::Core 37 : public base::RefCountedThreadSafe<CloudPrintProxyBackend::Core>, 38 public CloudPrintAuth::Client, 39 public CloudPrintConnector::Client, 40 public notifier::PushClientObserver { 41 public: 42 // It is OK for print_server_url to be empty. In this case system should 43 // use system default (local) print server. 44 Core(CloudPrintProxyBackend* backend, 45 const ConnectorSettings& settings, 46 const gaia::OAuthClientInfo& oauth_client_info, 47 bool enable_job_poll); 48 49 // Note: 50 // 51 // The Do* methods are the various entry points from CloudPrintProxyBackend 52 // It calls us on a dedicated thread to actually perform synchronous 53 // (and potentially blocking) operations. 54 void DoInitializeWithToken(const std::string& cloud_print_token); 55 void DoInitializeWithRobotToken(const std::string& robot_oauth_refresh_token, 56 const std::string& robot_email); 57 void DoInitializeWithRobotAuthCode(const std::string& robot_oauth_auth_code, 58 const std::string& robot_email); 59 60 // Called on the CloudPrintProxyBackend core_thread_ to perform 61 // shutdown. 62 void DoShutdown(); 63 void DoRegisterSelectedPrinters( 64 const printing::PrinterList& printer_list); 65 void DoUnregisterPrinters(); 66 67 // CloudPrintAuth::Client implementation. 68 virtual void OnAuthenticationComplete( 69 const std::string& access_token, 70 const std::string& robot_oauth_refresh_token, 71 const std::string& robot_email, 72 const std::string& user_email) OVERRIDE; 73 virtual void OnInvalidCredentials() OVERRIDE; 74 75 // CloudPrintConnector::Client implementation. 76 virtual void OnAuthFailed() OVERRIDE; 77 virtual void OnXmppPingUpdated(int ping_timeout) OVERRIDE; 78 79 // notifier::PushClientObserver implementation. 80 virtual void OnNotificationsEnabled() OVERRIDE; 81 virtual void OnNotificationsDisabled( 82 notifier::NotificationsDisabledReason reason) OVERRIDE; 83 virtual void OnIncomingNotification( 84 const notifier::Notification& notification) OVERRIDE; 85 virtual void OnPingResponse() OVERRIDE; 86 87 private: 88 friend class base::RefCountedThreadSafe<Core>; 89 90 virtual ~Core() {} 91 92 void CreateAuthAndConnector(); 93 void DestroyAuthAndConnector(); 94 95 // NotifyXXX is how the Core communicates with the frontend across 96 // threads. 97 void NotifyPrinterListAvailable( 98 const printing::PrinterList& printer_list); 99 void NotifyAuthenticated( 100 const std::string& robot_oauth_refresh_token, 101 const std::string& robot_email, 102 const std::string& user_email); 103 void NotifyAuthenticationFailed(); 104 void NotifyPrintSystemUnavailable(); 105 void NotifyUnregisterPrinters(const std::string& auth_token, 106 const std::list<std::string>& printer_ids); 107 void NotifyXmppPingUpdated(int ping_timeout); 108 109 // Init XMPP channel 110 void InitNotifications(const std::string& robot_email, 111 const std::string& access_token); 112 113 void HandlePrinterNotification(const std::string& notification); 114 void PollForJobs(); 115 // Schedules a task to poll for jobs. Does nothing if a task is already 116 // scheduled. 117 void ScheduleJobPoll(); 118 void PingXmppServer(); 119 void ScheduleXmppPing(); 120 void CheckXmppPingStatus(); 121 122 CloudPrintTokenStore* GetTokenStore(); 123 124 // Our parent CloudPrintProxyBackend 125 CloudPrintProxyBackend* backend_; 126 127 // Cloud Print authenticator. 128 scoped_refptr<CloudPrintAuth> auth_; 129 130 // Cloud Print connector. 131 scoped_refptr<CloudPrintConnector> connector_; 132 133 // OAuth client info. 134 gaia::OAuthClientInfo oauth_client_info_; 135 // Notification (xmpp) handler. 136 scoped_ptr<notifier::PushClient> push_client_; 137 // Indicates whether XMPP notifications are currently enabled. 138 bool notifications_enabled_; 139 // The time when notifications were enabled. Valid only when 140 // notifications_enabled_ is true. 141 base::TimeTicks notifications_enabled_since_; 142 // Indicates whether a task to poll for jobs has been scheduled. 143 bool job_poll_scheduled_; 144 // Indicates whether we should poll for jobs when we lose XMPP connection. 145 bool enable_job_poll_; 146 // Indicates whether a task to ping xmpp server has been scheduled. 147 bool xmpp_ping_scheduled_; 148 // Number of XMPP pings pending reply from the server. 149 int pending_xmpp_pings_; 150 // Connector settings. 151 ConnectorSettings settings_; 152 std::string robot_email_; 153 scoped_ptr<CloudPrintTokenStore> token_store_; 154 155 DISALLOW_COPY_AND_ASSIGN(Core); 156}; 157 158CloudPrintProxyBackend::CloudPrintProxyBackend( 159 CloudPrintProxyFrontend* frontend, 160 const ConnectorSettings& settings, 161 const gaia::OAuthClientInfo& oauth_client_info, 162 bool enable_job_poll) 163 : core_thread_("Chrome_CloudPrintProxyCoreThread"), 164 frontend_loop_(base::MessageLoop::current()), 165 frontend_(frontend) { 166 DCHECK(frontend_); 167 core_ = new Core(this, settings, oauth_client_info, enable_job_poll); 168} 169 170CloudPrintProxyBackend::~CloudPrintProxyBackend() { DCHECK(!core_.get()); } 171 172bool CloudPrintProxyBackend::InitializeWithToken( 173 const std::string& cloud_print_token) { 174 if (!core_thread_.Start()) 175 return false; 176 core_thread_.message_loop()->PostTask( 177 FROM_HERE, 178 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithToken, 179 core_.get(), cloud_print_token)); 180 return true; 181} 182 183bool CloudPrintProxyBackend::InitializeWithRobotToken( 184 const std::string& robot_oauth_refresh_token, 185 const std::string& robot_email) { 186 if (!core_thread_.Start()) 187 return false; 188 core_thread_.message_loop()->PostTask( 189 FROM_HERE, 190 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken, 191 core_.get(), robot_oauth_refresh_token, robot_email)); 192 return true; 193} 194 195bool CloudPrintProxyBackend::InitializeWithRobotAuthCode( 196 const std::string& robot_oauth_auth_code, 197 const std::string& robot_email) { 198 if (!core_thread_.Start()) 199 return false; 200 core_thread_.message_loop()->PostTask( 201 FROM_HERE, 202 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode, 203 core_.get(), robot_oauth_auth_code, robot_email)); 204 return true; 205} 206 207void CloudPrintProxyBackend::Shutdown() { 208 core_thread_.message_loop()->PostTask( 209 FROM_HERE, 210 base::Bind(&CloudPrintProxyBackend::Core::DoShutdown, core_.get())); 211 core_thread_.Stop(); 212 core_ = NULL; // Releases reference to core_. 213} 214 215void CloudPrintProxyBackend::UnregisterPrinters() { 216 core_thread_.message_loop()->PostTask( 217 FROM_HERE, 218 base::Bind(&CloudPrintProxyBackend::Core::DoUnregisterPrinters, 219 core_.get())); 220} 221 222CloudPrintProxyBackend::Core::Core( 223 CloudPrintProxyBackend* backend, 224 const ConnectorSettings& settings, 225 const gaia::OAuthClientInfo& oauth_client_info, 226 bool enable_job_poll) 227 : backend_(backend), 228 oauth_client_info_(oauth_client_info), 229 notifications_enabled_(false), 230 job_poll_scheduled_(false), 231 enable_job_poll_(enable_job_poll), 232 xmpp_ping_scheduled_(false), 233 pending_xmpp_pings_(0) { 234 settings_.CopyFrom(settings); 235} 236 237void CloudPrintProxyBackend::Core::CreateAuthAndConnector() { 238 if (!auth_.get()) { 239 auth_ = new CloudPrintAuth(this, settings_.server_url(), oauth_client_info_, 240 settings_.proxy_id()); 241 } 242 243 if (!connector_.get()) { 244 connector_ = new CloudPrintConnector(this, settings_); 245 } 246} 247 248void CloudPrintProxyBackend::Core::DestroyAuthAndConnector() { 249 auth_ = NULL; 250 connector_ = NULL; 251} 252 253void CloudPrintProxyBackend::Core::DoInitializeWithToken( 254 const std::string& cloud_print_token) { 255 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 256 CreateAuthAndConnector(); 257 auth_->AuthenticateWithToken(cloud_print_token); 258} 259 260void CloudPrintProxyBackend::Core::DoInitializeWithRobotToken( 261 const std::string& robot_oauth_refresh_token, 262 const std::string& robot_email) { 263 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 264 CreateAuthAndConnector(); 265 auth_->AuthenticateWithRobotToken(robot_oauth_refresh_token, robot_email); 266} 267 268void CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode( 269 const std::string& robot_oauth_auth_code, 270 const std::string& robot_email) { 271 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 272 CreateAuthAndConnector(); 273 auth_->AuthenticateWithRobotAuthCode(robot_oauth_auth_code, robot_email); 274} 275 276void CloudPrintProxyBackend::Core::OnAuthenticationComplete( 277 const std::string& access_token, 278 const std::string& robot_oauth_refresh_token, 279 const std::string& robot_email, 280 const std::string& user_email) { 281 CloudPrintTokenStore* token_store = GetTokenStore(); 282 bool first_time = token_store->token().empty(); 283 token_store->SetToken(access_token); 284 robot_email_ = robot_email; 285 // Let the frontend know that we have authenticated. 286 backend_->frontend_loop_->PostTask( 287 FROM_HERE, 288 base::Bind(&Core::NotifyAuthenticated, this, robot_oauth_refresh_token, 289 robot_email, user_email)); 290 if (first_time) { 291 InitNotifications(robot_email, access_token); 292 } else { 293 // If we are refreshing a token, update the XMPP token too. 294 DCHECK(push_client_.get()); 295 push_client_->UpdateCredentials(robot_email, access_token); 296 } 297 // Start cloud print connector if needed. 298 if (!connector_->IsRunning()) { 299 if (!connector_->Start()) { 300 // Let the frontend know that we do not have a print system. 301 backend_->frontend_loop_->PostTask( 302 FROM_HERE, base::Bind(&Core::NotifyPrintSystemUnavailable, this)); 303 } 304 } 305} 306 307void CloudPrintProxyBackend::Core::OnInvalidCredentials() { 308 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 309 VLOG(1) << "CP_CONNECTOR: Auth Error"; 310 backend_->frontend_loop_->PostTask( 311 FROM_HERE, base::Bind(&Core::NotifyAuthenticationFailed, this)); 312} 313 314void CloudPrintProxyBackend::Core::OnAuthFailed() { 315 VLOG(1) << "CP_CONNECTOR: Authentication failed in connector."; 316 // Let's stop connecter and refresh token. We'll restart connecter once 317 // new token available. 318 if (connector_->IsRunning()) 319 connector_->Stop(); 320 321 // Refresh Auth token. 322 auth_->RefreshAccessToken(); 323} 324 325void CloudPrintProxyBackend::Core::OnXmppPingUpdated(int ping_timeout) { 326 settings_.SetXmppPingTimeoutSec(ping_timeout); 327 backend_->frontend_loop_->PostTask( 328 FROM_HERE, 329 base::Bind(&Core::NotifyXmppPingUpdated, this, ping_timeout)); 330} 331 332void CloudPrintProxyBackend::Core::InitNotifications( 333 const std::string& robot_email, 334 const std::string& access_token) { 335 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 336 337 pending_xmpp_pings_ = 0; 338 notifier::NotifierOptions notifier_options; 339 notifier_options.request_context_getter = 340 g_service_process->GetServiceURLRequestContextGetter(); 341 notifier_options.auth_mechanism = "X-OAUTH2"; 342 notifier_options.try_ssltcp_first = true; 343 push_client_ = notifier::PushClient::CreateDefault(notifier_options); 344 push_client_->AddObserver(this); 345 notifier::Subscription subscription; 346 subscription.channel = kCloudPrintPushNotificationsSource; 347 subscription.from = kCloudPrintPushNotificationsSource; 348 push_client_->UpdateSubscriptions( 349 notifier::SubscriptionList(1, subscription)); 350 push_client_->UpdateCredentials(robot_email, access_token); 351} 352 353void CloudPrintProxyBackend::Core::DoShutdown() { 354 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 355 VLOG(1) << "CP_CONNECTOR: Shutdown connector, id: " << settings_.proxy_id(); 356 357 if (connector_->IsRunning()) 358 connector_->Stop(); 359 360 // Important to delete the PushClient on this thread. 361 if (push_client_.get()) { 362 push_client_->RemoveObserver(this); 363 } 364 push_client_.reset(); 365 notifications_enabled_ = false; 366 notifications_enabled_since_ = base::TimeTicks(); 367 token_store_.reset(); 368 369 DestroyAuthAndConnector(); 370} 371 372void CloudPrintProxyBackend::Core::DoUnregisterPrinters() { 373 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 374 375 std::string access_token = GetTokenStore()->token(); 376 377 std::list<std::string> printer_ids; 378 connector_->GetPrinterIds(&printer_ids); 379 380 backend_->frontend_loop_->PostTask( 381 FROM_HERE, 382 base::Bind(&Core::NotifyUnregisterPrinters, 383 this, access_token, printer_ids)); 384} 385 386void CloudPrintProxyBackend::Core::HandlePrinterNotification( 387 const std::string& notification) { 388 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 389 390 size_t pos = notification.rfind(kNotificationUpdateSettings); 391 if (pos == std::string::npos) { 392 VLOG(1) << "CP_CONNECTOR: Handle printer notification, id: " 393 << notification; 394 connector_->CheckForJobs(kJobFetchReasonNotified, notification); 395 } else { 396 DCHECK(pos == notification.length() - strlen(kNotificationUpdateSettings)); 397 std::string printer_id = notification.substr(0, pos); 398 VLOG(1) << "CP_CONNECTOR: Update printer settings, id: " << printer_id; 399 connector_->UpdatePrinterSettings(printer_id); 400 } 401} 402 403void CloudPrintProxyBackend::Core::PollForJobs() { 404 VLOG(1) << "CP_CONNECTOR: Polling for jobs."; 405 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 406 // Check all printers for jobs. 407 connector_->CheckForJobs(kJobFetchReasonPoll, std::string()); 408 409 job_poll_scheduled_ = false; 410 // If we don't have notifications and job polling is enabled, poll again 411 // after a while. 412 if (!notifications_enabled_ && enable_job_poll_) 413 ScheduleJobPoll(); 414} 415 416void CloudPrintProxyBackend::Core::ScheduleJobPoll() { 417 if (!job_poll_scheduled_) { 418 base::TimeDelta interval = base::TimeDelta::FromSeconds( 419 base::RandInt(kMinJobPollIntervalSecs, kMaxJobPollIntervalSecs)); 420 base::MessageLoop::current()->PostDelayedTask( 421 FROM_HERE, 422 base::Bind(&CloudPrintProxyBackend::Core::PollForJobs, this), 423 interval); 424 job_poll_scheduled_ = true; 425 } 426} 427 428void CloudPrintProxyBackend::Core::PingXmppServer() { 429 xmpp_ping_scheduled_ = false; 430 431 if (!push_client_.get()) 432 return; 433 434 push_client_->SendPing(); 435 436 pending_xmpp_pings_++; 437 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) { 438 // Check ping status when we close to the limit. 439 base::MessageLoop::current()->PostDelayedTask( 440 FROM_HERE, 441 base::Bind(&CloudPrintProxyBackend::Core::CheckXmppPingStatus, this), 442 base::TimeDelta::FromSeconds(kXmppPingCheckIntervalSecs)); 443 } 444 445 // Schedule next ping if needed. 446 if (notifications_enabled_) 447 ScheduleXmppPing(); 448} 449 450void CloudPrintProxyBackend::Core::ScheduleXmppPing() { 451 // settings_.xmpp_ping_enabled() is obsolete, we are now control 452 // XMPP pings from Cloud Print server. 453 if (!xmpp_ping_scheduled_) { 454 base::TimeDelta interval = base::TimeDelta::FromSeconds( 455 base::RandInt(settings_.xmpp_ping_timeout_sec() * 0.9, 456 settings_.xmpp_ping_timeout_sec() * 1.1)); 457 base::MessageLoop::current()->PostDelayedTask( 458 FROM_HERE, 459 base::Bind(&CloudPrintProxyBackend::Core::PingXmppServer, this), 460 interval); 461 xmpp_ping_scheduled_ = true; 462 } 463} 464 465void CloudPrintProxyBackend::Core::CheckXmppPingStatus() { 466 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) { 467 // Reconnect to XMPP. 468 pending_xmpp_pings_ = 0; 469 push_client_.reset(); 470 InitNotifications(robot_email_, GetTokenStore()->token()); 471 } 472} 473 474CloudPrintTokenStore* CloudPrintProxyBackend::Core::GetTokenStore() { 475 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 476 if (!token_store_.get()) 477 token_store_.reset(new CloudPrintTokenStore); 478 return token_store_.get(); 479} 480 481void CloudPrintProxyBackend::Core::NotifyAuthenticated( 482 const std::string& robot_oauth_refresh_token, 483 const std::string& robot_email, 484 const std::string& user_email) { 485 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 486 backend_->frontend_ 487 ->OnAuthenticated(robot_oauth_refresh_token, robot_email, user_email); 488} 489 490void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() { 491 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 492 backend_->frontend_->OnAuthenticationFailed(); 493} 494 495void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() { 496 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 497 backend_->frontend_->OnPrintSystemUnavailable(); 498} 499 500void CloudPrintProxyBackend::Core::NotifyUnregisterPrinters( 501 const std::string& auth_token, 502 const std::list<std::string>& printer_ids) { 503 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 504 backend_->frontend_->OnUnregisterPrinters(auth_token, printer_ids); 505} 506 507void CloudPrintProxyBackend::Core::NotifyXmppPingUpdated(int ping_timeout) { 508 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_); 509 backend_->frontend_->OnXmppPingUpdated(ping_timeout); 510} 511 512void CloudPrintProxyBackend::Core::OnNotificationsEnabled() { 513 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 514 notifications_enabled_ = true; 515 notifications_enabled_since_ = base::TimeTicks::Now(); 516 VLOG(1) << "Notifications for connector " << settings_.proxy_id() 517 << " were enabled at " 518 << notifications_enabled_since_.ToInternalValue(); 519 // Notifications just got re-enabled. In this case we want to schedule 520 // a poll once for jobs we might have missed when we were dark. 521 // Note that ScheduleJobPoll will not schedule again if a job poll task is 522 // already scheduled. 523 ScheduleJobPoll(); 524 525 // Schedule periodic ping for XMPP notification channel. 526 ScheduleXmppPing(); 527} 528 529void CloudPrintProxyBackend::Core::OnNotificationsDisabled( 530 notifier::NotificationsDisabledReason reason) { 531 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 532 notifications_enabled_ = false; 533 LOG(ERROR) << "Notifications for connector " << settings_.proxy_id() 534 << " disabled."; 535 notifications_enabled_since_ = base::TimeTicks(); 536 // We just lost notifications. This this case we want to schedule a 537 // job poll if enable_job_poll_ is true. 538 if (enable_job_poll_) 539 ScheduleJobPoll(); 540} 541 542 543void CloudPrintProxyBackend::Core::OnIncomingNotification( 544 const notifier::Notification& notification) { 545 // Since we got some notification from the server, 546 // reset pending ping counter to 0. 547 pending_xmpp_pings_ = 0; 548 549 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop()); 550 VLOG(1) << "CP_CONNECTOR: Incoming notification."; 551 if (0 == base::strcasecmp(kCloudPrintPushNotificationsSource, 552 notification.channel.c_str())) 553 HandlePrinterNotification(notification.data); 554} 555 556void CloudPrintProxyBackend::Core::OnPingResponse() { 557 pending_xmpp_pings_ = 0; 558 VLOG(1) << "CP_CONNECTOR: Ping response received."; 559} 560 561} // namespace cloud_print 562