Commit a26877df authored by skau's avatar skau Committed by Commit bot

Implement IPP Get-Jobs and Get-Printer-Attributes requests.

CUPS provides cupsGetJobs2 but it doesn't provide the necessary fields
to report status accurately.  Notably, it doesn't provide
job-impressions-completed or printer-state-reasons both of which are
necessary to differentiate errors.

BUG=684853

Review-Url: https://codereview.chromium.org/2691093006
Cr-Commit-Position: refs/heads/master@{#456225}
parent dece487a
......@@ -33,6 +33,9 @@ namespace {
// The rate in milliseconds at which we will poll CUPS for print job updates.
const int kPollRate = 1000;
// Threshold for giving up on communicating with CUPS.
const int kRetryMax = 6;
// Returns the equivalient CupsPrintJob#State from a CupsJob#JobState.
chromeos::CupsPrintJob::State ConvertState(printing::CupsJob::JobState state) {
using cpj = chromeos::CupsPrintJob::State;
......@@ -61,6 +64,13 @@ chromeos::CupsPrintJob::State ConvertState(printing::CupsJob::JobState state) {
return cpj::STATE_NONE;
}
chromeos::QueryResult QueryCups(::printing::CupsConnection* connection,
const std::vector<std::string>& printer_ids) {
chromeos::QueryResult result;
result.success = connection->GetJobs(printer_ids, &result.queues);
return result;
}
} // namespace
namespace chromeos {
......@@ -134,6 +144,7 @@ bool CupsPrintJobManagerImpl::CreatePrintJob(const std::string& printer_name,
total_page_number);
std::string key = cpj->GetUniqueId();
jobs_[key] = std::move(cpj);
CupsPrintJob* job = jobs_[key].get();
NotifyJobCreated(job);
......@@ -153,36 +164,67 @@ void CupsPrintJobManagerImpl::ScheduleQuery() {
void CupsPrintJobManagerImpl::ScheduleQuery(const base::TimeDelta& delay) {
if (!in_query_) {
in_query_ = true;
content::BrowserThread::PostDelayedTask(
content::BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
base::Bind(&CupsPrintJobManagerImpl::QueryCups,
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&CupsPrintJobManagerImpl::PostQuery,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kPollRate));
delay);
}
}
// Query CUPS asynchronously. Post results back to UI thread.
void CupsPrintJobManagerImpl::QueryCups() {
std::vector<::printing::CupsJob> jobs = cups_connection_.GetJobs();
void CupsPrintJobManagerImpl::PostQuery() {
// The set of active printers is expected to be small.
std::set<std::string> printer_ids;
for (const auto& entry : jobs_) {
printer_ids.insert(entry.second->printer().id());
}
std::vector<std::string> ids{printer_ids.begin(), printer_ids.end()};
content::BrowserThread::PostTask(
content::BrowserThread::ID::UI, FROM_HERE,
content::BrowserThread::PostTaskAndReplyWithResult(
content::BrowserThread::FILE_USER_BLOCKING, FROM_HERE,
base::Bind(&QueryCups, &cups_connection_, ids),
base::Bind(&CupsPrintJobManagerImpl::UpdateJobs,
weak_ptr_factory_.GetWeakPtr(), jobs));
weak_ptr_factory_.GetWeakPtr()));
}
// Use job information to update local job states. Previously completed jobs
// could be in |jobs| but those are ignored as we will not emit updates for them
// after they are completed.
void CupsPrintJobManagerImpl::UpdateJobs(
const std::vector<::printing::CupsJob>& jobs) {
void CupsPrintJobManagerImpl::UpdateJobs(const QueryResult& result) {
const std::vector<::printing::QueueStatus>& queues = result.queues;
// Query has completed. Allow more queries.
in_query_ = false;
// If the query failed, either retry or purge.
if (!result.success) {
retry_count_++;
LOG(WARNING) << "Failed to query CUPS for queue status. Schedule retry ("
<< retry_count_ << ")";
if (retry_count_ > kRetryMax) {
LOG(ERROR) << "CUPS is unreachable. Giving up on all jobs.";
PurgeJobs();
} else {
// Schedule another query with a larger delay.
DCHECK_GE(1, retry_count_);
ScheduleQuery(
base::TimeDelta::FromMilliseconds(kPollRate * retry_count_));
}
return;
}
// A query has completed. Reset retry counter.
retry_count_ = 0;
std::vector<std::string> active_jobs;
for (auto& job : jobs) {
for (const auto& queue : queues) {
for (auto& job : queue.jobs) {
std::string key = CupsPrintJob::GetUniqueId(job.printer_id, job.id);
const auto& entry = jobs_.find(key);
if (entry != jobs_.end()) {
if (entry == jobs_.end())
continue;
CupsPrintJob* print_job = entry->second.get();
// Update a job we're tracking.
......@@ -199,18 +241,23 @@ void CupsPrintJobManagerImpl::UpdateJobs(
// Keep polling until all jobs complete or error.
if (!active_jobs.empty()) {
// During normal operations, we poll at the default rate.
ScheduleQuery();
} else if (!jobs_.empty()) {
// We're tracking jobs that we didn't receive an update for. Something bad
// has happened.
LOG(ERROR) << "Lost track of (" << jobs_.size() << ") jobs";
PurgeJobs();
}
}
void CupsPrintJobManagerImpl::PurgeJobs() {
for (const auto& entry : jobs_) {
// Declare all lost jobs errors.
JobStateUpdated(entry.second.get(), CupsPrintJob::State::STATE_ERROR);
}
jobs_.clear();
}
}
void CupsPrintJobManagerImpl::JobStateUpdated(CupsPrintJob* job,
......
......@@ -23,6 +23,11 @@ class Profile;
namespace chromeos {
struct QueryResult {
bool success;
std::vector<::printing::QueueStatus> queues;
};
class CupsPrintJobManagerImpl : public CupsPrintJobManager,
public content::NotificationObserver {
public:
......@@ -52,11 +57,15 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager,
// Schedule a query of CUPS for print job status with a delay of |delay|.
void ScheduleQuery(const base::TimeDelta& delay);
// Query CUPS for print job status.
void QueryCups();
// Schedule the CUPS query off the UI thread. Posts results back to UI thread
// to UpdateJobs.
void PostQuery();
// Process jobs from CUPS and perform notifications.
void UpdateJobs(const std::vector<::printing::CupsJob>& jobs);
void UpdateJobs(const QueryResult& results);
// Mark remaining jobs as errors and remove active jobs.
void PurgeJobs();
// Updates the state and performs the appropriate notifications.
void JobStateUpdated(CupsPrintJob* job, CupsPrintJob::State new_state);
......@@ -67,6 +76,9 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager,
// Prevents multiple queries from being scheduled simultaneously.
bool in_query_ = false;
// Records the number of consecutive times the GetJobs query has failed.
int retry_count_ = 0;
::printing::CupsConnection cups_connection_;
content::NotificationRegistrar registrar_;
base::WeakPtrFactory<CupsPrintJobManagerImpl> weak_ptr_factory_;
......
......@@ -189,6 +189,8 @@ component("printing") {
"backend/cups_deleters.h",
"backend/cups_ipp_util.cc",
"backend/cups_ipp_util.h",
"backend/cups_jobs.cc",
"backend/cups_jobs.h",
"backend/cups_printer.cc",
"backend/cups_printer.h",
"backend/print_backend_cups_ipp.cc",
......
......@@ -4,18 +4,31 @@
#include "printing/backend/cups_connection.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "printing/backend/cups_jobs.h"
namespace printing {
namespace {
const int kTimeoutMs = 3000;
constexpr int kTimeoutMs = 3000;
// The number of jobs we'll retrieve for a queue. We expect a user to queue at
// most 10 jobs per printer. If they queue more, they won't receive updates for
// the 11th job until one finishes.
constexpr int kProcessingJobsLimit = 10;
// The number of completed jobs that are retrieved. We only need one update for
// a completed job to confirm its final status. We could retrieve one but we
// retrieve the last 3 in case that many finished between queries.
constexpr int kCompletedJobsLimit = 3;
class DestinationEnumerator {
public:
......@@ -44,42 +57,6 @@ class DestinationEnumerator {
DISALLOW_COPY_AND_ASSIGN(DestinationEnumerator);
};
CupsJob createCupsJob(int job_id,
base::StringPiece job_title,
base::StringPiece printer_id,
ipp_jstate_t state) {
CupsJob::JobState converted_state = CupsJob::UNKNOWN;
switch (state) {
case IPP_JOB_ABORTED:
converted_state = CupsJob::ABORTED;
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();
break;
}
return {job_id, job_title.as_string(), printer_id.as_string(),
converted_state};
}
} // namespace
CupsConnection::CupsConnection(const GURL& print_server_url,
......@@ -162,24 +139,41 @@ std::unique_ptr<CupsPrinter> CupsConnection::GetPrinter(
std::unique_ptr<cups_dinfo_t, DestInfoDeleter>(info));
}
std::vector<CupsJob> CupsConnection::GetJobs() {
cups_job_t* jobs;
int num_jobs = cupsGetJobs2(cups_http_.get(), // http connection
&jobs, // out param
nullptr, // all printers
0, // all users
CUPS_WHICHJOBS_ALL);
const JobsDeleter deleter(num_jobs);
std::unique_ptr<cups_job_t, const JobsDeleter&> scoped_jobs(jobs, deleter);
std::vector<CupsJob> job_copies;
for (int i = 0; i < num_jobs; i++) {
job_copies.push_back(
createCupsJob(jobs[i].id, jobs[i].title, jobs[i].dest, jobs[i].state));
bool CupsConnection::GetJobs(const std::vector<std::string>& printer_ids,
std::vector<QueueStatus>* queues) {
DCHECK(queues);
if (!Connect()) {
LOG(ERROR) << "Could not establish connection to CUPS";
return false;
}
std::vector<QueueStatus> temp_queues;
for (const std::string& id : printer_ids) {
temp_queues.emplace_back();
QueueStatus* queue_status = &temp_queues.back();
if (!GetPrinterStatus(cups_http_.get(), id,
&queue_status->printer_status)) {
LOG(WARNING) << "Could not retrieve printer status for " << id;
return false;
}
if (!GetCupsJobs(cups_http_.get(), id, kCompletedJobsLimit, COMPLETED,
&queue_status->jobs)) {
LOG(WARNING) << "Could not get completed jobs for " << id;
return false;
}
if (!GetCupsJobs(cups_http_.get(), id, kProcessingJobsLimit, PROCESSING,
&queue_status->jobs)) {
LOG(WARNING) << "Could not get in progress jobs for " << id;
return false;
}
}
queues->insert(queues->end(), temp_queues.begin(), temp_queues.end());
return job_copies;
return true;
}
std::string CupsConnection::server_name() const {
......
......@@ -14,29 +14,17 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "printing/backend/cups_deleters.h"
#include "printing/backend/cups_jobs.h"
#include "printing/backend/cups_printer.h"
#include "printing/printing_export.h"
#include "url/gurl.h"
namespace printing {
// Represents a print job sent to the queue.
struct PRINTING_EXPORT CupsJob {
enum JobState {
UNKNOWN,
PENDING,
HELD,
COMPLETED,
PROCESSING,
STOPPED,
CANCELED,
ABORTED
};
int id;
std::string title;
std::string printer_id;
JobState state;
// Represents the status of a printer queue.
struct PRINTING_EXPORT QueueStatus {
PrinterStatus printer_status;
std::vector<CupsJob> jobs;
};
// Represents a connection to a CUPS server.
......@@ -56,8 +44,12 @@ class PRINTING_EXPORT CupsConnection {
// Returns a printer for |printer_name| from the connected server.
std::unique_ptr<CupsPrinter> GetPrinter(const std::string& printer_name);
// Returns a list of print jobs from all connected printers.
std::vector<CupsJob> GetJobs();
// Queries CUPS for printer queue status for |printer_ids|. Populates |jobs|
// with said information with one QueueStatus per printer_id. Returns true if
// all the queries were successful. In the event of failure, |jobs| will be
// unchanged.
bool GetJobs(const std::vector<std::string>& printer_ids,
std::vector<QueueStatus>* jobs);
std::string server_name() const;
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Methods for parsing IPP Printer attributes.
#ifndef PRINTING_BACKEND_CUPS_IPP_UTIL_H_
#define PRINTING_BACKEND_CUPS_IPP_UTIL_H_
......
// 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 <array>
#include <map>
#include <memory>
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
namespace printing {
namespace {
using PReason = PrinterStatus::PrinterReason::Reason;
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";
constexpr std::array<const char* const, 3> kPrinterAttributes = {
kPrinterState, kPrinterStateReasons, kPrinterStateMessage};
std::unique_ptr<ipp_t, void (*)(ipp_t*)> WrapIpp(ipp_t* ipp) {
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);
switch (state) {
case IPP_JOB_ABORTED:
return CupsJob::ABORTED;
case IPP_JOB_CANCELLED:
return CupsJob::CANCELED;
case IPP_JOB_COMPLETED:
return CupsJob::COMPLETED;
case IPP_JOB_HELD:
return CupsJob::HELD;
case IPP_JOB_PENDING:
return CupsJob::PENDING;
case IPP_JOB_PROCESSING:
return CupsJob::PROCESSING;
case IPP_JOB_STOPPED:
return CupsJob::STOPPED;
default:
NOTREACHED() << "Unidentifed state " << state;
break;
}
return CupsJob::UNKNOWN;
}
// Returns a lookup map from strings to PrinterReason::Reason.
const std::map<base::StringPiece, PReason>& GetLabelToReason() {
static const std::map<base::StringPiece, PReason> kLabelToReason =
std::map<base::StringPiece, PReason>{
{kNone, PReason::NONE},
{kMediaNeeded, PReason::MEDIA_NEEDED},
{kMediaJam, PReason::MEDIA_JAM},
{kMovingToPaused, PReason::MOVING_TO_PAUSED},
{kPaused, PReason::PAUSED},
{kShutdown, PReason::SHUTDOWN},
{kConnectingToDevice, PReason::CONNECTING_TO_DEVICE},
{kTimedOut, PReason::TIMED_OUT},
{kStopping, PReason::STOPPING},
{kStoppedPartly, PReason::STOPPED_PARTLY},
{kTonerLow, PReason::TONER_LOW},
{kTonerEmpty, PReason::TONER_EMPTY},
{kSpoolAreaFull, PReason::SPOOL_AREA_FULL},
{kCoverOpen, PReason::COVER_OPEN},
{kInterlockOpen, PReason::INTERLOCK_OPEN},
{kDoorOpen, PReason::DOOR_OPEN},
{kInputTrayMissing, PReason::INPUT_TRAY_MISSING},
{kMediaLow, PReason::MEDIA_LOW},
{kMediaEmpty, PReason::MEDIA_EMPTY},
{kOutputTrayMissing, PReason::OUTPUT_TRAY_MISSING},
{kOutputAreaAlmostFull, PReason::OUTPUT_AREA_ALMOST_FULL},
{kOutputAreaFull, PReason::OUTPUT_AREA_FULL},
{kMarkerSupplyLow, PReason::MARKER_SUPPLY_LOW},
{kMarkerSupplyEmpty, PReason::MARKER_SUPPLY_EMPTY},
{kMarkerWasteAlmostFull, PReason::MARKER_WASTE_ALMOST_FULL},
{kMarkerWasteFull, PReason::MARKER_WASTE_FULL},
{kFuserOverTemp, PReason::FUSER_OVER_TEMP},
{kFuserUnderTemp, PReason::FUSER_UNDER_TEMP},
{kOpcNearEol, PReason::OPC_NEAR_EOL},
{kOpcLifeOver, PReason::OPC_LIFE_OVER},
{kDeveloperLow, PReason::DEVELOPER_LOW},
{kDeveloperEmpty, PReason::DEVELOPER_EMPTY},
{kInterpreterResourceUnavailable,
PReason::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);
return entry != enum_map.end() ? entry->second : PReason::UNKNOWN_REASON;
}
// 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 = PReason::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);
}
}
// Returns a new CupsJob allocated in |jobs| with |printer_id| populated.
CupsJob* NewJob(const std::string& printer_id, std::vector<CupsJob>* jobs) {
jobs->emplace_back();
CupsJob* job = &jobs->back();
job->printer_id = printer_id;
return job;
}
void ParseJobs(ipp_t* response,
const std::string& printer_id,
ipp_attribute_t* starting_attr,
std::vector<CupsJob>* jobs) {
// We know this is a non-empty job section. Start parsing fields for at least
// one job.
CupsJob* current_job = NewJob(printer_id, jobs);
for (ipp_attribute_t* attr = starting_attr; attr != nullptr;
attr = ippNextAttribute(response)) {
base::StringPiece attribute_name = ippGetName(attr);
// Separators indicate a new job. Separators have empty names.
if (attribute_name.empty()) {
current_job = NewJob(printer_id, jobs);
continue;
}
// Continue to populate the job fileds.
ParseField(attr, attribute_name, current_job);
}
}
// 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) {
ParseJobs(response, printer_id, attr, 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());
ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
kRequestedAttributes, kPrinterAttributes.size(), nullptr,
kPrinterAttributes.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,
JobCompletionState 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
// 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.
// Implementations of IPP requests for printer queue information.
#ifndef PRINTING_BACKEND_CUPS_JOBS_H_
#define PRINTING_BACKEND_CUPS_JOBS_H_
#include <cups/cups.h>
#include <string>
#include <vector>
#include "printing/printing_export.h"
namespace printing {
// Represents a print job sent to the queue.
struct PRINTING_EXPORT CupsJob {
// Corresponds to job-state from RFC2911.
enum JobState {
UNKNOWN,
PENDING, // waiting to be processed
HELD, // the job has not begun printing and will not without intervention
COMPLETED,
PROCESSING, // job is being sent to the printer/printed
STOPPED, // job was being processed and has now stopped
CANCELED, // either the spooler or a user canclled the job
ABORTED // an error occurred causing the printer to give up
};
// job id
int id = -1;
// printer name in CUPS
std::string printer_id;
JobState state = UNKNOWN;
// the last page printed
int current_pages = -1;
// detail for the job state
std::vector<std::string> state_reasons;
// human readable message explaining the state
std::string state_message;
// most recent timestamp where the job entered PROCESSING
int processing_started = 0;
};
// Represents the status of a printer containing the properties printer-state,
// printer-state-reasons, and printer-state-message.
struct PrinterStatus {
struct PrinterReason {
// Standardized reasons from RFC2911.
enum Reason {
UNKNOWN_REASON,
NONE,
MEDIA_NEEDED,
MEDIA_JAM,
MOVING_TO_PAUSED,
PAUSED,
SHUTDOWN,
CONNECTING_TO_DEVICE,
TIMED_OUT,
STOPPING,
STOPPED_PARTLY,
TONER_LOW,
TONER_EMPTY,
SPOOL_AREA_FULL,
COVER_OPEN,
INTERLOCK_OPEN,
DOOR_OPEN,
INPUT_TRAY_MISSING,
MEDIA_LOW,
MEDIA_EMPTY,
OUTPUT_TRAY_MISSING,
OUTPUT_AREA_ALMOST_FULL,
OUTPUT_AREA_FULL,
MARKER_SUPPLY_LOW,
MARKER_SUPPLY_EMPTY,
MARKER_WASTE_ALMOST_FULL,
MARKER_WASTE_FULL,
FUSER_OVER_TEMP,
FUSER_UNDER_TEMP,
OPC_NEAR_EOL,
OPC_LIFE_OVER,
DEVELOPER_LOW,
DEVELOPER_EMPTY,
INTERPRETER_RESOURCE_UNAVAILABLE
};
// Severity of the state-reason.
enum Severity { UNKNOWN_SEVERITY, REPORT, WARNING, ERROR };
Reason reason;
Severity severity;
};
// printer-state
ipp_pstate_t state;
// printer-state-reasons
std::vector<PrinterReason> reasons;
// printer-state-message
std::string message;
};
// Specifies classes of jobs.
enum JobCompletionState {
COMPLETED, // only completed jobs
PROCESSING // only jobs that are being processed
};
// Extracts structured job information from the |response| for |printer_id|.
// Extracted jobs are added to |jobs|.
void ParseJobsResponse(ipp_t* response,
const std::string& printer_id,
std::vector<CupsJob>* jobs);
// Attempts to extract a PrinterStatus object out of |response|.
void ParsePrinterStatus(ipp_t* response, PrinterStatus* printer_status);
// Attempts to retrieve printer status using connection |http| for |printer_id|.
// Returns true if succcssful and updates the fields in |printer_status| as
// appropriate. Returns false if the request failed.
bool GetPrinterStatus(http_t* http,
const std::string& printer_id,
PrinterStatus* printer_status);
// Attempts to retrieve job information using connection |http| for the printer
// named |printer_id|. Retrieves at most |limit| jobs. If |completed| then
// completed jobs are retrieved. Otherwise, jobs that are currently in progress
// are retrieved. Results are added to |jobs| if the operation was successful.
bool GetCupsJobs(http_t* http,
const std::string& printer_id,
int limit,
JobCompletionState completed,
std::vector<CupsJob>* jobs);
} // namespace printing
#endif // PRINTING_BACKEND_CUPS_JOBS_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment