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

Unified Diff: printing/backend/cups_jobs.cc

Issue 2691093006: Implement IPP Get-Jobs and Get-Printer-Attributes requests. (Closed)
Patch Set: rebase Created 3 years, 10 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 side-by-side diff with in-line comments
Download patch
« printing/backend/cups_jobs.h ('K') | « printing/backend/cups_jobs.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« 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