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. 4 5 #include "chrome/service/cloud_print/printer_job_handler.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/files/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/grit/generated_resources.h" 19 #include "chrome/service/cloud_print/cloud_print_service_helpers.h" 20 #include "chrome/service/cloud_print/job_status_updater.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/printing_utils.h" 25 #include "ui/base/l10n/l10n_util.h" 26 #include "url/gurl.h" 27 28 namespace cloud_print { 29 30 namespace { 31 32 base::subtle::Atomic32 g_total_jobs_started = 0; 33 base::subtle::Atomic32 g_total_jobs_done = 0; 34 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, 52 }; 53 54 } // namespace 55 56 PrinterJobHandler::PrinterInfoFromCloud::PrinterInfoFromCloud() 57 : current_xmpp_timeout(0), pending_xmpp_timeout(0) { 58 } 59 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) { 83 } 84 85 bool PrinterJobHandler::Initialize() { 86 if (!print_system_->IsValidPrinter(printer_info_.printer_name)) 87 return false; 88 89 printer_watcher_ = print_system_->CreatePrinterWatcher( 90 printer_info_.printer_name); 91 printer_watcher_->StartWatching(this); 92 CheckForJobs(kJobFetchReasonStartup); 93 return true; 94 } 95 96 std::string PrinterJobHandler::GetPrinterName() const { 97 return printer_info_.printer_name; 98 } 99 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)); 112 } 113 } 114 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(); 124 } 125 } 126 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; 144 } 145 return CloudPrintURLFetcher::CONTINUE_PROCESSING; 146 } 147 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); 155 } 156 157 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleJSONData( 158 const net::URLFetcher* source, 159 const GURL& url, 160 base::DictionaryValue* json_data, 161 bool succeeded) { 162 DCHECK(next_json_data_handler_); 163 return (this->*next_json_data_handler_)(source, url, json_data, succeeded); 164 } 165 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)); 179 } 180 } 181 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; 189 } 190 191 std::string PrinterJobHandler::GetAuthHeader() { 192 return GetCloudPrintAuthHeaderFromStore(); 193 } 194 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_); 204 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; 211 } 212 } 213 return ret; 214 } 215 216 void PrinterJobHandler::OnAuthError() { 217 base::MessageLoop::current()->PostTask( 218 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this)); 219 if (delegate_) 220 delegate_->OnAuthError(); 221 } 222 223 void PrinterJobHandler::OnPrinterDeleted() { 224 if (delegate_) 225 delegate_->OnPrinterDeleted(printer_info_cloud_.printer_id); 226 } 227 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)); 233 } 234 } 235 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())); 243 } 244 } 245 246 void PrinterJobHandler::OnJobSpoolSucceeded(const PlatformJobId& job_id) { 247 DCHECK(base::MessageLoop::current() == print_thread_.message_loop()); 248 job_spooler_->AddRef(); 249 print_thread_.message_loop()->ReleaseSoon(FROM_HERE, job_spooler_.get()); 250 job_spooler_ = NULL; 251 job_handler_message_loop_proxy_->PostTask( 252 FROM_HERE, base::Bind(&PrinterJobHandler::JobSpooled, this, job_id)); 253 } 254 255 void PrinterJobHandler::OnJobSpoolFailed() { 256 DCHECK(base::MessageLoop::current() == print_thread_.message_loop()); 257 job_spooler_->AddRef(); 258 print_thread_.message_loop()->ReleaseSoon(FROM_HERE, job_spooler_.get()); 259 job_spooler_ = NULL; 260 VLOG(1) << "CP_CONNECTOR: Job failed (spool failed)"; 261 job_handler_message_loop_proxy_->PostTask( 262 FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this, JOB_FAILED)); 263 } 264 265 // static 266 void PrinterJobHandler::ReportsStats() { 267 base::subtle::Atomic32 started = 268 base::subtle::NoBarrier_AtomicExchange(&g_total_jobs_started, 0); 269 base::subtle::Atomic32 done = 270 base::subtle::NoBarrier_AtomicExchange(&g_total_jobs_done, 0); 271 UMA_HISTOGRAM_COUNTS_100("CloudPrint.JobsStartedPerInterval", started); 272 UMA_HISTOGRAM_COUNTS_100("CloudPrint.JobsDonePerInterval", done); 273 } 274 275 PrinterJobHandler::~PrinterJobHandler() { 276 if (printer_watcher_.get()) 277 printer_watcher_->StopWatching(); 278 } 279 280 // Begin Response handlers 281 CloudPrintURLFetcher::ResponseAction 282 PrinterJobHandler::HandlePrinterUpdateResponse( 283 const net::URLFetcher* source, 284 const GURL& url, 285 base::DictionaryValue* json_data, 286 bool succeeded) { 287 VLOG(1) << "CP_CONNECTOR: Handling printer update response" 288 << ", printer id: " << printer_info_cloud_.printer_id; 289 // We are done here. Go to the Stop state 290 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler" 291 << ", printer id: " << printer_info_cloud_.printer_id; 292 base::MessageLoop::current()->PostTask( 293 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this)); 294 return CloudPrintURLFetcher::STOP_PROCESSING; 295 } 296 297 CloudPrintURLFetcher::ResponseAction 298 PrinterJobHandler::HandleJobMetadataResponse( 299 const net::URLFetcher* source, 300 const GURL& url, 301 base::DictionaryValue* json_data, 302 bool succeeded) { 303 VLOG(1) << "CP_CONNECTOR: Handling job metadata response" 304 << ", printer id: " << printer_info_cloud_.printer_id; 305 bool job_available = false; 306 if (succeeded) { 307 std::vector<JobDetails> jobs; 308 job_queue_handler_.GetJobsFromQueue(json_data, &jobs); 309 if (!jobs.empty()) { 310 if (jobs[0].time_remaining_ == base::TimeDelta()) { 311 job_available = true; 312 job_details_ = jobs[0]; 313 job_start_time_ = base::Time::Now(); 314 base::subtle::NoBarrier_AtomicIncrement(&g_total_jobs_started, 1); 315 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 316 JOB_HANDLER_JOB_STARTED, JOB_HANDLER_MAX); 317 SetNextDataHandler(&PrinterJobHandler::HandlePrintTicketResponse); 318 request_ = CloudPrintURLFetcher::Create(); 319 if (print_system_->UseCddAndCjt()) { 320 request_->StartGetRequest( 321 CloudPrintURLFetcher::REQUEST_TICKET, 322 GetUrlForJobCjt(cloud_print_server_url_, job_details_.job_id_, 323 job_fetch_reason_), 324 this, kJobDataMaxRetryCount, std::string()); 325 } else { 326 request_->StartGetRequest( 327 CloudPrintURLFetcher::REQUEST_TICKET, 328 GURL(job_details_.print_ticket_url_), this, kJobDataMaxRetryCount, 329 std::string()); 330 } 331 } else { 332 job_available = false; 333 base::MessageLoop::current()->PostDelayedTask( 334 FROM_HERE, 335 base::Bind(&PrinterJobHandler::RunScheduledJobCheck, this), 336 jobs[0].time_remaining_); 337 } 338 } 339 } 340 341 if (!job_available) { 342 // If no jobs are available, go to the Stop state. 343 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler" 344 << ", printer id: " << printer_info_cloud_.printer_id; 345 base::MessageLoop::current()->PostTask( 346 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this)); 347 } 348 return CloudPrintURLFetcher::STOP_PROCESSING; 349 } 350 351 CloudPrintURLFetcher::ResponseAction 352 PrinterJobHandler::HandlePrintTicketResponse(const net::URLFetcher* source, 353 const GURL& url, 354 const std::string& data) { 355 VLOG(1) << "CP_CONNECTOR: Handling print ticket response" 356 << ", printer id: " << printer_info_cloud_.printer_id; 357 std::string mime_type; 358 source->GetResponseHeaders()->GetMimeType(&mime_type); 359 if (print_system_->ValidatePrintTicket(printer_info_.printer_name, data, 360 mime_type)) { 361 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 362 JOB_HANDLER_VALID_TICKET, JOB_HANDLER_MAX); 363 job_details_.print_ticket_ = data; 364 job_details_.print_ticket_mime_type_ = mime_type; 365 SetNextDataHandler(&PrinterJobHandler::HandlePrintDataResponse); 366 request_ = CloudPrintURLFetcher::Create(); 367 std::string accept_headers = "Accept: "; 368 accept_headers += print_system_->GetSupportedMimeTypes(); 369 request_->StartGetRequest(CloudPrintURLFetcher::REQUEST_DATA, 370 GURL(job_details_.print_data_url_), this, kJobDataMaxRetryCount, 371 accept_headers); 372 } else { 373 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 374 JOB_HANDLER_INVALID_TICKET, JOB_HANDLER_MAX); 375 // The print ticket was not valid. We are done here. 376 ValidatePrintTicketFailed(); 377 } 378 return CloudPrintURLFetcher::STOP_PROCESSING; 379 } 380 381 CloudPrintURLFetcher::ResponseAction 382 PrinterJobHandler::HandlePrintDataResponse(const net::URLFetcher* source, 383 const GURL& url, 384 const std::string& data) { 385 VLOG(1) << "CP_CONNECTOR: Handling print data response" 386 << ", printer id: " << printer_info_cloud_.printer_id; 387 if (base::CreateTemporaryFile(&job_details_.print_data_file_path_)) { 388 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", JOB_HANDLER_DATA, 389 JOB_HANDLER_MAX); 390 int ret = base::WriteFile(job_details_.print_data_file_path_, 391 data.c_str(), data.length()); 392 source->GetResponseHeaders()->GetMimeType( 393 &job_details_.print_data_mime_type_); 394 DCHECK(ret == static_cast<int>(data.length())); 395 if (ret == static_cast<int>(data.length())) { 396 UpdateJobStatus(PRINT_JOB_STATUS_IN_PROGRESS, JOB_SUCCESS); 397 return CloudPrintURLFetcher::STOP_PROCESSING; 398 } 399 } 400 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 401 JOB_HANDLER_INVALID_DATA, JOB_HANDLER_MAX); 402 403 // If we are here, then there was an error in saving the print data, bail out 404 // here. 405 VLOG(1) << "CP_CONNECTOR: Error saving print data" 406 << ", printer id: " << printer_info_cloud_.printer_id; 407 base::MessageLoop::current()->PostTask( 408 FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this, 409 JOB_DOWNLOAD_FAILED)); 410 return CloudPrintURLFetcher::STOP_PROCESSING; 411 } 412 413 CloudPrintURLFetcher::ResponseAction 414 PrinterJobHandler::HandleInProgressStatusUpdateResponse( 415 const net::URLFetcher* source, 416 const GURL& url, 417 base::DictionaryValue* json_data, 418 bool succeeded) { 419 VLOG(1) << "CP_CONNECTOR: Handling success status update response" 420 << ", printer id: " << printer_info_cloud_.printer_id; 421 base::MessageLoop::current()->PostTask( 422 FROM_HERE, base::Bind(&PrinterJobHandler::StartPrinting, this)); 423 return CloudPrintURLFetcher::STOP_PROCESSING; 424 } 425 426 CloudPrintURLFetcher::ResponseAction 427 PrinterJobHandler::HandleFailureStatusUpdateResponse( 428 const net::URLFetcher* source, 429 const GURL& url, 430 base::DictionaryValue* json_data, 431 bool succeeded) { 432 VLOG(1) << "CP_CONNECTOR: Handling failure status update response" 433 << ", printer id: " << printer_info_cloud_.printer_id; 434 base::MessageLoop::current()->PostTask( 435 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this)); 436 return CloudPrintURLFetcher::STOP_PROCESSING; 437 } 438 439 void PrinterJobHandler::Start() { 440 VLOG(1) << "CP_CONNECTOR: Starting printer job handler" 441 << ", printer id: " << printer_info_cloud_.printer_id 442 << ", task in progress: " << task_in_progress_; 443 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 444 JOB_HANDLER_START, JOB_HANDLER_MAX); 445 if (task_in_progress_) { 446 // Multiple Starts can get posted because of multiple notifications 447 // We want to ignore the other ones that happen when a task is in progress. 448 return; 449 } 450 Reset(); 451 if (!shutting_down_) { 452 // Check if we have work to do. 453 if (HavePendingTasks()) { 454 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 455 JOB_HANDLER_PENDING_TASK, JOB_HANDLER_MAX); 456 if (!task_in_progress_ && printer_update_pending_) { 457 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 458 JOB_HANDLER_PRINTER_UPDATE, JOB_HANDLER_MAX); 459 printer_update_pending_ = false; 460 task_in_progress_ = UpdatePrinterInfo(); 461 VLOG(1) << "CP_CONNECTOR: Changed task in progress" 462 << ", printer id: " << printer_info_cloud_.printer_id 463 << ", task in progress: " << task_in_progress_; 464 } 465 if (!task_in_progress_ && job_check_pending_) { 466 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 467 JOB_HANDLER_JOB_CHECK, JOB_HANDLER_MAX); 468 task_in_progress_ = true; 469 VLOG(1) << "CP_CONNECTOR: Changed task in progress" 470 ", printer id: " << printer_info_cloud_.printer_id 471 << ", task in progress: " << task_in_progress_; 472 job_check_pending_ = false; 473 // We need to fetch any pending jobs for this printer 474 SetNextJSONHandler(&PrinterJobHandler::HandleJobMetadataResponse); 475 request_ = CloudPrintURLFetcher::Create(); 476 request_->StartGetRequest( 477 CloudPrintURLFetcher::REQUEST_JOB_FETCH, 478 GetUrlForJobFetch( 479 cloud_print_server_url_, printer_info_cloud_.printer_id, 480 job_fetch_reason_), 481 this, 482 kCloudPrintAPIMaxRetryCount, 483 std::string()); 484 last_job_fetch_time_ = base::TimeTicks::Now(); 485 VLOG(1) << "CP_CONNECTOR: Last job fetch time" 486 << ", printer name: " << printer_info_.printer_name.c_str() 487 << ", timestamp: " << last_job_fetch_time_.ToInternalValue(); 488 job_fetch_reason_.clear(); 489 } 490 } 491 } 492 } 493 494 void PrinterJobHandler::Stop() { 495 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler" 496 << ", printer id: " << printer_info_cloud_.printer_id; 497 task_in_progress_ = false; 498 VLOG(1) << "CP_CONNECTOR: Changed task in progress" 499 << ", printer id: " << printer_info_cloud_.printer_id 500 << ", task in progress: " << task_in_progress_; 501 Reset(); 502 if (HavePendingTasks()) { 503 base::MessageLoop::current()->PostTask( 504 FROM_HERE, base::Bind(&PrinterJobHandler::Start, this)); 505 } 506 } 507 508 void PrinterJobHandler::StartPrinting() { 509 VLOG(1) << "CP_CONNECTOR: Starting printing" 510 << ", printer id: " << printer_info_cloud_.printer_id; 511 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 512 JOB_HANDLER_SET_START_PRINTING, JOB_HANDLER_MAX); 513 // We are done with the request object for now. 514 request_ = NULL; 515 if (!shutting_down_) { 516 #if defined(OS_WIN) 517 print_thread_.init_com_with_mta(true); 518 #endif 519 if (!print_thread_.Start()) { 520 VLOG(1) << "CP_CONNECTOR: Failed to start print thread" 521 << ", printer id: " << printer_info_cloud_.printer_id; 522 JobFailed(JOB_FAILED); 523 } else { 524 print_thread_.message_loop()->PostTask( 525 FROM_HERE, base::Bind(&PrinterJobHandler::DoPrint, this, job_details_, 526 printer_info_.printer_name)); 527 } 528 } 529 } 530 531 void PrinterJobHandler::Reset() { 532 job_details_.Clear(); 533 request_ = NULL; 534 print_thread_.Stop(); 535 } 536 537 void PrinterJobHandler::UpdateJobStatus(PrintJobStatus status, 538 PrintJobError error) { 539 VLOG(1) << "CP_CONNECTOR: Updating job status" 540 << ", printer id: " << printer_info_cloud_.printer_id 541 << ", job id: " << job_details_.job_id_ 542 << ", job status: " << status; 543 if (shutting_down_) { 544 VLOG(1) << "CP_CONNECTOR: Job status update aborted (shutting down)" 545 << ", printer id: " << printer_info_cloud_.printer_id 546 << ", job id: " << job_details_.job_id_; 547 return; 548 } 549 if (job_details_.job_id_.empty()) { 550 VLOG(1) << "CP_CONNECTOR: Job status update aborted (empty job id)" 551 << ", printer id: " << printer_info_cloud_.printer_id; 552 return; 553 } 554 555 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobStatus", error, JOB_MAX); 556 557 if (error == JOB_SUCCESS) { 558 DCHECK_EQ(status, PRINT_JOB_STATUS_IN_PROGRESS); 559 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 560 JOB_HANDLER_SET_IN_PROGRESS, JOB_HANDLER_MAX); 561 SetNextJSONHandler( 562 &PrinterJobHandler::HandleInProgressStatusUpdateResponse); 563 } else { 564 SetNextJSONHandler( 565 &PrinterJobHandler::HandleFailureStatusUpdateResponse); 566 } 567 request_ = CloudPrintURLFetcher::Create(); 568 request_->StartGetRequest( 569 CloudPrintURLFetcher::REQUEST_UPDATE_JOB, 570 GetUrlForJobStatusUpdate(cloud_print_server_url_, job_details_.job_id_, 571 status, error), 572 this, kCloudPrintAPIMaxRetryCount, std::string()); 573 } 574 575 void PrinterJobHandler::RunScheduledJobCheck() { 576 CheckForJobs(kJobFetchReasonRetry); 577 } 578 579 void PrinterJobHandler::SetNextJSONHandler(JSONDataHandler handler) { 580 next_json_data_handler_ = handler; 581 next_data_handler_ = NULL; 582 } 583 584 void PrinterJobHandler::SetNextDataHandler(DataHandler handler) { 585 next_data_handler_ = handler; 586 next_json_data_handler_ = NULL; 587 } 588 589 void PrinterJobHandler::JobFailed(PrintJobError error) { 590 VLOG(1) << "CP_CONNECTOR: Job failed" 591 << ", printer id: " << printer_info_cloud_.printer_id 592 << ", job id: " << job_details_.job_id_ 593 << ", error: " << error; 594 if (!shutting_down_) { 595 UpdateJobStatus(PRINT_JOB_STATUS_ERROR, error); 596 // This job failed, but others may be pending. Schedule a check. 597 job_check_pending_ = true; 598 job_fetch_reason_ = kJobFetchReasonFailure; 599 } 600 } 601 602 void PrinterJobHandler::JobSpooled(PlatformJobId local_job_id) { 603 VLOG(1) << "CP_CONNECTOR: Job spooled" 604 << ", printer id: " << printer_info_cloud_.printer_id 605 << ", job id: " << local_job_id; 606 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", JOB_HANDLER_SPOOLED, 607 JOB_HANDLER_MAX); 608 UMA_HISTOGRAM_LONG_TIMES("CloudPrint.SpoolingTime", 609 base::Time::Now() - spooling_start_time_); 610 if (shutting_down_) 611 return; 612 613 local_job_id_ = local_job_id; 614 print_thread_.Stop(); 615 616 // The print job has been spooled locally. We now need to create an object 617 // that monitors the status of the job and updates the server. 618 scoped_refptr<JobStatusUpdater> job_status_updater( 619 new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_, 620 local_job_id_, cloud_print_server_url_, 621 print_system_.get(), this)); 622 job_status_updater_list_.push_back(job_status_updater); 623 base::MessageLoop::current()->PostTask( 624 FROM_HERE, 625 base::Bind(&JobStatusUpdater::UpdateStatus, job_status_updater.get())); 626 627 CheckForJobs(kJobFetchReasonQueryMore); 628 629 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler" 630 << ", printer id: " << printer_info_cloud_.printer_id; 631 base::MessageLoop::current()->PostTask( 632 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this)); 633 } 634 635 bool PrinterJobHandler::UpdatePrinterInfo() { 636 if (!printer_watcher_.get()) { 637 LOG(ERROR) << "CP_CONNECTOR: Printer watcher is missing." 638 << " Check printer server url for printer id: " 639 << printer_info_cloud_.printer_id; 640 return false; 641 } 642 643 VLOG(1) << "CP_CONNECTOR: Updating printer info" 644 << ", printer id: " << printer_info_cloud_.printer_id; 645 // We need to update the parts of the printer info that have changed 646 // (could be printer name, description, status or capabilities). 647 // First asynchronously fetch the capabilities. 648 printing::PrinterBasicInfo printer_info; 649 printer_watcher_->GetCurrentPrinterInfo(&printer_info); 650 651 // Asynchronously fetch the printer caps and defaults. The story will 652 // continue in OnReceivePrinterCaps. 653 print_system_->GetPrinterCapsAndDefaults( 654 printer_info.printer_name.c_str(), 655 base::Bind(&PrinterJobHandler::OnReceivePrinterCaps, 656 weak_ptr_factory_.GetWeakPtr())); 657 658 // While we are waiting for the data, pretend we have work to do and return 659 // true. 660 return true; 661 } 662 663 bool PrinterJobHandler::HavePendingTasks() { 664 return (job_check_pending_ || printer_update_pending_); 665 } 666 667 void PrinterJobHandler::ValidatePrintTicketFailed() { 668 if (!shutting_down_) { 669 LOG(ERROR) << "CP_CONNECTOR: Failed validating print ticket" 670 << ", printer name: " << printer_info_.printer_name 671 << ", job id: " << job_details_.job_id_; 672 JobFailed(JOB_VALIDATE_TICKET_FAILED); 673 } 674 } 675 676 void PrinterJobHandler::OnReceivePrinterCaps( 677 bool succeeded, 678 const std::string& printer_name, 679 const printing::PrinterCapsAndDefaults& caps_and_defaults) { 680 printing::PrinterBasicInfo printer_info; 681 if (printer_watcher_.get()) 682 printer_watcher_->GetCurrentPrinterInfo(&printer_info); 683 684 std::string post_data; 685 std::string mime_boundary; 686 CreateMimeBoundaryForUpload(&mime_boundary); 687 688 if (succeeded) { 689 std::string caps_hash = 690 base::MD5String(caps_and_defaults.printer_capabilities); 691 if (caps_hash != printer_info_cloud_.caps_hash) { 692 // Hashes don't match, we need to upload new capabilities (the defaults 693 // go for free along with the capabilities) 694 printer_info_cloud_.caps_hash = caps_hash; 695 if (caps_and_defaults.caps_mime_type == kContentTypeJSON) { 696 DCHECK(print_system_->UseCddAndCjt()); 697 net::AddMultipartValueForUpload(kUseCDD, "true", mime_boundary, 698 std::string(), &post_data); 699 } 700 net::AddMultipartValueForUpload(kPrinterCapsValue, 701 caps_and_defaults.printer_capabilities, mime_boundary, 702 caps_and_defaults.caps_mime_type, &post_data); 703 net::AddMultipartValueForUpload(kPrinterDefaultsValue, 704 caps_and_defaults.printer_defaults, mime_boundary, 705 caps_and_defaults.defaults_mime_type, &post_data); 706 net::AddMultipartValueForUpload(kPrinterCapsHashValue, 707 caps_hash, mime_boundary, std::string(), &post_data); 708 } 709 } else { 710 LOG(ERROR) << "Failed to get printer caps and defaults" 711 << ", printer name: " << printer_name; 712 } 713 714 std::string tags_hash = GetHashOfPrinterInfo(printer_info); 715 if (tags_hash != printer_info_cloud_.tags_hash) { 716 printer_info_cloud_.tags_hash = tags_hash; 717 post_data += GetPostDataForPrinterInfo(printer_info, mime_boundary); 718 // Remove all the existing proxy tags. 719 std::string cp_tag_wildcard(kCloudPrintServiceProxyTagPrefix); 720 cp_tag_wildcard += ".*"; 721 net::AddMultipartValueForUpload(kPrinterRemoveTagValue, 722 cp_tag_wildcard, mime_boundary, std::string(), &post_data); 723 724 if (!last_caps_update_time_.is_null()) { 725 UMA_HISTOGRAM_CUSTOM_TIMES( 726 "CloudPrint.CapsUpdateInterval", 727 base::Time::Now() - last_caps_update_time_, 728 base::TimeDelta::FromMilliseconds(1), 729 base::TimeDelta::FromDays(7), 50); 730 } 731 last_caps_update_time_ = base::Time::Now(); 732 } 733 734 if (printer_info.printer_name != printer_info_.printer_name) { 735 net::AddMultipartValueForUpload(kPrinterNameValue, 736 printer_info.printer_name, mime_boundary, std::string(), &post_data); 737 } 738 if (printer_info.printer_description != printer_info_.printer_description) { 739 net::AddMultipartValueForUpload(kPrinterDescValue, 740 printer_info.printer_description, mime_boundary, 741 std::string(), &post_data); 742 } 743 if (printer_info.printer_status != printer_info_.printer_status) { 744 net::AddMultipartValueForUpload(kPrinterStatusValue, 745 base::StringPrintf("%d", printer_info.printer_status), mime_boundary, 746 std::string(), &post_data); 747 } 748 749 // Add local_settings with a current XMPP ping interval. 750 if (printer_info_cloud_.pending_xmpp_timeout != 0) { 751 DCHECK(kMinXmppPingTimeoutSecs <= printer_info_cloud_.pending_xmpp_timeout); 752 net::AddMultipartValueForUpload(kPrinterLocalSettingsValue, 753 base::StringPrintf(kUpdateLocalSettingsXmppPingFormat, 754 printer_info_cloud_.current_xmpp_timeout), 755 mime_boundary, std::string(), &post_data); 756 } 757 758 printer_info_ = printer_info; 759 if (!post_data.empty()) { 760 net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data); 761 std::string mime_type("multipart/form-data; boundary="); 762 mime_type += mime_boundary; 763 SetNextJSONHandler(&PrinterJobHandler::HandlePrinterUpdateResponse); 764 request_ = CloudPrintURLFetcher::Create(); 765 request_->StartPostRequest( 766 CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER, 767 GetUrlForPrinterUpdate( 768 cloud_print_server_url_, printer_info_cloud_.printer_id), 769 this, 770 kCloudPrintAPIMaxRetryCount, 771 mime_type, 772 post_data, 773 std::string()); 774 } else { 775 // We are done here. Go to the Stop state 776 VLOG(1) << "CP_CONNECTOR: Stopping printer job handler" 777 << ", printer name: " << printer_name; 778 base::MessageLoop::current()->PostTask( 779 FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this)); 780 } 781 } 782 783 // The following methods are called on |print_thread_|. It is not safe to 784 // access any members other than |job_handler_message_loop_proxy_|, 785 // |job_spooler_| and |print_system_|. 786 void PrinterJobHandler::DoPrint(const JobDetails& job_details, 787 const std::string& printer_name) { 788 job_spooler_ = print_system_->CreateJobSpooler(); 789 UMA_HISTOGRAM_LONG_TIMES("CloudPrint.PrepareTime", 790 base::Time::Now() - job_start_time_); 791 DCHECK(job_spooler_.get()); 792 if (!job_spooler_.get()) 793 return; 794 base::string16 document_name = printing::SimplifyDocumentTitle( 795 base::UTF8ToUTF16(job_details.job_title_)); 796 if (document_name.empty()) { 797 document_name = printing::SimplifyDocumentTitle( 798 l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE)); 799 } 800 UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", 801 JOB_HANDLER_START_SPOOLING, JOB_HANDLER_MAX); 802 spooling_start_time_ = base::Time::Now(); 803 if (!job_spooler_->Spool(job_details.print_ticket_, 804 job_details.print_ticket_mime_type_, 805 job_details.print_data_file_path_, 806 job_details.print_data_mime_type_, 807 printer_name, 808 base::UTF16ToUTF8(document_name), 809 job_details.tags_, 810 this)) { 811 OnJobSpoolFailed(); 812 } 813 } 814 815 } // namespace cloud_print 816