printer_job_handler.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/file_util.h"
10#include "base/json/json_reader.h"
11#include "base/md5.h"
12#include "base/stringprintf.h"
13#include "base/utf_string_conversions.h"
14#include "base/values.h"
15#include "chrome/common/cloud_print/cloud_print_constants.h"
16#include "chrome/common/cloud_print/cloud_print_helpers.h"
17#include "chrome/service/cloud_print/cloud_print_helpers.h"
18#include "chrome/service/cloud_print/job_status_updater.h"
19#include "googleurl/src/gurl.h"
20#include "grit/generated_resources.h"
21#include "net/http/http_response_headers.h"
22#include "net/http/http_status_code.h"
23#include "printing/backend/print_backend.h"
24#include "ui/base/l10n/l10n_util.h"
25
26namespace cloud_print {
27
28PrinterJobHandler::PrinterJobHandler(
29    const printing::PrinterBasicInfo& printer_info,
30    const PrinterInfoFromCloud& printer_info_cloud,
31    const GURL& cloud_print_server_url,
32    PrintSystem* print_system,
33    Delegate* delegate)
34    : print_system_(print_system),
35      printer_info_(printer_info),
36      printer_info_cloud_(printer_info_cloud),
37      cloud_print_server_url_(cloud_print_server_url),
38      delegate_(delegate),
39      local_job_id_(-1),
40      next_json_data_handler_(NULL),
41      next_data_handler_(NULL),
42      server_error_count_(0),
43      print_thread_("Chrome_CloudPrintJobPrintThread"),
44      job_handler_message_loop_proxy_(
45          base::MessageLoopProxy::current()),
46      shutting_down_(false),
47      job_check_pending_(false),
48      printer_update_pending_(true),
49      task_in_progress_(false),
50      weak_ptr_factory_(this) {
51}
52
53bool PrinterJobHandler::Initialize() {
54  if (!print_system_->IsValidPrinter(printer_info_.printer_name))
55    return false;
56
57  printer_watcher_ = print_system_->CreatePrinterWatcher(
58      printer_info_.printer_name);
59  printer_watcher_->StartWatching(this);
60  CheckForJobs(kJobFetchReasonStartup);
61  return true;
62}
63
64std::string PrinterJobHandler::GetPrinterName() const {
65  return printer_info_.printer_name;
66}
67
68void PrinterJobHandler::CheckForJobs(const std::string& reason) {
69  VLOG(1) << "CP_CONNECTOR: Checking for jobs"
70          << ", printer id: " << printer_info_cloud_.printer_id
71          << ", reason: " << reason
72          << ", task in progress: " << task_in_progress_;
73  job_fetch_reason_ = reason;
74  job_check_pending_ = true;
75  if (!task_in_progress_) {
76    MessageLoop::current()->PostTask(
77        FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
78  }
79}
80
81void PrinterJobHandler::Shutdown() {
82  VLOG(1) << "CP_CONNECTOR: Shutting down printer job handler"
83          << ", printer id: " << printer_info_cloud_.printer_id;
84  Reset();
85  shutting_down_ = true;
86  while (!job_status_updater_list_.empty()) {
87    // Calling Stop() will cause the OnJobCompleted to be called which will
88    // remove the updater object from the list.
89    job_status_updater_list_.front()->Stop();
90  }
91}
92
93// CloudPrintURLFetcher::Delegate implementation.
94CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawResponse(
95    const net::URLFetcher* source,
96    const GURL& url,
97    const net::URLRequestStatus& status,
98    int response_code,
99    const net::ResponseCookies& cookies,
100    const std::string& data) {
101  // 415 (Unsupported media type) error while fetching data from the server
102  // means data conversion error. Stop fetching process and mark job as error.
103  if (next_data_handler_ == (&PrinterJobHandler::HandlePrintDataResponse) &&
104      response_code == net::HTTP_UNSUPPORTED_MEDIA_TYPE) {
105    VLOG(1) << "CP_CONNECTOR: Job failed (unsupported media type)";
106    MessageLoop::current()->PostTask(
107        FROM_HERE,
108        base::Bind(&PrinterJobHandler::JobFailed, this, JOB_DOWNLOAD_FAILED));
109    return CloudPrintURLFetcher::STOP_PROCESSING;
110  }
111  return CloudPrintURLFetcher::CONTINUE_PROCESSING;
112}
113
114CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawData(
115    const net::URLFetcher* source,
116    const GURL& url,
117    const std::string& data) {
118  if (!next_data_handler_)
119    return CloudPrintURLFetcher::CONTINUE_PROCESSING;
120  return (this->*next_data_handler_)(source, url, data);
121}
122
123CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleJSONData(
124    const net::URLFetcher* source,
125    const GURL& url,
126    DictionaryValue* json_data,
127    bool succeeded) {
128  DCHECK(next_json_data_handler_);
129  return (this->*next_json_data_handler_)(source, url, json_data, succeeded);
130}
131
132// Mark the job fetch as failed and check if other jobs can be printed
133void PrinterJobHandler::OnRequestGiveUp() {
134  if (job_queue_handler_.JobFetchFailed(job_details_.job_id_)) {
135    VLOG(1) << "CP_CONNECTOR: Job failed to load (scheduling retry)";
136    CheckForJobs(kJobFetchReasonFailure);
137    MessageLoop::current()->PostTask(
138        FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
139  } else {
140    VLOG(1) << "CP_CONNECTOR: Job failed (giving up after " <<
141        kNumRetriesBeforeAbandonJob << " retries)";
142    MessageLoop::current()->PostTask(
143        FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this,
144                              JOB_DOWNLOAD_FAILED));
145  }
146}
147
148CloudPrintURLFetcher::ResponseAction PrinterJobHandler::OnRequestAuthError() {
149  // We got an Auth error and have no idea how long it will take to refresh
150  // auth information (may take forever). We'll drop current request and
151  // propagate this error to the upper level. After auth issues will be
152  // resolved, GCP connector will restart.
153  OnAuthError();
154  return CloudPrintURLFetcher::STOP_PROCESSING;
155}
156
157std::string PrinterJobHandler::GetAuthHeader() {
158  return GetCloudPrintAuthHeaderFromStore();
159}
160
161// JobStatusUpdater::Delegate implementation
162bool PrinterJobHandler::OnJobCompleted(JobStatusUpdater* updater) {
163  bool ret = false;
164
165  job_queue_handler_.JobDone(job_details_.job_id_);
166
167  for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
168       index != job_status_updater_list_.end(); index++) {
169    if (index->get() == updater) {
170      job_status_updater_list_.erase(index);
171      ret = true;
172      break;
173    }
174  }
175  return ret;
176}
177
178void PrinterJobHandler::OnAuthError() {
179  MessageLoop::current()->PostTask(
180      FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
181  if (delegate_)
182    delegate_->OnAuthError();
183}
184
185void PrinterJobHandler::OnPrinterDeleted() {
186  if (delegate_)
187    delegate_->OnPrinterDeleted(printer_info_cloud_.printer_id);
188}
189
190void PrinterJobHandler::OnPrinterChanged() {
191  printer_update_pending_ = true;
192  if (!task_in_progress_) {
193    MessageLoop::current()->PostTask(
194        FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
195  }
196}
197
198void PrinterJobHandler::OnJobChanged() {
199  // Some job on the printer changed. Loop through all our JobStatusUpdaters
200  // and have them check for updates.
201  for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
202       index != job_status_updater_list_.end(); index++) {
203    MessageLoop::current()->PostTask(
204        FROM_HERE, base::Bind(&JobStatusUpdater::UpdateStatus, index->get()));
205  }
206}
207
208void PrinterJobHandler::OnJobSpoolSucceeded(const PlatformJobId& job_id) {
209  DCHECK(MessageLoop::current() == print_thread_.message_loop());
210  job_spooler_ = NULL;
211  job_handler_message_loop_proxy_->PostTask(
212      FROM_HERE, base::Bind(&PrinterJobHandler::JobSpooled, this, job_id));
213}
214
215void PrinterJobHandler::OnJobSpoolFailed() {
216  DCHECK(MessageLoop::current() == print_thread_.message_loop());
217  job_spooler_ = NULL;
218  VLOG(1) << "CP_CONNECTOR: Job failed (spool failed)";
219  job_handler_message_loop_proxy_->PostTask(
220      FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this, PRINT_FAILED));
221}
222
223PrinterJobHandler::~PrinterJobHandler() {
224  if (printer_watcher_)
225    printer_watcher_->StopWatching();
226}
227
228// Begin Response handlers
229CloudPrintURLFetcher::ResponseAction
230PrinterJobHandler::HandlePrinterUpdateResponse(
231    const net::URLFetcher* source,
232    const GURL& url,
233    DictionaryValue* json_data,
234    bool succeeded) {
235  VLOG(1) << "CP_CONNECTOR: Handling printer update response"
236          << ", printer id: " << printer_info_cloud_.printer_id;
237  // We are done here. Go to the Stop state
238  VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
239          << ", printer id: " << printer_info_cloud_.printer_id;
240  MessageLoop::current()->PostTask(
241      FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
242  return CloudPrintURLFetcher::STOP_PROCESSING;
243}
244
245CloudPrintURLFetcher::ResponseAction
246PrinterJobHandler::HandleJobMetadataResponse(
247    const net::URLFetcher* source,
248    const GURL& url,
249    DictionaryValue* json_data,
250    bool succeeded) {
251  VLOG(1) << "CP_CONNECTOR: Handling job metadata response"
252          << ", printer id: " << printer_info_cloud_.printer_id;
253  bool job_available = false;
254  if (succeeded) {
255    std::vector<JobDetails> jobs;
256    job_queue_handler_.GetJobsFromQueue(json_data, &jobs);
257    if (!jobs.empty()) {
258      if (jobs[0].time_remaining_ == base::TimeDelta()) {
259        job_available = true;
260        job_details_ = jobs[0];
261
262        SetNextDataHandler(&PrinterJobHandler::HandlePrintTicketResponse);
263        request_ = CloudPrintURLFetcher::Create();
264        request_->StartGetRequest(GURL(job_details_.print_ticket_url_),
265                                  this,
266                                  kJobDataMaxRetryCount,
267                                  std::string());
268      } else {
269        job_available = false;
270        MessageLoop::current()->PostDelayedTask(
271            FROM_HERE,
272            base::Bind(&PrinterJobHandler::RunScheduledJobCheck, this),
273            jobs[0].time_remaining_);
274      }
275    }
276  }
277
278  if (!job_available) {
279    // If no jobs are available, go to the Stop state.
280    VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
281            << ", printer id: " << printer_info_cloud_.printer_id;
282    MessageLoop::current()->PostTask(
283        FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
284  }
285  return CloudPrintURLFetcher::STOP_PROCESSING;
286}
287
288CloudPrintURLFetcher::ResponseAction
289PrinterJobHandler::HandlePrintTicketResponse(const net::URLFetcher* source,
290                                             const GURL& url,
291                                             const std::string& data) {
292  VLOG(1) << "CP_CONNECTOR: Handling print ticket response"
293          << ", printer id: " << printer_info_cloud_.printer_id;
294  if (print_system_->ValidatePrintTicket(printer_info_.printer_name, data)) {
295    job_details_.print_ticket_ = data;
296    SetNextDataHandler(&PrinterJobHandler::HandlePrintDataResponse);
297    request_ = CloudPrintURLFetcher::Create();
298    std::string accept_headers = "Accept: ";
299    accept_headers += print_system_->GetSupportedMimeTypes();
300    request_->StartGetRequest(GURL(job_details_.print_data_url_),
301                              this,
302                              kJobDataMaxRetryCount,
303                              accept_headers);
304  } else {
305    // The print ticket was not valid. We are done here.
306    FailedFetchingJobData();
307  }
308  return CloudPrintURLFetcher::STOP_PROCESSING;
309}
310
311CloudPrintURLFetcher::ResponseAction
312PrinterJobHandler::HandlePrintDataResponse(const net::URLFetcher* source,
313                                           const GURL& url,
314                                           const std::string& data) {
315  VLOG(1) << "CP_CONNECTOR: Handling print data response"
316          << ", printer id: " << printer_info_cloud_.printer_id;
317  base::Closure next_task;
318  if (file_util::CreateTemporaryFile(&job_details_.print_data_file_path_)) {
319    int ret = file_util::WriteFile(job_details_.print_data_file_path_,
320                                   data.c_str(),
321                                   data.length());
322    source->GetResponseHeaders()->GetMimeType(
323        &job_details_.print_data_mime_type_);
324    DCHECK(ret == static_cast<int>(data.length()));
325    if (ret == static_cast<int>(data.length())) {
326      next_task = base::Bind(&PrinterJobHandler::StartPrinting, this);
327    }
328  }
329  // If there was no task allocated above, then there was an error in
330  // saving the print data, bail out here.
331  if (next_task.is_null()) {
332    VLOG(1) << "CP_CONNECTOR: Error saving print data"
333            << ", printer id: " << printer_info_cloud_.printer_id;
334    next_task = base::Bind(&PrinterJobHandler::JobFailed, this,
335                           JOB_DOWNLOAD_FAILED);
336  }
337  MessageLoop::current()->PostTask(FROM_HERE, next_task);
338  return CloudPrintURLFetcher::STOP_PROCESSING;
339}
340
341CloudPrintURLFetcher::ResponseAction
342PrinterJobHandler::HandleSuccessStatusUpdateResponse(
343    const net::URLFetcher* source,
344    const GURL& url,
345    DictionaryValue* json_data,
346    bool succeeded) {
347  VLOG(1) << "CP_CONNECTOR: Handling success status update response"
348          << ", printer id: " << printer_info_cloud_.printer_id;
349  // The print job has been spooled locally. We now need to create an object
350  // that monitors the status of the job and updates the server.
351  scoped_refptr<JobStatusUpdater> job_status_updater(
352      new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_,
353                           local_job_id_, cloud_print_server_url_,
354                           print_system_.get(), this));
355  job_status_updater_list_.push_back(job_status_updater);
356  MessageLoop::current()->PostTask(
357      FROM_HERE, base::Bind(&JobStatusUpdater::UpdateStatus,
358                            job_status_updater.get()));
359  if (succeeded) {
360    // Since we just printed successfully, we want to look for more jobs.
361    CheckForJobs(kJobFetchReasonQueryMore);
362  }
363  VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
364          << ", printer id: " << printer_info_cloud_.printer_id;
365  MessageLoop::current()->PostTask(
366      FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
367  return CloudPrintURLFetcher::STOP_PROCESSING;
368}
369
370CloudPrintURLFetcher::ResponseAction
371PrinterJobHandler::HandleFailureStatusUpdateResponse(
372    const net::URLFetcher* source,
373    const GURL& url,
374    DictionaryValue* json_data,
375    bool succeeded) {
376  VLOG(1) << "CP_CONNECTOR: Handling failure status update response"
377          << ", printer id: " << printer_info_cloud_.printer_id;
378  MessageLoop::current()->PostTask(
379      FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
380  return CloudPrintURLFetcher::STOP_PROCESSING;
381}
382
383void PrinterJobHandler::Start() {
384  VLOG(1) << "CP_CONNECTOR: Starting printer job handler"
385          << ", printer id: " << printer_info_cloud_.printer_id
386          << ", task in progress: " << task_in_progress_;
387  if (task_in_progress_) {
388    // Multiple Starts can get posted because of multiple notifications
389    // We want to ignore the other ones that happen when a task is in progress.
390    return;
391  }
392  Reset();
393  if (!shutting_down_) {
394    // Check if we have work to do.
395    if (HavePendingTasks()) {
396      if (!task_in_progress_ && printer_update_pending_) {
397        printer_update_pending_ = false;
398        task_in_progress_ = UpdatePrinterInfo();
399        VLOG(1) << "CP_CONNECTOR: Changed task in progress"
400                << ", printer id: " << printer_info_cloud_.printer_id
401                << ", task in progress: " << task_in_progress_;
402      }
403      if (!task_in_progress_ && job_check_pending_) {
404        task_in_progress_ = true;
405        VLOG(1) << "CP_CONNECTOR: Changed task in progress"
406                ", printer id: " << printer_info_cloud_.printer_id
407                << ", task in progress: " << task_in_progress_;
408        job_check_pending_ = false;
409        // We need to fetch any pending jobs for this printer
410        SetNextJSONHandler(&PrinterJobHandler::HandleJobMetadataResponse);
411        request_ = CloudPrintURLFetcher::Create();
412        request_->StartGetRequest(
413            GetUrlForJobFetch(
414                cloud_print_server_url_, printer_info_cloud_.printer_id,
415                job_fetch_reason_),
416            this,
417            kCloudPrintAPIMaxRetryCount,
418            std::string());
419        last_job_fetch_time_ = base::TimeTicks::Now();
420        VLOG(1) << "CP_CONNECTOR: Last job fetch time"
421                << ", printer name: " << printer_info_.printer_name.c_str()
422                << ", timestamp: " << last_job_fetch_time_.ToInternalValue();
423        job_fetch_reason_.clear();
424      }
425    }
426  }
427}
428
429void PrinterJobHandler::Stop() {
430  VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
431          << ", printer id: " << printer_info_cloud_.printer_id;
432  task_in_progress_ = false;
433  VLOG(1) << "CP_CONNECTOR: Changed task in progress"
434          << ", printer id: " << printer_info_cloud_.printer_id
435          << ", task in progress: " << task_in_progress_;
436  Reset();
437  if (HavePendingTasks()) {
438    MessageLoop::current()->PostTask(
439        FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
440  }
441}
442
443void PrinterJobHandler::StartPrinting() {
444  VLOG(1) << "CP_CONNECTOR: Starting printing"
445          << ", printer id: " << printer_info_cloud_.printer_id;
446  // We are done with the request object for now.
447  request_ = NULL;
448  if (!shutting_down_) {
449    if (!print_thread_.Start()) {
450      VLOG(1) << "CP_CONNECTOR: Failed to start print thread"
451              << ", printer id: " << printer_info_cloud_.printer_id;
452      JobFailed(PRINT_FAILED);
453    } else {
454      print_thread_.message_loop()->PostTask(
455          FROM_HERE, base::Bind(&PrinterJobHandler::DoPrint, this, job_details_,
456                                printer_info_.printer_name));
457    }
458  }
459}
460
461void PrinterJobHandler::Reset() {
462  job_details_.Clear();
463  request_ = NULL;
464  print_thread_.Stop();
465}
466
467void PrinterJobHandler::UpdateJobStatus(PrintJobStatus status,
468                                        PrintJobError error) {
469  VLOG(1) << "CP_CONNECTOR: Updating job status"
470          << ", printer id: " << printer_info_cloud_.printer_id
471          << ", job id: " << job_details_.job_id_
472          << ", job status: " << status;
473  if (shutting_down_) {
474    VLOG(1) << "CP_CONNECTOR: Job status update aborted (shutting down)"
475            << ", printer id: " << printer_info_cloud_.printer_id
476            << ", job id: " << job_details_.job_id_;
477    return;
478  }
479  if (job_details_.job_id_.empty()) {
480    VLOG(1) << "CP_CONNECTOR: Job status update aborted (empty job id)"
481            << ", printer id: " << printer_info_cloud_.printer_id;
482    return;
483  }
484
485  if (error == SUCCESS) {
486    SetNextJSONHandler(
487        &PrinterJobHandler::HandleSuccessStatusUpdateResponse);
488  } else {
489    SetNextJSONHandler(
490        &PrinterJobHandler::HandleFailureStatusUpdateResponse);
491  }
492  request_ = CloudPrintURLFetcher::Create();
493  request_->StartGetRequest(GetUrlForJobStatusUpdate(cloud_print_server_url_,
494                                                     job_details_.job_id_,
495                                                     status),
496      this,
497      kCloudPrintAPIMaxRetryCount,
498      std::string());
499}
500
501void PrinterJobHandler::RunScheduledJobCheck() {
502  CheckForJobs(kJobFetchReasonRetry);
503}
504
505void PrinterJobHandler::SetNextJSONHandler(JSONDataHandler handler) {
506  next_json_data_handler_ = handler;
507  next_data_handler_ = NULL;
508}
509
510void PrinterJobHandler::SetNextDataHandler(DataHandler handler) {
511  next_data_handler_ = handler;
512  next_json_data_handler_ = NULL;
513}
514
515void PrinterJobHandler::JobFailed(PrintJobError error) {
516  VLOG(1) << "CP_CONNECTOR: Job failed"
517          << ", printer id: " << printer_info_cloud_.printer_id
518          << ", job id: " << job_details_.job_id_
519          << ", error: " << error;
520  if (!shutting_down_) {
521    UpdateJobStatus(PRINT_JOB_STATUS_ERROR, error);
522    // This job failed, but others may be pending.  Schedule a check.
523    job_check_pending_ = true;
524    job_fetch_reason_ = kJobFetchReasonFailure;
525  }
526}
527
528void PrinterJobHandler::JobSpooled(PlatformJobId local_job_id) {
529  VLOG(1) << "CP_CONNECTOR: Job spooled"
530          << ", printer id: " << printer_info_cloud_.printer_id
531          << ", job id: " << local_job_id;
532  if (!shutting_down_) {
533    local_job_id_ = local_job_id;
534    UpdateJobStatus(PRINT_JOB_STATUS_IN_PROGRESS, SUCCESS);
535    print_thread_.Stop();
536  }
537}
538
539bool PrinterJobHandler::UpdatePrinterInfo() {
540  if (!printer_watcher_) {
541    LOG(ERROR) << "CP_CONNECTOR: Printer watcher is missing."
542               << " Check printer server url for printer id: "
543               << printer_info_cloud_.printer_id;
544    return false;
545  }
546
547  VLOG(1) << "CP_CONNECTOR: Updating printer info"
548          << ", printer id: " << printer_info_cloud_.printer_id;
549  // We need to update the parts of the printer info that have changed
550  // (could be printer name, description, status or capabilities).
551  // First asynchronously fetch the capabilities.
552  printing::PrinterBasicInfo printer_info;
553  printer_watcher_->GetCurrentPrinterInfo(&printer_info);
554
555  // Asynchronously fetch the printer caps and defaults. The story will
556  // continue in OnReceivePrinterCaps.
557  print_system_->GetPrinterCapsAndDefaults(
558      printer_info.printer_name.c_str(),
559      base::Bind(&PrinterJobHandler::OnReceivePrinterCaps,
560                 weak_ptr_factory_.GetWeakPtr()));
561
562  // While we are waiting for the data, pretend we have work to do and return
563  // true.
564  return true;
565}
566
567bool PrinterJobHandler::HavePendingTasks() {
568  return (job_check_pending_ || printer_update_pending_);
569}
570
571void PrinterJobHandler::FailedFetchingJobData() {
572  if (!shutting_down_) {
573    LOG(ERROR) << "CP_CONNECTOR: Failed fetching job data"
574               << ", printer name: " << printer_info_.printer_name
575               << ", job id: " << job_details_.job_id_;
576    JobFailed(INVALID_JOB_DATA);
577  }
578}
579
580void PrinterJobHandler::OnReceivePrinterCaps(
581    bool succeeded,
582    const std::string& printer_name,
583    const printing::PrinterCapsAndDefaults& caps_and_defaults) {
584  printing::PrinterBasicInfo printer_info;
585  if (printer_watcher_)
586    printer_watcher_->GetCurrentPrinterInfo(&printer_info);
587
588  std::string post_data;
589  std::string mime_boundary;
590  CreateMimeBoundaryForUpload(&mime_boundary);
591
592  if (succeeded) {
593    std::string caps_hash =
594        base::MD5String(caps_and_defaults.printer_capabilities);
595    if (caps_hash != printer_info_cloud_.caps_hash) {
596      // Hashes don't match, we need to upload new capabilities (the defaults
597      // go for free along with the capabilities)
598      printer_info_cloud_.caps_hash = caps_hash;
599      AddMultipartValueForUpload(kPrinterCapsValue,
600          caps_and_defaults.printer_capabilities, mime_boundary,
601          caps_and_defaults.caps_mime_type, &post_data);
602      AddMultipartValueForUpload(kPrinterDefaultsValue,
603          caps_and_defaults.printer_defaults, mime_boundary,
604          caps_and_defaults.defaults_mime_type, &post_data);
605      AddMultipartValueForUpload(kPrinterCapsHashValue,
606          caps_hash, mime_boundary, std::string(), &post_data);
607    }
608  } else {
609    LOG(ERROR) << "Failed to get printer caps and defaults"
610               << ", printer name: " << printer_name;
611  }
612
613  std::string tags_hash = GetHashOfPrinterInfo(printer_info);
614  if (tags_hash != printer_info_cloud_.tags_hash) {
615    printer_info_cloud_.tags_hash = tags_hash;
616    post_data += GetPostDataForPrinterInfo(printer_info, mime_boundary);
617    // Remove all the existing proxy tags.
618    std::string cp_tag_wildcard(kCloudPrintServiceProxyTagPrefix);
619    cp_tag_wildcard += ".*";
620    AddMultipartValueForUpload(kPrinterRemoveTagValue,
621        cp_tag_wildcard, mime_boundary, std::string(), &post_data);
622  }
623
624  if (printer_info.printer_name != printer_info_.printer_name) {
625    AddMultipartValueForUpload(kPrinterNameValue,
626        printer_info.printer_name, mime_boundary, std::string(), &post_data);
627  }
628  if (printer_info.printer_description != printer_info_.printer_description) {
629    AddMultipartValueForUpload(kPrinterDescValue,
630      printer_info.printer_description, mime_boundary,
631      std::string(), &post_data);
632  }
633  if (printer_info.printer_status != printer_info_.printer_status) {
634    AddMultipartValueForUpload(kPrinterStatusValue,
635        base::StringPrintf("%d", printer_info.printer_status), mime_boundary,
636        std::string(), &post_data);
637  }
638  printer_info_ = printer_info;
639  if (!post_data.empty()) {
640    // Terminate the request body
641    post_data.append("--" + mime_boundary + "--\r\n");
642    std::string mime_type("multipart/form-data; boundary=");
643    mime_type += mime_boundary;
644    SetNextJSONHandler(&PrinterJobHandler::HandlePrinterUpdateResponse);
645    request_ = CloudPrintURLFetcher::Create();
646    request_->StartPostRequest(
647        GetUrlForPrinterUpdate(
648            cloud_print_server_url_, printer_info_cloud_.printer_id),
649        this,
650        kCloudPrintAPIMaxRetryCount,
651        mime_type,
652        post_data,
653        std::string());
654  } else {
655    // We are done here. Go to the Stop state
656    VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
657            << ", printer name: " << printer_name;
658    MessageLoop::current()->PostTask(
659        FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
660  }
661}
662
663// The following methods are called on |print_thread_|. It is not safe to
664// access any members other than |job_handler_message_loop_proxy_|,
665// |job_spooler_| and |print_system_|.
666void PrinterJobHandler::DoPrint(const JobDetails& job_details,
667                                const std::string& printer_name) {
668  job_spooler_ = print_system_->CreateJobSpooler();
669  DCHECK(job_spooler_);
670  if (!job_spooler_)
671    return;
672  string16 document_name =
673      printing::PrintBackend::SimplifyDocumentTitle(
674          UTF8ToUTF16(job_details.job_title_));
675  if (document_name.empty()) {
676    document_name = printing::PrintBackend::SimplifyDocumentTitle(
677        l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
678  }
679  if (!job_spooler_->Spool(job_details.print_ticket_,
680                           job_details.print_data_file_path_,
681                           job_details.print_data_mime_type_,
682                           printer_name,
683                           UTF16ToUTF8(document_name),
684                           job_details.tags_,
685                           this)) {
686    OnJobSpoolFailed();
687  }
688}
689
690}  // namespace cloud_print
691