Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(719)

Side by Side Diff: printing/backend/cups_jobs.cc

Issue 2691093006: Implement IPP Get-Jobs and Get-Printer-Attributes requests. (Closed)
Patch Set: assign default values to id and state Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« printing/backend/cups_jobs.h ('K') | « printing/backend/cups_jobs.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« printing/backend/cups_jobs.h ('K') | « printing/backend/cups_jobs.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698