diff options
author | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-13 17:18:01 +0000 |
---|---|---|
committer | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-13 17:18:01 +0000 |
commit | 8b50bd202626cf978685197adee0d8f32161443b (patch) | |
tree | fe543b2bba72103859baefb8e149814e3e35e2ee /chrome/service/cloud_print/printer_job_handler.cc | |
parent | d94ccedd108876d5be652dfa8129df54cab555e9 (diff) | |
download | chromium_src-8b50bd202626cf978685197adee0d8f32161443b.zip chromium_src-8b50bd202626cf978685197adee0d8f32161443b.tar.gz chromium_src-8b50bd202626cf978685197adee0d8f32161443b.tar.bz2 |
Moved cloud print code from the chrome/browser/printing/cloud_print to chrome/service/cloud_print
BUG=None.
TEST=None.
Review URL: http://codereview.chromium.org/2007012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47155 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/service/cloud_print/printer_job_handler.cc')
-rw-r--r-- | chrome/service/cloud_print/printer_job_handler.cc | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/chrome/service/cloud_print/printer_job_handler.cc b/chrome/service/cloud_print/printer_job_handler.cc new file mode 100644 index 0000000..09b649f --- /dev/null +++ b/chrome/service/cloud_print/printer_job_handler.cc @@ -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/service/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/service/cloud_print/cloud_print_consts.h" +#include "chrome/service/cloud_print/cloud_print_helpers.h" +#include "chrome/service/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)); + } +} + |