Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6928)

Unified Diff: chrome/browser/printing/cloud_print/printer_job_handler.cc

Issue 1566047: First cut of Cloud Print Proxy implementation. The code is not enabled for no... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Final review changes Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/printing/cloud_print/printer_job_handler.h ('k') | chrome/browser/profile.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « chrome/browser/printing/cloud_print/printer_job_handler.h ('k') | chrome/browser/profile.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698