| Index: chrome/service/cloud_print/printer_info_cups.cc
|
| ===================================================================
|
| --- chrome/service/cloud_print/printer_info_cups.cc (revision 47158)
|
| +++ chrome/service/cloud_print/printer_info_cups.cc (working copy)
|
| @@ -4,55 +4,269 @@
|
|
|
| #include "chrome/service/cloud_print/printer_info.h"
|
|
|
| +#include <cups/cups.h>
|
| +#include <list>
|
| +#include <map>
|
| +
|
| +#include "base/json/json_reader.h"
|
| +#include "base/file_path.h"
|
| +#include "base/file_util.h"
|
| #include "base/logging.h"
|
| +#include "base/lock.h"
|
| +#include "base/message_loop.h"
|
| +#include "base/rand_util.h"
|
| +#include "base/string_util.h"
|
| +#include "base/task.h"
|
| +#include "base/values.h"
|
| +#include "base/utf_string_conversions.h"
|
|
|
| -// TODO(sanjeevr): Implement the Linux interfaces.
|
| namespace cloud_print {
|
|
|
| +static const char kCUPSPrinterInfoOpt[] = "printer-info";
|
| +static const char kCUPSPrinterStateOpt[] = "printer-state";
|
| +
|
| void EnumeratePrinters(PrinterList* printer_list) {
|
| DCHECK(printer_list);
|
| - NOTIMPLEMENTED();
|
| + printer_list->clear();
|
| +
|
| + cups_dest_t* destinations = NULL;
|
| + int num_dests = cupsGetDests(&destinations);
|
| +
|
| + for (int i = 0; i < num_dests; i++) {
|
| + PrinterBasicInfo printer_info;
|
| + printer_info.printer_name = destinations[i].name;
|
| +
|
| + const char* info = cupsGetOption(kCUPSPrinterInfoOpt,
|
| + destinations[i].num_options, destinations[i].options);
|
| + if (info != NULL)
|
| + printer_info.printer_description = info;
|
| +
|
| + const char* state = cupsGetOption(kCUPSPrinterStateOpt,
|
| + destinations[i].num_options, destinations[i].options);
|
| + if (state != NULL)
|
| + StringToInt(state, &printer_info.printer_status);
|
| +
|
| + printer_list->push_back(printer_info);
|
| + }
|
| +
|
| + cupsFreeDests(num_dests, destinations);
|
| }
|
|
|
| bool GetPrinterCapsAndDefaults(const std::string& printer_name,
|
| PrinterCapsAndDefaults* printer_info) {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + DCHECK(printer_info);
|
| +
|
| + static Lock ppd_lock;
|
| + // cupsGetPPD returns a filename stored in a static buffer in CUPS.
|
| + // Protect this code with lock.
|
| + ppd_lock.Acquire();
|
| + FilePath ppd_path(cupsGetPPD(printer_name.c_str()));
|
| + ppd_lock.Release();
|
| +
|
| + std::string content;
|
| + bool res = file_util::ReadFileToString(ppd_path, &content);
|
| +
|
| + file_util::Delete(ppd_path, false);
|
| +
|
| + if (res) {
|
| + printer_info->printer_capabilities.swap(content);
|
| + printer_info->caps_mime_type = "application/pagemaker";
|
| + // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
|
| + printer_info->printer_defaults.clear();
|
| + printer_info->defaults_mime_type.clear();
|
| + }
|
| +
|
| + return res;
|
| }
|
|
|
| bool ValidatePrintTicket(const std::string& printer_name,
|
| const std::string& print_ticket_data) {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + scoped_ptr<Value> ticket_value(base::JSONReader::Read(print_ticket_data,
|
| + false));
|
| + return ticket_value != NULL && ticket_value->IsType(Value::TYPE_DICTIONARY);
|
| }
|
|
|
| std::string GenerateProxyId() {
|
| - NOTIMPLEMENTED();
|
| - return std::string();
|
| + // TODO(gene): This code should generate a unique id for proxy. ID should be
|
| + // unique for this user. Rand may return the same number. We'll need to change
|
| + // this in the future.
|
| + std::string id("CP_PROXY_");
|
| + id += Uint64ToString(base::RandUint64());
|
| + return id;
|
| }
|
|
|
| +// Print ticket on linux is a JSON string containing only one dictionary.
|
| +bool ParsePrintTicket(const std::string& print_ticket,
|
| + std::map<std::string, std::string>* options) {
|
| + DCHECK(options);
|
| + scoped_ptr<Value> ticket_value(base::JSONReader::Read(print_ticket, false));
|
| + if (ticket_value == NULL || !ticket_value->IsType(Value::TYPE_DICTIONARY))
|
| + return false;
|
| +
|
| + options->clear();
|
| + DictionaryValue* ticket_dict =
|
| + static_cast<DictionaryValue*>(ticket_value.get());
|
| + DictionaryValue::key_iterator it(ticket_dict->begin_keys());
|
| + for (; it != ticket_dict->end_keys(); ++it) {
|
| + std::wstring key = *it;
|
| + std::string value;
|
| + if (ticket_dict->GetString(key, &value)) {
|
| + (*options)[WideToUTF8(key.c_str())] = value;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| bool SpoolPrintJob(const std::string& print_ticket,
|
| const FilePath& print_data_file_path,
|
| const std::string& print_data_mime_type,
|
| const std::string& printer_name,
|
| const std::string& job_title,
|
| PlatformJobId* job_id_ret) {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + DCHECK(job_id_ret);
|
| +
|
| + // We need to store options as char* string for the duration of the
|
| + // cupsPrintFile call. We'll use map here to store options, since
|
| + // Dictionary value from JSON parser returns wchat_t.
|
| + std::map<std::string, std::string> options;
|
| + bool res = ParsePrintTicket(print_ticket, &options);
|
| + DCHECK(res); // If print ticket is invalid we still print using defaults.
|
| +
|
| + std::vector<cups_option_t> cups_options;
|
| + std::map<std::string, std::string>::iterator it;
|
| + for (it = options.begin(); it != options.end(); ++it) {
|
| + cups_option_t opt;
|
| + opt.name = const_cast<char*>(it->first.c_str());
|
| + opt.value = const_cast<char*>(it->second.c_str());
|
| + cups_options.push_back(opt);
|
| + }
|
| +
|
| + int job_id = cupsPrintFile(printer_name.c_str(),
|
| + print_data_file_path.value().c_str(),
|
| + job_title.c_str(),
|
| + cups_options.size(),
|
| + &(cups_options[0]));
|
| +
|
| + if (job_id == 0)
|
| + return false;
|
| +
|
| + *job_id_ret = job_id;
|
| + return true;
|
| }
|
|
|
| bool GetJobDetails(const std::string& printer_name,
|
| PlatformJobId job_id,
|
| PrintJobDetails *job_details) {
|
| - NOTIMPLEMENTED();
|
| + DCHECK(job_details);
|
| + cups_job_t* jobs = NULL;
|
| + int num_jobs = cupsGetJobs(&jobs, printer_name.c_str(), 1, -1);
|
| +
|
| + bool found = false;
|
| + for (int i = 0; i < num_jobs; i++) {
|
| + if (jobs[i].id == job_id) {
|
| + found = true;
|
| + switch (jobs[i].state) {
|
| + case IPP_JOB_PENDING :
|
| + case IPP_JOB_HELD :
|
| + case IPP_JOB_PROCESSING :
|
| + job_details->status = PRINT_JOB_STATUS_IN_PROGRESS;
|
| + break;
|
| + case IPP_JOB_STOPPED :
|
| + case IPP_JOB_CANCELLED :
|
| + case IPP_JOB_ABORTED :
|
| + job_details->status = PRINT_JOB_STATUS_ERROR;
|
| + break;
|
| + case IPP_JOB_COMPLETED :
|
| + job_details->status = PRINT_JOB_STATUS_COMPLETED;
|
| + break;
|
| + default:
|
| + job_details->status = PRINT_JOB_STATUS_INVALID;
|
| + }
|
| + job_details->platform_status_flags = jobs[i].state;
|
| +
|
| + // We don't have any details on the number of processed pages here.
|
| + break;
|
| + }
|
| + }
|
| +
|
| + cupsFreeJobs(num_jobs, jobs);
|
| + return found;
|
| +}
|
| +
|
| +bool GetPrinterInfo(const std::string& printer_name, PrinterBasicInfo* info) {
|
| + DCHECK(info);
|
| +
|
| + // This is not very efficient way to get specific printer info. CUPS 1.4
|
| + // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
|
| + // everywhere (for example, it supported from Mac OS 10.6 only).
|
| + PrinterList printer_list;
|
| + EnumeratePrinters(&printer_list);
|
| +
|
| + PrinterList::iterator it;
|
| + for (it = printer_list.begin(); it != printer_list.end(); ++it) {
|
| + if (it->printer_name == printer_name) {
|
| + *info = *it;
|
| + return true;
|
| + }
|
| + }
|
| return false;
|
| }
|
|
|
| bool IsValidPrinter(const std::string& printer_name) {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + PrinterBasicInfo info;
|
| + return GetPrinterInfo(printer_name, &info);
|
| }
|
|
|
| +class PrinterChangeNotifier::NotificationState
|
| + : public base::RefCountedThreadSafe<
|
| + PrinterChangeNotifier::NotificationState> {
|
| + public:
|
| + NotificationState() : delegate_(NULL) {}
|
| + bool Start(const std::string& printer_name,
|
| + PrinterChangeNotifier::Delegate* delegate) {
|
| + if (delegate_ != NULL)
|
| + Stop();
|
| +
|
| + printer_name_ = printer_name;
|
| + delegate_ = delegate;
|
| +
|
| + MessageLoop::current()->PostDelayedTask(FROM_HERE,
|
| + NewRunnableMethod(this,
|
| + &PrinterChangeNotifier::NotificationState::Update), 5000);
|
| + return true;
|
| + }
|
| + bool Stop() {
|
| + delegate_ = NULL;
|
| + return true;
|
| + }
|
| + void Update() {
|
| + if (delegate_ == NULL)
|
| + return; // Orphan call. We have been stopped already.
|
| + // For CUPS proxy, we are going to fire OnJobChanged notification
|
| + // periodically. Higher level will check if there are any outstanding
|
| + // jobs for this printer and check their status. If printer has no
|
| + // outstanding jobs, OnJobChanged() will do nothing.
|
| + delegate_->OnJobChanged();
|
| + MessageLoop::current()->PostDelayedTask(FROM_HERE,
|
| + NewRunnableMethod(this,
|
| + &PrinterChangeNotifier::NotificationState::Update),
|
| + kNotificationTimeout);
|
| + }
|
| + std::string printer_name() const {
|
| + return printer_name_;
|
| + }
|
| + private:
|
| + friend class base::RefCountedThreadSafe<
|
| + PrinterChangeNotifier::NotificationState>;
|
| + ~NotificationState() {
|
| + Stop();
|
| + }
|
| + static const int kNotificationTimeout = 5000; // in ms
|
| + std::string printer_name_; // The printer being watched
|
| + PrinterChangeNotifier::Delegate* delegate_; // Delegate to notify
|
| +};
|
| +
|
| PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) {
|
| }
|
|
|
| @@ -62,19 +276,36 @@
|
|
|
| bool PrinterChangeNotifier::StartWatching(const std::string& printer_name,
|
| Delegate* delegate) {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + if (state_) {
|
| + NOTREACHED();
|
| + return false;
|
| + }
|
| + state_ = new NotificationState;
|
| + state_->AddRef();
|
| + if (!state_->Start(printer_name, delegate)) {
|
| + StopWatching();
|
| + return false;
|
| + }
|
| + return true;
|
| }
|
|
|
| bool PrinterChangeNotifier::StopWatching() {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + if (!state_) {
|
| + return false;
|
| + }
|
| + state_->Stop();
|
| + state_->Release();
|
| + state_ = NULL;
|
| + return true;
|
| }
|
|
|
| bool PrinterChangeNotifier::GetCurrentPrinterInfo(
|
| PrinterBasicInfo* printer_info) {
|
| - NOTIMPLEMENTED();
|
| - return false;
|
| + if (!state_) {
|
| + return false;
|
| + }
|
| + DCHECK(printer_info);
|
| + return GetPrinterInfo(state_->printer_name(), printer_info);
|
| }
|
| } // namespace cloud_print
|
|
|
|
|