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

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

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

Powered by Google App Engine
This is Rietveld 408576698