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"
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/values.h"
16 #include "chrome/common/chrome_switches.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.h"
24 #include "chrome/service/service_process.h"
25 #include "google_apis/gaia/gaia_oauth_client.h"
26 #include "google_apis/gaia/gaia_urls.h"
27 #include "grit/generated_resources.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 "ui/base/l10n/l10n_util.h"
34 namespace cloud_print
{
36 // The real guts of CloudPrintProxyBackend, to keep the public client API clean.
37 class CloudPrintProxyBackend::Core
38 : public base::RefCountedThreadSafe
<CloudPrintProxyBackend::Core
>,
39 public CloudPrintAuth::Client
,
40 public CloudPrintConnector::Client
,
41 public notifier::PushClientObserver
{
43 // It is OK for print_server_url to be empty. In this case system should
44 // use system default (local) print server.
45 Core(CloudPrintProxyBackend
* backend
,
46 const ConnectorSettings
& settings
,
47 const gaia::OAuthClientInfo
& oauth_client_info
,
48 bool enable_job_poll
);
52 // The Do* methods are the various entry points from CloudPrintProxyBackend
53 // It calls us on a dedicated thread to actually perform synchronous
54 // (and potentially blocking) operations.
55 void DoInitializeWithToken(const std::string
& cloud_print_token
);
56 void DoInitializeWithRobotToken(const std::string
& robot_oauth_refresh_token
,
57 const std::string
& robot_email
);
58 void DoInitializeWithRobotAuthCode(const std::string
& robot_oauth_auth_code
,
59 const std::string
& robot_email
);
61 // Called on the CloudPrintProxyBackend core_thread_ to perform
64 void DoRegisterSelectedPrinters(
65 const printing::PrinterList
& printer_list
);
66 void DoUnregisterPrinters();
68 // CloudPrintAuth::Client implementation.
69 virtual void OnAuthenticationComplete(
70 const std::string
& access_token
,
71 const std::string
& robot_oauth_refresh_token
,
72 const std::string
& robot_email
,
73 const std::string
& user_email
) OVERRIDE
;
74 virtual void OnInvalidCredentials() OVERRIDE
;
76 // CloudPrintConnector::Client implementation.
77 virtual void OnAuthFailed() OVERRIDE
;
78 virtual void OnXmppPingUpdated(int ping_timeout
) OVERRIDE
;
80 // notifier::PushClientObserver implementation.
81 virtual void OnNotificationsEnabled() OVERRIDE
;
82 virtual void OnNotificationsDisabled(
83 notifier::NotificationsDisabledReason reason
) OVERRIDE
;
84 virtual void OnIncomingNotification(
85 const notifier::Notification
& notification
) OVERRIDE
;
86 virtual void OnPingResponse() OVERRIDE
;
89 friend class base::RefCountedThreadSafe
<Core
>;
93 void CreateAuthAndConnector();
94 void DestroyAuthAndConnector();
96 // NotifyXXX is how the Core communicates with the frontend across
98 void NotifyPrinterListAvailable(
99 const printing::PrinterList
& printer_list
);
100 void NotifyAuthenticated(
101 const std::string
& robot_oauth_refresh_token
,
102 const std::string
& robot_email
,
103 const std::string
& user_email
);
104 void NotifyAuthenticationFailed();
105 void NotifyPrintSystemUnavailable();
106 void NotifyUnregisterPrinters(const std::string
& auth_token
,
107 const std::list
<std::string
>& printer_ids
);
108 void NotifyXmppPingUpdated(int ping_timeout
);
111 void InitNotifications(const std::string
& robot_email
,
112 const std::string
& access_token
);
114 void HandlePrinterNotification(const std::string
& notification
);
116 // Schedules a task to poll for jobs. Does nothing if a task is already
118 void ScheduleJobPoll();
119 void PingXmppServer();
120 void ScheduleXmppPing();
121 void CheckXmppPingStatus();
123 CloudPrintTokenStore
* GetTokenStore();
125 // Our parent CloudPrintProxyBackend
126 CloudPrintProxyBackend
* backend_
;
128 // Cloud Print authenticator.
129 scoped_refptr
<CloudPrintAuth
> auth_
;
131 // Cloud Print connector.
132 scoped_refptr
<CloudPrintConnector
> connector_
;
134 // OAuth client info.
135 gaia::OAuthClientInfo oauth_client_info_
;
136 // Notification (xmpp) handler.
137 scoped_ptr
<notifier::PushClient
> push_client_
;
138 // Indicates whether XMPP notifications are currently enabled.
139 bool notifications_enabled_
;
140 // The time when notifications were enabled. Valid only when
141 // notifications_enabled_ is true.
142 base::TimeTicks notifications_enabled_since_
;
143 // Indicates whether a task to poll for jobs has been scheduled.
144 bool job_poll_scheduled_
;
145 // Indicates whether we should poll for jobs when we lose XMPP connection.
146 bool enable_job_poll_
;
147 // Indicates whether a task to ping xmpp server has been scheduled.
148 bool xmpp_ping_scheduled_
;
149 // Number of XMPP pings pending reply from the server.
150 int pending_xmpp_pings_
;
151 // Connector settings.
152 ConnectorSettings settings_
;
153 std::string robot_email_
;
154 scoped_ptr
<CloudPrintTokenStore
> token_store_
;
156 DISALLOW_COPY_AND_ASSIGN(Core
);
159 CloudPrintProxyBackend::CloudPrintProxyBackend(
160 CloudPrintProxyFrontend
* frontend
,
161 const ConnectorSettings
& settings
,
162 const gaia::OAuthClientInfo
& oauth_client_info
,
163 bool enable_job_poll
)
164 : core_thread_("Chrome_CloudPrintProxyCoreThread"),
165 frontend_loop_(base::MessageLoop::current()),
166 frontend_(frontend
) {
168 core_
= new Core(this, settings
, oauth_client_info
, enable_job_poll
);
171 CloudPrintProxyBackend::~CloudPrintProxyBackend() { DCHECK(!core_
.get()); }
173 bool CloudPrintProxyBackend::InitializeWithToken(
174 const std::string
& cloud_print_token
) {
175 if (!core_thread_
.Start())
177 core_thread_
.message_loop()->PostTask(
179 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithToken
,
180 core_
.get(), cloud_print_token
));
184 bool CloudPrintProxyBackend::InitializeWithRobotToken(
185 const std::string
& robot_oauth_refresh_token
,
186 const std::string
& robot_email
) {
187 if (!core_thread_
.Start())
189 core_thread_
.message_loop()->PostTask(
191 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken
,
192 core_
.get(), robot_oauth_refresh_token
, robot_email
));
196 bool CloudPrintProxyBackend::InitializeWithRobotAuthCode(
197 const std::string
& robot_oauth_auth_code
,
198 const std::string
& robot_email
) {
199 if (!core_thread_
.Start())
201 core_thread_
.message_loop()->PostTask(
203 base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode
,
204 core_
.get(), robot_oauth_auth_code
, robot_email
));
208 void CloudPrintProxyBackend::Shutdown() {
209 core_thread_
.message_loop()->PostTask(
211 base::Bind(&CloudPrintProxyBackend::Core::DoShutdown
, core_
.get()));
213 core_
= NULL
; // Releases reference to core_.
216 void CloudPrintProxyBackend::UnregisterPrinters() {
217 core_thread_
.message_loop()->PostTask(
219 base::Bind(&CloudPrintProxyBackend::Core::DoUnregisterPrinters
,
223 CloudPrintProxyBackend::Core::Core(
224 CloudPrintProxyBackend
* backend
,
225 const ConnectorSettings
& settings
,
226 const gaia::OAuthClientInfo
& oauth_client_info
,
227 bool enable_job_poll
)
229 oauth_client_info_(oauth_client_info
),
230 notifications_enabled_(false),
231 job_poll_scheduled_(false),
232 enable_job_poll_(enable_job_poll
),
233 xmpp_ping_scheduled_(false),
234 pending_xmpp_pings_(0) {
235 settings_
.CopyFrom(settings
);
238 void CloudPrintProxyBackend::Core::CreateAuthAndConnector() {
240 auth_
= new CloudPrintAuth(this, settings_
.server_url(), oauth_client_info_
,
241 settings_
.proxy_id());
244 if (!connector_
.get()) {
245 connector_
= new CloudPrintConnector(this, settings_
);
249 void CloudPrintProxyBackend::Core::DestroyAuthAndConnector() {
254 void CloudPrintProxyBackend::Core::DoInitializeWithToken(
255 const std::string
& cloud_print_token
) {
256 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
257 CreateAuthAndConnector();
258 auth_
->AuthenticateWithToken(cloud_print_token
);
261 void CloudPrintProxyBackend::Core::DoInitializeWithRobotToken(
262 const std::string
& robot_oauth_refresh_token
,
263 const std::string
& robot_email
) {
264 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
265 CreateAuthAndConnector();
266 auth_
->AuthenticateWithRobotToken(robot_oauth_refresh_token
, robot_email
);
269 void CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode(
270 const std::string
& robot_oauth_auth_code
,
271 const std::string
& robot_email
) {
272 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
273 CreateAuthAndConnector();
274 auth_
->AuthenticateWithRobotAuthCode(robot_oauth_auth_code
, robot_email
);
277 void CloudPrintProxyBackend::Core::OnAuthenticationComplete(
278 const std::string
& access_token
,
279 const std::string
& robot_oauth_refresh_token
,
280 const std::string
& robot_email
,
281 const std::string
& user_email
) {
282 CloudPrintTokenStore
* token_store
= GetTokenStore();
283 bool first_time
= token_store
->token().empty();
284 token_store
->SetToken(access_token
);
285 robot_email_
= robot_email
;
286 // Let the frontend know that we have authenticated.
287 backend_
->frontend_loop_
->PostTask(
289 base::Bind(&Core::NotifyAuthenticated
, this, robot_oauth_refresh_token
,
290 robot_email
, user_email
));
292 InitNotifications(robot_email
, access_token
);
294 // If we are refreshing a token, update the XMPP token too.
295 DCHECK(push_client_
.get());
296 push_client_
->UpdateCredentials(robot_email
, access_token
);
298 // Start cloud print connector if needed.
299 if (!connector_
->IsRunning()) {
300 if (!connector_
->Start()) {
301 // Let the frontend know that we do not have a print system.
302 backend_
->frontend_loop_
->PostTask(
303 FROM_HERE
, base::Bind(&Core::NotifyPrintSystemUnavailable
, this));
308 void CloudPrintProxyBackend::Core::OnInvalidCredentials() {
309 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
310 VLOG(1) << "CP_CONNECTOR: Auth Error";
311 backend_
->frontend_loop_
->PostTask(
312 FROM_HERE
, base::Bind(&Core::NotifyAuthenticationFailed
, this));
315 void CloudPrintProxyBackend::Core::OnAuthFailed() {
316 VLOG(1) << "CP_CONNECTOR: Authentication failed in connector.";
317 // Let's stop connecter and refresh token. We'll restart connecter once
318 // new token available.
319 if (connector_
->IsRunning())
322 // Refresh Auth token.
323 auth_
->RefreshAccessToken();
326 void CloudPrintProxyBackend::Core::OnXmppPingUpdated(int ping_timeout
) {
327 settings_
.SetXmppPingTimeoutSec(ping_timeout
);
328 backend_
->frontend_loop_
->PostTask(
330 base::Bind(&Core::NotifyXmppPingUpdated
, this, ping_timeout
));
333 void CloudPrintProxyBackend::Core::InitNotifications(
334 const std::string
& robot_email
,
335 const std::string
& access_token
) {
336 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
338 pending_xmpp_pings_
= 0;
339 notifier::NotifierOptions notifier_options
;
340 notifier_options
.request_context_getter
=
341 g_service_process
->GetServiceURLRequestContextGetter();
342 notifier_options
.auth_mechanism
= "X-OAUTH2";
343 notifier_options
.try_ssltcp_first
= true;
344 push_client_
= notifier::PushClient::CreateDefault(notifier_options
);
345 push_client_
->AddObserver(this);
346 notifier::Subscription subscription
;
347 subscription
.channel
= kCloudPrintPushNotificationsSource
;
348 subscription
.from
= kCloudPrintPushNotificationsSource
;
349 push_client_
->UpdateSubscriptions(
350 notifier::SubscriptionList(1, subscription
));
351 push_client_
->UpdateCredentials(robot_email
, access_token
);
354 void CloudPrintProxyBackend::Core::DoShutdown() {
355 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
356 VLOG(1) << "CP_CONNECTOR: Shutdown connector, id: " << settings_
.proxy_id();
358 if (connector_
->IsRunning())
361 // Important to delete the PushClient on this thread.
362 if (push_client_
.get()) {
363 push_client_
->RemoveObserver(this);
365 push_client_
.reset();
366 notifications_enabled_
= false;
367 notifications_enabled_since_
= base::TimeTicks();
368 token_store_
.reset();
370 DestroyAuthAndConnector();
373 void CloudPrintProxyBackend::Core::DoUnregisterPrinters() {
374 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
376 std::string access_token
= GetTokenStore()->token();
378 std::list
<std::string
> printer_ids
;
379 connector_
->GetPrinterIds(&printer_ids
);
381 backend_
->frontend_loop_
->PostTask(
383 base::Bind(&Core::NotifyUnregisterPrinters
,
384 this, access_token
, printer_ids
));
387 void CloudPrintProxyBackend::Core::HandlePrinterNotification(
388 const std::string
& notification
) {
389 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
391 size_t pos
= notification
.rfind(kNotificationUpdateSettings
);
392 if (pos
== std::string::npos
) {
393 VLOG(1) << "CP_CONNECTOR: Handle printer notification, id: "
395 connector_
->CheckForJobs(kJobFetchReasonNotified
, notification
);
397 DCHECK(pos
== notification
.length() - strlen(kNotificationUpdateSettings
));
398 std::string printer_id
= notification
.substr(0, pos
);
399 VLOG(1) << "CP_CONNECTOR: Update printer settings, id: " << printer_id
;
400 connector_
->UpdatePrinterSettings(printer_id
);
404 void CloudPrintProxyBackend::Core::PollForJobs() {
405 VLOG(1) << "CP_CONNECTOR: Polling for jobs.";
406 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
407 // Check all printers for jobs.
408 connector_
->CheckForJobs(kJobFetchReasonPoll
, std::string());
410 job_poll_scheduled_
= false;
411 // If we don't have notifications and job polling is enabled, poll again
413 if (!notifications_enabled_
&& enable_job_poll_
)
417 void CloudPrintProxyBackend::Core::ScheduleJobPoll() {
418 if (!job_poll_scheduled_
) {
419 base::TimeDelta interval
= base::TimeDelta::FromSeconds(
420 base::RandInt(kMinJobPollIntervalSecs
, kMaxJobPollIntervalSecs
));
421 base::MessageLoop::current()->PostDelayedTask(
423 base::Bind(&CloudPrintProxyBackend::Core::PollForJobs
, this),
425 job_poll_scheduled_
= true;
429 void CloudPrintProxyBackend::Core::PingXmppServer() {
430 xmpp_ping_scheduled_
= false;
432 if (!push_client_
.get())
435 push_client_
->SendPing();
437 pending_xmpp_pings_
++;
438 if (pending_xmpp_pings_
>= kMaxFailedXmppPings
) {
439 // Check ping status when we close to the limit.
440 base::MessageLoop::current()->PostDelayedTask(
442 base::Bind(&CloudPrintProxyBackend::Core::CheckXmppPingStatus
, this),
443 base::TimeDelta::FromSeconds(kXmppPingCheckIntervalSecs
));
446 // Schedule next ping if needed.
447 if (notifications_enabled_
)
451 void CloudPrintProxyBackend::Core::ScheduleXmppPing() {
452 // settings_.xmpp_ping_enabled() is obsolete, we are now control
453 // XMPP pings from Cloud Print server.
454 if (!xmpp_ping_scheduled_
) {
455 base::TimeDelta interval
= base::TimeDelta::FromSeconds(
456 base::RandInt(settings_
.xmpp_ping_timeout_sec() * 0.9,
457 settings_
.xmpp_ping_timeout_sec() * 1.1));
458 base::MessageLoop::current()->PostDelayedTask(
460 base::Bind(&CloudPrintProxyBackend::Core::PingXmppServer
, this),
462 xmpp_ping_scheduled_
= true;
466 void CloudPrintProxyBackend::Core::CheckXmppPingStatus() {
467 if (pending_xmpp_pings_
>= kMaxFailedXmppPings
) {
468 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", 99); // Max on fail.
469 // Reconnect to XMPP.
470 pending_xmpp_pings_
= 0;
471 push_client_
.reset();
472 InitNotifications(robot_email_
, GetTokenStore()->token());
476 CloudPrintTokenStore
* CloudPrintProxyBackend::Core::GetTokenStore() {
477 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
478 if (!token_store_
.get())
479 token_store_
.reset(new CloudPrintTokenStore
);
480 return token_store_
.get();
483 void CloudPrintProxyBackend::Core::NotifyAuthenticated(
484 const std::string
& robot_oauth_refresh_token
,
485 const std::string
& robot_email
,
486 const std::string
& user_email
) {
487 DCHECK(base::MessageLoop::current() == backend_
->frontend_loop_
);
489 ->OnAuthenticated(robot_oauth_refresh_token
, robot_email
, user_email
);
492 void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() {
493 DCHECK(base::MessageLoop::current() == backend_
->frontend_loop_
);
494 backend_
->frontend_
->OnAuthenticationFailed();
497 void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() {
498 DCHECK(base::MessageLoop::current() == backend_
->frontend_loop_
);
499 backend_
->frontend_
->OnPrintSystemUnavailable();
502 void CloudPrintProxyBackend::Core::NotifyUnregisterPrinters(
503 const std::string
& auth_token
,
504 const std::list
<std::string
>& printer_ids
) {
505 DCHECK(base::MessageLoop::current() == backend_
->frontend_loop_
);
506 backend_
->frontend_
->OnUnregisterPrinters(auth_token
, printer_ids
);
509 void CloudPrintProxyBackend::Core::NotifyXmppPingUpdated(int ping_timeout
) {
510 DCHECK(base::MessageLoop::current() == backend_
->frontend_loop_
);
511 backend_
->frontend_
->OnXmppPingUpdated(ping_timeout
);
514 void CloudPrintProxyBackend::Core::OnNotificationsEnabled() {
515 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
516 notifications_enabled_
= true;
517 notifications_enabled_since_
= base::TimeTicks::Now();
518 VLOG(1) << "Notifications for connector " << settings_
.proxy_id()
519 << " were enabled at "
520 << notifications_enabled_since_
.ToInternalValue();
521 // Notifications just got re-enabled. In this case we want to schedule
522 // a poll once for jobs we might have missed when we were dark.
523 // Note that ScheduleJobPoll will not schedule again if a job poll task is
524 // already scheduled.
527 // Schedule periodic ping for XMPP notification channel.
531 void CloudPrintProxyBackend::Core::OnNotificationsDisabled(
532 notifier::NotificationsDisabledReason reason
) {
533 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
534 notifications_enabled_
= false;
535 LOG(ERROR
) << "Notifications for connector " << settings_
.proxy_id()
537 notifications_enabled_since_
= base::TimeTicks();
538 // We just lost notifications. This this case we want to schedule a
539 // job poll if enable_job_poll_ is true.
540 if (enable_job_poll_
)
545 void CloudPrintProxyBackend::Core::OnIncomingNotification(
546 const notifier::Notification
& notification
) {
547 // Since we got some notification from the server,
548 // reset pending ping counter to 0.
549 pending_xmpp_pings_
= 0;
551 DCHECK(base::MessageLoop::current() == backend_
->core_thread_
.message_loop());
552 VLOG(1) << "CP_CONNECTOR: Incoming notification.";
553 if (0 == base::strcasecmp(kCloudPrintPushNotificationsSource
,
554 notification
.channel
.c_str()))
555 HandlePrinterNotification(notification
.data
);
558 void CloudPrintProxyBackend::Core::OnPingResponse() {
559 UMA_HISTOGRAM_COUNTS_100("CloudPrint.XmppPingTry", pending_xmpp_pings_
);
560 pending_xmpp_pings_
= 0;
561 VLOG(1) << "CP_CONNECTOR: Ping response received.";
564 } // namespace cloud_print