| Index: chrome/browser/chrome_to_mobile_service.cc
|
| diff --git a/chrome/browser/chrome_to_mobile_service.cc b/chrome/browser/chrome_to_mobile_service.cc
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..bf565186e5067dc79ee38c81e08fe16386eae38b
|
| --- /dev/null
|
| +++ b/chrome/browser/chrome_to_mobile_service.cc
|
| @@ -0,0 +1,300 @@
|
| +// Copyright (c) 2012 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/browser/chrome_to_mobile_service.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/json/json_writer.h"
|
| +#include "base/stringprintf.h"
|
| +#include "base/utf_string_conversions.h"
|
| +#include "base/values.h"
|
| +#include "chrome/app/chrome_command_ids.h"
|
| +#include "chrome/common/cloud_print/cloud_print_helpers.h"
|
| +#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
|
| +#include "chrome/browser/profiles/profile.h"
|
| +#include "chrome/browser/signin/token_service.h"
|
| +#include "chrome/browser/signin/token_service_factory.h"
|
| +#include "chrome/browser/ui/browser_list.h"
|
| +#include "chrome/common/guid.h"
|
| +#include "chrome/common/net/gaia/gaia_urls.h"
|
| +#include "chrome/common/net/gaia/oauth2_access_token_fetcher.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/common/url_fetcher.h"
|
| +#include "net/base/escape.h"
|
| +#include "net/url_request/url_request_context_getter.h"
|
| +
|
| +namespace {
|
| +
|
| +// The URLFetcher max number of retries.
|
| +size_t kMaxRetries = 10;
|
| +
|
| +// The request retry delays in seconds.
|
| +int kAuthRetryDelay = 30;
|
| +int kAutoSearchRetryDelay = 300;
|
| +
|
| +const char kOAuth2Scope[] = "https://www.googleapis.com/auth/cloudprint";
|
| +const char kMobileTypeAndroidChromeSnapshot[] = "ANDROID_CHROME_SNAPSHOT";
|
| +
|
| +const char kJobTypeURL[] = "url";
|
| +const char kJobTypeDelayedSnapshot[] = "url_with_delayed_snapshot";
|
| +const char kJobTypeSnapshot[] = "snapshot";
|
| +
|
| +std::string GetJobString(const internal::SubmitData& data) {
|
| + scoped_ptr<DictionaryValue> job(new DictionaryValue());
|
| + job->SetString("url", data.url.spec());
|
| + if (data.job_type == internal::URL) {
|
| + job->SetString("type", kJobTypeURL);
|
| + } else {
|
| + job->SetString("snapID", data.snapshot_id);
|
| + job->SetString("type", (data.job_type == internal::SNAPSHOT) ?
|
| + kJobTypeSnapshot : kJobTypeDelayedSnapshot);
|
| + }
|
| + std::string job_string;
|
| + base::JSONWriter::Write(job.get(), false, &job_string);
|
| + return job_string;
|
| +}
|
| +
|
| +GURL GetSubmitURL(GURL service_url, const internal::SubmitData& data) {
|
| + GURL submit_url = cloud_print::GetUrlForSubmit(service_url);
|
| + if (data.job_type == internal::SNAPSHOT)
|
| + return submit_url;
|
| +
|
| + // Append form data to the URL's query for |URL| and |DELAYED_SNAPSHOT| jobs.
|
| + static const bool kUsePlus = true;
|
| + std::string tag_string = net::EscapeQueryParamValue(
|
| + "__c2dm__job_data=" + GetJobString(data), kUsePlus);
|
| + GURL::Replacements replacements;
|
| + // Provide dummy content to workaround |errorCode| 412 'Document missing'.
|
| + std::string query = StringPrintf("printerid=%s&tag=%s&title=%s"
|
| + "&contentType=text/plain&content=dummy",
|
| + net::EscapeQueryParamValue(UTF16ToUTF8(data.mobile_id), kUsePlus).c_str(),
|
| + net::EscapeQueryParamValue(tag_string, kUsePlus).c_str(),
|
| + net::EscapeQueryParamValue(UTF16ToUTF8(data.title), kUsePlus).c_str());
|
| + replacements.SetQueryStr(query);
|
| + return submit_url.ReplaceComponents(replacements);
|
| +}
|
| +
|
| +void DeleteFilePath(const FilePath& file_path) {
|
| + bool success = file_util::Delete(file_path, false);
|
| + DCHECK(success);
|
| +}
|
| +
|
| +void SubmitSnapshot(content::URLFetcher* request,
|
| + const internal::SubmitData& data) {
|
| + std::string file;
|
| + if (file_util::ReadFileToString(data.snapshot_path, &file) && !file.empty()) {
|
| + std::string post_data, mime_boundary;
|
| + cloud_print::CreateMimeBoundaryForUpload(&mime_boundary);
|
| + cloud_print::AddMultipartValueForUpload("printerid",
|
| + UTF16ToUTF8(data.mobile_id), mime_boundary, std::string(), &post_data);
|
| + cloud_print::AddMultipartValueForUpload("tag", "__c2dm__job_data=" +
|
| + GetJobString(data), mime_boundary, std::string(), &post_data);
|
| + cloud_print::AddMultipartValueForUpload("title", UTF16ToUTF8(data.title),
|
| + mime_boundary, std::string(), &post_data);
|
| + cloud_print::AddMultipartValueForUpload("contentType", "multipart/related",
|
| + mime_boundary, std::string(), &post_data);
|
| +
|
| + // Append the snapshot MHTML content and terminate the request body.
|
| + post_data.append("--" + mime_boundary + "\r\n"
|
| + "Content-Disposition: form-data; "
|
| + "name=\"content\"; filename=\"blob\"\r\n"
|
| + "Content-Type: text/mhtml\r\n"
|
| + "\r\n" + file + "\r\n" "--" + mime_boundary + "--\r\n");
|
| + std::string content_type = "multipart/form-data; boundary=" + mime_boundary;
|
| + request->SetUploadData(content_type, post_data);
|
| + request->Start();
|
| + }
|
| +
|
| + content::BrowserThread::PostBlockingPoolTask(FROM_HERE,
|
| + base::Bind(&DeleteFilePath, data.snapshot_path));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace internal {
|
| +
|
| +SubmitData::SubmitData() {}
|
| +
|
| +SubmitData::~SubmitData() {}
|
| +
|
| +}
|
| +
|
| +ChromeToMobileService::ChromeToMobileService(Profile* profile)
|
| + : profile_(profile),
|
| + cloud_print_url_(new CloudPrintURL(profile)) {
|
| + RequestMobileListUpdate();
|
| +}
|
| +
|
| +ChromeToMobileService::~ChromeToMobileService() {}
|
| +
|
| +void ChromeToMobileService::OnURLFetchComplete(
|
| + const content::URLFetcher* source) {
|
| + if (source == search_request_.get())
|
| + HandleSearchResponse();
|
| + else
|
| + HandleSubmitResponse(source);
|
| +}
|
| +
|
| +void ChromeToMobileService::OnGetTokenSuccess(
|
| + const std::string& access_token) {
|
| + oauth2_request_.reset();
|
| + request_timer_.Stop();
|
| + oauth2_token_ = access_token;
|
| + RequestSearch();
|
| +}
|
| +
|
| +void ChromeToMobileService::OnGetTokenFailure(
|
| + const GoogleServiceAuthError& error) {
|
| + oauth2_request_.reset();
|
| + request_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kAuthRetryDelay),
|
| + this, &ChromeToMobileService::RequestAuth);
|
| +}
|
| +
|
| +void ChromeToMobileService::RequestMobileListUpdate() {
|
| + if (oauth2_token_.empty())
|
| + RequestAuth();
|
| + else
|
| + RequestSearch();
|
| +}
|
| +
|
| +void ChromeToMobileService::SendToMobile(const string16& printer_id,
|
| + const GURL& url,
|
| + const string16& title,
|
| + const FilePath& snapshot,
|
| + base::WeakPtr<Observer> observer) {
|
| + if (oauth2_token_.empty() || !url.is_valid())
|
| + return;
|
| +
|
| + bool send_snapshot = !snapshot.empty();
|
| + std::string id = send_snapshot ? guid::GenerateGUID() : std::string();
|
| + internal::SubmitData data;
|
| + data.mobile_id = printer_id;
|
| + data.url = url;
|
| + data.title = title;
|
| + data.snapshot_path = snapshot;
|
| + data.snapshot_id = id;
|
| + data.job_type = send_snapshot ? internal::DELAYED_SNAPSHOT : internal::URL;
|
| + content::URLFetcher* submit_url = CreateRequest(data);
|
| + request_observer_map_[submit_url] = observer;
|
| + submit_url->Start();
|
| + if (send_snapshot) {
|
| + data.job_type = internal::SNAPSHOT;
|
| + content::URLFetcher* submit_snapshot = CreateRequest(data);
|
| + request_observer_map_[submit_snapshot] = observer;
|
| + content::BrowserThread::PostBlockingPoolTask(FROM_HERE,
|
| + base::Bind(&SubmitSnapshot, submit_snapshot, data));
|
| + }
|
| +}
|
| +
|
| +content::URLFetcher* ChromeToMobileService::CreateRequest(
|
| + const internal::SubmitData& data) {
|
| + bool get = data.job_type != internal::SNAPSHOT;
|
| + content::URLFetcher* request = content::URLFetcher::Create(
|
| + GetSubmitURL(cloud_print_url_->GetCloudPrintServiceURL(), data),
|
| + get ? content::URLFetcher::GET : content::URLFetcher::POST, this);
|
| + request->SetRequestContext(profile_->GetRequestContext());
|
| + request->SetMaxRetries(kMaxRetries);
|
| + request->SetExtraRequestHeaders("Authorization: OAuth " +
|
| + oauth2_token_ + "\r\n" + cloud_print::kChromeCloudPrintProxyHeader);
|
| + return request;
|
| +}
|
| +
|
| +void ChromeToMobileService::RequestAuth() {
|
| + if (oauth2_request_.get() || !oauth2_token_.empty())
|
| + return;
|
| +
|
| + GURL auth_url = cloud_print::GetUrlForGetAuthCode(
|
| + cloud_print_url_->GetCloudPrintServiceURL(),
|
| + cloud_print::kDefaultCloudPrintOAuthClientId,
|
| + cloud_print::GenerateProxyId());
|
| + oauth2_request_.reset(
|
| + new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext()));
|
| + std::string token = TokenServiceFactory::GetForProfile(profile_)->
|
| + GetOAuth2LoginRefreshToken();
|
| + std::vector<std::string> scopes(1, kOAuth2Scope);
|
| + oauth2_request_->Start(GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
|
| + GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), token, scopes);
|
| +}
|
| +
|
| +void ChromeToMobileService::RequestSearch() {
|
| + if (search_request_.get() || oauth2_token_.empty())
|
| + return;
|
| +
|
| + GURL search_url =
|
| + cloud_print::GetUrlForSearch(cloud_print_url_->GetCloudPrintServiceURL());
|
| + search_request_.reset(
|
| + content::URLFetcher::Create(search_url, content::URLFetcher::GET, this));
|
| + search_request_->SetRequestContext(profile_->GetRequestContext());
|
| + search_request_->SetMaxRetries(kMaxRetries);
|
| + search_request_->SetExtraRequestHeaders("Authorization: OAuth " +
|
| + oauth2_token_ + "\r\n" + cloud_print::kChromeCloudPrintProxyHeader);
|
| + search_request_->Start();
|
| +}
|
| +
|
| +void ChromeToMobileService::HandleSearchResponse() {
|
| + std::string data;
|
| + search_request_->GetResponseAsString(&data);
|
| + search_request_.reset();
|
| +
|
| + DictionaryValue* json_data = NULL;
|
| + ListValue* list = NULL;
|
| + cloud_print::ParseResponseJSON(data, NULL, &json_data);
|
| + if (json_data && json_data->GetList(cloud_print::kPrinterListValue, &list)) {
|
| + mobiles_.clear();
|
| + for (size_t index = 0; index < list->GetSize(); index++) {
|
| + DictionaryValue* mobile_data = NULL;
|
| + if (list->GetDictionary(index, &mobile_data)) {
|
| + std::string mobile_type;
|
| + mobile_data->GetString("type", &mobile_type);
|
| + if (mobile_type.compare(kMobileTypeAndroidChromeSnapshot) == 0)
|
| + mobiles_.push_back(mobile_data);
|
| + }
|
| + }
|
| + }
|
| +
|
| + BrowserList::GetLastActiveWithProfile(profile_)->command_updater()->
|
| + UpdateCommandEnabled(IDC_CHROME_TO_MOBILE_PAGE, !mobiles_.empty());
|
| +
|
| + if (!request_timer_.IsRunning())
|
| + request_timer_.Start(FROM_HERE,
|
| + base::TimeDelta::FromSeconds(kAutoSearchRetryDelay),
|
| + this, &ChromeToMobileService::RequestSearch);
|
| +}
|
| +
|
| +void ChromeToMobileService::HandleSubmitResponse(
|
| + const content::URLFetcher* source) {
|
| + // Get the observer for this response; bail if there is none or it is NULL.
|
| + RequestObserverMap::iterator i = request_observer_map_.find(source);
|
| + if (i == request_observer_map_.end())
|
| + return;
|
| + base::WeakPtr<Observer> observer = i->second;
|
| + request_observer_map_.erase(i);
|
| + if (!observer.get())
|
| + return;
|
| +
|
| + // Get the success value from the CloudPrint server response data.
|
| + std::string data;
|
| + source->GetResponseAsString(&data);
|
| + DictionaryValue* json_data = NULL;
|
| + cloud_print::ParseResponseJSON(data, NULL, &json_data);
|
| + bool success = false;
|
| + if (json_data)
|
| + json_data->GetBoolean("success", &success);
|
| +
|
| + // Check if the observer is waiting on a second response (url and snapshot).
|
| + RequestObserverMap::iterator other = request_observer_map_.begin();
|
| + for (; other != request_observer_map_.end(); ++other) {
|
| + if (other->second == observer) {
|
| + // Do not call OnSendComplete for observers waiting on a second response.
|
| + if (success)
|
| + return;
|
| +
|
| + // Ensure a second response is not sent after reporting failure below.
|
| + request_observer_map_.erase(other);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + observer->OnSendComplete(success);
|
| +}
|
|
|