blob: 593284927fa08fa5bc39efc81355c6192509a4cd [file] [log] [blame]
// Copyright 2019 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 <string>
#include <utility>
#include <vector>
#include "base/containers/queue.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromeos/printing/ppd_cache.h"
#include "chromeos/printing/ppd_metadata_manager.h"
#include "chromeos/printing/ppd_provider_v3.h"
#include "chromeos/printing/printer_config_cache.h"
namespace chromeos {
namespace {
// The exact queue length at which PpdProvider will begin to post
// failure callbacks in response to its queue-able public methods.
// Arbitrarily chosen.
// See also: struct MethodDeferralContext
constexpr size_t kMethodDeferralLimit = 20;
// Helper struct for PpdProviderImpl. Allows PpdProviderImpl to defer
// its public method calls, which PpdProviderImpl will do when the
// PpdMetadataManager is not ready to deal with locale-sensitive PPD
// metadata.
//
// Note that the semantics of this struct demand two things of the
// deferable public methods of PpdProviderImpl:
// 1. that they check for its presence and
// 2. that they check its |current_method_is_being_failed| member to
// prevent infinite re-enqueueing of public methods once the queue
// is full.
struct MethodDeferralContext {
MethodDeferralContext() = default;
~MethodDeferralContext() = default;
// This struct is not copyable.
MethodDeferralContext(const MethodDeferralContext&) = delete;
MethodDeferralContext& operator=(const MethodDeferralContext&) = delete;
// Pops the first entry from |deferred_methods| and synchronously runs
// it with the intent to fail it.
void FailOneEnqueuedMethod() {
DCHECK(!current_method_is_being_failed);
// Explicitly activates the failure codepath for whatever public
// method of PpdProviderImpl that we'll now Run().
current_method_is_being_failed = true;
std::move(deferred_methods.front()).Run();
deferred_methods.pop();
current_method_is_being_failed = false;
}
// Dequeues and posts all |deferred_methods| onto our sequence.
void FlushAndPostAll() {
while (!deferred_methods.empty()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(deferred_methods.front()));
deferred_methods.pop();
}
}
bool IsFull() { return deferred_methods.size() >= kMethodDeferralLimit; }
// This bool is checked during execution of a queue-able public method
// of PpdProviderImpl. If it is true, then
// 1. the current queue-able public method was previously enqueued,
// 2. the deferral queue is full, and so
// 3. the current queue-able public method was posted for the sole
// purpose of being _failed_, and should not be re-enqueued.
bool current_method_is_being_failed = false;
base::queue<base::OnceCallback<void()>> deferred_methods;
};
// This class implements the PpdProvider interface for the v3 metadata
// (https://crbug.com/888189).
class PpdProviderImpl : public PpdProvider {
public:
PpdProviderImpl(base::StringPiece browser_locale,
const base::Version& current_version,
scoped_refptr<PpdCache> cache,
std::unique_ptr<PpdMetadataManager> metadata_manager,
std::unique_ptr<PrinterConfigCache> config_cache)
: browser_locale_(std::string(browser_locale)),
version_(current_version),
cache_(cache),
deferral_context_(std::make_unique<MethodDeferralContext>()),
metadata_manager_(std::move(metadata_manager)),
config_cache_(std::move(config_cache)),
file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
TryToGetMetadataManagerLocale();
}
void ResolveManufacturers(ResolveManufacturersCallback cb) override {
// Do we need
// 1. to defer this method?
// 2. to fail this method (which was already previously deferred)?
if (deferral_context_) {
if (deferral_context_->current_method_is_being_failed) {
auto failure_cb = base::BindOnce(
std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR,
std::vector<std::string>());
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(failure_cb));
return;
}
if (deferral_context_->IsFull()) {
deferral_context_->FailOneEnqueuedMethod();
DCHECK(!deferral_context_->IsFull());
}
base::OnceCallback<void()> this_method =
base::BindOnce(&PpdProviderImpl::ResolveManufacturers,
weak_factory_.GetWeakPtr(), std::move(cb));
deferral_context_->deferred_methods.push(std::move(this_method));
return;
}
// TODO(crbug.com/888189): implement this.
}
void ResolvePrinters(const std::string& manufacturer,
ResolvePrintersCallback cb) override {
// Caller must not call ResolvePrinters() before a successful reply
// from ResolveManufacturers(). ResolveManufacturers() cannot have
// been successful if the |deferral_context_| still exists.
if (deferral_context_) {
auto failure_cb = base::BindOnce(
std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR,
ResolvedPrintersList());
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(failure_cb));
}
// TODO(crbug.com/888189): implement this.
}
// This method depends on
// 1. forward indices and
// 2. USB indices,
// neither of which are locale-sensitive.
void ResolvePpdReference(const PrinterSearchData& search_data,
ResolvePpdReferenceCallback cb) override {
// TODO(crbug.com/888189): implement this.
}
// This method depends on a successful prior call to
// ResolvePpdReference().
void ResolvePpd(const Printer::PpdReference& reference,
ResolvePpdCallback cb) override {
// TODO(crbug.com/888189): implement this.
}
void ReverseLookup(const std::string& effective_make_and_model,
ReverseLookupCallback cb) override {
// Do we need
// 1. to defer this method?
// 2. to fail this method (which was already previously deferred)?
if (deferral_context_) {
if (deferral_context_->current_method_is_being_failed) {
auto failure_cb = base::BindOnce(
std::move(cb), PpdProvider::CallbackResultCode::INTERNAL_ERROR, "",
"");
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(failure_cb));
return;
}
if (deferral_context_->IsFull()) {
deferral_context_->FailOneEnqueuedMethod();
DCHECK(!deferral_context_->IsFull());
}
base::OnceCallback<void()> this_method = base::BindOnce(
&PpdProviderImpl::ReverseLookup, weak_factory_.GetWeakPtr(),
effective_make_and_model, std::move(cb));
deferral_context_->deferred_methods.push(std::move(this_method));
return;
}
// TODO(crbug.com/888189): implement this.
}
// This method depends on forward indices, which are not
// locale-sensitive.
void ResolvePpdLicense(base::StringPiece effective_make_and_model,
ResolvePpdLicenseCallback cb) override {
// TODO(crbug.com/888189): implement this.
}
protected:
~PpdProviderImpl() override = default;
private:
// Readies |metadata_manager_| to call methods which require a
// successful callback from PpdMetadataManager::GetLocale().
//
// |this| is largely useless if its |metadata_manager_| is not ready
// to traffick in locale-sensitive PPD metadata, so we want this
// method to eventually succeed.
void TryToGetMetadataManagerLocale() {
auto callback =
base::BindOnce(&PpdProviderImpl::OnMetadataManagerLocaleGotten,
weak_factory_.GetWeakPtr());
metadata_manager_->GetLocale(std::move(callback));
}
// Callback fed to PpdMetadataManager::GetLocale().
void OnMetadataManagerLocaleGotten(bool succeeded) {
if (!succeeded) {
TryToGetMetadataManagerLocale();
return;
}
deferral_context_->FlushAndPostAll();
// It is no longer necessary to defer public method calls.
deferral_context_.reset();
}
// Locale of the browser, as returned by
// BrowserContext::GetApplicationLocale();
const std::string browser_locale_;
// Current version used to filter restricted ppds
const base::Version version_;
// Provides PPD storage on-device.
scoped_refptr<PpdCache> cache_;
// Used to
// 1. to determine if |this| should defer locale-sensitive public
// method calls and
// 2. to defer those method calls, if necessary.
// These deferrals are only necessary before the |metadata_manager_|
// is ready to deal with locale-sensitive PPD metadata. This member is
// reset once deferrals are unnecessary.
std::unique_ptr<MethodDeferralContext> deferral_context_;
// Interacts with and controls PPD metadata.
std::unique_ptr<PpdMetadataManager> metadata_manager_;
// Fetches PPDs from the Chrome OS Printing team's serving root.
std::unique_ptr<PrinterConfigCache> config_cache_;
// Where to run disk operations.
const scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
base::WeakPtrFactory<PpdProviderImpl> weak_factory_{this};
};
// Copied directly from v2 PpdProvider
// TODO(crbug.com/888189): figure out where this fits in the big picture
bool PpdReferenceIsWellFormed(const Printer::PpdReference& reference) {
int filled_fields = 0;
if (!reference.user_supplied_ppd_url.empty()) {
++filled_fields;
GURL tmp_url(reference.user_supplied_ppd_url);
if (!tmp_url.is_valid() || !tmp_url.SchemeIs("file")) {
LOG(ERROR) << "Invalid url for a user-supplied ppd: "
<< reference.user_supplied_ppd_url
<< " (must be a file:// URL)";
return false;
}
}
if (!reference.effective_make_and_model.empty()) {
++filled_fields;
}
// All effective-make-and-model strings should be lowercased, since v2.
// Since make-and-model strings could include non-Latin chars, only checking
// that it excludes all upper-case chars A-Z.
if (!std::all_of(reference.effective_make_and_model.begin(),
reference.effective_make_and_model.end(),
[](char c) -> bool { return !base::IsAsciiUpper(c); })) {
return false;
}
// Should have exactly one non-empty field.
return filled_fields == 1;
}
} // namespace
PrinterSearchData::PrinterSearchData() = default;
PrinterSearchData::PrinterSearchData(const PrinterSearchData& other) = default;
PrinterSearchData::~PrinterSearchData() = default;
// static; copied directly from v2 PpdProvider
// TODO(crbug.com/888189): figure out where this fits in the big picture
std::string PpdProvider::PpdReferenceToCacheKey(
const Printer::PpdReference& reference) {
DCHECK(PpdReferenceIsWellFormed(reference));
// The key prefixes here are arbitrary, but ensure we can't have an (unhashed)
// collision between keys generated from different PpdReference fields.
if (!reference.effective_make_and_model.empty()) {
return std::string("em:") + reference.effective_make_and_model;
} else {
return std::string("up:") + reference.user_supplied_ppd_url;
}
}
// static
scoped_refptr<PpdProvider> PpdProvider::Create(
const std::string& browser_locale,
network::mojom::URLLoaderFactory* loader_factory,
scoped_refptr<PpdCache> ppd_cache,
const base::Version& current_version,
const PpdProvider::Options& options) {
NOTREACHED(); // TODO(crbug.com/888189): deprecate this Create().
return nullptr;
}
// free function; _not_ static
scoped_refptr<PpdProvider> CreateV3Provider(
base::StringPiece browser_locale,
const base::Version& current_version,
scoped_refptr<PpdCache> cache,
std::unique_ptr<PpdMetadataManager> metadata_manager,
std::unique_ptr<PrinterConfigCache> config_cache) {
return base::MakeRefCounted<PpdProviderImpl>(
browser_locale, current_version, cache, std::move(metadata_manager),
std::move(config_cache));
}
} // namespace chromeos