OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 "printing/backend/cups_jobs.h" | |
6 | |
7 #include <cups/ipp.h> | |
8 | |
9 #include <map> | |
10 | |
11 #include "base/logging.h" | |
12 #include "base/strings/string_piece.h" | |
13 #include "base/strings/stringprintf.h" | |
14 | |
15 namespace printing { | |
16 namespace { | |
17 | |
18 // printer attributes | |
19 const char kPrinterUri[] = "printer-uri"; | |
20 const char kPrinterState[] = "printer-state"; | |
21 const char kPrinterStateReasons[] = "printer-state-reasons"; | |
22 const char kPrinterStateMessage[] = "printer-state-message"; | |
23 | |
24 // job attributes | |
25 const char kJobUri[] = "job-uri"; | |
26 const char kJobId[] = "job-id"; | |
27 const char kJobState[] = "job-state"; | |
28 const char kJobStateReasons[] = "job-state-reasons"; | |
29 const char kJobStateMessage[] = "job-state-message"; | |
30 const char kJobImpressionsCompleted[] = "job-impressions-completed"; | |
31 const char kTimeAtProcessing[] = "time-at-processing"; | |
32 | |
33 // request parameters | |
34 const char kRequestedAttributes[] = "requested-attributes"; | |
35 const char kWhichJobs[] = "which-jobs"; | |
36 const char kLimit[] = "limit"; | |
37 | |
38 // request values | |
39 const char kCompleted[] = "completed"; | |
40 const char kNotCompleted[] = "not-completed"; | |
41 | |
42 // printer state severities | |
43 const char kSeverityReport[] = "report"; | |
44 const char kSeverityWarn[] = "warning"; | |
45 const char kSeverityError[] = "error"; | |
46 | |
47 // printer state reason values | |
48 const char kNone[] = "none"; | |
49 const char kMediaNeeded[] = "media-needed"; | |
50 const char kMediaJam[] = "media-jam"; | |
51 const char kMovingToPaused[] = "moving-to-paused"; | |
52 const char kPaused[] = "paused"; | |
53 const char kShutdown[] = "shutdown"; | |
54 const char kConnectingToDevice[] = "connecting-to-device"; | |
55 const char kTimedOut[] = "timed-out"; | |
56 const char kStopping[] = "stopping"; | |
57 const char kStoppedPartly[] = "stopped-partly"; | |
58 const char kTonerLow[] = "toner-low"; | |
59 const char kTonerEmpty[] = "toner-empty"; | |
60 const char kSpoolAreaFull[] = "spool-area-full"; | |
61 const char kCoverOpen[] = "cover-open"; | |
62 const char kInterlockOpen[] = "interlock-open"; | |
63 const char kDoorOpen[] = "door-open"; | |
64 const char kInputTrayMissing[] = "input-tray-missing"; | |
65 const char kMediaLow[] = "media-low"; | |
66 const char kMediaEmpty[] = "media-empty"; | |
67 const char kOutputTrayMissing[] = "output-tray-missing"; | |
68 const char kOutputAreaAlmostFull[] = "output-area-almost-full"; | |
69 const char kOutputAreaFull[] = "output-area-full"; | |
70 const char kMarkerSupplyLow[] = "marker-supply-low"; | |
71 const char kMarkerSupplyEmpty[] = "marker-supply-empty"; | |
72 const char kMarkerWasteAlmostFull[] = "marker-waste-almost-full"; | |
73 const char kMarkerWasteFull[] = "marker-waste-full"; | |
74 const char kFuserOverTemp[] = "fuser-over-temp"; | |
75 const char kFuserUnderTemp[] = "fuser-under-temp"; | |
76 const char kOpcNearEol[] = "opc-near-eol"; | |
77 const char kOpcLifeOver[] = "opc-life-over"; | |
78 const char kDeveloperLow[] = "developer-low"; | |
79 const char kDeveloperEmpty[] = "developer-empty"; | |
80 const char kInterpreterResourceUnavailable[] = | |
81 "interpreter-resource-unavailable"; | |
82 | |
83 struct IppDeleter { | |
Carlson
2017/02/24 00:02:08
I don't think you need to define a struct for this
skau
2017/02/28 00:59:59
I think the Deleter must be a type. How do you do
Carlson
2017/02/28 01:29:47
You can use a function pointer as the second type,
skau
2017/02/28 19:01:16
Right. Function pointers. This would be awful wi
| |
84 void operator()(ipp_t* ipp) const { ippDelete(ipp); } | |
85 }; | |
86 | |
87 std::unique_ptr<ipp_t, IppDeleter> WrapIpp(ipp_t* ipp) { | |
88 return std::unique_ptr<ipp_t, IppDeleter>(ipp, IppDeleter()); | |
89 } | |
90 | |
91 CupsJob::JobState ToJobState(ipp_attribute_t* attr) { | |
92 DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr)); | |
93 int state = ippGetInteger(attr, 0); | |
94 CupsJob::JobState converted_state = CupsJob::UNKNOWN; | |
95 switch (state) { | |
96 case IPP_JOB_ABORTED: | |
97 converted_state = CupsJob::ABORTED; | |
98 break; | |
99 case IPP_JOB_CANCELLED: | |
100 converted_state = CupsJob::CANCELED; | |
101 break; | |
102 case IPP_JOB_COMPLETED: | |
103 converted_state = CupsJob::COMPLETED; | |
104 break; | |
105 case IPP_JOB_HELD: | |
106 converted_state = CupsJob::HELD; | |
107 break; | |
108 case IPP_JOB_PENDING: | |
109 converted_state = CupsJob::PENDING; | |
110 break; | |
111 case IPP_JOB_PROCESSING: | |
112 converted_state = CupsJob::PROCESSING; | |
113 break; | |
114 case IPP_JOB_STOPPED: | |
115 converted_state = CupsJob::STOPPED; | |
116 break; | |
117 default: | |
118 NOTREACHED() << "Unidentifed state " << state; | |
119 break; | |
120 } | |
121 | |
122 return converted_state; | |
123 } | |
124 | |
125 using PR = PrinterStatus::PrinterReason::Reason; | |
126 using PSeverity = PrinterStatus::PrinterReason::Severity; | |
127 | |
128 const std::map<base::StringPiece, PR>& GetLabelToReason() { | |
129 static const std::map<base::StringPiece, PR>* kLabelToReason = | |
Carlson
2017/02/24 00:02:08
I'm pretty sure this doesn't need to be a pointer.
skau
2017/02/28 00:59:59
I think it needs to be a pointer to avoid being al
Carlson
2017/02/28 01:29:47
I think you have an incomplete reading here. The
skau
2017/02/28 19:01:16
Yep. I read the wrong heading. Also, I had to Go
| |
130 new std::map<base::StringPiece, PR>{ | |
131 {kNone, PR::NONE}, | |
132 {kMediaNeeded, PR::MEDIA_NEEDED}, | |
133 {kMediaJam, PR::MEDIA_JAM}, | |
134 {kMovingToPaused, PR::MOVING_TO_PAUSED}, | |
135 {kPaused, PR::PAUSED}, | |
136 {kShutdown, PR::SHUTDOWN}, | |
137 {kConnectingToDevice, PR::CONNECTING_TO_DEVICE}, | |
138 {kTimedOut, PR::TIMED_OUT}, | |
139 {kStopping, PR::STOPPING}, | |
140 {kStoppedPartly, PR::STOPPED_PARTLY}, | |
141 {kTonerLow, PR::TONER_LOW}, | |
142 {kTonerEmpty, PR::TONER_EMPTY}, | |
143 {kSpoolAreaFull, PR::SPOOL_AREA_FULL}, | |
144 {kCoverOpen, PR::COVER_OPEN}, | |
145 {kInterlockOpen, PR::INTERLOCK_OPEN}, | |
146 {kDoorOpen, PR::DOOR_OPEN}, | |
147 {kInputTrayMissing, PR::INPUT_TRAY_MISSING}, | |
148 {kMediaLow, PR::MEDIA_LOW}, | |
149 {kMediaEmpty, PR::MEDIA_EMPTY}, | |
150 {kOutputTrayMissing, PR::OUTPUT_TRAY_MISSING}, | |
151 {kOutputAreaAlmostFull, PR::OUTPUT_AREA_ALMOST_FULL}, | |
152 {kOutputAreaFull, PR::OUTPUT_AREA_FULL}, | |
153 {kMarkerSupplyLow, PR::MARKER_SUPPLY_LOW}, | |
154 {kMarkerSupplyEmpty, PR::MARKER_SUPPLY_EMPTY}, | |
155 {kMarkerWasteAlmostFull, PR::MARKER_WASTE_ALMOST_FULL}, | |
156 {kMarkerWasteFull, PR::MARKER_WASTE_FULL}, | |
157 {kFuserOverTemp, PR::FUSER_OVER_TEMP}, | |
158 {kFuserUnderTemp, PR::FUSER_UNDER_TEMP}, | |
159 {kOpcNearEol, PR::OPC_NEAR_EOL}, | |
160 {kOpcLifeOver, PR::OPC_LIFE_OVER}, | |
161 {kDeveloperLow, PR::DEVELOPER_LOW}, | |
162 {kDeveloperEmpty, PR::DEVELOPER_EMPTY}, | |
163 {kInterpreterResourceUnavailable, | |
164 PR::INTERPRETER_RESOURCE_UNAVAILABLE}, | |
165 }; | |
166 return *kLabelToReason; | |
167 } | |
168 | |
169 PrinterStatus::PrinterReason::Reason ToReason(base::StringPiece reason) { | |
Carlson
2017/02/24 00:02:09
Please add function comments for functions not dec
skau
2017/02/28 00:59:59
Done.
| |
170 const auto& enum_map = GetLabelToReason(); | |
171 const auto& entry = enum_map.find(reason); | |
172 if (entry == enum_map.end()) { | |
173 return PR::UNKNOWN_REASON; | |
174 } | |
175 | |
176 return entry->second; | |
177 } | |
178 | |
179 PSeverity ToSeverity(base::StringPiece severity) { | |
180 if (severity == kSeverityError) | |
181 return PSeverity::ERROR; | |
182 | |
183 if (severity == kSeverityWarn) | |
184 return PSeverity::WARNING; | |
185 | |
186 if (severity == kSeverityReport) | |
187 return PSeverity::REPORT; | |
188 | |
189 return PSeverity::UNKNOWN_SEVERITY; | |
190 } | |
191 | |
192 PrinterStatus::PrinterReason ToPrinterReason(base::StringPiece reason) { | |
193 PrinterStatus::PrinterReason parsed; | |
194 | |
195 if (reason == kNone) { | |
196 parsed.reason = PrinterStatus::PrinterReason::NONE; | |
197 parsed.severity = PrinterStatus::PrinterReason::UNKNOWN_SEVERITY; | |
198 return parsed; | |
199 } | |
200 | |
201 size_t last_dash = reason.rfind('-'); | |
202 auto severity = PSeverity::UNKNOWN_SEVERITY; | |
203 if (last_dash != base::StringPiece::npos) { | |
204 // try to parse the last part of the string as the severity. | |
205 severity = ToSeverity(reason.substr(last_dash + 1)); | |
206 } | |
207 | |
208 if (severity == PSeverity::UNKNOWN_SEVERITY) { | |
209 // Severity is unknown. No severity in the reason. | |
210 // Per spec, if there is no severity, severity is error. | |
211 parsed.severity = PrinterStatus::PrinterReason::ERROR; | |
212 parsed.reason = ToReason(reason); | |
213 } else { | |
214 parsed.severity = severity; | |
215 // reason is the beginning of the string | |
216 parsed.reason = ToReason(reason.substr(0, last_dash)); | |
217 } | |
218 | |
219 return parsed; | |
220 } | |
221 | |
222 void ParseCollection(ipp_attribute_t* attr, | |
223 std::vector<std::string>* collection) { | |
224 int count = ippGetCount(attr); | |
225 for (int i = 0; i < count; i++) { | |
226 base::StringPiece value = ippGetString(attr, i, nullptr); | |
227 collection->push_back(value.as_string()); | |
228 } | |
229 } | |
230 | |
231 void ParseField(ipp_attribute_t* attr, base::StringPiece name, CupsJob* job) { | |
232 DCHECK(!name.empty()); | |
233 if (name == kJobId) { | |
234 job->id = ippGetInteger(attr, 0); | |
235 } else if (name == kJobImpressionsCompleted) { | |
236 job->current_pages = ippGetInteger(attr, 0); | |
237 } else if (name == kJobState) { | |
238 job->state = ToJobState(attr); | |
239 } else if (name == kJobStateReasons) { | |
240 ParseCollection(attr, &(job->state_reasons)); | |
241 } else if (name == kJobStateMessage) { | |
242 job->state_message = ippGetString(attr, 0, nullptr); | |
243 } else if (name == kTimeAtProcessing) { | |
244 job->processing_started = ippGetInteger(attr, 0); | |
245 } | |
246 } | |
247 | |
248 // forward decl | |
Carlson
2017/02/24 00:02:08
Comment not useful, more useful would be why forwa
skau
2017/02/28 00:59:58
Done.
| |
249 void ParseFields(ipp_t* response, | |
250 const std::string& printer_id, | |
251 ipp_attribute_t* attr, | |
252 CupsJob* current_job, | |
253 std::vector<CupsJob>* jobs); | |
254 | |
255 void NewJob(ipp_t* response, | |
256 const std::string& printer_id, | |
257 std::vector<CupsJob>* jobs) { | |
258 jobs->emplace_back(); | |
259 CupsJob* job = &(*jobs)[jobs->size() - 1]; | |
Carlson
2017/02/24 00:02:09
&(*jobs)[jobs->size() - 1]
is the same as
&jobs-
skau
2017/02/28 00:59:59
Done.
In C++17, emplace_back will return a refere
| |
260 job->printer_id = printer_id; | |
261 ParseFields(response, printer_id, ippNextAttribute(response), job, jobs); | |
262 } | |
263 | |
264 void ParseFields(ipp_t* response, | |
265 const std::string& printer_id, | |
266 ipp_attribute_t* attr, | |
267 CupsJob* current_job, | |
268 std::vector<CupsJob>* jobs) { | |
269 if (!attr) | |
270 return; | |
271 | |
272 base::StringPiece attribute_name = ippGetName(attr); | |
273 // Separators indicate a new job. Separators have empty names. | |
274 if (attribute_name.empty()) { | |
275 NewJob(response, printer_id, jobs); | |
276 return; | |
277 } | |
278 | |
279 // Continue to populate the job fileds. | |
280 ParseField(attr, attribute_name, current_job); | |
281 | |
282 ParseFields(response, printer_id, ippNextAttribute(response), current_job, | |
283 jobs); | |
284 } | |
285 | |
286 // Returns the uri for printer with |id| as served by CUPS. | |
287 std::string PrinterUriFromName(const std::string& id) { | |
288 return base::StringPrintf("ipp://localhost/printers/%s", id.c_str()); | |
Carlson
2017/02/24 00:02:08
Doesn't this need escaping, depending on the id?
skau
2017/02/28 00:59:59
I don't think so. CUPS rejects non-ascii character
Carlson
2017/02/28 01:29:47
OK, but I already found a bug in CUPS' validation
skau
2017/02/28 19:01:16
Our ids will be valid at least since they're a SHA
| |
289 } | |
290 | |
291 } // namespace | |
292 | |
293 void ParseJobsResponse(ipp_t* response, | |
294 const std::string& printer_id, | |
295 std::vector<CupsJob>* jobs) { | |
296 // Advance the position in the response to the jobs section. | |
297 ipp_attribute_t* attr = ippFirstAttribute(response); | |
298 while (attr != nullptr && ippGetGroupTag(attr) != IPP_TAG_JOB) { | |
299 attr = ippNextAttribute(response); | |
300 } | |
301 | |
302 if (attr != nullptr) { | |
303 NewJob(response, printer_id, jobs); | |
304 } | |
305 } | |
306 | |
307 void ParsePrinterStatus(ipp_t* response, PrinterStatus* printer_status) { | |
308 for (ipp_attribute_t* attr = ippFirstAttribute(response); attr != nullptr; | |
309 attr = ippNextAttribute(response)) { | |
310 base::StringPiece name = ippGetName(attr); | |
311 if (name.empty()) { | |
312 continue; | |
313 } | |
314 | |
315 if (name == kPrinterState) { | |
316 DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr)); | |
317 printer_status->state = static_cast<ipp_pstate_t>(ippGetInteger(attr, 0)); | |
318 } else if (name == kPrinterStateReasons) { | |
319 std::vector<std::string> reason_strings; | |
320 ParseCollection(attr, &reason_strings); | |
321 for (const std::string& reason : reason_strings) { | |
Carlson
2017/02/24 00:02:09
It's legit to do
for (const std::string& reason :
skau
2017/02/28 00:59:59
I don't think so, ParseCollection returns void. D
Carlson
2017/02/28 01:29:47
Oops, you're right, didn't look closely at the fun
| |
322 printer_status->reasons.push_back(ToPrinterReason(reason)); | |
323 } | |
324 } else if (name == kPrinterStateMessage) { | |
325 printer_status->message = ippGetString(attr, 0, nullptr); | |
326 } | |
327 } | |
328 } | |
329 | |
330 bool GetPrinterStatus(http_t* http, | |
331 const std::string& printer_id, | |
332 PrinterStatus* printer_status) { | |
333 DCHECK(http); | |
334 | |
335 auto request = WrapIpp(ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES)); | |
336 | |
337 const std::string printer_uri = PrinterUriFromName(printer_id); | |
338 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri, | |
339 nullptr, printer_uri.data()); | |
340 | |
341 std::vector<const char*> printer_attributes = { | |
342 kPrinterState, kPrinterStateReasons, kPrinterStateMessage}; | |
343 | |
344 ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, | |
345 kRequestedAttributes, printer_attributes.size(), nullptr, | |
346 printer_attributes.data()); | |
347 | |
348 auto response = | |
349 WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str())); | |
350 | |
351 if (ippGetStatusCode(response.get()) != IPP_STATUS_OK) | |
352 return false; | |
353 | |
354 ParsePrinterStatus(response.get(), printer_status); | |
355 | |
356 return true; | |
357 } | |
358 | |
359 bool GetCupsJobs(http_t* http, | |
360 const std::string& printer_id, | |
361 int limit, | |
362 bool completed, | |
363 std::vector<CupsJob>* jobs) { | |
364 DCHECK(http); | |
365 | |
366 auto request = WrapIpp(ippNewRequest(IPP_OP_GET_JOBS)); | |
367 const std::string printer_uri = PrinterUriFromName(printer_id); | |
368 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri, | |
369 nullptr, printer_uri.data()); | |
370 ippAddInteger(request.get(), IPP_TAG_OPERATION, IPP_TAG_INTEGER, kLimit, | |
371 limit); | |
372 | |
373 std::vector<const char*> job_attributes = { | |
374 kJobUri, kJobId, kJobState, | |
375 kJobStateReasons, kJobStateMessage, kJobImpressionsCompleted, | |
376 kTimeAtProcessing}; | |
377 | |
378 ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, | |
379 kRequestedAttributes, job_attributes.size(), nullptr, | |
380 job_attributes.data()); | |
381 | |
382 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, kWhichJobs, | |
383 nullptr, completed ? kCompleted : kNotCompleted); | |
384 | |
385 if (ippValidateAttributes(request.get()) != 1) { | |
386 LOG(WARNING) << "Could not validate ipp request: " << cupsLastErrorString(); | |
387 return false; | |
388 } | |
389 | |
390 // cupsDoRequest will delete the request. | |
391 auto response = | |
392 WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str())); | |
393 | |
394 ipp_status_t status = ippGetStatusCode(response.get()); | |
395 | |
396 if (status != IPP_OK) { | |
397 LOG(WARNING) << "IPP Error: " << cupsLastErrorString(); | |
398 return false; | |
399 } | |
400 | |
401 ParseJobsResponse(response.get(), printer_id, jobs); | |
402 | |
403 return true; | |
404 } | |
405 | |
406 } // namespace printing | |
OLD | NEW |