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 "printing/backend/cups_helper.h"
     32 #include "printing/backend/print_backend.h"
     33 #include "printing/backend/print_backend_consts.h"
     34 #include "url/gurl.h"
     35 
     36 namespace {
     37 
     38 // Print system config options.
     39 const char kCUPSPrintServerURLs[] = "print_server_urls";
     40 const char kCUPSUpdateTimeoutMs[] = "update_timeout_ms";
     41 const char kCUPSNotifyDelete[] = "notify_delete";
     42 const char kCUPSSupportedMimeTipes[] = "supported_mime_types";
     43 
     44 // Default mime types supported by CUPS
     45 // http://www.cups.org/articles.php?L205+TFAQ+Q
     46 const char kCUPSDefaultSupportedTypes[] =
     47     "application/pdf,application/postscript,image/jpeg,image/png,image/gif";
     48 
     49 // Time interval to check for printer's updates.
     50 const int kCheckForPrinterUpdatesMinutes = 5;
     51 
     52 // Job update timeout
     53 const int kJobUpdateTimeoutSeconds = 5;
     54 
     55 // Job id for dry run (it should not affect CUPS job ids, since 0 job-id is
     56 // invalid in CUPS.
     57 const int kDryRunJobId = 0;
     58 
     59 }  // namespace
     60 
     61 namespace cloud_print {
     62 
     63 struct PrintServerInfoCUPS {
     64   GURL url;
     65   scoped_refptr<printing::PrintBackend> backend;
     66   printing::PrinterList printers;
     67   // CapsMap cache PPD until the next update and give a fast access to it by
     68   // printer name. PPD request is relatively expensive and this should minimize
     69   // the number of requests.
     70   typedef std::map<std::string, printing::PrinterCapsAndDefaults> CapsMap;
     71   CapsMap caps_cache;
     72 };
     73 
     74 class PrintSystemCUPS : public PrintSystem {
     75  public:
     76   explicit PrintSystemCUPS(const base::DictionaryValue* print_system_settings);
     77 
     78   // PrintSystem implementation.
     79   virtual PrintSystemResult Init() OVERRIDE;
     80   virtual PrintSystem::PrintSystemResult EnumeratePrinters(
     81       printing::PrinterList* printer_list) OVERRIDE;
     82   virtual void GetPrinterCapsAndDefaults(
     83       const std::string& printer_name,
     84       const PrinterCapsAndDefaultsCallback& callback) OVERRIDE;
     85   virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
     86   virtual bool ValidatePrintTicket(
     87       const std::string& printer_name,
     88       const std::string& print_ticket_data,
     89       const std::string& print_ticket_mime_type) OVERRIDE;
     90   virtual bool GetJobDetails(const std::string& printer_name,
     91                              PlatformJobId job_id,
     92                              PrintJobDetails *job_details) OVERRIDE;
     93   virtual PrintSystem::PrintServerWatcher* CreatePrintServerWatcher() OVERRIDE;
     94   virtual PrintSystem::PrinterWatcher* CreatePrinterWatcher(
     95       const std::string& printer_name) OVERRIDE;
     96   virtual PrintSystem::JobSpooler* CreateJobSpooler() OVERRIDE;
     97   virtual bool UseCddAndCjt() 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 base::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 std::string& print_ticket_mime_type,
    378                      const base::FilePath& print_data_file_path,
    379                      const std::string& print_data_mime_type,
    380                      const std::string& printer_name,
    381                      const std::string& job_title,
    382                      const std::vector<std::string>& tags,
    383                      JobSpooler::Delegate* delegate) OVERRIDE{
    384     DCHECK(delegate);
    385     bool dry_run = false;
    386     int job_id = print_system_->SpoolPrintJob(
    387         print_ticket, print_data_file_path, print_data_mime_type,
    388         printer_name, job_title, tags, &dry_run);
    389     base::MessageLoop::current()->PostTask(
    390         FROM_HERE,
    391         base::Bind(&JobSpoolerCUPS::NotifyDelegate, delegate, job_id, dry_run));
    392     return true;
    393   }
    394 
    395   static void NotifyDelegate(JobSpooler::Delegate* delegate,
    396                              int job_id, bool dry_run) {
    397     if (dry_run || job_id)
    398       delegate->OnJobSpoolSucceeded(job_id);
    399     else
    400       delegate->OnJobSpoolFailed();
    401   }
    402 
    403  protected:
    404   virtual ~JobSpoolerCUPS() {}
    405 
    406  private:
    407   scoped_refptr<PrintSystemCUPS> print_system_;
    408 
    409   DISALLOW_COPY_AND_ASSIGN(JobSpoolerCUPS);
    410 };
    411 
    412 PrintSystemCUPS::PrintSystemCUPS(
    413     const base::DictionaryValue* print_system_settings)
    414     : update_timeout_(base::TimeDelta::FromMinutes(
    415         kCheckForPrinterUpdatesMinutes)),
    416       initialized_(false),
    417       printer_enum_succeeded_(false),
    418       notify_delete_(true),
    419       cups_encryption_(HTTP_ENCRYPT_NEVER),
    420       supported_mime_types_(kCUPSDefaultSupportedTypes) {
    421   if (print_system_settings) {
    422     int timeout;
    423     if (print_system_settings->GetInteger(kCUPSUpdateTimeoutMs, &timeout))
    424       update_timeout_ = base::TimeDelta::FromMilliseconds(timeout);
    425 
    426     int encryption;
    427     if (print_system_settings->GetInteger(kCUPSEncryption, &encryption))
    428       cups_encryption_ =
    429           static_cast<http_encryption_t>(encryption);
    430 
    431     bool notify_delete = true;
    432     if (print_system_settings->GetBoolean(kCUPSNotifyDelete, &notify_delete))
    433       notify_delete_ = notify_delete;
    434 
    435     std::string types;
    436     if (print_system_settings->GetString(kCUPSSupportedMimeTipes, &types))
    437       supported_mime_types_ = types;
    438   }
    439 
    440   InitPrintBackends(print_system_settings);
    441 }
    442 
    443 void PrintSystemCUPS::InitPrintBackends(
    444     const base::DictionaryValue* print_system_settings) {
    445   const base::ListValue* url_list;
    446   if (print_system_settings &&
    447       print_system_settings->GetList(kCUPSPrintServerURLs, &url_list)) {
    448     for (size_t i = 0; i < url_list->GetSize(); i++) {
    449       std::string print_server_url;
    450       if (url_list->GetString(i, &print_server_url))
    451         AddPrintServer(print_server_url);
    452     }
    453   }
    454 
    455   // If server list is empty, use default print server.
    456   if (print_servers_.empty())
    457     AddPrintServer(std::string());
    458 }
    459 
    460 void PrintSystemCUPS::AddPrintServer(const std::string& url) {
    461   if (url.empty())
    462     LOG(WARNING) << "No print server specified. Using default print server.";
    463 
    464   // Get Print backend for the specific print server.
    465   base::DictionaryValue backend_settings;
    466   backend_settings.SetString(kCUPSPrintServerURL, url);
    467 
    468   // Make CUPS requests non-blocking.
    469   backend_settings.SetString(kCUPSBlocking, kValueFalse);
    470 
    471   // Set encryption for backend.
    472   backend_settings.SetInteger(kCUPSEncryption, cups_encryption_);
    473 
    474   PrintServerInfoCUPS print_server;
    475   print_server.backend =
    476     printing::PrintBackend::CreateInstance(&backend_settings);
    477   print_server.url = GURL(url.c_str());
    478 
    479   print_servers_.push_back(print_server);
    480 }
    481 
    482 PrintSystem::PrintSystemResult PrintSystemCUPS::Init() {
    483   UpdatePrinters();
    484   initialized_ = true;
    485   return PrintSystemResult(true, std::string());
    486 }
    487 
    488 void PrintSystemCUPS::UpdatePrinters() {
    489   PrintServerList::iterator it;
    490   printer_enum_succeeded_ = true;
    491   for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
    492     if (!it->backend->EnumeratePrinters(&it->printers))
    493       printer_enum_succeeded_ = false;
    494     it->caps_cache.clear();
    495     printing::PrinterList::iterator printer_it;
    496     for (printer_it = it->printers.begin();
    497         printer_it != it->printers.end(); ++printer_it) {
    498       printer_it->printer_name = MakeFullPrinterName(it->url,
    499                                                      printer_it->printer_name);
    500     }
    501     VLOG(1) << "CP_CUPS: Updated printers list"
    502             << ", server: " << it->url
    503             << ", # of printers: " << it->printers.size();
    504   }
    505 
    506   // Schedule next update.
    507   base::MessageLoop::current()->PostDelayedTask(
    508       FROM_HERE,
    509       base::Bind(&PrintSystemCUPS::UpdatePrinters, this),
    510       GetUpdateTimeout());
    511 }
    512 
    513 PrintSystem::PrintSystemResult PrintSystemCUPS::EnumeratePrinters(
    514     printing::PrinterList* printer_list) {
    515   DCHECK(initialized_);
    516   printer_list->clear();
    517   PrintServerList::iterator it;
    518   for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
    519     printer_list->insert(printer_list->end(),
    520         it->printers.begin(), it->printers.end());
    521   }
    522   VLOG(1) << "CP_CUPS: Total printers enumerated: " << printer_list->size();
    523   // TODO(sanjeevr): Maybe some day we want to report the actual server names
    524   // for which the enumeration failed.
    525   return PrintSystemResult(printer_enum_succeeded_, std::string());
    526 }
    527 
    528 void PrintSystemCUPS::GetPrinterCapsAndDefaults(
    529     const std::string& printer_name,
    530     const PrinterCapsAndDefaultsCallback& callback) {
    531   printing::PrinterCapsAndDefaults printer_info;
    532   bool succeeded = GetPrinterCapsAndDefaults(printer_name, &printer_info);
    533   base::MessageLoop::current()->PostTask(
    534       FROM_HERE,
    535       base::Bind(&PrintSystemCUPS::RunCapsCallback,
    536                  callback,
    537                  succeeded,
    538                  printer_name,
    539                  printer_info));
    540 }
    541 
    542 bool PrintSystemCUPS::IsValidPrinter(const std::string& printer_name) {
    543   return GetPrinterInfo(printer_name, NULL);
    544 }
    545 
    546 bool PrintSystemCUPS::ValidatePrintTicket(
    547     const std::string& printer_name,
    548     const std::string& print_ticket_data,
    549     const std::string& print_ticket_mime_type) {
    550   DCHECK(initialized_);
    551   scoped_ptr<base::Value> ticket_value(
    552       base::JSONReader::Read(print_ticket_data));
    553   return ticket_value != NULL &&
    554          ticket_value->IsType(base::Value::TYPE_DICTIONARY);
    555 }
    556 
    557 // Print ticket on linux is a JSON string containing only one dictionary.
    558 bool PrintSystemCUPS::ParsePrintTicket(
    559     const std::string& print_ticket,
    560     std::map<std::string, std::string>* options) {
    561   DCHECK(options);
    562   scoped_ptr<base::Value> ticket_value(base::JSONReader::Read(print_ticket));
    563   if (ticket_value == NULL ||
    564       !ticket_value->IsType(base::Value::TYPE_DICTIONARY)) {
    565     return false;
    566   }
    567 
    568   options->clear();
    569   base::DictionaryValue* ticket_dict =
    570       static_cast<base::DictionaryValue*>(ticket_value.get());
    571   for (base::DictionaryValue::Iterator it(*ticket_dict); !it.IsAtEnd();
    572        it.Advance()) {
    573     std::string value;
    574     if (it.value().GetAsString(&value))
    575       (*options)[it.key()] = value;
    576   }
    577 
    578   return true;
    579 }
    580 
    581 bool PrintSystemCUPS::GetPrinterCapsAndDefaults(
    582     const std::string& printer_name,
    583     printing::PrinterCapsAndDefaults* printer_info) {
    584   DCHECK(initialized_);
    585   std::string short_printer_name;
    586   PrintServerInfoCUPS* server_info =
    587       FindServerByFullName(printer_name, &short_printer_name);
    588   if (!server_info)
    589     return false;
    590 
    591   PrintServerInfoCUPS::CapsMap::iterator caps_it =
    592       server_info->caps_cache.find(printer_name);
    593   if (caps_it != server_info->caps_cache.end()) {
    594     *printer_info = caps_it->second;
    595     return true;
    596   }
    597 
    598   // TODO(gene): Retry multiple times in case of error.
    599   crash_keys::ScopedPrinterInfo crash_key(
    600       server_info->backend->GetPrinterDriverInfo(short_printer_name));
    601   if (!server_info->backend->GetPrinterCapsAndDefaults(short_printer_name,
    602                                                        printer_info) ) {
    603     return false;
    604   }
    605 
    606   server_info->caps_cache[printer_name] = *printer_info;
    607   return true;
    608 }
    609 
    610 bool PrintSystemCUPS::GetJobDetails(const std::string& printer_name,
    611                                     PlatformJobId job_id,
    612                                     PrintJobDetails *job_details) {
    613   DCHECK(initialized_);
    614   DCHECK(job_details);
    615 
    616   std::string short_printer_name;
    617   PrintServerInfoCUPS* server_info =
    618       FindServerByFullName(printer_name, &short_printer_name);
    619   if (!server_info)
    620     return false;
    621 
    622   crash_keys::ScopedPrinterInfo crash_key(
    623       server_info->backend->GetPrinterDriverInfo(short_printer_name));
    624   cups_job_t* jobs = NULL;
    625   int num_jobs = GetJobs(&jobs, server_info->url, cups_encryption_,
    626                          short_printer_name.c_str(), 1, -1);
    627   bool error = (num_jobs == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE);
    628   if (error) {
    629     VLOG(1) << "CP_CUPS: Error getting jobs from CUPS server"
    630             << ", printer name:" << printer_name
    631             << ", error: " << static_cast<int>(cupsLastError());
    632     return false;
    633   }
    634 
    635   // Check if the request is for dummy dry run job.
    636   // We check this after calling GetJobs API to see if this printer is actually
    637   // accessible through CUPS.
    638   if (job_id == kDryRunJobId) {
    639     job_details->status = PRINT_JOB_STATUS_COMPLETED;
    640     VLOG(1) << "CP_CUPS: Dry run job succeeded"
    641             << ", printer name: " << printer_name;
    642     return true;
    643   }
    644 
    645   bool found = false;
    646   for (int i = 0; i < num_jobs; i++) {
    647     if (jobs[i].id == job_id) {
    648       found = true;
    649       switch (jobs[i].state) {
    650         case IPP_JOB_PENDING :
    651         case IPP_JOB_HELD :
    652         case IPP_JOB_PROCESSING :
    653           job_details->status = PRINT_JOB_STATUS_IN_PROGRESS;
    654           break;
    655         case IPP_JOB_STOPPED :
    656         case IPP_JOB_CANCELLED :
    657         case IPP_JOB_ABORTED :
    658           job_details->status = PRINT_JOB_STATUS_ERROR;
    659           break;
    660         case IPP_JOB_COMPLETED :
    661           job_details->status = PRINT_JOB_STATUS_COMPLETED;
    662           break;
    663         default:
    664           job_details->status = PRINT_JOB_STATUS_INVALID;
    665       }
    666       job_details->platform_status_flags = jobs[i].state;
    667 
    668       // We don't have any details on the number of processed pages here.
    669       break;
    670     }
    671   }
    672 
    673   if (found)
    674     VLOG(1) << "CP_CUPS: Job found"
    675             << ", printer name: " << printer_name
    676             << ", cups job id: " << job_id
    677             << ", cups job status: " << job_details->status;
    678   else
    679     LOG(WARNING) << "CP_CUPS: Job not found"
    680                  << ", printer name: " << printer_name
    681                  << ", cups job id: " << job_id;
    682 
    683   cupsFreeJobs(num_jobs, jobs);
    684   return found;
    685 }
    686 
    687 bool PrintSystemCUPS::GetPrinterInfo(const std::string& printer_name,
    688                                      printing::PrinterBasicInfo* info) {
    689   DCHECK(initialized_);
    690   if (info)
    691     VLOG(1) << "CP_CUPS: Getting printer info"
    692             << ", printer name: " << printer_name;
    693 
    694   std::string short_printer_name;
    695   PrintServerInfoCUPS* server_info =
    696       FindServerByFullName(printer_name, &short_printer_name);
    697   if (!server_info)
    698     return false;
    699 
    700   printing::PrinterList::iterator it;
    701   for (it = server_info->printers.begin();
    702       it != server_info->printers.end(); ++it) {
    703     if (it->printer_name == printer_name) {
    704       if (info)
    705         *info = *it;
    706       return true;
    707     }
    708   }
    709   return false;
    710 }
    711 
    712 PrintSystem::PrintServerWatcher*
    713 PrintSystemCUPS::CreatePrintServerWatcher() {
    714   DCHECK(initialized_);
    715   return new PrintServerWatcherCUPS(this);
    716 }
    717 
    718 PrintSystem::PrinterWatcher* PrintSystemCUPS::CreatePrinterWatcher(
    719     const std::string& printer_name) {
    720   DCHECK(initialized_);
    721   DCHECK(!printer_name.empty());
    722   return new PrinterWatcherCUPS(this, printer_name);
    723 }
    724 
    725 PrintSystem::JobSpooler* PrintSystemCUPS::CreateJobSpooler() {
    726   DCHECK(initialized_);
    727   return new JobSpoolerCUPS(this);
    728 }
    729 
    730 bool PrintSystemCUPS::UseCddAndCjt() {
    731   return false;
    732 }
    733 
    734 std::string PrintSystemCUPS::GetSupportedMimeTypes() {
    735   return supported_mime_types_;
    736 }
    737 
    738 scoped_refptr<PrintSystem> PrintSystem::CreateInstance(
    739     const base::DictionaryValue* print_system_settings) {
    740   return new PrintSystemCUPS(print_system_settings);
    741 }
    742 
    743 int PrintSystemCUPS::PrintFile(const GURL& url, http_encryption_t encryption,
    744                                const char* name, const char* filename,
    745                                const char* title, int num_options,
    746                                cups_option_t* options) {
    747   if (url.is_empty()) {  // Use default (local) print server.
    748     return cupsPrintFile(name, filename, title, num_options, options);
    749   } else {
    750     printing::HttpConnectionCUPS http(url, encryption);
    751     http.SetBlocking(false);
    752     return cupsPrintFile2(http.http(), name, filename,
    753                           title, num_options, options);
    754   }
    755 }
    756 
    757 int PrintSystemCUPS::GetJobs(cups_job_t** jobs, const GURL& url,
    758                              http_encryption_t encryption,
    759                              const char* name, int myjobs, int whichjobs) {
    760   if (url.is_empty()) {  // Use default (local) print server.
    761     return cupsGetJobs(jobs, name, myjobs, whichjobs);
    762   } else {
    763     printing::HttpConnectionCUPS http(url, encryption);
    764     http.SetBlocking(false);
    765     return cupsGetJobs2(http.http(), jobs, name, myjobs, whichjobs);
    766   }
    767 }
    768 
    769 PlatformJobId PrintSystemCUPS::SpoolPrintJob(
    770     const std::string& print_ticket,
    771     const base::FilePath& print_data_file_path,
    772     const std::string& print_data_mime_type,
    773     const std::string& printer_name,
    774     const std::string& job_title,
    775     const std::vector<std::string>& tags,
    776     bool* dry_run) {
    777   DCHECK(initialized_);
    778   VLOG(1) << "CP_CUPS: Spooling print job, printer name: " << printer_name;
    779 
    780   std::string short_printer_name;
    781   PrintServerInfoCUPS* server_info =
    782       FindServerByFullName(printer_name, &short_printer_name);
    783   if (!server_info)
    784     return false;
    785 
    786   crash_keys::ScopedPrinterInfo crash_key(
    787       server_info->backend->GetPrinterDriverInfo(printer_name));
    788 
    789   // We need to store options as char* string for the duration of the
    790   // cupsPrintFile2 call. We'll use map here to store options, since
    791   // Dictionary value from JSON parser returns wchat_t.
    792   std::map<std::string, std::string> options;
    793   bool res = ParsePrintTicket(print_ticket, &options);
    794   DCHECK(res);  // If print ticket is invalid we still print using defaults.
    795 
    796   // Check if this is a dry run (test) job.
    797   *dry_run = IsDryRunJob(tags);
    798   if (*dry_run) {
    799     VLOG(1) << "CP_CUPS: Dry run job spooled";
    800     return kDryRunJobId;
    801   }
    802 
    803   std::vector<cups_option_t> cups_options;
    804   std::map<std::string, std::string>::iterator it;
    805 
    806   for (it = options.begin(); it != options.end(); ++it) {
    807     cups_option_t opt;
    808     opt.name = const_cast<char*>(it->first.c_str());
    809     opt.value = const_cast<char*>(it->second.c_str());
    810     cups_options.push_back(opt);
    811   }
    812 
    813   int job_id = PrintFile(server_info->url,
    814                          cups_encryption_,
    815                          short_printer_name.c_str(),
    816                          print_data_file_path.value().c_str(),
    817                          job_title.c_str(),
    818                          cups_options.size(),
    819                          &(cups_options[0]));
    820 
    821   // TODO(alexyu): Output printer id.
    822   VLOG(1) << "CP_CUPS: Job spooled"
    823           << ", printer name: " << printer_name
    824           << ", cups job id: " << job_id;
    825 
    826   return job_id;
    827 }
    828 
    829 std::string PrintSystemCUPS::MakeFullPrinterName(
    830     const GURL& url, const std::string& short_printer_name) {
    831   std::string full_name;
    832   full_name += "\\\\";
    833   full_name += url.host();
    834   if (!url.port().empty()) {
    835     full_name += ":";
    836     full_name += url.port();
    837   }
    838   full_name += "\\";
    839   full_name += short_printer_name;
    840   return full_name;
    841 }
    842 
    843 PrintServerInfoCUPS* PrintSystemCUPS::FindServerByFullName(
    844     const std::string& full_printer_name, std::string* short_printer_name) {
    845   size_t front = full_printer_name.find("\\\\");
    846   size_t separator = full_printer_name.find("\\", 2);
    847   if (front == std::string::npos || separator == std::string::npos) {
    848     LOG(WARNING) << "CP_CUPS: Invalid UNC"
    849                  << ", printer name: " << full_printer_name;
    850     return NULL;
    851   }
    852   std::string server = full_printer_name.substr(2, separator - 2);
    853 
    854   PrintServerList::iterator it;
    855   for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
    856     std::string cur_server;
    857     cur_server += it->url.host();
    858     if (!it->url.port().empty()) {
    859       cur_server += ":";
    860       cur_server += it->url.port();
    861     }
    862     if (cur_server == server) {
    863       *short_printer_name = full_printer_name.substr(separator + 1);
    864       return &(*it);
    865     }
    866   }
    867 
    868   LOG(WARNING) << "CP_CUPS: Server not found"
    869                << ", printer name: " << full_printer_name;
    870   return NULL;
    871 }
    872 
    873 void PrintSystemCUPS::RunCapsCallback(
    874     const PrinterCapsAndDefaultsCallback& callback,
    875     bool succeeded,
    876     const std::string& printer_name,
    877     const printing::PrinterCapsAndDefaults& printer_info) {
    878   callback.Run(succeeded, printer_name, printer_info);
    879 }
    880 
    881 }  // namespace cloud_print
    882