Index: chrome/browser/chrome_to_mobile/receive/chrome_to_mobile_snapshots_polling_fetcher.cc |
diff --git a/chrome/browser/chrome_to_mobile/receive/chrome_to_mobile_snapshots_polling_fetcher.cc b/chrome/browser/chrome_to_mobile/receive/chrome_to_mobile_snapshots_polling_fetcher.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b0953c039ed58eae2f4c39ebfebc97a5c4824f4a |
--- /dev/null |
+++ b/chrome/browser/chrome_to_mobile/receive/chrome_to_mobile_snapshots_polling_fetcher.cc |
@@ -0,0 +1,531 @@ |
+// Copyright 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/receive/chrome_to_mobile_snapshots_polling_fetcher.h" |
+ |
+#include <vector> |
+ |
+#include "base/logging.h" |
+#include "base/rand_util.h" |
+#include "base/values.h" |
+#include "chrome/common/cloud_print/cloud_print_consts.h" |
+#include "chrome/common/cloud_print/cloud_print_helpers.h" |
+#include "chrome/browser/chrome_to_mobile/receive/chrome_to_mobile_receive_util.h" |
+ |
+namespace { |
+ |
+// Max times to poll cloud print server. |
+const int kMaxPollingNum = 50; |
+// Minimal time to wait between polling cloud print server. |
+const int kBasePollingIntervalInSecond = 2; |
+// Maximal time to wait between polling cloud print server. |
+const int kMaxPollingIntervalInSecond = 180; |
+ |
+} // namespace |
+ |
+namespace chrome_to_mobile_receive { |
+ |
+// Class that keeps the status of fetching a snapshot. |
+// |
+// The following steps are needed to complete the data fetching for a snapshot. |
+// Note these steps are asynchronous. |
+// |
+// (1) Get the url job. This job contains the webpage url that generates the |
+// snapshot. It also contains the information on if an offline data job |
+// of this snapshot is expected. |
+// |
+// (2) Delete the url print job. |
+// |
+// If no offline data job is expected, snapshot fetch completes when (2) |
+// completes. |
+// |
+// (3) Otherwise fetch the offline data job. This is done by polling the cloud |
+// print server before the maximum retry number is reached. |
+// Note the actual offline data is not included in this print job; instead |
+// a download url is given. |
+// |
+// If it fails to fetch the offline data job before the maximum retry number is |
+// reached, snapshot fetch completes when (2) completes. |
+// |
+// (4) Otherwise download the offline data, pointed by a url provided |
+// in the offline data job. |
+// |
+// (5) When (4) completes, successful or not, delete the offline data job. |
+// |
+// Note the offline data job should not be deleted before (4) completes, as |
+// the deletion of the offline data print job will cause the offline data to |
+// also be deleted. |
+// |
+// Snapshot fetch completes when (5) complets. |
+class ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry { |
+ public: |
+ SnapshotEntry(ChromeToMobileSnapshotsPollingFetcher* owner, |
+ const std::string& snapshot_id); |
+ ~SnapshotEntry(); |
+ // Initializes this snapshot entry by a url job. |
+ void InitialWithURLJob(const std::string& snapshot_type, |
+ const std::string& url_print_job_id, |
+ const std::string& original_url, |
+ const std::string& title, |
+ const std::string& create_time); |
+ // Updates this snapshot entry by an offline data job. |
+ void UpdateWithOfflineDataJob(const std::string& offline_data_print_job_id, |
+ const std::string& offline_data_download_url); |
+ |
+ // Processes request |source| if it is a delete request for this snapshot. |
+ // Returns true if it is a delete request for this snapshot. |
+ bool HandleDeleteComplete(chrome_to_mobile::CloudPrintRequest* source); |
+ // Processes request |source| if it is a download request for this snapshot. |
+ // Returns true if it is a download request for this snapshot. |
+ bool HandleDownloadComplete(chrome_to_mobile::CloudPrintRequest* source); |
+ |
+ // Gives up polling the cloud print server to fetch the offline data job for |
+ // this snapshot. |
+ void GiveUpPollingOfflineDataJob(); |
+ |
+ // Returns true if it is waiting for offline data job for this snapshot. |
+ bool IsWaitingForOfflineDataJob() const; |
+ // Returns true if the fetch of this snapshot has completed, as described |
+ // in the class document. |
+ bool HasCompleted() const; |
+ |
+ private: |
+ // Returns if an offline data is expected. |
+ bool IsOfflineDataExpected() const; |
+ |
+ // The owner of this snapshot. |
+ ChromeToMobileSnapshotsPollingFetcher* const owner_; |
+ |
+ // The id of this snapshot. This information is initialized from the url job, |
+ // and it is used to identify if an offline data job is for this snapshot. |
+ const std::string snapshot_id_; |
+ // The type of this snapshot: url only or url with an offline data. This |
+ // information is from the url job. |
+ std::string snapshot_type_; |
+ // The print job id of the url job. This information is from the url job. |
+ std::string url_print_job_id_; |
+ // The original url of the web page that generates this snapshot; it should |
+ // not be empty. This information is from the url job. |
+ std::string original_url_; |
+ |
+ // The print job id of the offlince data job; it is meaningful only if an |
+ // offline data is expected. This information is from the offline data job. |
+ std::string offline_data_print_job_id_; |
+ // The url pointing to an offline data of this snapshot; it is meaningful only |
+ // if an offline data is expected. This information is from the offline data |
+ // job. |
+ std::string offline_data_download_url_; |
+ |
+ // Request sent to delete a url job. |
+ scoped_ptr<chrome_to_mobile::CloudPrintRequest> delete_url_job_request_; |
+ // Request sent to download the offline data at |offline_data_download_url_|. |
+ scoped_ptr<chrome_to_mobile::CloudPrintRequest> download_request_; |
+ // Request sent to delete the offline data print job. This should be sent |
+ // after |download_request_| completes, as the deletion of the offline |
+ // data print job will cause the data at |offline_data_download_url_| to be |
+ // deleted. |
+ scoped_ptr<chrome_to_mobile::CloudPrintRequest> |
+ delete_offline_data_job_request_; |
+ |
+ // True if |owner->consumer_| has been notified that the offline data download |
+ // completes. |
+ bool has_notified_consumer_download_completed_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SnapshotEntry); |
+}; |
+ |
+ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry::SnapshotEntry( |
+ ChromeToMobileSnapshotsPollingFetcher* owner, |
+ const std::string& snapshot_id) |
+ : owner_(owner), |
+ snapshot_id_(snapshot_id), |
+ has_notified_consumer_download_completed_(false) { |
+} |
+ |
+ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry::~SnapshotEntry() { |
+ if (IsOfflineDataExpected() && !has_notified_consumer_download_completed_) { |
+ owner_->consumer_->OnSnapshotDataDownloadComplete( |
+ snapshot_id_, false, std::string(), std::string()); |
+ } |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry::InitialWithURLJob( |
+ const std::string& snapshot_type, |
+ const std::string& url_print_job_id, |
+ const std::string& original_url, |
+ const std::string& title, |
+ const std::string& create_time) { |
+ DCHECK(snapshot_type.size()); |
+ DCHECK(url_print_job_id.size()); |
+ DCHECK(original_url.size()); |
+ |
+ snapshot_type_ = snapshot_type; |
+ url_print_job_id_ = url_print_job_id; |
+ original_url_ = original_url; |
+ |
+ owner_->consumer_->OnSnapshotUrlFetched(snapshot_id_, |
+ original_url, |
+ title, |
+ IsOfflineDataExpected(), |
+ create_time); |
+ |
+ delete_url_job_request_.reset( |
+ chrome_to_mobile::CloudPrintRequest::CreateAndStartGetRequest( |
+ cloud_print::GetUrlForJobDelete( |
+ owner_->cloud_print_server_url_, url_print_job_id_), |
+ owner_->settings_, |
+ owner_)); |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry:: |
+ UpdateWithOfflineDataJob(const std::string& offline_data_print_job_id, |
+ const std::string& offline_data_download_url) { |
+ // This check is needed as it is possible to get such a job for more than once |
+ // due to the latency of the delete request. |
+ if (!IsWaitingForOfflineDataJob()) |
+ return; |
+ |
+ offline_data_print_job_id_ = offline_data_print_job_id; |
+ offline_data_download_url_ = offline_data_download_url; |
+ download_request_.reset(chrome_to_mobile::CloudPrintRequest::CreateAndStart( |
+ GURL(offline_data_download_url), std::string("Accept: application/pdf"), |
+ net::URLFetcher::GET, std::string(), std::string(), owner_->settings_, |
+ owner_)); |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry:: |
+ GiveUpPollingOfflineDataJob() { |
+ // No affect if it is not waiting for an offline data job. |
+ if (!IsWaitingForOfflineDataJob()) |
+ return; |
+ if (!has_notified_consumer_download_completed_) { |
+ owner_->consumer_->OnSnapshotDataDownloadComplete( |
+ snapshot_id_, false, std::string(), std::string()); |
+ has_notified_consumer_download_completed_ = true; |
+ } |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry::HandleDeleteComplete( |
+ chrome_to_mobile::CloudPrintRequest* source) { |
+ if (delete_url_job_request_ == source) { |
+ delete_url_job_request_.reset(); |
+ return true; |
+ } |
+ if (delete_offline_data_job_request_ == source) { |
+ delete_offline_data_job_request_.reset(); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry:: |
+ HandleDownloadComplete(chrome_to_mobile::CloudPrintRequest* source) { |
+ if (source != download_request_) |
+ return false; |
+ |
+ scoped_ptr<chrome_to_mobile::CloudPrintRequest> to_be_release( |
+ download_request_.release()); |
+ |
+ bool success; |
+ std::string mime_type = source->GetResponseMimeType(); |
+ std::string data = source->GetResponseData(&success); |
+ owner_->consumer_->OnSnapshotDataDownloadComplete( |
+ snapshot_id_, success, mime_type, data); |
+ has_notified_consumer_download_completed_ = true; |
+ |
+ delete_offline_data_job_request_.reset( |
+ chrome_to_mobile::CloudPrintRequest::CreateAndStartGetRequest( |
+ cloud_print::GetUrlForJobDelete( |
+ owner_->cloud_print_server_url_, offline_data_print_job_id_), |
+ owner_->settings_, |
+ owner_)); |
+ |
+ return true; |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry:: |
+ IsWaitingForOfflineDataJob() const { |
+ // No more print job is expected if no offline data is expected, or an offline |
+ // data job has been received, or the consumer has been notified that the |
+ // offline data download has completed (which can happen when it is decided to |
+ // give up polling for the offline data. |
+ if (!IsOfflineDataExpected() || |
+ !offline_data_download_url_.empty() || |
+ has_notified_consumer_download_completed_) { |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry::HasCompleted() |
+ const { |
+ return !IsWaitingForOfflineDataJob() && |
+ !delete_url_job_request_.get() && |
+ !download_request_.get() && |
+ !delete_offline_data_job_request_.get(); |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::SnapshotEntry:: |
+ IsOfflineDataExpected() const { |
+ return snapshot_type_.compare(kSnapshotTypeURLWithDelayedSnapshot) == 0; |
+} |
+ |
+ChromeToMobileSnapshotsFetcher* ChromeToMobileSnapshotsFetcher::Create( |
+ const GURL& cloud_print_server_url, |
+ const std::string& printer_id, |
+ const chrome_to_mobile::CloudPrintRequest::Settings& settings, |
+ ChromeToMobileSnapshotsFetcher::Consumer* delegate) { |
+ return new ChromeToMobileSnapshotsPollingFetcher(cloud_print_server_url, |
+ printer_id, settings, |
+ delegate, |
+ kBasePollingIntervalInSecond, |
+ kMaxPollingIntervalInSecond, |
+ kMaxPollingNum); |
+} |
+ |
+ChromeToMobileSnapshotsPollingFetcher::ChromeToMobileSnapshotsPollingFetcher( |
+ const GURL& cloud_print_server_url, |
+ const std::string& printer_id, |
+ const chrome_to_mobile::CloudPrintRequest::Settings& settings, |
+ ChromeToMobileSnapshotsFetcher::Consumer* consumer, |
+ int base_polling_interval_seconds, |
+ int max_polling_interval_seconds, |
+ int max_polling_number) |
+ : cloud_print_server_url_(cloud_print_server_url), |
+ printer_id_(printer_id), |
+ settings_(settings), |
+ consumer_(consumer), |
+ base_polling_interval_in_seconds_(base_polling_interval_seconds), |
+ max_polling_interval_in_seconds_(max_polling_interval_seconds), |
+ max_polling_number_(max_polling_number), |
+ polling_number_(0), |
+ has_oauth2_token_fetch_failure_(false), |
+ has_cloud_print_auth_error_(false) { |
+ DCHECK(consumer_); |
+} |
+ |
+ChromeToMobileSnapshotsPollingFetcher:: |
+ ~ChromeToMobileSnapshotsPollingFetcher() { |
+ Cancel(); |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::DeletePendingSnapshots() { |
+ bool fetch_completed_by_being_cancelled = false; |
+ |
+ for (SnapshotMap::const_iterator iter = pending_snapshots_.begin(); |
+ iter != pending_snapshots_.end(); |
+ ++iter) { |
+ if (!iter->second->HasCompleted()) |
+ fetch_completed_by_being_cancelled = true; |
+ delete iter->second; |
+ } |
+ pending_snapshots_.clear(); |
+ return fetch_completed_by_being_cancelled; |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::Cancel() { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ bool fetch_completed_by_being_cancelled = DeletePendingSnapshots(); |
+ pending_snapshots_.clear(); |
+ fetch_request_.reset(); |
+ if (fetch_completed_by_being_cancelled) |
+ consumer_->OnSnapshotsFetchComplete(this); |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::Fetch() { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ polling_number_ = 0; |
+ Poll(); |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::Poll() { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ ++polling_number_; |
+ polling_timer_.Stop(); |
+ fetch_request_.reset( |
+ chrome_to_mobile::CloudPrintRequest::CreateAndStartGetRequest( |
+ cloud_print::GetUrlForJobFetch(cloud_print_server_url_, |
+ printer_id_, |
+ std::string()), |
+ settings_, |
+ this)); |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::HandleFetchComplete( |
+ chrome_to_mobile::CloudPrintRequest* source) { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ if (source != fetch_request_.get()) |
+ return false; |
+ |
+ scoped_ptr<chrome_to_mobile::CloudPrintRequest> to_be_release( |
+ fetch_request_.release()); |
+ |
+ bool succeeded; |
+ std::string response_data = source->GetResponseData(&succeeded); |
+ scoped_ptr<base::DictionaryValue> response_dict_to_be_release; |
+ base::DictionaryValue* response_dict = NULL; |
+ if (succeeded) { |
+ cloud_print::ParseResponseJSON(response_data, &succeeded, &response_dict); |
+ response_dict_to_be_release.reset(response_dict); |
+ } |
+ base::ListValue* job_list = NULL; |
+ if (succeeded && response_dict) |
+ succeeded = response_dict->GetList(cloud_print::kJobListValue, &job_list); |
+ |
+ if (succeeded && job_list) { |
+ AddPendingSnapshotsFromURLJobs(job_list); |
+ UpdatePendingSnapshotsWithOfflineDataJobs(job_list); |
+ } |
+ |
+ // Checks if it should continue polling. |
+ bool should_give_up = polling_number_ >= max_polling_number_; |
+ bool is_waiting_for_offline_data_jobs = false; |
+ std::map<std::string, SnapshotEntry*>::iterator iter = |
+ pending_snapshots_.begin(); |
+ for (; iter != pending_snapshots_.end(); ++iter) { |
+ SnapshotEntry* entry = iter->second; |
+ if (entry->IsWaitingForOfflineDataJob()) |
+ is_waiting_for_offline_data_jobs = true; |
+ if (should_give_up) |
+ entry->GiveUpPollingOfflineDataJob(); |
+ } |
+ if (!should_give_up && is_waiting_for_offline_data_jobs) { |
+ int64 max_backoff = |
+ (1 << polling_number_) * base_polling_interval_in_seconds_; |
+ int64 backoff = |
+ base_polling_interval_in_seconds_ + base::RandDouble() * max_backoff; |
+ if (backoff > max_polling_interval_in_seconds_) |
+ backoff = max_polling_interval_in_seconds_; |
+ |
+ polling_timer_.Start(FROM_HERE, |
+ base::TimeDelta::FromSeconds(backoff), |
+ this, |
+ &ChromeToMobileSnapshotsPollingFetcher::Poll); |
+ } |
+ return true; |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::AddPendingSnapshotsFromURLJobs( |
+ const ListValue* job_list) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(job_list); |
+ |
+ for (size_t index = 0; index < job_list->GetSize(); ++index) { |
+ std::string job_id; |
+ std::string snapshot_id; |
+ std::string snapshot_type; |
+ std::string original_url; |
+ std::string title; |
+ std::string create_time; |
+ if (!ParseSnapshotURLJobInformationFromCloudPrintFetchJobList( |
+ job_list, index, &job_id, &snapshot_id, &snapshot_type, &original_url, |
+ &title, &create_time)) { |
+ continue; |
+ } |
+ if (pending_snapshots_.find(snapshot_id) != pending_snapshots_.end()) |
+ continue; |
+ |
+ SnapshotEntry* snapshot_entry = new SnapshotEntry(this, snapshot_id); |
+ snapshot_entry->InitialWithURLJob( |
+ snapshot_type, job_id, original_url, title, create_time); |
+ pending_snapshots_[snapshot_id] = snapshot_entry; |
+ } |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher:: |
+ UpdatePendingSnapshotsWithOfflineDataJobs(const ListValue* job_list) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(job_list); |
+ |
+ // Early return if there is no pending snapshot. |
+ if (!pending_snapshots_.size()) |
+ return; |
+ |
+ for (size_t index = 0; index < job_list->GetSize(); ++index) { |
+ std::string job_id; |
+ std::string snapshot_id; |
+ std::string download_url; |
+ if (!ParseSnapshotOfflineDataJobInformationFromCloudPrintFetchJobList( |
+ job_list, index, &job_id, &snapshot_id, &download_url)) { |
+ continue; |
+ } |
+ if (pending_snapshots_.find(snapshot_id) == pending_snapshots_.end()) |
+ return; |
+ SnapshotEntry* entry = pending_snapshots_[snapshot_id]; |
+ entry->UpdateWithOfflineDataJob(job_id, download_url); |
+ } |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::HandleDownloadComplete( |
+ chrome_to_mobile::CloudPrintRequest* source) { |
+ SnapshotMap::iterator iter = pending_snapshots_.begin(); |
+ for (; iter != pending_snapshots_.end(); ++iter) { |
+ if (iter->second->HandleDownloadComplete(source)) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::HandleDeleteComplete( |
+ chrome_to_mobile::CloudPrintRequest* source) { |
+ std::map<std::string, SnapshotEntry*>::iterator iter = |
+ pending_snapshots_.begin(); |
+ for (; iter != pending_snapshots_.end(); ++iter) { |
+ SnapshotEntry* snapshot_entry = iter->second; |
+ if (snapshot_entry->HandleDeleteComplete(source)) { |
+ if (snapshot_entry->HasCompleted()) { |
+ pending_snapshots_.erase(iter->first); |
+ delete snapshot_entry; |
+ } |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+void ChromeToMobileSnapshotsPollingFetcher::OnRequestComplete( |
+ chrome_to_mobile::CloudPrintRequest* source) { |
+ has_oauth2_token_fetch_failure_ = source->HasOAuth2AccessTokenFailure(); |
+ has_cloud_print_auth_error_ = source->HasCloudPrintAuthError(); |
+ if (has_oauth2_token_fetch_failure_ || has_cloud_print_auth_error_) { |
+ Cancel(); |
+ return; |
+ } |
+ |
+ if (!HandleFetchComplete(source)) { |
+ if (!HandleDownloadComplete(source)) |
+ HandleDeleteComplete(source); |
+ } |
+ |
+ bool has_completed = true; |
+ std::map<std::string, SnapshotEntry*>::const_iterator iter = |
+ pending_snapshots_.begin(); |
+ for (; iter != pending_snapshots_.end(); ++iter) { |
+ if (!iter->second->HasCompleted()) { |
+ has_completed = false; |
+ break; |
+ } |
+ } |
+ // Once completed, no request should come before |Fetch()| is called again. |
+ if (has_completed) { |
+ consumer_->OnSnapshotsFetchComplete(this); |
+ DeletePendingSnapshots(); |
+ } |
+} |
+ |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::HasCloudPrintAuthError() const { |
+ return has_cloud_print_auth_error_; |
+} |
+ |
+bool ChromeToMobileSnapshotsPollingFetcher::HasOAuth2AccessTokenFailure() |
+ const { |
+ return has_oauth2_token_fetch_failure_; |
+} |
+ |
+} // namespace chrome_to_mobile_receive |