Skip to content

Commit

Permalink
Implement IPP Get-Jobs and Get-Printer-Attributes requests.
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
skau authored and Commit bot committed Mar 11, 2017
1 parent dece487 commit a26877d
Show file tree
Hide file tree
Showing 8 changed files with 680 additions and 96 deletions.
89 changes: 68 additions & 21 deletions chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);

Expand All @@ -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) {
std::string key = CupsPrintJob::GetUniqueId(job.printer_id, job.id);
const auto& entry = jobs_.find(key);
if (entry != jobs_.end()) {
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())
continue;

CupsPrintJob* print_job = entry->second.get();

// Update a job we're tracking.
Expand All @@ -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";
for (const auto& entry : jobs_) {
// Declare all lost jobs errors.
JobStateUpdated(entry.second.get(), CupsPrintJob::State::STATE_ERROR);
}
PurgeJobs();
}
}

jobs_.clear();
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,
Expand Down
18 changes: 15 additions & 3 deletions chrome/browser/chromeos/printing/cups_print_job_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand All @@ -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_;
Expand Down
2 changes: 2 additions & 0 deletions printing/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
100 changes: 47 additions & 53 deletions printing/backend/cups_connection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
30 changes: 11 additions & 19 deletions printing/backend/cups_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions printing/backend/cups_ipp_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_

Expand Down
Loading

0 comments on commit a26877d

Please sign in to comment.