Chromium Code Reviews| 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 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 | |
| OLD | NEW |