- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / service / cloud_print / cloud_print_proxy_backend.cc
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/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_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"
32 #include "url/gurl.h"
33
34 namespace cloud_print {
35
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 {
42  public:
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);
49
50   // Note:
51   //
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);
60
61   // Called on the CloudPrintProxyBackend core_thread_ to perform
62   // shutdown.
63   void DoShutdown();
64   void DoRegisterSelectedPrinters(
65       const printing::PrinterList& printer_list);
66   void DoUnregisterPrinters();
67
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;
75
76   // CloudPrintConnector::Client implementation.
77   virtual void OnAuthFailed() OVERRIDE;
78   virtual void OnXmppPingUpdated(int ping_timeout) OVERRIDE;
79
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;
87
88  private:
89   friend class base::RefCountedThreadSafe<Core>;
90
91   virtual ~Core() {}
92
93   void CreateAuthAndConnector();
94   void DestroyAuthAndConnector();
95
96   // NotifyXXX is how the Core communicates with the frontend across
97   // threads.
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);
109
110   // Init XMPP channel
111   void InitNotifications(const std::string& robot_email,
112                          const std::string& access_token);
113
114   void HandlePrinterNotification(const std::string& notification);
115   void PollForJobs();
116   // Schedules a task to poll for jobs. Does nothing if a task is already
117   // scheduled.
118   void ScheduleJobPoll();
119   void PingXmppServer();
120   void ScheduleXmppPing();
121   void CheckXmppPingStatus();
122
123   CloudPrintTokenStore* GetTokenStore();
124
125   // Our parent CloudPrintProxyBackend
126   CloudPrintProxyBackend* backend_;
127
128   // Cloud Print authenticator.
129   scoped_refptr<CloudPrintAuth> auth_;
130
131   // Cloud Print connector.
132   scoped_refptr<CloudPrintConnector> connector_;
133
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_;
155
156   DISALLOW_COPY_AND_ASSIGN(Core);
157 };
158
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) {
167   DCHECK(frontend_);
168   core_ = new Core(this, settings, oauth_client_info, enable_job_poll);
169 }
170
171 CloudPrintProxyBackend::~CloudPrintProxyBackend() { DCHECK(!core_.get()); }
172
173 bool CloudPrintProxyBackend::InitializeWithToken(
174     const std::string& cloud_print_token) {
175   if (!core_thread_.Start())
176     return false;
177   core_thread_.message_loop()->PostTask(
178       FROM_HERE,
179       base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithToken,
180                  core_.get(), cloud_print_token));
181   return true;
182 }
183
184 bool CloudPrintProxyBackend::InitializeWithRobotToken(
185     const std::string& robot_oauth_refresh_token,
186     const std::string& robot_email) {
187   if (!core_thread_.Start())
188     return false;
189   core_thread_.message_loop()->PostTask(
190       FROM_HERE,
191       base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken,
192                  core_.get(), robot_oauth_refresh_token, robot_email));
193   return true;
194 }
195
196 bool CloudPrintProxyBackend::InitializeWithRobotAuthCode(
197     const std::string& robot_oauth_auth_code,
198     const std::string& robot_email) {
199   if (!core_thread_.Start())
200     return false;
201   core_thread_.message_loop()->PostTask(
202       FROM_HERE,
203       base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode,
204                  core_.get(), robot_oauth_auth_code, robot_email));
205   return true;
206 }
207
208 void CloudPrintProxyBackend::Shutdown() {
209   core_thread_.message_loop()->PostTask(
210       FROM_HERE,
211       base::Bind(&CloudPrintProxyBackend::Core::DoShutdown, core_.get()));
212   core_thread_.Stop();
213   core_ = NULL;  // Releases reference to core_.
214 }
215
216 void CloudPrintProxyBackend::UnregisterPrinters() {
217   core_thread_.message_loop()->PostTask(
218       FROM_HERE,
219       base::Bind(&CloudPrintProxyBackend::Core::DoUnregisterPrinters,
220                  core_.get()));
221 }
222
223 CloudPrintProxyBackend::Core::Core(
224     CloudPrintProxyBackend* backend,
225     const ConnectorSettings& settings,
226     const gaia::OAuthClientInfo& oauth_client_info,
227     bool enable_job_poll)
228       : backend_(backend),
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);
236 }
237
238 void CloudPrintProxyBackend::Core::CreateAuthAndConnector() {
239   if (!auth_.get()) {
240     auth_ = new CloudPrintAuth(this, settings_.server_url(), oauth_client_info_,
241                                settings_.proxy_id());
242   }
243
244   if (!connector_.get()) {
245     connector_ = new CloudPrintConnector(this, settings_);
246   }
247 }
248
249 void CloudPrintProxyBackend::Core::DestroyAuthAndConnector() {
250   auth_ = NULL;
251   connector_ = NULL;
252 }
253
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);
259 }
260
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);
267 }
268
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);
275 }
276
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(
288       FROM_HERE,
289       base::Bind(&Core::NotifyAuthenticated, this, robot_oauth_refresh_token,
290                  robot_email, user_email));
291   if (first_time) {
292     InitNotifications(robot_email, access_token);
293   } else {
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);
297   }
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));
304     }
305   }
306 }
307
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));
313 }
314
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())
320     connector_->Stop();
321
322   // Refresh Auth token.
323   auth_->RefreshAccessToken();
324 }
325
326 void CloudPrintProxyBackend::Core::OnXmppPingUpdated(int ping_timeout) {
327   settings_.SetXmppPingTimeoutSec(ping_timeout);
328   backend_->frontend_loop_->PostTask(
329       FROM_HERE,
330       base::Bind(&Core::NotifyXmppPingUpdated, this, ping_timeout));
331 }
332
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());
337
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);
352 }
353
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();
357
358   if (connector_->IsRunning())
359     connector_->Stop();
360
361   // Important to delete the PushClient on this thread.
362   if (push_client_.get()) {
363     push_client_->RemoveObserver(this);
364   }
365   push_client_.reset();
366   notifications_enabled_ = false;
367   notifications_enabled_since_ = base::TimeTicks();
368   token_store_.reset();
369
370   DestroyAuthAndConnector();
371 }
372
373 void CloudPrintProxyBackend::Core::DoUnregisterPrinters() {
374   DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
375
376   std::string access_token = GetTokenStore()->token();
377
378   std::list<std::string> printer_ids;
379   connector_->GetPrinterIds(&printer_ids);
380
381   backend_->frontend_loop_->PostTask(
382       FROM_HERE,
383       base::Bind(&Core::NotifyUnregisterPrinters,
384                  this, access_token, printer_ids));
385 }
386
387 void CloudPrintProxyBackend::Core::HandlePrinterNotification(
388     const std::string& notification) {
389   DCHECK(base::MessageLoop::current() == backend_->core_thread_.message_loop());
390
391   size_t pos = notification.rfind(kNotificationUpdateSettings);
392   if (pos == std::string::npos) {
393     VLOG(1) << "CP_CONNECTOR: Handle printer notification, id: "
394             << notification;
395     connector_->CheckForJobs(kJobFetchReasonNotified, notification);
396   } else {
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);
401   }
402 }
403
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());
409
410   job_poll_scheduled_ = false;
411   // If we don't have notifications and job polling is enabled, poll again
412   // after a while.
413   if (!notifications_enabled_ && enable_job_poll_)
414     ScheduleJobPoll();
415 }
416
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(
422         FROM_HERE,
423         base::Bind(&CloudPrintProxyBackend::Core::PollForJobs, this),
424         interval);
425     job_poll_scheduled_ = true;
426   }
427 }
428
429 void CloudPrintProxyBackend::Core::PingXmppServer() {
430   xmpp_ping_scheduled_ = false;
431
432   if (!push_client_.get())
433     return;
434
435   push_client_->SendPing();
436
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(
441         FROM_HERE,
442         base::Bind(&CloudPrintProxyBackend::Core::CheckXmppPingStatus, this),
443         base::TimeDelta::FromSeconds(kXmppPingCheckIntervalSecs));
444   }
445
446   // Schedule next ping if needed.
447   if (notifications_enabled_)
448     ScheduleXmppPing();
449 }
450
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(
459         FROM_HERE,
460         base::Bind(&CloudPrintProxyBackend::Core::PingXmppServer, this),
461         interval);
462     xmpp_ping_scheduled_ = true;
463   }
464 }
465
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());
473   }
474 }
475
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();
481 }
482
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_);
488   backend_->frontend_
489       ->OnAuthenticated(robot_oauth_refresh_token, robot_email, user_email);
490 }
491
492 void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() {
493   DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
494   backend_->frontend_->OnAuthenticationFailed();
495 }
496
497 void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() {
498   DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
499   backend_->frontend_->OnPrintSystemUnavailable();
500 }
501
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);
507 }
508
509 void CloudPrintProxyBackend::Core::NotifyXmppPingUpdated(int ping_timeout) {
510   DCHECK(base::MessageLoop::current() == backend_->frontend_loop_);
511   backend_->frontend_->OnXmppPingUpdated(ping_timeout);
512 }
513
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.
525   ScheduleJobPoll();
526
527   // Schedule periodic ping for XMPP notification channel.
528   ScheduleXmppPing();
529 }
530
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()
536              << " disabled.";
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_)
541     ScheduleJobPoll();
542 }
543
544
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;
550
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);
556 }
557
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.";
562 }
563
564 }  // namespace cloud_print