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