Chromium Code Reviews| Index: printing/backend/cups_jobs.cc |
| diff --git a/printing/backend/cups_jobs.cc b/printing/backend/cups_jobs.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4690686095db90ffd833e1fff7ba3901c8051618 |
| --- /dev/null |
| +++ b/printing/backend/cups_jobs.cc |
| @@ -0,0 +1,421 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "printing/backend/cups_jobs.h" |
| + |
| +#include <cups/ipp.h> |
| + |
| +#include <map> |
| + |
| +#include "base/logging.h" |
| +#include "base/strings/string_piece.h" |
| +#include "base/strings/stringprintf.h" |
| + |
| +namespace printing { |
| +namespace { |
| + |
| +using PR = PrinterStatus::PrinterReason::Reason; |
|
Lei Zhang
2017/03/07 23:31:29
Maybe PReason?
skau
2017/03/10 01:07:00
Done.
|
| +using PSeverity = PrinterStatus::PrinterReason::Severity; |
| + |
| +// printer attributes |
| +const char kPrinterUri[] = "printer-uri"; |
| +const char kPrinterState[] = "printer-state"; |
| +const char kPrinterStateReasons[] = "printer-state-reasons"; |
| +const char kPrinterStateMessage[] = "printer-state-message"; |
| + |
| +// job attributes |
| +const char kJobUri[] = "job-uri"; |
| +const char kJobId[] = "job-id"; |
| +const char kJobState[] = "job-state"; |
| +const char kJobStateReasons[] = "job-state-reasons"; |
| +const char kJobStateMessage[] = "job-state-message"; |
| +const char kJobImpressionsCompleted[] = "job-impressions-completed"; |
| +const char kTimeAtProcessing[] = "time-at-processing"; |
| + |
| +// request parameters |
| +const char kRequestedAttributes[] = "requested-attributes"; |
| +const char kWhichJobs[] = "which-jobs"; |
| +const char kLimit[] = "limit"; |
| + |
| +// request values |
| +const char kCompleted[] = "completed"; |
| +const char kNotCompleted[] = "not-completed"; |
| + |
| +// printer state severities |
| +const char kSeverityReport[] = "report"; |
| +const char kSeverityWarn[] = "warning"; |
| +const char kSeverityError[] = "error"; |
| + |
| +// printer state reason values |
| +const char kNone[] = "none"; |
| +const char kMediaNeeded[] = "media-needed"; |
| +const char kMediaJam[] = "media-jam"; |
| +const char kMovingToPaused[] = "moving-to-paused"; |
| +const char kPaused[] = "paused"; |
| +const char kShutdown[] = "shutdown"; |
| +const char kConnectingToDevice[] = "connecting-to-device"; |
| +const char kTimedOut[] = "timed-out"; |
| +const char kStopping[] = "stopping"; |
| +const char kStoppedPartly[] = "stopped-partly"; |
| +const char kTonerLow[] = "toner-low"; |
| +const char kTonerEmpty[] = "toner-empty"; |
| +const char kSpoolAreaFull[] = "spool-area-full"; |
| +const char kCoverOpen[] = "cover-open"; |
| +const char kInterlockOpen[] = "interlock-open"; |
| +const char kDoorOpen[] = "door-open"; |
| +const char kInputTrayMissing[] = "input-tray-missing"; |
| +const char kMediaLow[] = "media-low"; |
| +const char kMediaEmpty[] = "media-empty"; |
| +const char kOutputTrayMissing[] = "output-tray-missing"; |
| +const char kOutputAreaAlmostFull[] = "output-area-almost-full"; |
| +const char kOutputAreaFull[] = "output-area-full"; |
| +const char kMarkerSupplyLow[] = "marker-supply-low"; |
| +const char kMarkerSupplyEmpty[] = "marker-supply-empty"; |
| +const char kMarkerWasteAlmostFull[] = "marker-waste-almost-full"; |
| +const char kMarkerWasteFull[] = "marker-waste-full"; |
| +const char kFuserOverTemp[] = "fuser-over-temp"; |
| +const char kFuserUnderTemp[] = "fuser-under-temp"; |
| +const char kOpcNearEol[] = "opc-near-eol"; |
| +const char kOpcLifeOver[] = "opc-life-over"; |
| +const char kDeveloperLow[] = "developer-low"; |
| +const char kDeveloperEmpty[] = "developer-empty"; |
| +const char kInterpreterResourceUnavailable[] = |
| + "interpreter-resource-unavailable"; |
| + |
| +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.
|
| + return std::unique_ptr<ipp_t, void (*)(ipp_t*)>(ipp, &ippDelete); |
| +} |
| + |
| +// Converts an IPP attribute |attr| to the appropriate JobState enum. |
| +CupsJob::JobState ToJobState(ipp_attribute_t* attr) { |
| + DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr)); |
| + int state = ippGetInteger(attr, 0); |
| + CupsJob::JobState converted_state = CupsJob::UNKNOWN; |
| + switch (state) { |
| + case IPP_JOB_ABORTED: |
| + converted_state = CupsJob::ABORTED; |
|
Lei Zhang
2017/03/08 02:02:16
Just: return CupsJob::ABORTED; ?
skau
2017/03/10 01:07:00
Done.
|
| + break; |
| + case IPP_JOB_CANCELLED: |
| + converted_state = CupsJob::CANCELED; |
| + break; |
| + case IPP_JOB_COMPLETED: |
| + converted_state = CupsJob::COMPLETED; |
| + break; |
| + case IPP_JOB_HELD: |
| + converted_state = CupsJob::HELD; |
| + break; |
| + case IPP_JOB_PENDING: |
| + converted_state = CupsJob::PENDING; |
| + break; |
| + case IPP_JOB_PROCESSING: |
| + converted_state = CupsJob::PROCESSING; |
| + break; |
| + case IPP_JOB_STOPPED: |
| + converted_state = CupsJob::STOPPED; |
| + break; |
| + default: |
| + NOTREACHED() << "Unidentifed state " << state; |
| + break; |
| + } |
| + |
| + return converted_state; |
| +} |
| + |
| +// Returns a lookup map from strings to PrinterReason::Reason. |
| +const std::map<base::StringPiece, PR>& GetLabelToReason() { |
| + static const std::map<base::StringPiece, PR> kLabelToReason = |
| + std::map<base::StringPiece, PR>{ |
| + {kNone, PR::NONE}, |
| + {kMediaNeeded, PR::MEDIA_NEEDED}, |
| + {kMediaJam, PR::MEDIA_JAM}, |
| + {kMovingToPaused, PR::MOVING_TO_PAUSED}, |
| + {kPaused, PR::PAUSED}, |
| + {kShutdown, PR::SHUTDOWN}, |
| + {kConnectingToDevice, PR::CONNECTING_TO_DEVICE}, |
| + {kTimedOut, PR::TIMED_OUT}, |
| + {kStopping, PR::STOPPING}, |
| + {kStoppedPartly, PR::STOPPED_PARTLY}, |
| + {kTonerLow, PR::TONER_LOW}, |
| + {kTonerEmpty, PR::TONER_EMPTY}, |
| + {kSpoolAreaFull, PR::SPOOL_AREA_FULL}, |
| + {kCoverOpen, PR::COVER_OPEN}, |
| + {kInterlockOpen, PR::INTERLOCK_OPEN}, |
| + {kDoorOpen, PR::DOOR_OPEN}, |
| + {kInputTrayMissing, PR::INPUT_TRAY_MISSING}, |
| + {kMediaLow, PR::MEDIA_LOW}, |
| + {kMediaEmpty, PR::MEDIA_EMPTY}, |
| + {kOutputTrayMissing, PR::OUTPUT_TRAY_MISSING}, |
| + {kOutputAreaAlmostFull, PR::OUTPUT_AREA_ALMOST_FULL}, |
| + {kOutputAreaFull, PR::OUTPUT_AREA_FULL}, |
| + {kMarkerSupplyLow, PR::MARKER_SUPPLY_LOW}, |
| + {kMarkerSupplyEmpty, PR::MARKER_SUPPLY_EMPTY}, |
| + {kMarkerWasteAlmostFull, PR::MARKER_WASTE_ALMOST_FULL}, |
| + {kMarkerWasteFull, PR::MARKER_WASTE_FULL}, |
| + {kFuserOverTemp, PR::FUSER_OVER_TEMP}, |
| + {kFuserUnderTemp, PR::FUSER_UNDER_TEMP}, |
| + {kOpcNearEol, PR::OPC_NEAR_EOL}, |
| + {kOpcLifeOver, PR::OPC_LIFE_OVER}, |
| + {kDeveloperLow, PR::DEVELOPER_LOW}, |
| + {kDeveloperEmpty, PR::DEVELOPER_EMPTY}, |
| + {kInterpreterResourceUnavailable, |
| + PR::INTERPRETER_RESOURCE_UNAVAILABLE}, |
| + }; |
| + return kLabelToReason; |
| +} |
| + |
| +// Returns the Reason cooresponding to the string |reason|. Returns |
| +// UNKOWN_REASON if the string is not recognized. |
| +PrinterStatus::PrinterReason::Reason ToReason(base::StringPiece reason) { |
| + const auto& enum_map = GetLabelToReason(); |
| + const auto& entry = enum_map.find(reason); |
| + 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.
|
| + return PR::UNKNOWN_REASON; |
| + } |
| + |
| + return entry->second; |
| +} |
| + |
| +// Returns the Severity cooresponding to |severity|. Returns UNKNOWN_SEVERITY |
| +// if the strin gis not recognized. |
| +PSeverity ToSeverity(base::StringPiece severity) { |
| + if (severity == kSeverityError) |
| + return PSeverity::ERROR; |
| + |
| + if (severity == kSeverityWarn) |
| + return PSeverity::WARNING; |
| + |
| + if (severity == kSeverityReport) |
| + return PSeverity::REPORT; |
| + |
| + return PSeverity::UNKNOWN_SEVERITY; |
| +} |
| + |
| +// Parses the |reason| string into a PrinterReason. Splits the string based on |
| +// the last '-' to determine severity. If a recognized severity is not |
| +// included, severity is assumed to be ERROR per RFC2911. |
| +PrinterStatus::PrinterReason ToPrinterReason(base::StringPiece reason) { |
| + PrinterStatus::PrinterReason parsed; |
| + |
| + if (reason == kNone) { |
| + parsed.reason = PR::NONE; |
| + parsed.severity = PSeverity::UNKNOWN_SEVERITY; |
| + return parsed; |
| + } |
| + |
| + size_t last_dash = reason.rfind('-'); |
| + auto severity = PSeverity::UNKNOWN_SEVERITY; |
| + if (last_dash != base::StringPiece::npos) { |
| + // try to parse the last part of the string as the severity. |
| + severity = ToSeverity(reason.substr(last_dash + 1)); |
| + } |
| + |
| + if (severity == PSeverity::UNKNOWN_SEVERITY) { |
| + // Severity is unknown. No severity in the reason. |
| + // Per spec, if there is no severity, severity is error. |
| + parsed.severity = PSeverity::ERROR; |
| + parsed.reason = ToReason(reason); |
| + } else { |
| + parsed.severity = severity; |
| + // reason is the beginning of the string |
| + parsed.reason = ToReason(reason.substr(0, last_dash)); |
| + } |
| + |
| + return parsed; |
| +} |
| + |
| +// Populates |collection| with the collection of strings in |attr|. |
| +void ParseCollection(ipp_attribute_t* attr, |
| + std::vector<std::string>* collection) { |
| + int count = ippGetCount(attr); |
| + for (int i = 0; i < count; i++) { |
| + base::StringPiece value = ippGetString(attr, i, nullptr); |
| + collection->push_back(value.as_string()); |
| + } |
| +} |
| + |
| +// Parse a field for the CupsJob |job| from IPP attribute |attr| using the |
| +// attribute name |name|. |
| +void ParseField(ipp_attribute_t* attr, base::StringPiece name, CupsJob* job) { |
| + DCHECK(!name.empty()); |
| + if (name == kJobId) { |
| + job->id = ippGetInteger(attr, 0); |
| + } else if (name == kJobImpressionsCompleted) { |
| + job->current_pages = ippGetInteger(attr, 0); |
| + } else if (name == kJobState) { |
| + job->state = ToJobState(attr); |
| + } else if (name == kJobStateReasons) { |
| + ParseCollection(attr, &(job->state_reasons)); |
| + } else if (name == kJobStateMessage) { |
| + job->state_message = ippGetString(attr, 0, nullptr); |
| + } else if (name == kTimeAtProcessing) { |
| + job->processing_started = ippGetInteger(attr, 0); |
| + } |
| +} |
| + |
| +// ParseFields is forward declared because there is a circular dependency |
| +// between ParseFields and NewJob. |
| +void ParseFields(ipp_t* response, |
| + const std::string& printer_id, |
| + ipp_attribute_t* attr, |
| + CupsJob* current_job, |
| + std::vector<CupsJob>* jobs); |
| + |
| +// Begins the parsing of a new job and proceed to parse all the fields for the |
| +// job using ParseFields. The job that's added to |jobs| will be created with |
| +// |printer_id|. |
| +void NewJob(ipp_t* response, |
| + const std::string& printer_id, |
| + std::vector<CupsJob>* jobs) { |
| + jobs->emplace_back(); |
| + CupsJob* job = &jobs->back(); |
| + job->printer_id = printer_id; |
| + ParseFields(response, printer_id, ippNextAttribute(response), job, jobs); |
| +} |
| + |
| +// Populate the fields of |current_job| with the attributes from |response|. |
| +// |attr| is the current attribute. New jobs are added to |jobs|. |
| +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.
|
| + const std::string& printer_id, |
| + ipp_attribute_t* attr, |
| + 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.
|
| + std::vector<CupsJob>* jobs) { |
| + if (!attr) |
| + return; |
| + |
| + base::StringPiece attribute_name = ippGetName(attr); |
| + // Separators indicate a new job. Separators have empty names. |
| + if (attribute_name.empty()) { |
| + NewJob(response, printer_id, jobs); |
| + return; |
| + } |
| + |
| + // Continue to populate the job fileds. |
| + ParseField(attr, attribute_name, current_job); |
| + |
| + ParseFields(response, printer_id, ippNextAttribute(response), current_job, |
| + jobs); |
| +} |
| + |
| +// Returns the uri for printer with |id| as served by CUPS. Assumes that |id| |
| +// is a valid CUPS printer name and performs no error checking or escaping. |
| +std::string PrinterUriFromName(const std::string& id) { |
| + return base::StringPrintf("ipp://localhost/printers/%s", id.c_str()); |
| +} |
| + |
| +} // namespace |
| + |
| +void ParseJobsResponse(ipp_t* response, |
| + const std::string& printer_id, |
| + std::vector<CupsJob>* jobs) { |
| + // Advance the position in the response to the jobs section. |
| + ipp_attribute_t* attr = ippFirstAttribute(response); |
| + while (attr != nullptr && ippGetGroupTag(attr) != IPP_TAG_JOB) { |
| + attr = ippNextAttribute(response); |
| + } |
| + |
| + if (attr != nullptr) { |
| + NewJob(response, printer_id, jobs); |
| + } |
| +} |
| + |
| +void ParsePrinterStatus(ipp_t* response, PrinterStatus* printer_status) { |
| + for (ipp_attribute_t* attr = ippFirstAttribute(response); attr != nullptr; |
| + attr = ippNextAttribute(response)) { |
| + base::StringPiece name = ippGetName(attr); |
| + if (name.empty()) { |
| + continue; |
| + } |
| + |
| + if (name == kPrinterState) { |
| + DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr)); |
| + printer_status->state = static_cast<ipp_pstate_t>(ippGetInteger(attr, 0)); |
| + } else if (name == kPrinterStateReasons) { |
| + std::vector<std::string> reason_strings; |
| + ParseCollection(attr, &reason_strings); |
| + for (const std::string& reason : reason_strings) { |
| + printer_status->reasons.push_back(ToPrinterReason(reason)); |
| + } |
| + } else if (name == kPrinterStateMessage) { |
| + printer_status->message = ippGetString(attr, 0, nullptr); |
| + } |
| + } |
| +} |
| + |
| +bool GetPrinterStatus(http_t* http, |
| + const std::string& printer_id, |
| + PrinterStatus* printer_status) { |
| + DCHECK(http); |
| + |
| + auto request = WrapIpp(ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES)); |
| + |
| + const std::string printer_uri = PrinterUriFromName(printer_id); |
| + ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri, |
| + nullptr, printer_uri.data()); |
| + |
| + 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.
|
| + kPrinterState, kPrinterStateReasons, kPrinterStateMessage}; |
| + |
| + ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, |
| + kRequestedAttributes, printer_attributes.size(), nullptr, |
| + printer_attributes.data()); |
| + |
| + auto response = |
| + WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str())); |
| + |
| + if (ippGetStatusCode(response.get()) != IPP_STATUS_OK) |
| + return false; |
| + |
| + ParsePrinterStatus(response.get(), printer_status); |
| + |
| + return true; |
| +} |
| + |
| +bool GetCupsJobs(http_t* http, |
| + const std::string& printer_id, |
| + int limit, |
| + WhichJobs which, |
| + std::vector<CupsJob>* jobs) { |
| + DCHECK(http); |
| + |
| + auto request = WrapIpp(ippNewRequest(IPP_OP_GET_JOBS)); |
| + const std::string printer_uri = PrinterUriFromName(printer_id); |
| + ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri, |
| + nullptr, printer_uri.data()); |
| + ippAddInteger(request.get(), IPP_TAG_OPERATION, IPP_TAG_INTEGER, kLimit, |
| + limit); |
| + |
| + std::vector<const char*> job_attributes = { |
| + kJobUri, kJobId, kJobState, |
| + kJobStateReasons, kJobStateMessage, kJobImpressionsCompleted, |
| + kTimeAtProcessing}; |
| + |
| + ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, |
| + kRequestedAttributes, job_attributes.size(), nullptr, |
| + job_attributes.data()); |
| + |
| + ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, kWhichJobs, |
| + nullptr, which == COMPLETED ? kCompleted : kNotCompleted); |
| + |
| + if (ippValidateAttributes(request.get()) != 1) { |
| + LOG(WARNING) << "Could not validate ipp request: " << cupsLastErrorString(); |
| + return false; |
| + } |
| + |
| + // cupsDoRequest will delete the request. |
| + auto response = |
| + WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str())); |
| + |
| + ipp_status_t status = ippGetStatusCode(response.get()); |
| + |
| + if (status != IPP_OK) { |
| + LOG(WARNING) << "IPP Error: " << cupsLastErrorString(); |
| + return false; |
| + } |
| + |
| + ParseJobsResponse(response.get(), printer_id, jobs); |
| + |
| + return true; |
| +} |
| + |
| +} // namespace printing |