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