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