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

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

Issue 2691093006: Implement IPP Get-Jobs and Get-Printer-Attributes requests. (Closed)
Patch Set: rebase 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 using PR = PrinterStatus::PrinterReason::Reason;
Lei Zhang 2017/03/07 23:31:29 Maybe PReason?
skau 2017/03/10 01:07:00 Done.
19 using PSeverity = PrinterStatus::PrinterReason::Severity;
20
21 // printer attributes
22 const char kPrinterUri[] = "printer-uri";
23 const char kPrinterState[] = "printer-state";
24 const char kPrinterStateReasons[] = "printer-state-reasons";
25 const char kPrinterStateMessage[] = "printer-state-message";
26
27 // job attributes
28 const char kJobUri[] = "job-uri";
29 const char kJobId[] = "job-id";
30 const char kJobState[] = "job-state";
31 const char kJobStateReasons[] = "job-state-reasons";
32 const char kJobStateMessage[] = "job-state-message";
33 const char kJobImpressionsCompleted[] = "job-impressions-completed";
34 const char kTimeAtProcessing[] = "time-at-processing";
35
36 // request parameters
37 const char kRequestedAttributes[] = "requested-attributes";
38 const char kWhichJobs[] = "which-jobs";
39 const char kLimit[] = "limit";
40
41 // request values
42 const char kCompleted[] = "completed";
43 const char kNotCompleted[] = "not-completed";
44
45 // printer state severities
46 const char kSeverityReport[] = "report";
47 const char kSeverityWarn[] = "warning";
48 const char kSeverityError[] = "error";
49
50 // printer state reason values
51 const char kNone[] = "none";
52 const char kMediaNeeded[] = "media-needed";
53 const char kMediaJam[] = "media-jam";
54 const char kMovingToPaused[] = "moving-to-paused";
55 const char kPaused[] = "paused";
56 const char kShutdown[] = "shutdown";
57 const char kConnectingToDevice[] = "connecting-to-device";
58 const char kTimedOut[] = "timed-out";
59 const char kStopping[] = "stopping";
60 const char kStoppedPartly[] = "stopped-partly";
61 const char kTonerLow[] = "toner-low";
62 const char kTonerEmpty[] = "toner-empty";
63 const char kSpoolAreaFull[] = "spool-area-full";
64 const char kCoverOpen[] = "cover-open";
65 const char kInterlockOpen[] = "interlock-open";
66 const char kDoorOpen[] = "door-open";
67 const char kInputTrayMissing[] = "input-tray-missing";
68 const char kMediaLow[] = "media-low";
69 const char kMediaEmpty[] = "media-empty";
70 const char kOutputTrayMissing[] = "output-tray-missing";
71 const char kOutputAreaAlmostFull[] = "output-area-almost-full";
72 const char kOutputAreaFull[] = "output-area-full";
73 const char kMarkerSupplyLow[] = "marker-supply-low";
74 const char kMarkerSupplyEmpty[] = "marker-supply-empty";
75 const char kMarkerWasteAlmostFull[] = "marker-waste-almost-full";
76 const char kMarkerWasteFull[] = "marker-waste-full";
77 const char kFuserOverTemp[] = "fuser-over-temp";
78 const char kFuserUnderTemp[] = "fuser-under-temp";
79 const char kOpcNearEol[] = "opc-near-eol";
80 const char kOpcLifeOver[] = "opc-life-over";
81 const char kDeveloperLow[] = "developer-low";
82 const char kDeveloperEmpty[] = "developer-empty";
83 const char kInterpreterResourceUnavailable[] =
84 "interpreter-resource-unavailable";
85
86 std::unique_ptr<ipp_t, void (*)(ipp_t*)> WrapIpp(ipp_t* ipp) {
Lei Zhang 2017/03/08 02:02:16 ScopedIpp, like base::win::ScopedComPtr and friend
Lei Zhang 2017/03/08 02:21:10 Wait, I read this wrong. Ignore.
skau 2017/03/10 01:07:00 Acknowledged.
87 return std::unique_ptr<ipp_t, void (*)(ipp_t*)>(ipp, &ippDelete);
88 }
89
90 // Converts an IPP attribute |attr| to the appropriate JobState enum.
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;
Lei Zhang 2017/03/08 02:02:16 Just: return CupsJob::ABORTED; ?
skau 2017/03/10 01:07:00 Done.
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 // Returns a lookup map from strings to PrinterReason::Reason.
126 const std::map<base::StringPiece, PR>& GetLabelToReason() {
127 static const std::map<base::StringPiece, PR> kLabelToReason =
128 std::map<base::StringPiece, PR>{
129 {kNone, PR::NONE},
130 {kMediaNeeded, PR::MEDIA_NEEDED},
131 {kMediaJam, PR::MEDIA_JAM},
132 {kMovingToPaused, PR::MOVING_TO_PAUSED},
133 {kPaused, PR::PAUSED},
134 {kShutdown, PR::SHUTDOWN},
135 {kConnectingToDevice, PR::CONNECTING_TO_DEVICE},
136 {kTimedOut, PR::TIMED_OUT},
137 {kStopping, PR::STOPPING},
138 {kStoppedPartly, PR::STOPPED_PARTLY},
139 {kTonerLow, PR::TONER_LOW},
140 {kTonerEmpty, PR::TONER_EMPTY},
141 {kSpoolAreaFull, PR::SPOOL_AREA_FULL},
142 {kCoverOpen, PR::COVER_OPEN},
143 {kInterlockOpen, PR::INTERLOCK_OPEN},
144 {kDoorOpen, PR::DOOR_OPEN},
145 {kInputTrayMissing, PR::INPUT_TRAY_MISSING},
146 {kMediaLow, PR::MEDIA_LOW},
147 {kMediaEmpty, PR::MEDIA_EMPTY},
148 {kOutputTrayMissing, PR::OUTPUT_TRAY_MISSING},
149 {kOutputAreaAlmostFull, PR::OUTPUT_AREA_ALMOST_FULL},
150 {kOutputAreaFull, PR::OUTPUT_AREA_FULL},
151 {kMarkerSupplyLow, PR::MARKER_SUPPLY_LOW},
152 {kMarkerSupplyEmpty, PR::MARKER_SUPPLY_EMPTY},
153 {kMarkerWasteAlmostFull, PR::MARKER_WASTE_ALMOST_FULL},
154 {kMarkerWasteFull, PR::MARKER_WASTE_FULL},
155 {kFuserOverTemp, PR::FUSER_OVER_TEMP},
156 {kFuserUnderTemp, PR::FUSER_UNDER_TEMP},
157 {kOpcNearEol, PR::OPC_NEAR_EOL},
158 {kOpcLifeOver, PR::OPC_LIFE_OVER},
159 {kDeveloperLow, PR::DEVELOPER_LOW},
160 {kDeveloperEmpty, PR::DEVELOPER_EMPTY},
161 {kInterpreterResourceUnavailable,
162 PR::INTERPRETER_RESOURCE_UNAVAILABLE},
163 };
164 return kLabelToReason;
165 }
166
167 // Returns the Reason cooresponding to the string |reason|. Returns
168 // UNKOWN_REASON if the string is not recognized.
169 PrinterStatus::PrinterReason::Reason ToReason(base::StringPiece reason) {
170 const auto& enum_map = GetLabelToReason();
171 const auto& entry = enum_map.find(reason);
172 if (entry == enum_map.end()) {
Lei Zhang 2017/03/08 02:02:16 return entry != enum_map.end() ? entry->second : P
skau 2017/03/10 01:07:00 Done.
173 return PR::UNKNOWN_REASON;
174 }
175
176 return entry->second;
177 }
178
179 // Returns the Severity cooresponding to |severity|. Returns UNKNOWN_SEVERITY
180 // if the strin gis not recognized.
181 PSeverity ToSeverity(base::StringPiece severity) {
182 if (severity == kSeverityError)
183 return PSeverity::ERROR;
184
185 if (severity == kSeverityWarn)
186 return PSeverity::WARNING;
187
188 if (severity == kSeverityReport)
189 return PSeverity::REPORT;
190
191 return PSeverity::UNKNOWN_SEVERITY;
192 }
193
194 // Parses the |reason| string into a PrinterReason. Splits the string based on
195 // the last '-' to determine severity. If a recognized severity is not
196 // included, severity is assumed to be ERROR per RFC2911.
197 PrinterStatus::PrinterReason ToPrinterReason(base::StringPiece reason) {
198 PrinterStatus::PrinterReason parsed;
199
200 if (reason == kNone) {
201 parsed.reason = PR::NONE;
202 parsed.severity = PSeverity::UNKNOWN_SEVERITY;
203 return parsed;
204 }
205
206 size_t last_dash = reason.rfind('-');
207 auto severity = PSeverity::UNKNOWN_SEVERITY;
208 if (last_dash != base::StringPiece::npos) {
209 // try to parse the last part of the string as the severity.
210 severity = ToSeverity(reason.substr(last_dash + 1));
211 }
212
213 if (severity == PSeverity::UNKNOWN_SEVERITY) {
214 // Severity is unknown. No severity in the reason.
215 // Per spec, if there is no severity, severity is error.
216 parsed.severity = PSeverity::ERROR;
217 parsed.reason = ToReason(reason);
218 } else {
219 parsed.severity = severity;
220 // reason is the beginning of the string
221 parsed.reason = ToReason(reason.substr(0, last_dash));
222 }
223
224 return parsed;
225 }
226
227 // Populates |collection| with the collection of strings in |attr|.
228 void ParseCollection(ipp_attribute_t* attr,
229 std::vector<std::string>* collection) {
230 int count = ippGetCount(attr);
231 for (int i = 0; i < count; i++) {
232 base::StringPiece value = ippGetString(attr, i, nullptr);
233 collection->push_back(value.as_string());
234 }
235 }
236
237 // Parse a field for the CupsJob |job| from IPP attribute |attr| using the
238 // attribute name |name|.
239 void ParseField(ipp_attribute_t* attr, base::StringPiece name, CupsJob* job) {
240 DCHECK(!name.empty());
241 if (name == kJobId) {
242 job->id = ippGetInteger(attr, 0);
243 } else if (name == kJobImpressionsCompleted) {
244 job->current_pages = ippGetInteger(attr, 0);
245 } else if (name == kJobState) {
246 job->state = ToJobState(attr);
247 } else if (name == kJobStateReasons) {
248 ParseCollection(attr, &(job->state_reasons));
249 } else if (name == kJobStateMessage) {
250 job->state_message = ippGetString(attr, 0, nullptr);
251 } else if (name == kTimeAtProcessing) {
252 job->processing_started = ippGetInteger(attr, 0);
253 }
254 }
255
256 // ParseFields is forward declared because there is a circular dependency
257 // between ParseFields and NewJob.
258 void ParseFields(ipp_t* response,
259 const std::string& printer_id,
260 ipp_attribute_t* attr,
261 CupsJob* current_job,
262 std::vector<CupsJob>* jobs);
263
264 // Begins the parsing of a new job and proceed to parse all the fields for the
265 // job using ParseFields. The job that's added to |jobs| will be created with
266 // |printer_id|.
267 void NewJob(ipp_t* response,
268 const std::string& printer_id,
269 std::vector<CupsJob>* jobs) {
270 jobs->emplace_back();
271 CupsJob* job = &jobs->back();
272 job->printer_id = printer_id;
273 ParseFields(response, printer_id, ippNextAttribute(response), job, jobs);
274 }
275
276 // Populate the fields of |current_job| with the attributes from |response|.
277 // |attr| is the current attribute. New jobs are added to |jobs|.
278 void ParseFields(ipp_t* response,
Lei Zhang 2017/03/08 02:02:16 Do you think an iterative implementation might be
skau 2017/03/10 01:07:00 I find the recursive solution easy enough to read.
279 const std::string& printer_id,
280 ipp_attribute_t* attr,
281 CupsJob* current_job,
Lei Zhang 2017/03/08 02:02:16 Isn't this always &jobs->back()?
skau 2017/03/10 01:07:00 Acknowledged.
282 std::vector<CupsJob>* jobs) {
283 if (!attr)
284 return;
285
286 base::StringPiece attribute_name = ippGetName(attr);
287 // Separators indicate a new job. Separators have empty names.
288 if (attribute_name.empty()) {
289 NewJob(response, printer_id, jobs);
290 return;
291 }
292
293 // Continue to populate the job fileds.
294 ParseField(attr, attribute_name, current_job);
295
296 ParseFields(response, printer_id, ippNextAttribute(response), current_job,
297 jobs);
298 }
299
300 // Returns the uri for printer with |id| as served by CUPS. Assumes that |id|
301 // is a valid CUPS printer name and performs no error checking or escaping.
302 std::string PrinterUriFromName(const std::string& id) {
303 return base::StringPrintf("ipp://localhost/printers/%s", id.c_str());
304 }
305
306 } // namespace
307
308 void ParseJobsResponse(ipp_t* response,
309 const std::string& printer_id,
310 std::vector<CupsJob>* jobs) {
311 // Advance the position in the response to the jobs section.
312 ipp_attribute_t* attr = ippFirstAttribute(response);
313 while (attr != nullptr && ippGetGroupTag(attr) != IPP_TAG_JOB) {
314 attr = ippNextAttribute(response);
315 }
316
317 if (attr != nullptr) {
318 NewJob(response, printer_id, jobs);
319 }
320 }
321
322 void ParsePrinterStatus(ipp_t* response, PrinterStatus* printer_status) {
323 for (ipp_attribute_t* attr = ippFirstAttribute(response); attr != nullptr;
324 attr = ippNextAttribute(response)) {
325 base::StringPiece name = ippGetName(attr);
326 if (name.empty()) {
327 continue;
328 }
329
330 if (name == kPrinterState) {
331 DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr));
332 printer_status->state = static_cast<ipp_pstate_t>(ippGetInteger(attr, 0));
333 } else if (name == kPrinterStateReasons) {
334 std::vector<std::string> reason_strings;
335 ParseCollection(attr, &reason_strings);
336 for (const std::string& reason : reason_strings) {
337 printer_status->reasons.push_back(ToPrinterReason(reason));
338 }
339 } else if (name == kPrinterStateMessage) {
340 printer_status->message = ippGetString(attr, 0, nullptr);
341 }
342 }
343 }
344
345 bool GetPrinterStatus(http_t* http,
346 const std::string& printer_id,
347 PrinterStatus* printer_status) {
348 DCHECK(http);
349
350 auto request = WrapIpp(ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES));
351
352 const std::string printer_uri = PrinterUriFromName(printer_id);
353 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri,
354 nullptr, printer_uri.data());
355
356 std::vector<const char*> printer_attributes = {
Lei Zhang 2017/03/08 02:02:16 Can this be simpler? const char* const kPrinterAt
skau 2017/03/10 01:07:00 Done. Is there a way to have the compiler compute
Lei Zhang 2017/03/10 01:52:52 Does arraysize(kPrinterAttributes) work?
Carlson 2017/03/10 18:00:17 const std::array<const char* const> is another opt
skau 2017/03/10 21:49:13 Done.
357 kPrinterState, kPrinterStateReasons, kPrinterStateMessage};
358
359 ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
360 kRequestedAttributes, printer_attributes.size(), nullptr,
361 printer_attributes.data());
362
363 auto response =
364 WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str()));
365
366 if (ippGetStatusCode(response.get()) != IPP_STATUS_OK)
367 return false;
368
369 ParsePrinterStatus(response.get(), printer_status);
370
371 return true;
372 }
373
374 bool GetCupsJobs(http_t* http,
375 const std::string& printer_id,
376 int limit,
377 WhichJobs which,
378 std::vector<CupsJob>* jobs) {
379 DCHECK(http);
380
381 auto request = WrapIpp(ippNewRequest(IPP_OP_GET_JOBS));
382 const std::string printer_uri = PrinterUriFromName(printer_id);
383 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri,
384 nullptr, printer_uri.data());
385 ippAddInteger(request.get(), IPP_TAG_OPERATION, IPP_TAG_INTEGER, kLimit,
386 limit);
387
388 std::vector<const char*> job_attributes = {
389 kJobUri, kJobId, kJobState,
390 kJobStateReasons, kJobStateMessage, kJobImpressionsCompleted,
391 kTimeAtProcessing};
392
393 ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
394 kRequestedAttributes, job_attributes.size(), nullptr,
395 job_attributes.data());
396
397 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, kWhichJobs,
398 nullptr, which == COMPLETED ? kCompleted : kNotCompleted);
399
400 if (ippValidateAttributes(request.get()) != 1) {
401 LOG(WARNING) << "Could not validate ipp request: " << cupsLastErrorString();
402 return false;
403 }
404
405 // cupsDoRequest will delete the request.
406 auto response =
407 WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str()));
408
409 ipp_status_t status = ippGetStatusCode(response.get());
410
411 if (status != IPP_OK) {
412 LOG(WARNING) << "IPP Error: " << cupsLastErrorString();
413 return false;
414 }
415
416 ParseJobsResponse(response.get(), printer_id, jobs);
417
418 return true;
419 }
420
421 } // 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