UMA instrumentation for CloudPrint connector.
[chromium-blink-merge.git] / chrome / service / cloud_print / printer_job_handler.cc
blob53d88d558f1a5e700a4f9da2fcfd905c913bc87a
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/service/cloud_print/printer_job_handler.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/json/json_reader.h"
11 #include "base/md5.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/common/cloud_print/cloud_print_constants.h"
17 #include "chrome/common/cloud_print/cloud_print_helpers.h"
18 #include "chrome/service/cloud_print/cloud_print_helpers.h"
19 #include "chrome/service/cloud_print/job_status_updater.h"
20 #include "grit/generated_resources.h"
21 #include "net/base/mime_util.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_status_code.h"
24 #include "printing/backend/print_backend.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "url/gurl.h"
28 namespace cloud_print {
30 namespace {
32 base::subtle::Atomic32 g_total_jobs_started = 0;
33 base::subtle::Atomic32 g_total_jobs_done = 0;
35 enum PrinterJobHandlerEvent {
36 JOB_HANDLER_CHECK_FOR_JOBS,
37 JOB_HANDLER_START,
38 JOB_HANDLER_PENDING_TASK,
39 JOB_HANDLER_PRINTER_UPDATE,
40 JOB_HANDLER_JOB_CHECK,
41 JOB_HANDLER_JOB_STARTED,
42 JOB_HANDLER_VALID_TICKET,
43 JOB_HANDLER_DATA,
44 JOB_HANDLER_SET_IN_PROGRESS,
45 JOB_HANDLER_SET_START_PRINTING,
46 JOB_HANDLER_START_SPOOLING,
47 JOB_HANDLER_SPOOLED,
48 JOB_HANDLER_JOB_COMPLETED,
49 JOB_HANDLER_INVALID_TICKET,
50 JOB_HANDLER_INVALID_DATA,
51 JOB_HANDLER_MAX,
54 } // namespace
56 PrinterJobHandler::PrinterInfoFromCloud::PrinterInfoFromCloud()
57 : current_xmpp_timeout(0), pending_xmpp_timeout(0) {
60 PrinterJobHandler::PrinterJobHandler(
61 const printing::PrinterBasicInfo& printer_info,
62 const PrinterInfoFromCloud& printer_info_cloud,
63 const GURL& cloud_print_server_url,
64 PrintSystem* print_system,
65 Delegate* delegate)
66 : print_system_(print_system),
67 printer_info_(printer_info),
68 printer_info_cloud_(printer_info_cloud),
69 cloud_print_server_url_(cloud_print_server_url),
70 delegate_(delegate),
71 local_job_id_(-1),
72 next_json_data_handler_(NULL),
73 next_data_handler_(NULL),
74 server_error_count_(0),
75 print_thread_("Chrome_CloudPrintJobPrintThread"),
76 job_handler_message_loop_proxy_(
77 base::MessageLoopProxy::current()),
78 shutting_down_(false),
79 job_check_pending_(false),
80 printer_update_pending_(true),
81 task_in_progress_(false),
82 weak_ptr_factory_(this) {
85 bool PrinterJobHandler::Initialize() {
86 if (!print_system_->IsValidPrinter(printer_info_.printer_name))
87 return false;
89 printer_watcher_ = print_system_->CreatePrinterWatcher(
90 printer_info_.printer_name);
91 printer_watcher_->StartWatching(this);
92 CheckForJobs(kJobFetchReasonStartup);
93 return true;
96 std::string PrinterJobHandler::GetPrinterName() const {
97 return printer_info_.printer_name;
100 void PrinterJobHandler::CheckForJobs(const std::string& reason) {
101 VLOG(1) << "CP_CONNECTOR: Checking for jobs"
102 << ", printer id: " << printer_info_cloud_.printer_id
103 << ", reason: " << reason
104 << ", task in progress: " << task_in_progress_;
105 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
106 JOB_HANDLER_CHECK_FOR_JOBS, JOB_HANDLER_MAX);
107 job_fetch_reason_ = reason;
108 job_check_pending_ = true;
109 if (!task_in_progress_) {
110 base::MessageLoop::current()->PostTask(
111 FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
115 void PrinterJobHandler::Shutdown() {
116 VLOG(1) << "CP_CONNECTOR: Shutting down printer job handler"
117 << ", printer id: " << printer_info_cloud_.printer_id;
118 Reset();
119 shutting_down_ = true;
120 while (!job_status_updater_list_.empty()) {
121 // Calling Stop() will cause the OnJobCompleted to be called which will
122 // remove the updater object from the list.
123 job_status_updater_list_.front()->Stop();
127 // CloudPrintURLFetcher::Delegate implementation.
128 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawResponse(
129 const net::URLFetcher* source,
130 const GURL& url,
131 const net::URLRequestStatus& status,
132 int response_code,
133 const net::ResponseCookies& cookies,
134 const std::string& data) {
135 // 415 (Unsupported media type) error while fetching data from the server
136 // means data conversion error. Stop fetching process and mark job as error.
137 if (next_data_handler_ == (&PrinterJobHandler::HandlePrintDataResponse) &&
138 response_code == net::HTTP_UNSUPPORTED_MEDIA_TYPE) {
139 VLOG(1) << "CP_CONNECTOR: Job failed (unsupported media type)";
140 base::MessageLoop::current()->PostTask(
141 FROM_HERE,
142 base::Bind(&PrinterJobHandler::JobFailed, this, JOB_DOWNLOAD_FAILED));
143 return CloudPrintURLFetcher::STOP_PROCESSING;
145 return CloudPrintURLFetcher::CONTINUE_PROCESSING;
148 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawData(
149 const net::URLFetcher* source,
150 const GURL& url,
151 const std::string& data) {
152 if (!next_data_handler_)
153 return CloudPrintURLFetcher::CONTINUE_PROCESSING;
154 return (this->*next_data_handler_)(source, url, data);
157 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleJSONData(
158 const net::URLFetcher* source,
159 const GURL& url,
160 DictionaryValue* json_data,
161 bool succeeded) {
162 DCHECK(next_json_data_handler_);
163 return (this->*next_json_data_handler_)(source, url, json_data, succeeded);
166 // Mark the job fetch as failed and check if other jobs can be printed
167 void PrinterJobHandler::OnRequestGiveUp() {
168 if (job_queue_handler_.JobFetchFailed(job_details_.job_id_)) {
169 VLOG(1) << "CP_CONNECTOR: Job failed to load (scheduling retry)";
170 CheckForJobs(kJobFetchReasonFailure);
171 base::MessageLoop::current()->PostTask(
172 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
173 } else {
174 VLOG(1) << "CP_CONNECTOR: Job failed (giving up after " <<
175 kNumRetriesBeforeAbandonJob << " retries)";
176 base::MessageLoop::current()->PostTask(
177 FROM_HERE,
178 base::Bind(&PrinterJobHandler::JobFailed, this, JOB_DOWNLOAD_FAILED));
182 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::OnRequestAuthError() {
183 // We got an Auth error and have no idea how long it will take to refresh
184 // auth information (may take forever). We'll drop current request and
185 // propagate this error to the upper level. After auth issues will be
186 // resolved, GCP connector will restart.
187 OnAuthError();
188 return CloudPrintURLFetcher::STOP_PROCESSING;
191 std::string PrinterJobHandler::GetAuthHeader() {
192 return GetCloudPrintAuthHeaderFromStore();
195 // JobStatusUpdater::Delegate implementation
196 bool PrinterJobHandler::OnJobCompleted(JobStatusUpdater* updater) {
197 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
198 JOB_HANDLER_JOB_COMPLETED, JOB_HANDLER_MAX);
199 UMA_HISTOGRAM_LONG_TIMES("CloudPrint.PrintingTime",
200 base::Time::Now() - updater->start_time());
201 bool ret = false;
202 base::subtle::NoBarrier_AtomicIncrement(&g_total_jobs_done, 1);
203 job_queue_handler_.JobDone(job_details_.job_id_);
205 for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
206 index != job_status_updater_list_.end(); index++) {
207 if (index->get() == updater) {
208 job_status_updater_list_.erase(index);
209 ret = true;
210 break;
213 return ret;
216 void PrinterJobHandler::OnAuthError() {
217 base::MessageLoop::current()->PostTask(
218 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
219 if (delegate_)
220 delegate_->OnAuthError();
223 void PrinterJobHandler::OnPrinterDeleted() {
224 if (delegate_)
225 delegate_->OnPrinterDeleted(printer_info_cloud_.printer_id);
228 void PrinterJobHandler::OnPrinterChanged() {
229 printer_update_pending_ = true;
230 if (!task_in_progress_) {
231 base::MessageLoop::current()->PostTask(
232 FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
236 void PrinterJobHandler::OnJobChanged() {
237 // Some job on the printer changed. Loop through all our JobStatusUpdaters
238 // and have them check for updates.
239 for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
240 index != job_status_updater_list_.end(); index++) {
241 base::MessageLoop::current()->PostTask(
242 FROM_HERE, base::Bind(&JobStatusUpdater::UpdateStatus, index->get()));
246 void PrinterJobHandler::OnJobSpoolSucceeded(const PlatformJobId& job_id) {
247 DCHECK(base::MessageLoop::current() == print_thread_.message_loop());
248 job_spooler_ = NULL;
249 job_handler_message_loop_proxy_->PostTask(
250 FROM_HERE, base::Bind(&PrinterJobHandler::JobSpooled, this, job_id));
253 void PrinterJobHandler::OnJobSpoolFailed() {
254 DCHECK(base::MessageLoop::current() == print_thread_.message_loop());
255 job_spooler_ = NULL;
256 VLOG(1) << "CP_CONNECTOR: Job failed (spool failed)";
257 job_handler_message_loop_proxy_->PostTask(
258 FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this, JOB_FAILED));
261 // static
262 void PrinterJobHandler::ReportsStats() {
263 base::subtle::Atomic32 started =
264 base::subtle::NoBarrier_AtomicExchange(&g_total_jobs_started, 0);
265 base::subtle::Atomic32 done =
266 base::subtle::NoBarrier_AtomicExchange(&g_total_jobs_done, 0);
267 UMA_HISTOGRAM_COUNTS_100("CloudPrint.JobsStartedPerInterval", started);
268 UMA_HISTOGRAM_COUNTS_100("CloudPrint.JobsDonePerInterval", done);
271 PrinterJobHandler::~PrinterJobHandler() {
272 if (printer_watcher_.get())
273 printer_watcher_->StopWatching();
276 // Begin Response handlers
277 CloudPrintURLFetcher::ResponseAction
278 PrinterJobHandler::HandlePrinterUpdateResponse(
279 const net::URLFetcher* source,
280 const GURL& url,
281 DictionaryValue* json_data,
282 bool succeeded) {
283 VLOG(1) << "CP_CONNECTOR: Handling printer update response"
284 << ", printer id: " << printer_info_cloud_.printer_id;
285 // We are done here. Go to the Stop state
286 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
287 << ", printer id: " << printer_info_cloud_.printer_id;
288 base::MessageLoop::current()->PostTask(
289 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
290 return CloudPrintURLFetcher::STOP_PROCESSING;
293 CloudPrintURLFetcher::ResponseAction
294 PrinterJobHandler::HandleJobMetadataResponse(
295 const net::URLFetcher* source,
296 const GURL& url,
297 DictionaryValue* json_data,
298 bool succeeded) {
299 VLOG(1) << "CP_CONNECTOR: Handling job metadata response"
300 << ", printer id: " << printer_info_cloud_.printer_id;
301 bool job_available = false;
302 if (succeeded) {
303 std::vector<JobDetails> jobs;
304 job_queue_handler_.GetJobsFromQueue(json_data, &jobs);
305 if (!jobs.empty()) {
306 if (jobs[0].time_remaining_ == base::TimeDelta()) {
307 job_available = true;
308 job_details_ = jobs[0];
309 job_start_time_ = base::Time::Now();
310 base::subtle::NoBarrier_AtomicIncrement(&g_total_jobs_started, 1);
311 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
312 JOB_HANDLER_JOB_STARTED, JOB_HANDLER_MAX);
313 SetNextDataHandler(&PrinterJobHandler::HandlePrintTicketResponse);
314 request_ = CloudPrintURLFetcher::Create();
315 request_->StartGetRequest(CloudPrintURLFetcher::REQUEST_TICKET,
316 GURL(job_details_.print_ticket_url_), this, kJobDataMaxRetryCount,
317 std::string());
318 } else {
319 job_available = false;
320 base::MessageLoop::current()->PostDelayedTask(
321 FROM_HERE,
322 base::Bind(&PrinterJobHandler::RunScheduledJobCheck, this),
323 jobs[0].time_remaining_);
328 if (!job_available) {
329 // If no jobs are available, go to the Stop state.
330 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
331 << ", printer id: " << printer_info_cloud_.printer_id;
332 base::MessageLoop::current()->PostTask(
333 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
335 return CloudPrintURLFetcher::STOP_PROCESSING;
338 CloudPrintURLFetcher::ResponseAction
339 PrinterJobHandler::HandlePrintTicketResponse(const net::URLFetcher* source,
340 const GURL& url,
341 const std::string& data) {
342 VLOG(1) << "CP_CONNECTOR: Handling print ticket response"
343 << ", printer id: " << printer_info_cloud_.printer_id;
344 if (print_system_->ValidatePrintTicket(printer_info_.printer_name, data)) {
345 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
346 JOB_HANDLER_VALID_TICKET, JOB_HANDLER_MAX);
347 job_details_.print_ticket_ = data;
348 SetNextDataHandler(&PrinterJobHandler::HandlePrintDataResponse);
349 request_ = CloudPrintURLFetcher::Create();
350 std::string accept_headers = "Accept: ";
351 accept_headers += print_system_->GetSupportedMimeTypes();
352 request_->StartGetRequest(CloudPrintURLFetcher::REQUEST_DATA,
353 GURL(job_details_.print_data_url_), this, kJobDataMaxRetryCount,
354 accept_headers);
355 } else {
356 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
357 JOB_HANDLER_INVALID_TICKET, JOB_HANDLER_MAX);
358 // The print ticket was not valid. We are done here.
359 ValidatePrintTicketFailed();
361 return CloudPrintURLFetcher::STOP_PROCESSING;
364 CloudPrintURLFetcher::ResponseAction
365 PrinterJobHandler::HandlePrintDataResponse(const net::URLFetcher* source,
366 const GURL& url,
367 const std::string& data) {
368 VLOG(1) << "CP_CONNECTOR: Handling print data response"
369 << ", printer id: " << printer_info_cloud_.printer_id;
370 if (file_util::CreateTemporaryFile(&job_details_.print_data_file_path_)) {
371 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", JOB_HANDLER_DATA,
372 JOB_HANDLER_MAX);
373 int ret = file_util::WriteFile(job_details_.print_data_file_path_,
374 data.c_str(), data.length());
375 source->GetResponseHeaders()->GetMimeType(
376 &job_details_.print_data_mime_type_);
377 DCHECK(ret == static_cast<int>(data.length()));
378 if (ret == static_cast<int>(data.length())) {
379 UpdateJobStatus(PRINT_JOB_STATUS_IN_PROGRESS, JOB_SUCCESS);
380 return CloudPrintURLFetcher::STOP_PROCESSING;
383 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
384 JOB_HANDLER_INVALID_DATA, JOB_HANDLER_MAX);
386 // If we are here, then there was an error in saving the print data, bail out
387 // here.
388 VLOG(1) << "CP_CONNECTOR: Error saving print data"
389 << ", printer id: " << printer_info_cloud_.printer_id;
390 base::MessageLoop::current()->PostTask(
391 FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this,
392 JOB_DOWNLOAD_FAILED));
393 return CloudPrintURLFetcher::STOP_PROCESSING;
396 CloudPrintURLFetcher::ResponseAction
397 PrinterJobHandler::HandleInProgressStatusUpdateResponse(
398 const net::URLFetcher* source,
399 const GURL& url,
400 DictionaryValue* json_data,
401 bool succeeded) {
402 VLOG(1) << "CP_CONNECTOR: Handling success status update response"
403 << ", printer id: " << printer_info_cloud_.printer_id;
404 base::MessageLoop::current()->PostTask(
405 FROM_HERE, base::Bind(&PrinterJobHandler::StartPrinting, this));
406 return CloudPrintURLFetcher::STOP_PROCESSING;
409 CloudPrintURLFetcher::ResponseAction
410 PrinterJobHandler::HandleFailureStatusUpdateResponse(
411 const net::URLFetcher* source,
412 const GURL& url,
413 DictionaryValue* json_data,
414 bool succeeded) {
415 VLOG(1) << "CP_CONNECTOR: Handling failure status update response"
416 << ", printer id: " << printer_info_cloud_.printer_id;
417 base::MessageLoop::current()->PostTask(
418 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
419 return CloudPrintURLFetcher::STOP_PROCESSING;
422 void PrinterJobHandler::Start() {
423 VLOG(1) << "CP_CONNECTOR: Starting printer job handler"
424 << ", printer id: " << printer_info_cloud_.printer_id
425 << ", task in progress: " << task_in_progress_;
426 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
427 JOB_HANDLER_START, JOB_HANDLER_MAX);
428 if (task_in_progress_) {
429 // Multiple Starts can get posted because of multiple notifications
430 // We want to ignore the other ones that happen when a task is in progress.
431 return;
433 Reset();
434 if (!shutting_down_) {
435 // Check if we have work to do.
436 if (HavePendingTasks()) {
437 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
438 JOB_HANDLER_PENDING_TASK, JOB_HANDLER_MAX);
439 if (!task_in_progress_ && printer_update_pending_) {
440 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
441 JOB_HANDLER_PRINTER_UPDATE, JOB_HANDLER_MAX);
442 printer_update_pending_ = false;
443 task_in_progress_ = UpdatePrinterInfo();
444 VLOG(1) << "CP_CONNECTOR: Changed task in progress"
445 << ", printer id: " << printer_info_cloud_.printer_id
446 << ", task in progress: " << task_in_progress_;
448 if (!task_in_progress_ && job_check_pending_) {
449 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
450 JOB_HANDLER_JOB_CHECK, JOB_HANDLER_MAX);
451 task_in_progress_ = true;
452 VLOG(1) << "CP_CONNECTOR: Changed task in progress"
453 ", printer id: " << printer_info_cloud_.printer_id
454 << ", task in progress: " << task_in_progress_;
455 job_check_pending_ = false;
456 // We need to fetch any pending jobs for this printer
457 SetNextJSONHandler(&PrinterJobHandler::HandleJobMetadataResponse);
458 request_ = CloudPrintURLFetcher::Create();
459 request_->StartGetRequest(
460 CloudPrintURLFetcher::REQUEST_JOB_FETCH,
461 GetUrlForJobFetch(
462 cloud_print_server_url_, printer_info_cloud_.printer_id,
463 job_fetch_reason_),
464 this,
465 kCloudPrintAPIMaxRetryCount,
466 std::string());
467 last_job_fetch_time_ = base::TimeTicks::Now();
468 VLOG(1) << "CP_CONNECTOR: Last job fetch time"
469 << ", printer name: " << printer_info_.printer_name.c_str()
470 << ", timestamp: " << last_job_fetch_time_.ToInternalValue();
471 job_fetch_reason_.clear();
477 void PrinterJobHandler::Stop() {
478 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
479 << ", printer id: " << printer_info_cloud_.printer_id;
480 task_in_progress_ = false;
481 VLOG(1) << "CP_CONNECTOR: Changed task in progress"
482 << ", printer id: " << printer_info_cloud_.printer_id
483 << ", task in progress: " << task_in_progress_;
484 Reset();
485 if (HavePendingTasks()) {
486 base::MessageLoop::current()->PostTask(
487 FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
491 void PrinterJobHandler::StartPrinting() {
492 VLOG(1) << "CP_CONNECTOR: Starting printing"
493 << ", printer id: " << printer_info_cloud_.printer_id;
494 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
495 JOB_HANDLER_SET_START_PRINTING, JOB_HANDLER_MAX);
496 // We are done with the request object for now.
497 request_ = NULL;
498 if (!shutting_down_) {
499 if (!print_thread_.Start()) {
500 VLOG(1) << "CP_CONNECTOR: Failed to start print thread"
501 << ", printer id: " << printer_info_cloud_.printer_id;
502 JobFailed(JOB_FAILED);
503 } else {
504 print_thread_.message_loop()->PostTask(
505 FROM_HERE, base::Bind(&PrinterJobHandler::DoPrint, this, job_details_,
506 printer_info_.printer_name));
511 void PrinterJobHandler::Reset() {
512 job_details_.Clear();
513 request_ = NULL;
514 print_thread_.Stop();
517 void PrinterJobHandler::UpdateJobStatus(PrintJobStatus status,
518 PrintJobError error) {
519 VLOG(1) << "CP_CONNECTOR: Updating job status"
520 << ", printer id: " << printer_info_cloud_.printer_id
521 << ", job id: " << job_details_.job_id_
522 << ", job status: " << status;
523 if (shutting_down_) {
524 VLOG(1) << "CP_CONNECTOR: Job status update aborted (shutting down)"
525 << ", printer id: " << printer_info_cloud_.printer_id
526 << ", job id: " << job_details_.job_id_;
527 return;
529 if (job_details_.job_id_.empty()) {
530 VLOG(1) << "CP_CONNECTOR: Job status update aborted (empty job id)"
531 << ", printer id: " << printer_info_cloud_.printer_id;
532 return;
535 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobStatus", error, JOB_MAX);
537 if (error == JOB_SUCCESS) {
538 DCHECK_EQ(status, PRINT_JOB_STATUS_IN_PROGRESS);
539 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
540 JOB_HANDLER_SET_IN_PROGRESS, JOB_HANDLER_MAX);
541 SetNextJSONHandler(
542 &PrinterJobHandler::HandleInProgressStatusUpdateResponse);
543 } else {
544 SetNextJSONHandler(
545 &PrinterJobHandler::HandleFailureStatusUpdateResponse);
547 request_ = CloudPrintURLFetcher::Create();
548 request_->StartGetRequest(
549 CloudPrintURLFetcher::REQUEST_UPDATE_JOB,
550 GetUrlForJobStatusUpdate(cloud_print_server_url_, job_details_.job_id_,
551 status, error),
552 this, kCloudPrintAPIMaxRetryCount, std::string());
555 void PrinterJobHandler::RunScheduledJobCheck() {
556 CheckForJobs(kJobFetchReasonRetry);
559 void PrinterJobHandler::SetNextJSONHandler(JSONDataHandler handler) {
560 next_json_data_handler_ = handler;
561 next_data_handler_ = NULL;
564 void PrinterJobHandler::SetNextDataHandler(DataHandler handler) {
565 next_data_handler_ = handler;
566 next_json_data_handler_ = NULL;
569 void PrinterJobHandler::JobFailed(PrintJobError error) {
570 VLOG(1) << "CP_CONNECTOR: Job failed"
571 << ", printer id: " << printer_info_cloud_.printer_id
572 << ", job id: " << job_details_.job_id_
573 << ", error: " << error;
574 if (!shutting_down_) {
575 UpdateJobStatus(PRINT_JOB_STATUS_ERROR, error);
576 // This job failed, but others may be pending. Schedule a check.
577 job_check_pending_ = true;
578 job_fetch_reason_ = kJobFetchReasonFailure;
582 void PrinterJobHandler::JobSpooled(PlatformJobId local_job_id) {
583 VLOG(1) << "CP_CONNECTOR: Job spooled"
584 << ", printer id: " << printer_info_cloud_.printer_id
585 << ", job id: " << local_job_id;
586 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", JOB_HANDLER_SPOOLED,
587 JOB_HANDLER_MAX);
588 UMA_HISTOGRAM_LONG_TIMES("CloudPrint.SpoolingTime",
589 base::Time::Now() - spooling_start_time_);
590 if (shutting_down_)
591 return;
593 local_job_id_ = local_job_id;
594 print_thread_.Stop();
596 // The print job has been spooled locally. We now need to create an object
597 // that monitors the status of the job and updates the server.
598 scoped_refptr<JobStatusUpdater> job_status_updater(
599 new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_,
600 local_job_id_, cloud_print_server_url_,
601 print_system_.get(), this));
602 job_status_updater_list_.push_back(job_status_updater);
603 base::MessageLoop::current()->PostTask(
604 FROM_HERE,
605 base::Bind(&JobStatusUpdater::UpdateStatus, job_status_updater.get()));
607 CheckForJobs(kJobFetchReasonQueryMore);
609 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
610 << ", printer id: " << printer_info_cloud_.printer_id;
611 base::MessageLoop::current()->PostTask(
612 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
615 bool PrinterJobHandler::UpdatePrinterInfo() {
616 if (!printer_watcher_.get()) {
617 LOG(ERROR) << "CP_CONNECTOR: Printer watcher is missing."
618 << " Check printer server url for printer id: "
619 << printer_info_cloud_.printer_id;
620 return false;
623 VLOG(1) << "CP_CONNECTOR: Updating printer info"
624 << ", printer id: " << printer_info_cloud_.printer_id;
625 // We need to update the parts of the printer info that have changed
626 // (could be printer name, description, status or capabilities).
627 // First asynchronously fetch the capabilities.
628 printing::PrinterBasicInfo printer_info;
629 printer_watcher_->GetCurrentPrinterInfo(&printer_info);
631 // Asynchronously fetch the printer caps and defaults. The story will
632 // continue in OnReceivePrinterCaps.
633 print_system_->GetPrinterCapsAndDefaults(
634 printer_info.printer_name.c_str(),
635 base::Bind(&PrinterJobHandler::OnReceivePrinterCaps,
636 weak_ptr_factory_.GetWeakPtr()));
638 // While we are waiting for the data, pretend we have work to do and return
639 // true.
640 return true;
643 bool PrinterJobHandler::HavePendingTasks() {
644 return (job_check_pending_ || printer_update_pending_);
647 void PrinterJobHandler::ValidatePrintTicketFailed() {
648 if (!shutting_down_) {
649 LOG(ERROR) << "CP_CONNECTOR: Failed validating print ticket"
650 << ", printer name: " << printer_info_.printer_name
651 << ", job id: " << job_details_.job_id_;
652 JobFailed(JOB_VALIDATE_TICKET_FAILED);
656 void PrinterJobHandler::OnReceivePrinterCaps(
657 bool succeeded,
658 const std::string& printer_name,
659 const printing::PrinterCapsAndDefaults& caps_and_defaults) {
660 printing::PrinterBasicInfo printer_info;
661 if (printer_watcher_.get())
662 printer_watcher_->GetCurrentPrinterInfo(&printer_info);
664 std::string post_data;
665 std::string mime_boundary;
666 CreateMimeBoundaryForUpload(&mime_boundary);
668 if (succeeded) {
669 std::string caps_hash =
670 base::MD5String(caps_and_defaults.printer_capabilities);
671 if (caps_hash != printer_info_cloud_.caps_hash) {
672 // Hashes don't match, we need to upload new capabilities (the defaults
673 // go for free along with the capabilities)
674 printer_info_cloud_.caps_hash = caps_hash;
675 net::AddMultipartValueForUpload(kPrinterCapsValue,
676 caps_and_defaults.printer_capabilities, mime_boundary,
677 caps_and_defaults.caps_mime_type, &post_data);
678 net::AddMultipartValueForUpload(kPrinterDefaultsValue,
679 caps_and_defaults.printer_defaults, mime_boundary,
680 caps_and_defaults.defaults_mime_type, &post_data);
681 net::AddMultipartValueForUpload(kPrinterCapsHashValue,
682 caps_hash, mime_boundary, std::string(), &post_data);
684 } else {
685 LOG(ERROR) << "Failed to get printer caps and defaults"
686 << ", printer name: " << printer_name;
689 std::string tags_hash = GetHashOfPrinterInfo(printer_info);
690 if (tags_hash != printer_info_cloud_.tags_hash) {
691 printer_info_cloud_.tags_hash = tags_hash;
692 post_data += GetPostDataForPrinterInfo(printer_info, mime_boundary);
693 // Remove all the existing proxy tags.
694 std::string cp_tag_wildcard(kCloudPrintServiceProxyTagPrefix);
695 cp_tag_wildcard += ".*";
696 net::AddMultipartValueForUpload(kPrinterRemoveTagValue,
697 cp_tag_wildcard, mime_boundary, std::string(), &post_data);
699 if (!last_caps_update_time_.is_null()) {
700 UMA_HISTOGRAM_CUSTOM_TIMES(
701 "CloudPrint.CapsUpdateInterval",
702 base::Time::Now() - last_caps_update_time_,
703 base::TimeDelta::FromMilliseconds(1),
704 base::TimeDelta::FromDays(30), 50);
706 last_caps_update_time_ = base::Time::Now();
709 if (printer_info.printer_name != printer_info_.printer_name) {
710 net::AddMultipartValueForUpload(kPrinterNameValue,
711 printer_info.printer_name, mime_boundary, std::string(), &post_data);
713 if (printer_info.printer_description != printer_info_.printer_description) {
714 net::AddMultipartValueForUpload(kPrinterDescValue,
715 printer_info.printer_description, mime_boundary,
716 std::string(), &post_data);
718 if (printer_info.printer_status != printer_info_.printer_status) {
719 net::AddMultipartValueForUpload(kPrinterStatusValue,
720 base::StringPrintf("%d", printer_info.printer_status), mime_boundary,
721 std::string(), &post_data);
724 // Add local_settings with a current XMPP ping interval.
725 if (printer_info_cloud_.pending_xmpp_timeout != 0) {
726 DCHECK(kMinXmppPingTimeoutSecs <= printer_info_cloud_.pending_xmpp_timeout);
727 net::AddMultipartValueForUpload(kPrinterLocalSettingsValue,
728 base::StringPrintf(kUpdateLocalSettingsXmppPingFormat,
729 printer_info_cloud_.current_xmpp_timeout),
730 mime_boundary, std::string(), &post_data);
733 printer_info_ = printer_info;
734 if (!post_data.empty()) {
735 net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data);
736 std::string mime_type("multipart/form-data; boundary=");
737 mime_type += mime_boundary;
738 SetNextJSONHandler(&PrinterJobHandler::HandlePrinterUpdateResponse);
739 request_ = CloudPrintURLFetcher::Create();
740 request_->StartPostRequest(
741 CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER,
742 GetUrlForPrinterUpdate(
743 cloud_print_server_url_, printer_info_cloud_.printer_id),
744 this,
745 kCloudPrintAPIMaxRetryCount,
746 mime_type,
747 post_data,
748 std::string());
749 } else {
750 // We are done here. Go to the Stop state
751 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
752 << ", printer name: " << printer_name;
753 base::MessageLoop::current()->PostTask(
754 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
758 // The following methods are called on |print_thread_|. It is not safe to
759 // access any members other than |job_handler_message_loop_proxy_|,
760 // |job_spooler_| and |print_system_|.
761 void PrinterJobHandler::DoPrint(const JobDetails& job_details,
762 const std::string& printer_name) {
763 job_spooler_ = print_system_->CreateJobSpooler();
764 UMA_HISTOGRAM_LONG_TIMES("CloudPrint.PrepareTime",
765 base::Time::Now() - job_start_time_);
766 DCHECK(job_spooler_.get());
767 if (!job_spooler_.get())
768 return;
769 string16 document_name =
770 printing::PrintBackend::SimplifyDocumentTitle(
771 UTF8ToUTF16(job_details.job_title_));
772 if (document_name.empty()) {
773 document_name = printing::PrintBackend::SimplifyDocumentTitle(
774 l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
776 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
777 JOB_HANDLER_START_SPOOLING, JOB_HANDLER_MAX);
778 spooling_start_time_ = base::Time::Now();
779 if (!job_spooler_->Spool(job_details.print_ticket_,
780 job_details.print_data_file_path_,
781 job_details.print_data_mime_type_,
782 printer_name,
783 UTF16ToUTF8(document_name),
784 job_details.tags_,
785 this)) {
786 OnJobSpoolFailed();
790 } // namespace cloud_print