blob: 5483405309eaa2192e2fedf5a7ed6cafb01cb3f5 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/service/cloud_print/cloud_print_proxy_backend.h"
#include <map>
#include <vector>
#include "base/file_util.h"
#include "base/md5.h"
#include "base/rand_util.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/net/gaia/gaia_oauth_client.h"
#include "chrome/common/net/gaia/gaia_urls.h"
#include "chrome/service/cloud_print/cloud_print_consts.h"
#include "chrome/service/cloud_print/cloud_print_helpers.h"
#include "chrome/service/cloud_print/cloud_print_token_store.h"
#include "chrome/service/cloud_print/cloud_print_url_fetcher.h"
#include "chrome/service/cloud_print/printer_job_handler.h"
#include "chrome/service/gaia/service_gaia_authenticator.h"
#include "chrome/service/net/service_url_request_context.h"
#include "chrome/service/service_process.h"
#include "googleurl/src/gurl.h"
#include "grit/generated_resources.h"
#include "jingle/notifier/base/notifier_options.h"
#include "jingle/notifier/listener/mediator_thread_impl.h"
#include "jingle/notifier/listener/talk_mediator_impl.h"
#include "net/url_request/url_request_status.h"
#include "ui/base/l10n/l10n_util.h"
// The real guts of CloudPrintProxyBackend, to keep the public client API clean.
class CloudPrintProxyBackend::Core
: public base::RefCountedThreadSafe<CloudPrintProxyBackend::Core>,
public CloudPrintURLFetcherDelegate,
public cloud_print::PrintServerWatcherDelegate,
public PrinterJobHandlerDelegate,
public notifier::TalkMediator::Delegate,
public gaia::GaiaOAuthClient::Delegate {
public:
// It is OK for print_server_url to be empty. In this case system should
// use system default (local) print server.
Core(CloudPrintProxyBackend* backend,
const GURL& cloud_print_server_url,
const DictionaryValue* print_system_settings,
const gaia::OAuthClientInfo& oauth_client_info,
bool enable_job_poll);
// Note:
//
// The Do* methods are the various entry points from CloudPrintProxyBackend
// It calls us on a dedicated thread to actually perform synchronous
// (and potentially blocking) operations.
//
// Called on the CloudPrintProxyBackend core_thread_ to perform
// initialization. When we are passed in an LSID we authenticate using that
// and retrieve new auth tokens.
void DoInitializeWithLsid(const std::string& lsid,
const std::string& proxy_id,
const std::string& last_robot_refresh_token,
const std::string& last_robot_email,
const std::string& last_user_email);
void DoInitializeWithToken(const std::string cloud_print_token,
const std::string& proxy_id);
void DoInitializeWithRobotToken(const std::string& robot_oauth_refresh_token,
const std::string& robot_email,
const std::string& proxy_id);
void DoInitializeWithRobotAuthCode(const std::string& robot_oauth_auth_code,
const std::string& robot_email,
const std::string& proxy_id);
// Called on the CloudPrintProxyBackend core_thread_ to perform
// shutdown.
void DoShutdown();
void DoRegisterSelectedPrinters(
const printing::PrinterList& printer_list);
// CloudPrintURLFetcher::Delegate implementation.
virtual CloudPrintURLFetcher::ResponseAction HandleJSONData(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
virtual void OnRequestAuthError();
// cloud_print::PrintServerWatcherDelegate implementation
virtual void OnPrinterAdded();
// PrinterJobHandler::Delegate implementation
virtual void OnPrinterJobHandlerShutdown(PrinterJobHandler* job_handler,
const std::string& printer_id);
virtual void OnAuthError();
virtual void OnPrinterNotFound(const std::string& printer_name,
bool* delete_from_server);
// notifier::TalkMediator::Delegate implementation.
virtual void OnNotificationStateChange(
bool notifications_enabled);
virtual void OnIncomingNotification(
const notifier::Notification& notification);
virtual void OnOutgoingNotification();
// gaia::GaiaOAuthClient::Delegate implementation.
virtual void OnGetTokensResponse(const std::string& refresh_token,
const std::string& access_token,
int expires_in_seconds);
virtual void OnRefreshTokenResponse(const std::string& access_token,
int expires_in_seconds);
virtual void OnOAuthError();
virtual void OnNetworkError(int response_code);
private:
// Prototype for a response handler.
typedef CloudPrintURLFetcher::ResponseAction
(CloudPrintProxyBackend::Core::*ResponseHandler)(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
// Begin response handlers
CloudPrintURLFetcher::ResponseAction HandlePrinterListResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
CloudPrintURLFetcher::ResponseAction HandleRegisterPrinterResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
CloudPrintURLFetcher::ResponseAction HandleRegisterFailedStatusResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
CloudPrintURLFetcher::ResponseAction HandlePrintSystemUnavailableResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
CloudPrintURLFetcher::ResponseAction HandleEnumPrintersFailedResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
CloudPrintURLFetcher::ResponseAction HandleGetAuthCodeResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded);
// End response handlers
// NotifyXXX is how the Core communicates with the frontend across
// threads.
void NotifyPrinterListAvailable(
const printing::PrinterList& printer_list);
void NotifyAuthenticated(
const std::string& robot_oauth_refresh_token,
const std::string& robot_email,
const std::string& user_email);
void NotifyAuthenticationFailed();
void NotifyPrintSystemUnavailable();
// Once we have robot credentials, this method gets the ball rolling.
void PostAuthInitialization();
// Starts a new printer registration process.
void StartRegistration();
// Ends the printer registration process.
void EndRegistration();
// Registers printer capabilities and defaults for the next printer in the
// list with the cloud print server.
void RegisterNextPrinter();
// Retrieves the list of registered printers for this user/proxy combination
// from the cloud print server.
void GetRegisteredPrinters();
// Removes the given printer from the list. Returns false if the printer
// did not exist in the list.
bool RemovePrinterFromList(const std::string& printer_name);
// Initializes a job handler object for the specified printer. The job
// handler is responsible for checking for pending print jobs for this
// printer and print them.
void InitJobHandlerForPrinter(DictionaryValue* printer_data);
// Reports a diagnostic message to the server.
void ReportUserMessage(const std::string& message_id,
const std::string& failure_message,
ResponseHandler handler);
// Make a GAIA request to refresh the access token.
void RefreshAccessToken();
// Callback method for GetPrinterCapsAndDefaults.
void OnReceivePrinterCaps(
bool succeeded,
const std::string& printer_name,
const printing::PrinterCapsAndDefaults& caps_and_defaults);
void HandlePrinterNotification(const std::string& printer_id);
void PollForJobs();
// Schedules a task to poll for jobs. Does nothing if a task is already
// scheduled.
void ScheduleJobPoll();
CloudPrintTokenStore* GetTokenStore();
// Our parent CloudPrintProxyBackend
CloudPrintProxyBackend* backend_;
GURL cloud_print_server_url_;
gaia::OAuthClientInfo oauth_client_info_;
scoped_ptr<DictionaryValue> print_system_settings_;
// Pointer to current print system.
scoped_refptr<cloud_print::PrintSystem> print_system_;
// The list of printers to be registered with the cloud print server.
// To begin with,this list is initialized with the list of local and network
// printers available. Then we query the server for the list of printers
// already registered. We trim this list to remove the printers already
// registered. We then pass a copy of this list to the frontend to give the
// user a chance to further trim the list. When the frontend gives us the
// final list we make a copy into this so that we can start registering.
printing::PrinterList printer_list_;
// Indicates whether the printers in printer_list_ is the complete set of
// printers to be registered for this proxy.
bool complete_list_available_;
// The CloudPrintURLFetcher instance for the current request.
scoped_refptr<CloudPrintURLFetcher> request_;
// The index of the nex printer to be uploaded.
size_t next_upload_index_;
// The unique id for this proxy
std::string proxy_id_;
// The OAuth2 refresh token for the robot.
std::string refresh_token_;
// The email address of the user. This is only used during initial
// authentication with an LSID. This is only used for storing in prefs for
// display purposes.
std::string user_email_;
// The email address of the robot account.
std::string robot_email_;
// Cached info about the last printer that we tried to upload. We cache this
// so we won't have to requery the printer if the upload fails and we need
// to retry.
std::string last_uploaded_printer_name_;
printing::PrinterCapsAndDefaults last_uploaded_printer_info_;
// A map of printer id to job handler.
typedef std::map<std::string, scoped_refptr<PrinterJobHandler> >
JobHandlerMap;
JobHandlerMap job_handler_map_;
ResponseHandler next_response_handler_;
scoped_refptr<cloud_print::PrintSystem::PrintServerWatcher>
print_server_watcher_;
bool new_printers_available_;
bool registration_in_progress_;
// Notification (xmpp) handler.
scoped_ptr<notifier::TalkMediator> talk_mediator_;
// Indicates whether XMPP notifications are currently enabled.
bool notifications_enabled_;
// The time when notifications were enabled. Valid only when
// notifications_enabled_ is true.
base::TimeTicks notifications_enabled_since_;
// Indicates whether a task to poll for jobs has been scheduled.
bool job_poll_scheduled_;
// Indicates whether we should poll for jobs when we lose XMPP connection.
bool enable_job_poll_;
scoped_ptr<gaia::GaiaOAuthClient> oauth_client_;
scoped_ptr<CloudPrintTokenStore> token_store_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
CloudPrintProxyBackend::CloudPrintProxyBackend(
CloudPrintProxyFrontend* frontend,
const GURL& cloud_print_server_url,
const DictionaryValue* print_system_settings,
const gaia::OAuthClientInfo& oauth_client_info,
bool enable_job_poll)
: core_thread_("Chrome_CloudPrintProxyCoreThread"),
frontend_loop_(MessageLoop::current()),
frontend_(frontend) {
DCHECK(frontend_);
core_ = new Core(this,
cloud_print_server_url,
print_system_settings,
oauth_client_info,
enable_job_poll);
}
CloudPrintProxyBackend::~CloudPrintProxyBackend() {
DCHECK(!core_);
}
bool CloudPrintProxyBackend::InitializeWithLsid(
const std::string& lsid,
const std::string& proxy_id,
const std::string& last_robot_refresh_token,
const std::string& last_robot_email,
const std::string& last_user_email) {
if (!core_thread_.Start())
return false;
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(
core_.get(),
&CloudPrintProxyBackend::Core::DoInitializeWithLsid,
lsid,
proxy_id,
last_robot_refresh_token,
last_robot_email,
last_user_email));
return true;
}
bool CloudPrintProxyBackend::InitializeWithToken(
const std::string& cloud_print_token,
const std::string& proxy_id) {
if (!core_thread_.Start())
return false;
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(
core_.get(), &CloudPrintProxyBackend::Core::DoInitializeWithToken,
cloud_print_token, proxy_id));
return true;
}
bool CloudPrintProxyBackend::InitializeWithRobotToken(
const std::string& robot_oauth_refresh_token,
const std::string& robot_email,
const std::string& proxy_id) {
if (!core_thread_.Start())
return false;
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(
core_.get(),
&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken,
robot_oauth_refresh_token,
robot_email,
proxy_id));
return true;
}
bool CloudPrintProxyBackend::InitializeWithRobotAuthCode(
const std::string& robot_oauth_auth_code,
const std::string& robot_email,
const std::string& proxy_id) {
if (!core_thread_.Start())
return false;
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(
core_.get(),
&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode,
robot_oauth_auth_code,
robot_email,
proxy_id));
return true;
}
void CloudPrintProxyBackend::Shutdown() {
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(core_.get(),
&CloudPrintProxyBackend::Core::DoShutdown));
core_thread_.Stop();
core_ = NULL; // Releases reference to core_.
}
void CloudPrintProxyBackend::RegisterPrinters(
const printing::PrinterList& printer_list) {
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(
core_.get(),
&CloudPrintProxyBackend::Core::DoRegisterSelectedPrinters,
printer_list));
}
CloudPrintProxyBackend::Core::Core(
CloudPrintProxyBackend* backend,
const GURL& cloud_print_server_url,
const DictionaryValue* print_system_settings,
const gaia::OAuthClientInfo& oauth_client_info,
bool enable_job_poll)
: backend_(backend),
cloud_print_server_url_(cloud_print_server_url),
oauth_client_info_(oauth_client_info),
complete_list_available_(false),
next_upload_index_(0),
next_response_handler_(NULL),
new_printers_available_(false),
registration_in_progress_(false),
notifications_enabled_(false),
job_poll_scheduled_(false),
enable_job_poll_(enable_job_poll) {
if (print_system_settings) {
// It is possible to have no print settings specified.
print_system_settings_.reset(print_system_settings->DeepCopy());
}
}
void CloudPrintProxyBackend::Core::DoInitializeWithLsid(
const std::string& lsid,
const std::string& proxy_id,
const std::string& last_robot_refresh_token,
const std::string& last_robot_email,
const std::string& last_user_email) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
// Note: The GAIA login is synchronous but that should be OK because we are in
// the CloudPrintProxyCoreThread and we cannot really do anything else until
// the GAIA signin is successful.
std::string user_agent = "ChromiumBrowser";
scoped_refptr<ServiceGaiaAuthenticator> gaia_auth_for_print(
new ServiceGaiaAuthenticator(
user_agent, kCloudPrintGaiaServiceId,
GaiaUrls::GetInstance()->client_login_url(),
g_service_process->io_thread()->message_loop_proxy()));
gaia_auth_for_print->set_message_loop(MessageLoop::current());
if (gaia_auth_for_print->AuthenticateWithLsid(lsid)) {
// Stash away the user email so we can save it in prefs.
user_email_ = gaia_auth_for_print->email();
// If the same user is re-enabling Cloud Print and we have stashed robot
// credentials, we will use those.
if ((0 == base::strcasecmp(user_email_.c_str(), last_user_email.c_str())) &&
!last_robot_refresh_token.empty() &&
!last_robot_email.empty()) {
DoInitializeWithRobotToken(last_robot_refresh_token,
last_robot_email,
proxy_id);
}
DoInitializeWithToken(gaia_auth_for_print->auth_token(),
proxy_id);
} else {
// Let the frontend know the of authentication failure.
OnAuthError();
}
}
void CloudPrintProxyBackend::Core::DoInitializeWithToken(
const std::string cloud_print_token,
const std::string& proxy_id) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
VLOG(1) << "CP_PROXY: Starting proxy, id: " << proxy_id;
proxy_id_ = proxy_id;
GetTokenStore()->SetToken(cloud_print_token, false);
// We need to get the credentials of the robot here.
GURL get_authcode_url =
CloudPrintHelpers::GetUrlForGetAuthCode(cloud_print_server_url_,
oauth_client_info_.client_id,
proxy_id_);
next_response_handler_ =
&CloudPrintProxyBackend::Core::HandleGetAuthCodeResponse;
request_ = new CloudPrintURLFetcher;
request_->StartGetRequest(get_authcode_url,
this,
kCloudPrintAPIMaxRetryCount,
std::string());
}
void CloudPrintProxyBackend::Core::DoInitializeWithRobotToken(
const std::string& robot_oauth_refresh_token,
const std::string& robot_email,
const std::string& proxy_id) {
robot_email_ = robot_email;
proxy_id_ = proxy_id;
refresh_token_ = robot_oauth_refresh_token;
RefreshAccessToken();
}
void CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode(
const std::string& robot_oauth_auth_code,
const std::string& robot_email,
const std::string& proxy_id) {
robot_email_ = robot_email;
proxy_id_ = proxy_id;
// Now that we have an auth code we need to get the refresh and access tokens.
oauth_client_.reset(new gaia::GaiaOAuthClient(
gaia::kGaiaOAuth2Url,
g_service_process->GetServiceURLRequestContextGetter()));
oauth_client_->GetTokensFromAuthCode(oauth_client_info_,
robot_oauth_auth_code,
kCloudPrintAPIMaxRetryCount,
this);
}
void CloudPrintProxyBackend::Core::PostAuthInitialization() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
// Now we can get down to registering printers.
print_system_ =
cloud_print::PrintSystem::CreateInstance(print_system_settings_.get());
if (!print_system_.get()) {
NOTREACHED();
return; // No print system available, fail initalization.
}
cloud_print::PrintSystem::PrintSystemResult result = print_system_->Init();
if (result.succeeded()) {
notifier::NotifierOptions notifier_options;
notifier_options.request_context_getter =
g_service_process->GetServiceURLRequestContextGetter();
notifier_options.auth_mechanism = "X-OAUTH2";
talk_mediator_.reset(new notifier::TalkMediatorImpl(
new notifier::MediatorThreadImpl(notifier_options),
notifier_options));
notifier::Subscription subscription;
subscription.channel = kCloudPrintPushNotificationsSource;
subscription.from = kCloudPrintPushNotificationsSource;
talk_mediator_->AddSubscription(subscription);
talk_mediator_->SetDelegate(this);
talk_mediator_->SetAuthToken(
robot_email_,
CloudPrintTokenStore::current()->token(),
kSyncGaiaServiceId);
talk_mediator_->Login();
print_server_watcher_ = print_system_->CreatePrintServerWatcher();
print_server_watcher_->StartWatching(this);
StartRegistration();
} else {
// We could not initialize the print system. We need to notify the server.
ReportUserMessage(
kPrintSystemFailedMessageId,
result.message(),
&CloudPrintProxyBackend::Core::HandlePrintSystemUnavailableResponse);
}
}
void CloudPrintProxyBackend::Core::StartRegistration() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
printer_list_.clear();
cloud_print::PrintSystem::PrintSystemResult result =
print_system_->EnumeratePrinters(&printer_list_);
complete_list_available_ = result.succeeded();
registration_in_progress_ = true;
if (!result.succeeded()) {
std::string message = result.message();
if (message.empty())
message = l10n_util::GetStringUTF8(IDS_CLOUD_PRINT_ENUM_FAILED);
// There was a failure enumerating printers. Send a message to the server.
ReportUserMessage(
kEnumPrintersFailedMessageId,
message,
&CloudPrintProxyBackend::Core::HandleEnumPrintersFailedResponse);
} else {
// Now we need to ask the server about printers that were registered on the
// server so that we can trim this list.
GetRegisteredPrinters();
}
}
void CloudPrintProxyBackend::Core::EndRegistration() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
request_ = NULL;
registration_in_progress_ = false;
if (new_printers_available_) {
new_printers_available_ = false;
StartRegistration();
}
}
void CloudPrintProxyBackend::Core::DoShutdown() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
VLOG(1) << "CP_PROXY: Shutdown proxy, id: " << proxy_id_;
if (print_server_watcher_ != NULL)
print_server_watcher_->StopWatching();
// Need to kill all running jobs.
while (!job_handler_map_.empty()) {
JobHandlerMap::iterator index = job_handler_map_.begin();
// Shutdown will call our OnPrinterJobHandlerShutdown method which will
// remove this from the map.
index->second->Shutdown();
}
// Important to delete the TalkMediator on this thread.
if (talk_mediator_.get())
talk_mediator_->Logout();
talk_mediator_.reset();
notifications_enabled_ = false;
notifications_enabled_since_ = base::TimeTicks();
request_ = NULL;
token_store_.reset();
}
void CloudPrintProxyBackend::Core::DoRegisterSelectedPrinters(
const printing::PrinterList& printer_list) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
if (!print_system_.get())
return; // No print system available.
printer_list_.assign(printer_list.begin(), printer_list.end());
next_upload_index_ = 0;
RegisterNextPrinter();
}
void CloudPrintProxyBackend::Core::GetRegisteredPrinters() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
GURL printer_list_url =
CloudPrintHelpers::GetUrlForPrinterList(cloud_print_server_url_,
proxy_id_);
next_response_handler_ =
&CloudPrintProxyBackend::Core::HandlePrinterListResponse;
request_ = new CloudPrintURLFetcher;
request_->StartGetRequest(printer_list_url,
this,
kCloudPrintAPIMaxRetryCount,
std::string());
}
void CloudPrintProxyBackend::Core::RegisterNextPrinter() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
// For the next printer to be uploaded, create a multi-part post request to
// upload the printer capabilities and the printer defaults.
if (next_upload_index_ < printer_list_.size()) {
const printing::PrinterBasicInfo& info =
printer_list_.at(next_upload_index_);
// If we are retrying a previous upload, we don't need to fetch the caps
// and defaults again.
if (info.printer_name != last_uploaded_printer_name_) {
cloud_print::PrintSystem::PrinterCapsAndDefaultsCallback* callback =
NewCallback(this,
&CloudPrintProxyBackend::Core::OnReceivePrinterCaps);
// Asnchronously fetch the printer caps and defaults. The story will
// continue in OnReceivePrinterCaps.
print_system_->GetPrinterCapsAndDefaults(
info.printer_name.c_str(), callback);
} else {
OnReceivePrinterCaps(true,
last_uploaded_printer_name_,
last_uploaded_printer_info_);
}
} else {
EndRegistration();
}
}
void CloudPrintProxyBackend::Core::OnReceivePrinterCaps(
bool succeeded,
const std::string& printer_name,
const printing::PrinterCapsAndDefaults& caps_and_defaults) {
DCHECK(next_upload_index_ < printer_list_.size());
if (succeeded) {
const printing::PrinterBasicInfo& info =
printer_list_.at(next_upload_index_);
last_uploaded_printer_name_ = info.printer_name;
last_uploaded_printer_info_ = caps_and_defaults;
std::string mime_boundary;
CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary);
std::string post_data;
CloudPrintHelpers::AddMultipartValueForUpload(kProxyIdValue, proxy_id_,
mime_boundary,
std::string(), &post_data);
CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue,
info.printer_name,
mime_boundary,
std::string(), &post_data);
CloudPrintHelpers::AddMultipartValueForUpload(kPrinterDescValue,
info.printer_description,
mime_boundary,
std::string() , &post_data);
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterStatusValue, base::StringPrintf("%d", info.printer_status),
mime_boundary, std::string(), &post_data);
// Add printer options as tags.
CloudPrintHelpers::GenerateMultipartPostDataForPrinterTags(info.options,
mime_boundary,
&post_data);
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterCapsValue, last_uploaded_printer_info_.printer_capabilities,
mime_boundary, last_uploaded_printer_info_.caps_mime_type,
&post_data);
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterDefaultsValue, last_uploaded_printer_info_.printer_defaults,
mime_boundary, last_uploaded_printer_info_.defaults_mime_type,
&post_data);
// Send a hash of the printer capabilities to the server. We will use this
// later to check if the capabilities have changed
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterCapsHashValue,
base::MD5String(last_uploaded_printer_info_.printer_capabilities),
mime_boundary, std::string(), &post_data);
GURL post_url = CloudPrintHelpers::GetUrlForPrinterRegistration(
cloud_print_server_url_);
next_response_handler_ =
&CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse;
// Terminate the request body
post_data.append("--" + mime_boundary + "--\r\n");
std::string mime_type("multipart/form-data; boundary=");
mime_type += mime_boundary;
request_ = new CloudPrintURLFetcher;
request_->StartPostRequest(post_url,
this,
kCloudPrintAPIMaxRetryCount,
mime_type,
post_data,
std::string());
} else {
LOG(ERROR) << "CP_PROXY: Failed to get printer info for: " <<
printer_name;
// This printer failed to register, notify the server of this failure.
string16 printer_name_utf16 = UTF8ToUTF16(printer_name);
std::string status_message = l10n_util::GetStringFUTF8(
IDS_CLOUD_PRINT_REGISTER_PRINTER_FAILED,
printer_name_utf16);
ReportUserMessage(
kGetPrinterCapsFailedMessageId,
status_message,
&CloudPrintProxyBackend::Core::HandleRegisterFailedStatusResponse);
}
}
void CloudPrintProxyBackend::Core::HandlePrinterNotification(
const std::string& printer_id) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
VLOG(1) << "CP_PROXY: Handle printer notification, id: " << printer_id;
JobHandlerMap::iterator index = job_handler_map_.find(printer_id);
if (index != job_handler_map_.end())
index->second->CheckForJobs(kJobFetchReasonNotified);
}
void CloudPrintProxyBackend::Core::PollForJobs() {
VLOG(1) << "CP_PROXY: Polling for jobs.";
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
for (JobHandlerMap::iterator index = job_handler_map_.begin();
index != job_handler_map_.end(); index++) {
// If notifications are on, then we should poll for this printer only if
// the last time it fetched jobs was before notifications were last enabled.
bool should_poll =
!notifications_enabled_ ||
(index->second->last_job_fetch_time() <= notifications_enabled_since_);
if (should_poll)
index->second->CheckForJobs(kJobFetchReasonPoll);
}
job_poll_scheduled_ = false;
// If we don't have notifications and job polling is enabled, poll again
// after a while.
if (!notifications_enabled_ && enable_job_poll_)
ScheduleJobPoll();
}
void CloudPrintProxyBackend::Core::ScheduleJobPoll() {
if (!job_poll_scheduled_) {
int interval_in_seconds = base::RandInt(kMinJobPollIntervalSecs,
kMaxJobPollIntervalSecs);
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
NewRunnableMethod(this, &CloudPrintProxyBackend::Core::PollForJobs),
interval_in_seconds * 1000);
job_poll_scheduled_ = true;
}
}
CloudPrintTokenStore* CloudPrintProxyBackend::Core::GetTokenStore() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
if (!token_store_.get())
token_store_.reset(new CloudPrintTokenStore);
return token_store_.get();
}
// CloudPrintURLFetcher::Delegate implementation.
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandleJSONData(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(next_response_handler_);
return (this->*next_response_handler_)(source, url, json_data, succeeded);
}
void CloudPrintProxyBackend::Core::OnRequestAuthError() {
OnAuthError();
}
void CloudPrintProxyBackend::Core::NotifyPrinterListAvailable(
const printing::PrinterList& printer_list) {
DCHECK(MessageLoop::current() == backend_->frontend_loop_);
backend_->frontend_->OnPrinterListAvailable(printer_list);
}
void CloudPrintProxyBackend::Core::NotifyAuthenticated(
const std::string& robot_oauth_refresh_token,
const std::string& robot_email,
const std::string& user_email) {
DCHECK(MessageLoop::current() == backend_->frontend_loop_);
backend_->frontend_->OnAuthenticated(robot_oauth_refresh_token,
robot_email,
user_email);
}
void CloudPrintProxyBackend::Core::NotifyAuthenticationFailed() {
DCHECK(MessageLoop::current() == backend_->frontend_loop_);
backend_->frontend_->OnAuthenticationFailed();
}
void CloudPrintProxyBackend::Core::NotifyPrintSystemUnavailable() {
DCHECK(MessageLoop::current() == backend_->frontend_loop_);
backend_->frontend_->OnPrintSystemUnavailable();
}
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandleGetAuthCodeResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
if (!succeeded) {
OnAuthError();
return CloudPrintURLFetcher::STOP_PROCESSING;
}
std::string auth_code;
if (!json_data->GetString(kOAuthCodeValue, &auth_code)) {
OnAuthError();
return CloudPrintURLFetcher::STOP_PROCESSING;
}
json_data->GetString(kXMPPJidValue, &robot_email_);
// Now that we have an auth code we need to get the refresh and access tokens.
oauth_client_.reset(new gaia::GaiaOAuthClient(
gaia::kGaiaOAuth2Url,
g_service_process->GetServiceURLRequestContextGetter()));
oauth_client_->GetTokensFromAuthCode(oauth_client_info_,
auth_code,
kCloudPrintAPIMaxRetryCount,
this);
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandlePrinterListResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
if (!succeeded) {
NOTREACHED();
return CloudPrintURLFetcher::RETRY_REQUEST;
}
ListValue* printer_list = NULL;
// There may be no "printers" value in the JSON
if (json_data->GetList(kPrinterListValue, &printer_list) && printer_list) {
for (size_t index = 0; index < printer_list->GetSize(); index++) {
DictionaryValue* printer_data = NULL;
if (printer_list->GetDictionary(index, &printer_data)) {
std::string printer_name;
printer_data->GetString(kNameValue, &printer_name);
RemovePrinterFromList(printer_name);
InitJobHandlerForPrinter(printer_data);
} else {
NOTREACHED();
}
}
}
request_ = NULL;
if (!printer_list_.empty()) {
// Let the frontend know that we have a list of printers available.
backend_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
&Core::NotifyPrinterListAvailable, printer_list_));
} else {
// No more work to be done here.
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &Core::EndRegistration));
}
return CloudPrintURLFetcher::STOP_PROCESSING;
}
void CloudPrintProxyBackend::Core::InitJobHandlerForPrinter(
DictionaryValue* printer_data) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
DCHECK(printer_data);
PrinterJobHandler::PrinterInfoFromCloud printer_info_cloud;
printer_data->GetString(kIdValue, &printer_info_cloud.printer_id);
DCHECK(!printer_info_cloud.printer_id.empty());
VLOG(1) << "CP_PROXY: Init job handler for printer id: "
<< printer_info_cloud.printer_id;
JobHandlerMap::iterator index = job_handler_map_.find(
printer_info_cloud.printer_id);
// We might already have a job handler for this printer
if (index == job_handler_map_.end()) {
printing::PrinterBasicInfo printer_info;
printer_data->GetString(kNameValue, &printer_info.printer_name);
DCHECK(!printer_info.printer_name.empty());
printer_data->GetString(kPrinterDescValue,
&printer_info.printer_description);
// Printer status is a string value which actually contains an integer.
std::string printer_status;
if (printer_data->GetString(kPrinterStatusValue, &printer_status)) {
base::StringToInt(printer_status, &printer_info.printer_status);
}
printer_data->GetString(kPrinterCapsHashValue,
&printer_info_cloud.caps_hash);
ListValue* tags_list = NULL;
if (printer_data->GetList(kTagsValue, &tags_list) && tags_list) {
for (size_t index = 0; index < tags_list->GetSize(); index++) {
std::string tag;
if (tags_list->GetString(index, &tag) &&
StartsWithASCII(tag, kTagsHashTagName, false)) {
std::vector<std::string> tag_parts;
base::SplitStringDontTrim(tag, '=', &tag_parts);
DCHECK_EQ(tag_parts.size(), 2U);
if (tag_parts.size() == 2)
printer_info_cloud.tags_hash = tag_parts[1];
}
}
}
scoped_refptr<PrinterJobHandler> job_handler;
job_handler = new PrinterJobHandler(printer_info,
printer_info_cloud,
cloud_print_server_url_,
print_system_.get(),
this);
job_handler_map_[printer_info_cloud.printer_id] = job_handler;
job_handler->Initialize();
}
}
void CloudPrintProxyBackend::Core::ReportUserMessage(
const std::string& message_id,
const std::string& failure_message,
ResponseHandler handler) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
std::string mime_boundary;
CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary);
GURL post_url = CloudPrintHelpers::GetUrlForUserMessage(
cloud_print_server_url_,
message_id);
std::string post_data;
CloudPrintHelpers::AddMultipartValueForUpload(kMessageTextValue,
failure_message,
mime_boundary,
std::string(),
&post_data);
next_response_handler_ = handler;
// Terminate the request body
post_data.append("--" + mime_boundary + "--\r\n");
std::string mime_type("multipart/form-data; boundary=");
mime_type += mime_boundary;
request_ = new CloudPrintURLFetcher;
request_->StartPostRequest(post_url,
this,
kCloudPrintAPIMaxRetryCount,
mime_type,
post_data,
std::string());
}
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
if (succeeded) {
ListValue* printer_list = NULL;
// There should be a "printers" value in the JSON
if (json_data->GetList(kPrinterListValue, &printer_list)) {
DictionaryValue* printer_data = NULL;
if (printer_list->GetDictionary(0, &printer_data))
InitJobHandlerForPrinter(printer_data);
}
}
next_upload_index_++;
MessageLoop::current()->PostTask(
FROM_HERE,
NewRunnableMethod(this,
&CloudPrintProxyBackend::Core::RegisterNextPrinter));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandleRegisterFailedStatusResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
next_upload_index_++;
MessageLoop::current()->PostTask(
FROM_HERE,
NewRunnableMethod(this,
&CloudPrintProxyBackend::Core::RegisterNextPrinter));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandlePrintSystemUnavailableResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
// Let the frontend know that we do not have a print system.
backend_->frontend_loop_->PostTask(
FROM_HERE,
NewRunnableMethod(this,
&Core::NotifyPrintSystemUnavailable));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
CloudPrintProxyBackend::Core::HandleEnumPrintersFailedResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
// Now proceed with printer registration.
GetRegisteredPrinters();
return CloudPrintURLFetcher::STOP_PROCESSING;
}
bool CloudPrintProxyBackend::Core::RemovePrinterFromList(
const std::string& printer_name) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
for (printing::PrinterList::iterator index = printer_list_.begin();
index != printer_list_.end(); index++) {
if (0 == base::strcasecmp(index->printer_name.c_str(),
printer_name.c_str())) {
index = printer_list_.erase(index);
return true;
}
}
return false;
}
void CloudPrintProxyBackend::Core::RefreshAccessToken() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
oauth_client_.reset(new gaia::GaiaOAuthClient(
gaia::kGaiaOAuth2Url,
g_service_process->GetServiceURLRequestContextGetter()));
oauth_client_->RefreshToken(oauth_client_info_,
refresh_token_,
kCloudPrintAPIMaxRetryCount,
this);
}
void CloudPrintProxyBackend::Core::OnNotificationStateChange(
bool notification_enabled) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
notifications_enabled_ = notification_enabled;
if (notifications_enabled_) {
notifications_enabled_since_ = base::TimeTicks::Now();
VLOG(1) << "Notifications for proxy " << proxy_id_ << " were enabled at "
<< notifications_enabled_since_.ToInternalValue();
} else {
LOG(ERROR) << "Notifications for proxy " << proxy_id_ << " disabled.";
notifications_enabled_since_ = base::TimeTicks();
}
// A state change means one of two cases.
// Case 1: We just lost notifications. This this case we want to schedule a
// job poll if enable_job_poll_ is true.
// Case 2: Notifications just got re-enabled. In this case we want to schedule
// a poll once for jobs we might have missed when we were dark.
// Note that ScheduleJobPoll will not schedule again if a job poll task is
// already scheduled.
if (enable_job_poll_ || notifications_enabled_)
ScheduleJobPoll();
}
void CloudPrintProxyBackend::Core::OnIncomingNotification(
const notifier::Notification& notification) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
VLOG(1) << "CP_PROXY: Incoming notification.";
if (0 == base::strcasecmp(kCloudPrintPushNotificationsSource,
notification.channel.c_str()))
HandlePrinterNotification(notification.data);
}
void CloudPrintProxyBackend::Core::OnOutgoingNotification() {}
// cloud_print::PrinterChangeNotifier::Delegate implementation
void CloudPrintProxyBackend::Core::OnPrinterAdded() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
if (registration_in_progress_)
new_printers_available_ = true;
else
StartRegistration();
}
// PrinterJobHandler::Delegate implementation
void CloudPrintProxyBackend::Core::OnPrinterJobHandlerShutdown(
PrinterJobHandler* job_handler, const std::string& printer_id) {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
VLOG(1) << "CP_PROXY: Printer job handle shutdown, id " << printer_id;
job_handler_map_.erase(printer_id);
}
void CloudPrintProxyBackend::Core::OnAuthError() {
DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop());
VLOG(1) << "CP_PROXY: Auth Error";
backend_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
&Core::NotifyAuthenticationFailed));
}
void CloudPrintProxyBackend::Core::OnPrinterNotFound(
const std::string& printer_name,
bool* delete_from_server) {
// If we have a complete list of local printers, then this needs to be deleted
// from the server.
*delete_from_server = complete_list_available_;
}
// gaia::GaiaOAuthClient::Delegate implementation.
void CloudPrintProxyBackend::Core::OnGetTokensResponse(
const std::string& refresh_token,
const std::string& access_token,
int expires_in_seconds) {
refresh_token_ = refresh_token;
// After saving the refresh token, this is just like having just refreshed
// the access token. Just call OnRefreshTokenResponse.
OnRefreshTokenResponse(access_token, expires_in_seconds);
}
void CloudPrintProxyBackend::Core::OnRefreshTokenResponse(
const std::string& access_token, int expires_in_seconds) {
// If our current token is not OAuth, we either have no token at all or we
// have a ClientLogin token which we just exchanged for an OAuth token.
// In this case we need to do the startup initialiazation.
// TODO(sanjeevr): Use an enum for state instead of using this as a signal.
// I will do this in a follow-up change.
CloudPrintTokenStore* token_store = GetTokenStore();
bool first_time = !token_store->token_is_oauth();
token_store->SetToken(access_token, true);
// Let the frontend know that we have authenticated.
backend_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
&Core::NotifyAuthenticated, refresh_token_, robot_email_, user_email_));
if (first_time) {
PostAuthInitialization();
} else {
// If we are refreshing a token, update the XMPP token too.
DCHECK(talk_mediator_.get());
talk_mediator_->SetAuthToken(robot_email_,
access_token,
kSyncGaiaServiceId);
}
// Schedule a task to refresh the access token again when it is about to
// expire.
DCHECK(expires_in_seconds > kTokenRefreshGracePeriodSecs);
int64 refresh_delay =
(expires_in_seconds - kTokenRefreshGracePeriodSecs)*1000;
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
NewRunnableMethod(this, &Core::RefreshAccessToken),
refresh_delay);
}
void CloudPrintProxyBackend::Core::OnOAuthError() {
OnAuthError();
}
void CloudPrintProxyBackend::Core::OnNetworkError(int response_code) {
// Since we specify inifinite retries on network errors, this should never
// be called.
NOTREACHED() <<
"OnNetworkError invoked when not expected, response code is " <<
response_code;
}