blob: 3128b2abd54807f65b18be223fa75506f94da079 [file] [log] [blame]
// Copyright (c) 2011 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/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/net/http_return.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::JobDetails::JobDetails() {}
PrinterJobHandler::JobDetails::~JobDetails() {}
void PrinterJobHandler::JobDetails::Clear() {
job_id_.clear();
job_title_.clear();
print_ticket_.clear();
print_data_mime_type_.clear();
print_data_file_path_ = FilePath();
tags_.clear();
}
PrinterJobHandler::PrinterJobHandler(
const printing::PrinterBasicInfo& printer_info,
const PrinterInfoFromCloud& printer_info_cloud,
const GURL& cloud_print_server_url,
cloud_print::PrintSystem* print_system,
Delegate* delegate)
: print_system_(print_system),
printer_info_(printer_info),
printer_info_cloud_(printer_info_cloud),
cloud_print_server_url_(cloud_print_server_url),
delegate_(delegate),
local_job_id_(-1),
next_json_data_handler_(NULL),
next_data_handler_(NULL),
server_error_count_(0),
print_thread_("Chrome_CloudPrintJobPrintThread"),
job_handler_message_loop_proxy_(
base::MessageLoopProxy::current()),
shutting_down_(false),
job_check_pending_(false),
printer_update_pending_(true),
printer_delete_pending_(false),
task_in_progress_(false) {
}
bool PrinterJobHandler::Initialize() {
if (print_system_->IsValidPrinter(
printer_info_.printer_name)) {
printer_watcher_ = print_system_->CreatePrinterWatcher(
printer_info_.printer_name);
printer_watcher_->StartWatching(this);
CheckForJobs(kJobFetchReasonStartup);
} else {
// This printer does not exist any more. Check if we should delete it from
// the server.
bool delete_from_server = false;
delegate_->OnPrinterNotFound(printer_info_.printer_name,
&delete_from_server);
if (delete_from_server)
OnPrinterDeleted();
}
return true;
}
PrinterJobHandler::~PrinterJobHandler() {
if (printer_watcher_)
printer_watcher_->StopWatching();
}
void PrinterJobHandler::Reset() {
print_data_url_.clear();
job_details_.Clear();
request_ = NULL;
print_thread_.Stop();
}
void PrinterJobHandler::Start() {
VLOG(1) << "CP_PROXY: Start printer job handler, id: "
<< printer_info_cloud_.printer_id
<< ", task in progress: " << task_in_progress_;
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;
SetNextJSONHandler(&PrinterJobHandler::HandlePrinterDeleteResponse);
request_ = new CloudPrintURLFetcher;
request_->StartGetRequest(
CloudPrintHelpers::GetUrlForPrinterDelete(
cloud_print_server_url_, printer_info_cloud_.printer_id),
this,
kCloudPrintAPIMaxRetryCount,
std::string());
}
if (!task_in_progress_ && printer_update_pending_) {
printer_update_pending_ = false;
task_in_progress_ = UpdatePrinterInfo();
}
if (!task_in_progress_ && job_check_pending_) {
task_in_progress_ = true;
job_check_pending_ = false;
// We need to fetch any pending jobs for this printer
SetNextJSONHandler(&PrinterJobHandler::HandleJobMetadataResponse);
request_ = new CloudPrintURLFetcher;
request_->StartGetRequest(
CloudPrintHelpers::GetUrlForJobFetch(
cloud_print_server_url_, printer_info_cloud_.printer_id,
job_fetch_reason_),
this,
kCloudPrintAPIMaxRetryCount,
std::string());
last_job_fetch_time_ = base::TimeTicks::Now();
VLOG(1) << "Last job fetch time for printer "
<< printer_info_.printer_name.c_str() << " is "
<< last_job_fetch_time_.ToInternalValue();
job_fetch_reason_.clear();
}
}
}
}
void PrinterJobHandler::Stop() {
VLOG(1) << "CP_PROXY: Stop printer job handler, id: "
<< printer_info_cloud_.printer_id;
task_in_progress_ = false;
Reset();
if (HavePendingTasks()) {
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start));
}
}
void PrinterJobHandler::CheckForJobs(const std::string& reason) {
VLOG(1) << "CP_PROXY: CheckForJobs, id: "
<< printer_info_cloud_.printer_id
<< ", reason: " << reason
<< ", task in progress: " << task_in_progress_;
job_fetch_reason_ = reason;
job_check_pending_ = true;
if (!task_in_progress_) {
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start));
}
}
bool PrinterJobHandler::UpdatePrinterInfo() {
if (!printer_watcher_) {
LOG(ERROR) << "CP_PROXY: Printer watcher is missing."
<< "Check printer server url for printer id: "
<< printer_info_cloud_.printer_id;
return false;
}
VLOG(1) << "CP_PROXY: Update printer info, id: "
<< printer_info_cloud_.printer_id;
// We need to update the parts of the printer info that have changed
// (could be printer name, description, status or capabilities).
// First asynchronously fetch the capabilities.
printing::PrinterBasicInfo printer_info;
printer_watcher_->GetCurrentPrinterInfo(&printer_info);
cloud_print::PrintSystem::PrinterCapsAndDefaultsCallback* callback =
NewCallback(this,
&PrinterJobHandler::OnReceivePrinterCaps);
// Asnchronously fetch the printer caps and defaults. The story will
// continue in OnReceivePrinterCaps.
print_system_->GetPrinterCapsAndDefaults(
printer_info.printer_name.c_str(), callback);
// While we are waiting for the data, pretend we have work to do and return
// true.
return true;
}
void PrinterJobHandler::OnReceivePrinterCaps(
bool succeeded,
const std::string& printer_name,
const printing::PrinterCapsAndDefaults& caps_and_defaults) {
printing::PrinterBasicInfo printer_info;
if (printer_watcher_)
printer_watcher_->GetCurrentPrinterInfo(&printer_info);
std::string post_data;
std::string mime_boundary;
CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary);
if (succeeded) {
std::string caps_hash =
base::MD5String(caps_and_defaults.printer_capabilities);
if (caps_hash != printer_info_cloud_.caps_hash) {
// Hashes don't match, we need to upload new capabilities (the defaults
// go for free along with the capabilities)
printer_info_cloud_.caps_hash = caps_hash;
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterCapsValue, caps_and_defaults.printer_capabilities,
mime_boundary, caps_and_defaults.caps_mime_type, &post_data);
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterDefaultsValue, caps_and_defaults.printer_defaults,
mime_boundary, caps_and_defaults.defaults_mime_type,
&post_data);
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterCapsHashValue, caps_hash, mime_boundary, std::string(),
&post_data);
}
} else {
LOG(ERROR) << "Failed to get printer caps and defaults for printer: "
<< printer_name;
}
std::string tags_hash =
CloudPrintHelpers::GenerateHashOfStringMap(printer_info.options);
if (tags_hash != printer_info_cloud_.tags_hash) {
printer_info_cloud_.tags_hash = tags_hash;
CloudPrintHelpers::GenerateMultipartPostDataForPrinterTags(
printer_info.options, mime_boundary, &post_data);
// Remove all the exising proxy tags.
std::string cp_tag_wildcard(kProxyTagPrefix);
cp_tag_wildcard += ".*";
CloudPrintHelpers::AddMultipartValueForUpload(
kPrinterRemoveTagValue, cp_tag_wildcard, 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,
base::StringPrintf("%d", printer_info.printer_status),
mime_boundary,
std::string(),
&post_data);
}
printer_info_ = printer_info;
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;
SetNextJSONHandler(&PrinterJobHandler::HandlePrinterUpdateResponse);
request_ = new CloudPrintURLFetcher;
request_->StartPostRequest(
CloudPrintHelpers::GetUrlForPrinterUpdate(
cloud_print_server_url_, printer_info_cloud_.printer_id),
this,
kCloudPrintAPIMaxRetryCount,
mime_type,
post_data,
std::string());
} else {
// We are done here. Go to the Stop state
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
}
}
// CloudPrintURLFetcher::Delegate implementation.
CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawResponse(
const content::URLFetcher* source,
const GURL& url,
const net::URLRequestStatus& status,
int response_code,
const net::ResponseCookies& cookies,
const std::string& data) {
// 415 (Unsupported media type) error while fetching data from the server
// means data conversion error. Stop fetching process and mark job as error.
if (next_data_handler_ == (&PrinterJobHandler::HandlePrintDataResponse) &&
response_code == RC_UNSUPPORTED_MEDIA_TYPE) {
MessageLoop::current()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &PrinterJobHandler::JobFailed,
JOB_DOWNLOAD_FAILED));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
return CloudPrintURLFetcher::CONTINUE_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawData(
const content::URLFetcher* source,
const GURL& url,
const std::string& data) {
if (!next_data_handler_)
return CloudPrintURLFetcher::CONTINUE_PROCESSING;
return (this->*next_data_handler_)(source, url, data);
}
CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleJSONData(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
DCHECK(next_json_data_handler_);
return (this->*next_json_data_handler_)(source,
url,
json_data,
succeeded);
}
void PrinterJobHandler::OnRequestGiveUp() {
// The only time we call CloudPrintURLFetcher::StartGetRequest() with a
// specified number of retries, is when we are trying to fetch print job
// data. So, this function will be reached only if we failed to get job data.
// In that case, we should make job as error and should not try it later.
MessageLoop::current()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &PrinterJobHandler::JobFailed,
JOB_DOWNLOAD_FAILED));
}
void PrinterJobHandler::OnRequestAuthError() {
OnAuthError();
}
// 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;
}
void PrinterJobHandler::OnAuthError() {
if (delegate_)
delegate_->OnAuthError();
}
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));
}
}
// Begin Response handlers
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandlePrinterUpdateResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
VLOG(1) << "CP_PROXY: Handle printer update response, id: "
<< printer_info_cloud_.printer_id;
// We are done here. Go to the Stop state
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandlePrinterDeleteResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
VLOG(1) << "CP_PROXY: Handler printer delete response, id: "
<< printer_info_cloud_.printer_id;
// The printer has been deleted. Shutdown the handler class.
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Shutdown));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandleJobMetadataResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
VLOG(1) << "CP_PROXY: Handle job metadata response, id: "
<< printer_info_cloud_.printer_id;
bool job_available = false;
if (succeeded) {
ListValue* job_list = NULL;
if (json_data->GetList(kJobListValue, &job_list) && 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_available = true;
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_);
// Get tags for print job.
job_details_.tags_.clear();
ListValue* tags = NULL;
if (job_data->GetList(kTagsValue, &tags)) {
for (size_t i = 0; i < tags->GetSize(); i++) {
std::string value;
if (tags->GetString(i, &value))
job_details_.tags_.push_back(value);
}
}
SetNextDataHandler(&PrinterJobHandler::HandlePrintTicketResponse);
request_ = new CloudPrintURLFetcher;
request_->StartGetRequest(GURL(print_ticket_url.c_str()),
this,
kCloudPrintAPIMaxRetryCount,
std::string());
}
}
}
// If no jobs are available, go to the Stop state.
if (!job_available)
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandlePrintTicketResponse(const content::URLFetcher* source,
const GURL& url,
const std::string& data) {
VLOG(1) << "CP_PROXY: Handle print ticket response, id: "
<< printer_info_cloud_.printer_id;
if (print_system_->ValidatePrintTicket(printer_info_.printer_name, data)) {
job_details_.print_ticket_ = data;
SetNextDataHandler(&PrinterJobHandler::HandlePrintDataResponse);
request_ = new CloudPrintURLFetcher;
std::string accept_headers = "Accept: ";
accept_headers += print_system_->GetSupportedMimeTypes();
request_->StartGetRequest(GURL(print_data_url_.c_str()),
this,
kJobDataMaxRetryCount,
accept_headers);
} else {
// The print ticket was not valid. We are done here.
FailedFetchingJobData();
}
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandlePrintDataResponse(const content::URLFetcher* source,
const GURL& url,
const std::string& data) {
VLOG(1) << "CP_PROXY: Handle print data response, id: "
<< printer_info_cloud_.printer_id;
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->GetResponseHeaders()->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 CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandleSuccessStatusUpdateResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
VLOG(1) << "CP_PROXY: Handle success status update response, id: "
<< printer_info_cloud_.printer_id;
// 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_, cloud_print_server_url_,
print_system_.get(), this));
job_status_updater_list_.push_back(job_status_updater);
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(job_status_updater.get(),
&JobStatusUpdater::UpdateStatus));
if (succeeded) {
// Since we just printed successfully, we want to look for more jobs.
CheckForJobs(kJobFetchReasonQueryMore);
}
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
CloudPrintURLFetcher::ResponseAction
PrinterJobHandler::HandleFailureStatusUpdateResponse(
const content::URLFetcher* source,
const GURL& url,
DictionaryValue* json_data,
bool succeeded) {
VLOG(1) << "CP_PROXY: Handle failure status update response, id: "
<< printer_info_cloud_.printer_id;
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
return CloudPrintURLFetcher::STOP_PROCESSING;
}
// End Response handlers
void PrinterJobHandler::StartPrinting() {
VLOG(1) << "CP_PROXY: Start printing, id: " << printer_info_cloud_.printer_id;
// We are done with the request object for now.
request_ = NULL;
if (!shutting_down_) {
if (!print_thread_.Start()) {
JobFailed(PRINT_FAILED);
} else {
print_thread_.message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::DoPrint,
job_details_,
printer_info_.printer_name));
}
}
}
void PrinterJobHandler::JobFailed(PrintJobError error) {
VLOG(1) << "CP_PROXY: Job failed, id: " << printer_info_cloud_.printer_id;
if (!shutting_down_) {
UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_ERROR, error);
}
}
void PrinterJobHandler::JobSpooled(cloud_print::PlatformJobId local_job_id) {
VLOG(1) << "CP_PROXY: Job spooled, printer id: "
<< printer_info_cloud_.printer_id << ", job id: " << 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() {
VLOG(1) << "CP_PROXY: Printer job handler shutdown, id: "
<< printer_info_cloud_.printer_id;
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_info_cloud_.printer_id);
}
}
void PrinterJobHandler::UpdateJobStatus(cloud_print::PrintJobStatus status,
PrintJobError error) {
VLOG(1) << "CP_PROXY: Update job status, id: "
<< printer_info_cloud_.printer_id;
if (!shutting_down_) {
if (!job_details_.job_id_.empty()) {
VLOG(1) << "CP_PROXY: Updating status, job id: " << job_details_.job_id_
<< ", status: " << status;
if (error == SUCCESS) {
SetNextJSONHandler(
&PrinterJobHandler::HandleSuccessStatusUpdateResponse);
} else {
SetNextJSONHandler(
&PrinterJobHandler::HandleFailureStatusUpdateResponse);
}
request_ = new CloudPrintURLFetcher;
request_->StartGetRequest(
CloudPrintHelpers::GetUrlForJobStatusUpdate(cloud_print_server_url_,
job_details_.job_id_,
status),
this,
kCloudPrintAPIMaxRetryCount,
std::string());
}
}
}
void PrinterJobHandler::SetNextJSONHandler(JSONDataHandler handler) {
next_json_data_handler_ = handler;
next_data_handler_ = NULL;
}
void PrinterJobHandler::SetNextDataHandler(DataHandler handler) {
next_data_handler_ = handler;
next_json_data_handler_ = NULL;
}
bool PrinterJobHandler::HavePendingTasks() {
return (job_check_pending_ || printer_update_pending_ ||
printer_delete_pending_);
}
void PrinterJobHandler::FailedFetchingJobData() {
if (!shutting_down_) {
LOG(ERROR) << "CP_PROXY: Failed fetching job data for printer: " <<
printer_info_.printer_name << ", job id: " << job_details_.job_id_;
JobFailed(INVALID_JOB_DATA);
}
}
// The following methods are called on |print_thread_|. It is not safe to
// access any members other than |job_handler_message_loop_proxy_|,
// |job_spooler_| and |print_system_|.
void PrinterJobHandler::DoPrint(const JobDetails& job_details,
const std::string& printer_name) {
job_spooler_ = print_system_->CreateJobSpooler();
DCHECK(job_spooler_);
if (!job_spooler_ || !job_spooler_->Spool(job_details.print_ticket_,
job_details.print_data_file_path_,
job_details.print_data_mime_type_,
printer_name,
job_details.job_title_,
job_details.tags_,
this)) {
OnJobSpoolFailed();
}
}
void PrinterJobHandler::OnJobSpoolSucceeded(
const cloud_print::PlatformJobId& job_id) {
DCHECK(MessageLoop::current() == print_thread_.message_loop());
job_spooler_ = NULL;
job_handler_message_loop_proxy_->PostTask(FROM_HERE,
NewRunnableMethod(this,
&PrinterJobHandler::JobSpooled,
job_id));
}
void PrinterJobHandler::OnJobSpoolFailed() {
DCHECK(MessageLoop::current() == print_thread_.message_loop());
job_spooler_ = NULL;
job_handler_message_loop_proxy_->PostTask(FROM_HERE,
NewRunnableMethod(this,
&PrinterJobHandler::JobFailed,
PRINT_FAILED));
}