Index: chrome/browser/printing/cloud_print/printer_job_handler.cc |
=================================================================== |
--- chrome/browser/printing/cloud_print/printer_job_handler.cc (revision 0) |
+++ chrome/browser/printing/cloud_print/printer_job_handler.cc (revision 0) |
@@ -0,0 +1,560 @@ |
+// Copyright (c) 2010 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/printing/cloud_print/printer_job_handler.h" |
+ |
+#include "base/file_util.h" |
+#include "base/json/json_reader.h" |
+#include "base/md5.h" |
+#include "base/string_util.h" |
+#include "base/utf_string_conversions.h" |
+#include "base/values.h" |
+#include "chrome/browser/printing/cloud_print/cloud_print_consts.h" |
+#include "chrome/browser/printing/cloud_print/cloud_print_helpers.h" |
+#include "chrome/browser/printing/cloud_print/job_status_updater.h" |
+#include "googleurl/src/gurl.h" |
+#include "net/http/http_response_headers.h" |
+ |
+PrinterJobHandler::PrinterJobHandler( |
+ const cloud_print::PrinterBasicInfo& printer_info, |
+ const std::string& printer_id, |
+ const std::string& caps_hash, |
+ const std::string& auth_token, |
+ Delegate* delegate) |
+ : printer_info_(printer_info), |
+ printer_id_(printer_id), |
+ auth_token_(auth_token), |
+ last_caps_hash_(caps_hash), |
+ delegate_(delegate), |
+ local_job_id_(-1), |
+ next_response_handler_(NULL), |
+ server_error_count_(0), |
+ print_thread_("Chrome_CloudPrintJobPrintThread"), |
+ shutting_down_(false), |
+ server_job_available_(false), |
+ printer_update_pending_(true), |
+ printer_delete_pending_(false), |
+ task_in_progress_(false) { |
+} |
+ |
+bool PrinterJobHandler::Initialize() { |
+ if (cloud_print::IsValidPrinter(printer_info_.printer_name)) { |
+ printer_change_notifier_.StartWatching(printer_info_.printer_name, this); |
+ NotifyJobAvailable(); |
+ } else { |
+ // This printer does not exist any more. Delete it from the server. |
+ OnPrinterDeleted(); |
+ } |
+ return true; |
+} |
+ |
+PrinterJobHandler::~PrinterJobHandler() { |
+ printer_change_notifier_.StopWatching(); |
+} |
+ |
+void PrinterJobHandler::Reset() { |
+ print_data_url_.clear(); |
+ job_details_.Clear(); |
+ request_.reset(); |
+ print_thread_.Stop(); |
+} |
+ |
+void PrinterJobHandler::Start() { |
+ if (task_in_progress_) { |
+ // Multiple Starts can get posted because of multiple notifications |
+ // We want to ignore the other ones that happen when a task is in progress. |
+ return; |
+ } |
+ Reset(); |
+ if (!shutting_down_) { |
+ // Check if we have work to do. |
+ if (HavePendingTasks()) { |
+ if (printer_delete_pending_) { |
+ printer_delete_pending_ = false; |
+ task_in_progress_ = true; |
+ MakeServerRequest( |
+ CloudPrintHelpers::GetUrlForPrinterDelete(printer_id_), |
+ &PrinterJobHandler::HandlePrinterDeleteResponse); |
+ } |
+ if (!task_in_progress_ && printer_update_pending_) { |
+ printer_update_pending_ = false; |
+ task_in_progress_ = UpdatePrinterInfo(); |
+ } |
+ if (!task_in_progress_ && server_job_available_) { |
+ task_in_progress_ = true; |
+ server_job_available_ = false; |
+ // We need to fetch any pending jobs for this printer |
+ MakeServerRequest(CloudPrintHelpers::GetUrlForJobFetch(printer_id_), |
+ &PrinterJobHandler::HandleJobMetadataResponse); |
+ } |
+ } |
+ } |
+} |
+ |
+void PrinterJobHandler::Stop() { |
+ task_in_progress_ = false; |
+ Reset(); |
+ if (HavePendingTasks()) { |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); |
+ } |
+} |
+ |
+void PrinterJobHandler::NotifyJobAvailable() { |
+ server_job_available_ = true; |
+ if (!task_in_progress_) { |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); |
+ } |
+} |
+ |
+bool PrinterJobHandler::UpdatePrinterInfo() { |
+ // We need to update the parts of the printer info that have changed |
+ // (could be printer name, description, status or capabilities). |
+ cloud_print::PrinterBasicInfo printer_info; |
+ printer_change_notifier_.GetCurrentPrinterInfo(&printer_info); |
+ cloud_print::PrinterCapsAndDefaults printer_caps; |
+ std::string post_data; |
+ std::string mime_boundary; |
+ if (cloud_print::GetPrinterCapsAndDefaults(printer_info.printer_name, |
+ &printer_caps)) { |
+ std::string caps_hash = MD5String(printer_caps.printer_capabilities); |
+ CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); |
+ if (caps_hash != last_caps_hash_) { |
+ // Hashes don't match, we need to upload new capabilities (the defaults |
+ // go for free along with the capabilities) |
+ last_caps_hash_ = caps_hash; |
+ CloudPrintHelpers::AddMultipartValueForUpload( |
+ kPrinterCapsValue, printer_caps.printer_capabilities, |
+ mime_boundary, printer_caps.caps_mime_type, &post_data); |
+ CloudPrintHelpers::AddMultipartValueForUpload( |
+ kPrinterDefaultsValue, printer_caps.printer_defaults, |
+ mime_boundary, printer_caps.defaults_mime_type, |
+ &post_data); |
+ CloudPrintHelpers::AddMultipartValueForUpload( |
+ WideToUTF8(kPrinterCapsHashValue).c_str(), caps_hash, mime_boundary, |
+ std::string(), &post_data); |
+ } |
+ } |
+ if (printer_info.printer_name != printer_info_.printer_name) { |
+ CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue, |
+ printer_info.printer_name, |
+ mime_boundary, |
+ std::string(), &post_data); |
+ } |
+ if (printer_info.printer_description != printer_info_.printer_description) { |
+ CloudPrintHelpers::AddMultipartValueForUpload( |
+ kPrinterDescValue, printer_info.printer_description, mime_boundary, |
+ std::string() , &post_data); |
+ } |
+ if (printer_info.printer_status != printer_info_.printer_status) { |
+ CloudPrintHelpers::AddMultipartValueForUpload( |
+ kPrinterStatusValue, StringPrintf("%d", printer_info.printer_status), |
+ mime_boundary, std::string(), &post_data); |
+ } |
+ printer_info_ = printer_info; |
+ bool ret = false; |
+ if (!post_data.empty()) { |
+ // Terminate the request body |
+ post_data.append("--" + mime_boundary + "--\r\n"); |
+ std::string mime_type("multipart/form-data; boundary="); |
+ mime_type += mime_boundary; |
+ request_.reset( |
+ new URLFetcher(CloudPrintHelpers::GetUrlForPrinterUpdate(printer_id_), |
+ URLFetcher::POST, this)); |
+ CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); |
+ request_->set_upload_data(mime_type, post_data); |
+ next_response_handler_ = &PrinterJobHandler::HandlePrinterUpdateResponse; |
+ request_->Start(); |
+ ret = true; |
+ } |
+ return ret; |
+} |
+ |
+// URLFetcher::Delegate implementation. |
+void PrinterJobHandler::OnURLFetchComplete( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ if (!shutting_down_) { |
+ DCHECK(source == request_.get()); |
+ // We need a next response handler because we are strictly a sequential |
+ // state machine. We need each response handler to tell us which state to |
+ // advance to next. |
+ DCHECK(next_response_handler_); |
+ if (!(this->*next_response_handler_)(source, url, status, |
+ response_code, cookies, data)) { |
+ // By contract, if the response handler returns false, it wants us to |
+ // retry the request (upto the usual limit after which we give up and |
+ // send the state machine to the Stop state); |
+ HandleServerError(url); |
+ } |
+ } |
+} |
+ |
+// JobStatusUpdater::Delegate implementation |
+bool PrinterJobHandler::OnJobCompleted(JobStatusUpdater* updater) { |
+ bool ret = false; |
+ for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin(); |
+ index != job_status_updater_list_.end(); index++) { |
+ if (index->get() == updater) { |
+ job_status_updater_list_.erase(index); |
+ ret = true; |
+ break; |
+ } |
+ } |
+ return ret; |
+} |
+ |
+ // cloud_print::PrinterChangeNotifier::Delegate implementation |
+void PrinterJobHandler::OnPrinterAdded() { |
+ // Should never get this notification for a printer |
+ NOTREACHED(); |
+} |
+ |
+void PrinterJobHandler::OnPrinterDeleted() { |
+ printer_delete_pending_ = true; |
+ if (!task_in_progress_) { |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); |
+ } |
+} |
+ |
+void PrinterJobHandler::OnPrinterChanged() { |
+ printer_update_pending_ = true; |
+ if (!task_in_progress_) { |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); |
+ } |
+} |
+ |
+void PrinterJobHandler::OnJobChanged() { |
+ // Some job on the printer changed. Loop through all our JobStatusUpdaters |
+ // and have them check for updates. |
+ for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin(); |
+ index != job_status_updater_list_.end(); index++) { |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(index->get(), |
+ &JobStatusUpdater::UpdateStatus)); |
+ } |
+} |
+ |
+bool PrinterJobHandler::HandlePrinterUpdateResponse( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ bool ret = false; |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (status.is_success() && (response_code == 200)) { |
+ bool succeeded = false; |
+ DictionaryValue* response_dict = NULL; |
+ CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); |
+ // If we get valid JSON back, we are done. |
+ if (NULL != response_dict) { |
+ ret = true; |
+ } |
+ } |
+ if (ret) { |
+ // We are done here. Go to the Stop state |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); |
+ } else { |
+ // Since we failed to update the server, set the flag again. |
+ printer_update_pending_ = true; |
+ } |
+ return ret; |
+} |
+ |
+bool PrinterJobHandler::HandlePrinterDeleteResponse( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ bool ret = false; |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (status.is_success() && (response_code == 200)) { |
+ bool succeeded = false; |
+ DictionaryValue* response_dict = NULL; |
+ CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); |
+ // If we get valid JSON back, we are done. |
+ if (NULL != response_dict) { |
+ ret = true; |
+ } |
+ } |
+ if (ret) { |
+ // The printer has been deleted. Shutdown the handler class. |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Shutdown)); |
+ } else { |
+ // Since we failed to update the server, set the flag again. |
+ printer_delete_pending_ = true; |
+ } |
+ return ret; |
+} |
+ |
+bool PrinterJobHandler::HandleJobMetadataResponse( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (!status.is_success() || (response_code != 200)) { |
+ return false; |
+ } |
+ bool succeeded = false; |
+ DictionaryValue* response_dict = NULL; |
+ CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); |
+ if (NULL == response_dict) { |
+ // If we did not get a valid JSON response, we need to retry. |
+ return false; |
+ } |
+ Task* next_task = NULL; |
+ if (succeeded) { |
+ ListValue* job_list = NULL; |
+ response_dict->GetList(kJobListValue, &job_list); |
+ if (job_list) { |
+ // Even though it is a job list, for now we are only interested in the |
+ // first job |
+ DictionaryValue* job_data = NULL; |
+ if (job_list->GetDictionary(0, &job_data)) { |
+ job_data->GetString(kIdValue, &job_details_.job_id_); |
+ job_data->GetString(kTitleValue, &job_details_.job_title_); |
+ std::string print_ticket_url; |
+ job_data->GetString(kTicketUrlValue, &print_ticket_url); |
+ job_data->GetString(kFileUrlValue, &print_data_url_); |
+ next_task = NewRunnableMethod( |
+ this, &PrinterJobHandler::MakeServerRequest, |
+ GURL(print_ticket_url.c_str()), |
+ &PrinterJobHandler::HandlePrintTicketResponse); |
+ } |
+ } |
+ } |
+ if (!next_task) { |
+ // If we got a valid JSON but there were no jobs, we are done |
+ next_task = NewRunnableMethod(this, &PrinterJobHandler::Stop); |
+ } |
+ delete response_dict; |
+ DCHECK(next_task); |
+ MessageLoop::current()->PostTask(FROM_HERE, next_task); |
+ return true; |
+} |
+ |
+bool PrinterJobHandler::HandlePrintTicketResponse( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (!status.is_success() || (response_code != 200)) { |
+ return false; |
+ } |
+ if (cloud_print::ValidatePrintTicket(printer_info_.printer_name, data)) { |
+ job_details_.print_ticket_ = data; |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ NewRunnableMethod(this, |
+ &PrinterJobHandler::MakeServerRequest, |
+ GURL(print_data_url_.c_str()), |
+ &PrinterJobHandler::HandlePrintDataResponse)); |
+ } else { |
+ // The print ticket was not valid. We are done here. |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::JobFailed, |
+ INVALID_JOB_DATA)); |
+ } |
+ return true; |
+} |
+ |
+bool PrinterJobHandler::HandlePrintDataResponse(const URLFetcher* source, |
+ const GURL& url, |
+ const URLRequestStatus& status, |
+ int response_code, |
+ const ResponseCookies& cookies, |
+ const std::string& data) { |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (!status.is_success() || (response_code != 200)) { |
+ return false; |
+ } |
+ Task* next_task = NULL; |
+ if (file_util::CreateTemporaryFile(&job_details_.print_data_file_path_)) { |
+ int ret = file_util::WriteFile(job_details_.print_data_file_path_, |
+ data.c_str(), |
+ data.length()); |
+ source->response_headers()->GetMimeType( |
+ &job_details_.print_data_mime_type_); |
+ DCHECK(ret == static_cast<int>(data.length())); |
+ if (ret == static_cast<int>(data.length())) { |
+ next_task = NewRunnableMethod(this, &PrinterJobHandler::StartPrinting); |
+ } |
+ } |
+ // If there was no task allocated above, then there was an error in |
+ // saving the print data, bail out here. |
+ if (!next_task) { |
+ next_task = NewRunnableMethod(this, &PrinterJobHandler::JobFailed, |
+ JOB_DOWNLOAD_FAILED); |
+ } |
+ MessageLoop::current()->PostTask(FROM_HERE, next_task); |
+ return true; |
+} |
+ |
+void PrinterJobHandler::StartPrinting() { |
+ // We are done with the request object for now. |
+ request_.reset(); |
+ if (!shutting_down_) { |
+ if (!print_thread_.Start()) { |
+ JobFailed(PRINT_FAILED); |
+ } else { |
+ print_thread_.message_loop()->PostTask( |
+ FROM_HERE, NewRunnableFunction(&PrinterJobHandler::DoPrint, |
+ job_details_, |
+ printer_info_.printer_name, this, |
+ MessageLoop::current())); |
+ } |
+ } |
+} |
+ |
+void PrinterJobHandler::JobFailed(PrintJobError error) { |
+ if (!shutting_down_) { |
+ UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_ERROR, error); |
+ } |
+} |
+ |
+void PrinterJobHandler::JobSpooled(cloud_print::PlatformJobId local_job_id) { |
+ if (!shutting_down_) { |
+ local_job_id_ = local_job_id; |
+ UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_IN_PROGRESS, SUCCESS); |
+ print_thread_.Stop(); |
+ } |
+} |
+ |
+void PrinterJobHandler::Shutdown() { |
+ Reset(); |
+ shutting_down_ = true; |
+ while (!job_status_updater_list_.empty()) { |
+ // Calling Stop() will cause the OnJobCompleted to be called which will |
+ // remove the updater object from the list. |
+ job_status_updater_list_.front()->Stop(); |
+ } |
+ if (delegate_) { |
+ delegate_->OnPrinterJobHandlerShutdown(this, printer_id_); |
+ } |
+} |
+ |
+void PrinterJobHandler::HandleServerError(const GURL& url) { |
+ Task* task_to_retry = NewRunnableMethod(this, |
+ &PrinterJobHandler::MakeServerRequest, |
+ url, next_response_handler_); |
+ Task* task_on_give_up = NewRunnableMethod(this, &PrinterJobHandler::Stop); |
+ CloudPrintHelpers::HandleServerError(&server_error_count_, kMaxRetryCount, |
+ -1, kBaseRetryInterval, task_to_retry, |
+ task_on_give_up); |
+} |
+ |
+void PrinterJobHandler::UpdateJobStatus(cloud_print::PrintJobStatus status, |
+ PrintJobError error) { |
+ if (!shutting_down_) { |
+ if (!job_details_.job_id_.empty()) { |
+ ResponseHandler response_handler = NULL; |
+ if (error == SUCCESS) { |
+ response_handler = |
+ &PrinterJobHandler::HandleSuccessStatusUpdateResponse; |
+ } else { |
+ response_handler = |
+ &PrinterJobHandler::HandleFailureStatusUpdateResponse; |
+ } |
+ MakeServerRequest( |
+ CloudPrintHelpers::GetUrlForJobStatusUpdate(job_details_.job_id_, |
+ status), |
+ response_handler); |
+ } |
+ } |
+} |
+ |
+bool PrinterJobHandler::HandleSuccessStatusUpdateResponse( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (!status.is_success() || (response_code != 200)) { |
+ return false; |
+ } |
+ // The print job has been spooled locally. We now need to create an object |
+ // that monitors the status of the job and updates the server. |
+ scoped_refptr<JobStatusUpdater> job_status_updater = |
+ new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_, |
+ local_job_id_, auth_token_, this); |
+ job_status_updater_list_.push_back(job_status_updater); |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(job_status_updater.get(), |
+ &JobStatusUpdater::UpdateStatus)); |
+ bool succeeded = false; |
+ CloudPrintHelpers::ParseResponseJSON(data, &succeeded, NULL); |
+ if (succeeded) { |
+ // Since we just printed successfully, we want to look for more jobs. |
+ server_job_available_ = true; |
+ } |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); |
+ return true; |
+} |
+ |
+bool PrinterJobHandler::HandleFailureStatusUpdateResponse( |
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
+ int response_code, const ResponseCookies& cookies, |
+ const std::string& data) { |
+ // If there was a network error or a non-200 response (which, for our purposes |
+ // is the same as a network error), we want to retry. |
+ if (!status.is_success() || (response_code != 200)) { |
+ return false; |
+ } |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); |
+ return true; |
+} |
+ |
+void PrinterJobHandler::MakeServerRequest(const GURL& url, |
+ ResponseHandler response_handler) { |
+ if (!shutting_down_) { |
+ request_.reset(new URLFetcher(url, URLFetcher::GET, this)); |
+ server_error_count_ = 0; |
+ CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); |
+ // Set up the next response handler |
+ next_response_handler_ = response_handler; |
+ request_->Start(); |
+ } |
+} |
+ |
+bool PrinterJobHandler::HavePendingTasks() { |
+ return server_job_available_ || printer_update_pending_ || |
+ printer_delete_pending_; |
+} |
+ |
+ |
+void PrinterJobHandler::DoPrint(const JobDetails& job_details, |
+ const std::string& printer_name, |
+ PrinterJobHandler* job_handler, |
+ MessageLoop* job_message_loop) { |
+ DCHECK(job_handler); |
+ DCHECK(job_message_loop); |
+ cloud_print::PlatformJobId job_id = -1; |
+ if (cloud_print::SpoolPrintJob(job_details.print_ticket_, |
+ job_details.print_data_file_path_, |
+ job_details.print_data_mime_type_, |
+ printer_name, |
+ job_details.job_title_, &job_id)) { |
+ job_message_loop->PostTask(FROM_HERE, |
+ NewRunnableMethod(job_handler, |
+ &PrinterJobHandler::JobSpooled, |
+ job_id)); |
+ } else { |
+ job_message_loop->PostTask(FROM_HERE, |
+ NewRunnableMethod(job_handler, |
+ &PrinterJobHandler::JobFailed, |
+ PRINT_FAILED)); |
+ } |
+} |
+ |
Property changes on: chrome\browser\printing\cloud_print\printer_job_handler.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |