Make castv2 performance test work.
[chromium-blink-merge.git] / chrome / service / cloud_print / cloud_print_proxy_backend.cc
blob233ff2e63228c1e46738529d06de17f97ffe7846
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.
5 #include "chrome/service/cloud_print/cloud_print_proxy_backend.h"
7 #include <map>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/compiler_specific.h"
13 #include "base/metrics/histogram.h"
14 #include "base/rand_util.h"
15 #include "base/strings/string_util.h"
16 #include "base/values.h"
17 #include "chrome/common/cloud_print/cloud_print_constants.h"
18 #include "chrome/service/cloud_print/cloud_print_auth.h"
19 #include "chrome/service/cloud_print/cloud_print_connector.h"
20 #include "chrome/service/cloud_print/cloud_print_service_helpers.h"
21 #include "chrome/service/cloud_print/cloud_print_token_store.h"
22 #include "chrome/service/cloud_print/connector_settings.h"
23 #include "chrome/service/net/service_url_request_context_getter.h"
24 #include "chrome/service/service_process.h"
25 #include "components/cloud_devices/common/cloud_devices_switches.h"
26 #include "google_apis/gaia/gaia_oauth_client.h"
27 #include "google_apis/gaia/gaia_urls.h"
28 #include "jingle/notifier/base/notifier_options.h"
29 #include "jingle/notifier/listener/push_client.h"
30 #include "jingle/notifier/listener/push_client_observer.h"
31 #include "url/gurl.h"
33 namespace cloud_print {
35 // The real guts of CloudPrintProxyBackend, to keep the public client API clean.
36 class 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);
49 // Note:
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);
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();
67 // CloudPrintAuth::Client implementation.
68 void OnAuthenticationComplete(const std::string& access_token,
69 const std::string& robot_oauth_refresh_token,
70 const std::string& robot_email,
71 const std::string& user_email) override;
72 void OnInvalidCredentials() override;
74 // CloudPrintConnector::Client implementation.
75 void OnAuthFailed() override;
76 void OnXmppPingUpdated(int ping_timeout) override;
78 // notifier::PushClientObserver implementation.
79 void OnNotificationsEnabled() override;
80 void OnNotificationsDisabled(
81 notifier::NotificationsDisabledReason reason) override;
82 void OnIncomingNotification(
83 const notifier::Notification& notification) override;
84 void OnPingResponse() override;
86 private:
87 friend class base::RefCountedThreadSafe<Core>;
89 ~Core() override {}
91 void CreateAuthAndConnector();
92 void DestroyAuthAndConnector();
94 // NotifyXXX is how the Core communicates with the frontend across
95 // threads.
96 void NotifyPrinterListAvailable(
97 const printing::PrinterList& printer_list);
98 void NotifyAuthenticated(
99 const std::string& robot_oauth_refresh_token,
100 const std::string& robot_email,
101 const std::string& user_email);
102 void NotifyAuthenticationFailed();
103 void NotifyPrintSystemUnavailable();
104 void NotifyUnregisterPrinters(const std::string& auth_token,
105 const std::list<std::string>& printer_ids);
106 void NotifyXmppPingUpdated(int ping_timeout);
108 // Init XMPP channel
109 void InitNotifications(const std::string& robot_email,
110 const std::string& access_token);
112 void HandlePrinterNotification(const std::string& notification);
113 void PollForJobs();
114 // Schedules a task to poll for jobs. Does nothing if a task is already
115 // scheduled.
116 void ScheduleJobPoll();
117 void PingXmppServer();
118 void ScheduleXmppPing();
119 void CheckXmppPingStatus();
121 CloudPrintTokenStore* GetTokenStore();
123 // Our parent CloudPrintProxyBackend
124 CloudPrintProxyBackend* backend_;
126 // Cloud Print authenticator.
127 scoped_refptr<CloudPrintAuth> auth_;
129 // Cloud Print connector.
130 scoped_refptr<CloudPrintConnector> connector_;
132 // OAuth client info.
133 gaia::OAuthClientInfo oauth_client_info_;
134 // Notification (xmpp) handler.
135 scoped_ptr<notifier::PushClient> push_client_;
136 // Indicates whether XMPP notifications are currently enabled.
137 bool notifications_enabled_;
138 // The time when notifications were enabled. Valid only when
139 // notifications_enabled_ is true.
140 base::TimeTicks notifications_enabled_since_;
141 // Indicates whether a task to poll for jobs has been scheduled.
142 bool job_poll_scheduled_;
143 // Indicates whether we should poll for jobs when we lose XMPP connection.
144 bool enable_job_poll_;
145 // Indicates whether a task to ping xmpp server has been scheduled.
146 bool xmpp_ping_scheduled_;
147 // Number of XMPP pings pending reply from the server.
148 int pending_xmpp_pings_;
149 // Connector settings.
150 ConnectorSettings settings_;
151 std::string robot_email_;
152 scoped_ptr<CloudPrintTokenStore> token_store_;
154 DISALLOW_COPY_AND_ASSIGN(Core);
157 CloudPrintProxyBackend::CloudPrintProxyBackend(
158 CloudPrintProxyFrontend* frontend,
159 const ConnectorSettings& settings,
160 const gaia::OAuthClientInfo& oauth_client_info,
161 bool enable_job_poll)
162 : core_thread_("Chrome_CloudPrintProxyCoreThread"),
163 frontend_loop_(base::MessageLoop::current()),
164 frontend_(frontend) {
165 DCHECK(frontend_);
166 core_ = new Core(this, settings, oauth_client_info, enable_job_poll);
169 CloudPrintProxyBackend::~CloudPrintProxyBackend() { DCHECK(!core_.get()); }
171 bool CloudPrintProxyBackend::InitializeWithToken(
172 const std::string& cloud_print_token) {
173 if (!core_thread_.Start())
174 return false;
175 core_thread_.message_loop()->PostTask(
176 FROM_HERE,
177 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithToken,
178 core_.get(), cloud_print_token));
179 return true;
182 bool CloudPrintProxyBackend::InitializeWithRobotToken(
183 const std::string& robot_oauth_refresh_token,
184 const std::string& robot_email) {
185 if (!core_thread_.Start())
186 return false;
187 core_thread_.message_loop()->PostTask(
188 FROM_HERE,
189 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken,
190 core_.get(), robot_oauth_refresh_token, robot_email));
191 return true;
194 bool CloudPrintProxyBackend::InitializeWithRobotAuthCode(
195 const std::string& robot_oauth_auth_code,
196 const std::string& robot_email) {
197 if (!core_thread_.Start())
198 return false;
199 core_thread_.message_loop()->PostTask(
200 FROM_HERE,
201 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode,
202 core_.get(), robot_oauth_auth_code, robot_email));
203 return true;
206 void CloudPrintProxyBackend::Shutdown() {
207 core_thread_.message_loop()->PostTask(
208 FROM_HERE,
209 base::Bind(&CloudPrintProxyBackend::Core::DoShutdown, core_.get()));
210 core_thread_.Stop();
211 core_ = NULL; // Releases reference to core_.
214 void CloudPrintProxyBackend::UnregisterPrinters() {
215 core_thread_.message_loop()->PostTask(
216 FROM_HERE,
217 base::Bind(&CloudPrintProxyBackend::Core::DoUnregisterPrinters,
218 core_.get()));
221 CloudPrintProxyBackend::Core::Core(
222 CloudPrintProxyBackend* backend,
223 const ConnectorSettings& settings,
224 const gaia::OAuthClientInfo& oauth_client_info,
225 bool enable_job_poll)
226 : backend_(backend),
227 oauth_client_info_(oauth_client_info),
228 notifications_enabled_(false),
229 job_poll_scheduled_(false),
230 enable_job_poll_(enable_job_poll),
231 xmpp_ping_scheduled_(false),
232 pending_xmpp_pings_(0) {
233 settings_.CopyFrom(settings);
236 void CloudPrintProxyBackend::Core::CreateAuthAndConnector() {
237 if (!auth_.get()) {
238 auth_ = new CloudPrintAuth(this, settings_.server_url(), oauth_client_info_,
239 settings_.proxy_id());
242 if (!connector_.get()) {
243 connector_ = new CloudPrintConnector(this, settings_);
247 void CloudPrintProxyBackend::Core::DestroyAuthAndConnector() {
248 auth_ = NULL;
249 connector_ = NULL;
252 void CloudPrintProxyBackend::Core::DoInitializeWithToken(
253 const std::string& cloud_print_token) {
254 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
255 CreateAuthAndConnector();
256 auth_->AuthenticateWithToken(cloud_print_token);
259 void CloudPrintProxyBackend::Core::DoInitializeWithRobotToken(
260 const std::string& robot_oauth_refresh_token,
261 const std::string& robot_email) {
262 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
263 CreateAuthAndConnector();
264 auth_->AuthenticateWithRobotToken(robot_oauth_refresh_token, robot_email);
267 void CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode(
268 const std::string& robot_oauth_auth_code,
269 const std::string& robot_email) {
270 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
271 CreateAuthAndConnector();
272 auth_->AuthenticateWithRobotAuthCode(robot_oauth_auth_code, robot_email);
275 void CloudPrintProxyBackend::Core::OnAuthenticationComplete(
276 const std::string& access_token,
277 const std::string& robot_oauth_refresh_token,
278 const std::string& robot_email,
279 const std::string& user_email) {
280 CloudPrintTokenStore* token_store = GetTokenStore();
281 bool first_time = token_store->token().empty();
282 token_store->SetToken(access_token);
283 robot_email_ = robot_email;
284 // Let the frontend know that we have authenticated.
285 backend_->frontend_loop_->PostTask(
286 FROM_HERE,
287 base::Bind(&Core::NotifyAuthenticated, this, robot_oauth_refresh_token,
288 robot_email, user_email));
289 if (first_time) {
290 InitNotifications(robot_email, access_token);
291 } else {
292 // If we are refreshing a token, update the XMPP token too.
293 DCHECK(push_client_.get());
294 push_client_->UpdateCredentials(robot_email, access_token);
296 // Start cloud print connector if needed.
297 if (!connector_->IsRunning()) {
298 if (!connector_->Start()) {
299 // Let the frontend know that we do not have a print system.
300 backend_->frontend_loop_->PostTask(
301 FROM_HERE, base::Bind(&Core::NotifyPrintSystemUnavailable, this));
306 void CloudPrintProxyBackend::Core::OnInvalidCredentials() {
307 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
308 VLOG(1) << "CP_CONNECTOR: Auth Error";
309 backend_->frontend_loop_->PostTask(
310 FROM_HERE, base::Bind(&Core::NotifyAuthenticationFailed, this));
313 void CloudPrintProxyBackend::Core::OnAuthFailed() {
314 VLOG(1) << "CP_CONNECTOR: Authentication failed in connector.";
315 // Let's stop connecter and refresh token. We'll restart connecter once
316 // new token available.
317 if (connector_->IsRunning())
318 connector_->Stop();
320 // Refresh Auth token.
321 auth_->RefreshAccessToken();
324 void CloudPrintProxyBackend::Core::OnXmppPingUpdated(int ping_timeout) {
325 settings_.SetXmppPingTimeoutSec(ping_timeout);
326 backend_->frontend_loop_->PostTask(
327 FROM_HERE,
328 base::Bind(&Core::NotifyXmppPingUpdated, this, ping_timeout));
331 void CloudPrintProxyBackend::Core::InitNotifications(
332 const std::string& robot_email,
333 const std::string& access_token) {
334 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
336 pending_xmpp_pings_ = 0;
337 notifier::NotifierOptions notifier_options;
338 notifier_options.request_context_getter =
339 g_service_process->GetServiceURLRequestContextGetter();
340 notifier_options.auth_mechanism = "X-OAUTH2";
341 notifier_options.try_ssltcp_first = true;
342 notifier_options.xmpp_host_port = net::HostPortPair::FromString(
343 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
344 switches::kCloudPrintXmppEndpoint));
345 push_client_ = notifier::PushClient::CreateDefault(notifier_options);
346 push_client_->AddObserver(this);
347 notifier::Subscription subscription;
348 subscription.channel = kCloudPrintPushNotificationsSource;
349 subscription.from = kCloudPrintPushNotificationsSource;
350 push_client_->UpdateSubscriptions(
351 notifier::SubscriptionList(1, subscription));
352 push_client_->UpdateCredentials(robot_email, access_token);
355 void CloudPrintProxyBackend::Core::DoShutdown() {
356 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
357 VLOG(1) << "CP_CONNECTOR: Shutdown connector, id: " << settings_.proxy_id();
359 if (connector_->IsRunning())
360 connector_->Stop();
362 // Important to delete the PushClient on this thread.
363 if (push_client_.get()) {
364 push_client_->RemoveObserver(this);
366 push_client_.reset();
367 notifications_enabled_ = false;
368 notifications_enabled_since_ = base::TimeTicks();
369 token_store_.reset();
371 DestroyAuthAndConnector();
374 void CloudPrintProxyBackend::Core::DoUnregisterPrinters() {
375 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
377 std::string access_token = GetTokenStore()->token();
379 std::list<std::string> printer_ids;
380 connector_->GetPrinterIds(&printer_ids);
382 backend_->frontend_loop_->PostTask(
383 FROM_HERE,
384 base::Bind(&Core::NotifyUnregisterPrinters,
385 this, access_token, printer_ids));
388 void CloudPrintProxyBackend::Core::HandlePrinterNotification(
389 const std::string& notification) {
390 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
392 size_t pos = notification.rfind(kNotificationUpdateSettings);
393 if (pos == std::string::npos) {
394 VLOG(1) << "CP_CONNECTOR: Handle printer notification, id: "
395 << notification;
396 connector_->CheckForJobs(kJobFetchReasonNotified, notification);
397 } else {
398 DCHECK(pos == notification.length() - strlen(kNotificationUpdateSettings));
399 std::string printer_id = notification.substr(0, pos);
400 VLOG(1) << "CP_CONNECTOR: Update printer settings, id: " << printer_id;
401 connector_->UpdatePrinterSettings(printer_id);
405 void CloudPrintProxyBackend::Core::PollForJobs() {
406 VLOG(1) << "CP_CONNECTOR: Polling for jobs.";
407 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
408 // Check all printers for jobs.
409 connector_->CheckForJobs(kJobFetchReasonPoll, std::string());
411 job_poll_scheduled_ = false;
412 // If we don't have notifications and job polling is enabled, poll again
413 // after a while.
414 if (!notifications_enabled_ && enable_job_poll_)
415 ScheduleJobPoll();
418 void CloudPrintProxyBackend::Core::ScheduleJobPoll() {
419 if (!job_poll_scheduled_) {
420 base::TimeDelta interval = base::TimeDelta::FromSeconds(
421 base::RandInt(kMinJobPollIntervalSecs, kMaxJobPollIntervalSecs));
422 base::MessageLoop::current()->PostDelayedTask(
423 FROM_HERE,
424 base::Bind(&CloudPrintProxyBackend::Core::PollForJobs, this),
425 interval);
426 job_poll_scheduled_ = true;
430 void CloudPrintProxyBackend::Core::PingXmppServer() {
431 xmpp_ping_scheduled_ = false;
433 if (!push_client_.get())
434 return;
436 push_client_->SendPing();
438 pending_xmpp_pings_++;
439 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) {
440 // Check ping status when we close to the limit.
441 base::MessageLoop::current()->PostDelayedTask(
442 FROM_HERE,
443 base::Bind(&CloudPrintProxyBackend::Core::CheckXmppPingStatus, this),
444 base::TimeDelta::FromSeconds(kXmppPingCheckIntervalSecs));
447 // Schedule next ping if needed.
448 if (notifications_enabled_)
449 ScheduleXmppPing();
452 void CloudPrintProxyBackend::Core::ScheduleXmppPing() {
453 // settings_.xmpp_ping_enabled() is obsolete, we are now control
454 // XMPP pings from Cloud Print server.
455 if (!xmpp_ping_scheduled_) {
456 base::TimeDelta interval = base::TimeDelta::FromSeconds(
457 base::RandInt(settings_.xmpp_ping_timeout_sec() * 0.9,
458 settings_.xmpp_ping_timeout_sec() * 1.1));
459 base::MessageLoop::current()->PostDelayedTask(
460 FROM_HERE,
461 base::Bind(&CloudPrintProxyBackend::Core::PingXmppServer, this),
462 interval);
463 xmpp_ping_scheduled_ = true;
467 void CloudPrintProxyBackend::Core::CheckXmppPingStatus() {
468 if (pending_xmpp_pings_ >= kMaxFailedXmppPings) {
469 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", 99); // Max on fail.
470 // Reconnect to XMPP.
471 pending_xmpp_pings_ = 0;
472 push_client_.reset();
473 InitNotifications(robot_email_, GetTokenStore()->token());
477 CloudPrintTokenStore* CloudPrintProxyBackend::Core::GetTokenStore() {
478 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
479 if (!token_store_.get())
480 token_store_.reset(new CloudPrintTokenStore);
481 return token_store_.get();
484 void CloudPrintProxyBackend::Core::NotifyAuthenticated(
485 const std::string& robot_oauth_refresh_token,
486 const std::string& robot_email,
487 const std::string& user_email) {
488 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
489 backend_->frontend_
490 ->OnAuthenticated(robot_oauth_refresh_token, robot_email, user_email);
493 void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() {
494 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
495 backend_->frontend_->OnAuthenticationFailed();
498 void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() {
499 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
500 backend_->frontend_->OnPrintSystemUnavailable();
503 void CloudPrintProxyBackend::Core::NotifyUnregisterPrinters(
504 const std::string& auth_token,
505 const std::list<std::string>& printer_ids) {
506 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
507 backend_->frontend_->OnUnregisterPrinters(auth_token, printer_ids);
510 void CloudPrintProxyBackend::Core::NotifyXmppPingUpdated(int ping_timeout) {
511 DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
512 backend_->frontend_->OnXmppPingUpdated(ping_timeout);
515 void CloudPrintProxyBackend::Core::OnNotificationsEnabled() {
516 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
517 notifications_enabled_ = true;
518 notifications_enabled_since_ = base::TimeTicks::Now();
519 VLOG(1) << "Notifications for connector " << settings_.proxy_id()
520 << " were enabled at "
521 << notifications_enabled_since_.ToInternalValue();
522 // Notifications just got re-enabled. In this case we want to schedule
523 // a poll once for jobs we might have missed when we were dark.
524 // Note that ScheduleJobPoll will not schedule again if a job poll task is
525 // already scheduled.
526 ScheduleJobPoll();
528 // Schedule periodic ping for XMPP notification channel.
529 ScheduleXmppPing();
532 void CloudPrintProxyBackend::Core::OnNotificationsDisabled(
533 notifier::NotificationsDisabledReason reason) {
534 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
535 notifications_enabled_ = false;
536 LOG(ERROR) << "Notifications for connector " << settings_.proxy_id()
537 << " disabled.";
538 notifications_enabled_since_ = base::TimeTicks();
539 // We just lost notifications. This this case we want to schedule a
540 // job poll if enable_job_poll_ is true.
541 if (enable_job_poll_)
542 ScheduleJobPoll();
546 void CloudPrintProxyBackend::Core::OnIncomingNotification(
547 const notifier::Notification& notification) {
548 // Since we got some notification from the server,
549 // reset pending ping counter to 0.
550 pending_xmpp_pings_ = 0;
552 DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
553 VLOG(1) << "CP_CONNECTOR: Incoming notification.";
554 if (0 == base::strcasecmp(kCloudPrintPushNotificationsSource,
555 notification.channel.c_str()))
556 HandlePrinterNotification(notification.data);
559 void CloudPrintProxyBackend::Core::OnPingResponse() {
560 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", pending_xmpp_pings_);
561 pending_xmpp_pings_ = 0;
562 VLOG(1) << "CP_CONNECTOR: Ping response received.";
565 } // namespace cloud_print