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/print_system.h"
      6 
      7 #include <cups/cups.h>
      8 #include <dlfcn.h>
      9 #include <errno.h>
     10 #include <pthread.h>
     11 
     12 #include <algorithm>
     13 #include <list>
     14 #include <map>
     15 
     16 #include "base/bind.h"
     17 #include "base/files/file_path.h"
     18 #include "base/json/json_reader.h"
     19 #include "base/logging.h"
     20 #include "base/md5.h"
     21 #include "base/memory/scoped_ptr.h"
     22 #include "base/message_loop/message_loop.h"
     23 #include "base/rand_util.h"
     24 #include "base/strings/string_number_conversions.h"
     25 #include "base/strings/string_util.h"
     26 #include "base/strings/utf_string_conversions.h"
     27 #include "base/values.h"
     28 #include "chrome/common/cloud_print/cloud_print_constants.h"
     29 #include "chrome/common/crash_keys.h"
     30 #include "chrome/service/cloud_print/cloud_print_service_helpers.h"
     31 #include "grit/generated_resources.h"
     32 #include "printing/backend/cups_helper.h"
     33 #include "printing/backend/print_backend.h"
     34 #include "printing/backend/print_backend_consts.h"
     35 #include "ui/base/l10n/l10n_util.h"
     36 #include "url/gurl.h"
     37 
     38 namespace {
     39 
     40 // Print system config options.
     41 const char kCUPSPrintServerURLs[] = "print_server_urls";
     42 const char kCUPSUpdateTimeoutMs[] = "update_timeout_ms";
     43 const char kCUPSNotifyDelete[] = "notify_delete";
     44 const char kCUPSSupportedMimeTipes[] = "supported_mime_types";
     45 
     46 // Default mime types supported by CUPS
     47 // http://www.cups.org/articles.php?L205+TFAQ+Q
     48 const char kCUPSDefaultSupportedTypes[] =
     49     "application/pdf,application/postscript,image/jpeg,image/png,image/gif";
     50 
     51 // Time interval to check for printer's updates.
     52 const int kCheckForPrinterUpdatesMinutes = 5;
     53 
     54 // Job update timeout
     55 const int kJobUpdateTimeoutSeconds = 5;
     56 
     57 // Job id for dry run (it should not affect CUPS job ids, since 0 job-id is
     58 // invalid in CUPS.
     59 const int kDryRunJobId = 0;
     60 
     61 }  // namespace
     62 
     63 namespace cloud_print {
     64 
     65 struct PrintServerInfoCUPS {
     66   GURL url;
     67   scoped_refptr<printing::PrintBackend> backend;
     68   printing::PrinterList printers;
     69   // CapsMap cache PPD until the next update and give a fast access to it by
     70   // printer name. PPD request is relatively expensive and this should minimize
     71   // the number of requests.
     72   typedef std::map<std::string, printing::PrinterCapsAndDefaults> CapsMap;
     73   CapsMap caps_cache;
     74 };
     75 
     76 class PrintSystemCUPS : public PrintSystem {
     77  public:
     78   explicit PrintSystemCUPS(const DictionaryValue* print_system_settings);
     79 
     80   // PrintSystem implementation.
     81   virtual PrintSystemResult Init() OVERRIDE;
     82   virtual PrintSystem::PrintSystemResult EnumeratePrinters(
     83       printing::PrinterList* printer_list) OVERRIDE;
     84   virtual void GetPrinterCapsAndDefaults(
     85       const std::string& printer_name,
     86       const PrinterCapsAndDefaultsCallback& callback) OVERRIDE;
     87   virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
     88   virtual bool ValidatePrintTicket(
     89       const std::string& printer_name,
     90       const std::string& print_ticket_data) OVERRIDE;
     91   virtual bool GetJobDetails(const std::string& printer_name,
     92                              PlatformJobId job_id,
     93                              PrintJobDetails *job_details) OVERRIDE;
     94   virtual PrintSystem::PrintServerWatcher* CreatePrintServerWatcher() OVERRIDE;
     95   virtual PrintSystem::PrinterWatcher* CreatePrinterWatcher(
     96       const std::string& printer_name) OVERRIDE;
     97   virtual PrintSystem::JobSpooler* CreateJobSpooler() OVERRIDE;
     98   virtual std::string GetSupportedMimeTypes() OVERRIDE;
     99 
    100   // Helper functions.
    101   PlatformJobId SpoolPrintJob(const std::string& print_ticket,
    102                               const base::FilePath& print_data_file_path,
    103                               const std::string& print_data_mime_type,
    104                               const std::string& printer_name,
    105                               const std::string& job_title,
    106                               const std::vector<std::string>& tags,
    107                               bool* dry_run);
    108   bool GetPrinterInfo(const std::string& printer_name,
    109                       printing::PrinterBasicInfo* info);
    110   bool ParsePrintTicket(const std::string& print_ticket,
    111                         std::map<std::string, std::string>* options);
    112 
    113   // Synchronous version of GetPrinterCapsAndDefaults.
    114   bool GetPrinterCapsAndDefaults(
    115       const std::string& printer_name,
    116       printing::PrinterCapsAndDefaults* printer_info);
    117 
    118   base::TimeDelta GetUpdateTimeout() const {
    119     return update_timeout_;
    120   }
    121 
    122   bool NotifyDelete() const {
    123     // Notify about deleted printers only when we
    124     // fetched printers list without errors.
    125     return notify_delete_ && printer_enum_succeeded_;
    126   }
    127 
    128  private:
    129   virtual ~PrintSystemCUPS() {}
    130 
    131   // Following functions are wrappers around corresponding CUPS functions.
    132   // <functions>2()  are called when print server is specified, and plain
    133   // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
    134   // in the <functions>2(), it does not work in CUPS prior to 1.4.
    135   int GetJobs(cups_job_t** jobs, const GURL& url,
    136               http_encryption_t encryption, const char* name,
    137               int myjobs, int whichjobs);
    138   int PrintFile(const GURL& url, http_encryption_t encryption,
    139                 const char* name, const char* filename,
    140                 const char* title, int num_options, cups_option_t* options);
    141 
    142   void InitPrintBackends(const DictionaryValue* print_system_settings);
    143   void AddPrintServer(const std::string& url);
    144 
    145   void UpdatePrinters();
    146 
    147   // Full name contains print server url:port and printer name. Short name
    148   // is the name of the printer in the CUPS server.
    149   std::string MakeFullPrinterName(const GURL& url,
    150                                   const std::string& short_printer_name);
    151   PrintServerInfoCUPS* FindServerByFullName(
    152       const std::string& full_printer_name, std::string* short_printer_name);
    153 
    154   // Helper method to invoke a PrinterCapsAndDefaultsCallback.
    155   static void RunCapsCallback(
    156       const PrinterCapsAndDefaultsCallback& callback,
    157       bool succeeded,
    158       const std::string& printer_name,
    159       const printing::PrinterCapsAndDefaults& printer_info);
    160 
    161   // PrintServerList contains information about all print servers and backends
    162   // this proxy is connected to.
    163   typedef std::list<PrintServerInfoCUPS> PrintServerList;
    164   PrintServerList print_servers_;
    165 
    166   base::TimeDelta update_timeout_;
    167   bool initialized_;
    168   bool printer_enum_succeeded_;
    169   bool notify_delete_;
    170   http_encryption_t cups_encryption_;
    171   std::string supported_mime_types_;
    172 };
    173 
    174 class PrintServerWatcherCUPS
    175   : public PrintSystem::PrintServerWatcher {
    176  public:
    177   explicit PrintServerWatcherCUPS(PrintSystemCUPS* print_system)
    178       : print_system_(print_system),
    179         delegate_(NULL) {
    180   }
    181 
    182   // PrintSystem::PrintServerWatcher implementation.
    183   virtual bool StartWatching(
    184       PrintSystem::PrintServerWatcher::Delegate* delegate) OVERRIDE {
    185     delegate_ = delegate;
    186     printers_hash_ = GetPrintersHash();
    187     base::MessageLoop::current()->PostDelayedTask(
    188         FROM_HERE,
    189         base::Bind(&PrintServerWatcherCUPS::CheckForUpdates, this),
    190         print_system_->GetUpdateTimeout());
    191     return true;
    192   }
    193 
    194   virtual bool StopWatching() OVERRIDE {
    195     delegate_ = NULL;
    196     return true;
    197   }
    198 
    199   void CheckForUpdates() {
    200     if (delegate_ == NULL)
    201       return;  // Orphan call. We have been stopped already.
    202     VLOG(1) << "CP_CUPS: Checking for new printers";
    203     std::string new_hash = GetPrintersHash();
    204     if (printers_hash_ != new_hash) {
    205       printers_hash_ = new_hash;
    206       delegate_->OnPrinterAdded();
    207     }
    208     base::MessageLoop::current()->PostDelayedTask(
    209         FROM_HERE,
    210         base::Bind(&PrintServerWatcherCUPS::CheckForUpdates, this),
    211         print_system_->GetUpdateTimeout());
    212   }
    213 
    214  protected:
    215   virtual ~PrintServerWatcherCUPS() {
    216     StopWatching();
    217   }
    218 
    219  private:
    220   std::string GetPrintersHash() {
    221     printing::PrinterList printer_list;
    222     print_system_->EnumeratePrinters(&printer_list);
    223 
    224     // Sort printer names.
    225     std::vector<std::string> printers;
    226     printing::PrinterList::iterator it;
    227     for (it = printer_list.begin(); it != printer_list.end(); ++it)
    228       printers.push_back(it->printer_name);
    229     std::sort(printers.begin(), printers.end());
    230 
    231     std::string to_hash;
    232     for (size_t i = 0; i < printers.size(); i++)
    233       to_hash += printers[i];
    234 
    235     return base::MD5String(to_hash);
    236   }
    237 
    238   scoped_refptr<PrintSystemCUPS> print_system_;
    239   PrintSystem::PrintServerWatcher::Delegate* delegate_;
    240   std::string printers_hash_;
    241 
    242   DISALLOW_COPY_AND_ASSIGN(PrintServerWatcherCUPS);
    243 };
    244 
    245 class PrinterWatcherCUPS
    246     : public PrintSystem::PrinterWatcher {
    247  public:
    248   PrinterWatcherCUPS(PrintSystemCUPS* print_system,
    249                      const std::string& printer_name)
    250       : printer_name_(printer_name),
    251         delegate_(NULL),
    252         print_system_(print_system) {
    253   }
    254 
    255   // PrintSystem::PrinterWatcher implementation.
    256   virtual bool StartWatching(
    257       PrintSystem::PrinterWatcher::Delegate* delegate) OVERRIDE{
    258     scoped_refptr<printing::PrintBackend> print_backend(
    259         printing::PrintBackend::CreateInstance(NULL));
    260     crash_keys::ScopedPrinterInfo crash_key(
    261         print_backend->GetPrinterDriverInfo(printer_name_));
    262     if (delegate_ != NULL)
    263       StopWatching();
    264     delegate_ = delegate;
    265     settings_hash_ = GetSettingsHash();
    266     // Schedule next job status update.
    267     base::MessageLoop::current()->PostDelayedTask(
    268         FROM_HERE,
    269         base::Bind(&PrinterWatcherCUPS::JobStatusUpdate, this),
    270         base::TimeDelta::FromSeconds(kJobUpdateTimeoutSeconds));
    271     // Schedule next printer check.
    272     // TODO(gene): Randomize time for the next printer update.
    273     base::MessageLoop::current()->PostDelayedTask(
    274         FROM_HERE,
    275         base::Bind(&PrinterWatcherCUPS::PrinterUpdate, this),
    276         print_system_->GetUpdateTimeout());
    277     return true;
    278   }
    279 
    280   virtual bool StopWatching() OVERRIDE{
    281     delegate_ = NULL;
    282     return true;
    283   }
    284 
    285   virtual bool GetCurrentPrinterInfo(
    286       printing::PrinterBasicInfo* printer_info) OVERRIDE {
    287     DCHECK(printer_info);
    288     return print_system_->GetPrinterInfo(printer_name_, printer_info);
    289   }
    290 
    291   void JobStatusUpdate() {
    292     if (delegate_ == NULL)
    293       return;  // Orphan call. We have been stopped already.
    294     // For CUPS proxy, we are going to fire OnJobChanged notification
    295     // periodically. Higher level will check if there are any outstanding
    296     // jobs for this printer and check their status. If printer has no
    297     // outstanding jobs, OnJobChanged() will do nothing.
    298     delegate_->OnJobChanged();
    299     base::MessageLoop::current()->PostDelayedTask(
    300         FROM_HERE,
    301         base::Bind(&PrinterWatcherCUPS::JobStatusUpdate, this),
    302         base::TimeDelta::FromSeconds(kJobUpdateTimeoutSeconds));
    303   }
    304 
    305   void PrinterUpdate() {
    306     if (delegate_ == NULL)
    307       return;  // Orphan call. We have been stopped already.
    308     VLOG(1) << "CP_CUPS: Checking for updates"
    309             << ", printer name: " << printer_name_;
    310     if (print_system_->NotifyDelete() &&
    311         !print_system_->IsValidPrinter(printer_name_)) {
    312       delegate_->OnPrinterDeleted();
    313       VLOG(1) << "CP_CUPS: Printer deleted"
    314               << ", printer name: " << printer_name_;
    315     } else {
    316       std::string new_hash = GetSettingsHash();
    317       if (settings_hash_ != new_hash) {
    318         settings_hash_ = new_hash;
    319         delegate_->OnPrinterChanged();
    320         VLOG(1) << "CP_CUPS: Printer configuration changed"
    321                 << ", printer name: " << printer_name_;
    322       }
    323     }
    324     base::MessageLoop::current()->PostDelayedTask(
    325         FROM_HERE,
    326         base::Bind(&PrinterWatcherCUPS::PrinterUpdate, this),
    327         print_system_->GetUpdateTimeout());
    328   }
    329 
    330  protected:
    331   virtual ~PrinterWatcherCUPS() {
    332     StopWatching();
    333   }
    334 
    335  private:
    336   std::string GetSettingsHash() {
    337     printing::PrinterBasicInfo info;
    338     if (!print_system_->GetPrinterInfo(printer_name_, &info))
    339       return std::string();
    340 
    341     printing::PrinterCapsAndDefaults caps;
    342     if (!print_system_->GetPrinterCapsAndDefaults(printer_name_, &caps))
    343       return std::string();
    344 
    345     std::string to_hash(info.printer_name);
    346     to_hash += info.printer_description;
    347     std::map<std::string, std::string>::const_iterator it;
    348     for (it = info.options.begin(); it != info.options.end(); ++it) {
    349       to_hash += it->first;
    350       to_hash += it->second;
    351     }
    352 
    353     to_hash += caps.printer_capabilities;
    354     to_hash += caps.caps_mime_type;
    355     to_hash += caps.printer_defaults;
    356     to_hash += caps.defaults_mime_type;
    357 
    358     return base::MD5String(to_hash);
    359   }
    360   std::string printer_name_;
    361   PrintSystem::PrinterWatcher::Delegate* delegate_;
    362   scoped_refptr<PrintSystemCUPS> print_system_;
    363   std::string settings_hash_;
    364 
    365   DISALLOW_COPY_AND_ASSIGN(PrinterWatcherCUPS);
    366 };
    367 
    368 class JobSpoolerCUPS : public PrintSystem::JobSpooler {
    369  public:
    370   explicit JobSpoolerCUPS(PrintSystemCUPS* print_system)
    371       : print_system_(print_system) {
    372     DCHECK(print_system_.get());
    373   }
    374 
    375   // PrintSystem::JobSpooler implementation.
    376   virtual bool Spool(const std::string& print_ticket,
    377                      const base::FilePath& print_data_file_path,
    378                      const std::string& print_data_mime_type,
    379                      const std::string& printer_name,
    380                      const std::string& job_title,
    381                      const std::vector<std::string>& tags,
    382                      JobSpooler::Delegate* delegate) OVERRIDE{
    383     DCHECK(delegate);
    384     bool dry_run = false;
    385     int job_id = print_system_->SpoolPrintJob(
    386         print_ticket, print_data_file_path, print_data_mime_type,
    387         printer_name, job_title, tags, &dry_run);
    388     base::MessageLoop::current()->PostTask(
    389         FROM_HERE,
    390         base::Bind(&JobSpoolerCUPS::NotifyDelegate, delegate, job_id, dry_run));
    391     return true;
    392   }
    393 
    394   static void NotifyDelegate(JobSpooler::Delegate* delegate,
    395                              int job_id, bool dry_run) {
    396     if (dry_run || job_id)
    397       delegate->OnJobSpoolSucceeded(job_id);
    398     else
    399       delegate->OnJobSpoolFailed();
    400   }
    401 
    402  protected:
    403   virtual ~JobSpoolerCUPS() {}
    404 
    405  private:
    406   scoped_refptr<PrintSystemCUPS> print_system_;
    407 
    408   DISALLOW_COPY_AND_ASSIGN(JobSpoolerCUPS);
    409 };
    410 
    411 PrintSystemCUPS::PrintSystemCUPS(const DictionaryValue* print_system_settings)
    412     : update_timeout_(base::TimeDelta::FromMinutes(
    413         kCheckForPrinterUpdatesMinutes)),
    414       initialized_(false),
    415       printer_enum_succeeded_(false),
    416       notify_delete_(true),
    417       cups_encryption_(HTTP_ENCRYPT_NEVER),
    418       supported_mime_types_(kCUPSDefaultSupportedTypes) {
    419   if (print_system_settings) {
    420     int timeout;
    421     if (print_system_settings->GetInteger(kCUPSUpdateTimeoutMs, &timeout))
    422       update_timeout_ = base::TimeDelta::FromMilliseconds(timeout);
    423 
    424     int encryption;
    425     if (print_system_settings->GetInteger(kCUPSEncryption, &encryption))
    426       cups_encryption_ =
    427           static_cast<http_encryption_t>(encryption);
    428 
    429     bool notify_delete = true;
    430     if (print_system_settings->GetBoolean(kCUPSNotifyDelete, &notify_delete))
    431       notify_delete_ = notify_delete;
    432 
    433     std::string types;
    434     if (print_system_settings->GetString(kCUPSSupportedMimeTipes, &types))
    435       supported_mime_types_ = types;
    436   }
    437 
    438   InitPrintBackends(print_system_settings);
    439 }
    440 
    441 void PrintSystemCUPS::InitPrintBackends(
    442     const DictionaryValue* print_system_settings) {
    443   const ListValue* url_list;
    444   if (print_system_settings &&
    445       print_system_settings->GetList(kCUPSPrintServerURLs, &url_list)) {
    446     for (size_t i = 0; i < url_list->GetSize(); i++) {
    447       std::string print_server_url;
    448       if (url_list->GetString(i, &print_server_url))
    449         AddPrintServer(print_server_url);
    450     }
    451   }
    452 
    453   // If server list is empty, use default print server.
    454   if (print_servers_.empty())
    455     AddPrintServer(std::string());
    456 }
    457 
    458 void PrintSystemCUPS::AddPrintServer(const std::string& url) {
    459   if (url.empty())
    460     LOG(WARNING) << "No print server specified. Using default print server.";
    461 
    462   // Get Print backend for the specific print server.
    463   DictionaryValue backend_settings;
    464   backend_settings.SetString(kCUPSPrintServerURL, url);
    465 
    466   // Make CUPS requests non-blocking.
    467   backend_settings.SetString(kCUPSBlocking, kValueFalse);
    468 
    469   // Set encryption for backend.
    470   backend_settings.SetInteger(kCUPSEncryption, cups_encryption_);
    471 
    472   PrintServerInfoCUPS print_server;
    473   print_server.backend =
    474     printing::PrintBackend::CreateInstance(&backend_settings);
    475   print_server.url = GURL(url.c_str());
    476 
    477   print_servers_.push_back(print_server);
    478 }
    479 
    480 PrintSystem::PrintSystemResult PrintSystemCUPS::Init() {
    481   UpdatePrinters();
    482   initialized_ = true;
    483   return PrintSystemResult(true, std::string());
    484 }
    485 
    486 void PrintSystemCUPS::UpdatePrinters() {
    487   PrintServerList::iterator it;
    488   printer_enum_succeeded_ = true;
    489   for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
    490     if (!it->backend->EnumeratePrinters(&it->printers))
    491       printer_enum_succeeded_ = false;
    492     it->caps_cache.clear();
    493     printing::PrinterList::iterator printer_it;
    494     for (printer_it = it->printers.begin();
    495         printer_it != it->printers.end(); ++printer_it) {
    496       printer_it->printer_name = MakeFullPrinterName(it->url,
    497                                                      printer_it->printer_name);
    498     }
    499     VLOG(1) << "CP_CUPS: Updated printers list"
    500             << ", server: " << it->url
    501             << ", # of printers: " << it->printers.size();
    502   }
    503 
    504   // Schedule next update.
    505   base::MessageLoop::current()->PostDelayedTask(
    506       FROM_HERE,
    507       base::Bind(&PrintSystemCUPS::UpdatePrinters, this),
    508       GetUpdateTimeout());
    509 }
    510 
    511 PrintSystem::PrintSystemResult PrintSystemCUPS::EnumeratePrinters(
    512     printing::PrinterList* printer_list) {
    513   DCHECK(initialized_);
    514   printer_list->clear();
    515   PrintServerList::iterator it;
    516   for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
    517     printer_list->insert(printer_list->end(),
    518         it->printers.begin(), it->printers.end());
    519   }
    520   VLOG(1) << "CP_CUPS: Total printers enumerated: " << printer_list->size();
    521   // TODO(sanjeevr): Maybe some day we want to report the actual server names
    522   // for which the enumeration failed.
    523   return PrintSystemResult(printer_enum_succeeded_, std::string());
    524 }
    525 
    526 void PrintSystemCUPS::GetPrinterCapsAndDefaults(
    527     const std::string& printer_name,
    528     const PrinterCapsAndDefaultsCallback& callback) {
    529   printing::PrinterCapsAndDefaults printer_info;
    530   bool succeeded = GetPrinterCapsAndDefaults(printer_name, &printer_info);
    531   base::MessageLoop::current()->PostTask(
    532       FROM_HERE,
    533       base::Bind(&PrintSystemCUPS::RunCapsCallback,
    534                  callback,
    535                  succeeded,
    536                  printer_name,
    537                  printer_info));
    538 }
    539 
    540 bool PrintSystemCUPS::IsValidPrinter(const std::string& printer_name) {
    541   return GetPrinterInfo(printer_name, NULL);
    542 }
    543 
    544 bool PrintSystemCUPS::ValidatePrintTicket(const std::string& printer_name,
    545                                         const std::string& print_ticket_data) {
    546   DCHECK(initialized_);
    547   scoped_ptr<Value> ticket_value(base::JSONReader::Read(print_ticket_data));
    548   return ticket_value != NULL && ticket_value->IsType(Value::TYPE_DICTIONARY);
    549 }
    550 
    551 // Print ticket on linux is a JSON string containing only one dictionary.
    552 bool PrintSystemCUPS::ParsePrintTicket(
    553     const std::string& print_ticket,
    554     std::map<std::string, std::string>* options) {
    555   DCHECK(options);
    556   scoped_ptr<Value> ticket_value(base::JSONReader::Read(print_ticket));
    557   if (ticket_value == NULL || !ticket_value->IsType(Value::TYPE_DICTIONARY))
    558     return false;
    559 
    560   options->clear();
    561   DictionaryValue* ticket_dict =
    562       static_cast<DictionaryValue*>(ticket_value.get());
    563   for (DictionaryValue::Iterator it(*ticket_dict); !it.IsAtEnd();
    564        it.Advance()) {
    565     std::string value;
    566     if (it.value().GetAsString(&value))
    567       (*options)[it.key()] = value;
    568   }
    569 
    570   return true;
    571 }
    572 
    573 bool PrintSystemCUPS::GetPrinterCapsAndDefaults(
    574     const std::string& printer_name,
    575     printing::PrinterCapsAndDefaults* printer_info) {
    576   DCHECK(initialized_);
    577   std::string short_printer_name;
    578   PrintServerInfoCUPS* server_info =
    579       FindServerByFullName(printer_name, &short_printer_name);
    580   if (!server_info)
    581     return false;
    582 
    583   PrintServerInfoCUPS::CapsMap::iterator caps_it =
    584       server_info->caps_cache.find(printer_name);
    585   if (caps_it != server_info->caps_cache.end()) {
    586     *printer_info = caps_it->second;
    587     return true;
    588   }
    589 
    590   // TODO(gene): Retry multiple times in case of error.
    591   crash_keys::ScopedPrinterInfo crash_key(
    592       server_info->backend->GetPrinterDriverInfo(short_printer_name));
    593   if (!server_info->backend->GetPrinterCapsAndDefaults(short_printer_name,
    594                                                        printer_info) ) {
    595     return false;
    596   }
    597 
    598   server_info->caps_cache[printer_name] = *printer_info;
    599   return true;
    600 }
    601 
    602 bool PrintSystemCUPS::GetJobDetails(const std::string& printer_name,
    603                                     PlatformJobId job_id,
    604                                     PrintJobDetails *job_details) {
    605   DCHECK(initialized_);
    606   DCHECK(job_details);
    607 
    608   std::string short_printer_name;
    609   PrintServerInfoCUPS* server_info =
    610       FindServerByFullName(printer_name, &short_printer_name);
    611   if (!server_info)
    612     return false;
    613 
    614   crash_keys::ScopedPrinterInfo crash_key(
    615       server_info->backend->GetPrinterDriverInfo(short_printer_name));
    616   cups_job_t* jobs = NULL;
    617   int num_jobs = GetJobs(&jobs, server_info->url, cups_encryption_,
    618                          short_printer_name.c_str(), 1, -1);
    619   bool error = (num_jobs == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE);
    620   if (error) {
    621     VLOG(1) << "CP_CUPS: Error getting jobs from CUPS server"
    622             << ", printer name:" << printer_name
    623             << ", error: " << static_cast<int>(cupsLastError());
    624     return false;
    625   }
    626 
    627   // Check if the request is for dummy dry run job.
    628   // We check this after calling GetJobs API to see if this printer is actually
    629   // accessible through CUPS.
    630   if (job_id == kDryRunJobId) {
    631     job_details->status = PRINT_JOB_STATUS_COMPLETED;
    632     VLOG(1) << "CP_CUPS: Dry run job succeeded"
    633             << ", printer name: " << printer_name;
    634     return true;
    635   }
    636 
    637   bool found = false;
    638   for (int i = 0; i < num_jobs; i++) {
    639     if (jobs[i].id == job_id) {
    640       found = true;
    641       switch (jobs[i].state) {
    642         case IPP_JOB_PENDING :
    643         case IPP_JOB_HELD :
    644         case IPP_JOB_PROCESSING :
    645           job_details->status = PRINT_JOB_STATUS_IN_PROGRESS;
    646           break;
    647         case IPP_JOB_STOPPED :
    648         case IPP_JOB_CANCELLED :
    649         case IPP_JOB_ABORTED :
    650           job_details->status = PRINT_JOB_STATUS_ERROR;
    651           break;
    652         case IPP_JOB_COMPLETED :
    653           job_details->status = PRINT_JOB_STATUS_COMPLETED;
    654           break;
    655         default:
    656           job_details->status = PRINT_JOB_STATUS_INVALID;
    657       }
    658       job_details->platform_status_flags = jobs[i].state;
    659 
    660       // We don't have any details on the number of processed pages here.
    661       break;
    662     }
    663   }
    664 
    665   if (found)
    666     VLOG(1) << "CP_CUPS: Job found"
    667             << ", printer name: " << printer_name
    668             << ", cups job id: " << job_id
    669             << ", cups job status: " << job_details->status;
    670   else
    671     LOG(WARNING) << "CP_CUPS: Job not found"
    672                  << ", printer name: " << printer_name
    673                  << ", cups job id: " << job_id;
    674 
    675   cupsFreeJobs(num_jobs, jobs);
    676   return found;
    677 }
    678 
    679 bool PrintSystemCUPS::GetPrinterInfo(const std::string& printer_name,
    680                                      printing::PrinterBasicInfo* info) {
    681   DCHECK(initialized_);
    682   if (info)
    683     VLOG(1) << "CP_CUPS: Getting printer info"
    684             << ", printer name: " << printer_name;
    685 
    686   std::string short_printer_name;
    687   PrintServerInfoCUPS* server_info =
    688       FindServerByFullName(printer_name, &short_printer_name);
    689   if (!server_info)
    690     return false;
    691 
    692   printing::PrinterList::iterator it;
    693   for (it = server_info->printers.begin();
    694       it != server_info->printers.end(); ++it) {
    695     if (it->printer_name == printer_name) {
    696       if (info)
    697         *info = *it;
    698       return true;
    699     }
    700   }
    701   return false;
    702 }
    703 
    704 PrintSystem::PrintServerWatcher*
    705 PrintSystemCUPS::CreatePrintServerWatcher() {
    706   DCHECK(initialized_);
    707   return new PrintServerWatcherCUPS(this);
    708 }
    709 
    710 PrintSystem::PrinterWatcher* PrintSystemCUPS::CreatePrinterWatcher(
    711     const std::string& printer_name) {
    712   DCHECK(initialized_);
    713   DCHECK(!printer_name.empty());
    714   return new PrinterWatcherCUPS(this, printer_name);
    715 }
    716 
    717 PrintSystem::JobSpooler* PrintSystemCUPS::CreateJobSpooler() {
    718   DCHECK(initialized_);
    719   return new JobSpoolerCUPS(this);
    720 }
    721 
    722 std::string PrintSystemCUPS::GetSupportedMimeTypes() {
    723   return supported_mime_types_;
    724 }
    725 
    726 scoped_refptr<PrintSystem> PrintSystem::CreateInstance(
    727     const DictionaryValue* print_system_settings) {
    728   return new PrintSystemCUPS(print_system_settings);
    729 }
    730 
    731 int PrintSystemCUPS::PrintFile(const GURL& url, http_encryption_t encryption,
    732                                const char* name, const char* filename,
    733                                const char* title, int num_options,
    734                                cups_option_t* options) {
    735   if (url.is_empty()) {  // Use default (local) print server.
    736     return cupsPrintFile(name, filename, title, num_options, options);
    737   } else {
    738     printing::HttpConnectionCUPS http(url, encryption);
    739     http.SetBlocking(false);
    740     return cupsPrintFile2(http.http(), name, filename,
    741                           title, num_options, options);
    742   }
    743 }
    744 
    745 int PrintSystemCUPS::GetJobs(cups_job_t** jobs, const GURL& url,
    746                              http_encryption_t encryption,
    747                              const char* name, int myjobs, int whichjobs) {
    748   if (url.is_empty()) {  // Use default (local) print server.
    749     return cupsGetJobs(jobs, name, myjobs, whichjobs);
    750   } else {
    751     printing::HttpConnectionCUPS http(url, encryption);
    752     http.SetBlocking(false);
    753     return cupsGetJobs2(http.http(), jobs, name, myjobs, whichjobs);
    754   }
    755 }
    756 
    757 PlatformJobId PrintSystemCUPS::SpoolPrintJob(
    758     const std::string& print_ticket,
    759     const base::FilePath& print_data_file_path,
    760     const std::string& print_data_mime_type,
    761     const std::string& printer_name,
    762     const std::string& job_title,
    763     const std::vector<std::string>& tags,
    764     bool* dry_run) {
    765   DCHECK(initialized_);
    766   VLOG(1) << "CP_CUPS: Spooling print job, printer name: " << printer_name;
    767 
    768   std::string short_printer_name;
    769   PrintServerInfoCUPS* server_info =
    770       FindServerByFullName(printer_name, &short_printer_name);
    771   if (!server_info)
    772     return false;
    773 
    774   crash_keys::ScopedPrinterInfo crash_key(
    775       server_info->backend->GetPrinterDriverInfo(printer_name));
    776 
    777   // We need to store options as char* string for the duration of the
    778   // cupsPrintFile2 call. We'll use map here to store options, since
    779   // Dictionary value from JSON parser returns wchat_t.
    780   std::map<std::string, std::string> options;
    781   bool res = ParsePrintTicket(print_ticket, &options);
    782   DCHECK(res);  // If print ticket is invalid we still print using defaults.
    783 
    784   // Check if this is a dry run (test) job.
    785   *dry_run = IsDryRunJob(tags);
    786   if (*dry_run) {
    787     VLOG(1) << "CP_CUPS: Dry run job spooled";
    788     return kDryRunJobId;
    789   }
    790 
    791   std::vector<cups_option_t> cups_options;
    792   std::map<std::string, std::string>::iterator it;
    793 
    794   for (it = options.begin(); it != options.end(); ++it) {
    795     cups_option_t opt;
    796     opt.name = const_cast<char*>(it->first.c_str());
    797     opt.value = const_cast<char*>(it->second.c_str());
    798     cups_options.push_back(opt);
    799   }
    800 
    801   int job_id = PrintFile(server_info->url,
    802                          cups_encryption_,
    803                          short_printer_name.c_str(),
    804                          print_data_file_path.value().c_str(),
    805                          job_title.c_str(),
    806                          cups_options.size(),
    807                          &(cups_options[0]));
    808 
    809   // TODO(alexyu): Output printer id.
    810   VLOG(1) << "CP_CUPS: Job spooled"
    811           << ", printer name: " << printer_name
    812           << ", cups job id: " << job_id;
    813 
    814   return job_id;
    815 }
    816 
    817 std::string PrintSystemCUPS::MakeFullPrinterName(
    818     const GURL& url, const std::string& short_printer_name) {
    819   std::string full_name;
    820   full_name += "\\\\";
    821   full_name += url.host();
    822   if (!url.port().empty()) {
    823     full_name += ":";
    824     full_name += url.port();
    825   }
    826   full_name += "\\";
    827   full_name += short_printer_name;
    828   return full_name;
    829 }
    830 
    831 PrintServerInfoCUPS* PrintSystemCUPS::FindServerByFullName(
    832     const std::string& full_printer_name, std::string* short_printer_name) {
    833   size_t front = full_printer_name.find("\\\\");
    834   size_t separator = full_printer_name.find("\\", 2);
    835   if (front == std::string::npos || separator == std::string::npos) {
    836     LOG(WARNING) << "CP_CUPS: Invalid UNC"
    837                  << ", printer name: " << full_printer_name;
    838     return NULL;
    839   }
    840   std::string server = full_printer_name.substr(2, separator - 2);
    841 
    842   PrintServerList::iterator it;
    843   for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
    844     std::string cur_server;
    845     cur_server += it->url.host();
    846     if (!it->url.port().empty()) {
    847       cur_server += ":";
    848       cur_server += it->url.port();
    849     }
    850     if (cur_server == server) {
    851       *short_printer_name = full_printer_name.substr(separator + 1);
    852       return &(*it);
    853     }
    854   }
    855 
    856   LOG(WARNING) << "CP_CUPS: Server not found"
    857                << ", printer name: " << full_printer_name;
    858   return NULL;
    859 }
    860 
    861 void PrintSystemCUPS::RunCapsCallback(
    862     const PrinterCapsAndDefaultsCallback& callback,
    863     bool succeeded,
    864     const std::string& printer_name,
    865     const printing::PrinterCapsAndDefaults& printer_info) {
    866   callback.Run(succeeded, printer_name, printer_info);
    867 }
    868 
    869 }  // namespace cloud_print
    870