OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/service/cloud_print/printer_info.h" | 5 #include "chrome/service/cloud_print/printer_info.h" |
6 | 6 |
| 7 #include <cups/cups.h> |
| 8 #include <list> |
| 9 #include <map> |
| 10 |
| 11 #include "base/json/json_reader.h" |
| 12 #include "base/file_path.h" |
| 13 #include "base/file_util.h" |
7 #include "base/logging.h" | 14 #include "base/logging.h" |
8 | 15 #include "base/lock.h" |
9 // TODO(sanjeevr): Implement the Linux interfaces. | 16 #include "base/message_loop.h" |
| 17 #include "base/rand_util.h" |
| 18 #include "base/string_util.h" |
| 19 #include "base/task.h" |
| 20 #include "base/values.h" |
| 21 #include "base/utf_string_conversions.h" |
| 22 |
10 namespace cloud_print { | 23 namespace cloud_print { |
11 | 24 |
| 25 static const char kCUPSPrinterInfoOpt[] = "printer-info"; |
| 26 static const char kCUPSPrinterStateOpt[] = "printer-state"; |
| 27 |
12 void EnumeratePrinters(PrinterList* printer_list) { | 28 void EnumeratePrinters(PrinterList* printer_list) { |
13 DCHECK(printer_list); | 29 DCHECK(printer_list); |
14 NOTIMPLEMENTED(); | 30 printer_list->clear(); |
| 31 |
| 32 cups_dest_t* destinations = NULL; |
| 33 int num_dests = cupsGetDests(&destinations); |
| 34 |
| 35 for (int i = 0; i < num_dests; i++) { |
| 36 PrinterBasicInfo printer_info; |
| 37 printer_info.printer_name = destinations[i].name; |
| 38 |
| 39 const char* info = cupsGetOption(kCUPSPrinterInfoOpt, |
| 40 destinations[i].num_options, destinations[i].options); |
| 41 if (info != NULL) |
| 42 printer_info.printer_description = info; |
| 43 |
| 44 const char* state = cupsGetOption(kCUPSPrinterStateOpt, |
| 45 destinations[i].num_options, destinations[i].options); |
| 46 if (state != NULL) |
| 47 StringToInt(state, &printer_info.printer_status); |
| 48 |
| 49 printer_list->push_back(printer_info); |
| 50 } |
| 51 |
| 52 cupsFreeDests(num_dests, destinations); |
15 } | 53 } |
16 | 54 |
17 bool GetPrinterCapsAndDefaults(const std::string& printer_name, | 55 bool GetPrinterCapsAndDefaults(const std::string& printer_name, |
18 PrinterCapsAndDefaults* printer_info) { | 56 PrinterCapsAndDefaults* printer_info) { |
19 NOTIMPLEMENTED(); | 57 DCHECK(printer_info); |
20 return false; | 58 |
| 59 static Lock ppd_lock; |
| 60 // cupsGetPPD returns a filename stored in a static buffer in CUPS. |
| 61 // Protect this code with lock. |
| 62 ppd_lock.Acquire(); |
| 63 FilePath ppd_path(cupsGetPPD(printer_name.c_str())); |
| 64 ppd_lock.Release(); |
| 65 |
| 66 std::string content; |
| 67 bool res = file_util::ReadFileToString(ppd_path, &content); |
| 68 |
| 69 file_util::Delete(ppd_path, false); |
| 70 |
| 71 if (res) { |
| 72 printer_info->printer_capabilities.swap(content); |
| 73 printer_info->caps_mime_type = "application/pagemaker"; |
| 74 // In CUPS, printer defaults is a part of PPD file. Nothing to upload here. |
| 75 printer_info->printer_defaults.clear(); |
| 76 printer_info->defaults_mime_type.clear(); |
| 77 } |
| 78 |
| 79 return res; |
21 } | 80 } |
22 | 81 |
23 bool ValidatePrintTicket(const std::string& printer_name, | 82 bool ValidatePrintTicket(const std::string& printer_name, |
24 const std::string& print_ticket_data) { | 83 const std::string& print_ticket_data) { |
25 NOTIMPLEMENTED(); | 84 scoped_ptr<Value> ticket_value(base::JSONReader::Read(print_ticket_data, |
26 return false; | 85 false)); |
| 86 return ticket_value != NULL && ticket_value->IsType(Value::TYPE_DICTIONARY); |
27 } | 87 } |
28 | 88 |
29 std::string GenerateProxyId() { | 89 std::string GenerateProxyId() { |
30 NOTIMPLEMENTED(); | 90 // TODO(gene): This code should generate a unique id for proxy. ID should be |
31 return std::string(); | 91 // unique for this user. Rand may return the same number. We'll need to change |
| 92 // this in the future. |
| 93 std::string id("CP_PROXY_"); |
| 94 id += Uint64ToString(base::RandUint64()); |
| 95 return id; |
| 96 } |
| 97 |
| 98 // Print ticket on linux is a JSON string containing only one dictionary. |
| 99 bool ParsePrintTicket(const std::string& print_ticket, |
| 100 std::map<std::string, std::string>* options) { |
| 101 DCHECK(options); |
| 102 scoped_ptr<Value> ticket_value(base::JSONReader::Read(print_ticket, false)); |
| 103 if (ticket_value == NULL || !ticket_value->IsType(Value::TYPE_DICTIONARY)) |
| 104 return false; |
| 105 |
| 106 options->clear(); |
| 107 DictionaryValue* ticket_dict = |
| 108 static_cast<DictionaryValue*>(ticket_value.get()); |
| 109 DictionaryValue::key_iterator it(ticket_dict->begin_keys()); |
| 110 for (; it != ticket_dict->end_keys(); ++it) { |
| 111 std::wstring key = *it; |
| 112 std::string value; |
| 113 if (ticket_dict->GetString(key, &value)) { |
| 114 (*options)[WideToUTF8(key.c_str())] = value; |
| 115 } |
| 116 } |
| 117 |
| 118 return true; |
32 } | 119 } |
33 | 120 |
34 bool SpoolPrintJob(const std::string& print_ticket, | 121 bool SpoolPrintJob(const std::string& print_ticket, |
35 const FilePath& print_data_file_path, | 122 const FilePath& print_data_file_path, |
36 const std::string& print_data_mime_type, | 123 const std::string& print_data_mime_type, |
37 const std::string& printer_name, | 124 const std::string& printer_name, |
38 const std::string& job_title, | 125 const std::string& job_title, |
39 PlatformJobId* job_id_ret) { | 126 PlatformJobId* job_id_ret) { |
40 NOTIMPLEMENTED(); | 127 DCHECK(job_id_ret); |
41 return false; | 128 |
| 129 // We need to store options as char* string for the duration of the |
| 130 // cupsPrintFile call. We'll use map here to store options, since |
| 131 // Dictionary value from JSON parser returns wchat_t. |
| 132 std::map<std::string, std::string> options; |
| 133 bool res = ParsePrintTicket(print_ticket, &options); |
| 134 DCHECK(res); // If print ticket is invalid we still print using defaults. |
| 135 |
| 136 std::vector<cups_option_t> cups_options; |
| 137 std::map<std::string, std::string>::iterator it; |
| 138 for (it = options.begin(); it != options.end(); ++it) { |
| 139 cups_option_t opt; |
| 140 opt.name = const_cast<char*>(it->first.c_str()); |
| 141 opt.value = const_cast<char*>(it->second.c_str()); |
| 142 cups_options.push_back(opt); |
| 143 } |
| 144 |
| 145 int job_id = cupsPrintFile(printer_name.c_str(), |
| 146 print_data_file_path.value().c_str(), |
| 147 job_title.c_str(), |
| 148 cups_options.size(), |
| 149 &(cups_options[0])); |
| 150 |
| 151 if (job_id == 0) |
| 152 return false; |
| 153 |
| 154 *job_id_ret = job_id; |
| 155 return true; |
42 } | 156 } |
43 | 157 |
44 bool GetJobDetails(const std::string& printer_name, | 158 bool GetJobDetails(const std::string& printer_name, |
45 PlatformJobId job_id, | 159 PlatformJobId job_id, |
46 PrintJobDetails *job_details) { | 160 PrintJobDetails *job_details) { |
47 NOTIMPLEMENTED(); | 161 DCHECK(job_details); |
| 162 cups_job_t* jobs = NULL; |
| 163 int num_jobs = cupsGetJobs(&jobs, printer_name.c_str(), 1, -1); |
| 164 |
| 165 bool found = false; |
| 166 for (int i = 0; i < num_jobs; i++) { |
| 167 if (jobs[i].id == job_id) { |
| 168 found = true; |
| 169 switch (jobs[i].state) { |
| 170 case IPP_JOB_PENDING : |
| 171 case IPP_JOB_HELD : |
| 172 case IPP_JOB_PROCESSING : |
| 173 job_details->status = PRINT_JOB_STATUS_IN_PROGRESS; |
| 174 break; |
| 175 case IPP_JOB_STOPPED : |
| 176 case IPP_JOB_CANCELLED : |
| 177 case IPP_JOB_ABORTED : |
| 178 job_details->status = PRINT_JOB_STATUS_ERROR; |
| 179 break; |
| 180 case IPP_JOB_COMPLETED : |
| 181 job_details->status = PRINT_JOB_STATUS_COMPLETED; |
| 182 break; |
| 183 default: |
| 184 job_details->status = PRINT_JOB_STATUS_INVALID; |
| 185 } |
| 186 job_details->platform_status_flags = jobs[i].state; |
| 187 |
| 188 // We don't have any details on the number of processed pages here. |
| 189 break; |
| 190 } |
| 191 } |
| 192 |
| 193 cupsFreeJobs(num_jobs, jobs); |
| 194 return found; |
| 195 } |
| 196 |
| 197 bool GetPrinterInfo(const std::string& printer_name, PrinterBasicInfo* info) { |
| 198 DCHECK(info); |
| 199 |
| 200 // This is not very efficient way to get specific printer info. CUPS 1.4 |
| 201 // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available |
| 202 // everywhere (for example, it supported from Mac OS 10.6 only). |
| 203 PrinterList printer_list; |
| 204 EnumeratePrinters(&printer_list); |
| 205 |
| 206 PrinterList::iterator it; |
| 207 for (it = printer_list.begin(); it != printer_list.end(); ++it) { |
| 208 if (it->printer_name == printer_name) { |
| 209 *info = *it; |
| 210 return true; |
| 211 } |
| 212 } |
48 return false; | 213 return false; |
49 } | 214 } |
50 | 215 |
51 bool IsValidPrinter(const std::string& printer_name) { | 216 bool IsValidPrinter(const std::string& printer_name) { |
52 NOTIMPLEMENTED(); | 217 PrinterBasicInfo info; |
53 return false; | 218 return GetPrinterInfo(printer_name, &info); |
54 } | 219 } |
| 220 |
| 221 class PrinterChangeNotifier::NotificationState |
| 222 : public base::RefCountedThreadSafe< |
| 223 PrinterChangeNotifier::NotificationState> { |
| 224 public: |
| 225 NotificationState() : delegate_(NULL) {} |
| 226 bool Start(const std::string& printer_name, |
| 227 PrinterChangeNotifier::Delegate* delegate) { |
| 228 if (delegate_ != NULL) |
| 229 Stop(); |
| 230 |
| 231 printer_name_ = printer_name; |
| 232 delegate_ = delegate; |
| 233 |
| 234 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| 235 NewRunnableMethod(this, |
| 236 &PrinterChangeNotifier::NotificationState::Update), 5000); |
| 237 return true; |
| 238 } |
| 239 bool Stop() { |
| 240 delegate_ = NULL; |
| 241 return true; |
| 242 } |
| 243 void Update() { |
| 244 if (delegate_ == NULL) |
| 245 return; // Orphan call. We have been stopped already. |
| 246 // For CUPS proxy, we are going to fire OnJobChanged notification |
| 247 // periodically. Higher level will check if there are any outstanding |
| 248 // jobs for this printer and check their status. If printer has no |
| 249 // outstanding jobs, OnJobChanged() will do nothing. |
| 250 delegate_->OnJobChanged(); |
| 251 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| 252 NewRunnableMethod(this, |
| 253 &PrinterChangeNotifier::NotificationState::Update), |
| 254 kNotificationTimeout); |
| 255 } |
| 256 std::string printer_name() const { |
| 257 return printer_name_; |
| 258 } |
| 259 private: |
| 260 friend class base::RefCountedThreadSafe< |
| 261 PrinterChangeNotifier::NotificationState>; |
| 262 ~NotificationState() { |
| 263 Stop(); |
| 264 } |
| 265 static const int kNotificationTimeout = 5000; // in ms |
| 266 std::string printer_name_; // The printer being watched |
| 267 PrinterChangeNotifier::Delegate* delegate_; // Delegate to notify |
| 268 }; |
55 | 269 |
56 PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) { | 270 PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) { |
57 } | 271 } |
58 | 272 |
59 PrinterChangeNotifier::~PrinterChangeNotifier() { | 273 PrinterChangeNotifier::~PrinterChangeNotifier() { |
60 StopWatching(); | 274 StopWatching(); |
61 } | 275 } |
62 | 276 |
63 bool PrinterChangeNotifier::StartWatching(const std::string& printer_name, | 277 bool PrinterChangeNotifier::StartWatching(const std::string& printer_name, |
64 Delegate* delegate) { | 278 Delegate* delegate) { |
65 NOTIMPLEMENTED(); | 279 if (state_) { |
66 return false; | 280 NOTREACHED(); |
| 281 return false; |
| 282 } |
| 283 state_ = new NotificationState; |
| 284 state_->AddRef(); |
| 285 if (!state_->Start(printer_name, delegate)) { |
| 286 StopWatching(); |
| 287 return false; |
| 288 } |
| 289 return true; |
67 } | 290 } |
68 | 291 |
69 bool PrinterChangeNotifier::StopWatching() { | 292 bool PrinterChangeNotifier::StopWatching() { |
70 NOTIMPLEMENTED(); | 293 if (!state_) { |
71 return false; | 294 return false; |
| 295 } |
| 296 state_->Stop(); |
| 297 state_->Release(); |
| 298 state_ = NULL; |
| 299 return true; |
72 } | 300 } |
73 | 301 |
74 bool PrinterChangeNotifier::GetCurrentPrinterInfo( | 302 bool PrinterChangeNotifier::GetCurrentPrinterInfo( |
75 PrinterBasicInfo* printer_info) { | 303 PrinterBasicInfo* printer_info) { |
76 NOTIMPLEMENTED(); | 304 if (!state_) { |
77 return false; | 305 return false; |
| 306 } |
| 307 DCHECK(printer_info); |
| 308 return GetPrinterInfo(state_->printer_name(), printer_info); |
78 } | 309 } |
79 } // namespace cloud_print | 310 } // namespace cloud_print |
80 | 311 |
OLD | NEW |