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); |
+} |